0. $run_raw = filter_input(INPUT_GET, 'run_id', FILTER_UNSAFE_RAW); $run_id = is_scalar($run_raw) ? absint(wp_unslash((string) $run_raw)) : 0; if ($run_id <= 0) { $latest_audit = $repo->get_latest_completed_run('audit'); if ($latest_audit) { $run_id = (int) $latest_audit['id']; } } $category_raw = filter_input(INPUT_GET, 'category', FILTER_UNSAFE_RAW); $severity_raw = filter_input(INPUT_GET, 'severity', FILTER_UNSAFE_RAW); $status_raw = filter_input(INPUT_GET, 'status', FILTER_UNSAFE_RAW); $group_raw = filter_input(INPUT_GET, 'group', FILTER_UNSAFE_RAW); $paged_raw = filter_input(INPUT_GET, 'paged', FILTER_UNSAFE_RAW); $category = is_string($category_raw) ? sanitize_text_field(wp_unslash($category_raw)) : ''; $severity = is_string($severity_raw) ? sanitize_key(wp_unslash($severity_raw)) : ''; $status = is_string($status_raw) ? sanitize_key(wp_unslash($status_raw)) : ''; $group = is_string($group_raw) ? sanitize_key(wp_unslash($group_raw)) : ''; if (!in_array($group, ['fix', 'improve', 'note'], true)) { $group = ''; } $paged = is_scalar($paged_raw) ? max(1, absint(wp_unslash((string) $paged_raw))) : 1; $per_page = 30; echo wp_kses(Brand::wrap_open(), Brand::admin_kses()); echo wp_kses(Brand::header(I18n::tr('Findings', 'النتائج')), Brand::admin_kses()); $all = $run_id > 0 ? $repo->get_findings_for_run($run_id) : []; if (empty($all)) { $this->render_empty_state($run_id); echo ''; return; } // Counts per action group, over the whole run. $group_counts = ['fix' => 0, 'improve' => 0, 'note' => 0]; foreach ($all as $row) { if (!$this->is_handled($row)) { $group_counts[Labels::group_of((string) ($row['severity'] ?? 'info'))]++; } } // Apply filters. $rows = array_values(array_filter($all, function (array $row) use ($category, $severity, $status, $group): bool { if ($category !== '' && (string) ($row['category'] ?? '') !== $category) { return false; } if ($severity !== '' && strtolower((string) ($row['severity'] ?? '')) !== $severity) { return false; } if ($status !== '' && strtolower((string) ($row['status'] ?? '')) !== $status) { return false; } if ($group !== '' && Labels::group_of((string) ($row['severity'] ?? 'info')) !== $group) { return false; } return true; })); $base = admin_url('admin.php?page=aetos-seo-findings&run_id=' . (int) $run_id); $this->render_group_chips($base, $group, $group_counts); $previous_run_id = $repo->previous_completed_audit_run_id($run_id); $auto_resolved = $previous_run_id > 0 ? $repo->count_findings(['run_id' => $previous_run_id, 'status' => 'done']) : 0; if ($auto_resolved > 0) { echo '

' . esc_html(sprintf(I18n::tr('Fixed since the previous audit: %d', 'المحلولة منذ الفحص السابق: %d'), (int) $auto_resolved)) . '

'; } $this->render_filters($run_id, $category, $status); echo '
'; if (empty($rows)) { echo '

' . esc_html(I18n::tr('No findings match these filters.', 'لا توجد نتائج مطابقة لهذه التصفية.')) . '

'; $this->render_modal(); echo ''; return; } // Main list: open work (fix, then improve). Notes and handled findings // are folded below unless the user filtered for them explicitly. $explicit = $status !== '' || $group === 'note' || $severity === 'info'; $main = []; $notes = []; $handled = []; foreach ($rows as $row) { if (!$explicit && $this->is_handled($row)) { $handled[] = $row; } elseif (!$explicit && Labels::group_of((string) ($row['severity'] ?? 'info')) === 'note') { $notes[] = $row; } else { $main[] = $row; } } $total_pages = max(1, (int) ceil(count($main) / $per_page)); $paged = min($paged, $total_pages); $page_rows = array_slice($main, ($paged - 1) * $per_page, $per_page); if (!empty($page_rows)) { echo '
'; foreach ($page_rows as $row) { $this->render_card($row); } echo '
'; $this->render_pagination($paged, $total_pages); } elseif (!$explicit) { echo '

' . esc_html(I18n::tr('Nothing needs fixing in this audit.', 'لا توجد مشكلات تحتاج إصلاحًا في هذا الفحص.')) . '

'; } if (!empty($notes)) { echo '
' . esc_html(sprintf(I18n::tr('Notes for context (%d)', 'ملاحظات للعلم (%d)'), count($notes))) . ''; echo '

' . esc_html(I18n::tr('These do not lower the score much or at all. Read them when you have time.', 'هذه الملاحظات لا تؤثر في النتيجة أو تؤثر قليلًا. راجعها حين يتوفر لديك وقت.')) . '

'; echo '
'; foreach ($notes as $row) { $this->render_card($row); } echo '
'; } if (!empty($handled)) { echo '
' . esc_html(sprintf(I18n::tr('Fixed or ignored (%d)', 'تم حلها أو تجاهلها (%d)'), count($handled))) . ''; echo '
'; foreach ($handled as $row) { $this->render_card($row); } echo '
'; } $this->render_modal(); echo ''; } /** * @param array $row */ private function is_handled(array $row): bool { return in_array(strtolower((string) ($row['status'] ?? 'open')), ['done', 'ignored'], true); } private function render_empty_state(int $run_id): void { $dashboard_url = admin_url('admin.php?page=aetos-seo-dashboard'); echo '
'; echo '
'; echo '

' . esc_html($run_id > 0 ? I18n::tr('This audit found no issues', 'لم يجد هذا الفحص أي مشكلات') : I18n::tr('No audit yet', 'لم يُشغَّل أي فحص بعد')) . '

'; echo '

' . esc_html(I18n::tr( 'Start an audit from the dashboard. Findings appear here, ordered by what to fix first.', 'ابدأ فحصًا من لوحة التحكم. ستظهر النتائج هنا مرتبة حسب الأهم إصلاحًا.' )) . '

'; echo '' . esc_html(I18n::tr('Go to the dashboard', 'الذهاب إلى لوحة التحكم')) . ''; echo '
'; } /** * @param array $counts */ private function render_group_chips(string $base, string $active, array $counts): void { echo ''; } private function render_filters(int $run_id, string $category, string $status): void { echo '
' . esc_html(I18n::tr('Search and filter', 'بحث وتصفية')) . ''; echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
'; } /** * @param array $row */ private function render_card(array $row): void { $data = $this->finding_payload($row); $row_id = (int) ($row['id'] ?? 0); $sev = (string) ($data['severity'] ?? 'info'); $status = strtolower((string) ($data['status'] ?? 'open')); $why = $this->text_excerpt((string) ($data['why_it_matters'] ?? ''), 180); $data['ai_prompt'] = \AetosSeo\Utils\IssuePrompt::build($data); $data['row_id'] = $row_id; $data['is_handled'] = $this->is_handled($row); echo '
'; echo '
'; echo '' . esc_html(Labels::severity($sev)) . ''; echo '' . esc_html(CategoryLabels::label((string) ($data['category'] ?? ''))) . ''; if ($status !== 'open') { echo '' . esc_html(Labels::status($status)) . ''; } else { echo '' . esc_html(Labels::status('open')) . ''; } echo '
'; echo '

' . esc_html((string) ($data['title'] ?? '')) . '

'; if ($why !== '') { echo '

' . esc_html($why) . '

'; } echo '
'; echo ''; if (!$this->is_handled($row)) { echo ''; echo ''; } else { echo ''; } echo '
'; echo '
'; } private function render_pagination(int $paged, int $total_pages): void { if ($total_pages <= 1) { return; } $base_url = remove_query_arg('paged'); echo ''; } private function render_modal(): void { echo ''; } /** * @param array $row * @return array */ private function finding_payload(array $row): array { $evidence = $this->decode_payload($row['evidence'] ?? null, []); $rec_steps = $this->decode_payload($row['recommendation_steps'] ?? null, []); $retest = $this->decode_payload($row['retest'] ?? null, []); $finding_id = (string) ($row['finding_id'] ?? ''); $category = (string) ($row['category'] ?? ''); $fallback_title = $this->fallback_finding_title($finding_id, $category); return [ 'id' => $finding_id, 'title' => I18n::english_safe((string) ($row['title'] ?? ''), $fallback_title), 'category' => $category, 'severity' => (string) ($row['severity'] ?? ''), 'status' => (string) ($row['status'] ?? ''), // 4.0.17: the evidence modal printed the raw status and category keys // ("OPEN", "AI-Friendly") inside the Arabic interface. 'status_label' => $this->status_label((string) ($row['status'] ?? 'open')), 'category_label' => $category !== '' ? CategoryLabels::label($category) : '', 'score_impact' => (int) ($row['score_impact'] ?? 0), 'evidence' => $evidence, 'why_it_matters' => I18n::english_safe( (string) ($row['why_it_matters'] ?? ''), I18n::tr('Run a fresh audit in English to regenerate the full explanation for this stored finding.', 'أعد تشغيل الفحص بالعربية لتحديث شرح هذه النتيجة المخزنة.') ), 'recommendation_steps' => $this->english_safe_steps($rec_steps), 'retest' => $this->english_safe_retest($retest), ]; } private function fallback_finding_title(string $finding_id, string $category): string { $category_label = $category !== '' ? CategoryLabels::label($category) : I18n::tr('Audit', 'الفحص'); $finding_label = $finding_id !== '' ? $finding_id : I18n::tr('stored finding', 'نتيجة مخزنة'); return sprintf(I18n::tr('%1$s issue: %2$s', 'مشكلة %1$s: %2$s'), $category_label, $finding_label); } /** * @param array $steps * @return array */ private function english_safe_steps(array $steps): array { if (I18n::is_arabic()) { return $steps; } if ($this->contains_arabic_value($steps)) { return [ I18n::tr('Run a fresh audit in English to regenerate the step-by-step fix for this stored finding.', 'أعد تشغيل الفحص بالعربية لتحديث خطوات إصلاح هذه النتيجة المخزنة.'), ]; } return $steps; } /** * @param array $retest * @return array */ private function english_safe_retest(array $retest): array { if (I18n::is_arabic() || !$this->contains_arabic_value($retest)) { return $retest; } return [ 'how' => 'Run a fresh audit in English to regenerate the verification instructions for this stored finding.', 'pass_condition' => 'The new audit provides English verification instructions.', ]; } /** * @param mixed $value */ private function contains_arabic_value($value): bool { if (is_string($value)) { return I18n::contains_arabic($value); } if (is_array($value)) { foreach ($value as $key => $item) { if ((is_string($key) && I18n::contains_arabic($key)) || $this->contains_arabic_value($item)) { return true; } } } return false; } /** * @param mixed $value * @param array $fallback * @return array */ private function decode_payload($value, array $fallback): array { if (is_array($value)) { return $value; } if (!is_string($value) || trim($value) === '') { return $fallback; } $decoded = json_decode($value, true); return is_array($decoded) ? $decoded : $fallback; } private function severity_label(string $severity): string { return Labels::severity($severity); } private function status_label(string $status): string { return Labels::status($status); } private function text_excerpt(string $text, int $max): string { $text = trim(wp_strip_all_tags($text)); if ($text === '') { return ''; } return html_entity_decode(wp_html_excerpt($text, $max, '...'), ENT_QUOTES, 'UTF-8'); } /** * @return array */ private function categories(): array { return [ 'Performance', 'Technical SEO', 'On-Page', 'AI-Friendly', 'Mobile Usability', 'Security', 'Database', 'Media', 'Crawl', 'Analytics', ]; } }