|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Backstage\Seo\Checks\Meta; |
| 4 | + |
| 5 | +use Backstage\Seo\Interfaces\Check; |
| 6 | +use Backstage\Seo\Traits\PerformCheck; |
| 7 | +use Backstage\Seo\Traits\Translatable; |
| 8 | +use Illuminate\Http\Client\Response; |
| 9 | +use Symfony\Component\DomCrawler\Crawler; |
| 10 | + |
| 11 | +class InvalidHeadElementsCheck implements Check |
| 12 | +{ |
| 13 | + use PerformCheck, |
| 14 | + Translatable; |
| 15 | + |
| 16 | + public string $title = 'The page does not contain invalid HTML elements in the head section'; |
| 17 | + |
| 18 | + public string $description = 'The head section should not contain invalid HTML elements. According to Google\'s documentation, once Google detects an invalid element in the head, it assumes the end of the head element and stops reading any further elements. This can cause important meta tags to be missed.'; |
| 19 | + |
| 20 | + public string $priority = 'high'; |
| 21 | + |
| 22 | + public int $timeToFix = 2; |
| 23 | + |
| 24 | + public int $scoreWeight = 8; |
| 25 | + |
| 26 | + public bool $continueAfterFailure = true; |
| 27 | + |
| 28 | + public ?string $failureReason; |
| 29 | + |
| 30 | + public mixed $actualValue = null; |
| 31 | + |
| 32 | + public mixed $expectedValue = null; |
| 33 | + |
| 34 | + /** |
| 35 | + * Valid HTML elements that are allowed in the head section |
| 36 | + * Based on HTML5 specification and Google's documentation |
| 37 | + */ |
| 38 | + private array $validHeadElements = [ |
| 39 | + 'title', |
| 40 | + 'base', |
| 41 | + 'link', |
| 42 | + 'meta', |
| 43 | + 'style', |
| 44 | + 'script', |
| 45 | + 'noscript', |
| 46 | + 'template', |
| 47 | + ]; |
| 48 | + |
| 49 | + public function check(Response $response, Crawler $crawler): bool |
| 50 | + { |
| 51 | + if (! $this->validateContent($response)) { |
| 52 | + return false; |
| 53 | + } |
| 54 | + |
| 55 | + return true; |
| 56 | + } |
| 57 | + |
| 58 | + public function validateContent(Response $response): bool |
| 59 | + { |
| 60 | + // Get the raw HTML content from the response |
| 61 | + $html = $response->body(); |
| 62 | + |
| 63 | + // Extract the head section using regex |
| 64 | + if (preg_match('/<head[^>]*>(.*?)<\/head>/is', $html, $matches)) { |
| 65 | + $headContent = $matches[1]; |
| 66 | + } else { |
| 67 | + // No head section found |
| 68 | + $this->failureReason = __('failed.meta.invalid_head_elements.no_head'); |
| 69 | + $this->actualValue = 'No head section found'; |
| 70 | + |
| 71 | + return false; |
| 72 | + } |
| 73 | + |
| 74 | + // Extract all HTML tags from the head content, but exclude tags inside template elements |
| 75 | + $headTags = []; |
| 76 | + |
| 77 | + // First, remove template content to avoid detecting nested elements |
| 78 | + $headContentWithoutTemplates = preg_replace('/<template[^>]*>.*?<\/template>/is', '', $headContent); |
| 79 | + |
| 80 | + // Extract tags from the cleaned content |
| 81 | + preg_match_all('/<([a-zA-Z][a-zA-Z0-9]*)[^>]*>/i', $headContentWithoutTemplates, $matches); |
| 82 | + $headTags = $matches[1]; |
| 83 | + |
| 84 | + if (empty($headTags)) { |
| 85 | + // No elements in head section |
| 86 | + $this->failureReason = __('failed.meta.invalid_head_elements.no_head'); |
| 87 | + $this->actualValue = 'No head elements found'; |
| 88 | + |
| 89 | + return false; |
| 90 | + } |
| 91 | + |
| 92 | + $invalidElements = []; |
| 93 | + |
| 94 | + foreach ($headTags as $tagName) { |
| 95 | + $tagName = strtolower($tagName); |
| 96 | + |
| 97 | + // Check if the element is valid for the head section |
| 98 | + if (! in_array($tagName, $this->validHeadElements)) { |
| 99 | + $invalidElements[] = $tagName; |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + if (! empty($invalidElements)) { |
| 104 | + $this->failureReason = __('failed.meta.invalid_head_elements.found', [ |
| 105 | + 'actualValue' => implode(', ', array_unique($invalidElements)), |
| 106 | + ]); |
| 107 | + $this->actualValue = $invalidElements; |
| 108 | + |
| 109 | + return false; |
| 110 | + } |
| 111 | + |
| 112 | + return true; |
| 113 | + } |
| 114 | +} |
0 commit comments