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);
}
}