diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php
index 260db26cac..2b46167afc 100644
--- a/src/wp-includes/media.php
+++ b/src/wp-includes/media.php
@@ -1857,6 +1857,12 @@ function wp_filter_content_tags( $content, $context = null ) {
if ( $filtered_image !== $match[0] ) {
$content = str_replace( $match[0], $filtered_image, $content );
}
+
+ /*
+ * Unset image lookup to not run the same logic again unnecessarily if the same image tag is used more than
+ * once in the same blob of content.
+ */
+ unset( $images[ $match[0] ] );
}
// Filter an iframe match.
@@ -1871,6 +1877,12 @@ function wp_filter_content_tags( $content, $context = null ) {
if ( $filtered_iframe !== $match[0] ) {
$content = str_replace( $match[0], $filtered_iframe, $content );
}
+
+ /*
+ * Unset iframe lookup to not run the same logic again unnecessarily if the same iframe tag is used more
+ * than once in the same blob of content.
+ */
+ unset( $iframes[ $match[0] ] );
}
}
diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php
index 88a402e298..8f3340aaf7 100644
--- a/tests/phpunit/tests/media.php
+++ b/tests/phpunit/tests/media.php
@@ -2304,6 +2304,73 @@ EOF;
wp_filter_content_tags( $img_tag_1 );
$this->assertSame( 1, $filter->get_call_count() );
}
+
+ /**
+ * @ticket 55510
+ * @covers ::wp_filter_content_tags
+ */
+ public function test_wp_filter_content_tags_handles_duplicate_img_and_iframe_tags_once() {
+ $img = get_image_tag( self::$large_id, '', '', '', 'large' );
+ $iframe = '';
+ $content = "$img\n$img\n$iframe\n$iframe";
+
+ // Record how often one of the available img and iframe filters is run.
+ // Both images and iframes support lazy-loading, so that's why this is used here.
+ $img_filter = new MockAction();
+ add_filter( 'wp_img_tag_add_loading_attr', array( &$img_filter, 'filter' ) );
+ $iframe_filter = new MockAction();
+ add_filter( 'wp_iframe_tag_add_loading_attr', array( &$iframe_filter, 'filter' ) );
+
+ // Ensure the img and iframe filters only ran once because the content is a single duplicated img tag and a
+ // single duplicate iframe tag.
+ wp_filter_content_tags( $content );
+ $this->assertSame( 1, $img_filter->get_call_count() );
+ $this->assertSame( 1, $iframe_filter->get_call_count() );
+ }
+
+ /**
+ * @ticket 55510
+ * @covers ::wp_filter_content_tags
+ */
+ public function test_wp_filter_content_tags_filter_with_identical_image_tags_custom_attributes() {
+ $img = get_image_tag( self::$large_id, '', '', '', 'large' );
+ $img = str_replace( '
$filtered_image";
+ }
+ );
+
+ // Ensure there is no duplicate wrapping the image.
+ $this->assertStringNotContainsString( '
$filtered_image";
+ }
+ );
+
+ // Ensure the output has both instances of the image wrapped with a single .
+ $this->assertSame( "$img\n$img", wp_filter_content_tags( $content ) );
+ }
+
/**
* @ticket 33641
* @ticket 34528