File: /var/www/html/shootinschool/wp-content/plugins/wp-google-maps/includes/class.dom-element.php
<?php
namespace WPGMZA;
if(!defined('ABSPATH'))
return;
require_once(plugin_dir_path(__FILE__) . 'class.selector-to-xpath.php');
/**
* Direct replacement for the defauly class.dom-element.php
*
* Due to the fact it is located in the php8 sub directory, we will only load it for PHP 8 users
*/
class DOMElement extends \DOMElement
{
protected static $xpathConverter;
public function __construct()
{
\DOMElement::__construct();
}
public function __get($name)
{
switch($name)
{
case "html":
return $this->ownerDocument->saveHTML( $this );
break;
}
return \DOMElement::__get($name);
}
/**
* Runs a CSS selector on the element. This is equivilant to Javascripts querySelector
* @param string $query a CSS selector
* @return mixed The first descendant element that matches the selector, or NULL if there is no match
*/
public function querySelector($query)
{
$results = $this->querySelectorAll($query);
if(empty($results))
return null;
return $results[0];
}
/**
* Runs a CSS selector on the element. This is equivilant to Javascripts querySelectorAll
* @return mixed Any descendant element's that match the selector, or NULL if there is no match
*/
public function querySelectorAll($query, $sort=true)
{
$xpath = new \DOMXPath($this->ownerDocument);
try{
$expr = DOMElement::selectorToXPath($query);
}catch(\Exception $e) {
echo "<p class='notice notice-warning'>Failed to convert CSS selector to XPath (" . $e->getMessage() . ")</p>";
}
$list = $xpath->query($expr, $this);
$results = array();
for($i = 0; $i < $list->length; $i++)
array_push($results, $list->item($i));
if($sort)
usort($results, array('WPGMZA\DOMElement', 'sortByDOMPosition'));
return new DOMQueryResults($results);
}
/**
* Traverses from this node up it's ancestors and returns the first node that matches the given selector
* @param mixed $selector Either this node the first ancestor that matches this selector, or NULL if no match is found
*/
public function closest($selector)
{
if($this === $this->ownerDocument->getDocumentElementSafe())
throw new \Exception('Method not valid on document element');
for($el = $this; $el->parentNode != null; $el = $el->parentNode)
{
$m = $el->parentNode->querySelectorAll($selector);
if(array_search($el, $m, true) !== false)
return $el;
}
return null;
}
/**
* Wraps this element in the element passed in, then replaces this nodes original position
* @param DOMElement The element to wrap this element in
*/
public function wrap($wrapper)
{
$this->parentNode->replaceChild($wrapper, $this);
$wrapper->appendChild($this);
}
/**
* Test if this element comes before the other element in the DOM tree
* @return boolean TRUE if this element comes before the other, FALSE if not
*/
public function isBefore($other)
{
if($this->parentNode === $other->parentNode)
return ($this->getBreadth() < $other->getBreadth());
$this_depth = $this->getDepth();
$other_depth = $other->getDepth();
if($this_depth == $other_depth)
return $this->parentNode->isBefore($other->parentNode);
if($this_depth > $other_depth)
{
$ancestor = $this;
$ancestor_depth = $this_depth;
while($ancestor_depth > $other_depth)
{
$ancestor = $ancestor->parentNode;
$ancestor_depth--;
}
return $ancestor->isBefore($other);
}
if($this_depth < $other_depth)
{
$ancestor = $other;
$ancestor_depth = $other_depth;
while($ancestor_depth > $this_depth)
{
$ancestor = $ancestor->parentNode;
$ancestor_depth--;
}
return $this->isBefore($ancestor);
}
}
/**
* Returns the breadth (sometimes called child index) of this node in regards to it's siblings
* @return int The index of this node
*/
public function getBreadth()
{
$breadth = 0;
for($node = $this->previousSibling; $node != null; $node = $node->previousSibling)
$breadth++;
return $breadth;
}
/**
* Returns the depth of this node in regards to it's ancestors
* @return int The depth of this node
*/
public function getDepth()
{
$depth = 0;
for($node = $this->parentNode; $node != null; $node = $node->parentNode)
$depth++;
return $depth;
}
/**
* @internal sort function for DOM position sort
*/
private static function sortByDOMPosition($a, $b)
{
return ($a->isBefore($b) ? -1 : 1);
}
/**
* @internal Calls the CSS to XPath converter on the specified selector
* @param string $selector The CSS selector
* @return string The resulting XPath expression
*/
public static function selectorToXPath($selector)
{
if(!DOMElement::$xpathConverter)
DOMElement::$xpathConverter = new Selector\XPathConverter();
$xpath = DOMElement::$xpathConverter->convert($selector);
return $xpath;
}
/**
* Imports the supplied subject into this node.
* @param mixed $subject. Either a \DOMDocument, \DOMNode, .html filename, or string containing HTML/text. The function will attempt to detect which. If you import HTML that contains a <body> element, it will only import the inner body
* @throws \Exception the subject was not recognised as any of the types above
* @return $this element
*/
public function import($subject, $forcePHP=false)
{
global $wpgmza;
$node = null;
if($subject instanceof \DOMDocument)
{
if(!$subject->getDocumentElementSafe())
throw new \Exception('Document is empty');
$node = $this->ownerDocument->importNode($subject->getDocumentElementSafe(), true);
}
else if($subject instanceof \DOMNode)
{
$node = $this->ownerDocument->importNode($subject, true);
}
else if(preg_match('/(\.html|\.php)$/i', $subject, $m))
{
// Subject is a filename
if(!file_exists($subject))
throw new \Exception('HTML file not found');
$temp = new DOMDocument('1.0', 'UTF-8');
if($forcePHP || preg_match('/\.php$/i', $m[1]))
$temp->loadPHPFile($subject);
else
$temp->load($subject);
$node = $this->ownerDocument->importNode($temp->getDocumentElementSafe(), true);
}
else if(is_string($subject))
{
if(empty($subject))
return;
if($subject != strip_tags($subject) || preg_match('/&.+;/', $subject))
{
// Subject is a HTML string
$html = DOMDocument::convertUTF8ToHTMLEntities($subject);
$temp = new DOMDocument('1.0', 'UTF-8');
$str = "<div id='domdocument-import-payload___'>" . $html . "</div>";
if($wpgmza->isInDeveloperMode()){
$temp->loadHTML($str);
} else {
@$temp->loadHTML($str);
}
$body = $temp->querySelector('#domdocument-import-payload___');
for($child = $body->firstChild; $child != null; $child = $child->nextSibling)
{
$node = $this->ownerDocument->importNode($child, true);
$this->appendChild($node);
}
}
else
// Subject is a plain string
$this->appendText($subject);
return;
}
else if(empty($subject))
{
return;
}
else
throw new \Exception('Don\'t know how to import "' . print_r($subject, true) . '" in ' . $this->ownerDocument->documentURI . ' on line ' . $this->getLineNo());
if($body = $node->querySelector("body"))
{
// TODO: I don't think a query selector is necessary here. Iterating over the bodies children should be more optimal
$results = $node->querySelectorAll("body>*");
foreach($results as $child)
$this->appendChild($child);
return $results;
}
else
{
$this->appendChild($node);
return $node;
}
return null;
}
/**
* Sets an inline CSS style on this element. If it's already set, the old value will be removed first
* @param string $name the CSS property name eg 'background-color'
* @param string $value the value of the property eg '#ff4400'
* @return $this
*/
public function setInlineStyle($name, $value)
{
$this->removeInlineStyle($name);
$style = $this->getAttribute('style');
$this->setAttribute('style', $style . $name . ':' . $value . ';');
return $this;
}
/**
* Removes the inline CSS style specified by name
* @param string $name the name of the CSS property eg 'background-color'
* @return $this
*/
public function removeInlineStyle($name)
{
if(!$this->hasAttribute('style'))
return;
$style = $this->getAttribute('style');
$rules = preg_split('/\s*;\s*/', $style);
for($i = count($rules) - 1; $i >= 0; $i--)
{
$param = preg_quote($name);
if(preg_match("/^$param\s*:/", trim($rules[$i])))
unset($rules[$i]);
}
$this->setAttribute('style', implode(';', $rules));
return $this;
}
/**
* Check if this element has an inline style by name
* @param string $name the name of the CSS property to test for
*/
public function hasInlineStyle($name)
{
if(!$this->hasAttribute('style'))
return false;
return preg_match("/\s*$name:.*?((;\s*)|$)/", $this->getAttribute('style'));
}
/**
* Gets the value of the inline style by name
* @param string $name the name of the CSS property you want the value for
* @return mixed FALSE if there is no style property or no style with that name exists, or a string containing the property value if it does
*/
public function getInlineStyle($name)
{
if(!$this->hasAttribute('style'))
return false;
$m = null;
if(!preg_match("/\s*$name:(.*?)((;\s*)|$)/", $this->getAttribute('style')))
return false;
return $m[1];
}
/**
* Adds a class to this elements class attribute. It will be ignored if the class already exists
* @param string $name The classname
* @return $this
*/
public function addClass($name)
{
if($this->hasClass($name))
return;
$class = ($this->hasAttribute('class') ? $this->getAttribute('class') : '');
$this->setAttribute('class', $class . (strlen($class) > 0 ? ' ' : '') . $name);
return $this;
}
/**
* Removes the specified class from this nodes class attribute
* @param string $name The classname
* @return $this
*/
public function removeClass($name)
{
if(!$this->hasAttribute('class'))
return;
$class = trim(
preg_replace('/\s{2,}/', ' ',
preg_replace('/\\b' . $name . '\\b/', ' ', $this->getAttribute('class'))
)
);
$this->setAttribute('class', $class);
return $this;
}
/**
* Tests if the specified class exists on this elements class attribute
* @param string $name The classname
* @return boolean FALSE if no such class existst, TRUE if it does
*/
public function hasClass($name)
{
if(!$this->hasAttribute('class'))
return false;
return preg_match('/\\b' . $name . '\\b/', $this->getAttribute('class'));
}
/**
* Populates the target element. If it is a form element, the value will be set according to the elements type/nodeName. If not, the value will be imported instead.
* @param The target element. Usually a descendant of this element
* @param string $key the key of the value we're populating with, used for formatting
* @param string $value the value to populate with
* @param array $formatters an array associative of functions to format certain values with, functions should be specified by key
* @return void
*/
protected function populateElement($target, $key, $value, $formatters)
{
if(!($target instanceof \DOMElement))
throw new \Exception('Argument must be a DOMElement');
switch(strtolower($target->nodeName))
{
case 'textarea':
case 'select':
case 'input':
$target->setValue($value);
break;
case 'img':
$target->setAttribute('src', $value);
break;
default:
if(!is_null($formatters) && isset($formatters[$key]))
$value = $formatters[$key]($value);
if($value instanceof \DateTime)
$value = $value->format('D jS M \'y g:ia');
if($key == 'price')
$value = number_format($value, 2, '.', '');
if(is_object($value))
throw new \Exception('Expected simple type in "'.$key.'" => "'.print_r($value,true).'"');
$target->import( $value );
break;
}
}
/**
* Takes a source object or associative array and optional array of formatting functions, and populates descendant named elements with the values from that source.
* @param mixed $src Associative array or object with the keys and values
* @param array $formatters Optional associative array of functions to format values with. The keys on this array correspond with the keys on $src
* @return DOMElement This element
*/
public function populate($src=null, $formatters=null)
{
$x = new \DOMXPath($this->ownerDocument);
if(!$src)
return $this;
if(is_scalar($src))
{
$this->appendText($src);
return $this;
}
foreach($src as $key => $value)
{
if(is_array($value))
{
$m = $x->query('descendant-or-self::*[@name="' . $key . '[]"]', $this);
if($m->length > 0 && count($value) != $m->length)
{
if($src = $m->item(0)->closest('li,tr'))
{
for($i = $m->length; $i < count($value); $i++)
{
$item = $src->cloneNode(true);
$src->parentNode->appendChild($item);
}
$m = $x->query('descendant-or-self::*[@name="' . $key . '[]"]', $this);
}
else
throw new \Exception('Number of elements must match (' . count($value) . ' != ' . $m->length . ')');
}
for($i = 0; $i < $m->length; $i++)
$this->populateElement($m->item($i), $key, $value[$i], $formatters);
}
else
{
$m = $x->query('descendant-or-self::*[@name="' . $key . '" or @data-name="' . $key . '"]', $this);
for($i = 0; $i < $m->length; $i++)
$this->populateElement($m->item($i), $key, $value, $formatters);
}
}
return $this;
}
public function serializeFormData()
{
$data = array();
foreach($this->querySelectorAll('input, select, textarea') as $input)
{
$name = $input->getAttribute('name');
if(!$name)
continue;
if(preg_match('/nonce/i', $name))
continue; // NB: Do not serialize nonce values
switch($input->getAttribute('type'))
{
case 'checkbox':
if($input->getValue())
$data[$name] = true;
else
$data[$name] = false;
break;
case 'radio':
if($input->getAttribute('checked'))
$data[$name] = $input->getAttribute('value');
break;
default:
$data[$name] = $input->getValue();
break;
}
}
return $data;
}
/**
* Gets the value of this element
* @return mixed A string if the element a text input, textarea or plain node, a boolean if the element is a checkbox or radio, or the value of the selected option if this element is a select
*/
public function getValue()
{
switch(strtolower($this->nodeName))
{
case 'input':
$type = ($this->hasAttribute('type') ? $this->getAttribute('type') : 'text');
switch($type)
{
case 'radio':
case 'checkbox':
return $this->hasAttribute('checked');
break;
default:
return $this->getAttribute('value');
break;
}
break;
case 'select':
$option = $this->querySelector('option[selected]');
if(!$option)
return null;
if($option->hasAttribute('value'))
return $option->getAttribute('value');
default:
return $this->nodeValue;
break;
}
}
/**
* Sets the value of this element. Intended for form elements only. If this element is a textarea, it will be appended as plain text. If this element is a select, it will attempt to select the option with the specified value. If the input is a radio or checkbox, it will set it accordingly. Otherwise, the value will be put into the value attribute
* @throws \Exception If this element is a select, SMART_STRICT_MODE is declared and no option with that value exists
* @throws \Exception If you call this method on a non-form element
* @return This element
*/
public function setValue($value)
{
/*if($this->getAttribute("name") == "wpgmza_gdpr_require_consent_before_load")
{
echo "<pre>";
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
exit;
}*/
switch(strtolower($this->nodeName))
{
case 'textarea':
$this->clear();
$this->appendText( $value );
break;
case 'select':
$deselect = $this->querySelectorAll('option[selected]');
foreach($deselect as $d)
$d->removeAttribute('selected');
if($value === null)
return $this;
$option = $this->querySelector('option[value="' . $value . '"]');
if(!$option)
trigger_error('Option with value "' . $value . '" not found in "' . ($this->getAttribute('name')) . '"', E_USER_WARNING);
else
$option->setAttribute('selected', 'selected');
break;
case 'input':
if(!$this->hasAttribute('type') || $this->getAttribute('type') == 'text')
{
if(is_string($value))
$this->setAttribute('value', $value);
}
else switch(strtolower($this->getAttribute('type')))
{
case 'radio':
if($this->hasAttribute('value') && $this->getAttribute('value') == $value)
$this->setAttribute('checked', 'checked');
else
$this->removeAttribute('checked');
break;
case 'checkbox':
if(!empty($value) && $value != false)
$this->setAttribute('checked', 'checked');
else
$this->removeAttribute('checked');
break;
default:
$this->setAttribute('value', $value);
break;
}
break;
default:
throw new \Exception('Not yet implemented');
$this->nodeValue = $value;
break;
}
return $this;
}
/**
* Appends the specified text to this element, shorthand utility function
* @return \Smart\Element This element
*/
public function appendText($text)
{
$this->appendChild( $this->ownerDocument->createTextNode( $text ) );
return $this;
}
/**
* Utility function to append the specified element after one of this elements children. Will append at the end if after is null
* @param \Smart\Element the element to insert
* @param \Smart\Element one of this elements children, or null
* *@return \Smart\Element this element
*/
public function insertAfter($elem, $after=null)
{
if($after->parentNode && $after->parentNode !== $this)
throw new \Exception('Hierarchy error');
if($after->nextSibling)
$this->insertBefore($elem, $after->nextSibling);
else
$this->appendChild($elem);
return $this;
}
/**
* Clears this element, completely removing all it's contents
* @return \Smart\Element This element
*/
public function clear()
{
while($this->childNodes->length)
$this->removeChild($this->firstChild);
return $this;
}
/**
* Prepends the subject to this element.
* @param $subject element or array of elements
* @return $this element
*/
#[\ReturnTypeWillChange]
public function prepend(...$subject) : void
{
if(is_array($subject))
{
$originalFirst = $this->firstChild;
foreach($subject as $el)
$this->insertBefore($el, $originalFirst);
}
else
$this->insertBefore($subject, $this->firstChild);
// return $this;
}
/**
* Appends the subject to this element.
* @param $subject element or array of elements
*
*/
#[\ReturnTypeWillChange]
public function append(...$subject) : void
{
if(is_array($subject))
{
foreach($subject as $el)
$this->appendChild($el);
}
else
$this->appendChild($subject);
// return $this;
}
/**
* Removes this element from it's parent
* @return \Smart\Element This element
*/
public function remove() : void
{
if($this->parentNode)
$this->parentNode->removeChild($this);
}
}