all(); $guides = model(\App\Models\GuideModel::class)->published(1000); $issues = []; $seenTitles = []; $seenDescs = []; foreach ($tools as $tool) { $slug = $tool['slug']; $title = (string) $tool['seo_title']; $desc = (string) $tool['seo_description']; if (trim($title) === '') { $issues[] = ['severity' => 'critical', 'page' => '/' . $slug, 'check' => 'Missing SEO title']; } elseif (mb_strlen($title) > 65) { $issues[] = ['severity' => 'warning', 'page' => '/' . $slug, 'check' => 'Title longer than 65 chars (' . mb_strlen($title) . ')']; } if (trim($desc) === '') { $issues[] = ['severity' => 'critical', 'page' => '/' . $slug, 'check' => 'Missing meta description']; } elseif (mb_strlen($desc) < 70 || mb_strlen($desc) > 320) { $issues[] = ['severity' => 'warning', 'page' => '/' . $slug, 'check' => 'Description outside 70–320 chars (' . mb_strlen($desc) . ')']; } if (trim((string) $tool['h1']) === '') { $issues[] = ['severity' => 'critical', 'page' => '/' . $slug, 'check' => 'Missing H1']; } if ($tool['faqs'] === [] && ! in_array($tool['kind'], ['text'], true)) { $issues[] = ['severity' => 'warning', 'page' => '/' . $slug, 'check' => 'No FAQ content']; } if ($tool['related_slugs'] === [] && count(service('relatedTools')->forTool($tool, 4)) < 3) { $issues[] = ['severity' => 'warning', 'page' => '/' . $slug, 'check' => 'Thin internal linking (<3 related tools)']; } if (! preg_match('/^[a-z0-9]+$/', (string) ($tool['primary_input_format'] ?? '')) && $tool['operation'] === 'convert') { $issues[] = ['severity' => 'warning', 'page' => '/' . $slug, 'check' => 'Converter without primary format pair']; } $tKey = mb_strtolower($title); if ($tKey !== '' && isset($seenTitles[$tKey])) { $issues[] = ['severity' => 'critical', 'page' => '/' . $slug, 'check' => 'Duplicate title with /' . $seenTitles[$tKey]]; } $seenTitles[$tKey] = $slug; $dKey = mb_substr(mb_strtolower($desc), 0, 120); if ($dKey !== '' && isset($seenDescs[$dKey])) { // near-duplicate short descriptions are common across generated converters — flag only exact dupes if (mb_strtolower((string) $tool['short_description']) === mb_strtolower((string) $desc)) { $issues[] = ['severity' => 'warning', 'page' => '/' . $slug, 'check' => 'Description identical to /' . $seenDescs[$dKey]]; } } $seenDescs[$dKey] = $slug; } foreach ($guides as $g) { if ((string) $g['excerpt'] === '' || (string) $g['body_md'] === '') { $issues[] = ['severity' => 'critical', 'page' => '/blog/' . $g['slug'], 'check' => 'Guide missing excerpt/body']; } } usort($issues, static fn ($a, $b) => [$b['severity'], $a['page']] <=> [$a['severity'], $b['page']]); $criticalCount = count(array_filter($issues, static fn ($i) => $i['severity'] === 'critical')); return $this->render('seo_index', [ 'issues' => $issues, 'criticalCount' => $criticalCount, 'toolCount' => count($tools), 'guideCount' => count($guides), 'sitemapUrl' => base_url('/sitemap.xml'), ]); } }