publish; $no_kw_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_awb_seo_focus_keyword' WHERE p.post_type = 'post' AND p.post_status = 'publish' AND (pm.meta_value IS NULL OR pm.meta_value = '')" ); $no_desc_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_awb_seo_description' WHERE p.post_type = 'post' AND p.post_status = 'publish' AND (pm.meta_value IS NULL OR pm.meta_value = '')" ); $no_title_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_awb_seo_title' WHERE p.post_type = 'post' AND p.post_status = 'publish' AND (pm.meta_value IS NULL OR pm.meta_value = '')" ); $broken_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}awb_broken_links WHERE status_code >= 400 OR status_code = 0" ); $total_issues = $no_kw_count + $no_desc_count + $no_title_count + $broken_count; // ── Date range ───────────────────────────────────────────────────────────── $awb_range_presets = [ 7 => '7D', 28 => '28D', 90 => '90D', 180 => '6M' ]; $range_days = isset( $_GET['range'] ) ? (int) $_GET['range'] : 28; $is_awb_date = function ( $d ) { return is_string( $d ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $d ); }; $req_start = isset( $_GET['start'] ) ? sanitize_text_field( wp_unslash( $_GET['start'] ) ) : ''; $req_end = isset( $_GET['end'] ) ? sanitize_text_field( wp_unslash( $_GET['end'] ) ) : ''; $req_custom = $is_awb_date( $req_start ) && $is_awb_date( $req_end ); $awb_dash = AWBSEO_Admin::get_dashboard_data( $range_days, $req_custom ? $req_start : null, $req_custom ? $req_end : null ); $range_days = $awb_dash['range_days']; $use_custom = $awb_dash['use_custom']; $custom_start = $awb_dash['start']; $custom_end = $awb_dash['end']; $range_label = $awb_dash['range_label']; $range_days_display = $awb_dash['range_days_display']; $gsc_on = $awb_dash['gsc_on']; $gsc_stats = $awb_dash['gsc_stats']; $gsc_summary = $awb_dash['gsc_summary']; $gsc_last_error = $awb_dash['gsc_last_error']; $top_pages = $awb_dash['top_pages']; $top_queries = $awb_dash['top_queries']; $bing_on = $awb_dash['bing_on']; $bing_stats = $awb_dash['bing_stats']; $bing_summary = $awb_dash['bing_summary']; $bing_top_pages = $awb_dash['bing_top_pages']; $bing_top_queries = $awb_dash['bing_top_queries']; $awb_sources = []; if ( $gsc_on ) $awb_sources[] = __( 'Google Search Console', 'adworksbid-seo' ); if ( $bing_on ) $awb_sources[] = __( 'Bing Webmaster Tools', 'adworksbid-seo' ); // ── AI Search Visibility (GA4 referral sessions from AI engines) ─────────── // AWBSEO_GA4::get_ai_sessions() / get_sessions_by_source() already existed // (used nowhere in the admin UI until now) - they read the same GA4 // connection Settings already offers and bucket sessions by known AI engine // referrer domains (ChatGPT, Perplexity, Gemini, Copilot, Claude, etc.). // GA4's Data API here only supports "N days ago to today", not an arbitrary // start/end, so a custom date range uses its real day count ($range_days_display) // rather than lining up to the exact custom start date - same limitation // AWBSEO_GA4::get_sessions_by_language() already has. $ga4_on = false; $ai_sessions = []; $ai_total = 0; $ai_pct_of_total = null; $ga4_data_error = ''; if ( class_exists( 'AWBSEO_GA4' ) ) { $ga4_instance = AWBSEO_GA4::get_instance(); if ( $ga4_instance->is_connected() && $ga4_instance->get_property() ) { $ga4_on = true; $ai_days = max( 1, (int) $range_days_display ); $ai_sessions = $ga4_instance->get_ai_sessions( $ai_days ); $ai_total = array_sum( $ai_sessions ); $all_source_rows = $ga4_instance->get_sessions_by_source( $ai_days ); $all_sessions = array_sum( array_column( $all_source_rows, 'sessions' ) ); $ai_pct_of_total = $all_sessions > 0 ? round( ( $ai_total / $all_sessions ) * 100, 1 ) : null; $ga4_data_error = AWBSEO_GA4::get_data_error(); } } ?>
⚠️ .
| % |