From 5db31259ec8553a41655dc61daccc669bc77897a Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 17 May 2023 18:29:41 +0000 Subject: [PATCH] Media: Introduce `wp_get_attachment_image_context` filter. Since WordPress 5.9, a "context" value of "wp_get_attachment_image" has been used in the `wp_get_attachment_image()` function to provide context to underlying functions where that is relevant, e.g. `wp_get_loading_attr_default()`. Since that value used to be not customizable, it required a workaround in `get_the_post_thumbnail()` to avoid calling those functions in `wp_get_attachment_image()`, which resulted in unnecessary complexity and was prone to errors. This changeset introduces a `wp_get_attachment_image_context` filter and leverages it with private filter callback functions that are leveraged by default when `get_the_post_thumbnail()` is called. This avoids the need for the previous workaround and furthermore provides flexibility for other callers of `wp_get_attachment_image()` to provide their own contexts. Props flixos90, costdev, thekt12, westonruter, spacedmonkey. Fixes #58212. git-svn-id: https://develop.svn.wordpress.org/trunk@55821 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/default-filters.php | 4 +- src/wp-includes/media.php | 54 +++++++- src/wp-includes/post-thumbnail-template.php | 16 --- tests/phpunit/tests/media.php | 56 +++++++++ tests/phpunit/tests/post/thumbnails.php | 129 ++++++++++++++++++++ 5 files changed, 240 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 6d0f0fee4a..82b579913a 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -444,9 +444,11 @@ add_action( 'delete_term', '_wp_delete_tax_menu_item', 10, 3 ); add_action( 'transition_post_status', '_wp_auto_add_pages_to_menu', 10, 3 ); add_action( 'delete_post', '_wp_delete_customize_changeset_dependent_auto_drafts' ); -// Post Thumbnail CSS class filtering. +// Post Thumbnail specific image filtering. add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_add' ); add_action( 'end_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_remove' ); +add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_context_filter_add' ); +add_action( 'end_fetch_post_thumbnail_html', '_wp_post_thumbnail_context_filter_remove' ); // Redirect old slugs. add_action( 'template_redirect', 'wp_old_slug_redirect' ); diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 1478832785..111a99d02f 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -1050,9 +1050,18 @@ function wp_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = f 'decoding' => 'async', ); + /** + * Filters the context in which wp_get_attachment_image() is used. + * + * @since 6.3.0 + * + * @param string $context The context. Default 'wp_get_attachment_image'. + */ + $context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' ); + // Add `loading` attribute. - if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) { - $default_attr['loading'] = wp_get_loading_attr_default( 'wp_get_attachment_image' ); + if ( wp_lazy_loading_enabled( 'img', $context ) ) { + $default_attr['loading'] = wp_get_loading_attr_default( $context ); } $attr = wp_parse_args( $attr, $default_attr ); @@ -2170,6 +2179,47 @@ function _wp_post_thumbnail_class_filter_remove( $attr ) { remove_filter( 'wp_get_attachment_image_attributes', '_wp_post_thumbnail_class_filter' ); } +/** + * Overrides the context used in {@see wp_get_attachment_image()}. Internal use only. + * + * Uses the {@see 'begin_fetch_post_thumbnail_html'} and {@see 'end_fetch_post_thumbnail_html'} + * action hooks to dynamically add/remove itself so as to only filter post thumbnails. + * + * @ignore + * @since 6.3.0 + * @access private + * + * @param string $context The context for rendering an attachment image. + * @return string Modified context set to 'the_post_thumbnail'. + */ +function _wp_post_thumbnail_context_filter( $context ) { + return 'the_post_thumbnail'; +} + +/** + * Adds the '_wp_post_thumbnail_context_filter' callback to the 'wp_get_attachment_image_context' + * filter hook. Internal use only. + * + * @ignore + * @since 6.3.0 + * @access private + */ +function _wp_post_thumbnail_context_filter_add() { + add_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' ); +} + +/** + * Removes the '_wp_post_thumbnail_context_filter' callback from the 'wp_get_attachment_image_context' + * filter hook. Internal use only. + * + * @ignore + * @since 6.3.0 + * @access private + */ +function _wp_post_thumbnail_context_filter_remove() { + remove_filter( 'wp_get_attachment_image_context', '_wp_post_thumbnail_context_filter' ); +} + add_shortcode( 'wp_caption', 'img_caption_shortcode' ); add_shortcode( 'caption', 'img_caption_shortcode' ); diff --git a/src/wp-includes/post-thumbnail-template.php b/src/wp-includes/post-thumbnail-template.php index b349cc66e5..e801d53a97 100644 --- a/src/wp-includes/post-thumbnail-template.php +++ b/src/wp-includes/post-thumbnail-template.php @@ -186,22 +186,6 @@ function get_the_post_thumbnail( $post = null, $size = 'post-thumbnail', $attr = update_post_thumbnail_cache(); } - // Add `loading` attribute. - if ( wp_lazy_loading_enabled( 'img', 'the_post_thumbnail' ) ) { - // Get the 'loading' attribute value to use as default, taking precedence over the default from - // `wp_get_attachment_image()`. - $loading = wp_get_loading_attr_default( 'the_post_thumbnail' ); - - // Add the default to the given attributes unless they already include a 'loading' directive. - if ( empty( $attr ) ) { - $attr = array( 'loading' => $loading ); - } elseif ( is_array( $attr ) && ! array_key_exists( 'loading', $attr ) ) { - $attr['loading'] = $loading; - } elseif ( is_string( $attr ) && ! preg_match( '/(^|&)loading=/', $attr ) ) { - $attr .= '&loading=' . $loading; - } - } - $html = wp_get_attachment_image( $post_thumbnail_id, $size, false, $attr ); /** diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 1cb677dacf..c971a4a2a9 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -3963,6 +3963,62 @@ EOF; ); } + /** + * Tests that `wp_get_attachment_image()` uses the correct default context. + * + * @ticket 58212 + * + * @covers ::wp_get_attachment_image() + */ + public function test_wp_get_attachment_image_context_filter_default() { + $last_context = ''; + $this->track_last_attachment_image_context( $last_context ); + + wp_get_attachment_image( self::$large_id ); + $this->assertSame( 'wp_get_attachment_image', $last_context ); + } + + /** + * Tests that `wp_get_attachment_image()` allows overriding the context via filter. + * + * @ticket 58212 + * + * @covers ::wp_get_attachment_image() + */ + public function test_wp_get_attachment_image_context_filter_value_is_passed_correctly() { + $last_context = ''; + $this->track_last_attachment_image_context( $last_context ); + + // Add a filter that modifies the context. + add_filter( + 'wp_get_attachment_image_context', + static function() { + return 'my_custom_context'; + } + ); + + wp_get_attachment_image( self::$large_id ); + $this->assertSame( 'my_custom_context', $last_context ); + } + + /** + * Helper method to keep track of the last context returned by the 'wp_get_attachment_image_context' filter. + * + * The method parameter is passed by reference and therefore will always contain the last context value. + * + * @param mixed $last_context Variable to track last context. Passed by reference. + */ + private function track_last_attachment_image_context( &$last_context ) { + add_filter( + 'wp_get_attachment_image_context', + static function( $context ) use ( &$last_context ) { + $last_context = $context; + return $context; + }, + 11 + ); + } + /** * Add threshold to create a `-scaled` output image for testing. */ diff --git a/tests/phpunit/tests/post/thumbnails.php b/tests/phpunit/tests/post/thumbnails.php index 73199e4b4e..44801059a8 100644 --- a/tests/phpunit/tests/post/thumbnails.php +++ b/tests/phpunit/tests/post/thumbnails.php @@ -450,6 +450,135 @@ class Tests_Post_Thumbnail_Template extends WP_UnitTestCase { ); } + /** + * Tests that `_wp_post_thumbnail_context_filter()` returns 'the_post_thumbnail'. + * + * @ticket 58212 + * + * @covers::_wp_post_thumbnail_context_filter + */ + public function test_wp_post_thumbnail_context_filter_should_return_the_post_thumbnail() { + $this->assertSame( 'the_post_thumbnail', _wp_post_thumbnail_context_filter( 'wp_get_attachment_image' ) ); + } + + /** + * Tests that `::_wp_post_thumbnail_context_filter_add` adds a filter to override the context + * used in `wp_get_attachment_image()`. + * + * @ticket 58212 + * + * @covers ::_wp_post_thumbnail_context_filter_add + */ + public function test_wp_post_thumbnail_context_filter_add_should_add_the_filter() { + $last_context = ''; + $this->track_last_attachment_image_context( $last_context ); + + _wp_post_thumbnail_context_filter_add(); + wp_get_attachment_image( self::$attachment_id ); + + $this->assertSame( 'the_post_thumbnail', $last_context ); + } + + /** + * Tests that `_wp_post_thumbnail_context_filter_remove()` removes a filter to override the context + * used in `wp_get_attachment_image()`. + * + * @ticket 58212 + * + * @covers ::_wp_post_thumbnail_context_filter_remove + */ + public function test_wp_post_thumbnail_context_filter_remove_should_remove_the_filter() { + $last_context = ''; + $this->track_last_attachment_image_context( $last_context ); + + _wp_post_thumbnail_context_filter_add(); + wp_get_attachment_image( self::$attachment_id ); + + // Verify that the filter has been added before testing that it has been removed. + $this->assertSame( + 'the_post_thumbnail', + $last_context, + 'The filter was not added.' + ); + + _wp_post_thumbnail_context_filter_remove(); + + // The context should no longer be modified by the filter. + wp_get_attachment_image( self::$attachment_id ); + + $this->assertSame( + 'wp_get_attachment_image', + $last_context, + 'The filter was not removed.' + ); + } + + /** + * Tests that `get_the_post_thumbnail()` uses the 'the_post_thumbnail' context. + * + * @ticket 58212 + * + * @covers ::get_the_post_thumbnail + */ + public function test_get_the_post_thumbnail_should_use_the_post_thumbnail_context() { + $last_context = ''; + $this->track_last_attachment_image_context( $last_context ); + + set_post_thumbnail( self::$post, self::$attachment_id ); + get_the_post_thumbnail( self::$post ); + + $this->assertSame( 'the_post_thumbnail', $last_context ); + } + + /** + * Tests that `get_the_post_thumbnail()` restores the context afterwards. + * + * @ticket 58212 + * + * @covers ::get_the_post_thumbnail + */ + public function test_get_the_post_thumbnail_should_remove_the_post_thumbnail_context_afterwards() { + $last_context = ''; + $this->track_last_attachment_image_context( $last_context ); + + set_post_thumbnail( self::$post, self::$attachment_id ); + get_the_post_thumbnail( self::$post ); + + // Verify that the context was overridden before testing that it has been restored. + $this->assertSame( + 'the_post_thumbnail', + $last_context, + 'The context was not overridden.' + ); + + // The context should no longer be overridden. + wp_get_attachment_image( self::$attachment_id ); + + $this->assertSame( + 'wp_get_attachment_image', + $last_context, + 'The context was not restored.' + ); + } + + /** + * Helper method to keep track of the last context returned by the 'wp_get_attachment_image_context' filter. + * + * The method parameter is passed by reference and therefore will always contain the last context value. + * + * @param mixed $last_context Variable to track last context. Passed by reference. + */ + private function track_last_attachment_image_context( &$last_context ) { + add_filter( + 'wp_get_attachment_image_context', + static function( $context ) use ( &$last_context ) { + $last_context = $context; + return $context; + }, + 11 + ); + } + public function filter_post_thumbnail_size( $size, $post_id ) { if ( is_array( $this->current_size_filter_data ) && isset( $this->current_size_filter_data[ $post_id ] ) ) { return $this->current_size_filter_data[ $post_id ];