loadHTML( '', LIBXML_NOERROR | LIBXML_NOWARNING, ); if (! $loaded) { return $html; } foreach ($document->getElementsByTagName('a') as $anchor) { $this->applyToAnchor($anchor); } $root = $document->getElementById('pr-link-policy-root'); if (! $root) { return $html; } $result = ''; foreach ($root->childNodes as $child) { $result .= $document->saveHTML($child); } return $result; } private function applyToAnchor(DOMElement $anchor): void { $href = trim($anchor->getAttribute('href')); // rel ist systemgesteuert — Autoren-Eingaben zählen nie. $anchor->removeAttribute('rel'); if ($href === '' || Str::startsWith($href, ['mailto:', 'tel:', '#'])) { $anchor->removeAttribute('target'); return; } if ($this->isInternal($href)) { $anchor->removeAttribute('target'); return; } $anchor->setAttribute('rel', 'sponsored nofollow noopener'); $anchor->setAttribute('target', '_blank'); } /** * Intern = relative Pfade sowie absolute URLs auf eine der * konfigurierten Portal-Domains (inkl. www-Variante). */ private function isInternal(string $href): bool { if (! preg_match('#^https?://#i', $href)) { // Relative Pfade (/firma/...) — kein Protokoll, keine Domain. return ! Str::startsWith($href, '//'); } $host = strtolower((string) parse_url($href, PHP_URL_HOST)); if ($host === '') { return false; } $internalHosts = $this->internalHosts(); return in_array($host, $internalHosts, true) || in_array(Str::after($host, 'www.'), $internalHosts, true); } /** * @return list */ private function internalHosts(): array { $hosts = []; foreach ((array) config('domains.domains', []) as $domain) { foreach ([$domain['domain_name'] ?? null, parse_url((string) ($domain['url'] ?? ''), PHP_URL_HOST)] as $candidate) { if (is_string($candidate) && $candidate !== '') { $host = strtolower($candidate); $hosts[] = $host; $hosts[] = Str::after($host, 'www.'); } } } return array_values(array_unique($hosts)); } }