[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/SimplePie/src/ -> SimplePie.php (source)

   1  <?php
   2  
   3  /**
   4   * SimplePie
   5   *
   6   * A PHP-Based RSS and Atom Feed Framework.
   7   * Takes the hard work out of managing a complete RSS/Atom solution.
   8   *
   9   * Copyright (c) 2004-2022, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors
  10   * All rights reserved.
  11   *
  12   * Redistribution and use in source and binary forms, with or without modification, are
  13   * permitted provided that the following conditions are met:
  14   *
  15   *     * Redistributions of source code must retain the above copyright notice, this list of
  16   *       conditions and the following disclaimer.
  17   *
  18   *     * Redistributions in binary form must reproduce the above copyright notice, this list
  19   *       of conditions and the following disclaimer in the documentation and/or other materials
  20   *       provided with the distribution.
  21   *
  22   *     * Neither the name of the SimplePie Team nor the names of its contributors may be used
  23   *       to endorse or promote products derived from this software without specific prior
  24   *       written permission.
  25   *
  26   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
  27   * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  28   * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
  29   * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  30   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  31   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  32   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  33   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34   * POSSIBILITY OF SUCH DAMAGE.
  35   *
  36   * @package SimplePie
  37   * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue
  38   * @author Ryan Parman
  39   * @author Sam Sneddon
  40   * @author Ryan McCue
  41   * @link http://simplepie.org/ SimplePie
  42   * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  43   */
  44  
  45  namespace SimplePie;
  46  
  47  use InvalidArgumentException;
  48  use Psr\SimpleCache\CacheInterface;
  49  use SimplePie\Cache\Base;
  50  use SimplePie\Cache\BaseDataCache;
  51  use SimplePie\Cache\CallableNameFilter;
  52  use SimplePie\Cache\DataCache;
  53  use SimplePie\Cache\NameFilter;
  54  use SimplePie\Cache\Psr16;
  55  use SimplePie\Content\Type\Sniffer;
  56  
  57  /**
  58   * SimplePie
  59   *
  60   * @package SimplePie
  61   * @subpackage API
  62   */
  63  class SimplePie
  64  {
  65      /**
  66       * SimplePie Name
  67       */
  68      public const NAME = 'SimplePie';
  69  
  70      /**
  71       * SimplePie Version
  72       */
  73      public const VERSION = '1.8.0';
  74  
  75      /**
  76       * SimplePie Website URL
  77       */
  78      public const URL = 'http://simplepie.org';
  79  
  80      /**
  81       * SimplePie Linkback
  82       */
  83      public const LINKBACK = '<a href="' . self::URL . '" title="' . self::NAME . ' ' . self::VERSION . '">' . self::NAME . '</a>';
  84  
  85      /**
  86       * No Autodiscovery
  87       * @see SimplePie::set_autodiscovery_level()
  88       */
  89      public const LOCATOR_NONE = 0;
  90  
  91      /**
  92       * Feed Link Element Autodiscovery
  93       * @see SimplePie::set_autodiscovery_level()
  94       */
  95      public const LOCATOR_AUTODISCOVERY = 1;
  96  
  97      /**
  98       * Local Feed Extension Autodiscovery
  99       * @see SimplePie::set_autodiscovery_level()
 100       */
 101      public const LOCATOR_LOCAL_EXTENSION = 2;
 102  
 103      /**
 104       * Local Feed Body Autodiscovery
 105       * @see SimplePie::set_autodiscovery_level()
 106       */
 107      public const LOCATOR_LOCAL_BODY = 4;
 108  
 109      /**
 110       * Remote Feed Extension Autodiscovery
 111       * @see SimplePie::set_autodiscovery_level()
 112       */
 113      public const LOCATOR_REMOTE_EXTENSION = 8;
 114  
 115      /**
 116       * Remote Feed Body Autodiscovery
 117       * @see SimplePie::set_autodiscovery_level()
 118       */
 119      public const LOCATOR_REMOTE_BODY = 16;
 120  
 121      /**
 122       * All Feed Autodiscovery
 123       * @see SimplePie::set_autodiscovery_level()
 124       */
 125      public const LOCATOR_ALL = 31;
 126  
 127      /**
 128       * No known feed type
 129       */
 130      public const TYPE_NONE = 0;
 131  
 132      /**
 133       * RSS 0.90
 134       */
 135      public const TYPE_RSS_090 = 1;
 136  
 137      /**
 138       * RSS 0.91 (Netscape)
 139       */
 140      public const TYPE_RSS_091_NETSCAPE = 2;
 141  
 142      /**
 143       * RSS 0.91 (Userland)
 144       */
 145      public const TYPE_RSS_091_USERLAND = 4;
 146  
 147      /**
 148       * RSS 0.91 (both Netscape and Userland)
 149       */
 150      public const TYPE_RSS_091 = 6;
 151  
 152      /**
 153       * RSS 0.92
 154       */
 155      public const TYPE_RSS_092 = 8;
 156  
 157      /**
 158       * RSS 0.93
 159       */
 160      public const TYPE_RSS_093 = 16;
 161  
 162      /**
 163       * RSS 0.94
 164       */
 165      public const TYPE_RSS_094 = 32;
 166  
 167      /**
 168       * RSS 1.0
 169       */
 170      public const TYPE_RSS_10 = 64;
 171  
 172      /**
 173       * RSS 2.0
 174       */
 175      public const TYPE_RSS_20 = 128;
 176  
 177      /**
 178       * RDF-based RSS
 179       */
 180      public const TYPE_RSS_RDF = 65;
 181  
 182      /**
 183       * Non-RDF-based RSS (truly intended as syndication format)
 184       */
 185      public const TYPE_RSS_SYNDICATION = 190;
 186  
 187      /**
 188       * All RSS
 189       */
 190      public const TYPE_RSS_ALL = 255;
 191  
 192      /**
 193       * Atom 0.3
 194       */
 195      public const TYPE_ATOM_03 = 256;
 196  
 197      /**
 198       * Atom 1.0
 199       */
 200      public const TYPE_ATOM_10 = 512;
 201  
 202      /**
 203       * All Atom
 204       */
 205      public const TYPE_ATOM_ALL = 768;
 206  
 207      /**
 208       * All feed types
 209       */
 210      public const TYPE_ALL = 1023;
 211  
 212      /**
 213       * No construct
 214       */
 215      public const CONSTRUCT_NONE = 0;
 216  
 217      /**
 218       * Text construct
 219       */
 220      public const CONSTRUCT_TEXT = 1;
 221  
 222      /**
 223       * HTML construct
 224       */
 225      public const CONSTRUCT_HTML = 2;
 226  
 227      /**
 228       * XHTML construct
 229       */
 230      public const CONSTRUCT_XHTML = 4;
 231  
 232      /**
 233       * base64-encoded construct
 234       */
 235      public const CONSTRUCT_BASE64 = 8;
 236  
 237      /**
 238       * IRI construct
 239       */
 240      public const CONSTRUCT_IRI = 16;
 241  
 242      /**
 243       * A construct that might be HTML
 244       */
 245      public const CONSTRUCT_MAYBE_HTML = 32;
 246  
 247      /**
 248       * All constructs
 249       */
 250      public const CONSTRUCT_ALL = 63;
 251  
 252      /**
 253       * Don't change case
 254       */
 255      public const SAME_CASE = 1;
 256  
 257      /**
 258       * Change to lowercase
 259       */
 260      public const LOWERCASE = 2;
 261  
 262      /**
 263       * Change to uppercase
 264       */
 265      public const UPPERCASE = 4;
 266  
 267      /**
 268       * PCRE for HTML attributes
 269       */
 270      public const PCRE_HTML_ATTRIBUTE = '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*';
 271  
 272      /**
 273       * PCRE for XML attributes
 274       */
 275      public const PCRE_XML_ATTRIBUTE = '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*';
 276  
 277      /**
 278       * XML Namespace
 279       */
 280      public const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';
 281  
 282      /**
 283       * Atom 1.0 Namespace
 284       */
 285      public const NAMESPACE_ATOM_10 = 'http://www.w3.org/2005/Atom';
 286  
 287      /**
 288       * Atom 0.3 Namespace
 289       */
 290      public const NAMESPACE_ATOM_03 = 'http://purl.org/atom/ns#';
 291  
 292      /**
 293       * RDF Namespace
 294       */
 295      public const NAMESPACE_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
 296  
 297      /**
 298       * RSS 0.90 Namespace
 299       */
 300      public const NAMESPACE_RSS_090 = 'http://my.netscape.com/rdf/simple/0.9/';
 301  
 302      /**
 303       * RSS 1.0 Namespace
 304       */
 305      public const NAMESPACE_RSS_10 = 'http://purl.org/rss/1.0/';
 306  
 307      /**
 308       * RSS 1.0 Content Module Namespace
 309       */
 310      public const NAMESPACE_RSS_10_MODULES_CONTENT = 'http://purl.org/rss/1.0/modules/content/';
 311  
 312      /**
 313       * RSS 2.0 Namespace
 314       * (Stupid, I know, but I'm certain it will confuse people less with support.)
 315       */
 316      public const NAMESPACE_RSS_20 = '';
 317  
 318      /**
 319       * DC 1.0 Namespace
 320       */
 321      public const NAMESPACE_DC_10 = 'http://purl.org/dc/elements/1.0/';
 322  
 323      /**
 324       * DC 1.1 Namespace
 325       */
 326      public const NAMESPACE_DC_11 = 'http://purl.org/dc/elements/1.1/';
 327  
 328      /**
 329       * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace
 330       */
 331      public const NAMESPACE_W3C_BASIC_GEO = 'http://www.w3.org/2003/01/geo/wgs84_pos#';
 332  
 333      /**
 334       * GeoRSS Namespace
 335       */
 336      public const NAMESPACE_GEORSS = 'http://www.georss.org/georss';
 337  
 338      /**
 339       * Media RSS Namespace
 340       */
 341      public const NAMESPACE_MEDIARSS = 'http://search.yahoo.com/mrss/';
 342  
 343      /**
 344       * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec.
 345       */
 346      public const NAMESPACE_MEDIARSS_WRONG = 'http://search.yahoo.com/mrss';
 347  
 348      /**
 349       * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5.
 350       */
 351      public const NAMESPACE_MEDIARSS_WRONG2 = 'http://video.search.yahoo.com/mrss';
 352  
 353      /**
 354       * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace.
 355       */
 356      public const NAMESPACE_MEDIARSS_WRONG3 = 'http://video.search.yahoo.com/mrss/';
 357  
 358      /**
 359       * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace.
 360       */
 361      public const NAMESPACE_MEDIARSS_WRONG4 = 'http://www.rssboard.org/media-rss';
 362  
 363      /**
 364       * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL.
 365       */
 366      public const NAMESPACE_MEDIARSS_WRONG5 = 'http://www.rssboard.org/media-rss/';
 367  
 368      /**
 369       * iTunes RSS Namespace
 370       */
 371      public const NAMESPACE_ITUNES = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
 372  
 373      /**
 374       * XHTML Namespace
 375       */
 376      public const NAMESPACE_XHTML = 'http://www.w3.org/1999/xhtml';
 377  
 378      /**
 379       * IANA Link Relations Registry
 380       */
 381      public const IANA_LINK_RELATIONS_REGISTRY = 'http://www.iana.org/assignments/relation/';
 382  
 383      /**
 384       * No file source
 385       */
 386      public const FILE_SOURCE_NONE = 0;
 387  
 388      /**
 389       * Remote file source
 390       */
 391      public const FILE_SOURCE_REMOTE = 1;
 392  
 393      /**
 394       * Local file source
 395       */
 396      public const FILE_SOURCE_LOCAL = 2;
 397  
 398      /**
 399       * fsockopen() file source
 400       */
 401      public const FILE_SOURCE_FSOCKOPEN = 4;
 402  
 403      /**
 404       * cURL file source
 405       */
 406      public const FILE_SOURCE_CURL = 8;
 407  
 408      /**
 409       * file_get_contents() file source
 410       */
 411      public const FILE_SOURCE_FILE_GET_CONTENTS = 16;
 412  
 413      /**
 414       * @var array Raw data
 415       * @access private
 416       */
 417      public $data = [];
 418  
 419      /**
 420       * @var mixed Error string
 421       * @access private
 422       */
 423      public $error;
 424  
 425      /**
 426       * @var int HTTP status code
 427       * @see SimplePie::status_code()
 428       * @access private
 429       */
 430      public $status_code = 0;
 431  
 432      /**
 433       * @var object Instance of \SimplePie\Sanitize (or other class)
 434       * @see SimplePie::set_sanitize_class()
 435       * @access private
 436       */
 437      public $sanitize;
 438  
 439      /**
 440       * @var string SimplePie Useragent
 441       * @see SimplePie::set_useragent()
 442       * @access private
 443       */
 444      public $useragent = '';
 445  
 446      /**
 447       * @var string Feed URL
 448       * @see SimplePie::set_feed_url()
 449       * @access private
 450       */
 451      public $feed_url;
 452  
 453      /**
 454       * @var string Original feed URL, or new feed URL iff HTTP 301 Moved Permanently
 455       * @see SimplePie::subscribe_url()
 456       * @access private
 457       */
 458      public $permanent_url = null;
 459  
 460      /**
 461       * @var object Instance of \SimplePie\File to use as a feed
 462       * @see SimplePie::set_file()
 463       * @access private
 464       */
 465      public $file;
 466  
 467      /**
 468       * @var string Raw feed data
 469       * @see SimplePie::set_raw_data()
 470       * @access private
 471       */
 472      public $raw_data;
 473  
 474      /**
 475       * @var int Timeout for fetching remote files
 476       * @see SimplePie::set_timeout()
 477       * @access private
 478       */
 479      public $timeout = 10;
 480  
 481      /**
 482       * @var array Custom curl options
 483       * @see SimplePie::set_curl_options()
 484       * @access private
 485       */
 486      public $curl_options = [];
 487  
 488      /**
 489       * @var bool Forces fsockopen() to be used for remote files instead
 490       * of cURL, even if a new enough version is installed
 491       * @see SimplePie::force_fsockopen()
 492       * @access private
 493       */
 494      public $force_fsockopen = false;
 495  
 496      /**
 497       * @var bool Force the given data/URL to be treated as a feed no matter what
 498       * it appears like
 499       * @see SimplePie::force_feed()
 500       * @access private
 501       */
 502      public $force_feed = false;
 503  
 504      /**
 505       * @var bool Enable/Disable Caching
 506       * @see SimplePie::enable_cache()
 507       * @access private
 508       */
 509      private $enable_cache = true;
 510  
 511      /**
 512       * @var DataCache|null
 513       * @see SimplePie::set_cache()
 514       */
 515      private $cache = null;
 516  
 517      /**
 518       * @var NameFilter
 519       * @see SimplePie::set_cache_namefilter()
 520       */
 521      private $cache_namefilter;
 522  
 523      /**
 524       * @var bool Force SimplePie to fallback to expired cache, if enabled,
 525       * when feed is unavailable.
 526       * @see SimplePie::force_cache_fallback()
 527       * @access private
 528       */
 529      public $force_cache_fallback = false;
 530  
 531      /**
 532       * @var int Cache duration (in seconds)
 533       * @see SimplePie::set_cache_duration()
 534       * @access private
 535       */
 536      public $cache_duration = 3600;
 537  
 538      /**
 539       * @var int Auto-discovery cache duration (in seconds)
 540       * @see SimplePie::set_autodiscovery_cache_duration()
 541       * @access private
 542       */
 543      public $autodiscovery_cache_duration = 604800; // 7 Days.
 544  
 545      /**
 546       * @var string Cache location (relative to executing script)
 547       * @see SimplePie::set_cache_location()
 548       * @access private
 549       */
 550      public $cache_location = './cache';
 551  
 552      /**
 553       * @var string Function that creates the cache filename
 554       * @see SimplePie::set_cache_name_function()
 555       * @access private
 556       */
 557      public $cache_name_function = 'md5';
 558  
 559      /**
 560       * @var bool Reorder feed by date descending
 561       * @see SimplePie::enable_order_by_date()
 562       * @access private
 563       */
 564      public $order_by_date = true;
 565  
 566      /**
 567       * @var mixed Force input encoding to be set to the follow value
 568       * (false, or anything type-cast to false, disables this feature)
 569       * @see SimplePie::set_input_encoding()
 570       * @access private
 571       */
 572      public $input_encoding = false;
 573  
 574      /**
 575       * @var int Feed Autodiscovery Level
 576       * @see SimplePie::set_autodiscovery_level()
 577       * @access private
 578       */
 579      public $autodiscovery = self::LOCATOR_ALL;
 580  
 581      /**
 582       * Class registry object
 583       *
 584       * @var \SimplePie\Registry
 585       */
 586      public $registry;
 587  
 588      /**
 589       * @var int Maximum number of feeds to check with autodiscovery
 590       * @see SimplePie::set_max_checked_feeds()
 591       * @access private
 592       */
 593      public $max_checked_feeds = 10;
 594  
 595      /**
 596       * @var array All the feeds found during the autodiscovery process
 597       * @see SimplePie::get_all_discovered_feeds()
 598       * @access private
 599       */
 600      public $all_discovered_feeds = [];
 601  
 602      /**
 603       * @var string Web-accessible path to the handler_image.php file.
 604       * @see SimplePie::set_image_handler()
 605       * @access private
 606       */
 607      public $image_handler = '';
 608  
 609      /**
 610       * @var array Stores the URLs when multiple feeds are being initialized.
 611       * @see SimplePie::set_feed_url()
 612       * @access private
 613       */
 614      public $multifeed_url = [];
 615  
 616      /**
 617       * @var array Stores SimplePie objects when multiple feeds initialized.
 618       * @access private
 619       */
 620      public $multifeed_objects = [];
 621  
 622      /**
 623       * @var array Stores the get_object_vars() array for use with multifeeds.
 624       * @see SimplePie::set_feed_url()
 625       * @access private
 626       */
 627      public $config_settings = null;
 628  
 629      /**
 630       * @var integer Stores the number of items to return per-feed with multifeeds.
 631       * @see SimplePie::set_item_limit()
 632       * @access private
 633       */
 634      public $item_limit = 0;
 635  
 636      /**
 637       * @var bool Stores if last-modified and/or etag headers were sent with the
 638       * request when checking a feed.
 639       */
 640      public $check_modified = false;
 641  
 642      /**
 643       * @var array Stores the default attributes to be stripped by strip_attributes().
 644       * @see SimplePie::strip_attributes()
 645       * @access private
 646       */
 647      public $strip_attributes = ['bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'];
 648  
 649      /**
 650       * @var array Stores the default attributes to add to different tags by add_attributes().
 651       * @see SimplePie::add_attributes()
 652       * @access private
 653       */
 654      public $add_attributes = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']];
 655  
 656      /**
 657       * @var array Stores the default tags to be stripped by strip_htmltags().
 658       * @see SimplePie::strip_htmltags()
 659       * @access private
 660       */
 661      public $strip_htmltags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'];
 662  
 663      /**
 664       * @var array Stores the default attributes to be renamed by rename_attributes().
 665       * @see SimplePie::rename_attributes()
 666       * @access private
 667       */
 668      public $rename_attributes = [];
 669  
 670      /**
 671       * @var bool Should we throw exceptions, or use the old-style error property?
 672       * @access private
 673       */
 674      public $enable_exceptions = false;
 675  
 676      /**
 677       * The SimplePie class contains feed level data and options
 678       *
 679       * To use SimplePie, create the SimplePie object with no parameters. You can
 680       * then set configuration options using the provided methods. After setting
 681       * them, you must initialise the feed using $feed->init(). At that point the
 682       * object's methods and properties will be available to you.
 683       *
 684       * Previously, it was possible to pass in the feed URL along with cache
 685       * options directly into the constructor. This has been removed as of 1.3 as
 686       * it caused a lot of confusion.
 687       *
 688       * @since 1.0 Preview Release
 689       */
 690      public function __construct()
 691      {
 692          if (version_compare(PHP_VERSION, '7.2', '<')) {
 693              exit('Please upgrade to PHP 7.2 or newer.');
 694          }
 695  
 696          $this->set_useragent();
 697  
 698          $this->set_cache_namefilter(new CallableNameFilter($this->cache_name_function));
 699  
 700          // Other objects, instances created here so we can set options on them
 701          $this->sanitize = new \SimplePie\Sanitize();
 702          $this->registry = new \SimplePie\Registry();
 703  
 704          if (func_num_args() > 0) {
 705              trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', \E_USER_DEPRECATED);
 706  
 707              $args = func_get_args();
 708              switch (count($args)) {
 709                  case 3:
 710                      $this->set_cache_duration($args[2]);
 711                      // no break
 712                  case 2:
 713                      $this->set_cache_location($args[1]);
 714                      // no break
 715                  case 1:
 716                      $this->set_feed_url($args[0]);
 717                      $this->init();
 718              }
 719          }
 720      }
 721  
 722      /**
 723       * Used for converting object to a string
 724       */
 725      public function __toString()
 726      {
 727          return md5(serialize($this->data));
 728      }
 729  
 730      /**
 731       * Remove items that link back to this before destroying this object
 732       */
 733      public function __destruct()
 734      {
 735          if (!gc_enabled()) {
 736              if (!empty($this->data['items'])) {
 737                  foreach ($this->data['items'] as $item) {
 738                      $item->__destruct();
 739                  }
 740                  unset($item, $this->data['items']);
 741              }
 742              if (!empty($this->data['ordered_items'])) {
 743                  foreach ($this->data['ordered_items'] as $item) {
 744                      $item->__destruct();
 745                  }
 746                  unset($item, $this->data['ordered_items']);
 747              }
 748          }
 749      }
 750  
 751      /**
 752       * Force the given data/URL to be treated as a feed
 753       *
 754       * This tells SimplePie to ignore the content-type provided by the server.
 755       * Be careful when using this option, as it will also disable autodiscovery.
 756       *
 757       * @since 1.1
 758       * @param bool $enable Force the given data/URL to be treated as a feed
 759       */
 760      public function force_feed($enable = false)
 761      {
 762          $this->force_feed = (bool) $enable;
 763      }
 764  
 765      /**
 766       * Set the URL of the feed you want to parse
 767       *
 768       * This allows you to enter the URL of the feed you want to parse, or the
 769       * website you want to try to use auto-discovery on. This takes priority
 770       * over any set raw data.
 771       *
 772       * You can set multiple feeds to mash together by passing an array instead
 773       * of a string for the $url. Remember that with each additional feed comes
 774       * additional processing and resources.
 775       *
 776       * @since 1.0 Preview Release
 777       * @see set_raw_data()
 778       * @param string|array $url This is the URL (or array of URLs) that you want to parse.
 779       */
 780      public function set_feed_url($url)
 781      {
 782          $this->multifeed_url = [];
 783          if (is_array($url)) {
 784              foreach ($url as $value) {
 785                  $this->multifeed_url[] = $this->registry->call(Misc::class, 'fix_protocol', [$value, 1]);
 786              }
 787          } else {
 788              $this->feed_url = $this->registry->call(Misc::class, 'fix_protocol', [$url, 1]);
 789              $this->permanent_url = $this->feed_url;
 790          }
 791      }
 792  
 793      /**
 794       * Set an instance of {@see \SimplePie\File} to use as a feed
 795       *
 796       * @param \SimplePie\File &$file
 797       * @return bool True on success, false on failure
 798       */
 799      public function set_file(&$file)
 800      {
 801          if ($file instanceof \SimplePie\File) {
 802              $this->feed_url = $file->url;
 803              $this->permanent_url = $this->feed_url;
 804              $this->file = &$file;
 805              return true;
 806          }
 807          return false;
 808      }
 809  
 810      /**
 811       * Set the raw XML data to parse
 812       *
 813       * Allows you to use a string of RSS/Atom data instead of a remote feed.
 814       *
 815       * If you have a feed available as a string in PHP, you can tell SimplePie
 816       * to parse that data string instead of a remote feed. Any set feed URL
 817       * takes precedence.
 818       *
 819       * @since 1.0 Beta 3
 820       * @param string $data RSS or Atom data as a string.
 821       * @see set_feed_url()
 822       */
 823      public function set_raw_data($data)
 824      {
 825          $this->raw_data = $data;
 826      }
 827  
 828      /**
 829       * Set the default timeout for fetching remote feeds
 830       *
 831       * This allows you to change the maximum time the feed's server to respond
 832       * and send the feed back.
 833       *
 834       * @since 1.0 Beta 3
 835       * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed.
 836       */
 837      public function set_timeout($timeout = 10)
 838      {
 839          $this->timeout = (int) $timeout;
 840      }
 841  
 842      /**
 843       * Set custom curl options
 844       *
 845       * This allows you to change default curl options
 846       *
 847       * @since 1.0 Beta 3
 848       * @param array $curl_options Curl options to add to default settings
 849       */
 850      public function set_curl_options(array $curl_options = [])
 851      {
 852          $this->curl_options = $curl_options;
 853      }
 854  
 855      /**
 856       * Force SimplePie to use fsockopen() instead of cURL
 857       *
 858       * @since 1.0 Beta 3
 859       * @param bool $enable Force fsockopen() to be used
 860       */
 861      public function force_fsockopen($enable = false)
 862      {
 863          $this->force_fsockopen = (bool) $enable;
 864      }
 865  
 866      /**
 867       * Enable/disable caching in SimplePie.
 868       *
 869       * This option allows you to disable caching all-together in SimplePie.
 870       * However, disabling the cache can lead to longer load times.
 871       *
 872       * @since 1.0 Preview Release
 873       * @param bool $enable Enable caching
 874       */
 875      public function enable_cache($enable = true)
 876      {
 877          $this->enable_cache = (bool) $enable;
 878      }
 879  
 880      /**
 881       * Set a PSR-16 implementation as cache
 882       *
 883       * @param CacheInterface $psr16cache The PSR-16 cache implementation
 884       *
 885       * @return void
 886       */
 887      public function set_cache(CacheInterface $cache)
 888      {
 889          $this->cache = new Psr16($cache);
 890      }
 891  
 892      /**
 893       * SimplePie to continue to fall back to expired cache, if enabled, when
 894       * feed is unavailable.
 895       *
 896       * This tells SimplePie to ignore any file errors and fall back to cache
 897       * instead. This only works if caching is enabled and cached content
 898       * still exists.
 899       *
 900       * @deprecated since SimplePie 1.8.0, expired cache will not be used anymore.
 901       *
 902       * @param bool $enable Force use of cache on fail.
 903       */
 904      public function force_cache_fallback($enable = false)
 905      {
 906          // @trigger_error(sprintf('SimplePie\SimplePie::force_cache_fallback() is deprecated since SimplePie 1.8.0, expired cache will not be used anymore.'), \E_USER_DEPRECATED);
 907          $this->force_cache_fallback = (bool) $enable;
 908      }
 909  
 910      /**
 911       * Set the length of time (in seconds) that the contents of a feed will be
 912       * cached
 913       *
 914       * @param int $seconds The feed content cache duration
 915       */
 916      public function set_cache_duration($seconds = 3600)
 917      {
 918          $this->cache_duration = (int) $seconds;
 919      }
 920  
 921      /**
 922       * Set the length of time (in seconds) that the autodiscovered feed URL will
 923       * be cached
 924       *
 925       * @param int $seconds The autodiscovered feed URL cache duration.
 926       */
 927      public function set_autodiscovery_cache_duration($seconds = 604800)
 928      {
 929          $this->autodiscovery_cache_duration = (int) $seconds;
 930      }
 931  
 932      /**
 933       * Set the file system location where the cached files should be stored
 934       *
 935       * @deprecated since SimplePie 1.8.0, use \SimplePie\SimplePie::set_cache() instead.
 936       *
 937       * @param string $location The file system location.
 938       */
 939      public function set_cache_location($location = './cache')
 940      {
 941          // @trigger_error(sprintf('SimplePie\SimplePie::set_cache_location() is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()" instead.'), \E_USER_DEPRECATED);
 942          $this->cache_location = (string) $location;
 943      }
 944  
 945      /**
 946       * Return the filename (i.e. hash, without path and without extension) of the file to cache a given URL.
 947       *
 948       * @param string $url The URL of the feed to be cached.
 949       * @return string A filename (i.e. hash, without path and without extension).
 950       */
 951      public function get_cache_filename($url)
 952      {
 953          // Append custom parameters to the URL to avoid cache pollution in case of multiple calls with different parameters.
 954          $url .= $this->force_feed ? '#force_feed' : '';
 955          $options = [];
 956          if ($this->timeout != 10) {
 957              $options[CURLOPT_TIMEOUT] = $this->timeout;
 958          }
 959          if ($this->useragent !== \SimplePie\Misc::get_default_useragent()) {
 960              $options[CURLOPT_USERAGENT] = $this->useragent;
 961          }
 962          if (!empty($this->curl_options)) {
 963              foreach ($this->curl_options as $k => $v) {
 964                  $options[$k] = $v;
 965              }
 966          }
 967          if (!empty($options)) {
 968              ksort($options);
 969              $url .= '#' . urlencode(var_export($options, true));
 970          }
 971  
 972          return $this->cache_namefilter->filter($url);
 973      }
 974  
 975      /**
 976       * Set whether feed items should be sorted into reverse chronological order
 977       *
 978       * @param bool $enable Sort as reverse chronological order.
 979       */
 980      public function enable_order_by_date($enable = true)
 981      {
 982          $this->order_by_date = (bool) $enable;
 983      }
 984  
 985      /**
 986       * Set the character encoding used to parse the feed
 987       *
 988       * This overrides the encoding reported by the feed, however it will fall
 989       * back to the normal encoding detection if the override fails
 990       *
 991       * @param string $encoding Character encoding
 992       */
 993      public function set_input_encoding($encoding = false)
 994      {
 995          if ($encoding) {
 996              $this->input_encoding = (string) $encoding;
 997          } else {
 998              $this->input_encoding = false;
 999          }
1000      }
1001  
1002      /**
1003       * Set how much feed autodiscovery to do
1004       *
1005       * @see \SimplePie\SimplePie::LOCATOR_NONE
1006       * @see \SimplePie\SimplePie::LOCATOR_AUTODISCOVERY
1007       * @see \SimplePie\SimplePie::LOCATOR_LOCAL_EXTENSION
1008       * @see \SimplePie\SimplePie::LOCATOR_LOCAL_BODY
1009       * @see \SimplePie\SimplePie::LOCATOR_REMOTE_EXTENSION
1010       * @see \SimplePie\SimplePie::LOCATOR_REMOTE_BODY
1011       * @see \SimplePie\SimplePie::LOCATOR_ALL
1012       * @param int $level Feed Autodiscovery Level (level can be a combination of the above constants, see bitwise OR operator)
1013       */
1014      public function set_autodiscovery_level($level = self::LOCATOR_ALL)
1015      {
1016          $this->autodiscovery = (int) $level;
1017      }
1018  
1019      /**
1020       * Get the class registry
1021       *
1022       * Use this to override SimplePie's default classes
1023       * @see \SimplePie\Registry
1024       *
1025       * @return Registry
1026       */
1027      public function &get_registry()
1028      {
1029          return $this->registry;
1030      }
1031  
1032      /**
1033       * Set which class SimplePie uses for caching
1034       *
1035       * @deprecated since SimplePie 1.3, use {@see set_cache()} instead
1036       *
1037       * @param string $class Name of custom class
1038       *
1039       * @return boolean True on success, false otherwise
1040       */
1041      public function set_cache_class($class = Cache::class)
1042      {
1043          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::set_cache()" instead.', __METHOD__), \E_USER_DEPRECATED);
1044  
1045          return $this->registry->register(Cache::class, $class, true);
1046      }
1047  
1048      /**
1049       * Set which class SimplePie uses for auto-discovery
1050       *
1051       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1052       *
1053       * @param string $class Name of custom class
1054       *
1055       * @return boolean True on success, false otherwise
1056       */
1057      public function set_locator_class($class = Locator::class)
1058      {
1059          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1060  
1061          return $this->registry->register(Locator::class, $class, true);
1062      }
1063  
1064      /**
1065       * Set which class SimplePie uses for XML parsing
1066       *
1067       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1068       *
1069       * @param string $class Name of custom class
1070       *
1071       * @return boolean True on success, false otherwise
1072       */
1073      public function set_parser_class($class = Parser::class)
1074      {
1075          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1076  
1077          return $this->registry->register(Parser::class, $class, true);
1078      }
1079  
1080      /**
1081       * Set which class SimplePie uses for remote file fetching
1082       *
1083       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1084       *
1085       * @param string $class Name of custom class
1086       *
1087       * @return boolean True on success, false otherwise
1088       */
1089      public function set_file_class($class = File::class)
1090      {
1091          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1092  
1093          return $this->registry->register(File::class, $class, true);
1094      }
1095  
1096      /**
1097       * Set which class SimplePie uses for data sanitization
1098       *
1099       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1100       *
1101       * @param string $class Name of custom class
1102       *
1103       * @return boolean True on success, false otherwise
1104       */
1105      public function set_sanitize_class($class = Sanitize::class)
1106      {
1107          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1108  
1109          return $this->registry->register(Sanitize::class, $class, true);
1110      }
1111  
1112      /**
1113       * Set which class SimplePie uses for handling feed items
1114       *
1115       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1116       *
1117       * @param string $class Name of custom class
1118       *
1119       * @return boolean True on success, false otherwise
1120       */
1121      public function set_item_class($class = Item::class)
1122      {
1123          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1124  
1125          return $this->registry->register(Item::class, $class, true);
1126      }
1127  
1128      /**
1129       * Set which class SimplePie uses for handling author data
1130       *
1131       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1132       *
1133       * @param string $class Name of custom class
1134       *
1135       * @return boolean True on success, false otherwise
1136       */
1137      public function set_author_class($class = Author::class)
1138      {
1139          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1140  
1141          return $this->registry->register(Author::class, $class, true);
1142      }
1143  
1144      /**
1145       * Set which class SimplePie uses for handling category data
1146       *
1147       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1148       *
1149       * @param string $class Name of custom class
1150       *
1151       * @return boolean True on success, false otherwise
1152       */
1153      public function set_category_class($class = Category::class)
1154      {
1155          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1156  
1157          return $this->registry->register(Category::class, $class, true);
1158      }
1159  
1160      /**
1161       * Set which class SimplePie uses for feed enclosures
1162       *
1163       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1164       *
1165       * @param string $class Name of custom class
1166       *
1167       * @return boolean True on success, false otherwise
1168       */
1169      public function set_enclosure_class($class = Enclosure::class)
1170      {
1171          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1172  
1173          return $this->registry->register(Enclosure::class, $class, true);
1174      }
1175  
1176      /**
1177       * Set which class SimplePie uses for `<media:text>` captions
1178       *
1179       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1180       *
1181       * @param string $class Name of custom class
1182       *
1183       * @return boolean True on success, false otherwise
1184       */
1185      public function set_caption_class($class = Caption::class)
1186      {
1187          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1188  
1189          return $this->registry->register(Caption::class, $class, true);
1190      }
1191  
1192      /**
1193       * Set which class SimplePie uses for `<media:copyright>`
1194       *
1195       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1196       *
1197       * @param string $class Name of custom class
1198       *
1199       * @return boolean True on success, false otherwise
1200       */
1201      public function set_copyright_class($class = Copyright::class)
1202      {
1203          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1204  
1205          return $this->registry->register(Copyright::class, $class, true);
1206      }
1207  
1208      /**
1209       * Set which class SimplePie uses for `<media:credit>`
1210       *
1211       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1212       *
1213       * @param string $class Name of custom class
1214       *
1215       * @return boolean True on success, false otherwise
1216       */
1217      public function set_credit_class($class = Credit::class)
1218      {
1219          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1220  
1221          return $this->registry->register(Credit::class, $class, true);
1222      }
1223  
1224      /**
1225       * Set which class SimplePie uses for `<media:rating>`
1226       *
1227       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1228       *
1229       * @param string $class Name of custom class
1230       *
1231       * @return boolean True on success, false otherwise
1232       */
1233      public function set_rating_class($class = Rating::class)
1234      {
1235          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1236  
1237          return $this->registry->register(Rating::class, $class, true);
1238      }
1239  
1240      /**
1241       * Set which class SimplePie uses for `<media:restriction>`
1242       *
1243       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1244       *
1245       * @param string $class Name of custom class
1246       *
1247       * @return boolean True on success, false otherwise
1248       */
1249      public function set_restriction_class($class = Restriction::class)
1250      {
1251          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1252  
1253          return $this->registry->register(Restriction::class, $class, true);
1254      }
1255  
1256      /**
1257       * Set which class SimplePie uses for content-type sniffing
1258       *
1259       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1260       *
1261       * @param string $class Name of custom class
1262       *
1263       * @return boolean True on success, false otherwise
1264       */
1265      public function set_content_type_sniffer_class($class = Sniffer::class)
1266      {
1267          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1268  
1269          return $this->registry->register(Sniffer::class, $class, true);
1270      }
1271  
1272      /**
1273       * Set which class SimplePie uses item sources
1274       *
1275       * @deprecated since SimplePie 1.3, use {@see get_registry()} instead
1276       *
1277       * @param string $class Name of custom class
1278       *
1279       * @return boolean True on success, false otherwise
1280       */
1281      public function set_source_class($class = Source::class)
1282      {
1283          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.3, please use "SimplePie\SimplePie::get_registry()" instead.', __METHOD__), \E_USER_DEPRECATED);
1284  
1285          return $this->registry->register(Source::class, $class, true);
1286      }
1287  
1288      /**
1289       * Set the user agent string
1290       *
1291       * @param string $ua New user agent string.
1292       */
1293      public function set_useragent($ua = null)
1294      {
1295          if ($ua === null) {
1296              $ua = \SimplePie\Misc::get_default_useragent();
1297          }
1298  
1299          $this->useragent = (string) $ua;
1300      }
1301  
1302      /**
1303       * Set a namefilter to modify the cache filename with
1304       *
1305       * @param NameFilter $filter
1306       *
1307       * @return void
1308       */
1309      public function set_cache_namefilter(NameFilter $filter): void
1310      {
1311          $this->cache_namefilter = $filter;
1312      }
1313  
1314      /**
1315       * Set callback function to create cache filename with
1316       *
1317       * @deprecated since SimplePie 1.8.0, use {@see set_cache_namefilter()} instead
1318       *
1319       * @param mixed $function Callback function
1320       */
1321      public function set_cache_name_function($function = 'md5')
1322      {
1323          // trigger_error(sprintf('"%s()" is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache_namefilter()" instead.', __METHOD__), \E_USER_DEPRECATED);
1324  
1325          if (is_callable($function)) {
1326              $this->cache_name_function = $function;
1327  
1328              $this->set_cache_namefilter(new CallableNameFilter($this->cache_name_function));
1329          }
1330      }
1331  
1332      /**
1333       * Set options to make SP as fast as possible
1334       *
1335       * Forgoes a substantial amount of data sanitization in favor of speed. This
1336       * turns SimplePie into a dumb parser of feeds.
1337       *
1338       * @param bool $set Whether to set them or not
1339       */
1340      public function set_stupidly_fast($set = false)
1341      {
1342          if ($set) {
1343              $this->enable_order_by_date(false);
1344              $this->remove_div(false);
1345              $this->strip_comments(false);
1346              $this->strip_htmltags(false);
1347              $this->strip_attributes(false);
1348              $this->add_attributes(false);
1349              $this->set_image_handler(false);
1350              $this->set_https_domains([]);
1351          }
1352      }
1353  
1354      /**
1355       * Set maximum number of feeds to check with autodiscovery
1356       *
1357       * @param int $max Maximum number of feeds to check
1358       */
1359      public function set_max_checked_feeds($max = 10)
1360      {
1361          $this->max_checked_feeds = (int) $max;
1362      }
1363  
1364      public function remove_div($enable = true)
1365      {
1366          $this->sanitize->remove_div($enable);
1367      }
1368  
1369      public function strip_htmltags($tags = '', $encode = null)
1370      {
1371          if ($tags === '') {
1372              $tags = $this->strip_htmltags;
1373          }
1374          $this->sanitize->strip_htmltags($tags);
1375          if ($encode !== null) {
1376              $this->sanitize->encode_instead_of_strip($tags);
1377          }
1378      }
1379  
1380      public function encode_instead_of_strip($enable = true)
1381      {
1382          $this->sanitize->encode_instead_of_strip($enable);
1383      }
1384  
1385      public function rename_attributes($attribs = '')
1386      {
1387          if ($attribs === '') {
1388              $attribs = $this->rename_attributes;
1389          }
1390          $this->sanitize->rename_attributes($attribs);
1391      }
1392  
1393      public function strip_attributes($attribs = '')
1394      {
1395          if ($attribs === '') {
1396              $attribs = $this->strip_attributes;
1397          }
1398          $this->sanitize->strip_attributes($attribs);
1399      }
1400  
1401      public function add_attributes($attribs = '')
1402      {
1403          if ($attribs === '') {
1404              $attribs = $this->add_attributes;
1405          }
1406          $this->sanitize->add_attributes($attribs);
1407      }
1408  
1409      /**
1410       * Set the output encoding
1411       *
1412       * Allows you to override SimplePie's output to match that of your webpage.
1413       * This is useful for times when your webpages are not being served as
1414       * UTF-8. This setting will be obeyed by {@see handle_content_type()}, and
1415       * is similar to {@see set_input_encoding()}.
1416       *
1417       * It should be noted, however, that not all character encodings can support
1418       * all characters. If your page is being served as ISO-8859-1 and you try
1419       * to display a Japanese feed, you'll likely see garbled characters.
1420       * Because of this, it is highly recommended to ensure that your webpages
1421       * are served as UTF-8.
1422       *
1423       * The number of supported character encodings depends on whether your web
1424       * host supports {@link http://php.net/mbstring mbstring},
1425       * {@link http://php.net/iconv iconv}, or both. See
1426       * {@link http://simplepie.org/wiki/faq/Supported_Character_Encodings} for
1427       * more information.
1428       *
1429       * @param string $encoding
1430       */
1431      public function set_output_encoding($encoding = 'UTF-8')
1432      {
1433          $this->sanitize->set_output_encoding($encoding);
1434      }
1435  
1436      public function strip_comments($strip = false)
1437      {
1438          $this->sanitize->strip_comments($strip);
1439      }
1440  
1441      /**
1442       * Set element/attribute key/value pairs of HTML attributes
1443       * containing URLs that need to be resolved relative to the feed
1444       *
1445       * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite,
1446       * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite,
1447       * |q|@cite
1448       *
1449       * @since 1.0
1450       * @param array|null $element_attribute Element/attribute key/value pairs, null for default
1451       */
1452      public function set_url_replacements($element_attribute = null)
1453      {
1454          $this->sanitize->set_url_replacements($element_attribute);
1455      }
1456  
1457      /**
1458       * Set the list of domains for which to force HTTPS.
1459       * @see \SimplePie\Sanitize::set_https_domains()
1460       * @param array List of HTTPS domains. Example array('biz', 'example.com', 'example.org', 'www.example.net').
1461       */
1462      public function set_https_domains($domains = [])
1463      {
1464          if (is_array($domains)) {
1465              $this->sanitize->set_https_domains($domains);
1466          }
1467      }
1468  
1469      /**
1470       * Set the handler to enable the display of cached images.
1471       *
1472       * @param string $page Web-accessible path to the handler_image.php file.
1473       * @param string $qs The query string that the value should be passed to.
1474       */
1475      public function set_image_handler($page = false, $qs = 'i')
1476      {
1477          if ($page !== false) {
1478              $this->sanitize->set_image_handler($page . '?' . $qs . '=');
1479          } else {
1480              $this->image_handler = '';
1481          }
1482      }
1483  
1484      /**
1485       * Set the limit for items returned per-feed with multifeeds
1486       *
1487       * @param integer $limit The maximum number of items to return.
1488       */
1489      public function set_item_limit($limit = 0)
1490      {
1491          $this->item_limit = (int) $limit;
1492      }
1493  
1494      /**
1495       * Enable throwing exceptions
1496       *
1497       * @param boolean $enable Should we throw exceptions, or use the old-style error property?
1498       */
1499      public function enable_exceptions($enable = true)
1500      {
1501          $this->enable_exceptions = $enable;
1502      }
1503  
1504      /**
1505       * Initialize the feed object
1506       *
1507       * This is what makes everything happen. Period. This is where all of the
1508       * configuration options get processed, feeds are fetched, cached, and
1509       * parsed, and all of that other good stuff.
1510       *
1511       * @return boolean True if successful, false otherwise
1512       */
1513      public function init()
1514      {
1515          // Check absolute bare minimum requirements.
1516          if (!extension_loaded('xml') || !extension_loaded('pcre')) {
1517              $this->error = 'XML or PCRE extensions not loaded!';
1518              return false;
1519          }
1520          // Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader.
1521          elseif (!extension_loaded('xmlreader')) {
1522              static $xml_is_sane = null;
1523              if ($xml_is_sane === null) {
1524                  $parser_check = xml_parser_create();
1525                  xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
1526                  xml_parser_free($parser_check);
1527                  $xml_is_sane = isset($values[0]['value']);
1528              }
1529              if (!$xml_is_sane) {
1530                  return false;
1531              }
1532          }
1533  
1534          // The default sanitize class gets set in the constructor, check if it has
1535          // changed.
1536          if ($this->registry->get_class(Sanitize::class) !== 'SimplePie\Sanitize') {
1537              $this->sanitize = $this->registry->create(Sanitize::class);
1538          }
1539          if (method_exists($this->sanitize, 'set_registry')) {
1540              $this->sanitize->set_registry($this->registry);
1541          }
1542  
1543          // Pass whatever was set with config options over to the sanitizer.
1544          // Pass the classes in for legacy support; new classes should use the registry instead
1545          $this->sanitize->pass_cache_data(
1546              $this->enable_cache,
1547              $this->cache_location,
1548              $this->cache_namefilter,
1549              $this->registry->get_class(Cache::class),
1550              $this->cache
1551          );
1552          $this->sanitize->pass_file_data($this->registry->get_class(File::class), $this->timeout, $this->useragent, $this->force_fsockopen, $this->curl_options);
1553  
1554          if (!empty($this->multifeed_url)) {
1555              $i = 0;
1556              $success = 0;
1557              $this->multifeed_objects = [];
1558              $this->error = [];
1559              foreach ($this->multifeed_url as $url) {
1560                  $this->multifeed_objects[$i] = clone $this;
1561                  $this->multifeed_objects[$i]->set_feed_url($url);
1562                  $single_success = $this->multifeed_objects[$i]->init();
1563                  $success |= $single_success;
1564                  if (!$single_success) {
1565                      $this->error[$i] = $this->multifeed_objects[$i]->error();
1566                  }
1567                  $i++;
1568              }
1569              return (bool) $success;
1570          } elseif ($this->feed_url === null && $this->raw_data === null) {
1571              return false;
1572          }
1573  
1574          $this->error = null;
1575          $this->data = [];
1576          $this->check_modified = false;
1577          $this->multifeed_objects = [];
1578          $cache = false;
1579  
1580          if ($this->feed_url !== null) {
1581              $parsed_feed_url = $this->registry->call(Misc::class, 'parse_url', [$this->feed_url]);
1582  
1583              // Decide whether to enable caching
1584              if ($this->enable_cache && $parsed_feed_url['scheme'] !== '') {
1585                  $cache = $this->get_cache($this->feed_url);
1586              }
1587  
1588              // Fetch the data via \SimplePie\File into $this->raw_data
1589              if (($fetched = $this->fetch_data($cache)) === true) {
1590                  return true;
1591              } elseif ($fetched === false) {
1592                  return false;
1593              }
1594  
1595              [$headers, $sniffed] = $fetched;
1596          }
1597  
1598          // Empty response check
1599          if (empty($this->raw_data)) {
1600              $this->error = "A feed could not be found at `$this->feed_url`. Empty body.";
1601              $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]);
1602              return false;
1603          }
1604  
1605          // Set up array of possible encodings
1606          $encodings = [];
1607  
1608          // First check to see if input has been overridden.
1609          if ($this->input_encoding !== false) {
1610              $encodings[] = strtoupper($this->input_encoding);
1611          }
1612  
1613          $application_types = ['application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity'];
1614          $text_types = ['text/xml', 'text/xml-external-parsed-entity'];
1615  
1616          // RFC 3023 (only applies to sniffed content)
1617          if (isset($sniffed)) {
1618              if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml') {
1619                  if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) {
1620                      $encodings[] = strtoupper($charset[1]);
1621                  }
1622                  $encodings = array_merge($encodings, $this->registry->call(Misc::class, 'xml_encoding', [$this->raw_data, &$this->registry]));
1623                  $encodings[] = 'UTF-8';
1624              } elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') {
1625                  if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) {
1626                      $encodings[] = strtoupper($charset[1]);
1627                  }
1628                  $encodings[] = 'US-ASCII';
1629              }
1630              // Text MIME-type default
1631              elseif (substr($sniffed, 0, 5) === 'text/') {
1632                  $encodings[] = 'UTF-8';
1633              }
1634          }
1635  
1636          // Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1
1637          $encodings = array_merge($encodings, $this->registry->call(Misc::class, 'xml_encoding', [$this->raw_data, &$this->registry]));
1638          $encodings[] = 'UTF-8';
1639          $encodings[] = 'ISO-8859-1';
1640  
1641          // There's no point in trying an encoding twice
1642          $encodings = array_unique($encodings);
1643  
1644          // Loop through each possible encoding, till we return something, or run out of possibilities
1645          foreach ($encodings as $encoding) {
1646              // Change the encoding to UTF-8 (as we always use UTF-8 internally)
1647              if ($utf8_data = $this->registry->call(Misc::class, 'change_encoding', [$this->raw_data, $encoding, 'UTF-8'])) {
1648                  // Create new parser
1649                  $parser = $this->registry->create(Parser::class);
1650  
1651                  // If it's parsed fine
1652                  if ($parser->parse($utf8_data, 'UTF-8', $this->permanent_url)) {
1653                      $this->data = $parser->get_data();
1654                      if (!($this->get_type() & ~self::TYPE_NONE)) {
1655                          $this->error = "A feed could not be found at `$this->feed_url`. This does not appear to be a valid RSS or Atom feed.";
1656                          $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]);
1657                          return false;
1658                      }
1659  
1660                      if (isset($headers)) {
1661                          $this->data['headers'] = $headers;
1662                      }
1663                      $this->data['build'] = \SimplePie\Misc::get_build();
1664  
1665                      // Cache the file if caching is enabled
1666                      $this->data['cache_expiration_time'] = $this->cache_duration + time();
1667                      if ($cache && !$cache->set_data($this->get_cache_filename($this->feed_url), $this->data, $this->cache_duration)) {
1668                          trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
1669                      }
1670                      return true;
1671                  }
1672              }
1673          }
1674  
1675          if (isset($parser)) {
1676              // We have an error, just set \SimplePie\Misc::error to it and quit
1677              $this->error = $this->feed_url;
1678              $this->error .= sprintf(' is invalid XML, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column());
1679          } else {
1680              $this->error = 'The data could not be converted to UTF-8.';
1681              if (!extension_loaded('mbstring') && !extension_loaded('iconv') && !class_exists('\UConverter')) {
1682                  $this->error .= ' You MUST have either the iconv, mbstring or intl (PHP 5.5+) extension installed and enabled.';
1683              } else {
1684                  $missingExtensions = [];
1685                  if (!extension_loaded('iconv')) {
1686                      $missingExtensions[] = 'iconv';
1687                  }
1688                  if (!extension_loaded('mbstring')) {
1689                      $missingExtensions[] = 'mbstring';
1690                  }
1691                  if (!class_exists('\UConverter')) {
1692                      $missingExtensions[] = 'intl (PHP 5.5+)';
1693                  }
1694                  $this->error .= ' Try installing/enabling the ' . implode(' or ', $missingExtensions) . ' extension.';
1695              }
1696          }
1697  
1698          $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]);
1699  
1700          return false;
1701      }
1702  
1703      /**
1704       * Fetch the data via \SimplePie\File
1705       *
1706       * If the data is already cached, attempt to fetch it from there instead
1707       * @param Base|DataCache|false $cache Cache handler, or false to not load from the cache
1708       * @return array|true Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type
1709       */
1710      protected function fetch_data(&$cache)
1711      {
1712          if (is_object($cache) && $cache instanceof Base) {
1713              // @trigger_error(sprintf('Providing $cache as "\SimplePie\Cache\Base" in %s() is deprecated since SimplePie 1.8.0, please provide "\SimplePie\Cache\DataCache" implementation instead.', __METHOD__), \E_USER_DEPRECATED);
1714              $cache = new BaseDataCache($cache);
1715          }
1716  
1717          if ($cache !== false && !$cache instanceof DataCache) {
1718              throw new InvalidArgumentException(sprintf(
1719                  '%s(): Argument #1 ($cache) must be of type %s|false',
1720                  __METHOD__,
1721                  DataCache::class
1722              ), 1);
1723          }
1724  
1725          $cacheKey = $this->get_cache_filename($this->feed_url);
1726  
1727          // If it's enabled, use the cache
1728          if ($cache) {
1729              // Load the Cache
1730              $this->data = $cache->get_data($cacheKey, []);
1731  
1732              if (!empty($this->data)) {
1733                  // If the cache is for an outdated build of SimplePie
1734                  if (!isset($this->data['build']) || $this->data['build'] !== \SimplePie\Misc::get_build()) {
1735                      $cache->delete_data($cacheKey);
1736                      $this->data = [];
1737                  }
1738                  // If we've hit a collision just rerun it with caching disabled
1739                  elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url) {
1740                      $cache = false;
1741                      $this->data = [];
1742                  }
1743                  // If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL.
1744                  elseif (isset($this->data['feed_url'])) {
1745                      // Do not need to do feed autodiscovery yet.
1746                      if ($this->data['feed_url'] !== $this->data['url']) {
1747                          $this->set_feed_url($this->data['feed_url']);
1748                          $this->data['url'] = $this->data['feed_url'];
1749  
1750                          $cache->set_data($this->get_cache_filename($this->feed_url), $this->data, $this->autodiscovery_cache_duration);
1751  
1752                          return $this->init();
1753                      }
1754  
1755                      $cache->delete_data($this->get_cache_filename($this->feed_url));
1756                      $this->data = [];
1757                  }
1758                  // Check if the cache has been updated
1759                  elseif (isset($this->data['cache_expiration_time']) && $this->data['cache_expiration_time'] > time()) {
1760                      // Want to know if we tried to send last-modified and/or etag headers
1761                      // when requesting this file. (Note that it's up to the file to
1762                      // support this, but we don't always send the headers either.)
1763                      $this->check_modified = true;
1764                      if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) {
1765                          $headers = [
1766                              'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
1767                          ];
1768                          if (isset($this->data['headers']['last-modified'])) {
1769                              $headers['if-modified-since'] = $this->data['headers']['last-modified'];
1770                          }
1771                          if (isset($this->data['headers']['etag'])) {
1772                              $headers['if-none-match'] = $this->data['headers']['etag'];
1773                          }
1774  
1775                          $file = $this->registry->create(File::class, [$this->feed_url, $this->timeout / 10, 5, $headers, $this->useragent, $this->force_fsockopen, $this->curl_options]);
1776                          $this->status_code = $file->status_code;
1777  
1778                          if ($file->success) {
1779                              if ($file->status_code === 304) {
1780                                  // Set raw_data to false here too, to signify that the cache
1781                                  // is still valid.
1782                                  $this->raw_data = false;
1783                                  $cache->set_data($cacheKey, $this->data, $this->cache_duration);
1784                                  return true;
1785                              }
1786                          } else {
1787                              $this->check_modified = false;
1788                              if ($this->force_cache_fallback) {
1789                                  $cache->set_data($cacheKey, $this->data, $this->cache_duration);
1790                                  return true;
1791                              }
1792  
1793                              unset($file);
1794                          }
1795                      }
1796                  }
1797                  // If the cache is still valid, just return true
1798                  else {
1799                      $this->raw_data = false;
1800                      return true;
1801                  }
1802              }
1803              // If the cache is empty
1804              else {
1805                  $this->data = [];
1806              }
1807          }
1808  
1809          // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it.
1810          if (!isset($file)) {
1811              if ($this->file instanceof \SimplePie\File && $this->file->url === $this->feed_url) {
1812                  $file = &$this->file;
1813              } else {
1814                  $headers = [
1815                      'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
1816                  ];
1817                  $file = $this->registry->create(File::class, [$this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen, $this->curl_options]);
1818              }
1819          }
1820          $this->status_code = $file->status_code;
1821  
1822          // If the file connection has an error, set SimplePie::error to that and quit
1823          if (!$file->success && !($file->method & self::FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) {
1824              $this->error = $file->error;
1825              return !empty($this->data);
1826          }
1827  
1828          if (!$this->force_feed) {
1829              // Check if the supplied URL is a feed, if it isn't, look for it.
1830              $locate = $this->registry->create(Locator::class, [&$file, $this->timeout, $this->useragent, $this->max_checked_feeds, $this->force_fsockopen, $this->curl_options]);
1831  
1832              if (!$locate->is_feed($file)) {
1833                  $copyStatusCode = $file->status_code;
1834                  $copyContentType = $file->headers['content-type'];
1835                  try {
1836                      $microformats = false;
1837                      if (class_exists('DOMXpath') && function_exists('Mf2\parse')) {
1838                          $doc = new \DOMDocument();
1839                          @$doc->loadHTML($file->body);
1840                          $xpath = new \DOMXpath($doc);
1841                          // Check for both h-feed and h-entry, as both a feed with no entries
1842                          // and a list of entries without an h-feed wrapper are both valid.
1843                          $query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '.
1844                              'contains(concat(" ", @class, " "), " h-entry ")]';
1845                          $result = $xpath->query($query);
1846                          $microformats = $result->length !== 0;
1847                      }
1848                      // Now also do feed discovery, but if microformats were found don't
1849                      // overwrite the current value of file.
1850                      $discovered = $locate->find(
1851                          $this->autodiscovery,
1852                          $this->all_discovered_feeds
1853                      );
1854                      if ($microformats) {
1855                          if ($hub = $locate->get_rel_link('hub')) {
1856                              $self = $locate->get_rel_link('self');
1857                              $this->store_links($file, $hub, $self);
1858                          }
1859                          // Push the current file onto all_discovered feeds so the user can
1860                          // be shown this as one of the options.
1861                          if (isset($this->all_discovered_feeds)) {
1862                              $this->all_discovered_feeds[] = $file;
1863                          }
1864                      } else {
1865                          if ($discovered) {
1866                              $file = $discovered;
1867                          } else {
1868                              // We need to unset this so that if SimplePie::set_file() has
1869                              // been called that object is untouched
1870                              unset($file);
1871                              $this->error = "A feed could not be found at `$this->feed_url`; the status code is `$copyStatusCode` and content-type is `$copyContentType`";
1872                              $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, __FILE__, __LINE__]);
1873                              return false;
1874                          }
1875                      }
1876                  } catch (\SimplePie\Exception $e) {
1877                      // We need to unset this so that if SimplePie::set_file() has been called that object is untouched
1878                      unset($file);
1879                      // This is usually because DOMDocument doesn't exist
1880                      $this->error = $e->getMessage();
1881                      $this->registry->call(Misc::class, 'error', [$this->error, E_USER_NOTICE, $e->getFile(), $e->getLine()]);
1882                      return false;
1883                  }
1884  
1885                  if ($cache) {
1886                      $this->data = [
1887                          'url' => $this->feed_url,
1888                          'feed_url' => $file->url,
1889                          'build' => \SimplePie\Misc::get_build(),
1890                          'cache_expiration_time' => $this->cache_duration + time(),
1891                      ];
1892  
1893                      if (!$cache->set_data($cacheKey, $this->data, $this->cache_duration)) {
1894                          trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
1895                      }
1896                  }
1897              }
1898              $this->feed_url = $file->url;
1899              $locate = null;
1900          }
1901  
1902          $this->raw_data = $file->body;
1903          $this->permanent_url = $file->permanent_url;
1904          $headers = $file->headers;
1905          $sniffer = $this->registry->create(Sniffer::class, [&$file]);
1906          $sniffed = $sniffer->get_type();
1907  
1908          return [$headers, $sniffed];
1909      }
1910  
1911      /**
1912       * Get the error message for the occurred error
1913       *
1914       * @return string|array Error message, or array of messages for multifeeds
1915       */
1916      public function error()
1917      {
1918          return $this->error;
1919      }
1920  
1921      /**
1922       * Get the last HTTP status code
1923       *
1924       * @return int Status code
1925       */
1926      public function status_code()
1927      {
1928          return $this->status_code;
1929      }
1930  
1931      /**
1932       * Get the raw XML
1933       *
1934       * This is the same as the old `$feed->enable_xml_dump(true)`, but returns
1935       * the data instead of printing it.
1936       *
1937       * @return string|boolean Raw XML data, false if the cache is used
1938       */
1939      public function get_raw_data()
1940      {
1941          return $this->raw_data;
1942      }
1943  
1944      /**
1945       * Get the character encoding used for output
1946       *
1947       * @since Preview Release
1948       * @return string
1949       */
1950      public function get_encoding()
1951      {
1952          return $this->sanitize->output_encoding;
1953      }
1954  
1955      /**
1956       * Send the content-type header with correct encoding
1957       *
1958       * This method ensures that the SimplePie-enabled page is being served with
1959       * the correct {@link http://www.iana.org/assignments/media-types/ mime-type}
1960       * and character encoding HTTP headers (character encoding determined by the
1961       * {@see set_output_encoding} config option).
1962       *
1963       * This won't work properly if any content or whitespace has already been
1964       * sent to the browser, because it relies on PHP's
1965       * {@link http://php.net/header header()} function, and these are the
1966       * circumstances under which the function works.
1967       *
1968       * Because it's setting these settings for the entire page (as is the nature
1969       * of HTTP headers), this should only be used once per page (again, at the
1970       * top).
1971       *
1972       * @param string $mime MIME type to serve the page as
1973       */
1974      public function handle_content_type($mime = 'text/html')
1975      {
1976          if (!headers_sent()) {
1977              $header = "Content-type: $mime;";
1978              if ($this->get_encoding()) {
1979                  $header .= ' charset=' . $this->get_encoding();
1980              } else {
1981                  $header .= ' charset=UTF-8';
1982              }
1983              header($header);
1984          }
1985      }
1986  
1987      /**
1988       * Get the type of the feed
1989       *
1990       * This returns a \SimplePie\SimplePie::TYPE_* constant, which can be tested against
1991       * using {@link http://php.net/language.operators.bitwise bitwise operators}
1992       *
1993       * @since 0.8 (usage changed to using constants in 1.0)
1994       * @see \SimplePie\SimplePie::TYPE_NONE Unknown.
1995       * @see \SimplePie\SimplePie::TYPE_RSS_090 RSS 0.90.
1996       * @see \SimplePie\SimplePie::TYPE_RSS_091_NETSCAPE RSS 0.91 (Netscape).
1997       * @see \SimplePie\SimplePie::TYPE_RSS_091_USERLAND RSS 0.91 (Userland).
1998       * @see \SimplePie\SimplePie::TYPE_RSS_091 RSS 0.91.
1999       * @see \SimplePie\SimplePie::TYPE_RSS_092 RSS 0.92.
2000       * @see \SimplePie\SimplePie::TYPE_RSS_093 RSS 0.93.
2001       * @see \SimplePie\SimplePie::TYPE_RSS_094 RSS 0.94.
2002       * @see \SimplePie\SimplePie::TYPE_RSS_10 RSS 1.0.
2003       * @see \SimplePie\SimplePie::TYPE_RSS_20 RSS 2.0.x.
2004       * @see \SimplePie\SimplePie::TYPE_RSS_RDF RDF-based RSS.
2005       * @see \SimplePie\SimplePie::TYPE_RSS_SYNDICATION Non-RDF-based RSS (truly intended as syndication format).
2006       * @see \SimplePie\SimplePie::TYPE_RSS_ALL Any version of RSS.
2007       * @see \SimplePie\SimplePie::TYPE_ATOM_03 Atom 0.3.
2008       * @see \SimplePie\SimplePie::TYPE_ATOM_10 Atom 1.0.
2009       * @see \SimplePie\SimplePie::TYPE_ATOM_ALL Any version of Atom.
2010       * @see \SimplePie\SimplePie::TYPE_ALL Any known/supported feed type.
2011       * @return int \SimplePie\SimplePie::TYPE_* constant
2012       */
2013      public function get_type()
2014      {
2015          if (!isset($this->data['type'])) {
2016              $this->data['type'] = self::TYPE_ALL;
2017              if (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'])) {
2018                  $this->data['type'] &= self::TYPE_ATOM_10;
2019              } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'])) {
2020                  $this->data['type'] &= self::TYPE_ATOM_03;
2021              } elseif (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'])) {
2022                  if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['channel'])
2023                  || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['image'])
2024                  || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['item'])
2025                  || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_10]['textinput'])) {
2026                      $this->data['type'] &= self::TYPE_RSS_10;
2027                  }
2028                  if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['channel'])
2029                  || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['image'])
2030                  || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['item'])
2031                  || isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][self::NAMESPACE_RSS_090]['textinput'])) {
2032                      $this->data['type'] &= self::TYPE_RSS_090;
2033                  }
2034              } elseif (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'])) {
2035                  $this->data['type'] &= self::TYPE_RSS_ALL;
2036                  if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) {
2037                      switch (trim($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) {
2038                          case '0.91':
2039                              $this->data['type'] &= self::TYPE_RSS_091;
2040                              if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][self::NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) {
2041                                  switch (trim($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][self::NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) {
2042                                      case '0':
2043                                          $this->data['type'] &= self::TYPE_RSS_091_NETSCAPE;
2044                                          break;
2045  
2046                                      case '24':
2047                                          $this->data['type'] &= self::TYPE_RSS_091_USERLAND;
2048                                          break;
2049                                  }
2050                              }
2051                              break;
2052  
2053                          case '0.92':
2054                              $this->data['type'] &= self::TYPE_RSS_092;
2055                              break;
2056  
2057                          case '0.93':
2058                              $this->data['type'] &= self::TYPE_RSS_093;
2059                              break;
2060  
2061                          case '0.94':
2062                              $this->data['type'] &= self::TYPE_RSS_094;
2063                              break;
2064  
2065                          case '2.0':
2066                              $this->data['type'] &= self::TYPE_RSS_20;
2067                              break;
2068                      }
2069                  }
2070              } else {
2071                  $this->data['type'] = self::TYPE_NONE;
2072              }
2073          }
2074          return $this->data['type'];
2075      }
2076  
2077      /**
2078       * Get the URL for the feed
2079       *
2080       * When the 'permanent' mode is enabled, returns the original feed URL,
2081       * except in the case of an `HTTP 301 Moved Permanently` status response,
2082       * in which case the location of the first redirection is returned.
2083       *
2084       * When the 'permanent' mode is disabled (default),
2085       * may or may not be different from the URL passed to {@see set_feed_url()},
2086       * depending on whether auto-discovery was used, and whether there were
2087       * any redirects along the way.
2088       *
2089       * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.)
2090       * @todo Support <itunes:new-feed-url>
2091       * @todo Also, |atom:link|@rel=self
2092       * @param bool $permanent Permanent mode to return only the original URL or the first redirection
2093       * iff it is a 301 redirection
2094       * @return string|null
2095       */
2096      public function subscribe_url($permanent = false)
2097      {
2098          if ($permanent) {
2099              if ($this->permanent_url !== null) {
2100                  // sanitize encodes ampersands which are required when used in a url.
2101                  return str_replace(
2102                      '&amp;',
2103                      '&',
2104                      $this->sanitize(
2105                          $this->permanent_url,
2106                          self::CONSTRUCT_IRI
2107                      )
2108                  );
2109              }
2110          } else {
2111              if ($this->feed_url !== null) {
2112                  return str_replace(
2113                      '&amp;',
2114                      '&',
2115                      $this->sanitize(
2116                          $this->feed_url,
2117                          self::CONSTRUCT_IRI
2118                      )
2119                  );
2120              }
2121          }
2122          return null;
2123      }
2124  
2125      /**
2126       * Get data for an feed-level element
2127       *
2128       * This method allows you to get access to ANY element/attribute that is a
2129       * sub-element of the opening feed tag.
2130       *
2131       * The return value is an indexed array of elements matching the given
2132       * namespace and tag name. Each element has `attribs`, `data` and `child`
2133       * subkeys. For `attribs` and `child`, these contain namespace subkeys.
2134       * `attribs` then has one level of associative name => value data (where
2135       * `value` is a string) after the namespace. `child` has tag-indexed keys
2136       * after the namespace, each member of which is an indexed array matching
2137       * this same format.
2138       *
2139       * For example:
2140       * <pre>
2141       * // This is probably a bad example because we already support
2142       * // <media:content> natively, but it shows you how to parse through
2143       * // the nodes.
2144       * $group = $item->get_item_tags(\SimplePie\SimplePie::NAMESPACE_MEDIARSS, 'group');
2145       * $content = $group[0]['child'][\SimplePie\SimplePie::NAMESPACE_MEDIARSS]['content'];
2146       * $file = $content[0]['attribs']['']['url'];
2147       * echo $file;
2148       * </pre>
2149       *
2150       * @since 1.0
2151       * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
2152       * @param string $namespace The URL of the XML namespace of the elements you're trying to access
2153       * @param string $tag Tag name
2154       * @return array
2155       */
2156      public function get_feed_tags($namespace, $tag)
2157      {
2158          $type = $this->get_type();
2159          if ($type & self::TYPE_ATOM_10) {
2160              if (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag])) {
2161                  return $this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag];
2162              }
2163          }
2164          if ($type & self::TYPE_ATOM_03) {
2165              if (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag])) {
2166                  return $this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag];
2167              }
2168          }
2169          if ($type & self::TYPE_RSS_RDF) {
2170              if (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag])) {
2171                  return $this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag];
2172              }
2173          }
2174          if ($type & self::TYPE_RSS_SYNDICATION) {
2175              if (isset($this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag])) {
2176                  return $this->data['child'][self::NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag];
2177              }
2178          }
2179          return null;
2180      }
2181  
2182      /**
2183       * Get data for an channel-level element
2184       *
2185       * This method allows you to get access to ANY element/attribute in the
2186       * channel/header section of the feed.
2187       *
2188       * See {@see SimplePie::get_feed_tags()} for a description of the return value
2189       *
2190       * @since 1.0
2191       * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
2192       * @param string $namespace The URL of the XML namespace of the elements you're trying to access
2193       * @param string $tag Tag name
2194       * @return array
2195       */
2196      public function get_channel_tags($namespace, $tag)
2197      {
2198          $type = $this->get_type();
2199          if ($type & self::TYPE_ATOM_ALL) {
2200              if ($return = $this->get_feed_tags($namespace, $tag)) {
2201                  return $return;
2202              }
2203          }
2204          if ($type & self::TYPE_RSS_10) {
2205              if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'channel')) {
2206                  if (isset($channel[0]['child'][$namespace][$tag])) {
2207                      return $channel[0]['child'][$namespace][$tag];
2208                  }
2209              }
2210          }
2211          if ($type & self::TYPE_RSS_090) {
2212              if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'channel')) {
2213                  if (isset($channel[0]['child'][$namespace][$tag])) {
2214                      return $channel[0]['child'][$namespace][$tag];
2215                  }
2216              }
2217          }
2218          if ($type & self::TYPE_RSS_SYNDICATION) {
2219              if ($channel = $this->get_feed_tags(self::NAMESPACE_RSS_20, 'channel')) {
2220                  if (isset($channel[0]['child'][$namespace][$tag])) {
2221                      return $channel[0]['child'][$namespace][$tag];
2222                  }
2223              }
2224          }
2225          return null;
2226      }
2227  
2228      /**
2229       * Get data for an channel-level element
2230       *
2231       * This method allows you to get access to ANY element/attribute in the
2232       * image/logo section of the feed.
2233       *
2234       * See {@see SimplePie::get_feed_tags()} for a description of the return value
2235       *
2236       * @since 1.0
2237       * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
2238       * @param string $namespace The URL of the XML namespace of the elements you're trying to access
2239       * @param string $tag Tag name
2240       * @return array
2241       */
2242      public function get_image_tags($namespace, $tag)
2243      {
2244          $type = $this->get_type();
2245          if ($type & self::TYPE_RSS_10) {
2246              if ($image = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'image')) {
2247                  if (isset($image[0]['child'][$namespace][$tag])) {
2248                      return $image[0]['child'][$namespace][$tag];
2249                  }
2250              }
2251          }
2252          if ($type & self::TYPE_RSS_090) {
2253              if ($image = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'image')) {
2254                  if (isset($image[0]['child'][$namespace][$tag])) {
2255                      return $image[0]['child'][$namespace][$tag];
2256                  }
2257              }
2258          }
2259          if ($type & self::TYPE_RSS_SYNDICATION) {
2260              if ($image = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'image')) {
2261                  if (isset($image[0]['child'][$namespace][$tag])) {
2262                      return $image[0]['child'][$namespace][$tag];
2263                  }
2264              }
2265          }
2266          return null;
2267      }
2268  
2269      /**
2270       * Get the base URL value from the feed
2271       *
2272       * Uses `<xml:base>` if available, otherwise uses the first link in the
2273       * feed, or failing that, the URL of the feed itself.
2274       *
2275       * @see get_link
2276       * @see subscribe_url
2277       *
2278       * @param array $element
2279       * @return string
2280       */
2281      public function get_base($element = [])
2282      {
2283          if (!empty($element['xml_base_explicit']) && isset($element['xml_base'])) {
2284              return $element['xml_base'];
2285          } elseif ($this->get_link() !== null) {
2286              return $this->get_link();
2287          }
2288  
2289          return $this->subscribe_url();
2290      }
2291  
2292      /**
2293       * Sanitize feed data
2294       *
2295       * @access private
2296       * @see \SimplePie\Sanitize::sanitize()
2297       * @param string $data Data to sanitize
2298       * @param int $type One of the \SimplePie\SimplePie::CONSTRUCT_* constants
2299       * @param string $base Base URL to resolve URLs against
2300       * @return string Sanitized data
2301       */
2302      public function sanitize($data, $type, $base = '')
2303      {
2304          try {
2305              return $this->sanitize->sanitize($data, $type, $base);
2306          } catch (\SimplePie\Exception $e) {
2307              if (!$this->enable_exceptions) {
2308                  $this->error = $e->getMessage();
2309                  $this->registry->call(Misc::class, 'error', [$this->error, E_USER_WARNING, $e->getFile(), $e->getLine()]);
2310                  return '';
2311              }
2312  
2313              throw $e;
2314          }
2315      }
2316  
2317      /**
2318       * Get the title of the feed
2319       *
2320       * Uses `<atom:title>`, `<title>` or `<dc:title>`
2321       *
2322       * @since 1.0 (previously called `get_feed_title` since 0.8)
2323       * @return string|null
2324       */
2325      public function get_title()
2326      {
2327          if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'title')) {
2328              return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
2329          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'title')) {
2330              return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
2331          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'title')) {
2332              return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
2333          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'title')) {
2334              return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
2335          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'title')) {
2336              return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
2337          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'title')) {
2338              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2339          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'title')) {
2340              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2341          }
2342  
2343          return null;
2344      }
2345  
2346      /**
2347       * Get a category for the feed
2348       *
2349       * @since Unknown
2350       * @param int $key The category that you want to return. Remember that arrays begin with 0, not 1
2351       * @return \SimplePie\Category|null
2352       */
2353      public function get_category($key = 0)
2354      {
2355          $categories = $this->get_categories();
2356          if (isset($categories[$key])) {
2357              return $categories[$key];
2358          }
2359  
2360          return null;
2361      }
2362  
2363      /**
2364       * Get all categories for the feed
2365       *
2366       * Uses `<atom:category>`, `<category>` or `<dc:subject>`
2367       *
2368       * @since Unknown
2369       * @return array|null List of {@see \SimplePie\Category} objects
2370       */
2371      public function get_categories()
2372      {
2373          $categories = [];
2374  
2375          foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'category') as $category) {
2376              $term = null;
2377              $scheme = null;
2378              $label = null;
2379              if (isset($category['attribs']['']['term'])) {
2380                  $term = $this->sanitize($category['attribs']['']['term'], self::CONSTRUCT_TEXT);
2381              }
2382              if (isset($category['attribs']['']['scheme'])) {
2383                  $scheme = $this->sanitize($category['attribs']['']['scheme'], self::CONSTRUCT_TEXT);
2384              }
2385              if (isset($category['attribs']['']['label'])) {
2386                  $label = $this->sanitize($category['attribs']['']['label'], self::CONSTRUCT_TEXT);
2387              }
2388              $categories[] = $this->registry->create(Category::class, [$term, $scheme, $label]);
2389          }
2390          foreach ((array) $this->get_channel_tags(self::NAMESPACE_RSS_20, 'category') as $category) {
2391              // This is really the label, but keep this as the term also for BC.
2392              // Label will also work on retrieving because that falls back to term.
2393              $term = $this->sanitize($category['data'], self::CONSTRUCT_TEXT);
2394              if (isset($category['attribs']['']['domain'])) {
2395                  $scheme = $this->sanitize($category['attribs']['']['domain'], self::CONSTRUCT_TEXT);
2396              } else {
2397                  $scheme = null;
2398              }
2399              $categories[] = $this->registry->create(Category::class, [$term, $scheme, null]);
2400          }
2401          foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_11, 'subject') as $category) {
2402              $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], self::CONSTRUCT_TEXT), null, null]);
2403          }
2404          foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_10, 'subject') as $category) {
2405              $categories[] = $this->registry->create(Category::class, [$this->sanitize($category['data'], self::CONSTRUCT_TEXT), null, null]);
2406          }
2407  
2408          if (!empty($categories)) {
2409              return array_unique($categories);
2410          }
2411  
2412          return null;
2413      }
2414  
2415      /**
2416       * Get an author for the feed
2417       *
2418       * @since 1.1
2419       * @param int $key The author that you want to return. Remember that arrays begin with 0, not 1
2420       * @return \SimplePie\Author|null
2421       */
2422      public function get_author($key = 0)
2423      {
2424          $authors = $this->get_authors();
2425          if (isset($authors[$key])) {
2426              return $authors[$key];
2427          }
2428  
2429          return null;
2430      }
2431  
2432      /**
2433       * Get all authors for the feed
2434       *
2435       * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>`
2436       *
2437       * @since 1.1
2438       * @return array|null List of {@see \SimplePie\Author} objects
2439       */
2440      public function get_authors()
2441      {
2442          $authors = [];
2443          foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'author') as $author) {
2444              $name = null;
2445              $uri = null;
2446              $email = null;
2447              if (isset($author['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'])) {
2448                  $name = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'], self::CONSTRUCT_TEXT);
2449              }
2450              if (isset($author['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'])) {
2451                  $uri = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'], self::CONSTRUCT_IRI, $this->get_base($author['child'][self::NAMESPACE_ATOM_10]['uri'][0]));
2452              }
2453              if (isset($author['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'])) {
2454                  $email = $this->sanitize($author['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'], self::CONSTRUCT_TEXT);
2455              }
2456              if ($name !== null || $email !== null || $uri !== null) {
2457                  $authors[] = $this->registry->create(Author::class, [$name, $uri, $email]);
2458              }
2459          }
2460          if ($author = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'author')) {
2461              $name = null;
2462              $url = null;
2463              $email = null;
2464              if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'])) {
2465                  $name = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'], self::CONSTRUCT_TEXT);
2466              }
2467              if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'])) {
2468                  $url = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'], self::CONSTRUCT_IRI, $this->get_base($author[0]['child'][self::NAMESPACE_ATOM_03]['url'][0]));
2469              }
2470              if (isset($author[0]['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'])) {
2471                  $email = $this->sanitize($author[0]['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'], self::CONSTRUCT_TEXT);
2472              }
2473              if ($name !== null || $email !== null || $url !== null) {
2474                  $authors[] = $this->registry->create(Author::class, [$name, $url, $email]);
2475              }
2476          }
2477          foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_11, 'creator') as $author) {
2478              $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]);
2479          }
2480          foreach ((array) $this->get_channel_tags(self::NAMESPACE_DC_10, 'creator') as $author) {
2481              $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]);
2482          }
2483          foreach ((array) $this->get_channel_tags(self::NAMESPACE_ITUNES, 'author') as $author) {
2484              $authors[] = $this->registry->create(Author::class, [$this->sanitize($author['data'], self::CONSTRUCT_TEXT), null, null]);
2485          }
2486  
2487          if (!empty($authors)) {
2488              return array_unique($authors);
2489          }
2490  
2491          return null;
2492      }
2493  
2494      /**
2495       * Get a contributor for the feed
2496       *
2497       * @since 1.1
2498       * @param int $key The contrbutor that you want to return. Remember that arrays begin with 0, not 1
2499       * @return \SimplePie\Author|null
2500       */
2501      public function get_contributor($key = 0)
2502      {
2503          $contributors = $this->get_contributors();
2504          if (isset($contributors[$key])) {
2505              return $contributors[$key];
2506          }
2507  
2508          return null;
2509      }
2510  
2511      /**
2512       * Get all contributors for the feed
2513       *
2514       * Uses `<atom:contributor>`
2515       *
2516       * @since 1.1
2517       * @return array|null List of {@see \SimplePie\Author} objects
2518       */
2519      public function get_contributors()
2520      {
2521          $contributors = [];
2522          foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'contributor') as $contributor) {
2523              $name = null;
2524              $uri = null;
2525              $email = null;
2526              if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'])) {
2527                  $name = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['name'][0]['data'], self::CONSTRUCT_TEXT);
2528              }
2529              if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'])) {
2530                  $uri = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]['data'], self::CONSTRUCT_IRI, $this->get_base($contributor['child'][self::NAMESPACE_ATOM_10]['uri'][0]));
2531              }
2532              if (isset($contributor['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'])) {
2533                  $email = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_10]['email'][0]['data'], self::CONSTRUCT_TEXT);
2534              }
2535              if ($name !== null || $email !== null || $uri !== null) {
2536                  $contributors[] = $this->registry->create(Author::class, [$name, $uri, $email]);
2537              }
2538          }
2539          foreach ((array) $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'contributor') as $contributor) {
2540              $name = null;
2541              $url = null;
2542              $email = null;
2543              if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'])) {
2544                  $name = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['name'][0]['data'], self::CONSTRUCT_TEXT);
2545              }
2546              if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'])) {
2547                  $url = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]['data'], self::CONSTRUCT_IRI, $this->get_base($contributor['child'][self::NAMESPACE_ATOM_03]['url'][0]));
2548              }
2549              if (isset($contributor['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'])) {
2550                  $email = $this->sanitize($contributor['child'][self::NAMESPACE_ATOM_03]['email'][0]['data'], self::CONSTRUCT_TEXT);
2551              }
2552              if ($name !== null || $email !== null || $url !== null) {
2553                  $contributors[] = $this->registry->create(Author::class, [$name, $url, $email]);
2554              }
2555          }
2556  
2557          if (!empty($contributors)) {
2558              return array_unique($contributors);
2559          }
2560  
2561          return null;
2562      }
2563  
2564      /**
2565       * Get a single link for the feed
2566       *
2567       * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8)
2568       * @param int $key The link that you want to return. Remember that arrays begin with 0, not 1
2569       * @param string $rel The relationship of the link to return
2570       * @return string|null Link URL
2571       */
2572      public function get_link($key = 0, $rel = 'alternate')
2573      {
2574          $links = $this->get_links($rel);
2575          if (isset($links[$key])) {
2576              return $links[$key];
2577          }
2578  
2579          return null;
2580      }
2581  
2582      /**
2583       * Get the permalink for the item
2584       *
2585       * Returns the first link available with a relationship of "alternate".
2586       * Identical to {@see get_link()} with key 0
2587       *
2588       * @see get_link
2589       * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8)
2590       * @internal Added for parity between the parent-level and the item/entry-level.
2591       * @return string|null Link URL
2592       */
2593      public function get_permalink()
2594      {
2595          return $this->get_link(0);
2596      }
2597  
2598      /**
2599       * Get all links for the feed
2600       *
2601       * Uses `<atom:link>` or `<link>`
2602       *
2603       * @since Beta 2
2604       * @param string $rel The relationship of links to return
2605       * @return array|null Links found for the feed (strings)
2606       */
2607      public function get_links($rel = 'alternate')
2608      {
2609          if (!isset($this->data['links'])) {
2610              $this->data['links'] = [];
2611              if ($links = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'link')) {
2612                  foreach ($links as $link) {
2613                      if (isset($link['attribs']['']['href'])) {
2614                          $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
2615                          $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], self::CONSTRUCT_IRI, $this->get_base($link));
2616                      }
2617                  }
2618              }
2619              if ($links = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'link')) {
2620                  foreach ($links as $link) {
2621                      if (isset($link['attribs']['']['href'])) {
2622                          $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
2623                          $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], self::CONSTRUCT_IRI, $this->get_base($link));
2624                      }
2625                  }
2626              }
2627              if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'link')) {
2628                  $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0]));
2629              }
2630              if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'link')) {
2631                  $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0]));
2632              }
2633              if ($links = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'link')) {
2634                  $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], self::CONSTRUCT_IRI, $this->get_base($links[0]));
2635              }
2636  
2637              $keys = array_keys($this->data['links']);
2638              foreach ($keys as $key) {
2639                  if ($this->registry->call(Misc::class, 'is_isegment_nz_nc', [$key])) {
2640                      if (isset($this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key])) {
2641                          $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key]);
2642                          $this->data['links'][$key] = &$this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key];
2643                      } else {
2644                          $this->data['links'][self::IANA_LINK_RELATIONS_REGISTRY . $key] = &$this->data['links'][$key];
2645                      }
2646                  } elseif (substr($key, 0, 41) === self::IANA_LINK_RELATIONS_REGISTRY) {
2647                      $this->data['links'][substr($key, 41)] = &$this->data['links'][$key];
2648                  }
2649                  $this->data['links'][$key] = array_unique($this->data['links'][$key]);
2650              }
2651          }
2652  
2653          if (isset($this->data['headers']['link'])) {
2654              $link_headers = $this->data['headers']['link'];
2655              if (is_array($link_headers)) {
2656                  $link_headers = implode(',', $link_headers);
2657              }
2658              // https://datatracker.ietf.org/doc/html/rfc8288
2659              if (is_string($link_headers) &&
2660                  preg_match_all('/<(?P<uri>[^>]+)>\s*;\s*rel\s*=\s*(?P<quote>"?)' . preg_quote($rel) . '(?P=quote)\s*(?=,|$)/i', $link_headers, $matches)) {
2661                  return $matches['uri'];
2662              }
2663          }
2664  
2665          if (isset($this->data['links'][$rel])) {
2666              return $this->data['links'][$rel];
2667          }
2668  
2669          return null;
2670      }
2671  
2672      public function get_all_discovered_feeds()
2673      {
2674          return $this->all_discovered_feeds;
2675      }
2676  
2677      /**
2678       * Get the content for the item
2679       *
2680       * Uses `<atom:subtitle>`, `<atom:tagline>`, `<description>`,
2681       * `<dc:description>`, `<itunes:summary>` or `<itunes:subtitle>`
2682       *
2683       * @since 1.0 (previously called `get_feed_description()` since 0.8)
2684       * @return string|null
2685       */
2686      public function get_description()
2687      {
2688          if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'subtitle')) {
2689              return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
2690          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'tagline')) {
2691              return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
2692          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_10, 'description')) {
2693              return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
2694          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_090, 'description')) {
2695              return $this->sanitize($return[0]['data'], self::CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
2696          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'description')) {
2697              return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0]));
2698          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'description')) {
2699              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2700          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'description')) {
2701              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2702          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'summary')) {
2703              return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0]));
2704          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'subtitle')) {
2705              return $this->sanitize($return[0]['data'], self::CONSTRUCT_HTML, $this->get_base($return[0]));
2706          }
2707  
2708          return null;
2709      }
2710  
2711      /**
2712       * Get the copyright info for the feed
2713       *
2714       * Uses `<atom:rights>`, `<atom:copyright>` or `<dc:rights>`
2715       *
2716       * @since 1.0 (previously called `get_feed_copyright()` since 0.8)
2717       * @return string|null
2718       */
2719      public function get_copyright()
2720      {
2721          if ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'rights')) {
2722              return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_10_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
2723          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_03, 'copyright')) {
2724              return $this->sanitize($return[0]['data'], $this->registry->call(Misc::class, 'atom_03_construct_type', [$return[0]['attribs']]), $this->get_base($return[0]));
2725          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'copyright')) {
2726              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2727          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'rights')) {
2728              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2729          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'rights')) {
2730              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2731          }
2732  
2733          return null;
2734      }
2735  
2736      /**
2737       * Get the language for the feed
2738       *
2739       * Uses `<language>`, `<dc:language>`, or @xml_lang
2740       *
2741       * @since 1.0 (previously called `get_feed_language()` since 0.8)
2742       * @return string|null
2743       */
2744      public function get_language()
2745      {
2746          if ($return = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'language')) {
2747              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2748          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_11, 'language')) {
2749              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2750          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_DC_10, 'language')) {
2751              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2752          } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['xml_lang'])) {
2753              return $this->sanitize($this->data['child'][self::NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], self::CONSTRUCT_TEXT);
2754          } elseif (isset($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['xml_lang'])) {
2755              return $this->sanitize($this->data['child'][self::NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], self::CONSTRUCT_TEXT);
2756          } elseif (isset($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['xml_lang'])) {
2757              return $this->sanitize($this->data['child'][self::NAMESPACE_RDF]['RDF'][0]['xml_lang'], self::CONSTRUCT_TEXT);
2758          } elseif (isset($this->data['headers']['content-language'])) {
2759              return $this->sanitize($this->data['headers']['content-language'], self::CONSTRUCT_TEXT);
2760          }
2761  
2762          return null;
2763      }
2764  
2765      /**
2766       * Get the latitude coordinates for the item
2767       *
2768       * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications
2769       *
2770       * Uses `<geo:lat>` or `<georss:point>`
2771       *
2772       * @since 1.0
2773       * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo
2774       * @link http://www.georss.org/ GeoRSS
2775       * @return string|null
2776       */
2777      public function get_latitude()
2778      {
2779          if ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'lat')) {
2780              return (float) $return[0]['data'];
2781          } elseif (($return = $this->get_channel_tags(self::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) {
2782              return (float) $match[1];
2783          }
2784  
2785          return null;
2786      }
2787  
2788      /**
2789       * Get the longitude coordinates for the feed
2790       *
2791       * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications
2792       *
2793       * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>`
2794       *
2795       * @since 1.0
2796       * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo
2797       * @link http://www.georss.org/ GeoRSS
2798       * @return string|null
2799       */
2800      public function get_longitude()
2801      {
2802          if ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'long')) {
2803              return (float) $return[0]['data'];
2804          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_W3C_BASIC_GEO, 'lon')) {
2805              return (float) $return[0]['data'];
2806          } elseif (($return = $this->get_channel_tags(self::NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) {
2807              return (float) $match[2];
2808          }
2809  
2810          return null;
2811      }
2812  
2813      /**
2814       * Get the feed logo's title
2815       *
2816       * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" title.
2817       *
2818       * Uses `<image><title>` or `<image><dc:title>`
2819       *
2820       * @return string|null
2821       */
2822      public function get_image_title()
2823      {
2824          if ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'title')) {
2825              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2826          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'title')) {
2827              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2828          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'title')) {
2829              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2830          } elseif ($return = $this->get_image_tags(self::NAMESPACE_DC_11, 'title')) {
2831              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2832          } elseif ($return = $this->get_image_tags(self::NAMESPACE_DC_10, 'title')) {
2833              return $this->sanitize($return[0]['data'], self::CONSTRUCT_TEXT);
2834          }
2835  
2836          return null;
2837      }
2838  
2839      /**
2840       * Get the feed logo's URL
2841       *
2842       * RSS 0.9.0, 2.0, Atom 1.0, and feeds with iTunes RSS tags are allowed to
2843       * have a "feed logo" URL. This points directly to the image itself.
2844       *
2845       * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`,
2846       * `<image><title>` or `<image><dc:title>`
2847       *
2848       * @return string|null
2849       */
2850      public function get_image_url()
2851      {
2852          if ($return = $this->get_channel_tags(self::NAMESPACE_ITUNES, 'image')) {
2853              return $this->sanitize($return[0]['attribs']['']['href'], self::CONSTRUCT_IRI);
2854          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'logo')) {
2855              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2856          } elseif ($return = $this->get_channel_tags(self::NAMESPACE_ATOM_10, 'icon')) {
2857              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2858          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'url')) {
2859              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2860          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'url')) {
2861              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2862          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) {
2863              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2864          }
2865  
2866          return null;
2867      }
2868  
2869  
2870      /**
2871       * Get the feed logo's link
2872       *
2873       * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" link. This
2874       * points to a human-readable page that the image should link to.
2875       *
2876       * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`,
2877       * `<image><title>` or `<image><dc:title>`
2878       *
2879       * @return string|null
2880       */
2881      public function get_image_link()
2882      {
2883          if ($return = $this->get_image_tags(self::NAMESPACE_RSS_10, 'link')) {
2884              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2885          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_090, 'link')) {
2886              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2887          } elseif ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'link')) {
2888              return $this->sanitize($return[0]['data'], self::CONSTRUCT_IRI, $this->get_base($return[0]));
2889          }
2890  
2891          return null;
2892      }
2893  
2894      /**
2895       * Get the feed logo's link
2896       *
2897       * RSS 2.0 feeds are allowed to have a "feed logo" width.
2898       *
2899       * Uses `<image><width>` or defaults to 88 if no width is specified and
2900       * the feed is an RSS 2.0 feed.
2901       *
2902       * @return int|null
2903       */
2904      public function get_image_width()
2905      {
2906          if ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'width')) {
2907              return intval($return[0]['data']);
2908          } elseif ($this->get_type() & self::TYPE_RSS_SYNDICATION && $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) {
2909              return 88;
2910          }
2911  
2912          return null;
2913      }
2914  
2915      /**
2916       * Get the feed logo's height
2917       *
2918       * RSS 2.0 feeds are allowed to have a "feed logo" height.
2919       *
2920       * Uses `<image><height>` or defaults to 31 if no height is specified and
2921       * the feed is an RSS 2.0 feed.
2922       *
2923       * @return int|null
2924       */
2925      public function get_image_height()
2926      {
2927          if ($return = $this->get_image_tags(self::NAMESPACE_RSS_20, 'height')) {
2928              return intval($return[0]['data']);
2929          } elseif ($this->get_type() & self::TYPE_RSS_SYNDICATION && $this->get_image_tags(self::NAMESPACE_RSS_20, 'url')) {
2930              return 31;
2931          }
2932  
2933          return null;
2934      }
2935  
2936      /**
2937       * Get the number of items in the feed
2938       *
2939       * This is well-suited for {@link http://php.net/for for()} loops with
2940       * {@see get_item()}
2941       *
2942       * @param int $max Maximum value to return. 0 for no limit
2943       * @return int Number of items in the feed
2944       */
2945      public function get_item_quantity($max = 0)
2946      {
2947          $max = (int) $max;
2948          $qty = count($this->get_items());
2949          if ($max === 0) {
2950              return $qty;
2951          }
2952  
2953          return ($qty > $max) ? $max : $qty;
2954      }
2955  
2956      /**
2957       * Get a single item from the feed
2958       *
2959       * This is better suited for {@link http://php.net/for for()} loops, whereas
2960       * {@see get_items()} is better suited for
2961       * {@link http://php.net/foreach foreach()} loops.
2962       *
2963       * @see get_item_quantity()
2964       * @since Beta 2
2965       * @param int $key The item that you want to return. Remember that arrays begin with 0, not 1
2966       * @return \SimplePie\Item|null
2967       */
2968      public function get_item($key = 0)
2969      {
2970          $items = $this->get_items();
2971          if (isset($items[$key])) {
2972              return $items[$key];
2973          }
2974  
2975          return null;
2976      }
2977  
2978      /**
2979       * Get all items from the feed
2980       *
2981       * This is better suited for {@link http://php.net/for for()} loops, whereas
2982       * {@see get_items()} is better suited for
2983       * {@link http://php.net/foreach foreach()} loops.
2984       *
2985       * @see get_item_quantity
2986       * @since Beta 2
2987       * @param int $start Index to start at
2988       * @param int $end Number of items to return. 0 for all items after `$start`
2989       * @return \SimplePie\Item[]|null List of {@see \SimplePie\Item} objects
2990       */
2991      public function get_items($start = 0, $end = 0)
2992      {
2993          if (!isset($this->data['items'])) {
2994              if (!empty($this->multifeed_objects)) {
2995                  $this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit);
2996                  if (empty($this->data['items'])) {
2997                      return [];
2998                  }
2999                  return $this->data['items'];
3000              }
3001              $this->data['items'] = [];
3002              if ($items = $this->get_feed_tags(self::NAMESPACE_ATOM_10, 'entry')) {
3003                  $keys = array_keys($items);
3004                  foreach ($keys as $key) {
3005                      $this->data['items'][] = $this->registry->create(Item::class, [$this, $items[$key]]);
3006                  }
3007              }
3008              if ($items = $this->get_feed_tags(self::NAMESPACE_ATOM_03, 'entry')) {
3009                  $keys = array_keys($items);
3010                  foreach ($keys as $key) {
3011                      $this->data['items'][] = $this->registry->create(Item::class, [$this, $items[$key]]);
3012                  }
3013              }
3014              if ($items = $this->get_feed_tags(self::NAMESPACE_RSS_10, 'item')) {
3015                  $keys = array_keys($items);
3016                  foreach ($keys as $key) {
3017                      $this->data['items'][] = $this->registry->create(Item::class, [$this, $items[$key]]);
3018                  }
3019              }
3020              if ($items = $this->get_feed_tags(self::NAMESPACE_RSS_090, 'item')) {
3021                  $keys = array_keys($items);
3022                  foreach ($keys as $key) {
3023                      $this->data['items'][] = $this->registry->create(Item::class, [$this, $items[$key]]);
3024                  }
3025              }
3026              if ($items = $this->get_channel_tags(self::NAMESPACE_RSS_20, 'item')) {
3027                  $keys = array_keys($items);
3028                  foreach ($keys as $key) {
3029                      $this->data['items'][] = $this->registry->create(Item::class, [$this, $items[$key]]);
3030                  }
3031              }
3032          }
3033  
3034          if (empty($this->data['items'])) {
3035              return [];
3036          }
3037  
3038          if ($this->order_by_date) {
3039              if (!isset($this->data['ordered_items'])) {
3040                  $this->data['ordered_items'] = $this->data['items'];
3041                  usort($this->data['ordered_items'], [get_class($this), 'sort_items']);
3042              }
3043              $items = $this->data['ordered_items'];
3044          } else {
3045              $items = $this->data['items'];
3046          }
3047          // Slice the data as desired
3048          if ($end === 0) {
3049              return array_slice($items, $start);
3050          }
3051  
3052          return array_slice($items, $start, $end);
3053      }
3054  
3055      /**
3056       * Set the favicon handler
3057       *
3058       * @deprecated Use your own favicon handling instead
3059       */
3060      public function set_favicon_handler($page = false, $qs = 'i')
3061      {
3062          trigger_error('Favicon handling has been removed, please use your own handling', \E_USER_DEPRECATED);
3063          return false;
3064      }
3065  
3066      /**
3067       * Get the favicon for the current feed
3068       *
3069       * @deprecated Use your own favicon handling instead
3070       */
3071      public function get_favicon()
3072      {
3073          trigger_error('Favicon handling has been removed, please use your own handling', \E_USER_DEPRECATED);
3074  
3075          if (($url = $this->get_link()) !== null) {
3076              return 'https://www.google.com/s2/favicons?domain=' . urlencode($url);
3077          }
3078  
3079          return false;
3080      }
3081  
3082      /**
3083       * Magic method handler
3084       *
3085       * @param string $method Method name
3086       * @param array $args Arguments to the method
3087       * @return mixed
3088       */
3089      public function __call($method, $args)
3090      {
3091          if (strpos($method, 'subscribe_') === 0) {
3092              trigger_error('subscribe_*() has been deprecated, implement the callback yourself', \E_USER_DEPRECATED);
3093              return '';
3094          }
3095          if ($method === 'enable_xml_dump') {
3096              trigger_error('enable_xml_dump() has been deprecated, use get_raw_data() instead', \E_USER_DEPRECATED);
3097              return false;
3098          }
3099  
3100          $class = get_class($this);
3101          $trace = debug_backtrace(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
3102          $file = $trace[0]['file'];
3103          $line = $trace[0]['line'];
3104          throw new SimplePieException("Call to undefined method $class::$method() in $file on line $line");
3105      }
3106  
3107      /**
3108       * Sorting callback for items
3109       *
3110       * @access private
3111       * @param SimplePie $a
3112       * @param SimplePie $b
3113       * @return boolean
3114       */
3115      public static function sort_items($a, $b)
3116      {
3117          $a_date = $a->get_date('U');
3118          $b_date = $b->get_date('U');
3119          if ($a_date && $b_date) {
3120              return $a_date > $b_date ? -1 : 1;
3121          }
3122          // Sort items without dates to the top.
3123          if ($a_date) {
3124              return 1;
3125          }
3126          if ($b_date) {
3127              return -1;
3128          }
3129          return 0;
3130      }
3131  
3132      /**
3133       * Merge items from several feeds into one
3134       *
3135       * If you're merging multiple feeds together, they need to all have dates
3136       * for the items or else SimplePie will refuse to sort them.
3137       *
3138       * @link http://simplepie.org/wiki/tutorial/sort_multiple_feeds_by_time_and_date#if_feeds_require_separate_per-feed_settings
3139       * @param array $urls List of SimplePie feed objects to merge
3140       * @param int $start Starting item
3141       * @param int $end Number of items to return
3142       * @param int $limit Maximum number of items per feed
3143       * @return array
3144       */
3145      public static function merge_items($urls, $start = 0, $end = 0, $limit = 0)
3146      {
3147          if (is_array($urls) && sizeof($urls) > 0) {
3148              $items = [];
3149              foreach ($urls as $arg) {
3150                  if ($arg instanceof SimplePie) {
3151                      $items = array_merge($items, $arg->get_items(0, $limit));
3152                  } else {
3153                      trigger_error('Arguments must be SimplePie objects', E_USER_WARNING);
3154                  }
3155              }
3156  
3157              usort($items, [get_class($urls[0]), 'sort_items']);
3158  
3159              if ($end === 0) {
3160                  return array_slice($items, $start);
3161              }
3162  
3163              return array_slice($items, $start, $end);
3164          }
3165  
3166          trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING);
3167          return [];
3168      }
3169  
3170      /**
3171       * Store PubSubHubbub links as headers
3172       *
3173       * There is no way to find PuSH links in the body of a microformats feed,
3174       * so they are added to the headers when found, to be used later by get_links.
3175       * @param \SimplePie\File $file
3176       * @param string $hub
3177       * @param string $self
3178       */
3179      private function store_links(&$file, $hub, $self)
3180      {
3181          if (isset($file->headers['link']['hub']) ||
3182                (isset($file->headers['link']) &&
3183                 preg_match('/rel=hub/', $file->headers['link']))) {
3184              return;
3185          }
3186  
3187          if ($hub) {
3188              if (isset($file->headers['link'])) {
3189                  if ($file->headers['link'] !== '') {
3190                      $file->headers['link'] = ', ';
3191                  }
3192              } else {
3193                  $file->headers['link'] = '';
3194              }
3195              $file->headers['link'] .= '<'.$hub.'>; rel=hub';
3196              if ($self) {
3197                  $file->headers['link'] .= ', <'.$self.'>; rel=self';
3198              }
3199          }
3200      }
3201  
3202      /**
3203       * Get a DataCache
3204       *
3205       * @param string $feed_url Only needed for BC, can be removed in SimplePie 2.0.0
3206       *
3207       * @return DataCache
3208       */
3209      private function get_cache($feed_url = '')
3210      {
3211          if ($this->cache === null) {
3212              // @trigger_error(sprintf('Not providing as PSR-16 cache implementation is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()".'), \E_USER_DEPRECATED);
3213              $cache = $this->registry->call(Cache::class, 'get_handler', [
3214                  $this->cache_location,
3215                  $this->get_cache_filename($feed_url),
3216                  Base::TYPE_FEED
3217              ]);
3218  
3219              return new BaseDataCache($cache);
3220          }
3221  
3222          return $this->cache;
3223      }
3224  }
3225  
3226  class_alias('SimplePie\SimplePie', 'SimplePie');


Generated : Wed Dec 4 08:20:01 2024 Cross-referenced by PHPXref