| @@ -13,8 +13,12 @@ | ||
| 13 | 13 | * |
| 14 | 14 | * @package automattic/jetpack |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 18 | + exit( 0 ); | |
| 19 | +} | |
| 20 | + | |
| 17 | 21 | /** |
| 18 | 22 | * Replaces YouTube embeds with YouTube shortcodes. |
| 19 | 23 | * |
| 20 | 24 | * Covers the following formats: |
| @@ -32,9 +36,9 @@ | ||
| 32 | 36 | * |
| 33 | 37 | * @param string $content HTML content. |
| 34 | 38 | * @return string The content with YouTube embeds replaced with YouTube shortcodes. |
| 35 | 39 | */ |
| 36 | -function youtube_embed_to_short_code( $content ) { | |
| 40 | +function jetpack_youtube_embed_to_short_code( $content ) { | |
| 37 | 41 | if ( ! is_string( $content ) || ! str_contains( $content, 'youtube.com' ) ) { |
| 38 | 42 | return $content; |
| 39 | 43 | } |
| 40 | 44 | |
| @@ -105,10 +109,13 @@ | ||
| 105 | 109 | } |
| 106 | 110 | |
| 107 | 111 | return $content; |
| 108 | 112 | } |
| 109 | -add_filter( 'pre_kses', 'youtube_embed_to_short_code' ); | |
| 110 | 113 | |
| 114 | +if ( jetpack_shortcodes_should_hook_pre_kses() ) { | |
| 115 | + add_filter( 'pre_kses', 'jetpack_youtube_embed_to_short_code' ); | |
| 116 | +} | |
| 117 | + | |
| 111 | 118 | /** |
| 112 | 119 | * Replaces plain-text links to YouTube videos with YouTube embeds. |
| 113 | 120 | * |
| 114 | 121 | * @param string $content HTML content. |
| @@ -114,10 +121,10 @@ | ||
| 114 | 121 | * @param string $content HTML content. |
| 115 | 122 | * |
| 116 | 123 | * @return string The content with embeds instead of URLs |
| 117 | 124 | */ |
| 118 | -function youtube_link( $content ) { | |
| 119 | - return jetpack_preg_replace_callback_outside_tags( '!(?:\n|\A)https?://(?:www\.)?(?:youtube.com/(?:v/|playlist|watch[/\#?])|youtu\.be/)[^\s]+?(?:\n|\Z)!i', 'youtube_link_callback', $content, 'youtube.com/' ); | |
| 125 | +function jetpack_youtube_link( $content ) { | |
| 126 | + return jetpack_preg_replace_callback_outside_tags( '!(?:\n|\A)https?://(?:www\.)?(?:youtube.com/(?:v/|playlist|watch[/\#?])|youtu\.be/)[^\s]+?(?:\n|\Z)!i', 'jetpack_youtube_link_callback', $content, 'youtube.com/' ); | |
| 120 | 127 | } |
| 121 | 128 | |
| 122 | 129 | /** |
| 123 | 130 | * Callback function for the regex that replaces YouTube URLs with |
| @@ -124,30 +131,37 @@ | ||
| 124 | 131 | * YouTube embeds. |
| 125 | 132 | * |
| 126 | 133 | * @param array $matches An array containing a YouTube URL. |
| 127 | 134 | */ |
| 128 | -function youtube_link_callback( $matches ) { | |
| 129 | - return "\n" . youtube_id( $matches[0] ) . "\n"; | |
| 135 | +function jetpack_youtube_link_callback( $matches ) { | |
| 136 | + return "\n" . jetpack_youtube_id( $matches[0] ) . "\n"; | |
| 130 | 137 | } |
| 131 | 138 | |
| 132 | 139 | /** |
| 133 | 140 | * Normalizes a YouTube URL to include a v= parameter and a query string free of encoded ampersands. |
| 134 | 141 | * |
| 135 | - * @param string $url | |
| 136 | - * @return string The normalized URL | |
| 142 | + * @param string|array $url Youtube URL. | |
| 143 | + * @return string|false The normalized URL or false if input is invalid. | |
| 137 | 144 | */ |
| 138 | -if ( ! function_exists( 'youtube_sanitize_url' ) ) : | |
| 145 | +if ( ! function_exists( 'jetpack_youtube_sanitize_url' ) ) : | |
| 139 | 146 | /** |
| 140 | 147 | * Clean up Youtube URL to match a single format. |
| 141 | 148 | * |
| 142 | - * @param string $url Youtube URL. | |
| 149 | + * @param string|array $url Youtube URL. | |
| 143 | 150 | */ |
| 144 | - function youtube_sanitize_url( $url ) { | |
| 151 | + function jetpack_youtube_sanitize_url( $url ) { | |
| 152 | + if ( is_array( $url ) && isset( $url['url'] ) ) { | |
| 153 | + $url = $url['url']; | |
| 154 | + } | |
| 155 | + if ( ! is_string( $url ) ) { | |
| 156 | + return false; | |
| 157 | + } | |
| 158 | + | |
| 145 | 159 | $url = trim( $url, ' "' ); |
| 146 | 160 | $url = trim( $url ); |
| 147 | - $url = str_replace( array( 'youtu.be/', '/v/', '#!v=', '&', '&', 'playlist' ), array( 'youtu.be/?v=', '/?v=', '?v=', '&', '&', 'videoseries' ), $url ); | |
| 161 | + $url = str_replace( array( 'youtu.be/', '/v/', '/shorts/', '#!v=', '&', '&', 'playlist' ), array( 'youtu.be/?v=', '/?v=', '/watch?v=', '?v=', '&', '&', 'videoseries' ), $url ); | |
| 148 | 162 | |
| 149 | - // Replace any extra question marks with ampersands - the result of a URL like "http://www.youtube.com/v/9FhMMmqzbD8?fs=1&hl=en_US" being passed in. | |
| 163 | + // Replace any extra question marks with ampersands - the result of a URL like "https://www.youtube.com/v/dQw4w9WgXcQ?fs=1&hl=en_US" being passed in. | |
| 150 | 164 | $query_string_start = strpos( $url, '?' ); |
| 151 | 165 | |
| 152 | 166 | if ( false !== $query_string_start ) { |
| 153 | 167 | $url = substr( $url, 0, $query_string_start + 1 ) . str_replace( '?', '&', substr( $url, $query_string_start + 1 ) ); |
| @@ -171,9 +185,9 @@ | ||
| 171 | 185 | * https://www.youtube.com/watch?v=GJNxoe-iSb4&list=PLAVZ4NFtZX0fE54mDSqNKym-o_rz-8xmk |
| 172 | 186 | * |
| 173 | 187 | * @param string $url Youtube URL. |
| 174 | 188 | */ |
| 175 | -function youtube_id( $url ) { | |
| 189 | +function jetpack_youtube_id( $url ) { | |
| 176 | 190 | $id = jetpack_get_youtube_id( $url ); |
| 177 | 191 | |
| 178 | 192 | if ( ! $id ) { |
| 179 | 193 | return sprintf( '<!--%s-->', esc_html__( 'YouTube Error: bad URL entered', 'jetpack' ) ); |
| @@ -178,9 +192,9 @@ | ||
| 178 | 192 | if ( ! $id ) { |
| 179 | 193 | return sprintf( '<!--%s-->', esc_html__( 'YouTube Error: bad URL entered', 'jetpack' ) ); |
| 180 | 194 | } |
| 181 | 195 | |
| 182 | - $url = youtube_sanitize_url( $url ); | |
| 196 | + $url = jetpack_youtube_sanitize_url( $url ); | |
| 183 | 197 | $url = wp_parse_url( $url ); |
| 184 | 198 | |
| 185 | 199 | $thumbnail = "https://i.ytimg.com/vi/$id/hqdefault.jpg"; |
| 186 | 200 | $video_url = add_query_arg( 'v', $id, 'https://www.youtube.com/watch' ); |
| @@ -227,9 +241,9 @@ | ||
| 227 | 241 | $start = (int) $args['start']; |
| 228 | 242 | } elseif ( isset( $args['t'] ) ) { |
| 229 | 243 | if ( is_numeric( $args['t'] ) ) { |
| 230 | 244 | $start = (int) $args['t']; |
| 231 | - } else { | |
| 245 | + } elseif ( is_string( $args['t'] ) ) { | |
| 232 | 246 | $time_pieces = preg_split( '/(?<=\D)(?=\d+)/', $args['t'] ); |
| 233 | 247 | |
| 234 | 248 | foreach ( $time_pieces as $time_piece ) { |
| 235 | 249 | $int = (int) $time_piece; |
| @@ -433,13 +447,13 @@ | ||
| 433 | 447 | * @param array $atts Shortcode attributes. |
| 434 | 448 | * |
| 435 | 449 | * @return string The rendered shortcode. |
| 436 | 450 | */ |
| 437 | -function youtube_shortcode( $atts ) { | |
| 451 | +function jetpack_youtube_shortcode( $atts ) { | |
| 438 | 452 | $url = ( isset( $atts[0] ) ) ? ltrim( $atts[0], '=' ) : shortcode_new_to_old_params( $atts ); |
| 439 | - return youtube_id( $url ); | |
| 453 | + return jetpack_youtube_id( $url ); | |
| 440 | 454 | } |
| 441 | -add_shortcode( 'youtube', 'youtube_shortcode' ); | |
| 455 | +add_shortcode( 'youtube', 'jetpack_youtube_shortcode' ); | |
| 442 | 456 | |
| 443 | 457 | /** |
| 444 | 458 | * Gets the dimensions of the [youtube] shortcode. |
| 445 | 459 | * |
| @@ -459,12 +473,12 @@ | ||
| 459 | 473 | $input_h = ( isset( $query_args['h'] ) && (int) $query_args['h'] ) ? (int) $query_args['h'] : 0; |
| 460 | 474 | |
| 461 | 475 | // If we have $content_width, use it. |
| 462 | 476 | if ( ! empty( $content_width ) ) { |
| 463 | - $default_width = $content_width; | |
| 477 | + $default_width = (int) $content_width; | |
| 464 | 478 | } else { |
| 465 | 479 | // Otherwise get default width from the old, now deprecated embed_size_w option. |
| 466 | - $default_width = get_option( 'embed_size_w' ); | |
| 480 | + $default_width = (int) get_option( 'embed_size_w' ); | |
| 467 | 481 | } |
| 468 | 482 | |
| 469 | 483 | // If we don't know those 2 values use a hardcoded width. |
| 470 | 484 | if ( empty( $default_width ) ) { |
| @@ -519,26 +533,101 @@ | ||
| 519 | 533 | /** |
| 520 | 534 | * For bare URLs on their own line of the form |
| 521 | 535 | * http://www.youtube.com/v/9FhMMmqzbD8?fs=1&hl=en_US |
| 522 | 536 | * |
| 523 | - * @param array $matches Regex partial matches against the URL passed. | |
| 524 | - * @param array $attr Attributes received in embed response. | |
| 525 | - * @param array $url Requested URL to be embedded. | |
| 537 | + * @param array $matches Regex partial matches against the URL passed. | |
| 538 | + * @param array $attr Attributes received in embed response. | |
| 539 | + * @param string $url Requested URL to be embedded. | |
| 526 | 540 | */ |
| 527 | 541 | function wpcom_youtube_embed_crazy_url( $matches, $attr, $url ) { |
| 528 | - return youtube_id( $url ); | |
| 542 | + return jetpack_youtube_id( $url ); | |
| 529 | 543 | } |
| 530 | 544 | |
| 531 | 545 | /** |
| 546 | + * Get the regex for Youtube URLs. | |
| 547 | + */ | |
| 548 | +function wpcom_youtube_get_regex() { | |
| 549 | + return '#https?://(?:www\.)?(?:youtube.com/(?:v/|playlist|watch[/\#?])|youtu\.be/).*#i'; | |
| 550 | +} | |
| 551 | + | |
| 552 | +/** | |
| 532 | 553 | * Add a new handler to automatically transform custom Youtube URLs (like playlists) into embeds. |
| 533 | 554 | */ |
| 534 | 555 | function wpcom_youtube_embed_crazy_url_init() { |
| 535 | - wp_embed_register_handler( 'wpcom_youtube_embed_crazy_url', '#https?://(?:www\.)?(?:youtube.com/(?:v/|playlist|watch[/\#?])|youtu\.be/).*#i', 'wpcom_youtube_embed_crazy_url' ); | |
| 556 | + // Register the custom handler to provide the better support for the private video. | |
| 557 | + wp_embed_register_handler( 'wpcom_youtube_embed_crazy_url', wpcom_youtube_get_regex(), 'wpcom_youtube_embed_crazy_url' ); | |
| 536 | 558 | } |
| 537 | 559 | add_action( 'init', 'wpcom_youtube_embed_crazy_url_init' ); |
| 538 | 560 | |
| 561 | +/** | |
| 562 | + * Filters the oEmbed result before any HTTP requests are made for YouTube. | |
| 563 | + * | |
| 564 | + * @since 13.9 | |
| 565 | + * | |
| 566 | + * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null. | |
| 567 | + * @param string $url The URL that should be inspected for discovery `<link>` tags. | |
| 568 | + * @param array $args oEmbed remote get arguments. | |
| 569 | + * @return null|string The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. | |
| 570 | + * Null if the URL does not belong to the current site. | |
| 571 | + */ | |
| 572 | +function wpcom_youtube_filter_pre_oembed_result( $result, $url, $args ) { | |
| 573 | + // Return early if it's not a YouTube URL. | |
| 574 | + if ( ! preg_match( wpcom_youtube_get_regex(), $url, $matches ) ) { | |
| 575 | + return $result; | |
| 576 | + } | |
| 577 | + | |
| 578 | + // Try to get the oembed data by the Core's approach. | |
| 579 | + $wp_oembed = _wp_oembed_get_object(); | |
| 580 | + $data = $wp_oembed->get_data( $url, $args ); | |
| 581 | + if ( $data ) { | |
| 582 | + /** This filter is documented in wp-includes/class-wp-oembed.php */ | |
| 583 | + return apply_filters( 'oembed_result', $wp_oembed->data2html( $data, $url ), $url, $args ); | |
| 584 | + } | |
| 585 | + | |
| 586 | + // Fallback to the custom handler if the oembed result is not found, especially for the private video. | |
| 587 | + return jetpack_youtube_id( $url ); | |
| 588 | +} | |
| 589 | +add_filter( 'pre_oembed_result', 'wpcom_youtube_filter_pre_oembed_result', 10, 3 ); | |
| 590 | + | |
| 591 | +/** | |
| 592 | + * Remove the ending question mark from the video id of the YouTube URL. | |
| 593 | + * | |
| 594 | + * Example: https://www.youtube.com/watch?v=AVAWwXeOyyQ? | |
| 595 | + * | |
| 596 | + * @since 13.9 | |
| 597 | + * | |
| 598 | + * @param string $provider URL of the oEmbed provider. | |
| 599 | + * @param string $url URL of the content to be embedded. | |
| 600 | + * | |
| 601 | + * @return string | |
| 602 | + */ | |
| 603 | +function wpcom_youtube_oembed_fetch_url( $provider, $url ) { | |
| 604 | + if ( ! wp_startswith( $provider, 'https://www.youtube.com/oembed' ) ) { | |
| 605 | + return $provider; | |
| 606 | + } | |
| 607 | + | |
| 608 | + $parsed = wp_parse_url( $url ); | |
| 609 | + if ( ! isset( $parsed['query'] ) ) { | |
| 610 | + return $provider; | |
| 611 | + } | |
| 612 | + | |
| 613 | + $query_vars = array(); | |
| 614 | + wp_parse_str( $parsed['query'], $query_vars ); | |
| 615 | + if ( isset( $query_vars['v'] ) && wp_endswith( $query_vars['v'], '?' ) ) { | |
| 616 | + $url = remove_query_arg( array( 'v' ), $url ); | |
| 617 | + $url = add_query_arg( 'v', preg_replace( '/\?$/', '', $query_vars['v'] ), $url ); | |
| 618 | + } | |
| 619 | + | |
| 620 | + $provider = remove_query_arg( array( 'url' ), $provider ); | |
| 621 | + $provider = add_query_arg( 'url', rawurlencode( $url ), $provider ); | |
| 622 | + | |
| 623 | + return $provider; | |
| 624 | +} | |
| 625 | +add_filter( 'oembed_fetch_url', 'wpcom_youtube_oembed_fetch_url', 10, 2 ); | |
| 626 | + | |
| 539 | 627 | if ( |
| 540 | 628 | ! is_admin() |
| 629 | + && | |
| 541 | 630 | /** |
| 542 | 631 | * Allow oEmbeds in Jetpack's Comment form. |
| 543 | 632 | * |
| 544 | 633 | * @module shortcodes |
| @@ -546,9 +635,9 @@ | ||
| 546 | 635 | * @since 2.8.0 |
| 547 | 636 | * |
| 548 | 637 | * @param int $allow_oembed Option to automatically embed all plain text URLs. |
| 549 | 638 | */ |
| 550 | - && apply_filters( 'jetpack_comments_allow_oembed', true ) | |
| 639 | + apply_filters( 'jetpack_comments_allow_oembed', true ) | |
| 551 | 640 | // No need for this on WordPress.com, this is done for multiple shortcodes at a time there. |
| 552 | 641 | && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) |
| 553 | 642 | ) { |
| 554 | 643 | /* |
| @@ -555,9 +644,9 @@ | ||
| 555 | 644 | * We attach wp_kses_post to comment_text in default-filters.php with priority of 10 anyway, |
| 556 | 645 | * so the iframe gets filtered out. |
| 557 | 646 | * Higher priority because we need it before auto-link and autop get to it. |
| 558 | 647 | */ |
| 559 | - add_filter( 'comment_text', 'youtube_link', 1 ); | |
| 648 | + add_filter( 'comment_text', 'jetpack_youtube_link', 1 ); | |
| 560 | 649 | } |
| 561 | 650 | |
| 562 | 651 | /** |
| 563 | 652 | * Core changes to do_shortcode (https://core.trac.wordpress.org/changeset/34747) broke "improper" shortcodes |