OwlCyberSecurity - MANAGER
Edit File: LinkManager.php
<?php namespace AmpProject\Dom; use AmpProject\Html\Attribute; use AmpProject\Exception\FailedToCreateLink; use AmpProject\Html\RequestDestination; use AmpProject\Html\Tag; use DOMNode; /** * Link manager class that is used to manage the <link> tags within a document's <head>. * * These can be used for example to give the browser hints about how to prioritize resources. * * @package ampproject/amp-toolbox */ final class LinkManager { /** * List of relations currently managed by the link manager. * * @var array<string> */ const MANAGED_RELATIONS = [ Attribute::REL_DNS_PREFETCH, Attribute::REL_MODULEPRELOAD, Attribute::REL_PRECONNECT, Attribute::REL_PREFETCH, Attribute::REL_PRELOAD, Attribute::REL_PRERENDER, ]; /** * Document to manage the links for. * * @var Document */ private $document; /** * Reference node to attach the resource hint to. * * @var DOMNode|null */ private $referenceNode; /** * Collection of links already attached to the document. * * The key of the array is a concatenation of the HREF and the REL attributes. * * @var Element[] */ private $links = []; /** * LinkManager constructor. * * @param Document $document Document to manage the links for. */ public function __construct(Document $document) { $this->document = $document; $this->detectExistingLinks(); } private function detectExistingLinks() { $node = $this->document->head->firstChild; while ($node) { $nextSibling = $node->nextSibling; if ( ! $node instanceof Element || $node->tagName !== Tag::LINK ) { $node = $nextSibling; continue; } $key = $this->getKey($node); if ($key !== '') { $this->links[$this->getKey($node)] = $node; } $node = $nextSibling; } } /** * Get the key to use for storing the element in the links cache. * * @param Element $element Element to get the key for. * @return string Key to use. Returns an empty string for invalid elements. */ private function getKey(Element $element) { $href = $element->getAttribute(Attribute::HREF); $rel = $element->getAttribute(Attribute::REL); if (empty($href) || ! in_array($rel, self::MANAGED_RELATIONS, true)) { return ''; } return "{$href}{$rel}"; } /** * Add a dns-prefetch resource hint. * * @see https://www.w3.org/TR/resource-hints/#dns-prefetch * * @param string $href Origin to prefetch the DNS for. */ public function addDnsPrefetch($href) { $this->add(Attribute::REL_DNS_PREFETCH, $href); } /** * Add a modulepreload declarative fetch primitive. * * @see https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload * * @param string $href Modular resource to preload. * @param string|null $type Optional. Type of the resource. Defaults to not specified, which equals 'script'. * @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a * boolean crossorigin flag, or a string to set a specific crossorigin strategy. * Allowed values are 'anonymous' and 'use-credentials'. Defaults to true. */ public function addModulePreload($href, $type = null, $crossorigin = true) { $attributes = []; if ($type !== null) { $attributes = [Attribute::AS_ => $type]; } if ($crossorigin !== false) { $attributes[Attribute::CROSSORIGIN] = is_string($crossorigin) ? $crossorigin : null; } $this->add(Attribute::REL_MODULEPRELOAD, $href, $attributes); } /** * Add a preconnect resource hint. * * @see https://www.w3.org/TR/resource-hints/#dfn-preconnect * * @param string $href Origin to preconnect to. * @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a * boolean crossorigin flag, or a string to set a specific crossorigin strategy. * Allowed values are 'anonymous' and 'use-credentials'. Defaults to true. */ public function addPreconnect($href, $crossorigin = true) { $this->add( Attribute::REL_PRECONNECT, $href, $crossorigin !== false ? [Attribute::CROSSORIGIN => (is_string($crossorigin) ? $crossorigin : null)] : [] ); // Use dns-prefetch as fallback for browser that don't support preconnect. // See https://web.dev/preconnect-and-dns-prefetch/#resolve-domain-name-early-with-reldns-prefetch. $this->addDnsPrefetch($href); } /** * Add a prefetch resource hint. * * @see https://www.w3.org/TR/resource-hints/#prefetch * * @param string $href URL to the resource to prefetch. * @param string $type Optional. Type of the resource. Defaults to type 'image'. * @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a * boolean crossorigin flag, or a string to set a specific crossorigin strategy. * Allowed values are 'anonymous' and 'use-credentials'. Defaults to true. */ public function addPrefetch($href, $type = RequestDestination::IMAGE, $crossorigin = true) { // TODO: Should we enforce a valid $type here? $attributes = [Attribute::AS_ => $type]; if ($crossorigin !== false) { $attributes[Attribute::CROSSORIGIN] = is_string($crossorigin) ? $crossorigin : null; } $this->add(Attribute::REL_PREFETCH, $href, $attributes); } /** * Add a preload declarative fetch primitive. * * @see https://www.w3.org/TR/preload/ * * @param string $href Resource to preload. * @param string $type Optional. Type of the resource. Defaults to type 'image'. * @param string|null $media Optional. Media query to add to the preload. Defaults to none. * @param bool|string $crossorigin Optional. Whether and how to configure CORS. Accepts a boolean for adding a * boolean crossorigin flag, or a string to set a specific crossorigin strategy. * Allowed values are 'anonymous' and 'use-credentials'. Defaults to true. */ public function addPreload($href, $type = RequestDestination::IMAGE, $media = null, $crossorigin = true) { // TODO: Should we enforce a valid $type here? $attributes = [Attribute::AS_ => $type]; if (!empty($media)) { $attributes[Attribute::MEDIA] = $media; } if ($crossorigin !== false) { $attributes[Attribute::CROSSORIGIN] = is_string($crossorigin) ? $crossorigin : null; } $this->add(Attribute::REL_PRELOAD, $href, $attributes); } /** * Add a prerender resource hint. * * @see https://www.w3.org/TR/resource-hints/#prerender * * @param string $href URL of the page to prerender. */ public function addPrerender($href) { $this->add(Attribute::REL_PRERENDER, $href); } /** * Add a link to the document. * * @param string $rel A 'rel' string. * @param string $href URL to link to. * @param string[] $attributes Associative array of attributes and their values. */ public function add($rel, $href, $attributes = []) { $link = $this->document->createElement(Tag::LINK); $link->setAttribute(Attribute::REL, $rel); $link->setAttribute(Attribute::HREF, $href); foreach ($attributes as $attribute => $value) { $link->setAttribute($attribute, $value); } $this->remove($rel, $href); if (!isset($this->referenceNode)) { $this->referenceNode = $this->document->viewport; } if ($this->referenceNode) { $link = $this->document->head->insertBefore($link, $this->referenceNode->nextSibling); } else { $link = $this->document->head->appendChild($link); } if (! $link instanceof Element) { throw FailedToCreateLink::forLink($link); } $this->links[$this->getKey($link)] = $link; $this->referenceNode = $link; } /** * Get a specific link from the link manager. * * @param string $rel Relation to fetch. * @param string $href Reference to fetch. * @return Element|null Requested link as a Dom\Element, or null if not found. */ public function get($rel, $href) { $key = "{$href}{$rel}"; if (! array_key_exists($key, $this->links)) { return null; } return $this->links[$key]; } /** * Remove a specific link from the document. * * @param string $rel Relation of the link to remove. * @param string $href Reference of the link to remove. */ public function remove($rel, $href) { $key = "{$href}{$rel}"; if (! array_key_exists($key, $this->links)) { return; } $link = $this->links[$key]; $link->parentNode->removeChild($link); unset($this->links[$key]); } }