[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> feed.php (source)

   1  <?php
   2  /**
   3   * WordPress Feed API
   4   *
   5   * Many of the functions used in here belong in The Loop, or The Loop for the
   6   * Feeds.
   7   *
   8   * @package WordPress
   9   * @subpackage Feed
  10   * @since 2.1.0
  11   */
  12  
  13  /**
  14   * Retrieves RSS container for the bloginfo function.
  15   *
  16   * You can retrieve anything that you can using the get_bloginfo() function.
  17   * Everything will be stripped of tags and characters converted, when the values
  18   * are retrieved for use in the feeds.
  19   *
  20   * @since 1.5.1
  21   *
  22   * @see get_bloginfo() For the list of possible values to display.
  23   *
  24   * @param string $show See get_bloginfo() for possible values.
  25   * @return string
  26   */
  27  function get_bloginfo_rss( $show = '' ) {
  28      $info = strip_tags( get_bloginfo( $show ) );
  29      /**
  30       * Filters the bloginfo for use in RSS feeds.
  31       *
  32       * @since 2.2.0
  33       *
  34       * @see convert_chars()
  35       * @see get_bloginfo()
  36       *
  37       * @param string $info Converted string value of the blog information.
  38       * @param string $show The type of blog information to retrieve.
  39       */
  40      return apply_filters( 'get_bloginfo_rss', convert_chars( $info ), $show );
  41  }
  42  
  43  /**
  44   * Displays RSS container for the bloginfo function.
  45   *
  46   * You can retrieve anything that you can using the get_bloginfo() function.
  47   * Everything will be stripped of tags and characters converted, when the values
  48   * are retrieved for use in the feeds.
  49   *
  50   * @since 0.71
  51   *
  52   * @see get_bloginfo() For the list of possible values to display.
  53   *
  54   * @param string $show See get_bloginfo() for possible values.
  55   */
  56  function bloginfo_rss( $show = '' ) {
  57      /**
  58       * Filters the bloginfo for display in RSS feeds.
  59       *
  60       * @since 2.1.0
  61       *
  62       * @see get_bloginfo()
  63       *
  64       * @param string $rss_container RSS container for the blog information.
  65       * @param string $show          The type of blog information to retrieve.
  66       */
  67      echo apply_filters( 'bloginfo_rss', get_bloginfo_rss( $show ), $show );
  68  }
  69  
  70  /**
  71   * Retrieves the default feed.
  72   *
  73   * The default feed is 'rss2', unless a plugin changes it through the
  74   * {@see 'default_feed'} filter.
  75   *
  76   * @since 2.5.0
  77   *
  78   * @return string Default feed, or for example 'rss2', 'atom', etc.
  79   */
  80  function get_default_feed() {
  81      /**
  82       * Filters the default feed type.
  83       *
  84       * @since 2.5.0
  85       *
  86       * @param string $feed_type Type of default feed. Possible values include 'rss2', 'atom'.
  87       *                          Default 'rss2'.
  88       */
  89      $default_feed = apply_filters( 'default_feed', 'rss2' );
  90  
  91      return ( 'rss' === $default_feed ) ? 'rss2' : $default_feed;
  92  }
  93  
  94  /**
  95   * Retrieves the blog title for the feed title.
  96   *
  97   * @since 2.2.0
  98   * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`.
  99   *
 100   * @param string $deprecated Unused.
 101   * @return string The document title.
 102   *
 103   * @phpstan-param '&#8211;' $deprecated
 104   */
 105  function get_wp_title_rss( $deprecated = '&#8211;' ) {
 106      if ( '&#8211;' !== $deprecated ) {
 107          /* translators: %s: 'document_title_separator' filter name. */
 108          _deprecated_argument( __FUNCTION__, '4.4.0', sprintf( __( 'Use the %s filter instead.' ), '<code>document_title_separator</code>' ) );
 109      }
 110  
 111      /**
 112       * Filters the blog title for use as the feed title.
 113       *
 114       * @since 2.2.0
 115       * @since 4.4.0 The `$sep` parameter was deprecated and renamed to `$deprecated`.
 116       *
 117       * @param string $title      The current blog title.
 118       * @param string $deprecated Unused.
 119       */
 120      return apply_filters( 'get_wp_title_rss', wp_get_document_title(), $deprecated );
 121  }
 122  
 123  /**
 124   * Displays the blog title for display of the feed title.
 125   *
 126   * @since 2.2.0
 127   * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`.
 128   *
 129   * @param string $deprecated Unused.
 130   *
 131   * @phpstan-param '&#8211;' $deprecated
 132   */
 133  function wp_title_rss( $deprecated = '&#8211;' ) {
 134      if ( '&#8211;' !== $deprecated ) {
 135          /* translators: %s: 'document_title_separator' filter name. */
 136          _deprecated_argument( __FUNCTION__, '4.4.0', sprintf( __( 'Use the %s filter instead.' ), '<code>document_title_separator</code>' ) );
 137      }
 138  
 139      /**
 140       * Filters the blog title for display of the feed title.
 141       *
 142       * @since 2.2.0
 143       * @since 4.4.0 The `$sep` parameter was deprecated and renamed to `$deprecated`.
 144       *
 145       * @see get_wp_title_rss()
 146       *
 147       * @param string $wp_title_rss The current blog title.
 148       * @param string $deprecated   Unused.
 149       */
 150      echo apply_filters( 'wp_title_rss', get_wp_title_rss(), $deprecated );
 151  }
 152  
 153  /**
 154   * Retrieves the current post title for the feed.
 155   *
 156   * @since 2.0.0
 157   * @since 6.6.0 Added the `$post` parameter.
 158   *
 159   * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
 160   * @return string Current post title.
 161   */
 162  function get_the_title_rss( $post = 0 ) {
 163      $title = get_the_title( $post );
 164  
 165      /**
 166       * Filters the post title for use in a feed.
 167       *
 168       * @since 1.2.0
 169       *
 170       * @param string $title The current post title.
 171       */
 172      return apply_filters( 'the_title_rss', $title );
 173  }
 174  
 175  /**
 176   * Displays the post title in the feed.
 177   *
 178   * @since 0.71
 179   */
 180  function the_title_rss() {
 181      echo get_the_title_rss();
 182  }
 183  
 184  /**
 185   * Retrieves the post content for feeds.
 186   *
 187   * @since 2.9.0
 188   *
 189   * @see get_the_content()
 190   *
 191   * @param string $feed_type The type of feed. rss2 | atom | rss | rdf
 192   * @return string The filtered content.
 193   */
 194  function get_the_content_feed( $feed_type = null ) {
 195      if ( ! $feed_type ) {
 196          $feed_type = get_default_feed();
 197      }
 198  
 199      /** This filter is documented in wp-includes/post-template.php */
 200      $content = apply_filters( 'the_content', get_the_content() );
 201      $content = str_replace( ']]>', ']]&gt;', $content );
 202  
 203      /**
 204       * Filters the post content for use in feeds.
 205       *
 206       * @since 2.9.0
 207       *
 208       * @param string $content   The current post content.
 209       * @param string $feed_type Type of feed. Possible values include 'rss2', 'atom'.
 210       *                          Default 'rss2'.
 211       */
 212      return apply_filters( 'the_content_feed', $content, $feed_type );
 213  }
 214  
 215  /**
 216   * Displays the post content for feeds.
 217   *
 218   * @since 2.9.0
 219   *
 220   * @param string $feed_type The type of feed. rss2 | atom | rss | rdf
 221   */
 222  function the_content_feed( $feed_type = null ) {
 223      echo get_the_content_feed( $feed_type );
 224  }
 225  
 226  /**
 227   * Displays the post excerpt for the feed.
 228   *
 229   * @since 0.71
 230   */
 231  function the_excerpt_rss() {
 232      $output = get_the_excerpt();
 233      /**
 234       * Filters the post excerpt for a feed.
 235       *
 236       * @since 1.2.0
 237       *
 238       * @param string $output The current post excerpt.
 239       */
 240      echo apply_filters( 'the_excerpt_rss', $output );
 241  }
 242  
 243  /**
 244   * Displays the permalink to the post for use in feeds.
 245   *
 246   * @since 2.3.0
 247   */
 248  function the_permalink_rss() {
 249      /**
 250       * Filters the permalink to the post for use in feeds.
 251       *
 252       * @since 2.3.0
 253       *
 254       * @param string $post_permalink The current post permalink.
 255       */
 256      echo esc_url( apply_filters( 'the_permalink_rss', get_permalink() ) );
 257  }
 258  
 259  /**
 260   * Outputs the link to the comments for the current post in an XML safe way.
 261   *
 262   * @since 3.0.0
 263   */
 264  function comments_link_feed() {
 265      /**
 266       * Filters the comments permalink for the current post.
 267       *
 268       * @since 3.6.0
 269       *
 270       * @param string $comment_permalink The current comment permalink with
 271       *                                  '#comments' appended.
 272       */
 273      echo esc_url( apply_filters( 'comments_link_feed', get_comments_link() ) );
 274  }
 275  
 276  /**
 277   * Displays the feed GUID for the current comment.
 278   *
 279   * @since 2.5.0
 280   *
 281   * @param int|WP_Comment $comment_id Optional comment object or ID. Defaults to global comment object.
 282   */
 283  function comment_guid( $comment_id = null ) {
 284      echo esc_url( get_comment_guid( $comment_id ) );
 285  }
 286  
 287  /**
 288   * Retrieves the feed GUID for the current comment.
 289   *
 290   * @since 2.5.0
 291   *
 292   * @param int|WP_Comment $comment_id Optional comment object or ID. Defaults to global comment object.
 293   * @return string|false GUID for comment on success, false on failure.
 294   */
 295  function get_comment_guid( $comment_id = null ) {
 296      $comment = get_comment( $comment_id );
 297  
 298      if ( ! is_object( $comment ) ) {
 299          return false;
 300      }
 301  
 302      return get_the_guid( $comment->comment_post_ID ) . '#comment-' . $comment->comment_ID;
 303  }
 304  
 305  /**
 306   * Displays the link to the comments.
 307   *
 308   * @since 1.5.0
 309   * @since 4.4.0 Introduced the `$comment` argument.
 310   *
 311   * @param int|WP_Comment $comment Optional. Comment object or ID. Defaults to global comment object.
 312   */
 313  function comment_link( $comment = null ) {
 314      /**
 315       * Filters the current comment's permalink.
 316       *
 317       * @since 3.6.0
 318       *
 319       * @see get_comment_link()
 320       *
 321       * @param string $comment_permalink The current comment permalink.
 322       */
 323      echo esc_url( apply_filters( 'comment_link', get_comment_link( $comment ) ) );
 324  }
 325  
 326  /**
 327   * Retrieves the current comment author for use in the feeds.
 328   *
 329   * @since 2.0.0
 330   *
 331   * @return string Comment Author.
 332   */
 333  function get_comment_author_rss() {
 334      /**
 335       * Filters the current comment author for use in a feed.
 336       *
 337       * @since 1.5.0
 338       *
 339       * @see get_comment_author()
 340       *
 341       * @param string $comment_author The current comment author.
 342       */
 343      return apply_filters( 'comment_author_rss', get_comment_author() );
 344  }
 345  
 346  /**
 347   * Displays the current comment author in the feed.
 348   *
 349   * @since 1.0.0
 350   */
 351  function comment_author_rss() {
 352      echo get_comment_author_rss();
 353  }
 354  
 355  /**
 356   * Displays the current comment content for use in the feeds.
 357   *
 358   * @since 1.0.0
 359   */
 360  function comment_text_rss() {
 361      $comment_text = get_comment_text();
 362      /**
 363       * Filters the current comment content for use in a feed.
 364       *
 365       * @since 1.5.0
 366       *
 367       * @param string $comment_text The content of the current comment.
 368       */
 369      $comment_text = apply_filters( 'comment_text_rss', $comment_text );
 370      echo $comment_text;
 371  }
 372  
 373  /**
 374   * Retrieves all of the post categories, formatted for use in feeds.
 375   *
 376   * All of the categories for the current post in the feed loop, will be
 377   * retrieved and have feed markup added, so that they can easily be added to the
 378   * RSS2, Atom, or RSS1 and RSS0.91 RDF feeds.
 379   *
 380   * @since 2.1.0
 381   *
 382   * @param string $type Optional, default is the type returned by get_default_feed().
 383   * @return string All of the post categories for displaying in the feed.
 384   */
 385  function get_the_category_rss( $type = null ) {
 386      if ( empty( $type ) ) {
 387          $type = get_default_feed();
 388      }
 389      $categories = get_the_category();
 390      $tags       = get_the_tags();
 391      $the_list   = '';
 392      $cat_names  = array();
 393  
 394      $filter = 'rss';
 395      if ( 'atom' === $type ) {
 396          $filter = 'raw';
 397      }
 398  
 399      if ( ! empty( $categories ) ) {
 400          foreach ( (array) $categories as $category ) {
 401              $cat_names[] = sanitize_term_field( 'name', $category->name, $category->term_id, 'category', $filter );
 402          }
 403      }
 404  
 405      if ( ! empty( $tags ) ) {
 406          foreach ( (array) $tags as $tag ) {
 407              $cat_names[] = sanitize_term_field( 'name', $tag->name, $tag->term_id, 'post_tag', $filter );
 408          }
 409      }
 410  
 411      $cat_names = array_unique( $cat_names );
 412  
 413      $atom_scheme  = '';
 414      $blog_charset = '';
 415  
 416      if ( $cat_names ) {
 417          if ( 'atom' === $type ) {
 418              $atom_scheme = get_bloginfo_rss( 'url' );
 419          } elseif ( 'rdf' !== $type ) {
 420              $blog_charset = get_option( 'blog_charset' );
 421          }
 422      }
 423  
 424      foreach ( $cat_names as $cat_name ) {
 425          if ( 'rdf' === $type ) {
 426              $the_list .= "\t\t<dc:subject><![CDATA[$cat_name]]></dc:subject>\n";
 427          } elseif ( 'atom' === $type ) {
 428              $the_list .= sprintf( '<category scheme="%1$s" term="%2$s" />', esc_attr( $atom_scheme ), esc_attr( $cat_name ) );
 429          } else {
 430              $the_list .= "\t\t<category><![CDATA[" . html_entity_decode( $cat_name, ENT_COMPAT, $blog_charset ) . "]]></category>\n";
 431          }
 432      }
 433  
 434      /**
 435       * Filters all of the post categories for display in a feed.
 436       *
 437       * @since 1.2.0
 438       *
 439       * @param string $the_list All of the RSS post categories.
 440       * @param string $type     Type of feed. Possible values include 'rss2', 'atom'.
 441       *                         Default 'rss2'.
 442       */
 443      return apply_filters( 'the_category_rss', $the_list, $type );
 444  }
 445  
 446  /**
 447   * Displays the post categories in the feed.
 448   *
 449   * @since 0.71
 450   *
 451   * @see get_the_category_rss() For better explanation.
 452   *
 453   * @param string $type Optional, default is the type returned by get_default_feed().
 454   */
 455  function the_category_rss( $type = null ) {
 456      echo get_the_category_rss( $type );
 457  }
 458  
 459  /**
 460   * Displays the HTML type based on the blog setting.
 461   *
 462   * The two possible values are either 'xhtml' or 'html'.
 463   *
 464   * @since 2.2.0
 465   */
 466  function html_type_rss() {
 467      $type = get_bloginfo( 'html_type' );
 468      if ( str_contains( $type, 'xhtml' ) ) {
 469          $type = 'xhtml';
 470      } else {
 471          $type = 'html';
 472      }
 473      echo $type;
 474  }
 475  
 476  /**
 477   * Displays the rss enclosure for the current post.
 478   *
 479   * Uses the global $post to check whether the post requires a password and if
 480   * the user has the password for the post. If not then it will return before
 481   * displaying.
 482   *
 483   * Also uses the function get_post_custom() to get the post's 'enclosure'
 484   * metadata field and parses the value to display the enclosure(s). The
 485   * enclosure(s) consist of enclosure HTML tag(s) with a URI and other
 486   * attributes.
 487   *
 488   * @since 1.5.0
 489   */
 490  function rss_enclosure() {
 491      if ( post_password_required() ) {
 492          return;
 493      }
 494  
 495      foreach ( (array) get_post_custom() as $key => $val ) {
 496          if ( 'enclosure' === $key ) {
 497              foreach ( (array) $val as $enc ) {
 498                  $enclosure = explode( "\n", $enc );
 499  
 500                  if ( count( $enclosure ) < 3 ) {
 501                      continue;
 502                  }
 503  
 504                  // Only get the first element, e.g. 'audio/mpeg' from 'audio/mpeg mpga mp2 mp3'.
 505                  $t    = preg_split( '/[ \t]/', trim( $enclosure[2] ) );
 506                  $type = $t[0];
 507  
 508                  /**
 509                   * Filters the RSS enclosure HTML link tag for the current post.
 510                   *
 511                   * @since 2.2.0
 512                   *
 513                   * @param string $html_link_tag The HTML link tag with a URI and other attributes.
 514                   */
 515                  echo apply_filters( 'rss_enclosure', '<enclosure url="' . esc_url( trim( $enclosure[0] ) ) . '" length="' . absint( trim( $enclosure[1] ) ) . '" type="' . esc_attr( $type ) . '" />' . "\n" );
 516              }
 517          }
 518      }
 519  }
 520  
 521  /**
 522   * Displays the atom enclosure for the current post.
 523   *
 524   * Uses the global $post to check whether the post requires a password and if
 525   * the user has the password for the post. If not then it will return before
 526   * displaying.
 527   *
 528   * Also uses the function get_post_custom() to get the post's 'enclosure'
 529   * metadata field and parses the value to display the enclosure(s). The
 530   * enclosure(s) consist of link HTML tag(s) with a URI and other attributes.
 531   *
 532   * @since 2.2.0
 533   */
 534  function atom_enclosure() {
 535      if ( post_password_required() ) {
 536          return;
 537      }
 538  
 539      foreach ( (array) get_post_custom() as $key => $val ) {
 540          if ( 'enclosure' === $key ) {
 541              foreach ( (array) $val as $enc ) {
 542                  $enclosure = explode( "\n", $enc );
 543  
 544                  $url    = '';
 545                  $type   = '';
 546                  $length = 0;
 547  
 548                  $mimes = get_allowed_mime_types();
 549  
 550                  // Parse URL.
 551                  if ( isset( $enclosure[0] ) && is_string( $enclosure[0] ) ) {
 552                      $url = trim( $enclosure[0] );
 553                  }
 554  
 555                  // Parse length and type.
 556                  for ( $i = 1; $i <= 2; $i++ ) {
 557                      if ( isset( $enclosure[ $i ] ) ) {
 558                          if ( is_numeric( $enclosure[ $i ] ) ) {
 559                              $length = trim( $enclosure[ $i ] );
 560                          } elseif ( in_array( $enclosure[ $i ], $mimes, true ) ) {
 561                              $type = trim( $enclosure[ $i ] );
 562                          }
 563                      }
 564                  }
 565  
 566                  $html_link_tag = sprintf(
 567                      "<link href=\"%s\" rel=\"enclosure\" length=\"%d\" type=\"%s\" />\n",
 568                      esc_url( $url ),
 569                      esc_attr( $length ),
 570                      esc_attr( $type )
 571                  );
 572  
 573                  /**
 574                   * Filters the atom enclosure HTML link tag for the current post.
 575                   *
 576                   * @since 2.2.0
 577                   *
 578                   * @param string $html_link_tag The HTML link tag with a URI and other attributes.
 579                   */
 580                  echo apply_filters( 'atom_enclosure', $html_link_tag );
 581              }
 582          }
 583      }
 584  }
 585  
 586  /**
 587   * Determines the type of a string of data with the data formatted.
 588   *
 589   * Tell whether the type is text, HTML, or XHTML, per RFC 4287 section 3.1.
 590   *
 591   * In the case of WordPress, text is defined as containing no markup,
 592   * XHTML is defined as "well formed", and HTML as tag soup (i.e., the rest).
 593   *
 594   * Container div tags are added to XHTML values, per section 3.1.1.3.
 595   *
 596   * @link http://www.atomenabled.org/developers/syndication/atom-format-spec.php#rfc.section.3.1
 597   *
 598   * @since 2.5.0
 599   *
 600   * @param string $data Input string.
 601   * @return array array(type, value)
 602   */
 603  function prep_atom_text_construct( $data ) {
 604      if ( ! str_contains( $data, '<' ) && ! str_contains( $data, '&' ) ) {
 605          return array( 'text', $data );
 606      }
 607  
 608      if ( ! function_exists( 'xml_parser_create' ) ) {
 609          wp_trigger_error( '', __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
 610  
 611          return array( 'html', "<![CDATA[$data]]>" );
 612      }
 613  
 614      $parser = xml_parser_create();
 615      xml_parse( $parser, '<div>' . $data . '</div>', true );
 616      $code = xml_get_error_code( $parser );
 617  
 618      if ( PHP_VERSION_ID < 80000 ) { // xml_parser_free() has no effect as of PHP 8.0.
 619          xml_parser_free( $parser );
 620      }
 621  
 622      unset( $parser );
 623  
 624      if ( ! $code ) {
 625          if ( ! str_contains( $data, '<' ) ) {
 626              return array( 'text', $data );
 627          } else {
 628              $data = "<div xmlns='http://www.w3.org/1999/xhtml'>$data</div>";
 629              return array( 'xhtml', $data );
 630          }
 631      }
 632  
 633      if ( ! str_contains( $data, ']]>' ) ) {
 634          return array( 'html', "<![CDATA[$data]]>" );
 635      } else {
 636          return array( 'html', htmlspecialchars( $data ) );
 637      }
 638  }
 639  
 640  /**
 641   * Displays Site Icon in atom feeds.
 642   *
 643   * @since 4.3.0
 644   *
 645   * @see get_site_icon_url()
 646   */
 647  function atom_site_icon() {
 648      $url = get_site_icon_url( 32 );
 649      if ( $url ) {
 650          echo '<icon>' . convert_chars( $url ) . "</icon>\n";
 651      }
 652  }
 653  
 654  /**
 655   * Displays Site Icon in RSS2.
 656   *
 657   * @since 4.3.0
 658   */
 659  function rss2_site_icon() {
 660      $rss_title = get_wp_title_rss();
 661      if ( empty( $rss_title ) ) {
 662          $rss_title = get_bloginfo_rss( 'name' );
 663      }
 664  
 665      $url = get_site_icon_url( 32 );
 666      if ( $url ) {
 667          echo '
 668  <image>
 669      <url>' . convert_chars( $url ) . '</url>
 670      <title>' . $rss_title . '</title>
 671      <link>' . get_bloginfo_rss( 'url' ) . '</link>
 672      <width>32</width>
 673      <height>32</height>
 674  </image> ' . "\n";
 675      }
 676  }
 677  
 678  /**
 679   * Returns the link for the currently displayed feed.
 680   *
 681   * @since 5.3.0
 682   *
 683   * @return string Correct link for the atom:self element.
 684   */
 685  function get_self_link() {
 686      $parsed = parse_url( home_url() );
 687  
 688      $domain = $parsed['host'];
 689      if ( isset( $parsed['port'] ) ) {
 690          $domain .= ':' . $parsed['port'];
 691      }
 692  
 693      return set_url_scheme( 'http://' . $domain . wp_unslash( $_SERVER['REQUEST_URI'] ) );
 694  }
 695  
 696  /**
 697   * Displays the link for the currently displayed feed in a XSS safe way.
 698   *
 699   * Generate a correct link for the atom:self element.
 700   *
 701   * @since 2.5.0
 702   */
 703  function self_link() {
 704      /**
 705       * Filters the current feed URL.
 706       *
 707       * @since 3.6.0
 708       *
 709       * @see set_url_scheme()
 710       * @see wp_unslash()
 711       *
 712       * @param string $feed_link The link for the feed with set URL scheme.
 713       */
 714      echo esc_url( apply_filters( 'self_link', get_self_link() ) );
 715  }
 716  
 717  /**
 718   * Retrieves the XML namespaces for the root element of a feed.
 719   *
 720   * Namespaces are keyed by their prefix, so the same prefix cannot be
 721   * declared twice. The default namespaces of a feed type cannot be removed,
 722   * as the bundled feed templates use them in their static markup.
 723   *
 724   * @since 7.2.0
 725   *
 726   * @param string $type Type of feed. Possible values include 'rss2', 'rss2-comments',
 727   *                     'rdf', 'atom', and 'atom-comments'.
 728   * @return array<string, string> Array of namespace URIs, keyed by their prefix.
 729   * @phpstan-param non-falsy-string $type
 730   * @phpstan-return array<non-falsy-string, non-falsy-string>
 731   */
 732  function wp_get_feed_namespaces( string $type ): array {
 733      $defaults = array();
 734  
 735      switch ( $type ) {
 736          case 'rss2':
 737              $defaults = array(
 738                  'content' => 'http://purl.org/rss/1.0/modules/content/',
 739                  'wfw'     => 'http://wellformedweb.org/CommentAPI/',
 740                  'dc'      => 'http://purl.org/dc/elements/1.1/',
 741                  'atom'    => 'http://www.w3.org/2005/Atom',
 742                  'sy'      => 'http://purl.org/rss/1.0/modules/syndication/',
 743                  'slash'   => 'http://purl.org/rss/1.0/modules/slash/',
 744              );
 745              break;
 746  
 747          case 'rss2-comments':
 748              $defaults = array(
 749                  'content' => 'http://purl.org/rss/1.0/modules/content/',
 750                  'dc'      => 'http://purl.org/dc/elements/1.1/',
 751                  'atom'    => 'http://www.w3.org/2005/Atom',
 752                  'sy'      => 'http://purl.org/rss/1.0/modules/syndication/',
 753              );
 754              break;
 755  
 756          case 'rdf':
 757              $defaults = array(
 758                  'rdf'     => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 759                  'dc'      => 'http://purl.org/dc/elements/1.1/',
 760                  'sy'      => 'http://purl.org/rss/1.0/modules/syndication/',
 761                  'admin'   => 'http://webns.net/mvcb/',
 762                  'content' => 'http://purl.org/rss/1.0/modules/content/',
 763              );
 764              break;
 765  
 766          case 'atom':
 767          case 'atom-comments':
 768              $defaults = array(
 769                  'thr' => 'http://purl.org/syndication/thread/1.0',
 770              );
 771              break;
 772      }
 773  
 774      /**
 775       * Filters the XML namespaces of a feed's root element.
 776       *
 777       * Namespaces are keyed by their prefix, which makes duplicate `xmlns`
 778       * attributes impossible.
 779       *
 780       * @since 7.2.0
 781       *
 782       * @param array<string, string> $namespaces Array of namespace URIs, keyed by their prefix.
 783       * @param string                $type       Type of feed. Possible values include 'rss2',
 784       *                                          'rss2-comments', 'rdf', 'atom', and 'atom-comments'.
 785       */
 786      $namespaces = apply_filters( 'wp_feed_namespaces', $defaults, $type );
 787  
 788      if ( ! is_array( $namespaces ) ) {
 789          $namespaces = array();
 790      }
 791  
 792      // The bundled feed templates use the default namespaces, so they cannot be removed.
 793      $namespaces = array_merge( $namespaces, $defaults );
 794  
 795      $sanitized = array();
 796  
 797      foreach ( $namespaces as $prefix => $uri ) {
 798          $prefix = (string) $prefix;
 799  
 800          // Prefixes must be valid XML names, and the `xml` and `xmlns` prefixes are reserved.
 801          if ( ! preg_match( '/^[\p{L}_][\p{L}\p{M}\p{N}._\-\x{B7}]*\z/u', $prefix )
 802              || in_array( strtolower( $prefix ), array( 'xml', 'xmlns' ), true )
 803          ) {
 804              continue;
 805          }
 806  
 807          if ( ! is_string( $uri ) || empty( $uri ) ) {
 808              continue;
 809          }
 810  
 811          $sanitized[ $prefix ] = $uri;
 812      }
 813  
 814      return $sanitized;
 815  }
 816  
 817  /**
 818   * Displays the XML namespaces for the root element of a feed.
 819   *
 820   * Plugins should add namespaces via the {@see 'wp_feed_namespaces'} filter instead
 821   * of the older {@see "{$type}_ns"} actions, as two action callbacks printing the
 822   * same namespace produce a duplicate attribute, which is a well-formedness
 823   * error in XML.
 824   *
 825   * @since 7.2.0
 826   *
 827   * @param string $type Type of feed. Possible values include 'rss2', 'rss2-comments',
 828   *                     'rdf', 'atom', and 'atom-comments'.
 829   * @phpstan-param non-falsy-string $type
 830   */
 831  function wp_feed_namespaces( string $type ): void {
 832      foreach ( wp_get_feed_namespaces( $type ) as $prefix => $uri ) {
 833          printf( "xmlns:%s=\"%s\"\n\t", $prefix, esc_attr( $uri ) );
 834      }
 835  }
 836  
 837  /**
 838   * Gets the UTC time of the most recently modified post from WP_Query.
 839   *
 840   * If viewing a comment feed, the time of the most recently modified
 841   * comment will be returned.
 842   *
 843   * @since 5.2.0
 844   *
 845   * @global WP_Query $wp_query WordPress Query object.
 846   *
 847   * @param string $format Date format string to return the time in.
 848   * @return string|false The time in requested format, or false on failure.
 849   */
 850  function get_feed_build_date( $format ) {
 851      global $wp_query;
 852  
 853      $datetime          = false;
 854      $max_modified_time = false;
 855      $utc               = new DateTimeZone( 'UTC' );
 856  
 857      if ( $wp_query instanceof WP_Query && is_array( $wp_query->posts ) ) {
 858          /*
 859           * Collect the post modified times. WP_Post objects are read as-is, since
 860           * passing them through get_post() would look them up again by ID, which
 861           * drops virtual posts and discards in-memory changes. Queries using
 862           * fields => 'ids' (or 'id=>parent') yield post IDs instead, which are
 863           * looked up after priming the post cache to avoid a query per post.
 864           */
 865          $modified_times = array();
 866          $post_ids       = array();
 867          foreach ( $wp_query->posts as $post ) {
 868              if ( $post instanceof WP_Post ) {
 869                  $modified_times[] = $post->post_modified_gmt;
 870              } elseif ( is_int( $post ) ) {
 871                  // A post ID, from a query with fields => 'ids'.
 872                  $post_ids[] = $post;
 873              } elseif ( is_object( $post ) && isset( $post->ID ) && is_int( $post->ID ) ) {
 874                  /*
 875                   * A partial post object of shape object{ ID: int, post_parent: int },
 876                   * from a query with fields => 'id=>parent'. It lacks post_modified_gmt,
 877                   * so the post is looked up by its ID.
 878                   */
 879                  $post_ids[] = $post->ID;
 880              }
 881          }
 882  
 883          if ( $post_ids ) {
 884              _prime_post_caches( $post_ids, false, false );
 885              foreach ( $post_ids as $post_id ) {
 886                  $post = get_post( $post_id );
 887                  if ( $post instanceof WP_Post ) {
 888                      $modified_times[] = $post->post_modified_gmt;
 889                  }
 890              }
 891          }
 892  
 893          // If this is a comment feed, check those objects too.
 894          if ( $wp_query->is_comment_feed() && is_array( $wp_query->comments ) ) {
 895              foreach ( $wp_query->comments as $comment ) {
 896                  // A 'get_comment' filter may have returned null for a comment.
 897                  if ( $comment instanceof WP_Comment ) {
 898                      $modified_times[] = $comment->comment_date_gmt;
 899                  }
 900              }
 901          }
 902  
 903          // Determine the maximum modified time.
 904          if ( $modified_times ) {
 905              $datetime = date_create_immutable_from_format( 'Y-m-d H:i:s', max( $modified_times ), $utc );
 906          }
 907      }
 908  
 909      if ( false === $datetime ) {
 910          // Fall back to last time any post was modified or published.
 911          $datetime = date_create_immutable_from_format( 'Y-m-d H:i:s', get_lastpostmodified( 'GMT' ), $utc );
 912      }
 913  
 914      if ( false !== $datetime ) {
 915          $max_modified_time = $datetime->format( $format );
 916      }
 917  
 918      /**
 919       * Filters the date the last post or comment in the query was modified.
 920       *
 921       * @since 5.2.0
 922       *
 923       * @param string|false $max_modified_time Date the last post or comment was modified in the query, in UTC.
 924       *                                        False on failure.
 925       * @param string       $format            The date format requested in get_feed_build_date().
 926       */
 927      return apply_filters( 'get_feed_build_date', $max_modified_time, $format );
 928  }
 929  
 930  /**
 931   * Returns the content type for specified feed type.
 932   *
 933   * @since 2.8.0
 934   *
 935   * @param string $type Type of feed. Possible values include 'rss', rss2', 'atom', and 'rdf'.
 936   * @return string Content type for specified feed type.
 937   */
 938  function feed_content_type( $type = '' ) {
 939      if ( empty( $type ) ) {
 940          $type = get_default_feed();
 941      }
 942  
 943      $types = array(
 944          'rss'      => 'application/rss+xml',
 945          'rss2'     => 'application/rss+xml',
 946          'rss-http' => 'text/xml',
 947          'atom'     => 'application/atom+xml',
 948          'rdf'      => 'application/rdf+xml',
 949      );
 950  
 951      $content_type = ( ! empty( $types[ $type ] ) ) ? $types[ $type ] : 'application/octet-stream';
 952  
 953      /**
 954       * Filters the content type for a specific feed type.
 955       *
 956       * @since 2.8.0
 957       *
 958       * @param string $content_type Content type indicating the type of data that a feed contains.
 959       * @param string $type         Type of feed. Possible values include 'rss', rss2', 'atom', and 'rdf'.
 960       */
 961      return apply_filters( 'feed_content_type', $content_type, $type );
 962  }
 963  
 964  /**
 965   * Builds SimplePie object based on RSS or Atom feed from URL.
 966   *
 967   * @since 2.8.0
 968   *
 969   * @param string|string[] $url URL of feed to retrieve. If an array of URLs, the feeds are merged
 970   *                             using SimplePie's multifeed feature.
 971   *                             See also {@link http://simplepie.org/wiki/faq/typical_multifeed_gotchas}
 972   * @return SimplePie\SimplePie|WP_Error SimplePie object on success or WP_Error object on failure.
 973   */
 974  function fetch_feed( $url ) {
 975      if ( ! class_exists( 'SimplePie\SimplePie', false ) ) {
 976          require_once  ABSPATH . WPINC . '/class-simplepie.php';
 977      }
 978  
 979      require_once  ABSPATH . WPINC . '/class-wp-feed-cache-transient.php';
 980      require_once  ABSPATH . WPINC . '/class-wp-simplepie-file.php';
 981      require_once  ABSPATH . WPINC . '/class-wp-simplepie-sanitize-kses.php';
 982  
 983      $feed = new SimplePie\SimplePie();
 984  
 985      $feed->get_registry()->register( SimplePie\Sanitize::class, 'WP_SimplePie_Sanitize_KSES', true );
 986  
 987      /*
 988       * We must manually overwrite $feed->sanitize because SimplePie's constructor
 989       * sets it before we have a chance to set the sanitization class.
 990       */
 991      $feed->sanitize = new WP_SimplePie_Sanitize_KSES();
 992  
 993      // Register the cache handler using the recommended method for SimplePie 1.3 or later.
 994      if ( method_exists( 'SimplePie_Cache', 'register' ) ) {
 995          SimplePie_Cache::register( 'wp_transient', 'WP_Feed_Cache_Transient' );
 996          $feed->set_cache_location( 'wp_transient' );
 997      } else {
 998          // Back-compat for SimplePie 1.2.x.
 999          require_once  ABSPATH . WPINC . '/class-wp-feed-cache.php';
1000          $feed->set_cache_class( 'WP_Feed_Cache' );
1001      }
1002  
1003      $feed->get_registry()->register( SimplePie\File::class, 'WP_SimplePie_File', true );
1004  
1005      /** This filter is documented in wp-includes/class-wp-feed-cache-transient.php */
1006      $feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 12 * HOUR_IN_SECONDS, $url ) );
1007  
1008      /**
1009       * Fires just before processing the SimplePie feed object.
1010       *
1011       * @since 3.0.0
1012       *
1013       * @param SimplePie\SimplePie $feed SimplePie feed object (passed by reference).
1014       * @param string|string[]     $url  URL of feed or array of URLs of feeds to retrieve.
1015       */
1016      do_action_ref_array( 'wp_feed_options', array( &$feed, $url ) );
1017  
1018      if ( empty( $url ) ) {
1019          /*
1020           * @todo: Set $url to empty string once supported by SimplePie.
1021           *
1022           * The early return without proceeding is to work around a PHP 8.5
1023           * deprecation issue resolved in https://github.com/simplepie/simplepie/pull/949
1024           *
1025           * To avoid the duplicate code, this block can be replaced with `$url = '';` once SimplePie
1026           * is upgraded to a version that includes the fix.
1027           */
1028          $feed->init();
1029          $feed->set_output_encoding( get_bloginfo( 'charset' ) );
1030  
1031          if ( $feed->error() ) {
1032              return new WP_Error( 'simplepie-error', $feed->error() );
1033          }
1034  
1035          return $feed;
1036      } elseif ( is_array( $url ) && count( $url ) === 1 ) {
1037          $url = array_shift( $url );
1038      } elseif ( is_array( $url ) ) {
1039          $feeds            = array();
1040          $simplepie_errors = array();
1041          foreach ( $url as $feed_url ) {
1042              $simplepie_instance = clone $feed;
1043              $simplepie_instance->set_feed_url( $feed_url );
1044              $simplepie_instance->init();
1045              $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) );
1046  
1047              if ( $simplepie_instance->error() ) {
1048                  $simplepie_errors[] = sprintf(
1049                      /* translators: %1$s is the feed URL, %2$s is the error message. */
1050                      __( 'Error fetching feed %1$s: %2$s' ),
1051                      esc_url( $feed_url ),
1052                      $simplepie_instance->error()
1053                  );
1054                  unset( $simplepie_instance );
1055                  continue;
1056              }
1057  
1058              $feeds[] = $simplepie_instance;
1059              unset( $simplepie_instance );
1060          }
1061  
1062          if ( ! empty( $simplepie_errors ) ) {
1063              return new WP_Error( 'simplepie-error', $simplepie_errors );
1064          }
1065  
1066          $feed->init();
1067          $feed->data['items'] = SimplePie\SimplePie::merge_items( $feeds );
1068          return $feed;
1069      }
1070  
1071      $feed->set_feed_url( $url );
1072      $feed->init();
1073      $feed->set_output_encoding( get_bloginfo( 'charset' ) );
1074  
1075      if ( $feed->error() ) {
1076          return new WP_Error( 'simplepie-error', $feed->error() );
1077      }
1078  
1079      return $feed;
1080  }


Generated : Sun Oct 25 08:20:35 2026 Cross-referenced by PHPXref