$code ) ) ); exit; } /** * Call the MCPServerStatus WordPress API. * * @param string $method HTTP method. * @param string $path Path beginning with /api/wp/. * @param array|null $body JSON body. * @return array{code:int,body:array}|WP_Error */ function mcpss_api( $method, $path, $body = null ) { $token = mcpss_get_token(); if ( '' === $token ) { return new WP_Error( 'mcpss_not_connected', __( 'Not connected.', 'mcp-server-status' ) ); } $args = array( 'method' => $method, 'timeout' => 10, 'headers' => array( 'Authorization' => 'Bearer ' . $token, 'Accept' => 'application/json', ), ); if ( null !== $body ) { $args['headers']['Content-Type'] = 'application/json'; $args['body'] = wp_json_encode( $body ); } $res = wp_remote_request( mcpss_service_base() . $path, $args ); if ( is_wp_error( $res ) ) { return $res; } $code = (int) wp_remote_retrieve_response_code( $res ); $data = json_decode( wp_remote_retrieve_body( $res ), true ); if ( 401 === $code && '/api/wp/disconnect' !== $path ) { // Token was revoked from the MCPServerStatus dashboard. mcpss_forget_connection(); } return array( 'code' => $code, 'body' => is_array( $data ) ? $data : array(), ); } /** * Sanitize the read-only account summary returned by the API. * * @param mixed $a Raw account data. * @return array|null */ function mcpss_sanitize_account( $a ) { if ( ! is_array( $a ) || empty( $a['plan'] ) ) { return null; } $base = mcpss_service_base(); $url = isset( $a['manageUrl'] ) ? esc_url_raw( (string) $a['manageUrl'], array( 'https', 'http' ) ) : ''; if ( '' === $url || 0 !== strpos( $url, $base ) ) { $url = $base . '/dashboard'; } $interval = isset( $a['interval'] ) ? sanitize_key( (string) $a['interval'] ) : ''; return array( 'plan' => sanitize_key( (string) $a['plan'] ), 'planName' => isset( $a['planName'] ) ? sanitize_text_field( (string) $a['planName'] ) : '', 'interval' => in_array( $interval, array( 'monthly', 'yearly' ), true ) ? $interval : '', 'status' => isset( $a['status'] ) ? sanitize_key( (string) $a['status'] ) : '', 'renewsAt' => isset( $a['renewsAt'] ) && is_string( $a['renewsAt'] ) ? sanitize_text_field( $a['renewsAt'] ) : '', 'cancelsAt' => isset( $a['cancelsAt'] ) && is_string( $a['cancelsAt'] ) ? sanitize_text_field( $a['cancelsAt'] ) : '', 'monitorsUsed' => isset( $a['monitorsUsed'] ) ? absint( $a['monitorsUsed'] ) : 0, 'monitorsLimit' => isset( $a['monitorsLimit'] ) ? absint( $a['monitorsLimit'] ) : 0, 'manageUrl' => $url, ); } /** * Format an ISO date from the API in the site's date format. * * @param string $iso ISO 8601 date. * @return string */ function mcpss_format_date( $iso ) { $ts = $iso ? strtotime( $iso ) : false; return $ts ? wp_date( get_option( 'date_format' ), $ts ) : ''; } /** * Monitors + plan for the connected account, cached for 5 minutes. * * @param bool $force Skip the cache. * @return array|WP_Error */ function mcpss_get_monitors( $force = false ) { if ( ! $force ) { $cached = get_transient( MCPSS_MONITORS_TRANSIENT ); if ( is_array( $cached ) ) { return $cached; } } $res = mcpss_api( 'GET', '/api/wp/monitors' ); if ( is_wp_error( $res ) ) { return $res; } if ( 200 !== $res['code'] ) { $msg = isset( $res['body']['error'] ) ? (string) $res['body']['error'] : __( 'Could not load your monitors.', 'mcp-server-status' ); return new WP_Error( 'mcpss_api', $msg ); } $out = array( 'plan' => isset( $res['body']['plan'] ) && is_array( $res['body']['plan'] ) ? $res['body']['plan'] : array(), 'account' => mcpss_sanitize_account( isset( $res['body']['account'] ) ? $res['body']['account'] : null ), 'monitors' => array(), ); if ( isset( $res['body']['monitors'] ) && is_array( $res['body']['monitors'] ) ) { foreach ( $res['body']['monitors'] as $m ) { if ( ! is_array( $m ) || empty( $m['publicId'] ) ) { continue; } $out['monitors'][] = array( 'id' => isset( $m['id'] ) && preg_match( '/^[0-9a-f-]{36}$/i', (string) $m['id'] ) ? strtolower( (string) $m['id'] ) : '', 'publicId' => mcpss_parse_monitor( $m['publicId'] ), 'name' => isset( $m['name'] ) ? sanitize_text_field( $m['name'] ) : '', 'url' => isset( $m['url'] ) ? esc_url_raw( $m['url'] ) : '', 'status' => isset( $m['status'] ) ? sanitize_key( $m['status'] ) : 'unknown', 'lastCheckedAt' => isset( $m['lastCheckedAt'] ) ? sanitize_text_field( (string) $m['lastCheckedAt'] ) : '', 'uptimePct' => isset( $m['uptimePct'] ) && is_numeric( $m['uptimePct'] ) ? (float) $m['uptimePct'] : null, ); } } set_transient( MCPSS_MONITORS_TRANSIENT, $out, 5 * MINUTE_IN_SECONDS ); return $out; } /** * Step 1: send the admin to mcpserverstatus.com to approve the connection. */ function mcpss_action_connect() { mcpss_guard( 'mcpss_connect' ); $state = wp_generate_password( 32, false, false ); set_transient( 'mcpss_state_' . get_current_user_id(), $state, 15 * MINUTE_IN_SECONDS ); $url = mcpss_service_base() . '/connect/wordpress?' . http_build_query( array( 'site' => home_url( '/' ), 'return' => mcpss_settings_url(), 'state' => $state, ), '', '&', PHP_QUERY_RFC3986 ); wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- intentional redirect to the fixed MCPServerStatus service. exit; } /** * Step 2: mcpserverstatus.com redirects back with ?mcpss_token=…&state=…. * The one-time state (stored in a per-user transient) acts as the CSRF nonce. */ function mcpss_handle_connect_return() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- verified with the one-time state below. if ( ! isset( $_GET['page'] ) || 'mcpserverstatus' !== $_GET['page'] || ( ! isset( $_GET['mcpss_token'] ) && ! isset( $_GET['mcpss_error'] ) ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } $key = 'mcpss_state_' . get_current_user_id(); $expected = get_transient( $key ); $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : ''; delete_transient( $key ); if ( ! is_string( $expected ) || '' === $expected || ! hash_equals( $expected, $state ) ) { mcpss_back( 'state_error' ); } if ( isset( $_GET['mcpss_error'] ) ) { mcpss_back( 'cancelled' ); } $token = sanitize_text_field( wp_unslash( $_GET['mcpss_token'] ) ); // phpcs:enable if ( ! preg_match( '/^mcpss_[A-Za-z0-9_-]{20,200}$/', $token ) ) { mcpss_back( 'token_error' ); } update_option( MCPSS_TOKEN_OPTION, $token, false ); delete_transient( MCPSS_MONITORS_TRANSIENT ); mcpss_back( 'connected' ); } /** * Disconnect: revoke the token on mcpserverstatus.com and delete it locally. */ function mcpss_action_disconnect() { mcpss_guard( 'mcpss_disconnect' ); if ( mcpss_is_connected() ) { mcpss_api( 'POST', '/api/wp/disconnect' ); } mcpss_forget_connection(); mcpss_back( 'disconnected' ); } /** * Save a monitor public ID as the badge monitor. * * @param string $public_id Monitor public ID. */ function mcpss_use_monitor( $public_id ) { $s = mcpss_get_settings(); $s['monitor'] = mcpss_parse_monitor( $public_id ); update_option( MCPSS_OPTION, $s ); } /** * Pick one of the account's monitors for the badge. */ function mcpss_action_select_monitor() { mcpss_guard( 'mcpss_select_monitor' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by check_admin_referer() in mcpss_guard(). $id = isset( $_POST['mcpss_monitor'] ) ? mcpss_parse_monitor( sanitize_text_field( wp_unslash( $_POST['mcpss_monitor'] ) ) ) : ''; $data = mcpss_get_monitors(); $ok = false; if ( ! is_wp_error( $data ) ) { foreach ( $data['monitors'] as $m ) { if ( $m['publicId'] === $id ) { $ok = true; } } } if ( ! $ok ) { mcpss_back( 'error', __( 'That monitor was not found in your account.', 'mcp-server-status' ) ); } mcpss_use_monitor( $id ); mcpss_back( 'selected' ); } /** * Add a new server to the account, then select it. */ function mcpss_action_add_monitor() { mcpss_guard( 'mcpss_add_monitor' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified by check_admin_referer() in mcpss_guard(). $url = isset( $_POST['mcpss_new_url'] ) ? esc_url_raw( trim( sanitize_text_field( wp_unslash( $_POST['mcpss_new_url'] ) ) ), array( 'https' ) ) : ''; $name = isset( $_POST['mcpss_new_name'] ) ? sanitize_text_field( wp_unslash( $_POST['mcpss_new_name'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( '' === $url ) { mcpss_back( 'error', __( 'Enter your MCP server URL. It must start with https://.', 'mcp-server-status' ) ); } $res = mcpss_api( 'POST', '/api/wp/monitors', array( 'url' => $url, 'name' => $name, ) ); if ( is_wp_error( $res ) ) { mcpss_back( 'error', $res->get_error_message() ); } if ( 201 !== $res['code'] || empty( $res['body']['monitor']['publicId'] ) ) { $msg = isset( $res['body']['error'] ) ? (string) $res['body']['error'] : __( 'Could not add the server.', 'mcp-server-status' ); mcpss_back( 'error', $msg ); } mcpss_use_monitor( (string) $res['body']['monitor']['publicId'] ); delete_transient( MCPSS_MONITORS_TRANSIENT ); mcpss_back( 'added' ); } /** * Refresh the cached monitor list. */ function mcpss_action_refresh() { mcpss_guard( 'mcpss_refresh' ); $cached = get_transient( MCPSS_MONITORS_TRANSIENT ); if ( is_array( $cached ) && isset( $cached['monitors'] ) && function_exists( 'mcpss_flush_history' ) ) { mcpss_flush_history( $cached['monitors'] ); } delete_transient( MCPSS_MONITORS_TRANSIENT ); mcpss_back( 'refreshed' ); } /** * Admin notice for the last action. */ function mcpss_render_notice() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. $code = isset( $_GET['mcpss_msg'] ) ? sanitize_key( wp_unslash( $_GET['mcpss_msg'] ) ) : ''; if ( '' === $code ) { return; } $messages = array( 'connected' => array( 'success', __( 'Connected to MCPServerStatus. Pick a monitor below.', 'mcp-server-status' ) ), 'disconnected' => array( 'success', __( 'Disconnected from MCPServerStatus.', 'mcp-server-status' ) ), 'selected' => array( 'success', __( 'Badge monitor updated.', 'mcp-server-status' ) ), 'added' => array( 'success', __( 'Server added and selected for the badge. Its first check runs shortly.', 'mcp-server-status' ) ), 'refreshed' => array( 'success', __( 'Monitor list refreshed.', 'mcp-server-status' ) ), 'cancelled' => array( 'warning', __( 'Connection was cancelled.', 'mcp-server-status' ) ), 'state_error' => array( 'error', __( 'The connection request expired or did not match. Please click Connect again.', 'mcp-server-status' ) ), 'token_error' => array( 'error', __( 'MCPServerStatus returned an invalid token. Please try again.', 'mcp-server-status' ) ), ); $key = 'mcpss_notice_' . get_current_user_id(); $extra = get_transient( $key ); delete_transient( $key ); if ( isset( $messages[ $code ] ) ) { list( $type, $text ) = $messages[ $code ]; } else { $type = 'error'; $text = __( 'Something went wrong.', 'mcp-server-status' ); } if ( is_string( $extra ) && '' !== $extra ) { $text = $extra; } printf( '

%2$s

', esc_attr( $type ), esc_html( $text ) ); } /** * Human label for a status. * * @param string $status up|degraded|down|unknown. * @return string */ function mcpss_status_label( $status ) { $labels = array( 'up' => __( 'Operational', 'mcp-server-status' ), 'degraded' => __( 'Degraded', 'mcp-server-status' ), 'down' => __( 'Down', 'mcp-server-status' ), ); return isset( $labels[ $status ] ) ? $labels[ $status ] : __( 'Unknown (waiting for first check)', 'mcp-server-status' ); } /** * Small hidden-fields helper for admin-post forms. * * @param string $action Action name. */ function mcpss_post_fields( $action ) { echo ''; wp_nonce_field( $action ); }