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