diff --git a/src/wp-includes/class-oembed.php b/src/wp-includes/class-oembed.php index b60a4f4794..e0a749d807 100644 --- a/src/wp-includes/class-oembed.php +++ b/src/wp-includes/class-oembed.php @@ -284,10 +284,32 @@ class WP_oEmbed { * @return false|string False on failure, otherwise the UNSANITIZED (and potentially unsafe) HTML that should be used to embed. */ public function get_html( $url, $args = '' ) { + /** + * Filters the oEmbed result before any HTTP requests are made. + * + * This allows one to short-circuit the default logic, perhaps by + * replacing it with a routine that is more optimal for your setup. + * + * Passing a non-null value to the filter will effectively short-circuit retrieval, + * returning the passed value instead. + * + * @since 4.5.3 + * + * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null. + * @param string $url The URL to the content that should be attempted to be embedded. + * @param array $args Optional. Arguments, usually passed from a shortcode. Default empty. + */ + $pre = apply_filters( 'pre_oembed_result', null, $url, $args ); + + if ( null !== $pre ) { + return $pre; + } + $provider = $this->get_provider( $url, $args ); - if ( !$provider || false === $data = $this->fetch( $provider, $url, $args ) ) + if ( ! $provider || false === $data = $this->fetch( $provider, $url, $args ) ) { return false; + } /** * Filter the HTML returned by the oEmbed provider. diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 1faa084cf7..db03c684e2 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -473,5 +473,6 @@ add_filter( 'the_excerpt_embed', 'wp_embed_excerpt_attachment' ); add_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10, 3 ); add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 ); +add_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10, 3 ); unset( $filter, $action ); diff --git a/src/wp-includes/embed.php b/src/wp-includes/embed.php index e85309793b..821a633f20 100644 --- a/src/wp-includes/embed.php +++ b/src/wp-includes/embed.php @@ -1045,3 +1045,39 @@ function print_embed_sharing_dialog() { ` tags. + * @param array $args oEmbed remote get arguments. + * @return null|string The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. + * Null if the URL does not belong to the current site. + */ +function wp_filter_pre_oembed_result( $result, $url, $args ) { + $post_id = url_to_postid( $url ); + + /** This filter is documented in wp-includes/class-wp-oembed-controller.php */ + $post_id = apply_filters( 'oembed_request_post_id', $post_id, $url ); + + if ( ! $post_id ) { + return $result; + } + + $width = isset( $args['width'] ) ? $args['width'] : 0; + + $data = get_oembed_response_data( $post_id, $width ); + $data = _wp_oembed_get_object()->data2html( (object) $data, $url ); + + if ( ! $data ) { + return $result; + } + + return $data; +} diff --git a/tests/phpunit/tests/oembed/wpOembed.php b/tests/phpunit/tests/oembed/wpOembed.php new file mode 100644 index 0000000000..b7e6887214 --- /dev/null +++ b/tests/phpunit/tests/oembed/wpOembed.php @@ -0,0 +1,72 @@ +oembed = _wp_oembed_get_object(); + + $this->pre_oembed_result_filtered = false; + } + + public function _filter_pre_oembed_result( $result ) { + // If this is not null, the oEmbed result has been filtered before any HTTP requests were made. + $this->pre_oembed_result_filtered = $result; + + // Return false to prevent HTTP requests during tests. + return $result ? $result : false; + } + + public function test_wp_filter_pre_oembed_result_prevents_http_request_for_internal_permalinks() { + $post_id = self::factory()->post->create(); + $permalink = get_permalink( $post_id ); + + add_filter( 'pre_oembed_result', array( $this, '_filter_pre_oembed_result' ) ); + $actual = $this->oembed->get_html( $permalink ); + remove_filter( 'pre_oembed_result', array( $this, '_filter_pre_oembed_result' ) ); + + $this->assertTrue( false !== $this->pre_oembed_result_filtered ); + $this->assertEquals( $this->pre_oembed_result_filtered, $actual ); + } + + public function test_wp_filter_pre_oembed_result_prevents_http_request_when_viewing_the_post() { + $post_id = self::factory()->post->create(); + $permalink = get_permalink( $post_id ); + + $this->go_to( $permalink ); + $this->assertQueryTrue( 'is_single', 'is_singular' ); + + add_filter( 'pre_oembed_result', array( $this, '_filter_pre_oembed_result' ) ); + $actual = $this->oembed->get_html( $permalink ); + remove_filter( 'pre_oembed_result', array( $this, '_filter_pre_oembed_result' ) ); + + $this->assertTrue( false !== $this->pre_oembed_result_filtered ); + $this->assertEquals( $this->pre_oembed_result_filtered, $actual ); + } + + public function test_wp_filter_pre_oembed_result_non_existent_post() { + $post_id = self::factory()->post->create(); + $permalink = get_permalink( $post_id ); + + $this->go_to( $permalink ); + $this->assertQueryTrue( 'is_single', 'is_singular' ); + + add_filter( 'pre_oembed_result', array( $this, '_filter_pre_oembed_result' ) ); + $actual = $this->oembed->get_html( 'https://example.com/' ); + remove_filter( 'pre_oembed_result', array( $this, '_filter_pre_oembed_result' ) ); + + $this->assertTrue( false !== $this->pre_oembed_result_filtered ); + $this->assertFalse( $actual ); + } +}