get_var( $wpdb->prepare( "SELECT option_value FROM %i WHERE option_name=%s LIMIT 1", $wpdb->options, self::OPTION ) ); if ( '' !== $wpdb->last_error ) { return new \WP_Error( 'selftest_storage', 'Observer self-test state is unavailable.' ); } if ( null === $raw ) { return array( 'state' => 'not_run', 'last_success_at' => null, ); } if ( ! is_string( $raw ) || strlen( $raw ) > 2048 ) { return new \WP_Error( 'selftest_state', 'Observer self-test state is invalid.' ); } $value = json_decode( $raw, true, 5 ); if ( ! is_array( $value ) || ! in_array( $value['state'] ?? '', array( 'pending', 'observed', 'not_observed' ), true ) || ! is_int( $value['expires'] ?? null ) || ! is_int( $value['started_at'] ?? null ) ) { return new \WP_Error( 'selftest_state', 'Observer self-test state is invalid.' ); } return $value; } /** * Persist observer exercise JSON and verify its exact stored projection. * * @param array $value Supported bounded pending or completed exercise state. * * @return bool Whether the native write and read-back equality both succeed. */ private static function write( array $value ): bool { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Atomically write the owned nonautoloaded raw JSON state and verify direct readback; this protocol does not consume native option caches. return false !== $wpdb->query( $wpdb->prepare( "INSERT INTO %i (option_name,option_value,autoload) VALUES (%s,%s,'off') ON DUPLICATE KEY UPDATE option_value=VALUES(option_value),autoload='off'", $wpdb->options, self::OPTION, wp_json_encode( $value ) ) ) && self::read() === $value; } /** * Project current observer exercise status without exposing its stored token digest. * * @param Scope $scope Current operation scope whose native grants are checked. * * @return array|\WP_Error Finite visible status with expired pending tests marked unobserved, or an access error. */ public static function status( Scope $scope ): array|\WP_Error { if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) ) { return new \WP_Error( 'forbidden', 'Observer self-test status denied.' ); } $value = self::read(); if ( is_wp_error( $value ) ) { return $value; } unset( $value['token_hash'] ); if ( 'pending' === ( $value['state'] ?? '' ) && ( $value['expires'] ?? 0 ) <= time() ) { $value['state'] = 'not_observed'; } if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) ) { return new \WP_Error( 'forbidden', 'Observer self-test status authority changed.' ); } return array_intersect_key( $value, array_flip( array( 'state', 'last_success_at', 'started_at', 'expires', 'finished_at', 'evidence_id', 'source_derivation', 'source_quality', 'coverage' ) ) ); } /** * Create and audit an expiring authenticated observer exercise for the current site. * * @param Scope $scope Current operation scope whose native grants are checked. * * @return string|\WP_Error Token-bound local exercise URL, or an authority/storage/audit error. */ public static function begin( Scope $scope ): string|\WP_Error { if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) ) { return new \WP_Error( 'forbidden', 'Observer self-test denied.' ); } $actor = Store::actor_context(); $before = self::read(); if ( is_wp_error( $before ) ) { return $before; } $token = bin2hex( random_bytes( 16 ) ); $state = array( 'state' => 'pending', 'started_at' => time(), 'expires' => time() + 300, 'token_hash' => hash( 'sha256', $token ), 'last_success_at' => $before['last_success_at'] ?? null, ); if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) || ! self::write( $state ) ) { return new \WP_Error( 'selftest_storage', 'Observer self-test could not be started.' ); } if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) || ! Store::capture( array( 'code' => 'configuration.observer_test_started', 'actor_context' => $actor, 'outcome' => 'attempted', 'object_id' => $scope->site_id, 'payload' => array( 'observed_via' => 'logged_ai_observer_selftest', 'facts' => array(), ), ) ) ) { return new \WP_Error( 'selftest_audit', 'Observer test could not be audited; retry after checking storage.' ); } return add_query_arg( array( 'logged_ai_probe' => $token, '_wpnonce' => wp_create_nonce( 'logged_ai_observer_selftest_' . $token ), ), home_url( '/' ) ); } /** * Recognize only the authenticated current-home probe with valid token, nonce and expiry. * * @return bool Whether the current routed request qualifies as this pending observer exercise. */ public static function candidate(): bool { $scope = Scope::current( 'logged_ai_manage_settings' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Reject noncanonical raw token bytes before unslashing; the bounded nonce is cleaned and verified below under current settings authority. if ( 'GET' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) || ! is_string( $_GET['logged_ai_probe'] ?? null ) || ! is_string( $_GET['_wpnonce'] ?? null ) || is_wp_error( $scope ) ) { return false; } $token = sanitize_text_field( wp_unslash( $_GET['logged_ai_probe'] ) ); $nonce = sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Compare only against the bounded inputs above; cleanup must never repair a malformed token or nonce into an accepted exercise. if ( $token !== $_GET['logged_ai_probe'] || $nonce !== $_GET['_wpnonce'] || ! preg_match( '/\A[a-f0-9]{32}\z/D', $token ) || '' === $nonce || strlen( $nonce ) > 128 ) { return false; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- A bounded raw routed target is validated by exact home-path equality below, not repaired into an accepted path. $target = $_SERVER['REQUEST_URI'] ?? null; if ( ! is_string( $target ) || '' === $target || strlen( $target ) > 8192 ) { return false; } $path = explode( '?', $target, 2 )[0]; $home_path = wp_parse_url( home_url( '/' ), PHP_URL_PATH ); if ( ( $home_path ? $home_path : '/' ) !== $path ) { return false; } $state = self::read(); if ( is_wp_error( $state ) || 'pending' !== $state['state'] || $state['expires'] <= time() || ! is_string( $state['token_hash'] ?? null ) || ! hash_equals( $state['token_hash'], hash( 'sha256', $token ) ) || ! wp_verify_nonce( $nonce, 'logged_ai_observer_selftest_' . $token ) ) { return false; } if ( ! $scope->valid_for( 'logged_ai_manage_settings' ) ) { return false; } self::$active = true; self::$pending = $state; self::$origin = $scope; return true; } /** * Compare and swap the same pending test to its observed admission result. * * @param int $id Admitted synthetic request identifier, or zero when no observation was stored. * @param array $context Minimized source derivation and quality facts for the observer result. * * @return void */ public static function complete( int $id, array $context ): void { if ( ! self::$active || null === self::$pending || null === self::$origin || ! self::$origin->valid_for( 'logged_ai_manage_settings' ) ) { return; } $current = self::read(); if ( is_wp_error( $current ) || $current !== self::$pending ) { return; } $state = self::$pending; $state['state'] = $id > 0 ? 'observed' : 'not_observed'; $state['token_hash'] = null; $state['finished_at'] = time(); $state['evidence_id'] = $id ? $id : null; if ( $id > 0 ) { $state['last_success_at'] = time(); } $state['source_derivation'] = $context['derivation']; $state['source_quality'] = $context['quality']; $state['coverage'] = 'this_authenticated_routed_request_only'; if ( ! self::$origin->valid_for( 'logged_ai_manage_settings' ) ) { return; } // Compare-and-swap preserves a newer test started by another worker. global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Compare and swap the exact pending self-test preimage after current settings authority; an option cache update cannot preserve a concurrently newer test. $wpdb->query( $wpdb->prepare( "UPDATE %i SET option_value=%s WHERE option_name=%s AND option_value=%s", $wpdb->options, wp_json_encode( $state ), self::OPTION, wp_json_encode( self::$pending ) ) ); } /** * Register authenticated exercise return navigation and diagnostic-state retention. * * @return void */ public static function boot(): void { add_action( 'template_redirect', static function () { if ( self::$active && null !== self::$origin && self::$origin->valid_for( 'logged_ai_manage_settings' ) ) { nocache_headers(); wp_safe_redirect( admin_url( 'admin.php?page=logged-ai-request-source' ) ); exit; } }, -999 ); add_action( 'logged_ai_request_retention', static function () { $state = self::read(); if ( is_array( $state ) && ( $state['started_at'] ?? time() ) < time() - max( 1, (int) Settings::get()['diagnostic_days'] ) * DAY_IN_SECONDS ) { delete_option( self::OPTION ); } }, 100 ); } }