diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 38b6bbf72bc4..d71ab2d6c1c9 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -3937,10 +3937,26 @@ function wp_trim_excerpt( $text = '', $post = null ) { $text = strip_shortcodes( $text ); $text = excerpt_remove_blocks( $text ); + /* + * Temporarily unhook wp_filter_content_tags() since any tags + * within the excerpt are stripped out. Modifying the tags here + * is wasteful and can lead to bugs in the image counting logic. + */ + $filter_removed = remove_filter( 'the_content', 'wp_filter_content_tags' ); + /** This filter is documented in wp-includes/post-template.php */ $text = apply_filters( 'the_content', $text ); $text = str_replace( ']]>', ']]>', $text ); + /** + * Only restore the filter callback if it was removed above. The logic + * to unhook and restore only applies on the default priority of 10, + * which is generally used for the filter callback in WordPress core. + */ + if ( $filter_removed ) { + add_filter( 'the_content', 'wp_filter_content_tags' ); + } + /* translators: Maximum number of words used in a post excerpt. */ $excerpt_length = (int) _x( '55', 'excerpt_length' ); diff --git a/tests/phpunit/tests/formatting/wpTrimExcerpt.php b/tests/phpunit/tests/formatting/wpTrimExcerpt.php index c3ab336bf931..1cb4389ee9a3 100644 --- a/tests/phpunit/tests/formatting/wpTrimExcerpt.php +++ b/tests/phpunit/tests/formatting/wpTrimExcerpt.php @@ -92,4 +92,60 @@ public function test_should_generate_excerpt_for_empty_values() { $this->assertSame( 'Post content', wp_trim_excerpt( null, $post ) ); $this->assertSame( 'Post content', wp_trim_excerpt( false, $post ) ); } + + /** + * Tests that `wp_trim_excerpt()` unhooks `wp_filter_content_tags()` from 'the_content' filter. + * + * @ticket 56588 + */ + public function test_wp_trim_excerpt_unhooks_wp_filter_content_tags() { + $post = self::factory()->post->create(); + + /* + * Record that during 'the_content' filter run by wp_trim_excerpt() the + * wp_filter_content_tags() callback is not used. + */ + $has_filter = true; + add_filter( + 'the_content', + static function( $content ) use ( &$has_filter ) { + $has_filter = has_filter( 'the_content', 'wp_filter_content_tags' ); + return $content; + } + ); + + wp_trim_excerpt( '', $post ); + + $this->assertFalse( $has_filter, 'wp_filter_content_tags() was not unhooked in wp_trim_excerpt()' ); + } + + /** + * Tests that `wp_trim_excerpt()` doesn't permanently unhook `wp_filter_content_tags()` from 'the_content' filter. + * + * @ticket 56588 + */ + public function test_wp_trim_excerpt_should_not_permanently_unhook_wp_filter_content_tags() { + $post = self::factory()->post->create(); + + wp_trim_excerpt( '', $post ); + + $this->assertSame( 10, has_filter( 'the_content', 'wp_filter_content_tags' ), 'wp_filter_content_tags() was not restored in wp_trim_excerpt()' ); + } + + /** + * Tests that `wp_trim_excerpt()` doesn't restore `wp_filter_content_tags()` if it was previously unhooked. + * + * @ticket 56588 + */ + public function test_wp_trim_excerpt_does_not_restore_wp_filter_content_tags_if_previously_unhooked() { + $post = self::factory()->post->create(); + + // Remove wp_filter_content_tags() from 'the_content' filter generally. + remove_filter( 'the_content', 'wp_filter_content_tags' ); + + wp_trim_excerpt( '', $post ); + + // Assert that the filter callback was not restored after running 'the_content'. + $this->assertFalse( has_filter( 'the_content', 'wp_filter_content_tags' ) ); + } } diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index c38a17d1cfbe..9ea0cc53b087 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -75,6 +75,16 @@ public static function tear_down_after_class() { parent::tear_down_after_class(); } + /** + * Ensures that the static content media count and related filter are reset between tests. + */ + public function set_up() { + parent::set_up(); + + $this->reset_content_media_count(); + $this->reset_omit_loading_attr_filter(); + } + public function test_img_caption_shortcode_added() { global $shortcode_tags; $this->assertSame( 'img_caption_shortcode', $shortcode_tags['caption'] ); @@ -3567,8 +3577,6 @@ public function test_wp_get_loading_attr_default( $context ) { $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) ); $query = $this->get_new_wp_query_for_published_post(); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); while ( have_posts() ) { the_post(); @@ -3613,8 +3621,6 @@ public function data_wp_get_loading_attr_default() { public function test_wp_omit_loading_attr_threshold_filter() { $query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $query ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); // Use the filter to alter the threshold for not lazy-loading to the first five elements. $this->force_omit_loading_attr_threshold( 5 ); @@ -3655,8 +3661,6 @@ public function test_wp_filter_content_tags_with_wp_get_loading_attr_default() { $query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $query ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); while ( have_posts() ) { the_post(); @@ -3707,8 +3711,6 @@ public function test_wp_get_loading_attr_default_before_loop_if_not_main_query( global $wp_query; $wp_query = $this->get_new_wp_query_for_published_post(); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); do_action( 'get_header' ); @@ -3732,8 +3734,6 @@ public function test_wp_get_loading_attr_default_before_loop_in_main_query_but_h $wp_query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $wp_query ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); // Lazy if header not called. $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) ); @@ -3755,8 +3755,6 @@ public function test_wp_get_loading_attr_default_before_loop_if_main_query( $con $wp_query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $wp_query ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); do_action( 'get_header' ); $this->assertFalse( wp_get_loading_attr_default( $context ) ); @@ -3778,8 +3776,6 @@ public function test_wp_get_loading_attr_default_after_loop( $context ) { $wp_query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $wp_query ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); do_action( 'get_header' ); @@ -3805,8 +3801,6 @@ public function test_wp_get_loading_attr_default_no_loop( $context ) { $wp_query = $this->get_new_wp_query_for_published_post(); $this->set_main_query( $wp_query ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); // Ensure header and footer is called. do_action( 'get_header' ); @@ -3865,8 +3859,6 @@ public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_bl $wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) ); $wp_the_query = $wp_query; $post = get_post( self::$post_ids['publish'] ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); $_wp_current_template_content = ''; @@ -3922,8 +3914,6 @@ static function( $attr ) { $wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) ); $wp_the_query = $wp_query; $post = get_post( self::$post_ids['publish'] ); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); $_wp_current_template_content = ' '; @@ -4013,8 +4003,7 @@ static function() { */ $wp_query = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) ); $wp_the_query = $wp_query; - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); + $content = ''; while ( have_posts() ) { the_post(); @@ -4078,6 +4067,98 @@ public function data_special_contexts_for_the_content() { ); } + /** + * Tests that the content media count is not affected by `the_excerpt()` calls for posts that contain images. + * + * @ticket 56588 + * + * @covers ::wp_trim_excerpt + */ + public function test_the_excerpt_does_not_affect_content_media_count() { + global $wp_query, $wp_the_query; + + /* + * Use the filter to alter the threshold for not lazy-loading to the first 2 elements, + * then use a post that contains exactly 2 images. + */ + $this->force_omit_loading_attr_threshold( 2 ); + $post_content = ''; + $post_content .= '

Some text.

'; + $post_content .= ''; + + $post_id = self::factory()->post->create( + array( + 'post_content' => $post_content, + 'post_excerpt' => '', + ) + ); + + $wp_query = new WP_Query( array( 'post__in' => array( $post_id ) ) ); + $wp_the_query = $wp_query; + + while ( have_posts() ) { + the_post(); + + // Call `the_excerpt()` without generating output. + get_echo( 'the_excerpt' ); + } + + // The only way to access the value is by calling this function without increasing the value. + $content_media_count = wp_increase_content_media_count( 0 ); + + // Assert that the media count was not increased even though there are 3 images in the post's content. + $this->assertSame( 0, $content_media_count ); + } + + /** + * Tests that the lazy-loading result is not affected by `the_excerpt()` calls for posts that + * contain images. + * + * Printing the excerpt for a post that contains images in its content prior to its featured image should result in + * that featured image not being lazy-loaded, since the images in the post content aren't displayed in the excerpt. + * + * @ticket 56588 + * + * @covers ::wp_trim_excerpt + */ + public function test_the_excerpt_does_not_affect_omit_lazy_loading_logic() { + global $wp_query, $wp_the_query; + + /* + * Use the filter to alter the threshold for not lazy-loading to the first 2 elements, + * then use a post that contains exactly 2 images. + */ + $this->force_omit_loading_attr_threshold( 2 ); + $post_content = ''; + $post_content .= '

Some text.

'; + $post_content .= ''; + + $post_id = self::factory()->post->create( + array( + 'post_content' => $post_content, + 'post_excerpt' => '', + ) + ); + $featured_image_id = self::$large_id; + update_post_meta( $post_id, '_thumbnail_id', $featured_image_id ); + + $expected_image_tag = get_the_post_thumbnail( $post_id, 'post-thumbnail', array( 'loading' => false ) ); + + $wp_query = new WP_Query( array( 'post__in' => array( $post_id ) ) ); + $wp_the_query = $wp_query; + + $output = ''; + while ( have_posts() ) { + the_post(); + + // Print excerpt first, then the featured image. + $output .= get_echo( 'the_excerpt' ); + $output .= get_echo( 'the_post_thumbnail' ); + } + + $this->assertStringContainsString( $expected_image_tag, $output ); + } + private function reset_content_media_count() { // Get current value without increasing. $content_media_count = wp_increase_content_media_count( 0 );