__( 'Get search trend', 'ai-eshop-optimizer' ), 'description' => __( 'Search Console history from `wp_aieo_gsc_daily`: the top search terms or pages for a window, each with the SAME window a year earlier, so seasonal movement can be told apart from real decline. Use this for "is this term growing", "what is seasonal" and "what did we rank for last summer". Returns nothing useful until Search Console has been connected long enough to hold a year, and says so rather than implying a collapse.', 'ai-eshop-optimizer' ), 'category' => \AIEO\Claude\Bootstrap::CATEGORY, 'input_schema' => self::input_schema(), 'output_schema' => array( 'type' => 'object' ), 'execute_callback' => array( __CLASS__, 'execute' ), 'permission_callback' => array( __CLASS__, 'permission_check' ), 'meta' => array( 'annotations' => array( 'readonly' => true, 'idempotent' => true, ), 'show_in_rest' => true, ), ) ); } public static function permission_check( $input = null ) { unset( $input ); return current_user_can( 'manage_woocommerce' ); } private static function input_schema() { return array( 'default' => array(), 'type' => 'object', 'properties' => array( 'dimension' => array( 'type' => 'string', 'default' => 'query', 'enum' => array( 'query', 'page' ), 'description' => "'query' for search terms, 'page' for URLs.", ), 'days' => array( 'type' => 'integer', 'default' => 30, 'minimum' => 1, 'maximum' => 180, 'description' => 'Window size in days, ending where Search Console data ends.', ), 'term' => array( 'type' => 'string', 'default' => '', 'description' => 'One exact term or URL to return a full daily series for. Empty returns the leaderboard with year-on-year instead.', ), 'limit' => array( 'type' => 'integer', 'default' => 25, 'minimum' => 1, 'maximum' => 200, 'description' => 'Leaderboard size.', ), ), ); } public static function execute( $input ) { if ( ! class_exists( 'AIEO_GSC_History' ) ) { require_once AIEO_DIR . 'includes/seo/class-aieo-gsc-history.php'; } $input = is_array( $input ) ? $input : array(); $dim = ( isset( $input['dimension'] ) && 'page' === $input['dimension'] ) ? 'page' : 'query'; $days = isset( $input['days'] ) ? max( 1, min( 180, (int) $input['days'] ) ) : 30; $term = isset( $input['term'] ) ? (string) $input['term'] : ''; $limit = isset( $input['limit'] ) ? max( 1, min( 200, (int) $input['limit'] ) ) : 25; if ( ! \AIEO_GSC_History::table_exists() ) { return array( 'rows' => array(), 'note' => __( 'No Search Console history yet. It is built by the nightly sync once Search Console is connected, and bin/post-deploy-gsc-history-backfill.php fills it from Google\'s own 16-month window in one pass.', 'ai-eshop-optimizer' ), ); } $cov = \AIEO_GSC_History::coverage(); if ( empty( $cov['last'] ) ) { return array( 'rows' => array(), 'coverage' => $cov, 'note' => __( 'The history table exists but is empty, so Search Console has not synced yet.', 'ai-eshop-optimizer' ), ); } $to = (string) $cov['last']; $from = gmdate( 'Y-m-d', strtotime( $to ) - ( $days - 1 ) * DAY_IN_SECONDS ); if ( '' !== $term ) { return array( 'dimension' => $dim, 'term' => $term, 'window' => array( 'from' => $from, 'to' => $to ), 'coverage' => $cov, 'series' => \AIEO_GSC_History::series( $dim, $term, $from, $to ), ); } $yoy = \AIEO_GSC_History::year_on_year( $dim, $from, $to, $limit ); $rows = array(); foreach ( (array) $yoy['rows'] as $r ) { $now = (int) $r['impressions']; $ly = ( null === $r['ly_impressions'] ) ? null : (int) $r['ly_impressions']; $rows[] = array( 'value' => (string) $r['dim_value'], 'impressions' => $now, 'clicks' => (int) $r['clicks'], 'position' => (float) $r['position'], 'ly_impressions' => $ly, 'ly_clicks' => ( null === $r['ly_clicks'] ) ? null : (int) $r['ly_clicks'], 'yoy_pct' => ( $ly && $ly > 0 ) ? round( 100 * ( $now - $ly ) / $ly, 1 ) : null, ); } return array( 'dimension' => $dim, 'window' => $yoy['window'], 'last_year' => $yoy['last_year'], 'have_last_year' => (bool) $yoy['have_last_year'], 'coverage' => $cov, 'rows' => $rows, 'note' => empty( $yoy['have_last_year'] ) ? __( 'History does not reach back a year yet, so yoy_pct is null everywhere. That is missing history, NOT a decline — do not report it as one.', 'ai-eshop-optimizer' ) : __( 'Google withholds rare queries, so these will not sum to the property total.', 'ai-eshop-optimizer' ), ); } }