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

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1