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