publish; // Scoped to post + page (not just 'post') to match the "Pages without a..." // labels below and the Bulk Editor's own "All Posts & Pages" filter target. $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 IN ('post','page') 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 IN ('post','page') 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 IN ('post','page') AND p.post_status = 'publish' AND (pm.meta_value IS NULL OR pm.meta_value = '')" ); // Only a real 404 or a failed connection counts as "broken" - see // AWBSEO_Broken_Links::is_broken_code() for why 401/403/429/etc are // excluded (bot/rate-limit blocks that aren't actually broken links). $broken_sql = AWBSEO_Broken_Links::broken_sql(); $broken_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}awb_broken_links WHERE {$broken_sql} AND fix_status != 'fixed'" ); $redirect_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}awb_redirects" ); $total_issues = $no_kw_count + $no_desc_count + $no_title_count + $broken_count; // ── Content Decay & Cannibalization ─────────────────────────────────────── $decaying = AWBSEO_Content_Decay::get_decaying_posts( 5 ); $conflicts = AWBSEO_Keyword_Cannibalization::get_all_groups(); ?>

.

get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}awb_rank_tracker" ); $hub_report_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}awb_client_reports" ); $hub_badge = function( $text, $type = 'ok' ) { $colors = [ 'ok' => [ 'bg' => '#d4edda', 'fg' => '#155724' ], 'warn' => [ 'bg' => '#fff3cd', 'fg' => '#856404' ], 'bad' => [ 'bg' => '#f8d7da', 'fg' => '#721c24' ], 'info' => [ 'bg' => '#cce5ff', 'fg' => '#004085' ], ]; $c = $colors[ $type ] ?? $colors['ok']; return '' . esc_html( $text ) . ''; }; $pro_badge = 'PRO'; $hub_cats = [ [ 'label' => __( 'Your Pages & Content', 'adworksbid-seo' ), 'color' => '#2271b1', 'badge' => $hub_onpage_issues > 0 /* translators: %d = number of posts */ ? $hub_badge( sprintf( _n( '%d post needs attention', '%d posts need attention', $hub_onpage_issues, 'adworksbid-seo' ), $hub_onpage_issues ), 'warn' ) : $hub_badge( __( 'All optimized', 'adworksbid-seo' ), 'ok' ), 'tools' => [ [ 'label' => __( 'Edit Titles & Descriptions', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-bulk' ) ], [ 'label' => __( 'AI Content Writer', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-ai-content-writer' ), 'pro' => true ], [ 'label' => __( 'Local Business Visibility', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-local-seo' ) ], ], ], [ 'label' => __( 'Site Health', 'adworksbid-seo' ), 'color' => '#1d7a5c', 'badge' => $broken_count > 0 /* translators: %d = number of broken links */ ? $hub_badge( sprintf( _n( '%d broken link', '%d broken links', $broken_count, 'adworksbid-seo' ), $broken_count ), 'bad' ) : $hub_badge( __( 'No issues found', 'adworksbid-seo' ), 'ok' ), 'tools' => [ [ 'label' => __( 'Schema Markup', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-schema' ) ], [ 'label' => __( 'Sitemap', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-sitemap' ) ], [ 'label' => __( 'Redirects', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-redirects' ) ], [ 'label' => __( 'Broken Links', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-broken-links' ) ], [ 'label' => __( 'Instant Indexing', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-instant-indexing' ) ], [ 'label' => __( 'Page Speed', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-cwv' ), 'pro' => true ], [ 'label' => __( 'WooCommerce SEO', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-woocommerce' ), 'pro' => true ], ], ], [ 'label' => __( 'Rankings & Traffic', 'adworksbid-seo' ), 'color' => '#6d3fc0', 'badge' => ( $hub_decay_count > 0 || $hub_conflict_count > 0 ) /* translators: %1$d = fading count, %2$d = competing count */ ? $hub_badge( sprintf( __( '%1$d fading, %2$d competing', 'adworksbid-seo' ), $hub_decay_count, $hub_conflict_count ), 'warn' ) : ( $hub_kw_count > 0 /* translators: %d = keyword count */ ? $hub_badge( sprintf( _n( '%d keyword tracked', '%d keywords tracked', $hub_kw_count, 'adworksbid-seo' ), $hub_kw_count ), 'info' ) : $hub_badge( __( 'No issues', 'adworksbid-seo' ), 'ok' ) ), 'tools' => [ [ 'label' => __( 'Rank Tracker', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-rank-tracker' ), 'pro' => true ], [ 'label' => __( 'Fading Content', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-content-decay' ) ], [ 'label' => __( 'Competing Pages', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-cannibalization' ) ], ], ], [ 'label' => __( 'AI Search Visibility', 'adworksbid-seo' ), 'color' => '#c2600a', 'badge' => defined( 'AWBSEO_PRO_VERSION' ) ? $hub_badge( __( 'Active', 'adworksbid-seo' ), 'ok' ) : $hub_badge( __( 'PRO features', 'adworksbid-seo' ), 'info' ), 'tools' => [ [ 'label' => __( 'AI Search Optimizer', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-geo-aeo' ), 'pro' => true ], [ 'label' => __( 'AI Visibility Tools', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-ai-search' ), 'pro' => true ], [ 'label' => __( 'Smart Link Structure', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-link-architecture' ) ], [ 'label' => __( 'Revenue Intelligence', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-revenue-intelligence' ) ], ], ], [ 'label' => __( 'Reports & Insights', 'adworksbid-seo' ), 'color' => '#871a37', 'badge' => $hub_report_count > 0 /* translators: %d = report count */ ? $hub_badge( sprintf( _n( '%d report created', '%d reports created', $hub_report_count, 'adworksbid-seo' ), $hub_report_count ), 'info' ) : $hub_badge( __( 'PRO feature', 'adworksbid-seo' ), 'info' ), 'tools' => [ [ 'label' => __( 'Client Reports', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-client-reports' ), 'pro' => true ], [ 'label' => __( 'Settings', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-settings' ) ], [ 'label' => __( 'License', 'adworksbid-seo' ), 'url' => admin_url( 'admin.php?page=awbseo-license' ) ], ], ], ]; ?>

get_row( $wpdb->prepare( "SELECT COUNT(*) AS analyzed_count, AVG(CAST(pm.meta_value AS UNSIGNED)) AS avg_score FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = %s WHERE p.post_type IN ('post','page') AND p.post_status = 'publish' AND pm.meta_value != ''", $seo_score_meta_key ) ); $seo_score_analyzed = (int) ( $seo_score_row->analyzed_count ?? 0 ); $seo_score_avg = ( $seo_score_row && null !== $seo_score_row->avg_score ) ? (float) $seo_score_row->avg_score : null; $seo_score_total_pages = (int) wp_count_posts( 'page' )->publish; $seo_score_total_content = $total_posts + $seo_score_total_pages; $seo_score_unanalyzed = max( 0, $seo_score_total_content - $seo_score_analyzed ); $awb_health_score = null !== $seo_score_avg ? (int) round( $seo_score_avg ) : ( $total_posts > 0 ? max( 0, (int) round( ( ( $total_posts * 4 ) - $total_issues ) / ( $total_posts * 4 ) * 100 ) ) : 100 ); $awb_score_color = $awb_health_score >= 80 ? '#16a34a' : ( $awb_health_score >= 50 ? '#d97706' : '#dc2626' ); $awb_health_checks = [ [ 'label' => __( 'Broken links', 'adworksbid-seo' ), 'count' => $broken_count, 'severity' => $broken_count ? 'bad' : 'ok', 'url' => admin_url( 'admin.php?page=awbseo-broken-links' ), 'action' => __( 'Fix', 'adworksbid-seo' ), ], [ 'label' => __( 'Pages without a search title', 'adworksbid-seo' ), 'count' => $no_title_count, 'severity' => $no_title_count ? 'warn' : 'ok', 'url' => admin_url( 'admin.php?page=awbseo-bulk&awb_filter=missing_title' ), 'action' => __( 'Fix', 'adworksbid-seo' ), ], [ 'label' => __( 'Pages without a search description', 'adworksbid-seo' ), 'count' => $no_desc_count, 'severity' => $no_desc_count ? 'warn' : 'ok', 'url' => admin_url( 'admin.php?page=awbseo-bulk&awb_filter=missing_description' ), 'action' => __( 'Fix', 'adworksbid-seo' ), ], [ 'label' => __( 'Pages without a target keyword', 'adworksbid-seo' ), 'count' => $no_kw_count, 'severity' => $no_kw_count ? 'warn' : 'ok', 'url' => admin_url( 'admin.php?page=awbseo-bulk&awb_filter=missing_keyword' ), 'action' => __( 'Fix', 'adworksbid-seo' ), ], [ 'label' => __( 'Active redirects', 'adworksbid-seo' ), 'count' => $redirect_count, 'severity' => 'ok', 'url' => admin_url( 'admin.php?page=awbseo-redirects' ), 'action' => __( 'View', 'adworksbid-seo' ), ], ]; $awb_sev_colors = [ 'bad' => '#dc2626', 'warn' => '#d97706', 'ok' => '#16a34a', ]; $awb_sev_icons = [ 'bad' => '🔴', 'warn' => '🟠', 'ok' => '✅', ]; // ── Inline-style fallbacks ─────────────────────────────────────────────── // This page's real layout (card borders, the score ring, the checklist // rows, the tables below) lives in admin/assets/css/admin.css - it's // meant to look like a designed dashboard, not a plain document. On at // least one live site this session, that stylesheet failed to apply // (likely a caching/optimization plugin or a theme/plugin conflict on // that install) while every value still rendered correctly, just as // unstyled stacked text - the same content, none of the layout. Rather // than only chase that one site's asset pipeline, the key structural // styles below are now also written inline, mirroring admin.css exactly, // so this page still looks right even on a site where the stylesheet // doesn't load for whatever reason. The CSS classes stay in place too, // so nothing changes on a normal, correctly-loading site. $awb_card_style = 'background:#fff;border:1px solid var(--awb-border,#e2e8f0);border-radius:12px;padding:22px 26px;margin-bottom:18px;box-shadow:0 1px 2px rgba(15,23,42,.05),0 2px 6px rgba(15,23,42,.06)'; $awb_badge_colors = [ 'good' => [ 'bg' => '#d1fae5', 'fg' => '#065f46', 'bd' => '#6ee7b7' ], 'warn' => [ 'bg' => '#fef9c3', 'fg' => '#854d0e', 'bd' => '#fcd34d' ], 'bad' => [ 'bg' => '#fee2e2', 'fg' => '#991b1b', 'bd' => '#fca5a5' ], 'neutral' => [ 'bg' => '#f1f5f9', 'fg' => '#475569', 'bd' => '#cbd5e1' ], 'high' => [ 'bg' => '#fee2e2', 'fg' => '#991b1b', 'bd' => '#fca5a5' ], 'medium' => [ 'bg' => '#fef9c3', 'fg' => '#854d0e', 'bd' => '#fcd34d' ], 'low' => [ 'bg' => '#f1f5f9', 'fg' => '#475569', 'bd' => '#cbd5e1' ], ]; $awb_badge_style = function ( $variant ) use ( $awb_badge_colors ) { $c = $awb_badge_colors[ $variant ] ?? $awb_badge_colors['neutral']; return 'display:inline-flex;align-items:center;padding:4px 11px;border-radius:99px;font-size:11px;font-weight:700;letter-spacing:.3px;white-space:nowrap;background:' . $c['bg'] . ';color:' . $c['fg'] . ';border:1px solid ' . $c['bd']; }; ?>

%
= 80 ) { esc_html_e( 'Good', 'adworksbid-seo' ); } elseif ( $awb_health_score >= 50 ) { esc_html_e( 'Needs work', 'adworksbid-seo' ); } else { esc_html_e( 'Critical', 'adworksbid-seo' ); } ?>
0 ) : ?>

$chk ) : $pct = $total_posts > 0 ? min( 100, round( $chk['count'] / $total_posts * 100 ) ) : 0; $color = $awb_sev_colors[ $chk['severity'] ]; $icon = $awb_sev_icons[ $chk['severity'] ]; $count_label = 'ok' === $chk['severity'] && 0 === $chk['count'] ? '' . esc_html__( 'All clear', 'adworksbid-seo' ) . '' : '' . (int) $chk['count'] . ''; $awb_hc_last = ( $awb_chk_index === count( $awb_health_checks ) - 1 ); $awb_hc_style = 'display:grid;grid-template-columns:26px 1fr 80px 80px 56px;align-items:center;gap:12px;padding:12px 0;font-size:13px;' . ( $awb_hc_last ? '' : 'border-bottom:1px solid var(--awb-line,#f1f5f9)' ); ?>
0 || __( 'View', 'adworksbid-seo' ) === $chk['action'] ) : ?> →
0 ) { /* translators: %d = number of broken links */ $awb_a_text = _n( '%d broken link is sending visitors and crawlers to a dead page. Fix this first, broken links hurt both user experience and how much of your site AI crawlers and Google bother to read.', '%d broken links are sending visitors and crawlers to dead pages. Fix these first, broken links hurt both user experience and how much of your site AI crawlers and Google bother to read.', $broken_count, 'adworksbid-seo' ); $awb_analysis_items[] = [ 'severity' => 'bad', 'text' => sprintf( esc_html( $awb_a_text ), $broken_count ), 'url' => admin_url( 'admin.php?page=awbseo-broken-links' ), 'cta' => __( 'Fix broken links', 'adworksbid-seo' ), ]; } if ( $seo_score_unanalyzed > 0 && $seo_score_total_content > 0 && ( $seo_score_unanalyzed / $seo_score_total_content ) >= 0.2 ) { $awb_analysis_items[] = [ 'severity' => 'warn', 'text' => sprintf( /* translators: 1: number of unanalyzed pages, 2: current score percentage */ esc_html__( 'Your %2$d%% score only reflects the pages that have been scored so far. %1$d pages are still unanalyzed, so the real site-wide picture could be better or worse than what you see here.', 'adworksbid-seo' ), $seo_score_unanalyzed, $awb_health_score ), 'url' => '#awb-run-seo-analysis', 'cta' => __( 'Score remaining pages now', 'adworksbid-seo' ), ]; } if ( $no_title_count > 0 ) { /* translators: %d = number of pages without a search title */ $awb_a_text = _n( '%d page is using an auto-generated title instead of one you wrote. Write your own so you control what shows up in Google and AI answers, instead of leaving it to guesswork.', '%d pages are using auto-generated titles instead of ones you wrote. Write your own so you control what shows up in Google and AI answers, instead of leaving it to guesswork.', $no_title_count, 'adworksbid-seo' ); $awb_analysis_items[] = [ 'severity' => 'warn', 'text' => sprintf( esc_html( $awb_a_text ), $no_title_count ), 'url' => admin_url( 'admin.php?page=awbseo-bulk&awb_filter=missing_title' ), 'cta' => __( 'Add missing titles', 'adworksbid-seo' ), ]; } if ( $no_desc_count > 0 ) { /* translators: %d = number of pages without a search description */ $awb_a_text = _n( '%d page has no search description, so Google and AI engines write their own summary for it, usually a less compelling one than you would write.', '%d pages have no search description, so Google and AI engines write their own summary for them, usually a less compelling one than you would write.', $no_desc_count, 'adworksbid-seo' ); $awb_analysis_items[] = [ 'severity' => 'warn', 'text' => sprintf( esc_html( $awb_a_text ), $no_desc_count ), 'url' => admin_url( 'admin.php?page=awbseo-bulk&awb_filter=missing_description' ), 'cta' => __( 'Add missing descriptions', 'adworksbid-seo' ), ]; } if ( $no_kw_count > 0 ) { /* translators: %d = number of pages without a target keyword */ $awb_a_text = _n( '%d page has no target keyword set, so the analyzer skips its relevance checks entirely and can\'t tell you how well that page is actually optimized.', '%d pages have no target keyword set, so the analyzer skips their relevance checks entirely and can\'t tell you how well those pages are actually optimized.', $no_kw_count, 'adworksbid-seo' ); $awb_analysis_items[] = [ 'severity' => 'warn', 'text' => sprintf( esc_html( $awb_a_text ), $no_kw_count ), 'url' => admin_url( 'admin.php?page=awbseo-bulk&awb_filter=missing_keyword' ), 'cta' => __( 'Set target keywords', 'adworksbid-seo' ), ]; } // Why the score isn't higher: the checklist above only covers // missing title/description/keyword, but the score is built from // 15 on-page checks. Without this, a site can show "All clear" on // every row and still sit at 79% with no explanation. Tallies the // failing checks stored per page by analyze_and_cache(). $awb_failed_rows = $wpdb->get_col( $wpdb->prepare( "SELECT pm.meta_value FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = %s WHERE p.post_type IN ('post','page') AND p.post_status = 'publish'", AWBSEO_Post_Columns::META_FAILED ) ); $awb_failed_tally = []; foreach ( (array) $awb_failed_rows as $awb_row ) { foreach ( array_filter( explode( ',', (string) $awb_row ) ) as $awb_check_id ) { $awb_failed_tally[ $awb_check_id ] = ( $awb_failed_tally[ $awb_check_id ] ?? 0 ) + 1; } } // The checklist rows above already cover a missing keyword. unset( $awb_failed_tally['focus_keyword'] ); if ( $awb_failed_tally && null !== $seo_score_avg && $awb_health_score < 100 ) { arsort( $awb_failed_tally ); $awb_check_labels = [ 'title_length' => __( 'search title too short or too long', 'adworksbid-seo' ), 'desc_length' => __( 'meta description too short or too long', 'adworksbid-seo' ), 'kw_in_title' => __( 'keyword missing from the search title', 'adworksbid-seo' ), 'kw_in_desc' => __( 'keyword missing from the meta description', 'adworksbid-seo' ), 'kw_in_slug' => __( 'keyword missing from the URL', 'adworksbid-seo' ), 'kw_in_intro' => __( 'keyword missing from the first paragraph', 'adworksbid-seo' ), 'kw_in_headings' => __( 'keyword missing from subheadings', 'adworksbid-seo' ), 'kw_density' => __( 'keyword used too rarely or too often', 'adworksbid-seo' ), 'content_length' => __( 'thin content', 'adworksbid-seo' ), 'has_images' => __( 'no images', 'adworksbid-seo' ), 'img_alt' => __( 'images missing alt text', 'adworksbid-seo' ), 'internal_links' => __( 'fewer than 2 internal links', 'adworksbid-seo' ), 'external_links' => __( 'no external links', 'adworksbid-seo' ), 'clean_url' => __( 'URL not clean', 'adworksbid-seo' ), ]; $awb_top_parts = []; foreach ( array_slice( $awb_failed_tally, 0, 3, true ) as $awb_check_id => $awb_check_count ) { $awb_top_parts[] = sprintf( /* translators: 1: issue description, 2: number of pages */ _n( '%1$s (%2$d page)', '%1$s (%2$d pages)', $awb_check_count, 'adworksbid-seo' ), $awb_check_labels[ $awb_check_id ] ?? str_replace( '_', ' ', $awb_check_id ), $awb_check_count ); } $awb_lowest_url = $seo_score_total_pages > $total_posts ? admin_url( 'edit.php?post_type=page&orderby=awbseo_score&order=asc' ) : admin_url( 'edit.php?orderby=awbseo_score&order=asc' ); $awb_analysis_items[] = [ 'severity' => 'warn', 'text' => sprintf( /* translators: 1: current score percentage, 2: list of the most common issues with page counts */ esc_html__( 'Why %1$d%% and not higher: the checklist above only covers titles, descriptions, and keywords, but the score also checks content quality on every page. The most common issues pulling it down: %2$s.', 'adworksbid-seo' ), $awb_health_score, esc_html( implode( '; ', $awb_top_parts ) ) ), 'url' => $awb_lowest_url, 'cta' => __( 'See lowest-scoring pages', 'adworksbid-seo' ), ]; } ?>

$awb_item ) : $awb_a_color = $awb_sev_colors[ $awb_item['severity'] ]; $awb_a_last = ( $awb_ai_index === count( $awb_analysis_items ) - 1 ); ?>

→

🎉

__( 'HIGH', 'adworksbid-seo' ), 'medium' => __( 'MEDIUM', 'adworksbid-seo' ), 'low' => __( 'LOW', 'adworksbid-seo' ), ]; foreach ( $decaying as $item ) : $dash_risk_label = $dash_risk_labels[ $item['risk'] ] ?? strtoupper( $item['risk'] ); ?>
post_title ); ?>

__( 'Analyzing…', 'adworksbid-seo' ), 'starting' => __( 'Starting scan…', 'adworksbid-seo' ), 'progress' => __( '%1$d of %2$d pages scanned', 'adworksbid-seo' ), 'complete' => __( 'Scan complete! Reloading…', 'adworksbid-seo' ), 'run_analysis' => __( 'Run SEO Analysis', 'adworksbid-seo' ), 'nothing_to_scan' => __( 'No published pages to scan yet.', 'adworksbid-seo' ), 'failed' => __( 'Could not complete the analysis. Please try again.', 'adworksbid-seo' ), ] ) . ';'; $awb_dash_js .= <<<'AWBDASHJS' jQuery(function($){ var POLL_INTERVAL = 2000; var pollTimer = null; function awbFormatProgress(done, total){ return _awbDashStr.progress.replace('%1$d', done).replace('%2$d', total); } function awbResetButton(){ $('#awb-run-seo-analysis').prop('disabled', false).text(_awbDashStr.run_analysis); $('#awb-seo-analysis-status').text(''); $('#awb-seo-analysis-progress').hide(); } function awbStopPolling(){ if (pollTimer) { clearTimeout(pollTimer); pollTimer = null; } } // The scan itself runs server-side (see class-post-columns.php's // process_cron_step()) - this only reads its progress to update the // bar. If this tab closes or the admin navigates elsewhere, polling // just stops; the scan keeps going regardless, and a Heartbeat-driven // notification (admin/class-admin.php) picks it up from any other // open wp-admin page once it's done. function awbPollStatus(){ $.post(AWBSEO.ajax_url, { action: 'awbseo_seo_analysis_status', nonce: AWBSEO.nonce }) .done(function(res){ if (!res || !res.success || !res.data) { pollTimer = setTimeout(awbPollStatus, POLL_INTERVAL); return; } var d = res.data; var total = d.total || 0; var done = d.done || 0; var pct = total > 0 ? Math.min(100, Math.round((done / total) * 100)) : (d.status === 'done' ? 100 : 0); $('#awb-seo-analysis-bar').css('width', pct + '%'); if (d.status === 'done') { $('#awb-seo-analysis-status').text(_awbDashStr.complete); awbStopPolling(); setTimeout(function(){ location.reload(); }, 900); return; } $('#awb-seo-analysis-status').text(total > 0 ? awbFormatProgress(done, total) : _awbDashStr.starting); pollTimer = setTimeout(awbPollStatus, POLL_INTERVAL); }) .fail(function(){ pollTimer = setTimeout(awbPollStatus, POLL_INTERVAL); }); } // "Run SEO Analysis" / "Score remaining pages now" text links elsewhere // on the card start the same scan as the button. $(document).on('click', 'a[href="#awb-run-seo-analysis"]', function(e){ e.preventDefault(); var $btn = $('#awb-run-seo-analysis'); $('html, body').animate({ scrollTop: $btn.offset().top - 120 }, 200); if (!$btn.prop('disabled')) { $btn.trigger('click'); } }); $('#awb-run-seo-analysis').on('click', function(){ // Ask for notification permission now, while there's still a user // gesture to ask on - if granted, a native notification can fire // later even after navigating away from this page. if (window.Notification && Notification.permission === 'default') { Notification.requestPermission(); } var $btn = $(this); $btn.prop('disabled', true).text(_awbDashStr.analyzing); $('#awb-seo-analysis-status').text(_awbDashStr.starting); $('#awb-seo-analysis-progress').show(); $('#awb-seo-analysis-bar').css('width', '0%'); $.post(AWBSEO.ajax_url, { action: 'awbseo_seo_analysis_start', nonce: AWBSEO.nonce }) .done(function(res){ if (!res || !res.success) { awbResetButton(); alert(_awbDashStr.failed); return; } var total = res.data.total || 0; if (total === 0) { $('#awb-seo-analysis-status').text(_awbDashStr.nothing_to_scan); awbResetButton(); return; } awbPollStatus(); }) .fail(function(){ awbResetButton(); alert(_awbDashStr.failed); }); }); }); AWBDASHJS; wp_add_inline_script( 'awbseo-admin', $awb_dash_js ); ?>