You are here

class Reader in Zircon Profile 8

Same name in this branch
  1. 8 vendor/symfony/css-selector/Parser/Reader.php \Symfony\Component\CssSelector\Parser\Reader
  2. 8 vendor/zendframework/zend-feed/src/Reader/Reader.php \Zend\Feed\Reader\Reader
Same name and namespace in other branches
  1. 8.0 vendor/zendframework/zend-feed/src/Reader/Reader.php \Zend\Feed\Reader\Reader

Hierarchy

Expanded class hierarchy of Reader

1 file declares its use of Reader
DefaultParser.php in core/modules/aggregator/src/Plugin/aggregator/parser/DefaultParser.php
Contains \Drupal\aggregator\Plugin\aggregator\parser\DefaultParser.

File

vendor/zendframework/zend-feed/src/Reader/Reader.php, line 20

Namespace

Zend\Feed\Reader
View source
class Reader implements ReaderImportInterface {

  /**
   * Namespace constants
   */
  const NAMESPACE_ATOM_03 = 'http://purl.org/atom/ns#';
  const NAMESPACE_ATOM_10 = 'http://www.w3.org/2005/Atom';
  const NAMESPACE_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  const NAMESPACE_RSS_090 = 'http://my.netscape.com/rdf/simple/0.9/';
  const NAMESPACE_RSS_10 = 'http://purl.org/rss/1.0/';

  /**
   * Feed type constants
   */
  const TYPE_ANY = 'any';
  const TYPE_ATOM_03 = 'atom-03';
  const TYPE_ATOM_10 = 'atom-10';
  const TYPE_ATOM_10_ENTRY = 'atom-10-entry';
  const TYPE_ATOM_ANY = 'atom';
  const TYPE_RSS_090 = 'rss-090';
  const TYPE_RSS_091 = 'rss-091';
  const TYPE_RSS_091_NETSCAPE = 'rss-091n';
  const TYPE_RSS_091_USERLAND = 'rss-091u';
  const TYPE_RSS_092 = 'rss-092';
  const TYPE_RSS_093 = 'rss-093';
  const TYPE_RSS_094 = 'rss-094';
  const TYPE_RSS_10 = 'rss-10';
  const TYPE_RSS_20 = 'rss-20';
  const TYPE_RSS_ANY = 'rss';

  /**
   * Cache instance
   *
   * @var CacheStorage
   */
  protected static $cache = null;

  /**
   * HTTP client object to use for retrieving feeds
   *
   * @var ZendHttp\Client
   */
  protected static $httpClient = null;

  /**
   * Override HTTP PUT and DELETE request methods?
   *
   * @var bool
   */
  protected static $httpMethodOverride = false;
  protected static $httpConditionalGet = false;
  protected static $extensionManager = null;
  protected static $extensions = [
    'feed' => [
      'DublinCore\\Feed',
      'Atom\\Feed',
    ],
    'entry' => [
      'Content\\Entry',
      'DublinCore\\Entry',
      'Atom\\Entry',
    ],
    'core' => [
      'DublinCore\\Feed',
      'Atom\\Feed',
      'Content\\Entry',
      'DublinCore\\Entry',
      'Atom\\Entry',
    ],
  ];

  /**
   * Get the Feed cache
   *
   * @return CacheStorage
   */
  public static function getCache() {
    return static::$cache;
  }

  /**
   * Set the feed cache
   *
   * @param  CacheStorage $cache
   * @return void
   */
  public static function setCache(CacheStorage $cache) {
    static::$cache = $cache;
  }

  /**
   * Set the HTTP client instance
   *
   * Sets the HTTP client object to use for retrieving the feeds.
   *
   * @param  ZendHttp\Client $httpClient
   * @return void
   */
  public static function setHttpClient(ZendHttp\Client $httpClient) {
    static::$httpClient = $httpClient;
  }

  /**
   * Gets the HTTP client object. If none is set, a new ZendHttp\Client will be used.
   *
   * @return ZendHttp\Client
   */
  public static function getHttpClient() {
    if (!static::$httpClient instanceof ZendHttp\Client) {
      static::$httpClient = new ZendHttp\Client();
    }
    return static::$httpClient;
  }

  /**
   * Toggle using POST instead of PUT and DELETE HTTP methods
   *
   * Some feed implementations do not accept PUT and DELETE HTTP
   * methods, or they can't be used because of proxies or other
   * measures. This allows turning on using POST where PUT and
   * DELETE would normally be used; in addition, an
   * X-Method-Override header will be sent with a value of PUT or
   * DELETE as appropriate.
   *
   * @param  bool $override Whether to override PUT and DELETE.
   * @return void
   */
  public static function setHttpMethodOverride($override = true) {
    static::$httpMethodOverride = $override;
  }

  /**
   * Get the HTTP override state
   *
   * @return bool
   */
  public static function getHttpMethodOverride() {
    return static::$httpMethodOverride;
  }

  /**
   * Set the flag indicating whether or not to use HTTP conditional GET
   *
   * @param  bool $bool
   * @return void
   */
  public static function useHttpConditionalGet($bool = true) {
    static::$httpConditionalGet = $bool;
  }

  /**
   * Import a feed by providing a URI
   *
   * @param  string $uri The URI to the feed
   * @param  string $etag OPTIONAL Last received ETag for this resource
   * @param  string $lastModified OPTIONAL Last-Modified value for this resource
   * @return Feed\FeedInterface
   * @throws Exception\RuntimeException
   */
  public static function import($uri, $etag = null, $lastModified = null) {
    $cache = self::getCache();
    $client = self::getHttpClient();
    $client
      ->resetParameters();
    $headers = new ZendHttp\Headers();
    $client
      ->setHeaders($headers);
    $client
      ->setUri($uri);
    $cacheId = 'Zend_Feed_Reader_' . md5($uri);
    if (static::$httpConditionalGet && $cache) {
      $data = $cache
        ->getItem($cacheId);
      if ($data) {
        if ($etag === null) {
          $etag = $cache
            ->getItem($cacheId . '_etag');
        }
        if ($lastModified === null) {
          $lastModified = $cache
            ->getItem($cacheId . '_lastmodified');
        }
        if ($etag) {
          $headers
            ->addHeaderLine('If-None-Match', $etag);
        }
        if ($lastModified) {
          $headers
            ->addHeaderLine('If-Modified-Since', $lastModified);
        }
      }
      $response = $client
        ->send();
      if ($response
        ->getStatusCode() !== 200 && $response
        ->getStatusCode() !== 304) {
        throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response
          ->getStatusCode());
      }
      if ($response
        ->getStatusCode() == 304) {
        $responseXml = $data;
      }
      else {
        $responseXml = $response
          ->getBody();
        $cache
          ->setItem($cacheId, $responseXml);
        if ($response
          ->getHeaders()
          ->get('ETag')) {
          $cache
            ->setItem($cacheId . '_etag', $response
            ->getHeaders()
            ->get('ETag')
            ->getFieldValue());
        }
        if ($response
          ->getHeaders()
          ->get('Last-Modified')) {
          $cache
            ->setItem($cacheId . '_lastmodified', $response
            ->getHeaders()
            ->get('Last-Modified')
            ->getFieldValue());
        }
      }
      return static::importString($responseXml);
    }
    elseif ($cache) {
      $data = $cache
        ->getItem($cacheId);
      if ($data) {
        return static::importString($data);
      }
      $response = $client
        ->send();
      if ((int) $response
        ->getStatusCode() !== 200) {
        throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response
          ->getStatusCode());
      }
      $responseXml = $response
        ->getBody();
      $cache
        ->setItem($cacheId, $responseXml);
      return static::importString($responseXml);
    }
    else {
      $response = $client
        ->send();
      if ((int) $response
        ->getStatusCode() !== 200) {
        throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response
          ->getStatusCode());
      }
      $reader = static::importString($response
        ->getBody());
      $reader
        ->setOriginalSourceUri($uri);
      return $reader;
    }
  }

  /**
   * Import a feed from a remote URI
   *
   * Performs similarly to import(), except it uses the HTTP client passed to
   * the method, and does not take into account cached data.
   *
   * Primary purpose is to make it possible to use the Reader with alternate
   * HTTP client implementations.
   *
   * @param  string $uri
   * @param  Http\ClientInterface $client
   * @return self
   * @throws Exception\RuntimeException if response is not an Http\ResponseInterface
   */
  public static function importRemoteFeed($uri, Http\ClientInterface $client) {
    $response = $client
      ->get($uri);
    if (!$response instanceof Http\ResponseInterface) {
      throw new Exception\RuntimeException(sprintf('Did not receive a %s\\Http\\ResponseInterface from the provided HTTP client; received "%s"', __NAMESPACE__, is_object($response) ? get_class($response) : gettype($response)));
    }
    if ((int) $response
      ->getStatusCode() !== 200) {
      throw new Exception\RuntimeException('Feed failed to load, got response code ' . $response
        ->getStatusCode());
    }
    $reader = static::importString($response
      ->getBody());
    $reader
      ->setOriginalSourceUri($uri);
    return $reader;
  }

  /**
   * Import a feed from a string
   *
   * @param  string $string
   * @return Feed\FeedInterface
   * @throws Exception\InvalidArgumentException
   * @throws Exception\RuntimeException
   */
  public static function importString($string) {
    $trimmed = trim($string);
    if (!is_string($string) || empty($trimmed)) {
      throw new Exception\InvalidArgumentException('Only non empty strings are allowed as input');
    }
    $libxmlErrflag = libxml_use_internal_errors(true);
    $oldValue = libxml_disable_entity_loader(true);
    $dom = new DOMDocument();
    $status = $dom
      ->loadXML(trim($string));
    foreach ($dom->childNodes as $child) {
      if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
        throw new Exception\InvalidArgumentException('Invalid XML: Detected use of illegal DOCTYPE');
      }
    }
    libxml_disable_entity_loader($oldValue);
    libxml_use_internal_errors($libxmlErrflag);
    if (!$status) {

      // Build error message
      $error = libxml_get_last_error();
      if ($error && $error->message) {
        $error->message = trim($error->message);
        $errormsg = "DOMDocument cannot parse XML: {$error->message}";
      }
      else {
        $errormsg = "DOMDocument cannot parse XML: Please check the XML document's validity";
      }
      throw new Exception\RuntimeException($errormsg);
    }
    $type = static::detectType($dom);
    static::registerCoreExtensions();
    if (substr($type, 0, 3) == 'rss') {
      $reader = new Feed\Rss($dom, $type);
    }
    elseif (substr($type, 8, 5) == 'entry') {
      $reader = new Entry\Atom($dom->documentElement, 0, self::TYPE_ATOM_10);
    }
    elseif (substr($type, 0, 4) == 'atom') {
      $reader = new Feed\Atom($dom, $type);
    }
    else {
      throw new Exception\RuntimeException('The URI used does not point to a ' . 'valid Atom, RSS or RDF feed that Zend\\Feed\\Reader can parse.');
    }
    return $reader;
  }

  /**
   * Imports a feed from a file located at $filename.
   *
   * @param  string $filename
   * @throws Exception\RuntimeException
   * @return Feed\FeedInterface
   */
  public static function importFile($filename) {
    ErrorHandler::start();
    $feed = file_get_contents($filename);
    $err = ErrorHandler::stop();
    if ($feed === false) {
      throw new Exception\RuntimeException("File '{$filename}' could not be loaded", 0, $err);
    }
    return static::importString($feed);
  }

  /**
   * Find feed links
   *
   * @param $uri
   * @return FeedSet
   * @throws Exception\RuntimeException
   */
  public static function findFeedLinks($uri) {
    $client = static::getHttpClient();
    $client
      ->setUri($uri);
    $response = $client
      ->send();
    if ($response
      ->getStatusCode() !== 200) {
      throw new Exception\RuntimeException("Failed to access {$uri}, got response code " . $response
        ->getStatusCode());
    }
    $responseHtml = $response
      ->getBody();
    $libxmlErrflag = libxml_use_internal_errors(true);
    $oldValue = libxml_disable_entity_loader(true);
    $dom = new DOMDocument();
    $status = $dom
      ->loadHTML(trim($responseHtml));
    libxml_disable_entity_loader($oldValue);
    libxml_use_internal_errors($libxmlErrflag);
    if (!$status) {

      // Build error message
      $error = libxml_get_last_error();
      if ($error && $error->message) {
        $error->message = trim($error->message);
        $errormsg = "DOMDocument cannot parse HTML: {$error->message}";
      }
      else {
        $errormsg = "DOMDocument cannot parse HTML: Please check the XML document's validity";
      }
      throw new Exception\RuntimeException($errormsg);
    }
    $feedSet = new FeedSet();
    $links = $dom
      ->getElementsByTagName('link');
    $feedSet
      ->addLinks($links, $uri);
    return $feedSet;
  }

  /**
   * Detect the feed type of the provided feed
   *
   * @param  Feed\AbstractFeed|DOMDocument|string $feed
   * @param  bool $specOnly
   * @return string
   * @throws Exception\InvalidArgumentException
   * @throws Exception\RuntimeException
   */
  public static function detectType($feed, $specOnly = false) {
    if ($feed instanceof Feed\AbstractFeed) {
      $dom = $feed
        ->getDomDocument();
    }
    elseif ($feed instanceof DOMDocument) {
      $dom = $feed;
    }
    elseif (is_string($feed) && !empty($feed)) {
      ErrorHandler::start(E_NOTICE | E_WARNING);
      ini_set('track_errors', 1);
      $oldValue = libxml_disable_entity_loader(true);
      $dom = new DOMDocument();
      $status = $dom
        ->loadXML($feed);
      foreach ($dom->childNodes as $child) {
        if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
          throw new Exception\InvalidArgumentException('Invalid XML: Detected use of illegal DOCTYPE');
        }
      }
      libxml_disable_entity_loader($oldValue);
      ini_restore('track_errors');
      ErrorHandler::stop();
      if (!$status) {
        if (!isset($phpErrormsg)) {
          if (function_exists('xdebug_is_enabled')) {
            $phpErrormsg = '(error message not available, when XDebug is running)';
          }
          else {
            $phpErrormsg = '(error message not available)';
          }
        }
        throw new Exception\RuntimeException("DOMDocument cannot parse XML: {$phpErrormsg}");
      }
    }
    else {
      throw new Exception\InvalidArgumentException('Invalid object/scalar provided: must' . ' be of type Zend\\Feed\\Reader\\Feed, DomDocument or string');
    }
    $xpath = new DOMXPath($dom);
    if ($xpath
      ->query('/rss')->length) {
      $type = self::TYPE_RSS_ANY;
      $version = $xpath
        ->evaluate('string(/rss/@version)');
      if (strlen($version) > 0) {
        switch ($version) {
          case '2.0':
            $type = self::TYPE_RSS_20;
            break;
          case '0.94':
            $type = self::TYPE_RSS_094;
            break;
          case '0.93':
            $type = self::TYPE_RSS_093;
            break;
          case '0.92':
            $type = self::TYPE_RSS_092;
            break;
          case '0.91':
            $type = self::TYPE_RSS_091;
            break;
        }
      }
      return $type;
    }
    $xpath
      ->registerNamespace('rdf', self::NAMESPACE_RDF);
    if ($xpath
      ->query('/rdf:RDF')->length) {
      $xpath
        ->registerNamespace('rss', self::NAMESPACE_RSS_10);
      if ($xpath
        ->query('/rdf:RDF/rss:channel')->length || $xpath
        ->query('/rdf:RDF/rss:image')->length || $xpath
        ->query('/rdf:RDF/rss:item')->length || $xpath
        ->query('/rdf:RDF/rss:textinput')->length) {
        return self::TYPE_RSS_10;
      }
      $xpath
        ->registerNamespace('rss', self::NAMESPACE_RSS_090);
      if ($xpath
        ->query('/rdf:RDF/rss:channel')->length || $xpath
        ->query('/rdf:RDF/rss:image')->length || $xpath
        ->query('/rdf:RDF/rss:item')->length || $xpath
        ->query('/rdf:RDF/rss:textinput')->length) {
        return self::TYPE_RSS_090;
      }
    }
    $xpath
      ->registerNamespace('atom', self::NAMESPACE_ATOM_10);
    if ($xpath
      ->query('//atom:feed')->length) {
      return self::TYPE_ATOM_10;
    }
    if ($xpath
      ->query('//atom:entry')->length) {
      if ($specOnly == true) {
        return self::TYPE_ATOM_10;
      }
      else {
        return self::TYPE_ATOM_10_ENTRY;
      }
    }
    $xpath
      ->registerNamespace('atom', self::NAMESPACE_ATOM_03);
    if ($xpath
      ->query('//atom:feed')->length) {
      return self::TYPE_ATOM_03;
    }
    return self::TYPE_ANY;
  }

  /**
   * Set plugin manager for use with Extensions
   *
   * @param ExtensionManagerInterface $extensionManager
   */
  public static function setExtensionManager(ExtensionManagerInterface $extensionManager) {
    static::$extensionManager = $extensionManager;
  }

  /**
   * Get plugin manager for use with Extensions
   *
   * @return ExtensionManagerInterface
   */
  public static function getExtensionManager() {
    if (!isset(static::$extensionManager)) {
      static::setExtensionManager(new StandaloneExtensionManager());
    }
    return static::$extensionManager;
  }

  /**
   * Register an Extension by name
   *
   * @param  string $name
   * @return void
   * @throws Exception\RuntimeException if unable to resolve Extension class
   */
  public static function registerExtension($name) {
    $feedName = $name . '\\Feed';
    $entryName = $name . '\\Entry';
    $manager = static::getExtensionManager();
    if (static::isRegistered($name)) {
      if ($manager
        ->has($feedName) || $manager
        ->has($entryName)) {
        return;
      }
    }
    if (!$manager
      ->has($feedName) && !$manager
      ->has($entryName)) {
      throw new Exception\RuntimeException('Could not load extension: ' . $name . ' using Plugin Loader. Check prefix paths are configured and extension exists.');
    }
    if ($manager
      ->has($feedName)) {
      static::$extensions['feed'][] = $feedName;
    }
    if ($manager
      ->has($entryName)) {
      static::$extensions['entry'][] = $entryName;
    }
  }

  /**
   * Is a given named Extension registered?
   *
   * @param  string $extensionName
   * @return bool
   */
  public static function isRegistered($extensionName) {
    $feedName = $extensionName . '\\Feed';
    $entryName = $extensionName . '\\Entry';
    if (in_array($feedName, static::$extensions['feed']) || in_array($entryName, static::$extensions['entry'])) {
      return true;
    }
    return false;
  }

  /**
   * Get a list of extensions
   *
   * @return array
   */
  public static function getExtensions() {
    return static::$extensions;
  }

  /**
   * Reset class state to defaults
   *
   * @return void
   */
  public static function reset() {
    static::$cache = null;
    static::$httpClient = null;
    static::$httpMethodOverride = false;
    static::$httpConditionalGet = false;
    static::$extensionManager = null;
    static::$extensions = [
      'feed' => [
        'DublinCore\\Feed',
        'Atom\\Feed',
      ],
      'entry' => [
        'Content\\Entry',
        'DublinCore\\Entry',
        'Atom\\Entry',
      ],
      'core' => [
        'DublinCore\\Feed',
        'Atom\\Feed',
        'Content\\Entry',
        'DublinCore\\Entry',
        'Atom\\Entry',
      ],
    ];
  }

  /**
   * Register core (default) extensions
   *
   * @return void
   */
  protected static function registerCoreExtensions() {
    static::registerExtension('DublinCore');
    static::registerExtension('Content');
    static::registerExtension('Atom');
    static::registerExtension('Slash');
    static::registerExtension('WellFormedWeb');
    static::registerExtension('Thread');
    static::registerExtension('Podcast');
  }

  /**
   * Utility method to apply array_unique operation to a multidimensional
   * array.
   *
   * @param array
   * @return array
   */
  public static function arrayUnique(array $array) {
    foreach ($array as &$value) {
      $value = serialize($value);
    }
    $array = array_unique($array);
    foreach ($array as &$value) {
      $value = unserialize($value);
    }
    return $array;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Reader::$cache protected static property Cache instance
Reader::$extensionManager protected static property
Reader::$extensions protected static property
Reader::$httpClient protected static property HTTP client object to use for retrieving feeds
Reader::$httpConditionalGet protected static property
Reader::$httpMethodOverride protected static property Override HTTP PUT and DELETE request methods?
Reader::arrayUnique public static function Utility method to apply array_unique operation to a multidimensional array.
Reader::detectType public static function Detect the feed type of the provided feed
Reader::findFeedLinks public static function Find feed links
Reader::getCache public static function Get the Feed cache
Reader::getExtensionManager public static function Get plugin manager for use with Extensions
Reader::getExtensions public static function Get a list of extensions
Reader::getHttpClient public static function Gets the HTTP client object. If none is set, a new ZendHttp\Client will be used.
Reader::getHttpMethodOverride public static function Get the HTTP override state
Reader::import public static function Import a feed by providing a URI Overrides ReaderImportInterface::import
Reader::importFile public static function Imports a feed from a file located at $filename. Overrides ReaderImportInterface::importFile
Reader::importRemoteFeed public static function Import a feed from a remote URI Overrides ReaderImportInterface::importRemoteFeed
Reader::importString public static function Import a feed from a string Overrides ReaderImportInterface::importString
Reader::isRegistered public static function Is a given named Extension registered?
Reader::NAMESPACE_ATOM_03 constant Namespace constants
Reader::NAMESPACE_ATOM_10 constant
Reader::NAMESPACE_RDF constant
Reader::NAMESPACE_RSS_090 constant
Reader::NAMESPACE_RSS_10 constant
Reader::registerCoreExtensions protected static function Register core (default) extensions
Reader::registerExtension public static function Register an Extension by name
Reader::reset public static function Reset class state to defaults
Reader::setCache public static function Set the feed cache
Reader::setExtensionManager public static function Set plugin manager for use with Extensions
Reader::setHttpClient public static function Set the HTTP client instance
Reader::setHttpMethodOverride public static function Toggle using POST instead of PUT and DELETE HTTP methods
Reader::TYPE_ANY constant Feed type constants
Reader::TYPE_ATOM_03 constant
Reader::TYPE_ATOM_10 constant
Reader::TYPE_ATOM_10_ENTRY constant
Reader::TYPE_ATOM_ANY constant
Reader::TYPE_RSS_090 constant
Reader::TYPE_RSS_091 constant
Reader::TYPE_RSS_091_NETSCAPE constant
Reader::TYPE_RSS_091_USERLAND constant
Reader::TYPE_RSS_092 constant
Reader::TYPE_RSS_093 constant
Reader::TYPE_RSS_094 constant
Reader::TYPE_RSS_10 constant
Reader::TYPE_RSS_20 constant
Reader::TYPE_RSS_ANY constant
Reader::useHttpConditionalGet public static function Set the flag indicating whether or not to use HTTP conditional GET