Search moodle.org's
Developer Documentation

See Release Notes

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