Disable oEmbed for a Single Shortcode or at Least All Internal Links

Here we consider the question:

can post embeds be disabled for all the internal links on a site currently running WordPress 4.5.3?

The Short Story

If we want to disable post embedding of internal links, but allow it on external sites, then we can use:

/**
 * Disable post embeds for internal links but allow it on external sites
 */
add_filter( 'pre_oembed_result', function( $result, $url, $args )
{    
    if( parse_url( home_url(), PHP_URL_HOST ) ===  parse_url( $url, PHP_URL_HOST ) )
        $result = false;

    return $result;

}, PHP_INT_MAX, 3 );

Here we check if the link is for the current host. This can be adjusted further to our needs.

We then don’t need this filter callback:

add_action( 'init', function()
{
    remove_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10 );
} );

and probably others as well.

Note that the embeds are cached for 24 hours by default. Here are some examples how to regenerate it.

Sorry for the long part here below, but I decided to write it down, to better understand it 😉

The Longer Version

When we paste the post url:

http://example.tld/hello-world

in the visual editor, then

http://example.tld/hello-world

is sent to the wp_ajax_parse_embed() function via an ajax request, where it calls $wp_embed->run_shortcode().

Similarly the content is filtered through the $wp_embed->autoembed() and $wp_embed->run_shortcode() via the the_content filter.

Both are based on the WP_Embed::shortcode() method src.

Within the shortcode method

If the data isn’t cached, in the post meta, then wp_oembed_get() is fired up including the following sequence:

WP_oEmbed::get_html()
    WP_oEmbed::fetch()
        WP_oEmbed::_fetch_with_format()
            wp_safe_remote_get()
            wp_remote_retrieve_body()

and the data is fetched with a request to:

http://example.tld/wp-json/oembed/1.0/embed?url=http%3A%2F%2Fexample.tld%2Fhello-world%2F&
maxwidth=900&maxheight=1000&format=json

This rest endpoint, generates data with the get_oembed_response_data() function and also applies the oembed_request_post_id filter src.

This filter was introduced in #36767 in WordPress version 4.5.3.

By default link discovery is active. that means we would get an extra request with wp_safe_remote_get() to be able to parse the relevant <link> tags, from the remote site, to determine the provder’s url:

http://example.tld/wp-json/oembed/1.0/embed?url=http%3A%2F%2Fexample.tld%2Fhello-world%2F

The pre_oembed_result filter

A new filter was also introduced in #36767, within WP_oEmbed::get_html() src:

$pre = apply_filters( 'pre_oembed_result', null, $url, $args ); 

if ( null !== $pre ) { 
    return $pre; 
}    

to avoid external HTTP requests for internal links. It will short-circuit the wp_oembed_get() if the filter returns anything else than null.

In core we now have:

add_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10, 3 );

where the filter’s callback wp_filter_pre_oembed_result() generates data with the get_oembed_response_data() function, just as for the embed rest endpoint. It’s filterable through the oembed_response_data filter. The get_oembed_response_data_rich() callback is hooked into it with:

add_filter( 'oembed_response_data',   'get_oembed_response_data_rich', 10, 4 ); 

It contains the get_post_embed_html() function that generates the <blockquote>, <script> and <iframe> code.

Disable internal post embeds

To disable post embeds for internal links, our suggestion is therefore to make sure the pre_oembed_result filter returns false (different than null):

/**
 * Disable post embeds for internal links but allow it on external sites
 */
add_filter( 'pre_oembed_result', function( $result, $url, $args )
{    
    if( parse_url( home_url(), PHP_URL_HOST ) ===  parse_url( $url, PHP_URL_HOST ) )
        $result = false;
    return $result;

}, PHP_INT_MAX, 3 );

where we return false as late as possible, hence we use a large priority like PHP_INT_MAX.

Then we don’t need wp_filter_pre_oembed_result() callback to run, so we can also remove it with:

remove_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10 );

where we have to remove it with the same priority as it was added with.

Another approach could be to use:

add_filter( 'oembed_request_post_id', '__return_false' );

This would mean that we get HTTP requests to the rest endpoint that will through an error because the post id isn’t valid.

Simple Testing

Here’s a way to test it. First we need an object instance of the WP_Embed class:

$e = new WP_Embed(); 

or use the global $wp_embed object. Then we setup the internal and external post links:

$internal="http://internal.tld/hello-world/";
$external="http://external.tld/hello-world/";

and run:

$internal_is_embeddable = $internal !== $e->run_shortcode( 
    sprintf( '%s', 
    $internal 
);

$external_is_embeddable = $external !== $e->run_shortcode( 
    sprintf( '%s', 
    $external 
);

We can also test it without shortcodes using:

$internal_is_embeddable = $internal !== $e->autoembed( $internal );
$external_is_embeddable = $external !== $e->autoembed( $external );

We just have to be aware of the oembed (post meta) cache and use e.g. a cache busting parameter for the test links. Here are some examples how to regenerate it.

Leave a Comment