File: /home/slfopp7cb1df/public_html/sitepacket.com/system/system/ThirdParty/Kint/Parser/DomPlugin.php
<?php
declare(strict_types=1);
/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Jonathan Vollebregt (jnvsor@gmail.com), Rokas Šleinius (raveren@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Kint\Parser;
use Dom\Attr;
use Dom\CharacterData;
use Dom\Document;
use Dom\DocumentType;
use Dom\Element;
use Dom\HTMLElement;
use Dom\NamedNodeMap;
use Dom\Node;
use Dom\NodeList;
use DOMAttr;
use DOMCharacterData;
use DOMDocumentType;
use DOMElement;
use DOMNamedNodeMap;
use DOMNode;
use DOMNodeList;
use Kint\Value\AbstractValue;
use Kint\Value\Context\BaseContext;
use Kint\Value\Context\ClassDeclaredContext;
use Kint\Value\Context\ContextInterface;
use Kint\Value\Context\PropertyContext;
use Kint\Value\DomNodeListValue;
use Kint\Value\DomNodeValue;
use Kint\Value\FixedWidthValue;
use Kint\Value\InstanceValue;
use Kint\Value\Representation\ContainerRepresentation;
use Kint\Value\StringValue;
use LogicException;
class DomPlugin extends AbstractPlugin implements PluginBeginInterface
{
/**
* Reflection doesn't work below 8.1, also it won't show readonly status.
*
* In order to ensure this is stable enough we're only going to provide
* properties for element and node. If subclasses like attr or document
* have their own fields then tough shit we're not showing them.
*
* @psalm-var non-empty-array<string, bool> Property names to readable status
*/
public const NODE_PROPS = [
'nodeType' => true,
'nodeName' => true,
'baseURI' => true,
'isConnected' => true,
'ownerDocument' => true,
'parentNode' => true,
'parentElement' => true,
'childNodes' => true,
'firstChild' => true,
'lastChild' => true,
'previousSibling' => true,
'nextSibling' => true,
'nodeValue' => true,
'textContent' => false,
];
/**
* @psalm-var non-empty-array<string, bool> Property names to readable status
*/
public const ELEMENT_PROPS = [
'namespaceURI' => true,
'prefix' => true,
'localName' => true,
'tagName' => true,
'id' => false,
'className' => false,
'classList' => true,
'attributes' => true,
'firstElementChild' => true,
'lastElementChild' => true,
'childElementCount' => true,
'previousElementSibling' => true,
'nextElementSibling' => true,
'innerHTML' => false,
'outerHTML' => false,
'substitutedNodeValue' => false,
];
public const DOM_NS_VERSIONS = [
'outerHTML' => KINT_PHP85,
];
/**
* @psalm-var non-empty-array<string, bool> Property names to readable status
*/
public const DOMNODE_PROPS = [
'nodeName' => true,
'nodeValue' => false,
'nodeType' => true,
'parentNode' => true,
'parentElement' => true,
'childNodes' => true,
'firstChild' => true,
'lastChild' => true,
'previousSibling' => true,
'nextSibling' => true,
'attributes' => true,
'isConnected' => true,
'ownerDocument' => true,
'namespaceURI' => true,
'prefix' => false,
'localName' => true,
'baseURI' => true,
'textContent' => false,
];
/**
* @psalm-var non-empty-array<string, bool> Property names to readable status
*/
public const DOMELEMENT_PROPS = [
'tagName' => true,
'className' => false,
'id' => false,
'schemaTypeInfo' => true,
'firstElementChild' => true,
'lastElementChild' => true,
'childElementCount' => true,
'previousElementSibling' => true,
'nextElementSibling' => true,
];
public const DOM_VERSIONS = [
'parentElement' => KINT_PHP83,
'isConnected' => KINT_PHP83,
'className' => KINT_PHP83,
'id' => KINT_PHP83,
'firstElementChild' => KINT_PHP80,
'lastElementChild' => KINT_PHP80,
'childElementCount' => KINT_PHP80,
'previousElementSibling' => KINT_PHP80,
'nextElementSibling' => KINT_PHP80,
];
/**
* List of properties to skip parsing.
*
* The properties of a Dom\Node can do a *lot* of damage to debuggers. The
* Dom\Node contains not one, not two, but 13 different ways to recurse into itself:
* * parentNode
* * firstChild
* * lastChild
* * previousSibling
* * nextSibling
* * parentElement
* * firstElementChild
* * lastElementChild
* * previousElementSibling
* * nextElementSibling
* * childNodes
* * attributes
* * ownerDocument
*
* All of this combined: the tiny SVGs used as the caret in Kint were already
* enough to make parsing and rendering take over a second, and send memory
* usage over 128 megs, back in the old DOM API. So we blacklist every field
* we don't strictly need and hope that that's good enough.
*
* In retrospect -- this is probably why print_r does the same
*
* @psalm-var array<string, true>
*/
public static array $blacklist = [
'parentNode' => true,
'firstChild' => true,
'lastChild' => true,
'previousSibling' => true,
'nextSibling' => true,
'firstElementChild' => true,
'lastElementChild' => true,
'parentElement' => true,
'previousElementSibling' => true,
'nextElementSibling' => true,
'ownerDocument' => true,
];
/**
* Show all properties and methods.
*/
public static bool $verbose = false;
protected ClassMethodsPlugin $methods_plugin;
protected ClassStaticsPlugin $statics_plugin;
public function __construct(Parser $parser)
{
parent::__construct($parser);
$this->methods_plugin = new ClassMethodsPlugin($parser);
$this->statics_plugin = new ClassStaticsPlugin($parser);
}
public function setParser(Parser $p): void
{
parent::setParser($p);
$this->methods_plugin->setParser($p);
$this->statics_plugin->setParser($p);
}
public function getTypes(): array
{
return ['object'];
}
public function getTriggers(): int
{
return Parser::TRIGGER_BEGIN;
}
public function parseBegin(&$var, ContextInterface $c): ?AbstractValue
{
// Attributes and chardata (Which is parent of comments and text
// nodes) don't need children or attributes of their own
if ($var instanceof Attr || $var instanceof CharacterData || $var instanceof DOMAttr || $var instanceof DOMCharacterData) {
return $this->parseText($var, $c);
}
if ($var instanceof NamedNodeMap || $var instanceof NodeList || $var instanceof DOMNamedNodeMap || $var instanceof DOMNodeList) {
return $this->parseList($var, $c);
}
if ($var instanceof Node || $var instanceof DOMNode) {
return $this->parseNode($var, $c);
}
return null;
}
/** @psalm-param Node|DOMNode $var */
private function parseProperty(object $var, string $prop, ContextInterface $c): AbstractValue
{
if (!isset($var->{$prop})) {
return new FixedWidthValue($c, null);
}
$parser = $this->getParser();
$value = $var->{$prop};
if (\is_scalar($value)) {
return $parser->parse($value, $c);
}
if (isset(self::$blacklist[$prop])) {
$b = new InstanceValue($c, \get_class($value), \spl_object_hash($value), \spl_object_id($value));
$b->flags |= AbstractValue::FLAG_GENERATED | AbstractValue::FLAG_BLACKLIST;
return $b;
}
// Everything we can handle in parseBegin
if ($value instanceof Attr || $value instanceof CharacterData || $value instanceof DOMAttr || $value instanceof DOMCharacterData || $value instanceof NamedNodeMap || $value instanceof NodeList || $value instanceof DOMNamedNodeMap || $value instanceof DOMNodeList || $value instanceof Node || $value instanceof DOMNode) {
$out = $this->parseBegin($value, $c);
}
if (!isset($out)) {
// Shouldn't ever happen
$out = $parser->parse($value, $c); // @codeCoverageIgnore
}
$out->flags |= AbstractValue::FLAG_GENERATED;
return $out;
}
/** @psalm-param Attr|CharacterData|DOMAttr|DOMCharacterData $var */
private function parseText(object $var, ContextInterface $c): AbstractValue
{
if ($c instanceof BaseContext && null !== $c->access_path) {
$c->access_path .= '->nodeValue';
}
return $this->parseProperty($var, 'nodeValue', $c);
}
/** @psalm-param NamedNodeMap|NodeList|DOMNamedNodeMap|DOMNodeList $var */
private function parseList(object $var, ContextInterface $c): InstanceValue
{
if ($var instanceof NodeList || $var instanceof DOMNodeList) {
$v = new DomNodeListValue($c, $var);
} else {
$v = new InstanceValue($c, \get_class($var), \spl_object_hash($var), \spl_object_id($var));
}
$parser = $this->getParser();
$pdepth = $parser->getDepthLimit();
// Depth limit
// Use empty iterator representation since we need it to point out depth limits
if (($var instanceof NodeList || $var instanceof DOMNodeList) && $pdepth && $c->getDepth() >= $pdepth) {
$v->flags |= AbstractValue::FLAG_DEPTH_LIMIT;
return $v;
}
if (self::$verbose) {
$v = $this->methods_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
$v = $this->statics_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
}
if (0 === $var->length) {
$v->setChildren([]);
return $v;
}
$cdepth = $c->getDepth();
$ap = $c->getAccessPath();
$contents = [];
foreach ($var as $key => $item) {
$base_obj = new BaseContext($item->nodeName);
$base_obj->depth = $cdepth + 1;
if ($var instanceof NamedNodeMap || $var instanceof DOMNamedNodeMap) {
if (null !== $ap) {
$base_obj->access_path = $ap.'['.\var_export($item->nodeName, true).']';
}
} else { // NodeList
if (null !== $ap) {
$base_obj->access_path = $ap.'['.\var_export($key, true).']';
}
}
if ($item instanceof HTMLElement) {
$base_obj->name = $item->localName;
}
$item = $parser->parse($item, $base_obj);
$item->flags |= AbstractValue::FLAG_GENERATED;
$contents[] = $item;
}
$v->setChildren($contents);
if ($contents) {
$v->addRepresentation(new ContainerRepresentation('Iterator', $contents), 0);
}
return $v;
}
/** @psalm-param Node|DOMNode $var */
private function parseNode(object $var, ContextInterface $c): DomNodeValue
{
$class = \get_class($var);
$pdepth = $this->getParser()->getDepthLimit();
if ($pdepth && $c->getDepth() >= $pdepth) {
$v = new DomNodeValue($c, $var);
$v->flags |= AbstractValue::FLAG_DEPTH_LIMIT;
return $v;
}
if (($var instanceof DocumentType || $var instanceof DOMDocumentType) && $c instanceof BaseContext && $c->name === $var->nodeName) {
$c->name = '!DOCTYPE '.$c->name;
}
$cdepth = $c->getDepth();
$ap = $c->getAccessPath();
$properties = [];
$children = [];
$attributes = [];
foreach (self::getKnownProperties($var) as $prop => $readonly) {
$prop_c = new PropertyContext($prop, $class, ClassDeclaredContext::ACCESS_PUBLIC);
$prop_c->depth = $cdepth + 1;
$prop_c->readonly = KINT_PHP81 && $readonly;
if (null !== $ap) {
$prop_c->access_path = $ap.'->'.$prop;
}
$properties[] = $prop_obj = $this->parseProperty($var, $prop, $prop_c);
if ('childNodes' === $prop) {
if (!$prop_obj instanceof DomNodeListValue) {
throw new LogicException('childNodes property parsed incorrectly'); // @codeCoverageIgnore
}
$children = self::getChildren($prop_obj);
} elseif ('attributes' === $prop) {
$attributes = $prop_obj->getRepresentation('iterator');
$attributes = $attributes instanceof ContainerRepresentation ? $attributes->getContents() : [];
} elseif ('classList' === $prop) {
if ($iter = $prop_obj->getRepresentation('iterator')) {
$prop_obj->removeRepresentation($iter);
$prop_obj->addRepresentation($iter, 0);
}
}
}
$v = new DomNodeValue($c, $var);
// If we're in text mode, we can see children through the childNodes property
$v->setChildren($properties);
if ($children) {
$v->addRepresentation(new ContainerRepresentation('Children', $children, null, true));
}
if ($attributes) {
$v->addRepresentation(new ContainerRepresentation('Attributes', $attributes));
}
if (self::$verbose) {
$v->addRepresentation(new ContainerRepresentation('Properties', $properties));
$v = $this->methods_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
$v = $this->statics_plugin->parseComplete($var, $v, Parser::TRIGGER_SUCCESS);
}
return $v;
}
/**
* @psalm-param Node|DOMNode $var
*
* @psalm-return non-empty-array<string, bool>
*/
public static function getKnownProperties(object $var): array
{
if ($var instanceof Node) {
$known_properties = self::NODE_PROPS;
if ($var instanceof Element) {
$known_properties += self::ELEMENT_PROPS;
}
if ($var instanceof Document) {
$known_properties['textContent'] = true;
}
if ($var instanceof Attr || $var instanceof CharacterData) {
$known_properties['nodeValue'] = false;
}
foreach (self::DOM_NS_VERSIONS as $key => $val) {
/**
* @psalm-var bool $val
* Psalm bug #4509
*/
if (false === $val) {
unset($known_properties[$key]); // @codeCoverageIgnore
}
}
} else {
$known_properties = self::DOMNODE_PROPS;
if ($var instanceof DOMElement) {
$known_properties += self::DOMELEMENT_PROPS;
}
foreach (self::DOM_VERSIONS as $key => $val) {
/**
* @psalm-var bool $val
* Psalm bug #4509
*/
if (false === $val) {
unset($known_properties[$key]); // @codeCoverageIgnore
}
}
}
/** @psalm-var non-empty-array $known_properties */
if (!self::$verbose) {
$known_properties = \array_intersect_key($known_properties, [
'nodeValue' => null,
'childNodes' => null,
'attributes' => null,
]);
}
return $known_properties;
}
/** @psalm-return list<AbstractValue> */
private static function getChildren(DomNodeListValue $property): array
{
if (0 === $property->getLength()) {
return [];
}
if ($property->flags & AbstractValue::FLAG_DEPTH_LIMIT) {
return [$property];
}
$list_items = $property->getChildren();
if (null === $list_items) {
// This is here for psalm but all DomNodeListValue should
// either be depth_limit or have array children
return []; // @codeCoverageIgnore
}
$children = [];
foreach ($list_items as $node) {
// Remove text nodes if theyre empty
if ($node instanceof StringValue && '#text' === $node->getContext()->getName()) {
/**
* @psalm-suppress InvalidArgument
* Psalm bug #11055
*/
if (\ctype_space($node->getValue()) || '' === $node->getValue()) {
continue;
}
}
$children[] = $node;
}
return $children;
}
}