Current File : /home/pacjaorg/public_html/kmm/libraries/src/Feed/FeedParser.php |
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2012 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Feed;
use Joomla\CMS\Feed\Parser\NamespaceParserInterface;
use Joomla\CMS\Filter\InputFilter;
// phpcs:disable PSR1.Files.SideEffects
\defined('JPATH_PLATFORM') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Feed Parser class.
*
* @since 3.1.4
*/
abstract class FeedParser
{
/**
* The feed element name for the entry elements.
*
* @var string
* @since 3.1.4
*/
protected $entryElementName = 'entry';
/**
* Array of NamespaceParserInterface objects
*
* @var array
* @since 3.1.4
*/
protected $namespaces = [];
/**
* The XMLReader stream object for the feed.
*
* @var \XMLReader
* @since 3.1.4
*/
protected $stream;
/**
* The InputFilter
*
* @var InputFilter
* @since 3.9.25
*/
protected $inputFilter;
/**
* Constructor.
*
* @param \XMLReader $stream The XMLReader stream object for the feed.
* @param InputFilter $inputFilter The InputFilter object to be used
*
* @since 3.1.4
*/
public function __construct(\XMLReader $stream, InputFilter $inputFilter = null)
{
$this->stream = $stream;
$this->inputFilter = $inputFilter ?: InputFilter::getInstance([], [], 1, 1);
}
/**
* Method to parse the feed into a JFeed object.
*
* @return Feed
*
* @since 3.1.4
*/
public function parse()
{
$feed = new Feed();
// Detect the feed version.
$this->initialise();
// Let's get this party started...
do {
// Expand the element for processing.
$el = new \SimpleXMLElement($this->stream->readOuterXml());
// Get the list of namespaces used within this element.
$ns = $el->getNamespaces(true);
// Get an array of available namespace objects for the element.
$namespaces = [];
foreach ($ns as $prefix => $uri) {
// Ignore the empty namespace prefix.
if (empty($prefix)) {
continue;
}
// Get the necessary namespace objects for the element.
$namespace = $this->fetchNamespace($prefix);
if ($namespace) {
$namespaces[] = $namespace;
}
}
// Process the element.
$this->processElement($feed, $el, $namespaces);
// Skip over this element's children since it has been processed.
$this->moveToClosingElement();
} while ($this->moveToNextElement());
return $feed;
}
/**
* Method to register a namespace handler object.
*
* @param string $prefix The XML namespace prefix for which to register the namespace object.
* @param NamespaceParserInterface $namespace The namespace object to register.
*
* @return FeedParser
*
* @since 3.1.4
*/
public function registerNamespace($prefix, NamespaceParserInterface $namespace)
{
$this->namespaces[$prefix] = $namespace;
return $this;
}
/**
* Method to initialise the feed for parsing. If child parsers need to detect versions or other
* such things this is where you'll want to implement that logic.
*
* @return void
*
* @since 3.1.4
*/
abstract protected function initialise();
/**
* Method to parse a specific feed element.
*
* @param Feed $feed The Feed object being built from the parsed feed.
* @param \SimpleXMLElement $el The current XML element object to handle.
* @param array $namespaces The array of relevant namespace objects to process for the element.
*
* @return void
*
* @since 3.1.4
*/
protected function processElement(Feed $feed, \SimpleXMLElement $el, array $namespaces)
{
// Build the internal method name.
$method = 'handle' . ucfirst($el->getName());
// If we are dealing with an item then it is feed entry time.
if ($el->getName() == $this->entryElementName) {
// Create a new feed entry for the item.
$entry = new FeedEntry();
// First call the internal method.
$this->processFeedEntry($entry, $el);
foreach ($namespaces as $namespace) {
if ($namespace instanceof NamespaceParserInterface) {
$namespace->processElementForFeedEntry($entry, $el);
}
}
// Add the new entry to the feed.
$feed->addEntry($entry);
return;
}
// Otherwise we treat it like any other element.
// First call the internal method.
if (\is_callable([$this, $method])) {
$this->$method($feed, $el);
}
foreach ($namespaces as $namespace) {
if ($namespace instanceof NamespaceParserInterface) {
$namespace->processElementForFeed($feed, $el);
}
}
}
/**
* Method to get a namespace object for a given namespace prefix.
*
* @param string $prefix The XML prefix for which to fetch the namespace object.
*
* @return mixed NamespaceParserInterface or false if none exists.
*
* @since 3.1.4
*/
protected function fetchNamespace($prefix)
{
if (isset($this->namespaces[$prefix])) {
return $this->namespaces[$prefix];
}
$className = \get_class($this) . ucfirst($prefix);
if (class_exists($className)) {
$this->namespaces[$prefix] = new $className();
return $this->namespaces[$prefix];
}
return false;
}
/**
* Method to move the stream parser to the next XML element node.
*
* @param string $name The name of the element for which to move the stream forward until is found.
*
* @return boolean True if the stream parser is on an XML element node.
*
* @since 3.1.4
*/
protected function moveToNextElement($name = null)
{
// Only keep looking until the end of the stream.
while ($this->stream->read()) {
// As soon as we get to the next ELEMENT node we are done.
if ($this->stream->nodeType == \XMLReader::ELEMENT) {
// If we are looking for a specific name make sure we have it.
if (isset($name) && ($this->stream->name != $name)) {
continue;
}
return true;
}
}
return false;
}
/**
* Method to move the stream parser to the closing XML node of the current element.
*
* @return void
*
* @since 3.1.4
* @throws \RuntimeException If the closing tag cannot be found.
*/
protected function moveToClosingElement()
{
// If we are on a self-closing tag then there is nothing to do.
if ($this->stream->isEmptyElement) {
return;
}
// Get the name and depth for the current node so that we can match the closing node.
$name = $this->stream->name;
$depth = $this->stream->depth;
// Only keep looking until the end of the stream.
while ($this->stream->read()) {
// If we have an END_ELEMENT node with the same name and depth as the node we started with we have a bingo. :-)
if (($this->stream->name == $name) && ($this->stream->depth == $depth) && ($this->stream->nodeType == \XMLReader::END_ELEMENT)) {
return;
}
}
throw new \RuntimeException('Unable to find the closing XML node.');
}
}