Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
94.44% |
17 / 18 |
CRAP | |
97.96% |
96 / 98 |
HTMLFilter | |
0.00% |
0 / 1 |
|
94.44% |
17 / 18 |
32 | |
97.96% |
96 / 98 |
__construct() | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
filter(Configuration $config, $html_text) | |
0.00% |
0 / 1 |
2.04 | |
77.78% |
7 / 9 |
|||
initialize(Configuration $config, $html_text) | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
createDOMDocument($html_text) | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
copyAllowedNodes() | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
findBodyNode(\DOMDocument $dom_document) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
copyAllowedChildNodes( \DOMNode $source, \DOMNode $destination ) | |
100.00% |
1 / 1 |
4 | |
100.00% |
10 / 10 |
|||
isAllowedNode(\DOMNode $node) | |
100.00% |
1 / 1 |
3 | |
100.00% |
3 / 3 |
|||
copyNode(\DOMNode $node, \DOMNode $destination) | |
100.00% |
1 / 1 |
4 | |
100.00% |
8 / 8 |
|||
copyTextNode(\DOMText $text_node, \DOMNode $destination) | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
copyDOMElement( \DOMElement $element, \DOMNode $destination ) | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
copyAllowedAttributes( \DOMElement $source, \DOMElement $destination ) | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
|||
isAllowedAttribute(\DOMAttr $attribute) | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
copyAttribute( \DOMAttr $attribute, \DOMElement $destination ) | |
100.00% |
1 / 1 |
1 | |
100.00% |
8 / 8 |
|||
copyDOMComment(\DOMComment $comment, \DOMNode $destination) | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
fetchFilteredHTML() | |
100.00% |
1 / 1 |
1 | |
100.00% |
4 / 4 |
|||
trimBodyTags($html_text) | |
100.00% |
1 / 1 |
4 | |
100.00% |
9 / 9 |
|||
cleanup() | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
<?php | |
namespace AthosHun\HTMLFilter; | |
class HTMLFilter | |
{ | |
private $config; | |
private $original_dom; | |
private $filtered_dom; | |
private $libxml_used_internal_errors; | |
public function __construct() | |
{ | |
} | |
public function filter(Configuration $config, $html_text) | |
{ | |
$this->libxml_used_internal_errors = libxml_use_internal_errors(true); | |
try { | |
$this->initialize($config, $html_text); | |
$this->copyAllowedNodes(); | |
$filtered_html = $this->fetchFilteredHTML(); | |
} catch (\Exception $error) { | |
$this->cleanup(); | |
throw $error; | |
} | |
$this->cleanup(); | |
return $filtered_html; | |
} | |
private function initialize(Configuration $config, $html_text) | |
{ | |
$this->config = $config; | |
$html_text = mb_convert_encoding($html_text, "UTF-8", "UTF-8"); | |
$this->original_dom = $this->createDOMDocument($html_text); | |
$this->filtered_dom = $this->createDOMDocument(""); | |
} | |
private function createDOMDocument($html_text) | |
{ | |
$dom_document = new \DOMDocument("1.0", "UTF-8"); | |
$dom_document->loadHTML( | |
"<?xml encoding=\"UTF-8\"><html><body>$html_text</body></html>" | |
); | |
return $dom_document; | |
} | |
private function copyAllowedNodes() | |
{ | |
$original_body = $this->findBodyNode($this->original_dom); | |
$filtered_body = $this->findBodyNode($this->filtered_dom); | |
$this->copyAllowedChildNodes($original_body, $filtered_body); | |
} | |
private function findBodyNode(\DOMDocument $dom_document) | |
{ | |
return $dom_document->getElementsByTagName("body")->item(0); | |
} | |
private function copyAllowedChildNodes( | |
\DOMNode $source, | |
\DOMNode $destination | |
) { | |
if (!$source->hasChildNodes()) { | |
return; | |
} | |
for ($i = 0, $l = $source->childNodes->length; $i != $l; ++$i) { | |
$node = $source->childNodes->item($i); | |
if ($this->isAllowedNode($node)) { | |
$this->copyNode($node, $destination); | |
} else { | |
$this->copyAllowedChildNodes($node, $destination); | |
} | |
} | |
} | |
private function isAllowedNode(\DOMNode $node) | |
{ | |
return ($node instanceof \DOMText) | |
|| ($node instanceof \DOMComment) | |
|| ($this->config->isAllowedTag($node->nodeName)); | |
} | |
private function copyNode(\DOMNode $node, \DOMNode $destination) | |
{ | |
if ($node instanceof \DOMText) { | |
$this->copyTextNode($node, $destination); | |
} else if ($node instanceof \DOMElement) { | |
$this->copyDOMElement($node, $destination); | |
} else if ($node instanceof \DOMComment) { | |
$this->copyDOMComment($node, $destination); | |
} | |
} | |
private function copyTextNode(\DOMText $text_node, \DOMNode $destination) | |
{ | |
$destination->appendChild( | |
$destination->ownerDocument->createTextNode($text_node->data) | |
); | |
} | |
private function copyDOMElement( | |
\DOMElement $element, | |
\DOMNode $destination | |
) { | |
$copied_element = $destination->ownerDocument | |
->createElement($element->nodeName); | |
$destination->appendChild($copied_element); | |
$this->copyAllowedAttributes($element, $copied_element); | |
$this->copyAllowedChildNodes($element, $copied_element); | |
} | |
private function copyAllowedAttributes( | |
\DOMElement $source, | |
\DOMElement $destination | |
) { | |
for ($i = 0, $l = $source->attributes->length; $i != $l; ++$i) { | |
$attribute = $source->attributes->item($i); | |
if ($this->isAllowedAttribute($attribute)) { | |
$this->copyAttribute($attribute, $destination); | |
} | |
} | |
} | |
private function isAllowedAttribute(\DOMAttr $attribute) | |
{ | |
return $this->config->isAllowedAttribute( | |
$attribute->ownerElement->nodeName, | |
$attribute->name, | |
$attribute->value | |
); | |
} | |
private function copyAttribute( | |
\DOMAttr $attribute, | |
\DOMElement $destination | |
) { | |
$copied_attribute = $destination->ownerDocument | |
->createAttribute($attribute->name); | |
$copied_attribute->value = htmlspecialchars( | |
$attribute->value, | |
ENT_QUOTES, | |
"UTF-8" | |
); | |
$destination->appendChild($copied_attribute); | |
} | |
private function copyDOMComment(\DOMComment $comment, \DOMNode $destination) | |
{ | |
$destination->appendChild( | |
$destination->ownerDocument->createComment($comment->data) | |
); | |
} | |
private function fetchFilteredHTML() | |
{ | |
$filtered_html = $this->filtered_dom->saveXML( | |
$this->findBodyNode($this->filtered_dom) | |
); | |
return $this->trimBodyTags($filtered_html); | |
} | |
private function trimBodyTags($html_text) | |
{ | |
if ($html_text === "<body/>") { | |
return ""; | |
} | |
if (substr($html_text, 0, 6) === "<body>") { | |
$html_text = substr($html_text, 6); | |
} | |
if (substr($html_text, -7, 7) === "</body>") { | |
$html_text = substr($html_text, 0, strlen($html_text) - 7); | |
} | |
return $html_text; | |
} | |
private function cleanup() | |
{ | |
$this->config = null; | |
$this->original_dom = null; | |
$this->filtered_dom = null; | |
libxml_use_internal_errors($this->libxml_used_internal_errors); | |
} | |
} |