loadHTML(''.$html.'', LIBXML_HTML_NODEFDTD); libxml_clear_errors(); libxml_use_internal_errors($previous); $xpath = new DOMXPath($document); $nodes = iterator_to_array($xpath->query('//body//*') ?: []); foreach (array_reverse($nodes) as $node) { if (! $node instanceof DOMElement || ! $node->parentNode) { continue; } $tag = strtolower($node->tagName); if (in_array($tag, self::DISCARD, true)) { $node->parentNode->removeChild($node); continue; } if (! in_array($tag, self::ALLOWED, true)) { $this->unwrap($node); continue; } $href = $tag === 'a' ? $node->getAttribute('href') : ''; while ($node->attributes->length > 0) { $node->removeAttributeNode($node->attributes->item(0)); } if ($tag === 'a' && $this->safeHref($href)) { $node->setAttribute('href', $href); $node->setAttribute('rel', 'noopener noreferrer'); } } $body = $document->getElementsByTagName('body')->item(0); if (! $body) { return ''; } return collect(iterator_to_array($body->childNodes)) ->map(fn (DOMNode $node): string => $document->saveHTML($node) ?: '') ->implode(''); } private function unwrap(DOMElement $node): void { $parent = $node->parentNode; while ($node->firstChild) { $parent->insertBefore($node->firstChild, $node); } $parent->removeChild($node); } private function safeHref(string $href): bool { $href = trim($href); if ($href === '') { return false; } if (str_starts_with($href, '/') && ! str_starts_with($href, '//')) { return true; } $scheme = strtolower((string) parse_url($href, PHP_URL_SCHEME)); return in_array($scheme, ['http', 'https', 'mailto'], true); } }