Make WordPress Core

Changeset 61553


Ignore:
Timestamp:
01/29/2026 01:05:19 AM (9 days ago)
Author:
peterwilsoncc
Message:

Feeds: Fix backward compatibility of fetch_feed().

In simplepie/simplepie#795 handling of multiple feed requests was deprecated, triggering the message Fetching multiple feeds with single SimplePie instance is deprecated since SimplePie 1.9.0, create one SimplePie instance per feed and use SimplePie::merge_items to get a single list of items.

This updates fetch_feed() to handle multiple requests using seperate SimplePie instances in order to retain backward compatibility.

A PHP 8.5 deprecation was throwing notices in the event an empty URL was passed to fetch_feed(), Using null as an array offset is deprecated, use an empty string instead.

This includes a workaround pending the release of a SimplePie version including simplepie/simplepie#949.

Reviewed by jorbin.
Merges [61551] to the 6.9 branch.

Fixes #64136.
Props audrasjb, jorbin, muryam, oglekler, ozgursar, presskopp, swissspidy, westonruter, wildworks, peterwilsoncc.

Location:
branches/6.9
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/6.9

  • branches/6.9/src/wp-includes/feed.php

    r60771 r61553  
    833833    $feed->get_registry()->register( SimplePie\File::class, 'WP_SimplePie_File', true );
    834834
    835     $feed->set_feed_url( $url );
    836835    /** This filter is documented in wp-includes/class-wp-feed-cache-transient.php */
    837836    $feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifetime', 12 * HOUR_IN_SECONDS, $url ) );
     
    847846    do_action_ref_array( 'wp_feed_options', array( &$feed, $url ) );
    848847
     848    if ( empty( $url ) ) {
     849        /*
     850         * @todo: Set $url to empty string once supported by SimplePie.
     851         *
     852         * The early return without proceeding is to work around a PHP 8.5
     853         * deprecation issue resolved in https://github.com/simplepie/simplepie/pull/949
     854         *
     855         * To avoid the duplicate code, this block can be replaced with `$url = '';` once SimplePie
     856         * is upgraded to a version that includes the fix.
     857         */
     858        $feed->init();
     859        $feed->set_output_encoding( get_bloginfo( 'charset' ) );
     860
     861        if ( $feed->error() ) {
     862            return new WP_Error( 'simplepie-error', $feed->error() );
     863        }
     864
     865        return $feed;
     866    } elseif ( is_array( $url ) && count( $url ) === 1 ) {
     867        $url = array_shift( $url );
     868    } elseif ( is_array( $url ) ) {
     869        $feeds            = array();
     870        $simplepie_errors = array();
     871        foreach ( $url as $feed_url ) {
     872            $simplepie_instance = clone $feed;
     873            $simplepie_instance->set_feed_url( $feed_url );
     874            $simplepie_instance->init();
     875            $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) );
     876
     877            if ( $simplepie_instance->error() ) {
     878                $simplepie_errors[] = sprintf(
     879                    /* translators: %1$s is the feed URL, %2$s is the error message. */
     880                    __( 'Error fetching feed %1$s: %2$s' ),
     881                    esc_url( $feed_url ),
     882                    $simplepie_instance->error()
     883                );
     884                unset( $simplepie_instance );
     885                continue;
     886            }
     887
     888            $feeds[] = $simplepie_instance;
     889            unset( $simplepie_instance );
     890        }
     891
     892        if ( ! empty( $simplepie_errors ) ) {
     893            return new WP_Error( 'simplepie-error', $simplepie_errors );
     894        }
     895
     896        $feed->init();
     897        $feed->data['items'] = SimplePie\SimplePie::merge_items( $feeds );
     898        return $feed;
     899    }
     900
     901    $feed->set_feed_url( $url );
    849902    $feed->init();
    850903    $feed->set_output_encoding( get_bloginfo( 'charset' ) );
  • branches/6.9/tests/phpunit/tests/feed/fetchFeed.php

    r60524 r61553  
    3535
    3636    /**
     37     * Ensure WP_Error object returned for 404 response.
     38     *
     39     * @ticket 64136
     40     */
     41    public function test_fetch_feed_returns_error_for_404_response() {
     42        // Priority 15 to ensure this runs after the mocked_rss_response filter.
     43        add_filter( 'pre_http_request', array( $this, 'mocked_rss_404_error_response' ), 15 );
     44
     45        $feed = fetch_feed( 'https://example.org/news/feed/' );
     46
     47        $this->assertWPError( $feed, 'A WP_Error object is expected for failing requests.' );
     48        $this->assertSame( 'simplepie-error', $feed->get_error_code() );
     49    }
     50
     51    /**
     52     * Ensure fetch_feed() returns WP_Error if any feed errors.
     53     *
     54     * @ticket 64136
     55     */
     56    public function test_fetch_feed_multiple_returns_error_if_any_feed_errors() {
     57        // Priority 15 to ensure this runs after the mocked_rss_response filter.
     58        add_filter( 'pre_http_request', array( $this, 'mocked_rss_404_error_response' ), 15 );
     59        add_filter(
     60            'pre_http_request',
     61            /**
     62             * Remove the 404 error response after the first call.
     63             */
     64            function ( $response ) {
     65                remove_filter( 'pre_http_request', array( $this, 'mocked_rss_404_error_response' ), 15 );
     66
     67                return $response;
     68            },
     69            20 // Priority 20 to ensure it runs after the 404 error response.
     70        );
     71
     72        $feed = fetch_feed( array( 'https://example.org/news/feed/', 'https://wordpress.org/news/feed/' ) );
     73
     74        $this->assertWPError( $feed, 'A WP_Error object is expected for any failing requests.' );
     75        $this->assertSame( 'simplepie-error', $feed->get_error_code() );
     76        $this->assertCount( 1, $feed->get_error_messages()[0], 'There should be one error message for the failed feed.' );
     77    }
     78
     79    /**
     80     * Ensure fetch_feed() includes messages for all feeds that error.
     81     *
     82     * @ticket 64136
     83     */
     84    public function test_fetch_feed_multiple_returns_error_if_all_feeds_error() {
     85        // Priority 15 to ensure this runs after the mocked_rss_response filter.
     86        add_filter( 'pre_http_request', array( $this, 'mocked_rss_404_error_response' ), 15 );
     87        $feed = fetch_feed( array( 'https://example.org/news/feed/', 'https://example.com/news/feed/' ) );
     88
     89        $this->assertWPError( $feed, 'A WP_Error object is expected for failing requests.' );
     90        $this->assertSame( 'simplepie-error', $feed->get_error_code() );
     91        $this->assertCount( 2, $feed->get_error_messages()[0], 'There should be two error messages, one for each failed feed.' );
     92    }
     93
     94    /**
     95     * Ensure fetch_feed() returns a SimplePie object for an empty URL (string).
     96     *
     97     * @ticket 64136
     98     */
     99    public function test_fetch_feed_returns_a_simplepie_object_for_unspecified_url_string() {
     100        $feed = fetch_feed( '' );
     101
     102        $this->assertInstanceOf( 'SimplePie\\SimplePie', $feed );
     103    }
     104
     105    /**
     106     * Ensure fetch_feed() returns a SimplePie object for an empty URL (array).
     107     *
     108     * @ticket 64136
     109     */
     110    public function test_fetch_feed_returns_a_simplepie_object_for_unspecified_url_array() {
     111        $feed = fetch_feed( array() );
     112
     113        $this->assertInstanceOf( 'SimplePie\\SimplePie', $feed );
     114    }
     115
     116    /**
     117     * Ensure fetch_feed() accepts multiple feeds.
     118     *
     119     * The main purpose of this test is to ensure that the SimplePie deprecation warning
     120     * is not thrown when requesting multiple feeds.
     121     *
     122     * Secondly it confirms that the markup of the first two items match as they will
     123     * both be from the same feed URL as the array contains the WordPress News feed twice.
     124     *
     125     * @ticket 64136
     126     */
     127    public function test_fetch_feed_supports_multiple_feeds() {
     128        $feed    = fetch_feed( array( 'https://wordpress.org/news/feed/', 'https://wordpress.org/news/feed/atom/' ) );
     129        $content = array();
     130
     131        foreach ( $feed->get_items( 0, 2 ) as $item ) {
     132            $content[] = $item->get_content();
     133        }
     134
     135        $this->assertEqualHTML( $content[0], $content[1], null, 'The contents of the first two items should be identical.' );
     136        $this->assertCount( 20, $feed->get_items(), 'The feed should contain 20 items.' );
     137    }
     138
     139    /**
    37140     * Ensure that fetch_feed() is cached on second and subsequent calls.
    38141     *
     
    112215        );
    113216    }
     217
     218    /**
     219     * Mock 404 error response for `fetch_feed()`.
     220     *
     221     * This simulates a 404 response to test error handling in `fetch_feed()`.
     222     *
     223     * @return array Mocked 404 error response data.
     224     */
     225    public function mocked_rss_404_error_response() {
     226        return array(
     227            'headers'  => new WpOrg\Requests\Utility\CaseInsensitiveDictionary(),
     228            'body'     => '',
     229            'response' => array(
     230                'code'    => 404,
     231                'message' => 'Not Found',
     232            ),
     233            'cookies'  => array(),
     234            'filename' => null,
     235        );
     236    }
    114237}
Note: See TracChangeset for help on using the changeset viewer.