id() ) { return $rows; } $is_shop = ! class_exists( 'AIEO_SEO_Score' ) || 'shop' === AIEO_SEO_Score::site_profile(); $overrides = class_exists( 'AIEO_SEO_Pillars' ) ? (array) AIEO_SEO_Pillars::overrides() : array(); $rec = array( 'defend' => __( 'Strong performer — rank-protect and watch for engagement decay.', 'ai-eshop-optimizer' ), 'intent_mismatch' => $is_shop ? __( 'High traffic but low on-site conversion — the keyword/title likely attracts the wrong intent. Re-target to the query that converts.', 'ai-eshop-optimizer' ) : __( 'High traffic but low engagement — the title/keyword promises something the page doesn’t deliver. Re-angle it to the intent that keeps visitors reading.', 'ai-eshop-optimizer' ), 'hidden_gem' => $is_shop ? __( 'Converts well but under-exposed — promote to cornerstone, add internal links, raise sitemap priority, target higher-volume keywords.', 'ai-eshop-optimizer' ) : __( 'Engages strongly but under-exposed — promote to cornerstone, add internal links, raise sitemap priority, target higher-volume keywords.', 'ai-eshop-optimizer' ), 'prune' => $is_shop ? __( 'Low traffic and low conversion — candidate to merge/noindex and reclaim crawl budget.', 'ai-eshop-optimizer' ) : __( 'Low traffic and low engagement — candidate to refresh, merge or noindex and reclaim crawl budget.', 'ai-eshop-optimizer' ), ); foreach ( $rows as $i => $r ) { $q = (string) $r['quadrant']; if ( isset( $rec[ $q ] ) ) { $rows[ $i ]['recommendation'] = $rec[ $q ]; } $pid = (int) $r['object_id']; if ( 'product' !== (string) $r['object_type'] ) { $rows[ $i ]['pin_state'] = 'auto'; } elseif ( isset( $overrides[ $pid ] ) && 1 === (int) $overrides[ $pid ] ) { $rows[ $i ]['pin_state'] = 'pinned'; } elseif ( isset( $overrides[ $pid ] ) && 0 === (int) $overrides[ $pid ] ) { $rows[ $i ]['pin_state'] = 'excluded'; } else { $rows[ $i ]['pin_state'] = 'pinnable'; } } return $rows; } public function save_cell( $handled, $ctx, $def ) { if ( true === $handled || 'seo_opportunities_grid' !== (string) $ctx['report'] ) { return $handled; } if ( ! class_exists( 'AIEO_SEO_Store' ) ) { return __( 'The SEO store is unavailable on this install.', 'ai-eshop-optimizer' ); } $id = absint( $ctx['row_key'] ); if ( $id <= 0 ) { return __( 'That row does not name an object.', 'ai-eshop-optimizer' ); } $post = get_post( $id ); if ( ! $post ) { return __( 'That object no longer exists.', 'ai-eshop-optimizer' ); } $otype = (string) $post->post_type; if ( 'focus_keyphrase' === $ctx['column'] ) { AIEO_SEO_Store::save( $otype, $id, array( 'focus_keyphrase' => sanitize_text_field( (string) $ctx['value'] ), 'focus_keyphrase_source' => 'behavior', ) ); } elseif ( 'sitemap_priority' === $ctx['column'] ) { AIEO_SEO_Store::save( $otype, $id, array( 'sitemap_priority' => max( 0.0, min( 1.0, (float) $ctx['value'] ) ), ) ); } else { return $handled; } if ( class_exists( 'AIEO_SEO_Score' ) && method_exists( 'AIEO_SEO_Score', 'score_object' ) ) { AIEO_SEO_Score::score_object( $otype, $id ); } return true; } private function guard() { AIEO_DMM::verify_ajax_request(); if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'ai-eshop-optimizer' ) ) ); } } public function ajax_save() { $this->guard(); $patch = static function ( array $fields ) { $out = array(); foreach ( $fields as $key => $spec ) { if ( ! isset( $_POST[ $key ] ) ) { continue; } $v = max( $spec[1], min( $spec[2], (float) wp_unslash( $_POST[ $key ] ) ) ); $out[ $spec[0] ] = empty( $spec[3] ) ? $v : (int) $v; } return $out; }; $corner = $patch( array( 'cw_sales' => array( 'sales', 0, 1 ), 'cw_turnover' => array( 'turnover', 0, 1 ), 'cw_forecast' => array( 'forecast', 0, 1 ), 'cw_links' => array( 'links', 0, 1 ), 'cw_behavior' => array( 'behavior', 0, 1 ), ) ); $opp = $patch( array( 'op_min_views' => array( 'min_views', 1, 100000, true ), 'op_traffic_threshold' => array( 'traffic_threshold', 1, 99, true ), 'op_conversion_threshold' => array( 'conversion_threshold', 1, 99, true ), 'op_conv_cart' => array( 'conv_w_cart', 0, 1 ), 'op_conv_engagement' => array( 'conv_w_engagement', 0, 1 ), 'op_conv_retention' => array( 'conv_w_retention', 0, 1 ), 'op_corner_entry' => array( 'corner_w_entry', 0, 1 ), 'op_corner_onward' => array( 'corner_w_onward', 0, 1 ), 'op_corner_engagement' => array( 'corner_w_engagement', 0, 1 ), 'op_corner_basket' => array( 'corner_w_basket', 0, 1 ), ) ); $seo_patch = array(); if ( ! empty( $corner ) ) { $seo_patch['cornerstone_weights'] = $corner; } if ( ! empty( $opp ) ) { $seo_patch['opportunity_config'] = $opp; } if ( isset( $_POST['pillar_top_pct'] ) ) { $seo_patch['pillar_top_pct'] = max( 0.01, min( 1, (float) wp_unslash( $_POST['pillar_top_pct'] ) ) ); } if ( ! empty( $seo_patch ) ) { AIEO_DMM_Settings::update( array( 'seo' => $seo_patch ) ); } wp_send_json_success( array( 'message' => __( 'Priorities saved. Recompute to apply.', 'ai-eshop-optimizer' ) ) ); } public function ajax_pin() { $this->guard(); $pid = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; $state = isset( $_POST['state'] ) ? sanitize_key( wp_unslash( $_POST['state'] ) ) : ''; if ( $pid <= 0 ) { wp_send_json_error( array( 'message' => __( 'Invalid product.', 'ai-eshop-optimizer' ) ) ); } $saved = AIEO_DMM_Settings::get( 'seo.pillar_overrides', '' ); if ( is_string( $saved ) && '' !== $saved ) { $saved = json_decode( $saved, true ); } $map = is_array( $saved ) ? $saved : array(); if ( 'clear' === $state ) { unset( $map[ $pid ] ); } else { $map[ $pid ] = ( 'pin' === $state ) ? 1 : 0; } AIEO_DMM_Settings::update( array( 'seo' => array( 'pillar_overrides' => wp_json_encode( $map ) ) ) ); if ( class_exists( 'AIEO_SEO_Pillars' ) ) { AIEO_SEO_Pillars::rebuild(); } wp_send_json_success( array( 'message' => __( 'Updated.', 'ai-eshop-optimizer' ), 'override' => isset( $map[ $pid ] ) ? $map[ $pid ] : null ) ); } public function ajax_apply() { $this->guard(); if ( ! class_exists( 'AIEO_SEO_Store' ) ) { wp_send_json_error( array( 'message' => __( 'SEO store unavailable.', 'ai-eshop-optimizer' ) ) ); } $pid = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; $otype = isset( $_POST['object_type'] ) ? sanitize_key( wp_unslash( $_POST['object_type'] ) ) : 'product'; $field = isset( $_POST['field'] ) ? sanitize_key( wp_unslash( $_POST['field'] ) ) : ''; $value = isset( $_POST['value'] ) ? wp_unslash( $_POST['value'] ) : ''; if ( $pid <= 0 || ! post_type_exists( $otype ) ) { wp_send_json_error( array( 'message' => __( 'Invalid object.', 'ai-eshop-optimizer' ) ) ); } if ( 'keyphrase' === $field ) { $kp = sanitize_text_field( (string) $value ); AIEO_SEO_Store::save( $otype, $pid, array( 'focus_keyphrase' => $kp, 'focus_keyphrase_source' => 'behavior', ) ); $msg = __( 'Focus keyphrase set.', 'ai-eshop-optimizer' ); } elseif ( 'sitemap_priority' === $field ) { $p = max( 0.0, min( 1.0, (float) $value ) ); AIEO_SEO_Store::save( $otype, $pid, array( 'sitemap_priority' => $p ) ); $msg = __( 'Sitemap priority set.', 'ai-eshop-optimizer' ); } else { wp_send_json_error( array( 'message' => __( 'Unknown field.', 'ai-eshop-optimizer' ) ) ); } if ( class_exists( 'AIEO_SEO_Score' ) && method_exists( 'AIEO_SEO_Score', 'score_object' ) ) { AIEO_SEO_Score::score_object( $otype, $pid ); } wp_send_json_success( array( 'message' => $msg ) ); } public function ajax_suggest() { $this->guard(); $pid = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0; if ( $pid <= 0 ) { wp_send_json_error( array( 'message' => __( 'Invalid product.', 'ai-eshop-optimizer' ) ) ); } $ptype = (string) get_post_type( $pid ); $terms = ( 'product' === $ptype ) ? self::converting_terms_for_product( $pid, 8 ) : array(); $seen = array(); foreach ( $terms as $t ) { $seen[ mb_strtolower( (string) $t['term'] ) ] = true; } foreach ( AIEO_DFS_Rankings::keywords_for( $ptype ?: 'product', $pid, 8 ) as $k ) { $key = mb_strtolower( (string) $k['keyword'] ); if ( isset( $seen[ $key ] ) || count( $terms ) >= 12 ) { continue; } $seen[ $key ] = true; $terms[] = array( 'term' => (string) $k['keyword'], 'hits' => (int) $k['volume'] ); } wp_send_json_success( array( 'terms' => $terms ) ); } private static function converting_terms_for_product( $pid, $limit = 8 ) { global $wpdb; $sess = $wpdb->prefix . 'aieo_sessions'; if ( (string) $wpdb->get_var( $wpdb->prepare( "SELECT table_name FROM information_schema.TABLES WHERE table_schema = DATABASE() AND table_name = %s", $sess ) ) !== $sess ) { return array(); } $rows = $wpdb->get_results( $wpdb->prepare( "SELECT s.product_name AS term, COUNT(*) AS hits FROM `{$sess}` s JOIN `{$sess}` c ON c.session_id = s.session_id AND c.event_type = 'add_to_cart' AND c.product_id = %d AND c.`timestamp` >= s.`timestamp` WHERE s.event_type = 'search' AND s.product_name <> '' GROUP BY s.product_name ORDER BY hits DESC LIMIT %d", $pid, (int) $limit ), ARRAY_A ); $out = array(); foreach ( (array) $rows as $r ) { $out[] = array( 'term' => (string) $r['term'], 'hits' => (int) $r['hits'] ); } return $out; } public function ajax_recompute() { $this->guard(); $opp = class_exists( 'AIEO_SEO_Opportunity_Scorer' ) ? AIEO_SEO_Opportunity_Scorer::rebuild() : array( 'updated' => 0 ); $pil = class_exists( 'AIEO_SEO_Pillars' ) ? AIEO_SEO_Pillars::rebuild() : array( 'pillars' => 0 ); wp_send_json_success( array( 'message' => __( 'Recomputed.', 'ai-eshop-optimizer' ), 'products' => (int) ( $opp['updated'] ?? 0 ), 'pillars' => (int) ( $pil['pillars'] ?? 0 ), ) ); } public function render_subtab() { $counts = self::counts(); $this->render_template( 'seo/opportunities.php', array( 'enabled' => class_exists( 'AIEO_SEO_Opportunity_Scorer' ) && AIEO_SEO_Opportunity_Scorer::enabled(), 'profile' => class_exists( 'AIEO_SEO_Score' ) ? AIEO_SEO_Score::site_profile() : 'shop', 'corner' => AIEO_SEO_Pillars::weights(), 'top_pct' => AIEO_SEO_Pillars::top_pct(), 'opp_cfg' => AIEO_SEO_Opportunity_Scorer::config(), 'opp_count' => array_sum( $counts ), 'counts' => $counts, 'striking' => AIEO_DFS_Rankings::striking_distance( 20 ), 'nonce' => wp_create_nonce( AIEO_DMM::NONCE_ACTION ), 'ajax' => admin_url( 'admin-ajax.php' ), ) ); } private static function counts() { global $wpdb; $t = $wpdb->prefix . 'aieo_seo_opportunities'; if ( (string) $wpdb->get_var( $wpdb->prepare( "SELECT table_name FROM information_schema.TABLES WHERE table_schema = DATABASE() AND table_name = %s", $t ) ) !== $t ) { return array(); } $out = array(); foreach ( (array) $wpdb->get_results( "SELECT quadrant, COUNT(*) c FROM `{$t}` GROUP BY quadrant", ARRAY_A ) as $r ) { $out[ $r['quadrant'] ] = (int) $r['c']; } return $out; } }