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