OwlCyberSecurity - MANAGER
Edit File: Element.php
<?php namespace AmpProject\Dom; use AmpProject\Html\Attribute; use AmpProject\Exception\MaxCssByteCountExceeded; use AmpProject\Optimizer\CssRule; use DOMAttr; use DOMElement; /** * Abstract away some convenience logic for handling DOMElement objects. * * @property Document $ownerDocument The ownerDocument for these elements should always be a Dom\Document. * @property int $inlineStyleByteCount Number of bytes that are consumed by the inline style attribute. * * @package ampproject/amp-toolbox */ final class Element extends DOMElement { /** * Regular expression pattern to match events and actions within an 'on' attribute. * * @var string */ const AMP_EVENT_ACTIONS_REGEX_PATTERN = '/((?<event>[^:;]+):(?<actions>(?:[^;,\(]+(?:\([^\)]+\))?,?)+))+?/'; /** * Regular expression pattern to match individual actions within an event. * * @var string */ const AMP_ACTION_REGEX_PATTERN = '/(?<action>[^(),\s]+(?:\([^\)]+\))?)+/'; /** * Error message to use when the __get() is triggered for an unknown property. * * @var string */ const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Dom\\Element::'; /** * Associative array for lazily-created, cached properties for the document. * * @var array */ private $properties = []; /** * Add CSS styles to the element as an inline style attribute. * * @param string $style CSS style(s) to add to the inline style attribute. * @param bool $prepend Optional. Whether to prepend the new style to existing styles or not. Defaults to false. * @return DOMAttr|false The new or modified DOMAttr or false if an error occurred. * @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded. */ public function addInlineStyle($style, $prepend = false) { $style = trim($style, CssRule::CSS_TRIM_CHARACTERS); $existingStyle = (string)trim($this->getAttribute(Attribute::STYLE)); if (!empty($existingStyle)) { $existingStyle = rtrim($existingStyle, ';') . ';'; } $newStyle = $prepend ? ($style . ';' . $existingStyle) : ($existingStyle . $style); return $this->setAttribute(Attribute::STYLE, $newStyle); } /** * Sets or modifies an attribute. * * @link https://php.net/manual/en/domelement.setattribute.php * @param string $name The name of the attribute. * @param string $value The value of the attribute. * @return DOMAttr|false The new or modified DOMAttr or false if an error occurred. * @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded. */ public function setAttribute($name, $value) { // Make sure $value is always a string and not null. $value = strval($value); if ( $name === Attribute::STYLE && $this->ownerDocument->isCssMaxByteCountEnforced() ) { $newByteCount = strlen($value); if ($this->ownerDocument->getRemainingCustomCssSpace() < ($newByteCount - $this->inlineStyleByteCount)) { throw MaxCssByteCountExceeded::forInlineStyle($this, $value); } $this->ownerDocument->addInlineStyleByteCount($newByteCount - $this->inlineStyleByteCount); $this->inlineStyleByteCount = $newByteCount; return parent::setAttribute(Attribute::STYLE, $value); } return parent::setAttribute($name, $value); } /** * Adds a boolean attribute without value. * * @param string $name The name of the attribute. * @return DOMAttr|false The new or modified DOMAttr or false if an error occurred. * @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded. */ public function addBooleanAttribute($name) { $attribute = new DOMAttr($name); $result = $this->setAttributeNode($attribute); if (!$result instanceof DOMAttr) { return false; } return $result; } /** * Copy one or more attributes from this element to another element. * * @param array|string $attributes Attribute name or array of attribute names to copy. * @param Element $target Target Dom\Element to copy the attributes to. * @param string $defaultSeparator Default separator to use for multiple values if the attribute is not known. */ public function copyAttributes($attributes, Element $target, $defaultSeparator = ',') { foreach ((array) $attributes as $attribute) { if ($this->hasAttribute($attribute)) { $values = $this->getAttribute($attribute); if ($target->hasAttribute($attribute)) { switch ($attribute) { case Attribute::ON: $values = self::mergeAmpActions($target->getAttribute($attribute), $values); break; case Attribute::CLASS_: $values = $target->getAttribute($attribute) . ' ' . $values; break; default: $values = $target->getAttribute($attribute) . $defaultSeparator . $values; } } $target->setAttribute($attribute, $values); } } } /** * Register an AMP action to an event. * * If the element already contains one or more events or actions, the method * will assemble them in a smart way. * * @param string $event Event to trigger the action on. * @param string $action Action to add. */ public function addAmpAction($event, $action) { $eventActionString = "{$event}:{$action}"; if (! $this->hasAttribute(Attribute::ON)) { // There's no "on" attribute yet, so just add it and be done. $this->setAttribute(Attribute::ON, $eventActionString); return; } $this->setAttribute( Attribute::ON, self::mergeAmpActions( $this->getAttribute(Attribute::ON), $eventActionString ) ); } /** * Merge two sets of AMP events & actions. * * @param string $first First event/action string. * @param string $second First event/action string. * @return string Merged event/action string. */ public static function mergeAmpActions($first, $second) { $events = []; foreach ([$first, $second] as $eventActionString) { $matches = []; $results = preg_match_all(self::AMP_EVENT_ACTIONS_REGEX_PATTERN, $eventActionString, $matches); if (! $results || ! isset($matches['event'])) { continue; } foreach ($matches['event'] as $index => $event) { $events[$event][] = $matches['actions'][ $index ]; } } $valueStrings = []; foreach ($events as $event => $actionStringsArray) { $actionsArray = []; array_walk( $actionStringsArray, static function ($actions) use (&$actionsArray) { $matches = []; $results = preg_match_all(self::AMP_ACTION_REGEX_PATTERN, $actions, $matches); if (! $results || ! isset($matches['action'])) { $actionsArray[] = $actions; return; } $actionsArray = array_merge($actionsArray, $matches['action']); } ); $actions = implode(',', array_unique(array_filter($actionsArray))); $valueStrings[] = "{$event}:{$actions}"; } return implode(';', $valueStrings); } /** * Extract this element's HTML attributes and return as an associative array. * * @return string[] The attributes for the passed node, or an empty array if it has no attributes. */ public function getAttributesAsAssocArray() { $attributes = []; if (! $this->hasAttributes()) { return $attributes; } foreach ($this->attributes as $attribute) { $attributes[ $attribute->nodeName ] = $attribute->nodeValue; } return $attributes; } /** * Add one or more HTML element attributes to this element. * * @param string[] $attributes One or more attributes for the node's HTML element. */ public function setAttributes($attributes) { foreach ($attributes as $name => $value) { try { $this->setAttribute($name, $value); } catch (MaxCssByteCountExceeded $e) { /* * Catch a "Invalid Character Error" when libxml is able to parse attributes with invalid characters, * but it throws error when attempting to set them via DOM methods. For example, '...this' can be parsed * as an attribute but it will throw an exception when attempting to setAttribute(). */ continue; } } } /** * Magic getter to implement lazily-created, cached properties for the element. * * @param string $name Name of the property to get. * @return mixed Value of the property, or null if unknown property was requested. */ public function __get($name) { switch ($name) { case 'inlineStyleByteCount': if (!isset($this->properties['inlineStyleByteCount'])) { $this->properties['inlineStyleByteCount'] = strlen((string)$this->getAttribute(Attribute::STYLE)); } return $this->properties['inlineStyleByteCount']; } // Mimic regular PHP behavior for missing notices. trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, E_USER_NOTICE); return null; } /** * Magic setter to implement lazily-created, cached properties for the element. * * @param string $name Name of the property to set. * @param mixed $value Value of the property. */ public function __set($name, $value) { if ($name !== 'inlineStyleByteCount') { // Mimic regular PHP behavior for missing notices. trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, E_USER_NOTICE); return; } $this->properties[$name] = $value; } /** * Magic callback for lazily-created, cached properties for the element. * * @param string $name Name of the property to set. */ public function __isset($name) { if ($name !== 'inlineStyleByteCount') { // Mimic regular PHP behavior for missing notices. trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, E_USER_NOTICE); return false; } return isset($this->properties[$name]); } }