9 class WP_Embed { |
9 class WP_Embed { |
10 public $handlers = array(); |
10 public $handlers = array(); |
11 public $post_ID; |
11 public $post_ID; |
12 public $usecache = true; |
12 public $usecache = true; |
13 public $linkifunknown = true; |
13 public $linkifunknown = true; |
14 |
14 public $last_attr = array(); |
15 /** |
15 public $last_url = ''; |
16 * When an URL cannot be embedded, return false instead of returning a link |
16 |
17 * or the URL. Bypasses the 'embed_maybe_make_link' filter. |
17 /** |
|
18 * When a URL cannot be embedded, return false instead of returning a link |
|
19 * or the URL. |
|
20 * |
|
21 * Bypasses the {@see 'embed_maybe_make_link'} filter. |
|
22 * |
|
23 * @var bool |
18 */ |
24 */ |
19 public $return_false_on_fail = false; |
25 public $return_false_on_fail = false; |
20 |
26 |
21 /** |
27 /** |
22 * Constructor |
28 * Constructor |
23 */ |
29 */ |
24 public function __construct() { |
30 public function __construct() { |
25 // Hack to get the [embed] shortcode to run before wpautop() |
31 // Hack to get the [embed] shortcode to run before wpautop() |
26 add_filter( 'the_content', array( $this, 'run_shortcode' ), 8 ); |
32 add_filter( 'the_content', array( $this, 'run_shortcode' ), 8 ); |
|
33 add_filter( 'widget_text_content', array( $this, 'run_shortcode' ), 8 ); |
27 |
34 |
28 // Shortcode placeholder for strip_shortcodes() |
35 // Shortcode placeholder for strip_shortcodes() |
29 add_shortcode( 'embed', '__return_false' ); |
36 add_shortcode( 'embed', '__return_false' ); |
30 |
37 |
31 // Attempts to embed all URLs in a post |
38 // Attempts to embed all URLs in a post |
32 add_filter( 'the_content', array( $this, 'autoembed' ), 8 ); |
39 add_filter( 'the_content', array( $this, 'autoembed' ), 8 ); |
33 |
40 add_filter( 'widget_text_content', array( $this, 'autoembed' ), 8 ); |
34 // After a post is saved, cache oEmbed items via AJAX |
41 |
|
42 // After a post is saved, cache oEmbed items via Ajax |
35 add_action( 'edit_form_advanced', array( $this, 'maybe_run_ajax_cache' ) ); |
43 add_action( 'edit_form_advanced', array( $this, 'maybe_run_ajax_cache' ) ); |
|
44 add_action( 'edit_page_form', array( $this, 'maybe_run_ajax_cache' ) ); |
36 } |
45 } |
37 |
46 |
38 /** |
47 /** |
39 * Process the [embed] shortcode. |
48 * Process the [embed] shortcode. |
40 * |
49 * |
41 * Since the [embed] shortcode needs to be run earlier than other shortcodes, |
50 * Since the [embed] shortcode needs to be run earlier than other shortcodes, |
42 * this function removes all existing shortcodes, registers the [embed] shortcode, |
51 * this function removes all existing shortcodes, registers the [embed] shortcode, |
43 * calls {@link do_shortcode()}, and then re-registers the old shortcodes. |
52 * calls do_shortcode(), and then re-registers the old shortcodes. |
44 * |
53 * |
45 * @uses $shortcode_tags |
54 * @global array $shortcode_tags |
46 * |
55 * |
47 * @param string $content Content to parse |
56 * @param string $content Content to parse |
48 * @return string Content with shortcode parsed |
57 * @return string Content with shortcode parsed |
49 */ |
58 */ |
50 public function run_shortcode( $content ) { |
59 public function run_shortcode( $content ) { |
83 </script> |
92 </script> |
84 <?php |
93 <?php |
85 } |
94 } |
86 |
95 |
87 /** |
96 /** |
88 * Register an embed handler. Do not use this function directly, use {@link wp_embed_register_handler()} instead. |
97 * Registers an embed handler. |
|
98 * |
|
99 * Do not use this function directly, use wp_embed_register_handler() instead. |
|
100 * |
89 * This function should probably also only be used for sites that do not support oEmbed. |
101 * This function should probably also only be used for sites that do not support oEmbed. |
90 * |
102 * |
91 * @param string $id An internal ID/name for the handler. Needs to be unique. |
103 * @param string $id An internal ID/name for the handler. Needs to be unique. |
92 * @param string $regex The regex that will be used to see if this handler should be used for a URL. |
104 * @param string $regex The regex that will be used to see if this handler should be used for a URL. |
93 * @param callback $callback The callback function that will be called if the regex is matched. |
105 * @param callable $callback The callback function that will be called if the regex is matched. |
94 * @param int $priority Optional. Used to specify the order in which the registered handlers will be tested (default: 10). Lower numbers correspond with earlier testing, and handlers with the same priority are tested in the order in which they were added to the action. |
106 * @param int $priority Optional. Used to specify the order in which the registered handlers will be tested (default: 10). Lower numbers correspond with earlier testing, and handlers with the same priority are tested in the order in which they were added to the action. |
95 */ |
107 */ |
96 public function register_handler( $id, $regex, $callback, $priority = 10 ) { |
108 public function register_handler( $id, $regex, $callback, $priority = 10 ) { |
97 $this->handlers[$priority][$id] = array( |
109 $this->handlers[$priority][$id] = array( |
98 'regex' => $regex, |
110 'regex' => $regex, |
99 'callback' => $callback, |
111 'callback' => $callback, |
100 ); |
112 ); |
101 } |
113 } |
102 |
114 |
103 /** |
115 /** |
104 * Unregister a previously registered embed handler. Do not use this function directly, use {@link wp_embed_unregister_handler()} instead. |
116 * Unregisters a previously-registered embed handler. |
|
117 * |
|
118 * Do not use this function directly, use wp_embed_unregister_handler() instead. |
105 * |
119 * |
106 * @param string $id The handler ID that should be removed. |
120 * @param string $id The handler ID that should be removed. |
107 * @param int $priority Optional. The priority of the handler to be removed (default: 10). |
121 * @param int $priority Optional. The priority of the handler to be removed (default: 10). |
108 */ |
122 */ |
109 public function unregister_handler( $id, $priority = 10 ) { |
123 public function unregister_handler( $id, $priority = 10 ) { |
110 if ( isset($this->handlers[$priority][$id]) ) |
124 unset( $this->handlers[ $priority ][ $id ] ); |
111 unset($this->handlers[$priority][$id]); |
125 } |
112 } |
126 |
113 |
127 /** |
114 /** |
128 * The do_shortcode() callback function. |
115 * The {@link do_shortcode()} callback function. |
129 * |
116 * |
130 * Attempts to convert a URL into embed HTML. Starts by checking the URL against the regex of |
117 * Attempts to convert a URL into embed HTML. Starts by checking the URL against the regex of the registered embed handlers. |
131 * the registered embed handlers. If none of the regex matches and it's enabled, then the URL |
118 * If none of the regex matches and it's enabled, then the URL will be given to the {@link WP_oEmbed} class. |
132 * will be given to the WP_oEmbed class. |
119 * |
133 * |
120 * @param array $attr { |
134 * @param array $attr { |
121 * Shortcode attributes. Optional. |
135 * Shortcode attributes. Optional. |
122 * |
136 * |
123 * @type int $width Width of the embed in pixels. |
137 * @type int $width Width of the embed in pixels. |
166 } |
185 } |
167 } |
186 } |
168 } |
187 } |
169 |
188 |
170 $post_ID = ( ! empty( $post->ID ) ) ? $post->ID : null; |
189 $post_ID = ( ! empty( $post->ID ) ) ? $post->ID : null; |
171 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 ) ) { |
172 $post_ID = $this->post_ID; |
193 $post_ID = $this->post_ID; |
173 |
194 } |
174 // Unknown URL format. Let oEmbed have a go. |
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 |
175 if ( $post_ID ) { |
218 if ( $post_ID ) { |
176 |
|
177 // Check for a cached result (stored in the post meta) |
|
178 $key_suffix = md5( $url . serialize( $attr ) ); |
|
179 $cachekey = '_oembed_' . $key_suffix; |
|
180 $cachekey_time = '_oembed_time_' . $key_suffix; |
|
181 |
|
182 /** |
|
183 * Filter the oEmbed TTL value (time to live). |
|
184 * |
|
185 * @since 4.0.0 |
|
186 * |
|
187 * @param int $time Time to live (in seconds). |
|
188 * @param string $url The attempted embed URL. |
|
189 * @param array $attr An array of shortcode attributes. |
|
190 * @param int $post_ID Post ID. |
|
191 */ |
|
192 $ttl = apply_filters( 'oembed_ttl', DAY_IN_SECONDS, $url, $attr, $post_ID ); |
|
193 |
|
194 $cache = get_post_meta( $post_ID, $cachekey, true ); |
219 $cache = get_post_meta( $post_ID, $cachekey, true ); |
195 $cache_time = get_post_meta( $post_ID, $cachekey_time, true ); |
220 $cache_time = get_post_meta( $post_ID, $cachekey_time, true ); |
196 |
221 |
197 if ( ! $cache_time ) { |
222 if ( ! $cache_time ) { |
198 $cache_time = 0; |
223 $cache_time = 0; |
199 } |
224 } |
200 |
225 } elseif ( $cached_post_id ) { |
201 $cached_recently = ( time() - $cache_time ) < $ttl; |
226 $cached_post = get_post( $cached_post_id ); |
202 |
227 |
203 if ( $this->usecache || $cached_recently ) { |
228 $cache = $cached_post->post_content; |
204 // Failures are cached. Serve one if we're using the cache. |
229 $cache_time = strtotime( $cached_post->post_modified_gmt ); |
205 if ( '{{unknown}}' === $cache ) |
230 } |
206 return $this->maybe_make_link( $url ); |
231 |
207 |
232 $cached_recently = ( time() - $cache_time ) < $ttl; |
208 if ( ! empty( $cache ) ) { |
233 |
209 /** |
234 if ( $this->usecache || $cached_recently ) { |
210 * Filter the cached oEmbed HTML. |
235 // Failures are cached. Serve one if we're using the cache. |
211 * |
236 if ( '{{unknown}}' === $cache ) { |
212 * @since 2.9.0 |
237 return $this->maybe_make_link( $url ); |
213 * |
238 } |
214 * @see WP_Embed::shortcode() |
239 |
215 * |
240 if ( ! empty( $cache ) ) { |
216 * @param mixed $cache The cached HTML result, stored in post meta. |
241 /** |
217 * @param string $url The attempted embed URL. |
242 * Filters the cached oEmbed HTML. |
218 * @param array $attr An array of shortcode attributes. |
243 * |
219 * @param int $post_ID Post ID. |
244 * @since 2.9.0 |
220 */ |
245 * |
221 return apply_filters( 'embed_oembed_html', $cache, $url, $attr, $post_ID ); |
246 * @see WP_Embed::shortcode() |
222 } |
247 * |
223 } |
248 * @param mixed $cache The cached HTML result, stored in post meta. |
224 |
249 * @param string $url The attempted embed URL. |
225 /** |
250 * @param array $attr An array of shortcode attributes. |
226 * Filter whether to inspect the given URL for discoverable link tags. |
251 * @param int $post_ID Post ID. |
227 * |
252 */ |
228 * @since 2.9.0 |
253 return apply_filters( 'embed_oembed_html', $cache, $url, $attr, $post_ID ); |
229 * |
254 } |
230 * @see WP_oEmbed::discover() |
255 } |
231 * |
256 |
232 * @param bool $enable Whether to enable `<link>` tag discovery. Default false. |
257 /** |
233 */ |
258 * Filters whether to inspect the given URL for discoverable link tags. |
234 $attr['discover'] = ( apply_filters( 'embed_oembed_discover', false ) && author_can( $post_ID, 'unfiltered_html' ) ); |
259 * |
235 |
260 * @since 2.9.0 |
236 // Use oEmbed to get the HTML |
261 * @since 4.4.0 The default value changed to true. |
237 $html = wp_oembed_get( $url, $attr ); |
262 * |
238 |
263 * @see WP_oEmbed::discover() |
239 // Maybe cache the result |
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 ) { |
240 if ( $html ) { |
273 if ( $html ) { |
241 update_post_meta( $post_ID, $cachekey, $html ); |
274 update_post_meta( $post_ID, $cachekey, $html ); |
242 update_post_meta( $post_ID, $cachekey_time, time() ); |
275 update_post_meta( $post_ID, $cachekey_time, time() ); |
243 } elseif ( ! $cache ) { |
276 } elseif ( ! $cache ) { |
244 update_post_meta( $post_ID, $cachekey, '{{unknown}}' ); |
277 update_post_meta( $post_ID, $cachekey, '{{unknown}}' ); |
245 } |
278 } |
246 |
279 } else { |
247 // If there was a result, return it |
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 $insert_post_args = array( |
|
288 'post_name' => $key_suffix, |
|
289 'post_status' => 'publish', |
|
290 'post_type' => 'oembed_cache', |
|
291 ); |
|
292 |
248 if ( $html ) { |
293 if ( $html ) { |
249 /** This filter is documented in wp-includes/class-wp-embed.php */ |
294 if ( $cached_post_id ) { |
250 return apply_filters( 'embed_oembed_html', $html, $url, $attr, $post_ID ); |
295 wp_update_post( wp_slash( array( |
251 } |
296 'ID' => $cached_post_id, |
|
297 'post_content' => $html, |
|
298 ) ) ); |
|
299 } else { |
|
300 wp_insert_post( wp_slash( array_merge( |
|
301 $insert_post_args, |
|
302 array( |
|
303 'post_content' => $html, |
|
304 ) |
|
305 ) ) ); |
|
306 } |
|
307 } elseif ( ! $cache ) { |
|
308 wp_insert_post( wp_slash( array_merge( |
|
309 $insert_post_args, |
|
310 array( |
|
311 'post_content' => '{{unknown}}', |
|
312 ) |
|
313 ) ) ); |
|
314 } |
|
315 |
|
316 if ( $has_kses ) { |
|
317 kses_init_filters(); |
|
318 } |
|
319 } |
|
320 |
|
321 // If there was a result, return it. |
|
322 if ( $html ) { |
|
323 /** This filter is documented in wp-includes/class-wp-embed.php */ |
|
324 return apply_filters( 'embed_oembed_html', $html, $url, $attr, $post_ID ); |
252 } |
325 } |
253 |
326 |
254 // Still unknown |
327 // Still unknown |
255 return $this->maybe_make_link( $url ); |
328 return $this->maybe_make_link( $url ); |
256 } |
329 } |
302 $this->usecache = true; |
375 $this->usecache = true; |
303 } |
376 } |
304 } |
377 } |
305 |
378 |
306 /** |
379 /** |
307 * Passes any unlinked URLs that are on their own line to {@link WP_Embed::shortcode()} for potential embedding. |
380 * Passes any unlinked URLs that are on their own line to WP_Embed::shortcode() for potential embedding. |
308 * |
381 * |
309 * @uses WP_Embed::autoembed_callback() |
382 * @see WP_Embed::autoembed_callback() |
310 * |
383 * |
311 * @param string $content The content to be searched. |
384 * @param string $content The content to be searched. |
312 * @return string Potentially modified $content. |
385 * @return string Potentially modified $content. |
313 */ |
386 */ |
314 public function autoembed( $content ) { |
387 public function autoembed( $content ) { |
315 return preg_replace_callback( '|^(\s*)(https?://[^\s"]+)(\s*)$|im', array( $this, 'autoembed_callback' ), $content ); |
388 // Replace line breaks from all HTML elements with placeholders. |
316 } |
389 $content = wp_replace_in_html_tags( $content, array( "\n" => '<!-- wp-line-break -->' ) ); |
317 |
390 |
318 /** |
391 if ( preg_match( '#(^|\s|>)https?://#i', $content ) ) { |
319 * Callback function for {@link WP_Embed::autoembed()}. |
392 // Find URLs on their own line. |
|
393 $content = preg_replace_callback( '|^(\s*)(https?://[^\s<>"]+)(\s*)$|im', array( $this, 'autoembed_callback' ), $content ); |
|
394 // Find URLs in their own paragraph. |
|
395 $content = preg_replace_callback( '|(<p(?: [^>]*)?>\s*)(https?://[^\s<>"]+)(\s*<\/p>)|i', array( $this, 'autoembed_callback' ), $content ); |
|
396 } |
|
397 |
|
398 // Put the line breaks back. |
|
399 return str_replace( '<!-- wp-line-break -->', "\n", $content ); |
|
400 } |
|
401 |
|
402 /** |
|
403 * Callback function for WP_Embed::autoembed(). |
320 * |
404 * |
321 * @param array $match A regex match array. |
405 * @param array $match A regex match array. |
322 * @return string The embed HTML on success, otherwise the original URL. |
406 * @return string The embed HTML on success, otherwise the original URL. |
323 */ |
407 */ |
324 public function autoembed_callback( $match ) { |
408 public function autoembed_callback( $match ) { |
342 } |
426 } |
343 |
427 |
344 $output = ( $this->linkifunknown ) ? '<a href="' . esc_url($url) . '">' . esc_html($url) . '</a>' : $url; |
428 $output = ( $this->linkifunknown ) ? '<a href="' . esc_url($url) . '">' . esc_html($url) . '</a>' : $url; |
345 |
429 |
346 /** |
430 /** |
347 * Filter the returned, maybe-linked embed URL. |
431 * Filters the returned, maybe-linked embed URL. |
348 * |
432 * |
349 * @since 2.9.0 |
433 * @since 2.9.0 |
350 * |
434 * |
351 * @param string $output The linked or original URL. |
435 * @param string $output The linked or original URL. |
352 * @param string $url The original URL. |
436 * @param string $url The original URL. |
353 */ |
437 */ |
354 return apply_filters( 'embed_maybe_make_link', $output, $url ); |
438 return apply_filters( 'embed_maybe_make_link', $output, $url ); |
355 } |
439 } |
|
440 |
|
441 /** |
|
442 * Find the oEmbed cache post ID for a given cache key. |
|
443 * |
|
444 * @since 4.9.0 |
|
445 * |
|
446 * @param string $cache_key oEmbed cache key. |
|
447 * @return int|null Post ID on success, null on failure. |
|
448 */ |
|
449 public function find_oembed_post_id( $cache_key ) { |
|
450 $cache_group = 'oembed_cache_post'; |
|
451 $oembed_post_id = wp_cache_get( $cache_key, $cache_group ); |
|
452 |
|
453 if ( $oembed_post_id && 'oembed_cache' === get_post_type( $oembed_post_id ) ) { |
|
454 return $oembed_post_id; |
|
455 } |
|
456 |
|
457 $oembed_post_query = new WP_Query( array( |
|
458 'post_type' => 'oembed_cache', |
|
459 'post_status' => 'publish', |
|
460 'name' => $cache_key, |
|
461 'posts_per_page' => 1, |
|
462 'no_found_rows' => true, |
|
463 'cache_results' => true, |
|
464 'update_post_meta_cache' => false, |
|
465 'update_post_term_cache' => false, |
|
466 'lazy_load_term_meta' => false, |
|
467 ) ); |
|
468 |
|
469 if ( ! empty( $oembed_post_query->posts ) ) { |
|
470 // Note: 'fields'=>'ids' is not being used in order to cache the post object as it will be needed. |
|
471 $oembed_post_id = $oembed_post_query->posts[0]->ID; |
|
472 wp_cache_set( $cache_key, $oembed_post_id, $cache_group ); |
|
473 |
|
474 return $oembed_post_id; |
|
475 } |
|
476 |
|
477 return null; |
|
478 } |
356 } |
479 } |
357 $GLOBALS['wp_embed'] = new WP_Embed(); |
|