Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion includes/abstract/feedzy-rss-feeds-admin-abstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
Expand Down
2 changes: 1 addition & 1 deletion includes/admin/feedzy-rss-feeds-import.php
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@
// Added this to activate post if publish is clicked and sometimes it does not change status.
if (
$source_is_valid && isset( $_POST['custom_post_status'] ) &&
'Publish' === sanitize_text_field( $_POST['custom_post_status'] )

Check warning on line 759 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.Security.ValidatedSanitizedInput.MissingUnslash

$_POST['custom_post_status'] not unslashed before sanitization. Use wp_unslash() or similar
) {
$activate = array(
'ID' => $post_id,
Expand Down Expand Up @@ -1369,7 +1369,7 @@
private function get_taxonomies() {
check_ajax_referer( FEEDZY_BASEFILE, 'security' );

$post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : '';

Check warning on line 1372 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.Security.ValidatedSanitizedInput.MissingUnslash

$_POST['post_type'] not unslashed before sanitization. Use wp_unslash() or similar
$taxonomies = get_object_taxonomies(
array(
'post_type' => $post_type,
Expand Down Expand Up @@ -1465,7 +1465,7 @@
private function dry_run() {
check_ajax_referer( FEEDZY_BASEFILE, 'security' );

$fields = urldecode( isset( $_POST['fields'] ) ? sanitize_url( $_POST['fields'] ) : '' );

Check warning on line 1468 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.Security.ValidatedSanitizedInput.MissingUnslash

$_POST['fields'] not unslashed before sanitization. Use wp_unslash() or similar
parse_str( $fields, $data );

$feedzy_meta_data = $data['feedzy_meta_data'];
Expand Down Expand Up @@ -1631,7 +1631,7 @@
do_action( 'feedzy_run_cron_extra', $job );
} catch ( Exception $e ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[Feedzy Run Cron][Post title: ' . ( ! empty( $job->post_title ) ? $job->post_title : '' ) . '] Error: ' . $e->getMessage() );

Check warning on line 1634 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.PHP.DevelopmentFunctions.error_log_error_log

error_log() found. Debug code should not normally be used in production.
}

Feedzy_Rss_Feeds_Log::error(
Expand Down Expand Up @@ -1826,7 +1826,7 @@
delete_post_meta( $job->ID, 'import_info' );

// let's increase this time in case spinnerchief/wordai is being used.
set_time_limit( apply_filters( 'feedzy_max_execution_time', 500 ) );

Check warning on line 1829 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

Squiz.PHP.DiscouragedFunctions.Discouraged

The use of function set_time_limit() is discouraged

$count = 0;
$index = 0;
Expand Down Expand Up @@ -2711,7 +2711,7 @@
$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,
Expand Down Expand Up @@ -3157,7 +3157,7 @@
);

// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink
unlink( $local_file );

Check failure on line 3160 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.WP.AlternativeFunctions.unlink_unlink

unlink() is discouraged. Use wp_delete_file() to delete a file.

return false;
}
Expand All @@ -3175,7 +3175,7 @@
);

// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink
unlink( $local_file );

Check failure on line 3178 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.WP.AlternativeFunctions.unlink_unlink

unlink() is discouraged. Use wp_delete_file() to delete a file.

return false;
}
Expand All @@ -3188,7 +3188,7 @@

if ( $correct_local_file !== $local_file ) {
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_rename
if ( rename( $local_file, $correct_local_file ) ) {

Check failure on line 3191 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.WP.AlternativeFunctions.rename_rename

rename() is discouraged. Use WP_Filesystem::move() to rename a file.
$local_file = $correct_local_file;
} else {
Feedzy_Rss_Feeds_Log::error(
Expand All @@ -3202,7 +3202,7 @@
);

// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink
unlink( $local_file );

Check failure on line 3205 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.WP.AlternativeFunctions.unlink_unlink

unlink() is discouraged. Use wp_delete_file() to delete a file.

return false;
}
Expand Down Expand Up @@ -3284,7 +3284,7 @@
$new_local_file = preg_replace( '/\.tmp$/', $extension, $local_file );

// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_rename
$renamed = rename( $local_file, $new_local_file );

Check failure on line 3287 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.WP.AlternativeFunctions.rename_rename

rename() is discouraged. Use WP_Filesystem::move() to rename a file.
if ( $renamed ) {
$local_file = $new_local_file;
} else {
Expand Down Expand Up @@ -3395,7 +3395,7 @@

if (
( isset( $_POST['nonce'] ) && isset( $_POST['tab'] ) ) &&
wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), sanitize_text_field( $_POST['tab'] ) )

Check warning on line 3398 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.Security.ValidatedSanitizedInput.MissingUnslash

$_POST['tab'] not unslashed before sanitization. Use wp_unslash() or similar
) {
if ( ! empty( $_POST['fz_cron_schedule'] ) ) {
$schedule = sanitize_text_field( wp_unslash( $_POST['fz_cron_schedule'] ) );
Expand Down Expand Up @@ -3649,7 +3649,7 @@
public function save_tab_settings( $settings, $tab ) {
if (
! isset( $_POST['nonce'] ) ||
! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), $tab )

Check warning on line 3652 in includes/admin/feedzy-rss-feeds-import.php

View workflow job for this annotation

GitHub Actions / WordPress.org Guidelines Check

WordPress.Security.ValidatedSanitizedInput.MissingUnslash

$_POST['nonce'] not unslashed before sanitization. Use wp_unslash() or similar
) {
return array();
}
Expand Down
84 changes: 83 additions & 1 deletion tests/test-admin-abstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -624,4 +624,86 @@ public function test_feedzy_image_encode_edge_cases() {
$this->assertEquals($expected, $result, "Failed for input: $input");
}
}
}

/**
* 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('<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Test Feed</title>
<item>
<title>Item without image</title>
<link>https://example.com/article</link>
<description>Plain text, no image tag.</description>
</item>
</channel>
</rss>');
$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 <img> 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('<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Test Feed</title>
<item>
<title>Item with image</title>
<enclosure url="https://example.com/real-image.jpg" type="image/jpeg" />
</item>
</channel>
</rss>');
$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' );
}
}
86 changes: 86 additions & 0 deletions tests/test-image-import.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => '<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>No Image Feed</title><link>http://feedzy.test</link><description>t</description>
<item><title>Imageless one</title><link>http://feedzy.test/1</link><description>Plain text.</description><guid>fz-1</guid></item>
<item><title>Imageless two</title><link>http://feedzy.test/2</link><description>Also plain.</description><guid>fz-2</guid></item>
</channel></rss>',
'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 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 false !== strpos( $url, 'feedzy.svg' ) || 0 === strpos( $url, home_url() );
}
);
$this->assertEmpty( $fallback_requests, 'Import must not download any fallback image for imageless items: ' . implode( ', ', $fallback_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.
Expand Down
Loading