-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Handle multiple feeds in fetch_feed().
#10631
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Handle multiple feeds in fetch_feed().
#10631
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
b73e186 to
3ec52a0
Compare
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
3ec52a0 to
1e227d2
Compare
|
If we simply want to handle multiple feeds, the following approach might work: diff --git a/src/wp-includes/feed.php b/src/wp-includes/feed.php
index 421bee7173..74f0ee77cd 100644
--- a/src/wp-includes/feed.php
+++ b/src/wp-includes/feed.php
@@ -832,7 +832,20 @@ function fetch_feed( $url ) {
$feed->get_registry()->register( SimplePie\File::class, 'WP_SimplePie_File', true );
- $feed->set_feed_url( $url );
+ if ( is_array( $url ) && count( $url ) > 1 ) {
+ $feed->multifeed_url = array();
+ foreach ( $url as $value ) {
+ $feed->multifeed_url[] = $feed->get_registry()->call(
+ SimplePie\Misc::class, 'fix_protocol',
+ array( $value, 1 )
+ );
+ }
+ } elseif ( is_array( $url ) && count( $url ) === 1 ) {
+ $feed->set_feed_url( array_shift( $url ) );
+ } else {
+ $feed->set_feed_url( $url );
+ }
+
/** This filter is documented in wp-includes/class-wp-feed-cache-transien
t.php */
$feed->set_cache_duration( apply_filters( 'wp_feed_cache_transient_lifeti
me', 12 * HOUR_IN_SECONDS, $url ) );However, this may not be the recommended approach. |
|
@t-hamano The hook will still run for each individual URL but it won't run with all of them. IE I'll take a look at your suggested diff. I don't know their plans but I wouldn't be surprised if |
3300255 to
c8f11ac
Compare
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | ||
|
|
||
| if ( $simplepie_instance->error() ) { | ||
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an error occurs in one feed, the other feeds will be ignored. As I understand it, SimplePie will continue processing even if an error occurs and merge successful feeds:
I haven't tested it, but how about an approach like this:
foreach ( (array) $url as $feed_url ) {
$simplepie_instance = clone $feed;
$simplepie_instance->set_feed_url( $feed_url );
$simplepie_instance->init();
$simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) );
if ( ! $simplepie_instance->error() ) {
$feeds[] = $simplepie_instance;
} else {
$errors[] = $simplepie_instance->error();
}
}
if ( empty( $feeds ) ) {
return new WP_Error( 'simplepie-error', $errors );
}We may also need unit tests to ensure that if an error occurs, the rest of the feed still gets processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the 6.8 behavior for when a feed encounters an error? We should try to replicate that
|
@swissspidy @TobiasBg This is my first time looking at SimplePie code, so I may have missed something, and I'd appreciate any feedback if there is anything. |
Sorry, I'm not familiar with this specific part either :-( |
|
Same here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for passing an array of feed URLs to fetch_feed() to handle multiple feeds. When multiple URLs are provided, the function fetches each feed individually, merges their items using SimplePie's merge_items() method, and returns a single SimplePie object with the combined items.
Changes:
- Modified
fetch_feed()to detect and handle array URLs by fetching each feed individually and merging items - Moved
set_feed_url()call to after array processing to avoid setting URL prematurely - Added test coverage to verify multiple feeds are supported without triggering deprecation warnings
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/wp-includes/feed.php | Implements multi-feed support by cloning SimplePie instance for each URL, fetching items, and merging them via SimplePie::merge_items() |
| tests/phpunit/tests/feed/fetchFeed.php | Adds test to verify fetch_feed() accepts array of URLs and doesn't trigger SimplePie deprecation warning |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $url = array_shift( $url ); | ||
| } elseif ( is_array( $url ) ) { | ||
| $feeds = array(); | ||
| $simplepie_instance = clone $feed; |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code doesn't preserve or apply the feed options (cache duration, sanitization settings, etc.) from the original $feed object to the cloned $simplepie_instance that processes individual feeds. This means each individual feed in the array might not respect the options set via wp_feed_options action or other configuration methods.
The cloned instance should inherit all the configuration from the parent feed object to ensure consistent behavior.
| if ( $simplepie_instance->error() ) { | ||
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When one feed in the array fails to fetch or has an error, the function returns immediately with a WP_Error, abandoning the processing of remaining feeds. This means if you request 5 feeds and the 2nd one fails, you won't get any data from feeds 1, 3, 4, or 5.
Consider whether this is the desired behavior or if it would be better to continue processing remaining feeds and either aggregate errors or return partial results with a warning about failed feeds.
| $simplepie_instance = clone $feed; | ||
| foreach ( (array) $url as $feed_url ) { | ||
| $simplepie_instance->set_feed_url( $feed_url ); | ||
| $simplepie_instance->init(); | ||
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | ||
|
|
||
| if ( $simplepie_instance->error() ) { | ||
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | ||
| } | ||
|
|
||
| $feeds[] = $simplepie_instance; | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same $simplepie_instance object is being reused across multiple iterations without proper reinitialization. This means that after the first feed is processed, the instance retains state from the previous feed which could lead to incorrect data or unexpected behavior. Each feed URL should either get a fresh SimplePie instance, or the instance should be properly reset between iterations.
Consider creating a new SimplePie instance for each feed URL in the loop instead of cloning once and reusing.
| } elseif ( is_array( $url ) ) { | ||
| $feeds = array(); | ||
| $simplepie_instance = clone $feed; | ||
| foreach ( (array) $url as $feed_url ) { | ||
| $simplepie_instance->set_feed_url( $feed_url ); | ||
| $simplepie_instance->init(); | ||
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | ||
|
|
||
| if ( $simplepie_instance->error() ) { | ||
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | ||
| } | ||
|
|
||
| $feeds[] = $simplepie_instance; | ||
| } | ||
| $items = SimplePie\SimplePie::merge_items( $feeds ); | ||
| $feed->init(); | ||
| $feed->data['items'] = $items; | ||
| return $feed; |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Directly assigning to $feed->data['items'] bypasses SimplePie's internal state management and could cause issues with other SimplePie methods that depend on proper initialization. The SimplePie object wasn't initialized with init() before this assignment, and calling init() on line 865 after data assignment may not work as expected since the object wasn't properly set up with a feed URL.
Instead of manually manipulating the data property, consider using SimplePie's merge_items() method to create a properly merged feed or ensure the feed object is properly initialized with the merged data.
| } elseif ( is_array( $url ) ) { | |
| $feeds = array(); | |
| $simplepie_instance = clone $feed; | |
| foreach ( (array) $url as $feed_url ) { | |
| $simplepie_instance->set_feed_url( $feed_url ); | |
| $simplepie_instance->init(); | |
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | |
| if ( $simplepie_instance->error() ) { | |
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | |
| } | |
| $feeds[] = $simplepie_instance; | |
| } | |
| $items = SimplePie\SimplePie::merge_items( $feeds ); | |
| $feed->init(); | |
| $feed->data['items'] = $items; | |
| return $feed; |
| if ( is_array( $url ) && count( $url ) <= 1 ) { | ||
| $url = array_shift( $url ); | ||
| } elseif ( is_array( $url ) ) { | ||
| $feeds = array(); | ||
| $simplepie_instance = clone $feed; | ||
| foreach ( (array) $url as $feed_url ) { | ||
| $simplepie_instance->set_feed_url( $feed_url ); | ||
| $simplepie_instance->init(); | ||
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | ||
|
|
||
| if ( $simplepie_instance->error() ) { | ||
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | ||
| } | ||
|
|
||
| $feeds[] = $simplepie_instance; | ||
| } | ||
| $items = SimplePie\SimplePie::merge_items( $feeds ); | ||
| $feed->init(); | ||
| $feed->data['items'] = $items; | ||
| return $feed; |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition count( $url ) <= 1 will cause arrays with exactly one URL to be processed differently (extracting the single URL), but arrays with 0 URLs will fall through to the multi-feed handling logic. This could lead to attempting to process an empty array of feeds, which may not be the intended behavior.
Consider also checking for empty arrays and handling them appropriately, or adding validation earlier in the function to ensure the URL parameter is valid.
| if ( is_array( $url ) && count( $url ) <= 1 ) { | |
| $url = array_shift( $url ); | |
| } elseif ( is_array( $url ) ) { | |
| $feeds = array(); | |
| $simplepie_instance = clone $feed; | |
| foreach ( (array) $url as $feed_url ) { | |
| $simplepie_instance->set_feed_url( $feed_url ); | |
| $simplepie_instance->init(); | |
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | |
| if ( $simplepie_instance->error() ) { | |
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | |
| } | |
| $feeds[] = $simplepie_instance; | |
| } | |
| $items = SimplePie\SimplePie::merge_items( $feeds ); | |
| $feed->init(); | |
| $feed->data['items'] = $items; | |
| return $feed; | |
| if ( is_array( $url ) ) { | |
| if ( 1 === count( $url ) ) { | |
| $url = array_shift( $url ); | |
| } elseif ( 0 === count( $url ) ) { | |
| return new WP_Error( 'simplepie-error', 'Feed URL array must not be empty.' ); | |
| } else { | |
| $feeds = array(); | |
| $simplepie_instance = clone $feed; | |
| foreach ( (array) $url as $feed_url ) { | |
| $simplepie_instance->set_feed_url( $feed_url ); | |
| $simplepie_instance->init(); | |
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | |
| if ( $simplepie_instance->error() ) { | |
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); | |
| } | |
| $feeds[] = $simplepie_instance; | |
| } | |
| $items = SimplePie\SimplePie::merge_items( $feeds ); | |
| $feed->init(); | |
| $feed->data['items'] = $items; | |
| return $feed; | |
| } |
| * @param SimplePie\SimplePie $feed SimplePie feed object (passed by reference). | ||
| * @param string|string[] $url URL of feed or array of URLs of feeds to retrieve. | ||
| */ | ||
| do_action_ref_array( 'wp_feed_options', array( &$feed, $url ) ); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wp_feed_options action is called with the original $feed object and the $url parameter before the URL array is processed. However, for multiple feeds, this hook is only called once with the array of URLs, not once per feed. This means plugins or themes that hook into this to configure feed options won't have a chance to configure each individual feed instance that's created in the loop.
Consider whether the hook should be called for each individual feed URL when processing multiple feeds, or clearly document this behavior change.
| $feed = fetch_feed( array( 'https://wordpress.org/news/feed/', 'https://wordpress.org/news/feed/' ) ); | ||
| $content = array(); | ||
|
|
||
| foreach ( $feed->get_items( 0, 2 ) as $item ) { | ||
| $content[] = $item->get_content(); | ||
| } | ||
|
|
||
| $this->assertEqualHTML( $content[0], $content[1], null, 'The contents of the first two items should be identical.' ); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test validates that the content of the first two items is identical by passing the same feed URL twice. However, this doesn't verify that both feed URLs were actually processed independently. If there's a bug in the implementation that only processes the first feed and ignores the rest, this test would still pass.
Consider using two different feed URLs (or mock responses) to verify that items from both feeds are actually included in the merged result, and that the feeds are processed independently.
| } elseif ( is_array( $url ) ) { | ||
| $feeds = array(); | ||
| $simplepie_instance = clone $feed; | ||
| foreach ( (array) $url as $feed_url ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: At this point, this is already known to be an array.
| foreach ( (array) $url as $feed_url ) { | |
| foreach ( $url as $feed_url ) { |
| * @ticket 64136 | ||
| */ | ||
| public function test_fetch_feed_supports_multiple_feeds() { | ||
| $feed = fetch_feed( array( 'https://wordpress.org/news/feed/', 'https://wordpress.org/news/feed/' ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second call should be returned cached. I think it might make sense to use a different url (maybe just the atom url?) since this response is mocked anyways.
| $simplepie_instance->set_output_encoding( get_bloginfo( 'charset' ) ); | ||
|
|
||
| if ( $simplepie_instance->error() ) { | ||
| return new WP_Error( 'simplepie-error', $simplepie_instance->error() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the 6.8 behavior for when a feed encounters an error? We should try to replicate that
Handles passing an array to
fetch_feed()by requesting each feed individually and then callingSimplePie\SimplePie::merge_items();on the result before passing the data back in to a SimplePie object.I don't love this so would love some suggestions of anything I am missing.
Trac ticket: https://core.trac.wordpress.org/ticket/64136
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.