New_Micro_Learning/backend/app/Modules/Courses/Application/RichTextSanitizer.php

85 خطوط
2.7 KiB
PHP

<?php
namespace App\Modules\Courses\Application;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMXPath;
final class RichTextSanitizer
{
private const ALLOWED = ['p', 'h1', 'h2', 'h3', 'strong', 'b', 'em', 'i', 'ul', 'ol', 'li', 'a', 'br'];
private const DISCARD = ['script', 'style', 'iframe', 'object', 'embed', 'svg', 'math', 'template'];
public function sanitize(string $html): string
{
$document = new DOMDocument('1.0', 'UTF-8');
$previous = libxml_use_internal_errors(true);
$document->loadHTML('<!doctype html><html><head><meta charset="utf-8"></head><body>'.$html.'</body></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);
}
}