diff --git a/src/AMP.php b/src/AMP.php index b51d5f6e..e852ade7 100644 --- a/src/AMP.php +++ b/src/AMP.php @@ -47,6 +47,7 @@ class AMP // The StandardScanPass should be first after all transform passes // The StandardFixPass should be after StandardScanPass + // The ObjectVideoTagTransformPass should be after all Object transform passes public $passes = [ 'Lullabot\AMP\Pass\PreliminaryPass', // Removes user blacklisted tags 'Lullabot\AMP\Pass\ImgTagTransformPass', @@ -63,6 +64,9 @@ class AMP 'Lullabot\AMP\Pass\PinterestTagTransformPass', 'Lullabot\AMP\Pass\FacebookNonIframeTransformPass', 'Lullabot\AMP\Pass\TwitterTransformPass', + 'Lullabot\AMP\Pass\ObjectYouTubeTagTransformPass', + 'Lullabot\AMP\Pass\ObjectVimeoTagTransformPass', + 'Lullabot\AMP\Pass\ObjectVideoTagTransformPass', 'Lullabot\AMP\Pass\StandardScanPass', 'Lullabot\AMP\Pass\StandardFixPass', 'Lullabot\AMP\Pass\AmpImgFixPass', diff --git a/src/Pass/ObjectVideoTagTransformPass.php b/src/Pass/ObjectVideoTagTransformPass.php new file mode 100644 index 00000000..fde2f6eb --- /dev/null +++ b/src/Pass/ObjectVideoTagTransformPass.php @@ -0,0 +1,112 @@ + tags + * + * This is what a video embed looks like: + * + * + * + * + * + * + */ +class ObjectVideoTagTransformPass extends BasePass +{ + function pass() + { + $all_objects = $this->q->find('object:not(noscript object)'); + /** @var DOMQuery $el */ + foreach ($all_objects as $el) { + /** @var \DOMElement $dom_el */ + $dom_el = $el->get(0); + $lineno = $this->getLineNo($dom_el); + $context_string = $this->getContextString($dom_el); + + $actionTakenType = ''; + + if ($this->isVideoObject($el)) { + $video_url = $this->getVideoUrl($el); + + if (empty($video_url)) { + continue; + } + + $video_url = htmlspecialchars($video_url, ENT_QUOTES); + $el->after("{$video_url}"); + $new_dom_el = $el->next()->get(0); + + $actionTakenType = ActionTakenType::OBJECT_CONVERTED_TO_A; + } else { + continue; + } + + // Remove the object and its children + $el->removeChildren()->remove(); + $this->addActionTaken(new ActionTakenLine('object', $actionTakenType, $lineno, $context_string)); + $this->context->addLineAssociation($new_dom_el, $lineno); + + } + + return $this->transformations; + } + + protected function isVideoObject(DOMQuery $el) + { + $params = $el->find('param'); + foreach ($params as $param) { + if ($param->attr('name') == 'movie') { + return true; + } + } + return false; + } + + /** + * + * Get the Video Url + * + * @param DOMQuery $el + * @return string + */ + protected function getVideoUrl(DOMQuery $el) + { + $matches = []; + $video_url = ''; + $params = $el->find('param'); + + foreach ($params as $param) { + if ($param->attr('name') == 'movie') { + return $param->attr('value'); + } + } + + return $video_url; + } +} diff --git a/src/Pass/ObjectVimeoTagTransformPass.php b/src/Pass/ObjectVimeoTagTransformPass.php new file mode 100644 index 00000000..260ac463 --- /dev/null +++ b/src/Pass/ObjectVimeoTagTransformPass.php @@ -0,0 +1,127 @@ + tags which don't have noscript as an ancestor to tags + * + * This is what a vimeo embed looks like: + * + * + * + * + * + * + * + * @see https://github.com/ampproject/amphtml/blob/master/extensions/amp-vimeo/amp-vimeo.md + * + */ +class ObjectVimeoTagTransformPass extends BasePass +{ + function pass() + { + $all_objects = $this->q->find('object:not(noscript object)'); + /** @var DOMQuery $el */ + foreach ($all_objects as $el) { + /** @var \DOMElement $dom_el */ + $dom_el = $el->get(0); + $lineno = $this->getLineNo($dom_el); + $context_string = $this->getContextString($dom_el); + + $actionTakenType = ''; + + if ($this->isVimeoObject($el)) { + $vimeo_code = $this->getVimeoCode($el); + + // If we couldnt find a vimeo videoid then we abort + if (empty($vimeo_code)) { + continue; + } + + $el->after(''); + $new_dom_el = $el->next()->get(0); + + $actionTakenType = ActionTakenType::VIMEO_OBJECT_CONVERTED; + + } else { + continue; + } + + // Remove the object and its children + $el->removeChildren()->remove(); + $this->addActionTaken(new ActionTakenLine('object', $actionTakenType, $lineno, $context_string)); + $this->context->addLineAssociation($new_dom_el, $lineno); + + } + + return $this->transformations; + } + + protected function isVimeoObject(DOMQuery $el) + { + $params = $el->find('param'); + foreach ($params as $param) { + if ($param->attr('name') == 'movie') { + $param_value = $param->attr('value'); + if (preg_match('&(*UTF8)(vimeo\.com)&i', $param_value)) { + return true; + } + } + } + return false; + } + + /** + * + * Get the Vimeo Code + * + * @param DOMQuery $el + * @return string + */ + protected function getVimeoCode(DOMQuery $el) + { + $matches = []; + $vimeo_code = ''; + $params = $el->find('param'); + + foreach ($params as $param) { + if ($param->attr('name') == 'movie') { + $param_value = $param->attr('value'); + + $pattern = '#http://(?:\w+.)?vimeo.com/(?:video/|moogaloop\.swf\?clip_id=)(\w+)#i'; + if (preg_match($pattern, $param_value, $matches)) { + if (!empty($matches[1])) { + $vimeo_code = $matches[1]; + return $vimeo_code; + } + } + } + } + + return $vimeo_code; + } +} diff --git a/src/Pass/ObjectYouTubeTagTransformPass.php b/src/Pass/ObjectYouTubeTagTransformPass.php new file mode 100644 index 00000000..b1de2e3f --- /dev/null +++ b/src/Pass/ObjectYouTubeTagTransformPass.php @@ -0,0 +1,163 @@ + tags which don't have noscript as an ancestor to tags + * + * This is what a youtube embed looks like: + * + * + * + * + * + * + * + * @see https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube/amp-youtube.md + * + */ +class ObjectYouTubeTagTransformPass extends BasePass +{ + /** + * A standard youtube video aspect ratio + * @var float + */ + const DEFAULT_ASPECT_RATIO = 1.7778; + const DEFAULT_VIDEO_WIDTH = 560; + const DEFAULT_VIDEO_HEIGHT = 315; + + function pass() + { + $all_objects = $this->q->find('object:not(noscript object)'); + /** @var DOMQuery $el */ + foreach ($all_objects as $el) { + /** @var \DOMElement $dom_el */ + $dom_el = $el->get(0); + $lineno = $this->getLineNo($dom_el); + $context_string = $this->getContextString($dom_el); + + $actionTakenType = ''; + + if ($this->isYouTubeObject($el)) { + $youtube_code = $this->getYouTubeCode($el); + + // If we couldnt find a youtube videoid then we abort + if (empty($youtube_code)) { + continue; + } + + $el->after(""); + + $new_el = $el->next(); + $new_dom_el = $new_el->get(0); + + $actionTakenType = ActionTakenType::YOUTUBE_OBJECT_CONVERTED; + + $this->setStandardAttributesFrom($el, $new_el, self::DEFAULT_VIDEO_WIDTH, self::DEFAULT_VIDEO_HEIGHT, self::DEFAULT_ASPECT_RATIO); + + } else { + continue; + } + + // Remove the object and its children + $el->removeChildren()->remove(); + $this->addActionTaken(new ActionTakenLine('object', $actionTakenType, $lineno, $context_string)); + $this->context->addLineAssociation($new_dom_el, $lineno); + + } + + return $this->transformations; + } + + protected function isYouTubeObject(DOMQuery $el) + { + $params = $el->find('param'); + foreach ($params as $param) { + if ($param->attr('name') == 'movie') { + $param_value = $param->attr('value'); + if (preg_match('&(*UTF8)(youtube\.com|youtu\.be)&i', $param_value)) { + return true; + } + } + } + + return false; + } + + /** + * + * Get the youtube videoid + * + * @param DOMQuery $el + * @return string + */ + protected function getYouTubeCode(DOMQuery $el) + { + $matches = []; + $youtube_code = ''; + $params = $el->find('param'); + + foreach ($params as $param) { + if ($param->attr('name') == 'movie') { + $param_value = $param->attr('value'); + + $pattern = + '~(?#!js YouTubeId Rev:20160125_1800) + # Match non-linked youtube URL in the wild. (Rev:20130823) + https?:// # Required scheme. Either http or https. + (?:[0-9A-Z-]+\.)? # Optional subdomain. + (?: # Group host alternatives. + youtu\.be/ # Either youtu.be, + | youtube # or youtube.com or + (?:-nocookie)? # youtube-nocookie.com + \.com # followed by + \S*? # Allow anything up to VIDEO_ID, + [^\w\s-] # but char before ID is non-ID char. + ) # End host alternatives. + ([\w-]{11}) # $1: VIDEO_ID is exactly 11 chars. + (?=[^\w-]|$) # Assert next char is non-ID or EOS. + (?! # Assert URL is not pre-linked. + [?=&+%\w.-]* # Allow URL (query) remainder. + (?: # Group pre-linked alternatives. + [\'"][^<>]*> # Either inside a start tag, + | # or inside element text contents. + ) # End recognized pre-linked alts. + ) # End negative lookahead assertion. + [?=&+%\w.-]* # Consume any URL (query) remainder. + ~ix'; + if (preg_match($pattern, $param_value, $matches)) { + if (!empty($matches[1])) { + $youtube_code = $matches[1]; + return $youtube_code; + } + } + } + } + + return $youtube_code; + } +} diff --git a/src/Utility/ActionTakenType.php b/src/Utility/ActionTakenType.php index 52462b5c..60b88025 100644 --- a/src/Utility/ActionTakenType.php +++ b/src/Utility/ActionTakenType.php @@ -52,4 +52,7 @@ class ActionTakenType const ISSUE_RESOLVED = 'no further action required as this issue was resolved due to an earlier fix'; const AMP_IMG_FIX = 'tried to fix problems with amp-img by trying to fetch height, width from image directly and/or setting layout to responsive'; const AMP_IMG_FIX_RESPONSIVE = 'tried to fix problems with amp-img by setting layout to responsive'; + const YOUTUBE_OBJECT_CONVERTED = 'youtube object code was converted to the amp-youtube tag.'; + const VIMEO_OBJECT_CONVERTED = 'vimeo object code was converted to the amp-vimeo tag.'; + const OBJECT_CONVERTED_TO_A = 'video object code was converted to a#href tag.'; } diff --git a/tests/test-data/fragment-html/object-video-fragment.html b/tests/test-data/fragment-html/object-video-fragment.html new file mode 100644 index 00000000..18faf3a9 --- /dev/null +++ b/tests/test-data/fragment-html/object-video-fragment.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/test-data/fragment-html/object-video-fragment.html.out b/tests/test-data/fragment-html/object-video-fragment.html.out new file mode 100644 index 00000000..a00438d3 --- /dev/null +++ b/tests/test-data/fragment-html/object-video-fragment.html.out @@ -0,0 +1,26 @@ +http://video.golem.de/player/videoplayer.swf?id=2883&autoPl=false + +ORIGINAL HTML +--------------- +Line 1: +Line 2: +Line 3: +Line 4: +Line 5: +Line 6: + + +Transformations made from HTML tags to AMP custom tags +------------------------------------------------------- + + at line 1 + ACTION TAKEN: object video object code was converted to a#href tag. + + +AMP-HTML Validation Issues and Fixes +------------------------------------- +PASS + +COMPONENT NAMES WITH JS PATH +------------------------------ +No custom amp script includes required diff --git a/tests/test-data/fragment-html/object-vimeo-fragment.html b/tests/test-data/fragment-html/object-vimeo-fragment.html new file mode 100644 index 00000000..b758d71f --- /dev/null +++ b/tests/test-data/fragment-html/object-vimeo-fragment.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/test-data/fragment-html/object-vimeo-fragment.html.out b/tests/test-data/fragment-html/object-vimeo-fragment.html.out new file mode 100644 index 00000000..d68c46db --- /dev/null +++ b/tests/test-data/fragment-html/object-vimeo-fragment.html.out @@ -0,0 +1,27 @@ + + +ORIGINAL HTML +--------------- +Line 1: +Line 2: +Line 3: +Line 4: +Line 5: +Line 6: + + +Transformations made from HTML tags to AMP custom tags +------------------------------------------------------- + + at line 1 + ACTION TAKEN: object vimeo object code was converted to the amp-vimeo tag. + + +AMP-HTML Validation Issues and Fixes +------------------------------------- +PASS + +COMPONENT NAMES WITH JS PATH +------------------------------ +'amp-vimeo', include path 'https://cdn.ampproject.org/v0/amp-vimeo-0.1.js' + diff --git a/tests/test-data/fragment-html/object-youtube-fragment.html b/tests/test-data/fragment-html/object-youtube-fragment.html new file mode 100644 index 00000000..db7fa775 --- /dev/null +++ b/tests/test-data/fragment-html/object-youtube-fragment.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/test-data/fragment-html/object-youtube-fragment.html.out b/tests/test-data/fragment-html/object-youtube-fragment.html.out new file mode 100644 index 00000000..f61c359a --- /dev/null +++ b/tests/test-data/fragment-html/object-youtube-fragment.html.out @@ -0,0 +1,27 @@ + + +ORIGINAL HTML +--------------- +Line 1: +Line 2: +Line 3: +Line 4: +Line 5: +Line 6: + + +Transformations made from HTML tags to AMP custom tags +------------------------------------------------------- + + at line 1 + ACTION TAKEN: object youtube object code was converted to the amp-youtube tag. + + +AMP-HTML Validation Issues and Fixes +------------------------------------- +PASS + +COMPONENT NAMES WITH JS PATH +------------------------------ +'amp-youtube', include path 'https://cdn.ampproject.org/v0/amp-youtube-0.1.js' +