> page_hash => query rows. */ private static $by_page = array(); /** * @var bool|null Null until the first lookup decides how to read the store: * true when the whole current period fitted in one read, * false when pages are looked up individually. */ private static $read_whole_period = null; /** Register only while Optimiser still owns traditional search evidence. */ public static function boot() { if ( ! self::content_pal_available() || self::external_search_ops_authoritative() ) { return; } add_filter( 'bcp_content_audit_performance_signals', array( __CLASS__, 'supply' ), 10, 3 ); } /** * Answer Content Pal's request for one page's observed search performance. * * Re-check authority at supply time as a defensive boundary: boot ordering or * a test/plugin changing the manifest later in the request must never make * legacy rows win over the authoritative Search Ops store. * * @param array $signals Signals collected so far. * @param int $post_id Post being audited. * @param string $url Public URL of that post. * @return array Signals, with Search Console query evidence merged in. */ public static function supply( $signals, $post_id, $url ) { $signals = is_array( $signals ) ? $signals : array(); if ( self::external_search_ops_authoritative() ) { return $signals; } $url = (string) $url; if ( '' === $url ) { $url = (string) get_permalink( (int) $post_id ); } if ( '' === $url ) { return $signals; } $rows = self::rows_for_url( $url ); if ( ! $rows ) { return $signals; } $queries = array(); foreach ( $rows as $row ) { $queries[] = array( 'query' => (string) $row['query_text'], 'clicks' => (float) $row['clicks'], 'impressions' => (float) $row['impressions'], 'position' => (float) $row['position'], ); } $signals['search_console'] = array( 'source' => 'ai-o-optimiser', 'period' => 'current', 'queries' => $queries, ); return $signals; } /** * Look up the queries recorded against one URL. * * @param string $url Page URL. * @return array */ private static function rows_for_url( $url ) { if ( ! self::store_available() ) { return array(); } $hash = hash( 'sha256', untrailingslashit( esc_url_raw( $url ) ) ); if ( null === self::$read_whole_period ) { self::$read_whole_period = AI_O_Optimiser_GSC_DB::count( 'current' ) <= self::MAX_ROWS; if ( self::$read_whole_period ) { self::index_rows( AI_O_Optimiser_GSC_DB::top_rows( 'current', self::MAX_ROWS ) ); } } if ( ! self::$read_whole_period && ! array_key_exists( $hash, self::$by_page ) ) { self::$by_page[ $hash ] = array(); self::index_rows( AI_O_Optimiser_GSC_DB::rows_for_page( 'current', $hash, self::MAX_QUERIES_PER_PAGE ) ); } return isset( self::$by_page[ $hash ] ) ? self::$by_page[ $hash ] : array(); } /** @param mixed $rows Store rows, highest impressions first. */ private static function index_rows( $rows ) { foreach ( is_array( $rows ) ? $rows : array() as $row ) { if ( empty( $row['page_hash'] ) || ! isset( $row['query_text'] ) ) { continue; } $page = (string) $row['page_hash']; if ( isset( self::$by_page[ $page ] ) && count( self::$by_page[ $page ] ) >= self::MAX_QUERIES_PER_PAGE ) { continue; } self::$by_page[ $page ][] = $row; } } /** Make sure the legacy Search Console store class is loaded. */ private static function store_available() { if ( ! class_exists( 'AI_O_Optimiser_GSC_DB' ) && defined( 'AI_O_Optimiser_PLUGIN_DIR' ) ) { $file = AI_O_Optimiser_PLUGIN_DIR . 'includes/class-ai-o-optimiser-gsc-db.php'; if ( is_readable( $file ) ) { require_once $file; } } return class_exists( 'AI_O_Optimiser_GSC_DB' ); } /** Read the public ownership contract without coupling to Content Pal classes. */ private static function external_search_ops_authoritative() { if ( ! class_exists( 'AI_O_Optimiser_Search_Ops_Ownership' ) && defined( 'AI_O_Optimiser_PLUGIN_DIR' ) ) { $file = AI_O_Optimiser_PLUGIN_DIR . 'includes/class-ai-o-optimiser-search-ops-ownership.php'; if ( is_readable( $file ) ) { require_once $file; } } return class_exists( 'AI_O_Optimiser_Search_Ops_Ownership' ) && AI_O_Optimiser_Search_Ops_Ownership::external_authoritative(); } /** Detect an installed AI-O Content Pal. */ private static function content_pal_available() { return class_exists( 'BCP_Config' ) || class_exists( 'BCP_Plugin' ) || defined( 'BCP_MENU_SLUG' ); } }