From ad4282898b4271f0856e2c3554a1bdf3c9becf4d Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:44:15 +0300 Subject: [PATCH 1/2] fix: prevent default fallback image from entering the import pipeline Import jobs identified by __jobID no longer receive the display fallback image (bundled SVG) from feedzy_retrieve_image(). This stops Media Library upload errors for feedzy.svg on WP-CLI-based cron runners and duplicate re-uploads of a configured fallback attachment. Imports rely on the default thumbnail setting via set_post_thumbnail() instead. Fixes #1277 Co-Authored-By: Claude Fable 5 --- .../feedzy-rss-feeds-admin-abstract.php | 3 +- includes/admin/feedzy-rss-feeds-import.php | 2 +- tests/test-admin-abstract.php | 84 +++++++++++++++++- tests/test-image-import.php | 86 +++++++++++++++++++ 4 files changed, 172 insertions(+), 3 deletions(-) diff --git a/includes/abstract/feedzy-rss-feeds-admin-abstract.php b/includes/abstract/feedzy-rss-feeds-admin-abstract.php index 1a04b0704..99983e5bc 100644 --- a/includes/abstract/feedzy-rss-feeds-admin-abstract.php +++ b/includes/abstract/feedzy-rss-feeds-admin-abstract.php @@ -2035,7 +2035,8 @@ public function feedzy_retrieve_image( $item, $sc = null ) { return $the_thumbnail; } - if ( ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && ! empty( $sc['feeds'] ) ) { + // Import jobs (__jobID set) skip the display fallback image: it cannot be sideloaded (SVG); imports use the default_thumbnail_id setting instead. + if ( ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && ! empty( $sc['feeds'] ) && empty( $sc['__jobID'] ) ) { $feed_url = $this->normalize_urls( $sc['feeds'] ); $the_thumbnail = ! empty( $the_thumbnail ) ? $the_thumbnail : apply_filters( 'feedzy_default_image', $sc['default'], $feed_url ); } diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index 0827bc946..9624651da 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -2711,7 +2711,7 @@ function ( $term ) { $img_success = $this->try_save_featured_image( $image_source_url, $new_post_id, $img_title, $import_info ); Feedzy_Rss_Feeds_Log::debug( - sprintf( 'Saved featured image for post ID %1$s: %2$s', $new_post_id, $image_source_url ), + sprintf( 'Tried to save featured image for post ID %1$s: %2$s. Success: %3$s', $new_post_id, $image_source_url, $img_success ? 'yes' : 'no' ), array( 'job_id' => $job->ID, 'image_source_url' => $image_source_url, diff --git a/tests/test-admin-abstract.php b/tests/test-admin-abstract.php index deb12919a..bc7d5f9e0 100644 --- a/tests/test-admin-abstract.php +++ b/tests/test-admin-abstract.php @@ -624,4 +624,86 @@ public function test_feedzy_image_encode_edge_cases() { $this->assertEquals($expected, $result, "Failed for input: $input"); } } -} \ No newline at end of file + + /** + * Build a SimplePie item that has no image anywhere (enclosure, media, content, description). + */ + private function get_imageless_item() { + $feed = new SimplePie(); + $feed->set_raw_data(' + + + Test Feed + + Item without image + https://example.com/article + Plain text, no image tag. + + + '); + $feed->init(); + $items = $feed->get_items(); + return $items[0]; + } + + /** + * During an import job the bundled default image (feedzy.svg) must NOT be used as + * the item image: WordPress rejects SVG uploads, causing Media Library errors. + * See https://github.com/Codeinwp/feedzy-rss-feeds/issues/1277. + */ + public function test_feedzy_retrieve_image_import_job_skips_default_image() { + $sc = array( + 'feeds' => 'https://example.com/feed', + 'default' => '', + '__jobID' => 42, + ); + + $result = $this->feedzy_abstract->feedzy_retrieve_image( $this->get_imageless_item(), $sc ); + + $this->assertSame( '', $result, 'Import jobs must not fall back to the bundled default image' ); + } + + /** + * Display contexts (shortcode/block rendering, no __jobID) keep the default image + * fallback, where the bundled SVG is fine inside an tag. + */ + public function test_feedzy_retrieve_image_display_keeps_default_image() { + $sc = array( + 'feeds' => 'https://example.com/feed', + 'default' => '', + ); + + $result = $this->feedzy_abstract->feedzy_retrieve_image( $this->get_imageless_item(), $sc ); + + $this->assertSame( FEEDZY_ABSURL . 'img/feedzy.svg', $result, 'Display contexts must keep the default image fallback' ); + } + + /** + * The import-job guard must only suppress the fallback, never a real item image. + */ + public function test_feedzy_retrieve_image_import_job_keeps_real_item_image() { + $feed = new SimplePie(); + $feed->set_raw_data(' + + + Test Feed + + Item with image + + + + '); + $feed->init(); + $items = $feed->get_items(); + + $sc = array( + 'feeds' => 'https://example.com/feed', + 'default' => '', + '__jobID' => 42, + ); + + $result = $this->feedzy_abstract->feedzy_retrieve_image( $items[0], $sc ); + + $this->assertSame( 'https://example.com/real-image.jpg', $result, 'Import jobs must still use the real item image' ); + } +} diff --git a/tests/test-image-import.php b/tests/test-image-import.php index ecfea9c52..b46df5bc4 100644 --- a/tests/test-image-import.php +++ b/tests/test-image-import.php @@ -10,6 +10,92 @@ */ class Test_Image_Import extends WP_UnitTestCase { + /** + * End-to-end import of a feed whose items have no images: the bundled SVG default + * must not be sideloaded, and the configured global fallback thumbnail must be used. + * See https://github.com/Codeinwp/feedzy-rss-feeds/issues/1277. + */ + public function test_import_without_item_images_uses_fallback_thumbnail() { + // Serve a two-item, image-less feed for any HTTP request, recording requested URLs. + $requested_urls = array(); + add_filter( + 'pre_http_request', + function ( $preempt, $args, $url ) use ( &$requested_urls ) { + $requested_urls[] = $url; + return array( + 'headers' => array( 'content-type' => 'application/rss+xml' ), + 'body' => ' + No Image Feedhttp://feedzy.testt + Imageless onehttp://feedzy.test/1Plain text.fz-1 + Imageless twohttp://feedzy.test/2Also plain.fz-2 + ', + 'response' => array( 'code' => 200, 'message' => 'OK' ), + 'cookies' => array(), + 'filename' => null, + ); + }, + 10, + 3 + ); + + // Configure a global fallback thumbnail. + $fallback_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/2004-07-22-DSC_0007.jpg' ); + $settings = get_option( 'feedzy-settings', array() ); + $settings['general']['default-thumbnail-id'] = $fallback_id; + update_option( 'feedzy-settings', $settings ); + + // Create the import job. + $job_id = wp_insert_post( + array( + 'post_title' => 'No image import', + 'post_type' => 'feedzy_imports', + 'post_status' => 'publish', + ) + ); + foreach ( array( + 'source' => 'http://feedzy.test/feed.xml', + 'import_post_title' => '[#item_title]', + 'import_post_date' => '[#item_date]', + 'import_post_content' => '[#item_content]', + 'import_post_featured_img' => '[#item_image]', + 'import_post_type' => 'post', + 'import_post_status' => 'publish', + 'import_use_external_image' => 'no', + ) as $key => $value ) { + update_post_meta( $job_id, $key, $value ); + } + + $attachments_before = count( get_posts( array( 'post_type' => 'attachment', 'numberposts' => -1 ) ) ); + + // Fresh instance so free_settings picks up the option; run outside wp-cron/AJAX, + // like WP-CLI based cron runners (the path that leaked the SVG). + $import = new Feedzy_Rss_Feeds_Import( 'feedzy-rss-feeds', '1.2.0' ); + $import->run_cron( 100, $job_id ); + + $created = get_posts( array( 'post_type' => 'post', 'post_status' => 'publish', 'numberposts' => -1 ) ); + $this->assertCount( 2, $created, 'Both feed items should be imported' ); + + foreach ( $created as $post ) { + $this->assertEquals( $fallback_id, get_post_thumbnail_id( $post->ID ), 'Imported post must use the fallback thumbnail' ); + } + + // No sideload happened: the bundled SVG (or anything else) was not uploaded. + $attachments_after = count( get_posts( array( 'post_type' => 'attachment', 'numberposts' => -1 ) ) ); + $this->assertEquals( $attachments_before, $attachments_after, 'Import must not upload any image to the Media Library' ); + + $this->assertEmpty( get_post_meta( $job_id, 'import_errors', true ), 'Import must finish without errors' ); + + // No fallback image (bundled SVG or configured attachment) may be fetched over + // HTTP during the import: the only allowed request is the feed itself. + $non_feed_requests = array_filter( + $requested_urls, + function ( $url ) { + return 0 !== strpos( $url, 'http://feedzy.test/' ); + } + ); + $this->assertEmpty( $non_feed_requests, 'Import must not download any image for imageless items: ' . implode( ', ', $non_feed_requests ) ); + } + /** * Test that the image import allows valid image URLs and logs errors for invalid ones. * Test introduced to cover this issue https://github.com/Codeinwp/feedzy-rss-feeds/issues/917. From 02f62991cd03395c44d64324bbfbd39370c5799d Mon Sep 17 00:00:00 2001 From: Alexia Soare <108459992+Alexia-Soare@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:51:53 +0300 Subject: [PATCH 2/2] test: only flag fallback-image fetches in import network assertion CI environments make unrelated background HTTP requests during the test run; restrict the assertion to the traffic the bug produces (bundled feedzy.svg or the site's own fallback attachment). Co-Authored-By: Claude Fable 5 --- tests/test-image-import.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test-image-import.php b/tests/test-image-import.php index b46df5bc4..d8c77f31a 100644 --- a/tests/test-image-import.php +++ b/tests/test-image-import.php @@ -85,15 +85,15 @@ function ( $preempt, $args, $url ) use ( &$requested_urls ) { $this->assertEmpty( get_post_meta( $job_id, 'import_errors', true ), 'Import must finish without errors' ); - // No fallback image (bundled SVG or configured attachment) may be fetched over - // HTTP during the import: the only allowed request is the feed itself. - $non_feed_requests = array_filter( + // No fallback image may be fetched over HTTP during the import: neither the + // bundled SVG nor the configured fallback attachment (hosted on this site). + $fallback_requests = array_filter( $requested_urls, function ( $url ) { - return 0 !== strpos( $url, 'http://feedzy.test/' ); + return false !== strpos( $url, 'feedzy.svg' ) || 0 === strpos( $url, home_url() ); } ); - $this->assertEmpty( $non_feed_requests, 'Import must not download any image for imageless items: ' . implode( ', ', $non_feed_requests ) ); + $this->assertEmpty( $fallback_requests, 'Import must not download any fallback image for imageless items: ' . implode( ', ', $fallback_requests ) ); } /**