balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-embed.php (15954B)


      1 <?php
      2 /**
      3  * API for easily embedding rich media such as videos and images into content.
      4  *
      5  * @package WordPress
      6  * @subpackage Embed
      7  * @since 2.9.0
      8  */
      9 class WP_Embed {
     10 	public $handlers = array();
     11 	public $post_ID;
     12 	public $usecache      = true;
     13 	public $linkifunknown = true;
     14 	public $last_attr     = array();
     15 	public $last_url      = '';
     16 
     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
     24 	 */
     25 	public $return_false_on_fail = false;
     26 
     27 	/**
     28 	 * Constructor
     29 	 */
     30 	public function __construct() {
     31 		// Hack to get the [embed] shortcode to run before wpautop().
     32 		add_filter( 'the_content', array( $this, 'run_shortcode' ), 8 );
     33 		add_filter( 'widget_text_content', array( $this, 'run_shortcode' ), 8 );
     34 		add_filter( 'widget_block_content', array( $this, 'run_shortcode' ), 8 );
     35 
     36 		// Shortcode placeholder for strip_shortcodes().
     37 		add_shortcode( 'embed', '__return_false' );
     38 
     39 		// Attempts to embed all URLs in a post.
     40 		add_filter( 'the_content', array( $this, 'autoembed' ), 8 );
     41 		add_filter( 'widget_text_content', array( $this, 'autoembed' ), 8 );
     42 		add_filter( 'widget_block_content', array( $this, 'autoembed' ), 8 );
     43 
     44 		// After a post is saved, cache oEmbed items via Ajax.
     45 		add_action( 'edit_form_advanced', array( $this, 'maybe_run_ajax_cache' ) );
     46 		add_action( 'edit_page_form', array( $this, 'maybe_run_ajax_cache' ) );
     47 	}
     48 
     49 	/**
     50 	 * Process the [embed] shortcode.
     51 	 *
     52 	 * Since the [embed] shortcode needs to be run earlier than other shortcodes,
     53 	 * this function removes all existing shortcodes, registers the [embed] shortcode,
     54 	 * calls do_shortcode(), and then re-registers the old shortcodes.
     55 	 *
     56 	 * @global array $shortcode_tags
     57 	 *
     58 	 * @param string $content Content to parse
     59 	 * @return string Content with shortcode parsed
     60 	 */
     61 	public function run_shortcode( $content ) {
     62 		global $shortcode_tags;
     63 
     64 		// Back up current registered shortcodes and clear them all out.
     65 		$orig_shortcode_tags = $shortcode_tags;
     66 		remove_all_shortcodes();
     67 
     68 		add_shortcode( 'embed', array( $this, 'shortcode' ) );
     69 
     70 		// Do the shortcode (only the [embed] one is registered).
     71 		$content = do_shortcode( $content, true );
     72 
     73 		// Put the original shortcodes back.
     74 		$shortcode_tags = $orig_shortcode_tags;
     75 
     76 		return $content;
     77 	}
     78 
     79 	/**
     80 	 * If a post/page was saved, then output JavaScript to make
     81 	 * an Ajax request that will call WP_Embed::cache_oembed().
     82 	 */
     83 	public function maybe_run_ajax_cache() {
     84 		$post = get_post();
     85 
     86 		if ( ! $post || empty( $_GET['message'] ) ) {
     87 			return;
     88 		}
     89 		?>
     90 <script type="text/javascript">
     91 	jQuery(document).ready(function($){
     92 		$.get("<?php echo esc_url( admin_url( 'admin-ajax.php', 'relative' ) ) . '?action=oembed-cache&post=' . $post->ID; ?>");
     93 	});
     94 </script>
     95 		<?php
     96 	}
     97 
     98 	/**
     99 	 * Registers an embed handler.
    100 	 *
    101 	 * Do not use this function directly, use wp_embed_register_handler() instead.
    102 	 *
    103 	 * This function should probably also only be used for sites that do not support oEmbed.
    104 	 *
    105 	 * @param string   $id       An internal ID/name for the handler. Needs to be unique.
    106 	 * @param string   $regex    The regex that will be used to see if this handler should be used for a URL.
    107 	 * @param callable $callback The callback function that will be called if the regex is matched.
    108 	 * @param int      $priority Optional. Used to specify the order in which the registered handlers will be tested.
    109 	 *                           Lower numbers correspond with earlier testing, and handlers with the same priority are
    110 	 *                           tested in the order in which they were added to the action. Default 10.
    111 	 */
    112 	public function register_handler( $id, $regex, $callback, $priority = 10 ) {
    113 		$this->handlers[ $priority ][ $id ] = array(
    114 			'regex'    => $regex,
    115 			'callback' => $callback,
    116 		);
    117 	}
    118 
    119 	/**
    120 	 * Unregisters a previously-registered embed handler.
    121 	 *
    122 	 * Do not use this function directly, use wp_embed_unregister_handler() instead.
    123 	 *
    124 	 * @param string $id       The handler ID that should be removed.
    125 	 * @param int    $priority Optional. The priority of the handler to be removed (default: 10).
    126 	 */
    127 	public function unregister_handler( $id, $priority = 10 ) {
    128 		unset( $this->handlers[ $priority ][ $id ] );
    129 	}
    130 
    131 	/**
    132 	 * Returns embed HTML for a given URL from embed handlers.
    133 	 *
    134 	 * Attempts to convert a URL into embed HTML by checking the URL
    135 	 * against the regex of the registered embed handlers.
    136 	 *
    137 	 * @since 5.5.0
    138 	 *
    139 	 * @param array  $attr {
    140 	 *     Shortcode attributes. Optional.
    141 	 *
    142 	 *     @type int $width  Width of the embed in pixels.
    143 	 *     @type int $height Height of the embed in pixels.
    144 	 * }
    145 	 * @param string $url The URL attempting to be embedded.
    146 	 * @return string|false The embed HTML on success, false otherwise.
    147 	 */
    148 	public function get_embed_handler_html( $attr, $url ) {
    149 		$rawattr = $attr;
    150 		$attr    = wp_parse_args( $attr, wp_embed_defaults( $url ) );
    151 
    152 		ksort( $this->handlers );
    153 		foreach ( $this->handlers as $priority => $handlers ) {
    154 			foreach ( $handlers as $id => $handler ) {
    155 				if ( preg_match( $handler['regex'], $url, $matches ) && is_callable( $handler['callback'] ) ) {
    156 					$return = call_user_func( $handler['callback'], $matches, $attr, $url, $rawattr );
    157 					if ( false !== $return ) {
    158 						/**
    159 						 * Filters the returned embed HTML.
    160 						 *
    161 						 * @since 2.9.0
    162 						 *
    163 						 * @see WP_Embed::shortcode()
    164 						 *
    165 						 * @param string|false $return The HTML result of the shortcode, or false on failure.
    166 						 * @param string       $url    The embed URL.
    167 						 * @param array        $attr   An array of shortcode attributes.
    168 						 */
    169 						return apply_filters( 'embed_handler_html', $return, $url, $attr );
    170 					}
    171 				}
    172 			}
    173 		}
    174 
    175 		return false;
    176 	}
    177 
    178 	/**
    179 	 * The do_shortcode() callback function.
    180 	 *
    181 	 * Attempts to convert a URL into embed HTML. Starts by checking the URL against the regex of
    182 	 * the registered embed handlers. If none of the regex matches and it's enabled, then the URL
    183 	 * will be given to the WP_oEmbed class.
    184 	 *
    185 	 * @param array  $attr {
    186 	 *     Shortcode attributes. Optional.
    187 	 *
    188 	 *     @type int $width  Width of the embed in pixels.
    189 	 *     @type int $height Height of the embed in pixels.
    190 	 * }
    191 	 * @param string $url The URL attempting to be embedded.
    192 	 * @return string|false The embed HTML on success, otherwise the original URL.
    193 	 *                      `->maybe_make_link()` can return false on failure.
    194 	 */
    195 	public function shortcode( $attr, $url = '' ) {
    196 		$post = get_post();
    197 
    198 		if ( empty( $url ) && ! empty( $attr['src'] ) ) {
    199 			$url = $attr['src'];
    200 		}
    201 
    202 		$this->last_url = $url;
    203 
    204 		if ( empty( $url ) ) {
    205 			$this->last_attr = $attr;
    206 			return '';
    207 		}
    208 
    209 		$rawattr = $attr;
    210 		$attr    = wp_parse_args( $attr, wp_embed_defaults( $url ) );
    211 
    212 		$this->last_attr = $attr;
    213 
    214 		// KSES converts & into &amp; and we need to undo this.
    215 		// See https://core.trac.wordpress.org/ticket/11311
    216 		$url = str_replace( '&amp;', '&', $url );
    217 
    218 		// Look for known internal handlers.
    219 		$embed_handler_html = $this->get_embed_handler_html( $rawattr, $url );
    220 		if ( false !== $embed_handler_html ) {
    221 			return $embed_handler_html;
    222 		}
    223 
    224 		$post_ID = ( ! empty( $post->ID ) ) ? $post->ID : null;
    225 
    226 		// Potentially set by WP_Embed::cache_oembed().
    227 		if ( ! empty( $this->post_ID ) ) {
    228 			$post_ID = $this->post_ID;
    229 		}
    230 
    231 		// Check for a cached result (stored as custom post or in the post meta).
    232 		$key_suffix    = md5( $url . serialize( $attr ) );
    233 		$cachekey      = '_oembed_' . $key_suffix;
    234 		$cachekey_time = '_oembed_time_' . $key_suffix;
    235 
    236 		/**
    237 		 * Filters the oEmbed TTL value (time to live).
    238 		 *
    239 		 * @since 4.0.0
    240 		 *
    241 		 * @param int    $time    Time to live (in seconds).
    242 		 * @param string $url     The attempted embed URL.
    243 		 * @param array  $attr    An array of shortcode attributes.
    244 		 * @param int    $post_ID Post ID.
    245 		 */
    246 		$ttl = apply_filters( 'oembed_ttl', DAY_IN_SECONDS, $url, $attr, $post_ID );
    247 
    248 		$cache      = '';
    249 		$cache_time = 0;
    250 
    251 		$cached_post_id = $this->find_oembed_post_id( $key_suffix );
    252 
    253 		if ( $post_ID ) {
    254 			$cache      = get_post_meta( $post_ID, $cachekey, true );
    255 			$cache_time = get_post_meta( $post_ID, $cachekey_time, true );
    256 
    257 			if ( ! $cache_time ) {
    258 				$cache_time = 0;
    259 			}
    260 		} elseif ( $cached_post_id ) {
    261 			$cached_post = get_post( $cached_post_id );
    262 
    263 			$cache      = $cached_post->post_content;
    264 			$cache_time = strtotime( $cached_post->post_modified_gmt );
    265 		}
    266 
    267 		$cached_recently = ( time() - $cache_time ) < $ttl;
    268 
    269 		if ( $this->usecache || $cached_recently ) {
    270 			// Failures are cached. Serve one if we're using the cache.
    271 			if ( '{{unknown}}' === $cache ) {
    272 				return $this->maybe_make_link( $url );
    273 			}
    274 
    275 			if ( ! empty( $cache ) ) {
    276 				/**
    277 				 * Filters the cached oEmbed HTML.
    278 				 *
    279 				 * @since 2.9.0
    280 				 *
    281 				 * @see WP_Embed::shortcode()
    282 				 *
    283 				 * @param string|false $cache   The cached HTML result, stored in post meta.
    284 				 * @param string       $url     The attempted embed URL.
    285 				 * @param array        $attr    An array of shortcode attributes.
    286 				 * @param int          $post_ID Post ID.
    287 				 */
    288 				return apply_filters( 'embed_oembed_html', $cache, $url, $attr, $post_ID );
    289 			}
    290 		}
    291 
    292 		/**
    293 		 * Filters whether to inspect the given URL for discoverable link tags.
    294 		 *
    295 		 * @since 2.9.0
    296 		 * @since 4.4.0 The default value changed to true.
    297 		 *
    298 		 * @see WP_oEmbed::discover()
    299 		 *
    300 		 * @param bool $enable Whether to enable `<link>` tag discovery. Default true.
    301 		 */
    302 		$attr['discover'] = apply_filters( 'embed_oembed_discover', true );
    303 
    304 		// Use oEmbed to get the HTML.
    305 		$html = wp_oembed_get( $url, $attr );
    306 
    307 		if ( $post_ID ) {
    308 			if ( $html ) {
    309 				update_post_meta( $post_ID, $cachekey, $html );
    310 				update_post_meta( $post_ID, $cachekey_time, time() );
    311 			} elseif ( ! $cache ) {
    312 				update_post_meta( $post_ID, $cachekey, '{{unknown}}' );
    313 			}
    314 		} else {
    315 			$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
    316 
    317 			if ( $has_kses ) {
    318 				// Prevent KSES from corrupting JSON in post_content.
    319 				kses_remove_filters();
    320 			}
    321 
    322 			$insert_post_args = array(
    323 				'post_name'   => $key_suffix,
    324 				'post_status' => 'publish',
    325 				'post_type'   => 'oembed_cache',
    326 			);
    327 
    328 			if ( $html ) {
    329 				if ( $cached_post_id ) {
    330 					wp_update_post(
    331 						wp_slash(
    332 							array(
    333 								'ID'           => $cached_post_id,
    334 								'post_content' => $html,
    335 							)
    336 						)
    337 					);
    338 				} else {
    339 					wp_insert_post(
    340 						wp_slash(
    341 							array_merge(
    342 								$insert_post_args,
    343 								array(
    344 									'post_content' => $html,
    345 								)
    346 							)
    347 						)
    348 					);
    349 				}
    350 			} elseif ( ! $cache ) {
    351 				wp_insert_post(
    352 					wp_slash(
    353 						array_merge(
    354 							$insert_post_args,
    355 							array(
    356 								'post_content' => '{{unknown}}',
    357 							)
    358 						)
    359 					)
    360 				);
    361 			}
    362 
    363 			if ( $has_kses ) {
    364 				kses_init_filters();
    365 			}
    366 		}
    367 
    368 		// If there was a result, return it.
    369 		if ( $html ) {
    370 			/** This filter is documented in wp-includes/class-wp-embed.php */
    371 			return apply_filters( 'embed_oembed_html', $html, $url, $attr, $post_ID );
    372 		}
    373 
    374 		// Still unknown.
    375 		return $this->maybe_make_link( $url );
    376 	}
    377 
    378 	/**
    379 	 * Delete all oEmbed caches. Unused by core as of 4.0.0.
    380 	 *
    381 	 * @param int $post_ID Post ID to delete the caches for.
    382 	 */
    383 	public function delete_oembed_caches( $post_ID ) {
    384 		$post_metas = get_post_custom_keys( $post_ID );
    385 		if ( empty( $post_metas ) ) {
    386 			return;
    387 		}
    388 
    389 		foreach ( $post_metas as $post_meta_key ) {
    390 			if ( '_oembed_' === substr( $post_meta_key, 0, 8 ) ) {
    391 				delete_post_meta( $post_ID, $post_meta_key );
    392 			}
    393 		}
    394 	}
    395 
    396 	/**
    397 	 * Triggers a caching of all oEmbed results.
    398 	 *
    399 	 * @param int $post_ID Post ID to do the caching for.
    400 	 */
    401 	public function cache_oembed( $post_ID ) {
    402 		$post = get_post( $post_ID );
    403 
    404 		$post_types = get_post_types( array( 'show_ui' => true ) );
    405 
    406 		/**
    407 		 * Filters the array of post types to cache oEmbed results for.
    408 		 *
    409 		 * @since 2.9.0
    410 		 *
    411 		 * @param string[] $post_types Array of post type names to cache oEmbed results for. Defaults to post types with `show_ui` set to true.
    412 		 */
    413 		$cache_oembed_types = apply_filters( 'embed_cache_oembed_types', $post_types );
    414 
    415 		if ( empty( $post->ID ) || ! in_array( $post->post_type, $cache_oembed_types, true ) ) {
    416 			return;
    417 		}
    418 
    419 		// Trigger a caching.
    420 		if ( ! empty( $post->post_content ) ) {
    421 			$this->post_ID  = $post->ID;
    422 			$this->usecache = false;
    423 
    424 			$content = $this->run_shortcode( $post->post_content );
    425 			$this->autoembed( $content );
    426 
    427 			$this->usecache = true;
    428 		}
    429 	}
    430 
    431 	/**
    432 	 * Passes any unlinked URLs that are on their own line to WP_Embed::shortcode() for potential embedding.
    433 	 *
    434 	 * @see WP_Embed::autoembed_callback()
    435 	 *
    436 	 * @param string $content The content to be searched.
    437 	 * @return string Potentially modified $content.
    438 	 */
    439 	public function autoembed( $content ) {
    440 		// Replace line breaks from all HTML elements with placeholders.
    441 		$content = wp_replace_in_html_tags( $content, array( "\n" => '<!-- wp-line-break -->' ) );
    442 
    443 		if ( preg_match( '#(^|\s|>)https?://#i', $content ) ) {
    444 			// Find URLs on their own line.
    445 			$content = preg_replace_callback( '|^(\s*)(https?://[^\s<>"]+)(\s*)$|im', array( $this, 'autoembed_callback' ), $content );
    446 			// Find URLs in their own paragraph.
    447 			$content = preg_replace_callback( '|(<p(?: [^>]*)?>\s*)(https?://[^\s<>"]+)(\s*<\/p>)|i', array( $this, 'autoembed_callback' ), $content );
    448 		}
    449 
    450 		// Put the line breaks back.
    451 		return str_replace( '<!-- wp-line-break -->', "\n", $content );
    452 	}
    453 
    454 	/**
    455 	 * Callback function for WP_Embed::autoembed().
    456 	 *
    457 	 * @param array $match A regex match array.
    458 	 * @return string The embed HTML on success, otherwise the original URL.
    459 	 */
    460 	public function autoembed_callback( $match ) {
    461 		$oldval              = $this->linkifunknown;
    462 		$this->linkifunknown = false;
    463 		$return              = $this->shortcode( array(), $match[2] );
    464 		$this->linkifunknown = $oldval;
    465 
    466 		return $match[1] . $return . $match[3];
    467 	}
    468 
    469 	/**
    470 	 * Conditionally makes a hyperlink based on an internal class variable.
    471 	 *
    472 	 * @param string $url URL to potentially be linked.
    473 	 * @return string|false Linked URL or the original URL. False if 'return_false_on_fail' is true.
    474 	 */
    475 	public function maybe_make_link( $url ) {
    476 		if ( $this->return_false_on_fail ) {
    477 			return false;
    478 		}
    479 
    480 		$output = ( $this->linkifunknown ) ? '<a href="' . esc_url( $url ) . '">' . esc_html( $url ) . '</a>' : $url;
    481 
    482 		/**
    483 		 * Filters the returned, maybe-linked embed URL.
    484 		 *
    485 		 * @since 2.9.0
    486 		 *
    487 		 * @param string $output The linked or original URL.
    488 		 * @param string $url    The original URL.
    489 		 */
    490 		return apply_filters( 'embed_maybe_make_link', $output, $url );
    491 	}
    492 
    493 	/**
    494 	 * Find the oEmbed cache post ID for a given cache key.
    495 	 *
    496 	 * @since 4.9.0
    497 	 *
    498 	 * @param string $cache_key oEmbed cache key.
    499 	 * @return int|null Post ID on success, null on failure.
    500 	 */
    501 	public function find_oembed_post_id( $cache_key ) {
    502 		$cache_group    = 'oembed_cache_post';
    503 		$oembed_post_id = wp_cache_get( $cache_key, $cache_group );
    504 
    505 		if ( $oembed_post_id && 'oembed_cache' === get_post_type( $oembed_post_id ) ) {
    506 			return $oembed_post_id;
    507 		}
    508 
    509 		$oembed_post_query = new WP_Query(
    510 			array(
    511 				'post_type'              => 'oembed_cache',
    512 				'post_status'            => 'publish',
    513 				'name'                   => $cache_key,
    514 				'posts_per_page'         => 1,
    515 				'no_found_rows'          => true,
    516 				'cache_results'          => true,
    517 				'update_post_meta_cache' => false,
    518 				'update_post_term_cache' => false,
    519 				'lazy_load_term_meta'    => false,
    520 			)
    521 		);
    522 
    523 		if ( ! empty( $oembed_post_query->posts ) ) {
    524 			// Note: 'fields' => 'ids' is not being used in order to cache the post object as it will be needed.
    525 			$oembed_post_id = $oembed_post_query->posts[0]->ID;
    526 			wp_cache_set( $cache_key, $oembed_post_id, $cache_group );
    527 
    528 			return $oembed_post_id;
    529 		}
    530 
    531 		return null;
    532 	}
    533 }