Make WordPress Core

Changeset 41651


Ignore:
Timestamp:
09/30/2017 01:14:34 AM (7 years ago)
Author:
westonruter
Message:

Embeds: Cache oEmbeds in an oembed_cache custom post type instead of postmeta when there is no global $post.

Add processing of embeds to rich Text widget.

Props swissspidy, westonruter, ocean90, johnbillion.
See #40854, #39994, #40935.
Fixes #34115.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-embed.php

    r41162 r41651  
    3131        // Hack to get the [embed] shortcode to run before wpautop()
    3232        add_filter( 'the_content', array( $this, 'run_shortcode' ), 8 );
     33        add_filter( 'widget_text_content', array( $this, 'run_shortcode' ), 8 );
    3334
    3435        // Shortcode placeholder for strip_shortcodes()
     
    3738        // Attempts to embed all URLs in a post
    3839        add_filter( 'the_content', array( $this, 'autoembed' ), 8 );
     40        add_filter( 'widget_text_content', array( $this, 'autoembed' ), 8 );
    3941
    4042        // After a post is saved, cache oEmbed items via Ajax
     
    186188
    187189        $post_ID = ( ! empty( $post->ID ) ) ? $post->ID : null;
    188         if ( ! empty( $this->post_ID ) ) // Potentially set by WP_Embed::cache_oembed()
     190
     191        // Potentially set by WP_Embed::cache_oembed().
     192        if ( ! empty( $this->post_ID ) ) {
    189193            $post_ID = $this->post_ID;
    190 
    191         // Unknown URL format. Let oEmbed have a go.
     194        }
     195
     196        // Check for a cached result (stored as custom post or in the post meta).
     197        $key_suffix    = md5( $url . serialize( $attr ) );
     198        $cachekey      = '_oembed_' . $key_suffix;
     199        $cachekey_time = '_oembed_time_' . $key_suffix;
     200
     201        /**
     202         * Filters the oEmbed TTL value (time to live).
     203         *
     204         * @since 4.0.0
     205         *
     206         * @param int    $time    Time to live (in seconds).
     207         * @param string $url     The attempted embed URL.
     208         * @param array  $attr    An array of shortcode attributes.
     209         * @param int    $post_ID Post ID.
     210         */
     211        $ttl = apply_filters( 'oembed_ttl', DAY_IN_SECONDS, $url, $attr, $post_ID );
     212
     213        $cache      = '';
     214        $cache_time = 0;
     215
     216        $cached_post_id = $this->find_oembed_post_id( $key_suffix );
     217
    192218        if ( $post_ID ) {
    193 
    194             // Check for a cached result (stored in the post meta)
    195             $key_suffix = md5( $url . serialize( $attr ) );
    196             $cachekey = '_oembed_' . $key_suffix;
    197             $cachekey_time = '_oembed_time_' . $key_suffix;
    198 
    199             /**
    200              * Filters the oEmbed TTL value (time to live).
    201              *
    202              * @since 4.0.0
    203              *
    204              * @param int    $time    Time to live (in seconds).
    205              * @param string $url     The attempted embed URL.
    206              * @param array  $attr    An array of shortcode attributes.
    207              * @param int    $post_ID Post ID.
    208              */
    209             $ttl = apply_filters( 'oembed_ttl', DAY_IN_SECONDS, $url, $attr, $post_ID );
    210 
    211219            $cache = get_post_meta( $post_ID, $cachekey, true );
    212220            $cache_time = get_post_meta( $post_ID, $cachekey_time, true );
     
    215223                $cache_time = 0;
    216224            }
    217 
    218             $cached_recently = ( time() - $cache_time ) < $ttl;
    219 
    220             if ( $this->usecache || $cached_recently ) {
    221                 // Failures are cached. Serve one if we're using the cache.
    222                 if ( '{{unknown}}' === $cache )
    223                     return $this->maybe_make_link( $url );
    224 
    225                 if ( ! empty( $cache ) ) {
    226                     /**
    227                      * Filters the cached oEmbed HTML.
    228                      *
    229                      * @since 2.9.0
    230                      *
    231                      * @see WP_Embed::shortcode()
    232                      *
    233                      * @param mixed  $cache   The cached HTML result, stored in post meta.
    234                      * @param string $url     The attempted embed URL.
    235                      * @param array  $attr    An array of shortcode attributes.
    236                      * @param int    $post_ID Post ID.
    237                      */
    238                     return apply_filters( 'embed_oembed_html', $cache, $url, $attr, $post_ID );
    239                 }
    240             }
    241 
    242             /**
    243              * Filters whether to inspect the given URL for discoverable link tags.
    244              *
    245              * @since 2.9.0
    246              * @since 4.4.0 The default value changed to true.
    247              *
    248              * @see WP_oEmbed::discover()
    249              *
    250              * @param bool $enable Whether to enable `<link>` tag discovery. Default true.
    251              */
    252             $attr['discover'] = ( apply_filters( 'embed_oembed_discover', true ) );
    253 
    254             // Use oEmbed to get the HTML
    255             $html = wp_oembed_get( $url, $attr );
    256 
    257             // Maybe cache the result
     225        } elseif ( $cached_post_id ) {
     226            $cached_post = get_post( $cached_post_id );
     227
     228            $cache      = $cached_post->post_content;
     229            $cache_time = strtotime( $cached_post->post_modified_gmt );
     230        }
     231
     232        $cached_recently = ( time() - $cache_time ) < $ttl;
     233
     234        if ( $this->usecache || $cached_recently ) {
     235            // Failures are cached. Serve one if we're using the cache.
     236            if ( '{{unknown}}' === $cache ) {
     237                return $this->maybe_make_link( $url );
     238            }
     239
     240            if ( ! empty( $cache ) ) {
     241                /**
     242                 * Filters the cached oEmbed HTML.
     243                 *
     244                 * @since 2.9.0
     245                 *
     246                 * @see WP_Embed::shortcode()
     247                 *
     248                 * @param mixed  $cache   The cached HTML result, stored in post meta.
     249                 * @param string $url     The attempted embed URL.
     250                 * @param array  $attr    An array of shortcode attributes.
     251                 * @param int    $post_ID Post ID.
     252                 */
     253                return apply_filters( 'embed_oembed_html', $cache, $url, $attr, $post_ID );
     254            }
     255        }
     256
     257        /**
     258         * Filters whether to inspect the given URL for discoverable link tags.
     259         *
     260         * @since 2.9.0
     261         * @since 4.4.0 The default value changed to true.
     262         *
     263         * @see WP_oEmbed::discover()
     264         *
     265         * @param bool $enable Whether to enable `<link>` tag discovery. Default true.
     266         */
     267        $attr['discover'] = apply_filters( 'embed_oembed_discover', true );
     268
     269        // Use oEmbed to get the HTML.
     270        $html = wp_oembed_get( $url, $attr );
     271
     272        if ( $post_ID ) {
    258273            if ( $html ) {
    259274                update_post_meta( $post_ID, $cachekey, $html );
     
    262277                update_post_meta( $post_ID, $cachekey, '{{unknown}}' );
    263278            }
    264 
    265             // If there was a result, return it
    266             if ( $html ) {
    267                 /** This filter is documented in wp-includes/class-wp-embed.php */
    268                 return apply_filters( 'embed_oembed_html', $html, $url, $attr, $post_ID );
    269             }
     279        } else {
     280            $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
     281
     282            if ( $has_kses ) {
     283                // Prevent KSES from corrupting JSON in post_content.
     284                kses_remove_filters();
     285            }
     286
     287            wp_insert_post( wp_slash( array(
     288                'post_name'    => $key_suffix,
     289                'post_content' => $html ? $html : '{{unknown}}',
     290                'post_status'  => 'publish',
     291                'post_type'    => 'oembed_cache',
     292            ) ) );
     293
     294            if ( $has_kses ) {
     295                kses_init_filters();
     296            }
     297        }
     298
     299        // If there was a result, return it.
     300        if ( $html ) {
     301            /** This filter is documented in wp-includes/class-wp-embed.php */
     302            return apply_filters( 'embed_oembed_html', $html, $url, $attr, $post_ID );
    270303        }
    271304
     
    383416        return apply_filters( 'embed_maybe_make_link', $output, $url );
    384417    }
     418
     419    /**
     420     * Find the oEmbed cache post ID for a given cache key.
     421     *
     422     * @since 4.9.0
     423     *
     424     * @param string $cache_key oEmbed cache key.
     425     * @return int|null Post ID on success, null on failure.
     426     */
     427    public function find_oembed_post_id( $cache_key ) {
     428        $cache_group    = 'oembed_cache_post';
     429        $oembed_post_id = wp_cache_get( $cache_key, $cache_group );
     430
     431        if ( $oembed_post_id && 'oembed_cache' === get_post_type( $oembed_post_id ) ) {
     432            return $oembed_post_id;
     433        }
     434
     435        $oembed_post_query = new WP_Query( array(
     436            'post_type'              => 'oembed_cache',
     437            'post_status'            => 'publish',
     438            'name'                   => $cache_key,
     439            'posts_per_page'         => 1,
     440            'no_found_rows'          => true,
     441            'cache_results'          => true,
     442            'update_post_meta_cache' => false,
     443            'update_post_term_cache' => false,
     444            'lazy_load_term_meta'    => false,
     445        ) );
     446
     447        if ( ! empty( $oembed_post_query->posts ) ) {
     448            // Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed.
     449            $oembed_post_id = $oembed_post_query->posts[0]->ID;
     450            wp_cache_set( $cache_key, $oembed_post_id, $cache_group );
     451
     452            return $oembed_post_id;
     453        }
     454
     455        return null;
     456    }
    385457}
  • trunk/src/wp-includes/post.php

    r41642 r41651  
    194194            'read_private_posts' => 'customize',
    195195        ),
     196    ) );
     197
     198    register_post_type( 'oembed_cache', array(
     199        'labels' => array(
     200            'name'          => __( 'oEmbed Responses' ),
     201            'singular_name' => __( 'oEmbed Response' ),
     202        ),
     203        'public'           => false,
     204        'hierarchical'     => false,
     205        'rewrite'          => false,
     206        'query_var'        => false,
     207        'delete_with_user' => false,
     208        'can_export'       => false,
     209        '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
     210        'supports'         => array(),
    196211    ) );
    197212
     
    30043019 *     @type array  $post_category         Array of category names, slugs, or IDs.
    30053020 *                                         Defaults to value of the 'default_category' option.
    3006  *     @type array  $tags_input            Array of tag names, slugs, or IDs. Default empty. 
     3021 *     @type array  $tags_input            Array of tag names, slugs, or IDs. Default empty.
    30073022 *     @type array  $tax_input             Array of taxonomy terms keyed by their taxonomy name. Default empty.
    30083023 *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
  • trunk/tests/phpunit/tests/external-http/oembed.php

    r35178 r41651  
    3636        global $wp_embed;
    3737        $out = $wp_embed->autoembed( 'https://www.youtube.com/embed/QcIy9NiNbmo' );
    38         $this->assertContains( 'https://youtube.com/watch?v=QcIy9NiNbmo', $out );
     38        $this->assertContains( 'https://www.youtube.com/embed/QcIy9NiNbmo?feature=oembed', $out );
    3939    }
    4040
     
    4242        global $wp_embed;
    4343        $out = $wp_embed->autoembed( 'https://www.youtube.com/v/QcIy9NiNbmo' );
    44         $this->assertContains( 'https://youtube.com/watch?v=QcIy9NiNbmo', $out );
     44        $this->assertContains( 'https://www.youtube.com/embed/QcIy9NiNbmo?feature=oembed', $out );
    4545    }
    4646}
  • trunk/tests/phpunit/tests/oembed/WpEmbed.php

    r38762 r41651  
    162162    }
    163163
    164     public function test_shortcode_should_cache_data_in_post_meta_for_known_post() {
    165         $GLOBALS['post'] = $this->factory()->post->create_and_get();
     164    public function test_shortcode_should_get_cached_data_from_post_meta_for_known_post() {
     165        global $post;
     166
     167        $post = $this->factory()->post->create_and_get();
    166168        $url             = 'https://example.com/';
    167169        $expected        = '<b>Embedded content</b>';
    168170        $key_suffix      = md5( $url . serialize( wp_embed_defaults( $url ) ) );
    169171        $cachekey        = '_oembed_' . $key_suffix;
    170         $cachekey_time   = '_oembed_time_' . $key_suffix;
     172
     173        add_post_meta( $post->ID, $cachekey, $expected );
    171174
    172175        add_filter( 'pre_oembed_result', array( $this, '_pre_oembed_result_callback' ) );
     
    174177        remove_filter( 'pre_oembed_result', array( $this, '_pre_oembed_result_callback' ) );
    175178
     179        $actual_2 = $this->wp_embed->shortcode( array(), $url );
     180
     181        $cached = get_post_meta( $post->ID, $cachekey, true );
     182
     183        // Cleanup.
     184        unset( $post );
     185
    176186        $this->assertEquals( $expected, $actual );
    177 
    178         $this->assertEquals( $expected, get_post_meta( $GLOBALS['post']->ID, $cachekey, true ) );
    179         $this->assertNotEmpty( get_post_meta( $GLOBALS['post']->ID, $cachekey_time, true ) );
    180 
    181         // Result should be cached.
    182         $actual = $this->wp_embed->shortcode( array(), $url );
    183         $this->assertEquals( $expected, $actual );
    184     }
    185 
    186     public function test_shortcode_should_cache_failure_in_post_meta_for_known_post() {
    187         $GLOBALS['post'] = $this->factory()->post->create_and_get();
     187        $this->assertEquals( $expected, $actual_2 );
     188        $this->assertEquals( $expected, $cached );
     189    }
     190
     191    public function test_shortcode_should_get_cached_failure_from_post_meta_for_known_post() {
     192        global $post;
     193
     194        $post = $this->factory()->post->create_and_get();
    188195        $url             = 'https://example.com/';
    189196        $expected        = '<a href="' . esc_url( $url ) . '">' . esc_html( $url ) . '</a>';
     
    192199        $cachekey_time   = '_oembed_time_' . $key_suffix;
    193200
     201        add_post_meta( $post->ID, $cachekey, '{{unknown}}' );
     202        add_post_meta( $post->ID, $cachekey_time, 0 );
     203
    194204        add_filter( 'pre_oembed_result', '__return_empty_string' );
    195205        $actual = $this->wp_embed->shortcode( array(), $url );
    196206        remove_filter( 'pre_oembed_result', '__return_empty_string' );
    197207
     208        // Result should be cached.
     209        $actual_2 = $this->wp_embed->shortcode( array(), $url );
     210
     211        $cached = get_post_meta( $post->ID, $cachekey, true );
     212        $cached_time = get_post_meta( $post->ID, $cachekey_time, true );
     213
     214        // Cleanup.
     215        unset( $post );
     216
    198217        $this->assertEquals( $expected, $actual );
    199 
    200         $this->assertEquals( '{{unknown}}', get_post_meta( $GLOBALS['post']->ID, $cachekey, true ) );
    201         $this->assertEmpty( get_post_meta( $GLOBALS['post']->ID, $cachekey_time, true ) );
     218        $this->assertEquals( '{{unknown}}', $cached );
     219        $this->assertEmpty( $cached_time );
     220        $this->assertEquals( $expected, $actual_2 );
     221    }
     222
     223    /**
     224     * @ticket 34115
     225     */
     226    public function test_shortcode_should_cache_data_in_custom_post() {
     227        $url        = 'https://example.com/';
     228        $expected   = '<b>Embedded content</b>';
     229        $key_suffix = md5( $url . serialize( wp_embed_defaults( $url ) ) );
     230
     231        add_filter( 'pre_oembed_result', array( $this, '_pre_oembed_result_callback' ) );
     232        $actual = $this->wp_embed->shortcode( array(), $url );
     233        remove_filter( 'pre_oembed_result', array( $this, '_pre_oembed_result_callback' ) );
     234
     235        $oembed_post_id = $this->wp_embed->find_oembed_post_id( $key_suffix );
     236        $post_content = get_post( $oembed_post_id )->post_content;
    202237
    203238        // Result should be cached.
    204         $actual = $this->wp_embed->shortcode( array(), $url );
     239        $actual_2 = $this->wp_embed->shortcode( array(), $url );
     240
     241        wp_delete_post( $oembed_post_id );
     242
     243        $this->assertNotNull( $oembed_post_id );
     244        $this->assertEquals( $expected, $post_content );
    205245        $this->assertEquals( $expected, $actual );
     246        $this->assertEquals( $expected, $actual_2 );
     247    }
     248
     249    /**
     250     * @ticket 34115
     251     */
     252    public function test_shortcode_should_cache_failure_in_custom_post() {
     253        $url        = 'https://example.com/';
     254        $expected   = '<a href="' . esc_url( $url ) . '">' . esc_html( $url ) . '</a>';
     255        $key_suffix = md5( $url . serialize( wp_embed_defaults( $url ) ) );
     256
     257        add_filter( 'pre_oembed_result', '__return_empty_string' );
     258        $actual = $this->wp_embed->shortcode( array(), $url );
     259        remove_filter( 'pre_oembed_result', '__return_empty_string' );
     260
     261        $oembed_post_id = $this->wp_embed->find_oembed_post_id( $key_suffix );
     262        $post_content = get_post( $oembed_post_id )->post_content;
     263
     264        // Result should be cached.
     265        $actual_2 = $this->wp_embed->shortcode( array(), $url );
     266
     267        wp_delete_post( $oembed_post_id );
     268
     269        $this->assertEquals( $expected, $actual );
     270        $this->assertEquals( $expected, $actual_2 );
     271        $this->assertNotNull( $oembed_post_id );
     272        $this->assertEquals( '{{unknown}}', $post_content );
    206273    }
    207274
Note: See TracChangeset for help on using the changeset viewer.