*/ class FeedSet extends ArrayObject { /** @var null|string */ public $rss; /** @var null|string */ public $rdf; /** @var null|string */ public $atom; /** * Import a DOMNodeList from any document containing a set of links * for alternate versions of a document, which will normally refer to * RSS/RDF/Atom feeds for the current document. * * All such links are stored internally, however the first instance of * each RSS, RDF or Atom type has its URI stored as a public property * as a shortcut where the use case is simply to get a quick feed ref. * * Note that feeds are not loaded at this point, but will be lazy * loaded automatically when each links 'feed' array key is accessed. * * @param string $uri * @return void */ public function addLinks(DOMNodeList $links, $uri) { foreach ($links as $link) { /** @var DOMElement $link */ if ( strtolower($link->getAttribute('rel')) !== 'alternate' || ! $link->getAttribute('type') || ! $link->getAttribute('href') ) { continue; } if (null === $this->rss && $link->getAttribute('type') === 'application/rss+xml') { $this->rss = $this->absolutiseUri(trim($link->getAttribute('href')), $uri); } elseif (null === $this->atom && $link->getAttribute('type') === 'application/atom+xml') { $this->atom = $this->absolutiseUri(trim($link->getAttribute('href')), $uri); } elseif (null === $this->rdf && $link->getAttribute('type') === 'application/rdf+xml') { $this->rdf = $this->absolutiseUri(trim($link->getAttribute('href')), $uri); } $this[] = new static([ 'rel' => 'alternate', 'type' => $link->getAttribute('type'), 'href' => $this->absolutiseUri(trim($link->getAttribute('href')), $uri), 'title' => $link->getAttribute('title'), ]); } } /** * Attempt to turn a relative URI into an absolute URI * * @param string $link * @param null|string $uri OPTIONAL * @return null|string absolutised link or null if invalid */ protected function absolutiseUri($link, $uri = null) { $linkUri = Uri::factory($link); if ($linkUri->isAbsolute()) { // invalid absolute link can not be recovered return $linkUri->isValid() ? $link : null; } $scheme = 'http'; if ($uri !== null) { $uri = Uri::factory($uri); $scheme = $uri->getScheme() ?: $scheme; } if ($linkUri->getHost()) { $link = $this->resolveSchemeRelativeUri($link, $scheme); } elseif ($uri !== null) { $link = $this->resolveRelativeUri($link, $scheme, $uri->getHost(), $uri->getPath()); } if (! Uri::factory($link)->isValid()) { return null; } return $link; } /** * Resolves scheme relative link to absolute * * @param string $link * @param string $scheme * @return string */ private function resolveSchemeRelativeUri($link, $scheme) { $link = ltrim($link, '/'); return sprintf('%s://%s', $scheme, $link); } /** * Resolves relative link to absolute * * @param string $link * @param string $scheme * @param string $host * @param string $uriPath * @return string */ private function resolveRelativeUri($link, $scheme, $host, $uriPath) { if ($link[0] !== '/') { $link = $uriPath . '/' . $link; } return sprintf( '%s://%s/%s', $scheme, $host, $this->canonicalizePath($link) ); } /** * Canonicalize relative path * * @param string $path * @return string */ protected function canonicalizePath($path) { $parts = array_filter(explode('/', $path)); $absolutes = []; foreach ($parts as $part) { if ('.' === $part) { continue; } if ('..' === $part) { array_pop($absolutes); } else { $absolutes[] = $part; } } return implode('/', $absolutes); } /** * @inheritDoc * * Supports lazy loading of feeds using Reader::import() but * delegates any other operations to the parent class. */ #[ReturnTypeWillChange] public function offsetGet($offset) { if ($offset === 'feed' && ! $this->offsetExists('feed')) { if (! $this->offsetExists('href')) { return; } $feed = Reader::import($this->offsetGet('href')); $this->offsetSet('feed', $feed); return $feed; } return parent::offsetGet($offset); } }