| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 '–' $deprecated 104 */ 105 function get_wp_title_rss( $deprecated = '–' ) { 106 if ( '–' !== $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 '–' $deprecated 132 */ 133 function wp_title_rss( $deprecated = '–' ) { 134 if ( '–' !== $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( ']]>', ']]>', $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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Oct 25 08:20:35 2026 | Cross-referenced by PHPXref |