feed.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  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. * Retrieves RSS container for the bloginfo function.
  14. *
  15. * You can retrieve anything that you can using the get_bloginfo() function.
  16. * Everything will be stripped of tags and characters converted, when the values
  17. * are retrieved for use in the feeds.
  18. *
  19. * @since 1.5.1
  20. *
  21. * @see get_bloginfo() For the list of possible values to display.
  22. *
  23. * @param string $show See get_bloginfo() for possible values.
  24. * @return string
  25. */
  26. function get_bloginfo_rss( $show = '' ) {
  27. $info = strip_tags( get_bloginfo( $show ) );
  28. /**
  29. * Filters the bloginfo for use in RSS feeds.
  30. *
  31. * @since 2.2.0
  32. *
  33. * @see convert_chars()
  34. * @see get_bloginfo()
  35. *
  36. * @param string $info Converted string value of the blog information.
  37. * @param string $show The type of blog information to retrieve.
  38. */
  39. return apply_filters( 'get_bloginfo_rss', convert_chars( $info ), $show );
  40. }
  41. /**
  42. * Displays RSS container for the bloginfo function.
  43. *
  44. * You can retrieve anything that you can using the get_bloginfo() function.
  45. * Everything will be stripped of tags and characters converted, when the values
  46. * are retrieved for use in the feeds.
  47. *
  48. * @since 0.71
  49. *
  50. * @see get_bloginfo() For the list of possible values to display.
  51. *
  52. * @param string $show See get_bloginfo() for possible values.
  53. */
  54. function bloginfo_rss( $show = '' ) {
  55. /**
  56. * Filters the bloginfo for display in RSS feeds.
  57. *
  58. * @since 2.1.0
  59. *
  60. * @see get_bloginfo()
  61. *
  62. * @param string $rss_container RSS container for the blog information.
  63. * @param string $show The type of blog information to retrieve.
  64. */
  65. echo apply_filters( 'bloginfo_rss', get_bloginfo_rss( $show ), $show );
  66. }
  67. /**
  68. * Retrieves the default feed.
  69. *
  70. * The default feed is 'rss2', unless a plugin changes it through the
  71. * {@see 'default_feed'} filter.
  72. *
  73. * @since 2.5.0
  74. *
  75. * @return string Default feed, or for example 'rss2', 'atom', etc.
  76. */
  77. function get_default_feed() {
  78. /**
  79. * Filters the default feed type.
  80. *
  81. * @since 2.5.0
  82. *
  83. * @param string $feed_type Type of default feed. Possible values include 'rss2', 'atom'.
  84. * Default 'rss2'.
  85. */
  86. $default_feed = apply_filters( 'default_feed', 'rss2' );
  87. return ( 'rss' === $default_feed ) ? 'rss2' : $default_feed;
  88. }
  89. /**
  90. * Retrieves the blog title for the feed title.
  91. *
  92. * @since 2.2.0
  93. * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`.
  94. *
  95. * @param string $deprecated Unused.
  96. * @return string The document title.
  97. */
  98. function get_wp_title_rss( $deprecated = '&#8211;' ) {
  99. if ( '&#8211;' !== $deprecated ) {
  100. /* translators: %s: 'document_title_separator' filter name. */
  101. _deprecated_argument( __FUNCTION__, '4.4.0', sprintf( __( 'Use the %s filter instead.' ), '<code>document_title_separator</code>' ) );
  102. }
  103. /**
  104. * Filters the blog title for use as the feed title.
  105. *
  106. * @since 2.2.0
  107. * @since 4.4.0 The `$sep` parameter was deprecated and renamed to `$deprecated`.
  108. *
  109. * @param string $title The current blog title.
  110. * @param string $deprecated Unused.
  111. */
  112. return apply_filters( 'get_wp_title_rss', wp_get_document_title(), $deprecated );
  113. }
  114. /**
  115. * Displays the blog title for display of the feed title.
  116. *
  117. * @since 2.2.0
  118. * @since 4.4.0 The optional `$sep` parameter was deprecated and renamed to `$deprecated`.
  119. *
  120. * @param string $deprecated Unused.
  121. */
  122. function wp_title_rss( $deprecated = '&#8211;' ) {
  123. if ( '&#8211;' !== $deprecated ) {
  124. /* translators: %s: 'document_title_separator' filter name. */
  125. _deprecated_argument( __FUNCTION__, '4.4.0', sprintf( __( 'Use the %s filter instead.' ), '<code>document_title_separator</code>' ) );
  126. }
  127. /**
  128. * Filters the blog title for display of the feed title.
  129. *
  130. * @since 2.2.0
  131. * @since 4.4.0 The `$sep` parameter was deprecated and renamed to `$deprecated`.
  132. *
  133. * @see get_wp_title_rss()
  134. *
  135. * @param string $wp_title_rss The current blog title.
  136. * @param string $deprecated Unused.
  137. */
  138. echo apply_filters( 'wp_title_rss', get_wp_title_rss(), $deprecated );
  139. }
  140. /**
  141. * Retrieves the current post title for the feed.
  142. *
  143. * @since 2.0.0
  144. *
  145. * @return string Current post title.
  146. */
  147. function get_the_title_rss() {
  148. $title = get_the_title();
  149. /**
  150. * Filters the post title for use in a feed.
  151. *
  152. * @since 1.2.0
  153. *
  154. * @param string $title The current post title.
  155. */
  156. return apply_filters( 'the_title_rss', $title );
  157. }
  158. /**
  159. * Displays the post title in the feed.
  160. *
  161. * @since 0.71
  162. */
  163. function the_title_rss() {
  164. echo get_the_title_rss();
  165. }
  166. /**
  167. * Retrieves the post content for feeds.
  168. *
  169. * @since 2.9.0
  170. *
  171. * @see get_the_content()
  172. *
  173. * @param string $feed_type The type of feed. rss2 | atom | rss | rdf
  174. * @return string The filtered content.
  175. */
  176. function get_the_content_feed( $feed_type = null ) {
  177. if ( ! $feed_type ) {
  178. $feed_type = get_default_feed();
  179. }
  180. /** This filter is documented in wp-includes/post-template.php */
  181. $content = apply_filters( 'the_content', get_the_content() );
  182. $content = str_replace( ']]>', ']]&gt;', $content );
  183. /**
  184. * Filters the post content for use in feeds.
  185. *
  186. * @since 2.9.0
  187. *
  188. * @param string $content The current post content.
  189. * @param string $feed_type Type of feed. Possible values include 'rss2', 'atom'.
  190. * Default 'rss2'.
  191. */
  192. return apply_filters( 'the_content_feed', $content, $feed_type );
  193. }
  194. /**
  195. * Displays the post content for feeds.
  196. *
  197. * @since 2.9.0
  198. *
  199. * @param string $feed_type The type of feed. rss2 | atom | rss | rdf
  200. */
  201. function the_content_feed( $feed_type = null ) {
  202. echo get_the_content_feed( $feed_type );
  203. }
  204. /**
  205. * Displays the post excerpt for the feed.
  206. *
  207. * @since 0.71
  208. */
  209. function the_excerpt_rss() {
  210. $output = get_the_excerpt();
  211. /**
  212. * Filters the post excerpt for a feed.
  213. *
  214. * @since 1.2.0
  215. *
  216. * @param string $output The current post excerpt.
  217. */
  218. echo apply_filters( 'the_excerpt_rss', $output );
  219. }
  220. /**
  221. * Displays the permalink to the post for use in feeds.
  222. *
  223. * @since 2.3.0
  224. */
  225. function the_permalink_rss() {
  226. /**
  227. * Filters the permalink to the post for use in feeds.
  228. *
  229. * @since 2.3.0
  230. *
  231. * @param string $post_permalink The current post permalink.
  232. */
  233. echo esc_url( apply_filters( 'the_permalink_rss', get_permalink() ) );
  234. }
  235. /**
  236. * Outputs the link to the comments for the current post in an XML safe way.
  237. *
  238. * @since 3.0.0
  239. */
  240. function comments_link_feed() {
  241. /**
  242. * Filters the comments permalink for the current post.
  243. *
  244. * @since 3.6.0
  245. *
  246. * @param string $comment_permalink The current comment permalink with
  247. * '#comments' appended.
  248. */
  249. echo esc_url( apply_filters( 'comments_link_feed', get_comments_link() ) );
  250. }
  251. /**
  252. * Displays the feed GUID for the current comment.
  253. *
  254. * @since 2.5.0
  255. *
  256. * @param int|WP_Comment $comment_id Optional comment object or ID. Defaults to global comment object.
  257. */
  258. function comment_guid( $comment_id = null ) {
  259. echo esc_url( get_comment_guid( $comment_id ) );
  260. }
  261. /**
  262. * Retrieves the feed GUID for the current comment.
  263. *
  264. * @since 2.5.0
  265. *
  266. * @param int|WP_Comment $comment_id Optional comment object or ID. Defaults to global comment object.
  267. * @return string|false GUID for comment on success, false on failure.
  268. */
  269. function get_comment_guid( $comment_id = null ) {
  270. $comment = get_comment( $comment_id );
  271. if ( ! is_object( $comment ) ) {
  272. return false;
  273. }
  274. return get_the_guid( $comment->comment_post_ID ) . '#comment-' . $comment->comment_ID;
  275. }
  276. /**
  277. * Displays the link to the comments.
  278. *
  279. * @since 1.5.0
  280. * @since 4.4.0 Introduced the `$comment` argument.
  281. *
  282. * @param int|WP_Comment $comment Optional. Comment object or ID. Defaults to global comment object.
  283. */
  284. function comment_link( $comment = null ) {
  285. /**
  286. * Filters the current comment's permalink.
  287. *
  288. * @since 3.6.0
  289. *
  290. * @see get_comment_link()
  291. *
  292. * @param string $comment_permalink The current comment permalink.
  293. */
  294. echo esc_url( apply_filters( 'comment_link', get_comment_link( $comment ) ) );
  295. }
  296. /**
  297. * Retrieves the current comment author for use in the feeds.
  298. *
  299. * @since 2.0.0
  300. *
  301. * @return string Comment Author.
  302. */
  303. function get_comment_author_rss() {
  304. /**
  305. * Filters the current comment author for use in a feed.
  306. *
  307. * @since 1.5.0
  308. *
  309. * @see get_comment_author()
  310. *
  311. * @param string $comment_author The current comment author.
  312. */
  313. return apply_filters( 'comment_author_rss', get_comment_author() );
  314. }
  315. /**
  316. * Displays the current comment author in the feed.
  317. *
  318. * @since 1.0.0
  319. */
  320. function comment_author_rss() {
  321. echo get_comment_author_rss();
  322. }
  323. /**
  324. * Displays the current comment content for use in the feeds.
  325. *
  326. * @since 1.0.0
  327. */
  328. function comment_text_rss() {
  329. $comment_text = get_comment_text();
  330. /**
  331. * Filters the current comment content for use in a feed.
  332. *
  333. * @since 1.5.0
  334. *
  335. * @param string $comment_text The content of the current comment.
  336. */
  337. $comment_text = apply_filters( 'comment_text_rss', $comment_text );
  338. echo $comment_text;
  339. }
  340. /**
  341. * Retrieves all of the post categories, formatted for use in feeds.
  342. *
  343. * All of the categories for the current post in the feed loop, will be
  344. * retrieved and have feed markup added, so that they can easily be added to the
  345. * RSS2, Atom, or RSS1 and RSS0.91 RDF feeds.
  346. *
  347. * @since 2.1.0
  348. *
  349. * @param string $type Optional, default is the type returned by get_default_feed().
  350. * @return string All of the post categories for displaying in the feed.
  351. */
  352. function get_the_category_rss( $type = null ) {
  353. if ( empty( $type ) ) {
  354. $type = get_default_feed();
  355. }
  356. $categories = get_the_category();
  357. $tags = get_the_tags();
  358. $the_list = '';
  359. $cat_names = array();
  360. $filter = 'rss';
  361. if ( 'atom' === $type ) {
  362. $filter = 'raw';
  363. }
  364. if ( ! empty( $categories ) ) {
  365. foreach ( (array) $categories as $category ) {
  366. $cat_names[] = sanitize_term_field( 'name', $category->name, $category->term_id, 'category', $filter );
  367. }
  368. }
  369. if ( ! empty( $tags ) ) {
  370. foreach ( (array) $tags as $tag ) {
  371. $cat_names[] = sanitize_term_field( 'name', $tag->name, $tag->term_id, 'post_tag', $filter );
  372. }
  373. }
  374. $cat_names = array_unique( $cat_names );
  375. foreach ( $cat_names as $cat_name ) {
  376. if ( 'rdf' === $type ) {
  377. $the_list .= "\t\t<dc:subject><![CDATA[$cat_name]]></dc:subject>\n";
  378. } elseif ( 'atom' === $type ) {
  379. $the_list .= sprintf( '<category scheme="%1$s" term="%2$s" />', esc_attr( get_bloginfo_rss( 'url' ) ), esc_attr( $cat_name ) );
  380. } else {
  381. $the_list .= "\t\t<category><![CDATA[" . html_entity_decode( $cat_name, ENT_COMPAT, get_option( 'blog_charset' ) ) . "]]></category>\n";
  382. }
  383. }
  384. /**
  385. * Filters all of the post categories for display in a feed.
  386. *
  387. * @since 1.2.0
  388. *
  389. * @param string $the_list All of the RSS post categories.
  390. * @param string $type Type of feed. Possible values include 'rss2', 'atom'.
  391. * Default 'rss2'.
  392. */
  393. return apply_filters( 'the_category_rss', $the_list, $type );
  394. }
  395. /**
  396. * Displays the post categories in the feed.
  397. *
  398. * @since 0.71
  399. *
  400. * @see get_the_category_rss() For better explanation.
  401. *
  402. * @param string $type Optional, default is the type returned by get_default_feed().
  403. */
  404. function the_category_rss( $type = null ) {
  405. echo get_the_category_rss( $type );
  406. }
  407. /**
  408. * Displays the HTML type based on the blog setting.
  409. *
  410. * The two possible values are either 'xhtml' or 'html'.
  411. *
  412. * @since 2.2.0
  413. */
  414. function html_type_rss() {
  415. $type = get_bloginfo( 'html_type' );
  416. if ( strpos( $type, 'xhtml' ) !== false ) {
  417. $type = 'xhtml';
  418. } else {
  419. $type = 'html';
  420. }
  421. echo $type;
  422. }
  423. /**
  424. * Displays the rss enclosure for the current post.
  425. *
  426. * Uses the global $post to check whether the post requires a password and if
  427. * the user has the password for the post. If not then it will return before
  428. * displaying.
  429. *
  430. * Also uses the function get_post_custom() to get the post's 'enclosure'
  431. * metadata field and parses the value to display the enclosure(s). The
  432. * enclosure(s) consist of enclosure HTML tag(s) with a URI and other
  433. * attributes.
  434. *
  435. * @since 1.5.0
  436. */
  437. function rss_enclosure() {
  438. if ( post_password_required() ) {
  439. return;
  440. }
  441. foreach ( (array) get_post_custom() as $key => $val ) {
  442. if ( 'enclosure' === $key ) {
  443. foreach ( (array) $val as $enc ) {
  444. $enclosure = explode( "\n", $enc );
  445. // Only get the first element, e.g. 'audio/mpeg' from 'audio/mpeg mpga mp2 mp3'.
  446. $t = preg_split( '/[ \t]/', trim( $enclosure[2] ) );
  447. $type = $t[0];
  448. /**
  449. * Filters the RSS enclosure HTML link tag for the current post.
  450. *
  451. * @since 2.2.0
  452. *
  453. * @param string $html_link_tag The HTML link tag with a URI and other attributes.
  454. */
  455. echo apply_filters( 'rss_enclosure', '<enclosure url="' . esc_url( trim( $enclosure[0] ) ) . '" length="' . absint( trim( $enclosure[1] ) ) . '" type="' . esc_attr( $type ) . '" />' . "\n" );
  456. }
  457. }
  458. }
  459. }
  460. /**
  461. * Displays the atom enclosure for the current post.
  462. *
  463. * Uses the global $post to check whether the post requires a password and if
  464. * the user has the password for the post. If not then it will return before
  465. * displaying.
  466. *
  467. * Also uses the function get_post_custom() to get the post's 'enclosure'
  468. * metadata field and parses the value to display the enclosure(s). The
  469. * enclosure(s) consist of link HTML tag(s) with a URI and other attributes.
  470. *
  471. * @since 2.2.0
  472. */
  473. function atom_enclosure() {
  474. if ( post_password_required() ) {
  475. return;
  476. }
  477. foreach ( (array) get_post_custom() as $key => $val ) {
  478. if ( 'enclosure' === $key ) {
  479. foreach ( (array) $val as $enc ) {
  480. $enclosure = explode( "\n", $enc );
  481. $url = '';
  482. $type = '';
  483. $length = 0;
  484. $mimes = get_allowed_mime_types();
  485. // Parse URL.
  486. if ( isset( $enclosure[0] ) && is_string( $enclosure[0] ) ) {
  487. $url = trim( $enclosure[0] );
  488. }
  489. // Parse length and type.
  490. for ( $i = 1; $i <= 2; $i++ ) {
  491. if ( isset( $enclosure[ $i ] ) ) {
  492. if ( is_numeric( $enclosure[ $i ] ) ) {
  493. $length = trim( $enclosure[ $i ] );
  494. } elseif ( in_array( $enclosure[ $i ], $mimes, true ) ) {
  495. $type = trim( $enclosure[ $i ] );
  496. }
  497. }
  498. }
  499. $html_link_tag = sprintf(
  500. "<link href=\"%s\" rel=\"enclosure\" length=\"%d\" type=\"%s\" />\n",
  501. esc_url( $url ),
  502. esc_attr( $length ),
  503. esc_attr( $type )
  504. );
  505. /**
  506. * Filters the atom enclosure HTML link tag for the current post.
  507. *
  508. * @since 2.2.0
  509. *
  510. * @param string $html_link_tag The HTML link tag with a URI and other attributes.
  511. */
  512. echo apply_filters( 'atom_enclosure', $html_link_tag );
  513. }
  514. }
  515. }
  516. }
  517. /**
  518. * Determines the type of a string of data with the data formatted.
  519. *
  520. * Tell whether the type is text, HTML, or XHTML, per RFC 4287 section 3.1.
  521. *
  522. * In the case of WordPress, text is defined as containing no markup,
  523. * XHTML is defined as "well formed", and HTML as tag soup (i.e., the rest).
  524. *
  525. * Container div tags are added to XHTML values, per section 3.1.1.3.
  526. *
  527. * @link http://www.atomenabled.org/developers/syndication/atom-format-spec.php#rfc.section.3.1
  528. *
  529. * @since 2.5.0
  530. *
  531. * @param string $data Input string.
  532. * @return array array(type, value)
  533. */
  534. function prep_atom_text_construct( $data ) {
  535. if ( strpos( $data, '<' ) === false && strpos( $data, '&' ) === false ) {
  536. return array( 'text', $data );
  537. }
  538. if ( ! function_exists( 'xml_parser_create' ) ) {
  539. trigger_error( __( "PHP's XML extension is not available. Please contact your hosting provider to enable PHP's XML extension." ) );
  540. return array( 'html', "<![CDATA[$data]]>" );
  541. }
  542. $parser = xml_parser_create();
  543. xml_parse( $parser, '<div>' . $data . '</div>', true );
  544. $code = xml_get_error_code( $parser );
  545. xml_parser_free( $parser );
  546. unset( $parser );
  547. if ( ! $code ) {
  548. if ( strpos( $data, '<' ) === false ) {
  549. return array( 'text', $data );
  550. } else {
  551. $data = "<div xmlns='http://www.w3.org/1999/xhtml'>$data</div>";
  552. return array( 'xhtml', $data );
  553. }
  554. }
  555. if ( strpos( $data, ']]>' ) === false ) {
  556. return array( 'html', "<![CDATA[$data]]>" );
  557. } else {
  558. return array( 'html', htmlspecialchars( $data ) );
  559. }
  560. }
  561. /**
  562. * Displays Site Icon in atom feeds.
  563. *
  564. * @since 4.3.0
  565. *
  566. * @see get_site_icon_url()
  567. */
  568. function atom_site_icon() {
  569. $url = get_site_icon_url( 32 );
  570. if ( $url ) {
  571. echo '<icon>' . convert_chars( $url ) . "</icon>\n";
  572. }
  573. }
  574. /**
  575. * Displays Site Icon in RSS2.
  576. *
  577. * @since 4.3.0
  578. */
  579. function rss2_site_icon() {
  580. $rss_title = get_wp_title_rss();
  581. if ( empty( $rss_title ) ) {
  582. $rss_title = get_bloginfo_rss( 'name' );
  583. }
  584. $url = get_site_icon_url( 32 );
  585. if ( $url ) {
  586. echo '
  587. <image>
  588. <url>' . convert_chars( $url ) . '</url>
  589. <title>' . $rss_title . '</title>
  590. <link>' . get_bloginfo_rss( 'url' ) . '</link>
  591. <width>32</width>
  592. <height>32</height>
  593. </image> ' . "\n";
  594. }
  595. }
  596. /**
  597. * Returns the link for the currently displayed feed.
  598. *
  599. * @since 5.3.0
  600. *
  601. * @return string Correct link for the atom:self element.
  602. */
  603. function get_self_link() {
  604. $host = parse_url( home_url() );
  605. return set_url_scheme( 'http://' . $host['host'] . wp_unslash( $_SERVER['REQUEST_URI'] ) );
  606. }
  607. /**
  608. * Displays the link for the currently displayed feed in a XSS safe way.
  609. *
  610. * Generate a correct link for the atom:self element.
  611. *
  612. * @since 2.5.0
  613. */
  614. function self_link() {
  615. /**
  616. * Filters the current feed URL.
  617. *
  618. * @since 3.6.0
  619. *
  620. * @see set_url_scheme()
  621. * @see wp_unslash()
  622. *
  623. * @param string $feed_link The link for the feed with set URL scheme.
  624. */
  625. echo esc_url( apply_filters( 'self_link', get_self_link() ) );
  626. }
  627. /**
  628. * Gets the UTC time of the most recently modified post from WP_Query.
  629. *
  630. * If viewing a comment feed, the time of the most recently modified
  631. * comment will be returned.
  632. *
  633. * @global WP_Query $wp_query WordPress Query object.
  634. *
  635. * @since 5.2.0
  636. *
  637. * @param string $format Date format string to return the time in.
  638. * @return string|false The time in requested format, or false on failure.
  639. */
  640. function get_feed_build_date( $format ) {
  641. global $wp_query;
  642. $datetime = false;
  643. $max_modified_time = false;
  644. $utc = new DateTimeZone( 'UTC' );
  645. if ( ! empty( $wp_query ) && $wp_query->have_posts() ) {
  646. // Extract the post modified times from the posts.
  647. $modified_times = wp_list_pluck( $wp_query->posts, 'post_modified_gmt' );
  648. // If this is a comment feed, check those objects too.
  649. if ( $wp_query->is_comment_feed() && $wp_query->comment_count ) {
  650. // Extract the comment modified times from the comments.
  651. $comment_times = wp_list_pluck( $wp_query->comments, 'comment_date_gmt' );
  652. // Add the comment times to the post times for comparison.
  653. $modified_times = array_merge( $modified_times, $comment_times );
  654. }
  655. // Determine the maximum modified time.
  656. $datetime = date_create_immutable_from_format( 'Y-m-d H:i:s', max( $modified_times ), $utc );
  657. }
  658. if ( false === $datetime ) {
  659. // Fall back to last time any post was modified or published.
  660. $datetime = date_create_immutable_from_format( 'Y-m-d H:i:s', get_lastpostmodified( 'GMT' ), $utc );
  661. }
  662. if ( false !== $datetime ) {
  663. $max_modified_time = $datetime->format( $format );
  664. }
  665. /**
  666. * Filters the date the last post or comment in the query was modified.
  667. *
  668. * @since 5.2.0
  669. *
  670. * @param string|false $max_modified_time Date the last post or comment was modified in the query, in UTC.
  671. * False on failure.
  672. * @param string $format The date format requested in get_feed_build_date().
  673. */
  674. return apply_filters( 'get_feed_build_date', $max_modified_time, $format );
  675. }
  676. /**
  677. * Returns the content type for specified feed type.
  678. *
  679. * @since 2.8.0
  680. *
  681. * @param string $type Type of feed. Possible values include 'rss', rss2', 'atom', and 'rdf'.
  682. */
  683. function feed_content_type( $type = '' ) {
  684. if ( empty( $type ) ) {
  685. $type = get_default_feed();
  686. }
  687. $types = array(
  688. 'rss' => 'application/rss+xml',
  689. 'rss2' => 'application/rss+xml',
  690. 'rss-http' => 'text/xml',
  691. 'atom' => 'application/atom+xml',
  692. 'rdf' => 'application/rdf+xml',
  693. );
  694. $content_type = ( ! empty( $types[ $type ] ) ) ? $types[ $type ] : 'application/octet-stream';
  695. /**
  696. * Filters the content type for a specific feed type.
  697. *
  698. * @since 2.8.0
  699. *
  700. * @param string $content_type Content type indicating the type of data that a feed contains.
  701. * @param string $type Type of feed. Possible values include 'rss', rss2', 'atom', and 'rdf'.
  702. */
  703. return apply_filters( 'feed_content_type', $content_type, $type );
  704. }
  705. /**
  706. * Builds SimplePie object based on RSS or Atom feed from URL.
  707. *
  708. * @since 2.8.0
  709. *
  710. * @param string|string[] $url URL of feed to retrieve. If an array of URLs, the feeds are merged
  711. * using SimplePie's multifeed feature.
  712. * See also {@link http://simplepie.org/wiki/faq/typical_multifeed_gotchas}
  713. * @return SimplePie|WP_Error SimplePie object on success or WP_Error object on failure.
  714. */
  715. function fetch_feed( $url ) {
  716. if ( ! class_exists( 'SimplePie', false ) ) {
  717. require_once ABSPATH . WPINC . '/class-simplepie.php';
  718. }
  719. require_once ABSPATH . WPINC . '/class-wp-feed-cache-transient.php';
  720. require_once ABSPATH . WPINC . '/class-wp-simplepie-file.php';
  721. require_once ABSPATH . WPINC . '/class-wp-simplepie-sanitize-kses.php';
  722. $feed = new SimplePie();
  723. $feed->set_sanitize_class( 'WP_SimplePie_Sanitize_KSES' );
  724. // We must manually overwrite $feed->sanitize because SimplePie's constructor
  725. // sets it before we have a chance to set the sanitization class.
  726. $feed->sanitize = new WP_SimplePie_Sanitize_KSES();
  727. // Register the cache handler using the recommended method for SimplePie 1.3 or later.
  728. if ( method_exists( 'SimplePie_Cache', 'register' ) ) {
  729. SimplePie_Cache::register( 'wp_transient', 'WP_Feed_Cache_Transient' );
  730. $feed->set_cache_location( 'wp_transient' );
  731. } else {
  732. // Back-compat for SimplePie 1.2.x.
  733. require_once ABSPATH . WPINC . '/class-wp-feed-cache.php';
  734. $feed->set_cache_class( 'WP_Feed_Cache' );
  735. }
  736. $feed->set_file_class( 'WP_SimplePie_File' );
  737. $feed->set_feed_url( $url );
  738. /** This filter is documented in wp-includes/class-wp-feed-cache-transient.php */
  739. $feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 12 * HOUR_IN_SECONDS, $url ) );
  740. /**
  741. * Fires just before processing the SimplePie feed object.
  742. *
  743. * @since 3.0.0
  744. *
  745. * @param SimplePie $feed SimplePie feed object (passed by reference).
  746. * @param string|string[] $url URL of feed or array of URLs of feeds to retrieve.
  747. */
  748. do_action_ref_array( 'wp_feed_options', array( &$feed, $url ) );
  749. $feed->init();
  750. $feed->set_output_encoding( get_option( 'blog_charset' ) );
  751. if ( $feed->error() ) {
  752. return new WP_Error( 'simplepie-error', $feed->error() );
  753. }
  754. return $feed;
  755. }