0 ? $client->get_manifest_summary(true, $status, 60) : $client->get_manifest_summary(false, $status); $err = is_wp_error($summary) ? $summary->get_error_message() : null; // Normalize to an array immediately: on API failure get_manifest_summary // returns a WP_Error, and the array subscripts below (e.g. line ~118 // $summary['countsByStatus']) FATAL on "object of type WP_Error as array" // before any is_array() check on the subscript result can run. Mirror the // $agg guard. Empty array → page renders the empty/first-run state. if (!is_array($summary)) { $summary = array(); } $tier = (string) $client->get_subscription_tier(); if ($tier === '') $tier = 'free'; $isFree = ($tier === 'free'); $site_options_obj = $client->get_site_options(); $features_list = array(); if (is_object($site_options_obj) && isset($site_options_obj->features) && is_array($site_options_obj->features)) { $features_list = $site_options_obj->features; } $has_agentic = in_array('agentic_fixes_approve', $features_list, true); $is_legacy_v1 = ($tier === 'legacy') || (!$has_agentic && !$isFree); AllAccessible_Debug::console('AgenticFixesPage', array( 'status_tab' => $status, 'tier' => $tier, 'is_free' => $isFree, 'is_legacy_v1' => $is_legacy_v1, 'has_agentic_feature'=> $has_agentic, 'features_list' => $features_list, 'summary_is_error' => is_wp_error($summary), 'summary_error' => $err, 'summary_keys' => is_array($summary) ? array_keys($summary) : null, 'summary' => is_wp_error($summary) ? null : $summary, )); if ($is_legacy_v1) { self::render_legacy_upgrade_page(); return; } $agg = $client->get_audit_aggregation(); if (is_wp_error($agg) || !is_array($agg)) { $agg = null; } $hero_site_score = null; $hero_remediation_score = null; $hero_pages_scanned = null; $hero_is_live_estimate = false; if ($agg && !empty($agg['hasData'])) { $hero_site_score = isset($agg['overallScore']) ? (float) $agg['overallScore'] : null; $hero_remediation_score = isset($agg['remediationScore']) ? (float) $agg['remediationScore'] : null; $hero_pages_scanned = isset($agg['totalPagesScanned']) ? (int) $agg['totalPagesScanned'] : (isset($agg['coverage']['totalPagesScanned']) ? (int) $agg['coverage']['totalPagesScanned'] : null); $hero_is_live_estimate = !empty($agg['isLiveEstimate']); } $extras_hero = array( 'site_score' => $hero_site_score, 'remediation_score' => $hero_remediation_score, 'pages_scanned' => $hero_pages_scanned, 'is_live_estimate' => $hero_is_live_estimate, 'fixes_url' => admin_url('admin.php?page=aacb-agentic-fixes'), ); $counts = is_array($summary['countsByStatus'] ?? null) ? $summary['countsByStatus'] : array(); $groups = is_array($summary['groups'] ?? null) ? $summary['groups'] : array(); $pending = (int) ($counts['draft'] ?? 0); $approved = (int) ($counts['approved'] ?? 0); $reverted = (int) ($counts['reverted'] ?? 0); // Served signal from the summary endpoint (last time the widget received a manifest). null/absent = never delivered. $live_raw = is_array($summary['liveSignal'] ?? null) ? $summary['liveSignal'] : null; $live_at = ($live_raw && !empty($live_raw['at'])) ? (int) $live_raw['at'] : 0; if ($live_at > 100000000000) { $live_at = intdiv($live_at, 1000); } // ms -> s guard $live_age_s = $live_at > 0 ? max(0, time() - $live_at) : null; $live_state = $approved === 0 ? 'none' : ($live_age_s === null ? 'waiting' : ($live_age_s < DAY_IN_SECONDS ? 'live' : 'stale')); $live_labels = AllAccessible_AgenticFixes_Labels::live_state(); // Still waiting for the first delivery: don't pin that state in the 30-min cache. if ($live_state === 'waiting') { $client->shorten_manifest_summary_cache($status, 90); } $show_first_run_hint = ($status === 'draft' && $approved === 0 && $reverted === 0); ?>

→

= 90) { $score_status = __('Nothing open on checked pages', 'allaccessible'); $score_mod = 'ok'; } elseif ($score_int >= 75) { $score_status = __('A few fixes to go', 'allaccessible'); $score_mod = ''; } else { $score_status = __('Needs attention', 'allaccessible'); $score_mod = 'warn'; } $tiles[] = array( 'label' => __('Score', 'allaccessible'), 'value' => esc_html(number_format_i18n($score_int)), 'unit' => __('of 100', 'allaccessible'), 'status' => $score_status, 'mod' => $score_mod, ); } if ($hero_pages_scanned !== null && $hero_pages_scanned > 0) { $tiles[] = array( 'label' => __('Pages audited', 'allaccessible'), 'value' => esc_html(number_format_i18n((int) $hero_pages_scanned)), 'unit' => '', 'status' => __('on this site', 'allaccessible'), 'mod' => '', ); } $last_scan_at = $summary['lastScanAt'] ?? null; $ls_ts = $last_scan_at ? strtotime((string) $last_scan_at) : false; if ($ls_ts) { $next_scan_at = $summary['nextScanAt'] ?? null; $ns_ts = $next_scan_at ? strtotime((string) $next_scan_at) : false; if ($ns_ts && $ns_ts < $now_ts) { $scan_status = __('Next scan overdue', 'allaccessible'); $scan_mod = 'warn'; } elseif ($ns_ts) { $scan_status = sprintf( /* translators: %s: human-readable time until the next scan (e.g. "3 days") */ __('Next in %s', 'allaccessible'), human_time_diff($now_ts, $ns_ts) ); $scan_mod = ''; } else { $scan_status = __('On demand', 'allaccessible'); $scan_mod = ''; } $tiles[] = array( 'label' => __('Last scan', 'allaccessible'), 'value' => '' . esc_html(sprintf( /* translators: %s: human-readable time difference (e.g. "2 hours") */ __('%s ago', 'allaccessible'), human_time_diff($ls_ts, $now_ts) )) . '', 'unit' => '', 'status' => $scan_status, 'mod' => $scan_mod, ); } $tiles[] = array( 'label' => __('Pending your review ✦', 'allaccessible'), 'value' => esc_html(number_format_i18n($pending)), 'unit' => '', 'status' => $pending > 0 ? __('Waiting for a person', 'allaccessible') : __('Queue is clear', 'allaccessible'), 'mod' => $pending > 0 ? 'warn' : 'ok', ); ?>
aiQuota)) ? $site_options_obj->aiQuota : null; $is_preview = $isFree && $ai_quota !== null; ?> 0) : ?>

array( 'label' => __('Pending review', 'allaccessible'), 'count' => $pending, ), 'approved' => array( 'label' => AllAccessible_AgenticFixes_Labels::live_state()['tab_approved'], 'count' => $approved, ), 'reverted' => array( 'label' => __('Reverted', 'allaccessible'), 'count' => $reverted, ), ); ?>
$tab) : $is_active = ($active === $key); $url = esc_url(add_query_arg(array('page' => self::PAGE_SLUG, 'status' => $key), admin_url('admin.php'))); ?>
((float) ($a['avgConfidence'] ?? 0)); } return $pb <=> $pa; }); if (empty($groups)) { self::render_empty_state($status); return; } if ($status === 'draft' && $show_first_run_hint) { self::render_first_run_hint(); } if ($status === 'draft') { $by_type = array(); foreach ($groups as $g) { $t = (string) ($g['type'] ?? 'other'); if (!isset($by_type[$t])) $by_type[$t] = 0; $by_type[$t]++; } arsort($by_type); $top_type = key($by_type); $top_count = (int) reset($by_type); if ($top_count >= 5) { self::render_bulk_approve_banner($top_type, $top_count); } } ?>

= 85) { $conf_class = 'aacx-v2__badge--ok'; } elseif ($confidence_pct >= 70) { $conf_class = 'aacx-v2__badge--warn'; } else { $conf_class = ''; } ?>

0) : ?>

' . esc_html($selector) . '' ); ?>

  • 0) : ?>
  • count($sample_urls)) : ?>
` to a page URL */ private static function highlight_url($url, $selector) { $url = (string) $url; $selector = (string) $selector; if ($selector === '') { return $url; } $encoded = rtrim(strtr(base64_encode($selector), '+/', '-_'), '='); $glue = (strpos($url, '?') !== false) ? '&' : '?'; return $url . $glue . 'aa-highlight=' . $encoded; } /* ─── Legacy V1 upgrade page ──────────────────────────────────── */ /** * Full-page upgrade prompt shown to legacy V1 widget customers in place * of the Agentic Fixes UI. */ private static function render_legacy_upgrade_page() { $upgrade_url = class_exists('AllAccessible_ApiClient') ? AllAccessible_ApiClient::get_instance()->get_migration_url() : 'https://app.allaccessible.org/billing'; ?>
'fixes', 'heading' => __('Nothing waiting for you', 'allaccessible'), 'description' => __('Agents draft fixes after each scan. Run one and anything they find lands here for your approval.', 'allaccessible'), 'success_redirect' => admin_url('admin.php?page=aacb-agentic-fixes&status=draft'), )); AllAccessible_ScanTriggerPanel::enqueue_inline_script(); } else { // Defensive fallback (panel class should always be loaded). ?>

manifestMax ?? 5); $used = (int) ($ai_quota->manifestUsed ?? 0); ?>

= $max; if ($is_locked) { echo ' $max) : ?>

get_site_options(); if (!is_wp_error($opts) && is_object($opts) && !empty($opts->siteID)) { $deeplink_site_token = (string) $opts->siteID; } } ?>