get_site_options(); if ($opts && !is_wp_error($opts) && !empty($opts->siteID)) { $token = (string) $opts->siteID; } } if ($token === '') { return $base . $path; } return sprintf('%s/site/%s/%d%s', $base, rawurlencode($token), $sub, $path); } } /** * Render the four-up dashboard hero stat row. * * Tiles: Site Score · Pages Audited · Pending Fixes · Plan Usage. * * @param string $account_tier free|trial|starter|legacy|enterprise|unknown * @param object $site_options /validate response — must expose ->usageSummary if usage data exists * @param array $extras optional data injected from the page: * - 'site_score' int|null 0-100 * - 'pages_scanned' int|null * - 'pages_audited_ts' int[] per-page last_scan unix ts (2.2) * - 'pending_fixes' int|null * - 'active_fixes' int|null * - 'fixes_url' string * - 'images_total' int|null live alt_text_data count * - 'images_ai_generated' int|null subset with AI alt text * - 'images_pending' int|null AI suggested, awaiting review */ if (!function_exists('aacb_info_tip')) { /** * Tiny accessible "ⓘ" affordance for a stat/card label. * * @param string $text Short plain-text explanation (one or two sentences). */ function aacb_info_tip($text) { $text = (string) $text; if ($text === '') { return ''; } return '' . 'i' . ''; } } if (!function_exists('aacb_ov_dial')) { /** * Score dial for the Site Overview hero. */ function aacb_ov_dial($score, $band) { $has = is_int($score); $val = $has ? max(0, min(100, $score)) : 0; $r = 40; $cx = 52; $cy = 52; $circ = 2 * M_PI * $r; $off = $circ * (1 - $val / 100); $colors = array( 'ok' => '#16a34a', 'primary' => '#2563eb', 'warn' => '#d97706', 'danger' => '#dc2626', 'none' => '#94a3b8', ); $stroke = isset($colors[$band]) ? $colors[$band] : $colors['primary']; ob_start(); ?> / 100 __('Daily', 'allaccessible'), 'weekly' => __('Weekly', 'allaccessible'), 'monthly' => __('Monthly', 'allaccessible'), 'never' => __('On-demand', 'allaccessible'), ); if (isset($freq_word[$freq])) { $cad_freq = $freq_word[$freq]; } elseif (!empty($sched['scanFrequencyDays'])) { $cad_freq = sprintf( /* translators: %s: number of days between scans */ __('Every %s days', 'allaccessible'), number_format_i18n((int) $sched['scanFrequencyDays']) ); } if ($cad_freq !== '' || $last_at) { // 'never' = no recurring schedule (e.g. free tier) — scans are // on-demand, so don't label them "Scheduled". $cad_header = ($freq === 'never') ? __('Scans', 'allaccessible') : __('Scheduled scans', 'allaccessible'); $now = current_time('timestamp'); if ($last_at && ($lt = strtotime($last_at))) { $cad_last = sprintf( /* translators: %s: human time diff, e.g. "2 hours" */ __('%s ago', 'allaccessible'), human_time_diff($lt, $now) ); } else { $cad_last = __('No scans yet', 'allaccessible'); } if ($freq !== 'never' && $next_at && ($nt = strtotime($next_at))) { $cad_next = $nt > $now ? sprintf(/* translators: %s: human time diff */ __('in %s', 'allaccessible'), human_time_diff($now, $nt)) : __('due now', 'allaccessible'); } } } $has_cadence = ($cad_header !== ''); // ── 2.2 Coverage + freshness ───────────────────────────────────── // A score only means something when most of the site has been audited // and the audit is recent. Decide that first; the score band comes second. $local_total = isset($extras['pages_total_local']) ? (int) $extras['pages_total_local'] : 0; $now_ts = current_time('timestamp'); $freq_key = $sched ? (string) ($sched['scanFrequency'] ?? '') : ''; $last_ts = ($sched && !empty($sched['lastScanAt'])) ? strtotime($sched['lastScanAt']) : false; $next_ts = ($sched && !empty($sched['nextScanAt'])) ? strtotime($sched['nextScanAt']) : false; $cad_days = $sched ? (int) ($sched['scanFrequencyDays'] ?? 0) : 0; if ($cad_days <= 0) { $cad_map = array('daily' => 1, 'weekly' => 7, 'monthly' => 30, 'never' => 90); $cad_days = isset($cad_map[$freq_key]) ? $cad_map[$freq_key] : 30; } $coverage_low = ($pages !== null && $local_total >= 4 && $pages < ($local_total / 2)); // 2.2: freshness is per page. A page checked longer than 2× the // cadence ago is stale even if the site-wide crawl is newer; the // most recent page check counts as "last scan" for the caveat. $stale_limit = 2 * $cad_days * DAY_IN_SECONDS; $page_ts = (isset($extras['pages_audited_ts']) && is_array($extras['pages_audited_ts'])) ? array_filter(array_map('intval', $extras['pages_audited_ts'])) : array(); $stale_pages = 0; $oldest_ts = false; foreach ($page_ts as $pts) { if (($now_ts - $pts) > $stale_limit) { $stale_pages++; if ($oldest_ts === false || $pts < $oldest_ts) $oldest_ts = $pts; } if ($last_ts === false || $pts > $last_ts) $last_ts = $pts; } $stale = $page_ts ? ($stale_pages > 0) : ($last_ts && ($now_ts - $last_ts) > $stale_limit); // One clock. With per-page data, the whole site was last covered when // its OLDEST page was checked, so the cadence rows count from there // and agree with the caveat. The schedule's own stamp tracks the last // fix generated, which never moves on a clean site and used to show a // third, unrelated age ("Last 5 months ago" beside "over 6 months"). if ($page_ts) { $covered_ts = min($page_ts); $next_ts = ($freq_key !== 'never') ? $covered_ts + $cad_days * DAY_IN_SECONDS : false; if ($has_cadence) { /* translators: %s: human time diff, e.g. "2 hours" */ $cad_last = sprintf(__('%s ago', 'allaccessible'), human_time_diff($covered_ts, $now_ts)); if ($next_ts) { $cad_next = $next_ts > $now_ts ? sprintf(/* translators: %s: human time diff */ __('in %s', 'allaccessible'), human_time_diff($now_ts, $next_ts)) : __('due now', 'allaccessible'); } } } $overdue = ($next_ts && $next_ts < $now_ts && $freq_key !== 'never'); $needs_scan = ($score === null) || $coverage_low || $stale || $overdue; $open_issues = $pending + $manual; if ($overdue) { $cad_next = __('Overdue', 'allaccessible'); } // Verdict + score band. Ring color always follows the score; the // headline follows coverage. NOTE: never claim "compliant" / // "compliance" — legal-exposure phrasing (see legal language audit). if ($score === null) { $band = 'none'; $accent = 'var(--aacx-slate-400)'; } elseif ($score >= 90) { $band = 'ok'; $accent = 'var(--aacx-ok-600)'; } elseif ($score >= 75) { $band = 'primary'; $accent = 'var(--aacx-primary-600)'; } elseif ($score >= 50) { $band = 'warn'; $accent = 'var(--aacx-warn-600)'; } else { $band = 'danger'; $accent = 'var(--aacx-danger-600)'; } if ($score === null) { $verdict = __('Run your first scan', 'allaccessible'); } elseif ($coverage_low || $stale || $overdue) { $verdict = $open_issues > 0 ? sprintf( /* translators: %s: number of open issues */ _n('%s issue open on the pages we\'ve checked', '%s issues open on the pages we\'ve checked', $open_issues, 'allaccessible'), number_format_i18n($open_issues) ) : __('Nothing open on the pages we\'ve checked', 'allaccessible'); } elseif ($score >= 90) { $verdict = $open_issues > 0 ? sprintf( /* translators: %s: number of open issues */ _n('Looking good, %s issue left', 'Looking good, %s issues left', $open_issues, 'allaccessible'), number_format_i18n($open_issues) ) : __('Looking good. Nothing open.', 'allaccessible'); } elseif ($score >= 75) { $verdict = __('Good, a few fixes to go', 'allaccessible'); } elseif ($score >= 50) { $verdict = __('Needs attention', 'allaccessible'); } else { $verdict = __('Needs work. Start with the top issues', 'allaccessible'); } // Caveat line (amber): why the score should not be trusted yet. $caveat_bits = array(); if ($coverage_low) { $caveat_bits[] = sprintf( /* translators: 1: pages audited, 2: pages on this site */ esc_html__('Only %1$s of %2$s pages on this site have been audited.', 'allaccessible'), '' . esc_html(number_format_i18n($pages)) . '', '' . esc_html(number_format_i18n($local_total)) . '' ); } if ($stale && $stale_pages > 0 && $oldest_ts) { $caveat_bits[] = sprintf( /* translators: 1: stale page count, 2: pages audited, 3: human time diff */ esc_html__('%1$s of %2$s audited pages were last checked over %3$s ago.', 'allaccessible'), '' . esc_html(number_format_i18n($stale_pages)) . '', '' . esc_html(number_format_i18n(max($pages, $stale_pages))) . '', '' . esc_html(human_time_diff($oldest_ts, $now_ts)) . '' ); } elseif ($stale && $last_ts) { $caveat_bits[] = sprintf( /* translators: %s: human time diff */ esc_html__('Last scan was %s ago.', 'allaccessible'), '' . esc_html(human_time_diff($last_ts, $now_ts)) . '' ); } elseif ($overdue) { $caveat_bits[] = esc_html__('Your scheduled scan is overdue.', 'allaccessible'); } if ($caveat_bits) { $caveat_bits[] = esc_html__('Run a scan to get a real picture.', 'allaccessible'); } $caveat_html = implode(' ', $caveat_bits); // Sub-line (pre-escaped segments joined by a muted dot). $dot = ' · '; if ($score === null) { $sub_html = esc_html__('Run a scan and AllAccessible agents will check every page for accessibility issues.', 'allaccessible'); } else { $bits = array(); if ($remediation !== null) { $bits[] = sprintf( /* translators: %s: percentage with wrapper */ esc_html__('%s%% of issues remediated', 'allaccessible'), '' . esc_html(number_format_i18n($remediation)) . '' ); } if (!$coverage_low && $pages !== null && $pages > 0) { $pg = sprintf( esc_html(_n('%s page audited', '%s pages audited', $pages, 'allaccessible')), '' . esc_html(number_format_i18n($pages)) . '' ); if ($pages_url) { $pg .= ' ' . esc_html__('View pages', 'allaccessible') . ' →'; } $bits[] = $pg; } $sub_html = implode($dot, $bits); } // Smart action bar — single most important next step. Hidden when the // scan panel below the hero IS the next step. $act_class = ''; $act_icon = ''; $act_text = ''; $act_btn = ''; if ($has_agentic && $pending > 0) { $act_class = 'aacb-ov-action--attention'; $act_icon = '⚡'; $act_text = sprintf( esc_html(_n('%s fix is ready for your review', '%s fixes are ready for your review', $pending, 'allaccessible')), '' . esc_html(number_format_i18n($pending)) . '' ); $act_btn = '' . esc_html__('Review fixes', 'allaccessible') . ' →'; } elseif ($manual > 0) { $act_class = 'aacb-ov-action--attention'; $act_icon = '⚠'; $act_text = sprintf( esc_html(_n('%s issue needs a manual fix', '%s issues need a manual fix', $manual, 'allaccessible')), '' . esc_html(number_format_i18n($manual)) . '' ); $act_btn = '' . esc_html__('View issues', 'allaccessible') . ' ↓'; } elseif (!$needs_scan) { $act_class = 'aacb-ov-action--clear'; $act_icon = '✓'; $act_text = ($cad_freq !== '') ? sprintf( /* translators: %s: scan cadence, e.g. "Monthly" */ esc_html__('Nothing needs you right now. AllAccessible agents re-check your site on a %s schedule.', 'allaccessible'), '' . esc_html(strtolower($cad_freq)) . '' ) : esc_html__('Nothing needs you right now.', 'allaccessible'); } // Stat tiles — label, value, status in words. Only what we have data for. $tiles = array(); $tiles[] = array( 'label' => __('Fixes live on your site ✦', 'allaccessible'), 'value' => number_format_i18n((int) ($extras['active_fixes'] ?? 0)), 'unit' => '', 'status' => ($pending > 0) ? sprintf(esc_html(_n('%s more waiting for approval', '%s more waiting for approval', $pending, 'allaccessible')), number_format_i18n($pending)) : (((int) ($extras['active_fixes'] ?? 0)) > 0 ? __('Applied by AllAccessible agents', 'allaccessible') : __('Nothing approved yet', 'allaccessible')), 'tone' => ($pending > 0) ? 'warn' : 'ai', ); $tiles[] = array( 'label' => __('Waiting for a person', 'allaccessible'), 'value' => number_format_i18n($open_issues), 'unit' => '', 'status' => $open_issues > 0 ? __('Needs your review', 'allaccessible') : __('Queue is clear', 'allaccessible'), 'tone' => $open_issues > 0 ? 'warn' : 'ok', ); if ($img_total > 0) { $img_pending = (int) ($extras['images_pending'] ?? 0); $tiles[] = array( 'label' => __('Images with alt text', 'allaccessible'), 'value' => number_format_i18n($alt_ai), /* translators: %s: total images */ 'unit' => sprintf(__('of %s', 'allaccessible'), number_format_i18n($img_total)), 'status' => $img_pending > 0 ? sprintf(esc_html(_n('%s suggestion waiting', '%s suggestions waiting', $img_pending, 'allaccessible')), number_format_i18n($img_pending)) : __('All reviewed', 'allaccessible'), 'tone' => $img_pending > 0 ? 'warn' : 'ok', ); } if ($pv_lim) { $pv_pct = (int) min(100, round(((int) $pv_cur / max(1, $pv_lim)) * 100)); $tiles[] = array( 'label' => __('Pageviews this month', 'allaccessible'), 'value' => number_format_i18n((int) $pv_cur), /* translators: %s: pageview allowance */ 'unit' => sprintf(__('of %s', 'allaccessible'), number_format_i18n($pv_lim)), 'status' => sprintf( /* translators: %s: percent used */ __('%s%% of your allowance used', 'allaccessible'), number_format_i18n($pv_pct) ), 'tone' => $pv_pct >= 75 ? 'warn' : 'muted', 'meter' => $pv_pct, ); } ?>

>
'settings', 'compact' => true, 'heading' => $score === null ? __('Run your first scan', 'allaccessible') : __('Scan all pages', 'allaccessible'), 'description' => __('AllAccessible agents find every published page on your site and check each one. It takes a few minutes and runs in the background.', 'allaccessible'), 'hide_schedule' => $has_cadence, 'button_variant' => 'secondary', )); AllAccessible_ScanTriggerPanel::enqueue_inline_script(); ?>