diff --git a/.github/workflows/build-test-measure.yml b/.github/workflows/build-test-measure.yml
index 4349a3673f2..0b34c20be8f 100644
--- a/.github/workflows/build-test-measure.yml
+++ b/.github/workflows/build-test-measure.yml
@@ -214,6 +214,7 @@ jobs:
# phpstan requires PHP 7.1+.
php-version: 7.4
extensions: dom, iconv, json, libxml, zip
+ tools: phpstan
- name: Get Composer Cache Directory
id: composer-cache
@@ -231,7 +232,9 @@ jobs:
run: composer install
- name: Static Analysis (PHPStan)
- run: vendor/bin/phpstan analyse
+ run: |
+ phpstan --version
+ phpstan analyse
#-----------------------------------------------------------------------------------------------------------------------
diff --git a/includes/embeds/class-amp-base-embed-handler.php b/includes/embeds/class-amp-base-embed-handler.php
index 70b869ff1d2..ace710a8000 100644
--- a/includes/embeds/class-amp-base-embed-handler.php
+++ b/includes/embeds/class-amp-base-embed-handler.php
@@ -21,7 +21,9 @@ abstract class AMP_Base_Embed_Handler {
/**
* Default width.
*
- * @var int
+ * In some cases, this may be the string 'auto' when a fixed-height layout is used.
+ *
+ * @var int|string
*/
protected $DEFAULT_WIDTH = 600;
diff --git a/includes/embeds/class-amp-youtube-embed-handler.php b/includes/embeds/class-amp-youtube-embed-handler.php
index 074d272773b..3319e589cc6 100644
--- a/includes/embeds/class-amp-youtube-embed-handler.php
+++ b/includes/embeds/class-amp-youtube-embed-handler.php
@@ -5,6 +5,13 @@
* @package AMP
*/
+use AmpProject\Attribute;
+use AmpProject\Dom\Document;
+use AmpProject\Dom\Element;
+use AmpProject\Extension;
+use AmpProject\Layout;
+use AmpProject\Tag;
+
/**
* Class AMP_YouTube_Embed_Handler
*
@@ -28,7 +35,7 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
/**
* Ratio for calculating the default height from the content width.
*
- * @param float
+ * @var float
*/
const RATIO = 0.5625;
@@ -46,6 +53,24 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
*/
protected $DEFAULT_HEIGHT = 338;
+ /**
+ * List of domains that are applicable for this embed.
+ *
+ * @var string[]
+ */
+ const APPLICABLE_DOMAINS = [ 'youtu.be', 'youtube.com', 'youtube-nocookie.com' ];
+
+ /**
+ * Attributes from iframe which are copied to amp-youtube.
+ *
+ * @var string[]
+ */
+ const IFRAME_ATTRIBUTES = [
+ Attribute::TITLE,
+ Attribute::HEIGHT,
+ Attribute::WIDTH,
+ ];
+
/**
* AMP_YouTube_Embed_Handler constructor.
*
@@ -75,6 +100,7 @@ public function register_embed() {
*/
public function unregister_embed() {
remove_filter( 'embed_oembed_html', [ $this, 'filter_embed_oembed_html' ], 10 );
+ remove_filter( 'wp_video_shortcode_override', [ $this, 'video_override' ], 10 );
}
/**
@@ -82,117 +108,274 @@ public function unregister_embed() {
*
* @param string $cache Cache for oEmbed.
* @param string $url Embed URL.
+ *
* @return string Embed.
*/
public function filter_embed_oembed_html( $cache, $url ) {
- $id = $this->get_video_id_from_url( $url );
- if ( ! $id ) {
+
+ if ( empty( $cache ) || empty( $url ) ) {
return $cache;
}
- $props = $this->parse_props( $cache, $url, $id );
- if ( empty( $props ) ) {
+ $video_id = $this->get_video_id_from_url( $url );
+
+ if ( ! $video_id ) {
return $cache;
}
- $props['video_id'] = $id;
- return $this->render( $props, $url );
+ return $this->render( $cache, $url, $video_id );
}
/**
- * Parse AMP component from iframe.
+ * Convert YouTube iframe into AMP YouTube component.
*
- * @param string $html HTML.
- * @param string $url Embed URL, for fallback purposes.
+ * @param string $html HTML markup of YouTube iframe.
+ * @param string $url YouTube URL.
* @param string $video_id YouTube video ID.
- * @return array|null Props for rendering the component, or null if unable to parse.
+ *
+ * @return string HTML markup of AMP YouTube component.
*/
- private function parse_props( $html, $url, $video_id ) {
- $props = $this->match_element_attributes( $html, 'iframe', [ 'title', 'height', 'width' ] );
- if ( ! isset( $props ) ) {
- return null;
- }
+ public function render( $html, $url, $video_id ) {
- $img_attributes = [
- 'src' => esc_url_raw( sprintf( 'https://i.ytimg.com/vi/%s/hqdefault.jpg', $video_id ) ),
- 'layout' => 'fill',
- 'object-fit' => 'cover',
- ];
- if ( ! empty( $props['title'] ) ) {
- $img_attributes['alt'] = $props['title'];
+ $attributes = $this->prepare_attributes( $url, $video_id );
+
+ $props = $this->match_element_attributes( $html, Tag::IFRAME, self::IFRAME_ATTRIBUTES );
+ foreach ( self::IFRAME_ATTRIBUTES as $iframe_prop ) {
+ if ( ! empty( $props[ $iframe_prop ] ) ) {
+ $attributes[ $iframe_prop ] = $props[ $iframe_prop ];
+ }
}
- $img = AMP_HTML_Utils::build_tag( 'img', $img_attributes );
- $props['placeholder'] = AMP_HTML_Utils::build_tag(
- 'a',
- [
- 'placeholder' => '',
- 'href' => esc_url_raw( $url ),
- ],
- $img
+ $placeholder = $this->get_placeholder_markup( $url, $video_id, $attributes );
+
+ return AMP_HTML_Utils::build_tag( Extension::YOUTUBE, $attributes, $placeholder );
+ }
+
+ /**
+ * Sanitize YouTube raw embeds.
+ *
+ * @param Document $dom Document.
+ *
+ * @return void
+ */
+ public function sanitize_raw_embeds( Document $dom ) {
+
+ $query_segments = array_map(
+ static function ( $domain ) {
+ return sprintf(
+ 'starts-with( @src, "https://www.%1$s/" ) or starts-with( @src, "https://%1$s/" ) or starts-with( @src, "http://www.%1$s/" ) or starts-with( @src, "http://%1$s/" )',
+ $domain
+ );
+ },
+ self::APPLICABLE_DOMAINS
);
- return $props;
+ $query = implode( ' or ', $query_segments );
+
+ $nodes = $dom->xpath->query( sprintf( '//iframe[ %s ]', $query ) );
+
+ /** @var Element $node */
+ foreach ( $nodes as $node ) {
+
+ $amp_youtube_component = $this->get_amp_component( $dom, $node );
+
+ if ( ! empty( $amp_youtube_component ) ) {
+ $node->parentNode->replaceChild( $amp_youtube_component, $node );
+ }
+ }
}
/**
- * Render embed.
+ * Parse YouTube iframe element and return an AMP YouTube component.
+ *
+ * @param Document $dom Document DOM.
+ * @param Element $node YouTube iframe element.
*
- * @param array $args Args.
- * @param string $url URL.
- * @return string Rendered.
+ * @return Element|false AMP component, otherwise `false`.
*/
- public function render( $args, $url ) {
- $args = wp_parse_args(
- $args,
- [
- 'video_id' => false,
- 'layout' => 'responsive',
- 'width' => $this->args['width'],
- 'height' => $this->args['height'],
- 'placeholder' => '',
- ]
+ private function get_amp_component( Document $dom, Element $node ) {
+
+ $url = $node->getAttribute( Attribute::SRC );
+ $video_id = $this->get_video_id_from_url( $url );
+ $attributes = $this->prepare_attributes( $url, $video_id );
+
+ foreach ( self::IFRAME_ATTRIBUTES as $iframe_prop ) {
+ if ( ! empty( $node->getAttribute( $iframe_prop ) ) ) {
+ $attributes[ $iframe_prop ] = $node->getAttribute( $iframe_prop );
+ }
+ }
+
+ if ( empty( $attributes[ Attribute::DATA_VIDEOID ] ) && empty( $attributes[ Attribute::DATA_LIVE_CHANNELID ] ) ) {
+ return false;
+ }
+
+ $amp_node = AMP_DOM_Utils::create_node(
+ $dom,
+ Extension::YOUTUBE,
+ $attributes
);
- if ( empty( $args['video_id'] ) ) {
- return AMP_HTML_Utils::build_tag(
- 'a',
- [
- 'href' => esc_url_raw( $url ),
- 'class' => 'amp-wp-embed-fallback',
- ],
- esc_html( $url )
+ if ( $video_id && $amp_node instanceof Element ) {
+ $amp_node->appendChild(
+ $this->get_placeholder_element( $amp_node, $video_id, $attributes )
);
}
- $this->did_convert_elements = true;
+ return $amp_node;
+ }
- $attributes = array_merge(
- [ 'data-videoid' => $args['video_id'] ],
- wp_array_slice_assoc( $args, [ 'layout', 'width', 'height' ] )
+ /**
+ * Prepare attributes for amp-youtube component.
+ *
+ * @param string $url YouTube video URL.
+ * @param string $video_id YouTube video ID.
+ *
+ * @return array prepared arguments for amp-youtube component.
+ */
+ private function prepare_attributes( $url, $video_id = '' ) {
+
+ $attributes = [
+ Attribute::LAYOUT => Layout::RESPONSIVE,
+ Attribute::WIDTH => $this->args['width'],
+ Attribute::HEIGHT => $this->args['height'],
+ ];
+
+ if ( ! empty( $video_id ) ) {
+ $attributes[ Attribute::DATA_VIDEOID ] = $video_id;
+ }
+
+ // Find start time of video.
+ $start_time = $this->get_start_time_from_url( $url );
+ if ( ! empty( $start_time ) && 0 < (int) $start_time ) {
+ $attributes['data-param-start'] = (int) $start_time;
+ }
+
+ $query_vars = [];
+ $query_param = wp_parse_url( $url, PHP_URL_QUERY );
+ wp_parse_str( $query_param, $query_vars );
+ $query_vars = ( is_array( $query_vars ) ) ? $query_vars : [];
+
+ $excluded_param = [ 'start', 'v', 'vi', 'w', 'h' ];
+
+ foreach ( $query_vars as $key => $value ) {
+
+ if ( in_array( $key, $excluded_param, true ) ) {
+ continue;
+ }
+
+ if ( in_array( $key, [ Attribute::AUTOPLAY, Attribute::LOOP ], true ) ) {
+ $attributes[ $key ] = $value;
+ continue;
+ }
+
+ if ( 'channel' === $key ) {
+ $attributes[ Attribute::DATA_LIVE_CHANNELID ] = $value;
+ continue;
+ }
+
+ $attributes[ sanitize_key( "data-param-$key" ) ] = $value;
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Placeholder element for AMP YouTube component in the DOM.
+ *
+ * @param Element $amp_component AMP component element.
+ * @param string $video_id Video ID.
+ * @param array $attributes YouTube attributes.
+ *
+ * @return Element Placeholder.
+ */
+ private function get_placeholder_element( Element $amp_component, $video_id, $attributes ) {
+ $dom = Document::fromNode( $amp_component );
+
+ $img_attributes = [
+ Attribute::SRC => esc_url_raw( sprintf( 'https://i.ytimg.com/vi/%s/hqdefault.jpg', $video_id ) ),
+ Attribute::LAYOUT => Layout::FILL,
+ Attribute::OBJECT_FIT => 'cover',
+ ];
+
+ if ( $attributes[ Attribute::TITLE ] ) {
+ $img_attributes[ Attribute::ALT ] = $attributes[ Attribute::TITLE ];
+ }
+
+ $img_node = AMP_DOM_Utils::create_node(
+ $dom,
+ Tag::IMG,
+ $img_attributes
);
- if ( ! empty( $args['title'] ) ) {
- $attributes['title'] = $args['title'];
+
+ $video_url = esc_url_raw( sprintf( 'https://www.youtube.com/watch?v=%s', $video_id ) );
+ if ( array_key_exists( 'data-param-start', $attributes ) ) {
+ $video_url .= '#t=' . $attributes['data-param-start'];
+ }
+
+ $placeholder = AMP_DOM_Utils::create_node(
+ $dom,
+ Tag::A,
+ [
+ Attribute::PLACEHOLDER => '',
+ Attribute::HREF => $video_url,
+ ]
+ );
+
+ $placeholder->appendChild( $img_node );
+
+ return $placeholder;
+ }
+
+ /**
+ * To get placeholder for AMP component as constructed HTML string.
+ *
+ * @param string $url YouTube URL.
+ * @param string $video_id Video ID.
+ * @param array $attributes YouTube attributes.
+ *
+ * @return string HTML string.
+ */
+ private function get_placeholder_markup( $url, $video_id, $attributes ) {
+
+ $img_attributes = [
+ Attribute::SRC => esc_url_raw( sprintf( 'https://i.ytimg.com/vi/%s/hqdefault.jpg', $video_id ) ),
+ Attribute::LAYOUT => Layout::FILL,
+ Attribute::OBJECT_FIT => 'cover',
+ ];
+
+ if ( ! empty( $attributes[ Attribute::TITLE ] ) ) {
+ $img_attributes[ Attribute::ALT ] = $attributes[ Attribute::TITLE ];
}
- return AMP_HTML_Utils::build_tag( 'amp-youtube', $attributes, $args['placeholder'] );
+ $img = ' ';
+
+ return AMP_HTML_Utils::build_tag(
+ Tag::A,
+ [
+ Attribute::PLACEHOLDER => '',
+ Attribute::HREF => esc_url_raw( $url ),
+ ],
+ $img
+ );
}
/**
* Determine the video ID from the URL.
*
* @param string $url URL.
+ *
* @return string|false Video ID, or false if none could be retrieved.
*/
private function get_video_id_from_url( $url ) {
+
$parsed_url = wp_parse_url( $url );
if ( ! isset( $parsed_url['host'] ) ) {
return false;
}
- $domain = implode( '.', array_slice( explode( '.', $parsed_url['host'] ), -2 ) );
- if ( ! in_array( $domain, [ 'youtu.be', 'youtube.com', 'youtube-nocookie.com' ], true ) ) {
+ $domain = implode( '.', array_slice( explode( '.', $parsed_url['host'] ), - 2 ) );
+ if ( ! in_array( $domain, self::APPLICABLE_DOMAINS, true ) ) {
return false;
}
@@ -230,12 +413,62 @@ private function get_video_id_from_url( $url ) {
// Other top-level segments indicate non-video URLs. There are examples of URLs having segments including
// 'v', 'vi', and 'e' but these do not work anymore. In any case, they are added here for completeness.
if ( ! empty( $segments[1] ) && in_array( $segments[0], [ 'embed', 'watch', 'v', 'vi', 'e' ], true ) ) {
+
+ /**
+ * Ignore live streaming channel URLs. For example:
+ * * https://www.youtube.com/embed/live_stream?channel=UCkaNo2FUEWips2z4BkOHl6Q
+ */
+ if ( 'embed' === $segments[0] && 'live_stream' === $segments[1] && isset( $query_vars['channel'] ) ) {
+ return false;
+ }
+
return $segments[1];
}
return false;
}
+ /**
+ * Get the start time of the YouTube video in seconds.
+ *
+ * @param string $url YouTube URL.
+ *
+ * @return int Start time in seconds.
+ */
+ private function get_start_time_from_url( $url ) {
+
+ $start_time = 0;
+ $parsed_url = wp_parse_url( $url );
+
+ if ( ! empty( $parsed_url['query'] ) ) {
+ $query_vars = [];
+ wp_parse_str( $parsed_url['query'], $query_vars );
+
+ if ( ! empty( $query_vars['start'] ) && 0 < (int) $query_vars['start'] ) {
+ return (int) $query_vars['start'];
+ }
+ }
+
+ if ( ! empty( $parsed_url['fragment'] ) ) {
+ $regex = '/^t=(?:(?\d+)m)?(?:(?\d+)s?)?$/';
+
+ preg_match( $regex, $parsed_url['fragment'], $matches );
+
+ if ( is_array( $matches ) ) {
+ $matches = wp_parse_args(
+ $matches,
+ [
+ 'minutes' => 0,
+ 'seconds' => 0,
+ ]
+ );
+ $start_time = ( (int) $matches['seconds'] + ( (int) $matches['minutes'] * 60 ) );
+ }
+ }
+
+ return $start_time;
+ }
+
/**
* Override the output of YouTube videos.
*
@@ -244,17 +477,22 @@ private function get_video_id_from_url( $url ) {
*
* @param string $html Empty variable to be replaced with shortcode markup.
* @param array $attr The shortcode attributes.
+ *
* @return string|null $markup The markup to output.
*/
public function video_override( $html, $attr ) {
- if ( ! isset( $attr['src'] ) ) {
+
+ if ( ! isset( $attr[ Attribute::SRC ] ) ) {
return $html;
}
- $video_id = $this->get_video_id_from_url( $attr['src'] );
+
+ $src = $attr[ Attribute::SRC ];
+ $video_id = $this->get_video_id_from_url( $src );
+
if ( ! $video_id ) {
return $html;
}
- return $this->render( compact( 'video_id' ), $attr['src'] );
+ return $this->render( $html, $src, $video_id );
}
}
diff --git a/includes/utils/class-amp-dom-utils.php b/includes/utils/class-amp-dom-utils.php
index 164abfc2c3f..a3dca61073f 100644
--- a/includes/utils/class-amp-dom-utils.php
+++ b/includes/utils/class-amp-dom-utils.php
@@ -158,7 +158,7 @@ public static function get_content_from_dom_node( Document $dom, $node ) {
* @param string $tag A valid HTML element tag for the element to be added.
* @param string[] $attributes One of more valid attributes for the new node.
*
- * @return DOMElement|false The DOMElement for the given $tag, or false on failure
+ * @return Element|false The element for the given $tag, or false on failure
*/
public static function create_node( Document $dom, $tag, $attributes ) {
$node = $dom->createElement( $tag );
diff --git a/includes/validation/class-amp-validation-error-taxonomy.php b/includes/validation/class-amp-validation-error-taxonomy.php
index d8c5e96a69a..4eac9a96dc2 100644
--- a/includes/validation/class-amp-validation-error-taxonomy.php
+++ b/includes/validation/class-amp-validation-error-taxonomy.php
@@ -128,7 +128,7 @@ class AMP_Validation_Error_Taxonomy {
* This is also used in WP_List_Table, like for the 'Bulk Actions' option.
* When this is present, this ensures that this isn't filtered.
*
- * @var int
+ * @var string
*/
const NO_FILTER_VALUE = '';
@@ -1095,7 +1095,7 @@ public static function add_term_filter_query_var( $url, $tax ) {
&&
in_array(
$_POST[ self::VALIDATION_ERROR_TYPE_QUERY_VAR ], // phpcs:ignore WordPress.Security.NonceVerification.Missing
- array_merge( self::get_error_types(), [ (string) self::NO_FILTER_VALUE ] ),
+ array_merge( self::get_error_types(), [ self::NO_FILTER_VALUE ] ),
true
)
) {
@@ -1457,7 +1457,7 @@ private static function output_error_status_filter_option_markup( $option_text,
/**
* Gets all of the possible error types.
*
- * @return array Error types.
+ * @return string[] Error types.
*/
public static function get_error_types() {
return [ self::HTML_ELEMENT_ERROR_TYPE, self::HTML_ATTRIBUTE_ERROR_TYPE, self::JS_ERROR_TYPE, self::CSS_ERROR_TYPE ];
diff --git a/src/BackgroundTask/MonitorCssTransientCaching.php b/src/BackgroundTask/MonitorCssTransientCaching.php
index 309b4f76311..a2965aefe84 100644
--- a/src/BackgroundTask/MonitorCssTransientCaching.php
+++ b/src/BackgroundTask/MonitorCssTransientCaching.php
@@ -108,7 +108,6 @@ public function process( ...$args ) {
$date = isset( $args[0] ) && $args[0] instanceof DateTimeInterface ? $args[0] : new DateTimeImmutable();
- /** @phpstan-ignore-next-line */
$transient_count = isset( $args[1] ) ? (int) $args[1] : $this->query_css_transient_count();
$date_string = $date->format( 'Ymd' );
diff --git a/tests/php/test-class-amp-youtube-embed-handler.php b/tests/php/test-class-amp-youtube-embed-handler.php
index 189b2d4b86d..ff0348864cc 100644
--- a/tests/php/test-class-amp-youtube-embed-handler.php
+++ b/tests/php/test-class-amp-youtube-embed-handler.php
@@ -13,7 +13,7 @@
/**
* Tests for AMP_YouTube_Embed_Handler.
*
- * @covers AMP_YouTube_Embed_Handler
+ * @coversDefaultClass \AMP_YouTube_Embed_Handler
*/
class Test_AMP_YouTube_Embed_Handler extends TestCase {
@@ -35,7 +35,7 @@ class Test_AMP_YouTube_Embed_Handler extends TestCase {
/**
* An instance of this embed handler.
*
- * @var AMP_YouTube_Embed_Handler.
+ * @var AMP_YouTube_Embed_Handler
*/
public $handler;
@@ -100,10 +100,90 @@ public function mock_http_request( $preempt, $r, $url ) {
];
}
+ /**
+ * @covers ::register_embed()
+ * @covers ::unregister_embed()
+ */
+ public function test_register_and_unregister_embed() {
+ $embed = new AMP_YouTube_Embed_Handler();
+ $embed->register_embed();
+ $this->assertEquals( 10, has_filter( 'embed_oembed_html', [ $embed, 'filter_embed_oembed_html' ] ) );
+ $this->assertEquals( 10, has_filter( 'wp_video_shortcode_override', [ $embed, 'video_override' ] ) );
+ $embed->unregister_embed();
+ $this->assertFalse( has_filter( 'embed_oembed_html', [ $embed, 'filter_embed_oembed_html' ] ) );
+ $this->assertFalse( has_filter( 'wp_video_shortcode_override', [ $embed, 'video_override' ] ) );
+ }
+
+ /**
+ * Data provider for $this->test_sanitize_raw_embeds()
+ *
+ * @return string[][]
+ */
+ public function sanitize_raw_embeds_data_provider() {
+
+ return [
+ 'youtube-embed' => [
+ 'source' => 'VIDEO ',
+ 'expected' => ' ',
+ ],
+ 'youtube-start' => [
+ 'source' => 'VIDEO ',
+ 'expected' => ' ',
+ ],
+ 'with_http' => [
+ 'source' => 'VIDEO ',
+ 'expected' => ' ',
+ ],
+ 'short-url' => [
+ 'source' => '',
+ 'expected' => ' ',
+ ],
+ 'live_streaming_channel' => [
+ 'source' => 'VIDEO ',
+ 'expected' => ' ',
+ ],
+ 'none-youtube' => [
+ 'source' => '',
+ 'expected' => '',
+ ],
+ 'youtube-unknown' => [
+ 'source' => 'VIDEO ',
+ 'expected' => 'VIDEO ',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider sanitize_raw_embeds_data_provider
+ *
+ * @covers ::sanitize_raw_embeds()
+ * @covers ::get_amp_component()
+ * @covers ::get_placeholder_element()
+ * @covers ::prepare_attributes()
+ */
+ public function test_sanitize_raw_embeds( $source, $expected ) {
+
+ $embed = new AMP_YouTube_Embed_Handler();
+ $embed->register_embed();
+
+ $dom = AMP_DOM_Utils::get_dom_from_content( $source );
+ $embed->sanitize_raw_embeds( $dom );
+
+ $layout_sanitizer = new AMP_Layout_Sanitizer( $dom );
+ $layout_sanitizer->sanitize();
+
+ $content = AMP_DOM_Utils::get_content_from_dom( $dom );
+
+ $this->assertEquals( $expected, trim( $content ) );
+ }
+
/**
* Test video_override().
*
- * @covers AMP_YouTube_Embed_Handler::video_override()
+ * @covers ::register_embed()
+ * @covers ::video_override()
+ * @covers ::render()
+ * @covers ::get_placeholder_markup()
*/
public function test_video_override() {
remove_all_filters( 'wp_video_shortcode_override' );
@@ -138,6 +218,7 @@ public function test_video_override() {
remove_all_filters( 'wp_video_shortcode_override' );
}
+ /** @return array */
public function get_conversion_data() {
return [
'no_embed' => [
@@ -147,20 +228,20 @@ public function get_conversion_data() {
'url_simple' => [
'https://www.youtube.com/watch?v=kfVsfOSbJY0' . PHP_EOL,
- '
' . PHP_EOL,
- '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
],
'url_short' => [
'https://youtu.be/kfVsfOSbJY0' . PHP_EOL,
- '
' . PHP_EOL,
- '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
],
'url_with_querystring' => [
'http://www.youtube.com/watch?v=kfVsfOSbJY0&hl=en&fs=1&w=425&h=349' . PHP_EOL,
- '
' . PHP_EOL,
- '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
],
// Several reports of invalid URLs that have multiple `?` in the URL.
@@ -171,14 +252,48 @@ public function get_conversion_data() {
'embed_url' => [
'https://www.youtube.com/embed/kfVsfOSbJY0' . PHP_EOL,
- '
' . PHP_EOL,
- '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ ],
+
+ 'with_start_time' => [
+ 'http://www.youtube.com/watch?v=kfVsfOSbJY0#t=1m10s' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ ],
+
+ 'with_start_time_2' => [
+ 'http://www.youtube.com/watch?v=kfVsfOSbJY0#t=62' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ ],
+
+ 'with_start_time_3' => [
+ 'http://www.youtube.com/watch?v=kfVsfOSbJY0#t=62s' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ ],
+
+ 'with_start_time_4' => [
+ 'http://www.youtube.com/watch?v=kfVsfOSbJY0#t=1m' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
+ ],
+
+ 'with_start_time_5' => [
+ 'http://www.youtube.com/watch?v=kfVsfOSbJY0#t=1m2' . PHP_EOL,
+ '
' . PHP_EOL,
+ '
' . PHP_EOL,
],
];
}
/**
* @dataProvider get_conversion_data
+ * @covers ::register_embed()
+ * @covers ::filter_embed_oembed_html()
+ * @covers ::render()
+ * @covers ::get_placeholder_markup()
*/
public function test__conversion( $source, $expected, $fallback_for_expected = null ) {
$this->handler->register_embed();
@@ -281,6 +396,10 @@ public function get_video_id_data() {
'http://youtube.com/?wrong=XOY3ZUO6P0k',
false,
],
+ 'live_streaming_channel' => [
+ 'https://www.youtube.com/embed/live_stream?channel=UCkaNo2FUEWips2z4BkOHl6Q',
+ false,
+ ],
];
}
@@ -288,7 +407,7 @@ public function get_video_id_data() {
* Tests get_video_id_from_url.
*
* @dataProvider get_video_id_data
- * @covers AMP_YouTube_Embed_Handler::get_video_id_from_url()
+ * @covers ::get_video_id_from_url()
*
* @param string $url The URL to test.
* @param string|false $expected The expected result.
@@ -301,6 +420,53 @@ public function test_get_video_id_from_url( $url, $expected ) {
);
}
+ /**
+ * Gets the test data for test_get_start_time_from_url().
+ *
+ * @return array The test data.
+ */
+ public function get_start_time_data() {
+
+ return [
+ 'empty_because_no_start_data' => [
+ 'http://youtube.com/?wrong=XOY3ZUO6P0k',
+ 0,
+ ],
+ 'data_in_query_string' => [
+ 'http://www.youtube.com/watch?v=0zM3nApSvMg&start=90',
+ 90,
+ ],
+ 'data_in_fragment' => [
+ 'http://www.youtube.com/watch?v=0zM3nApSvMg#t=0m10s',
+ 10,
+ ],
+ 'data_in_fragment_with_minutes' => [
+ 'http://www.youtube.com/watch?v=0zM3nApSvMg#t=1m10s',
+ 70,
+ ],
+ ];
+ }
+
+ /**
+ * Tests get_start_time_from_url.
+ *
+ * @dataProvider get_start_time_data
+ * @covers ::get_start_time_from_url()
+ *
+ * @param string $url The URL to test.
+ * @param string|false $expected The expected result.
+ *
+ * @throws ReflectionException If a reflection of the object is not possible.
+ */
+ public function test_get_start_time_from_url( $url, $expected ) {
+
+ $this->assertEquals(
+ $expected,
+ $this->call_private_method( $this->handler, 'get_start_time_from_url', [ $url ] )
+ );
+ }
+
+ /** @return array */
public function get_scripts_data() {
return [
'not_converted' => [
@@ -316,6 +482,8 @@ public function get_scripts_data() {
/**
* @dataProvider get_scripts_data
+ * @covers ::register_embed()
+ * @covers ::get_scripts()
*/
public function test__get_scripts( $source, $expected ) {
$this->handler->register_embed();