Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
renzit authored Mar 14, 2017
2 parents f38fb99 + bdf10c4 commit 1decfbb
Show file tree
Hide file tree
Showing 44 changed files with 3,262 additions and 2,220 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ before_script:

script:
- vendor/bin/phpunit tests

matrix:
allow_failures:
- php: nightly
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ $ ./amp-console amp:convert sample-html/several_errors.html --full-document
```
Note that you need to provide `--full-document` if you're providing a full html document file for conversion.

Lets see the output output of the first example command above. The first few lines is the AMPized HTML provided by our library. The rest of the headings are self explanatory.
Lets see the output of the first example command above. The first few lines is the AMPized HTML provided by our library. The rest of the headings are self explanatory.

```html
$ cd <amp-php-library-repo-cloned-location>
Expand Down
22 changes: 21 additions & 1 deletion src/AMP.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -174,8 +178,24 @@ public function loadHtml($html, $options = [])
$options['use_html5_parser'] = true;
}

// By default the convertion of img into amp-anim is disabled (because of ressource cost)
// => they will be converted into amp-img instead
if (!isset($options['use_img_anim_tag'])) {
$options['use_img_anim_tag'] = false;
}

// By default img that can't be converted are kept as it is and not removed
if (!isset($options['remove_non_converted_img_tag'])) {
$options['remove_non_converted_img_tag'] = false;
}

// By default the scope is 'body'
if (!isset($options['scope'])) {
$options['scope'] = Scope::BODY_SCOPE;
}

$this->options = $options;
$this->scope = !empty($options['scope']) ? $options['scope'] : Scope::BODY_SCOPE;
$this->scope = $options['scope'];

// Currently we only support these two scopes
if (!in_array($this->scope, [Scope::HTML_SCOPE, Scope::BODY_SCOPE])) {
Expand Down
11 changes: 11 additions & 0 deletions src/Pass/AmpImgFixPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ function pass()
$error->resolved = false;
}
}
elseif (in_array($error->code, [ValidationErrorCode::MANDATORY_TAG_ANCESTOR_WITH_HINT]) &&
!$error->resolved &&
!empty($error->dom_tag) &&
strtolower($error->dom_tag->tagName) == 'img' &&
!empty($this->options['remove_non_converted_img_tag'])
) {
// Remove the offending tag
$error->dom_tag->parentNode->removeChild($error->dom_tag);
$error->addActionTaken(new ActionTakenLine('img', ActionTakenType::TAG_REMOVED));
$error->resolved = TRUE;
}
}
}
}
49 changes: 49 additions & 0 deletions src/Pass/BaseFacebookPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/*
* Copyright 2016 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Lullabot\AMP\Pass;

abstract class BaseFacebookPass extends BasePass
{
/**
* Checks whether the given url is a valid facebook post url
*
* @param string $url
* @return bool
*/
protected function isValidPostUrl($url)
{
return
// e.g. https://www.facebook.com/20531316728/posts/10154009990506729/
preg_match('&(*UTF8)facebook\.com/.*/posts/\d+/?&i', $url)
// e.g. https://www.facebook.com/photos/{photo-id}
// or https://www.facebook.com/SanAntonioVAMC/photos/a.411451129506.185409.351086129506/10154231221264507/?type=3&amp;theater
|| preg_match('&(*UTF8)facebook\.com/.*photos.*&i', $url)
// e.g. https://www.facebook.com/photo.php?fbid={photo-id}
// or https://www.facebook.com/photo.php?v=10153655829445601
|| preg_match('&(*UTF8)facebook\.com/photo\.php.*&i', $url);
}

protected function isValidVideoUrl($url)
{
return
// e.g https://www.facebook.com/facebook/videos/10153231379946729/
preg_match('&(*UTF8)facebook\.com/.*/videos/\d+/?&i', $url)
// e.g https://www.facebook.com/video.php?v=10153231379946729
|| preg_match('&(*UTF8)facebook\.com/video\.php.*&i', $url);
}
}
9 changes: 4 additions & 5 deletions src/Pass/FacebookNonIframeTransformPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* @see https://github.com/ampproject/amphtml/blob/master/extensions/amp-facebook/amp-facebook.md
* @see https://github.com/ampproject/amphtml/blob/master/extensions/amp-facebook/0.1/validator-amp-facebook.protoascii
*/
class FacebookNonIframeTransformPass extends BasePass
class FacebookNonIframeTransformPass extends BaseFacebookPass
{
const DEFAULT_WIDTH = 500;
const DEFAULT_HEIGHT = 281;
Expand Down Expand Up @@ -109,17 +109,16 @@ protected function getFacebookEmbedAttrs(DOMQuery $el)
}

$card = true;
// e.g https://www.facebook.com/facebook/videos/10153231379946729/
if (preg_match('&(*UTF8)facebook\.com/.*/videos/\d+/?&i', $src)) {
if ($this->isValidVideoUrl($src)) {
// A facebook video can be embedded as a post. Doing that enables the video "card" to display
if ($el->attr('data-show-text') !== "false") {
$embed_as = 'post';
$card = true;
} else {
$embed_as = 'video';
}
} // e.g. https://www.facebook.com/20531316728/posts/10154009990506729/
else if (preg_match('&(*UTF8)facebook\.com/.*/posts/\d+/?&i', $src)) {
}
elseif ($this->isValidPostUrl($src)) {
$embed_as = 'post';
} else {
return false;
Expand Down
9 changes: 4 additions & 5 deletions src/Pass/IframeFacebookTagTransformPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
* @see https://github.com/ampproject/amphtml/blob/master/extensions/amp-facebook/amp-facebook.md
* @see https://github.com/ampproject/amphtml/blob/master/extensions/amp-facebook/0.1/validator-amp-facebook.protoascii
*/
class IframeFacebookTagTransformPass extends BasePass
class IframeFacebookTagTransformPass extends BaseFacebookPass
{
const DEFAULT_ASPECT_RATIO = 1.7778;
const DEFAULT_WIDTH = 500;
Expand Down Expand Up @@ -108,16 +108,15 @@ protected function setStandardFacebookParameters(DOMQuery $el)
return false;
}

// e.g https://www.facebook.com/facebook/videos/10153231379946729/
if (preg_match('&(*UTF8)facebook\.com/facebook/videos/\d+/?&i', $query_arr['href'])) {
if ($this->isValidVideoUrl($query_arr['href'])) {
// A facebook video can be embedded as a post. Doing that enables the video "card" to display
if (isset($query_arr['show_text']) && $query_arr['show_text'] !== "false") {
$embed_as = 'post';
} else {
$embed_as = 'video';
}
} // e.g. https://www.facebook.com/20531316728/posts/10154009990506729/
else if (preg_match('&(*UTF8)facebook\.com/.*/posts/\d+/?&i', $query_arr['href'])) {
}
elseif ($this->isValidPostUrl($query_arr['href'])) {
$embed_as = 'post';
} else {
return false;
Expand Down
28 changes: 8 additions & 20 deletions src/Pass/IframeYouTubeTagTransformPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,37 +113,25 @@ protected function isYouTubeIframe(DOMQuery $el)
*
* @param DOMQuery $el
* @return string
*
*/
protected function getYouTubeCode(DOMQuery $el)
{
$matches = [];
$youtube_code = '';
$href = $el->attr('src');

// @todo there seem to be a lot of ways to embed a youtube video. We probably need to capture all patterns here
// The next one is the embed code that youtube gives you
if (preg_match('&(*UTF8)/embed/([^/?]+)&i', $href, $matches)) {
if (!empty($matches[1])) {
$youtube_code = $matches[1];
return $youtube_code;
}
}

if (preg_match('&(*UTF8)youtu\.be/([^/?]+)&i', $href, $matches)) {
if (!empty($matches[1])) {
$youtube_code = $matches[1];
return $youtube_code;
}
}

if (preg_match('!(*UTF8)watch\?v=([^&]+)!i', $href, $matches)) {
if (!empty($matches[1])) {
// This regex is supposed to catch all possible way to embed youtube video
if (preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $href, $matches))
{
if (!empty($matches[1]))
{
$youtube_code = $matches[1];
return $youtube_code;
return htmlspecialchars($youtube_code);
}
}

return $youtube_code;
return htmlspecialchars($youtube_code);
}

/**
Expand Down
121 changes: 117 additions & 4 deletions src/Pass/ImgTagTransformPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ function pass()
}
if ($this->isPixel($el)) {
$new_dom_el = $this->convertAmpPixel($el, $lineno, $context_string);
}
else {
} else if (!empty($this->options['use_amp_anim_tag']) && $this->isAnimatedImg($dom_el)) {
$new_dom_el = $this->convertAmpAnim($el, $lineno, $context_string);
} else {
$new_dom_el = $this->convertAmpImg($el, $lineno, $context_string);
}
$this->context->addLineAssociation($new_dom_el, $lineno);
Expand Down Expand Up @@ -121,11 +122,42 @@ protected function convertAmpImg($el, $lineno, $context_string)
$dom_el = $el->get(0);
$new_dom_el = $this->cloneAndRenameDomElement($dom_el, 'amp-img');
$new_el = $el->prev();
$this->setLayoutIfNoLayout($new_el, 'responsive');
$this->setLayoutIfNoLayout($new_el, $this->getLayout($el));
$this->addActionTaken(new ActionTakenLine('img', ActionTakenType::IMG_CONVERTED, $lineno, $context_string));
return $new_dom_el;
}

/**
* Given an image DOMQuery
* Returns whether the image should have 'fixed' or 'responsive' layout
*
* @param DOMQuery $el
* @return string
*/
protected function getLayout($el) {
return (isset($this->options['img_max_fixed_layout_width'])
&& $this->options['img_max_fixed_layout_width'] >= $el->attr('width'))
? 'fixed' : 'responsive';
}

/**
* Given an animated image element returns an amp-anim element with the same attributes and children
*
* @param DOMQuery $el
* @param int $lineno
* @param string $context_string
* @return DOMElement
*/
protected function convertAmpAnim($el, $lineno, $context_string)
{
$dom_el = $el->get(0);
$new_dom_el = $this->cloneAndRenameDomElement($dom_el, 'amp-anim');
$new_el = $el->prev();
$this->setLayoutIfNoLayout($new_el, 'responsive');
$this->addActionTaken(new ActionTakenLine('img', ActionTakenType::IMG_ANIM_CONVERTED, $lineno, $context_string));
return $new_dom_el;
}

/**
* Given an image src attribute, try to get its dimensions
* Returns false on failure
Expand Down Expand Up @@ -175,6 +207,87 @@ protected function isPixel(DOMQuery $el)
return $el->attr('width') === '1' && $el->attr('height') === '1';
}

/**
* Detects if the img is animated. In that case we convert to <amp-anim> instead of <amp-img>
* @param \DOMElement $el
* @return bool
*/
protected function isAnimatedImg(\DOMElement $el)
{
$animated_type = ['gif', 'png'];
if (!$el->hasAttribute('src')) {
return true;
}

$src = trim($el->getAttribute('src'));
if (preg_match('/\.([a-z0-9]+)$/i', parse_url($src,PHP_URL_PATH), $match)) {
if (!empty($match[1]) && in_array(strtolower($match[1]), $animated_type)) {
if ($match[1] === "gif") {
if ($this->isAnimatedGif($src)) {
return true;
} else {
return false;
}
}
if ($this->isApng($src)) {
return true;
}
}
}

return false;
}

/**
* Identifies APNGs
* Written by Coda, functionified by Foone/Popcorn Mariachi#!9i78bPeIxI
* This code is in the public domain
*
* @see http://stackoverflow.com/a/4525194
* @see http://foone.org/apng/identify_apng.php
*
* @param string $src The filename
* @return bool true if the file is an APMG
*/
function isApng($src)
{
$img_bytes = @file_get_contents($src);
if ($img_bytes) {
if (strpos(substr($img_bytes, 0, strpos($img_bytes, 'IDAT')), 'acTL') !== false) {
return true;
}
}
return false;
}

/**
* Detects if the gif image is animated or not
* source: http://php.net/manual/en/function.imagecreatefromgif.php#104473
*
* @param string $filename
* @return bool
*/
function isAnimatedGif($filename) {
if (!($fh = @fopen($filename, 'rb')))
return FALSE;
$count = 0;
//an animated gif contains multiple "frames", with each frame having a
//header made up of:
// * a static 4-byte sequence (\x00\x21\xF9\x04)
// * 4 variable bytes
// * a static 2-byte sequence (\x00\x2C) (some variants may use \x00\x21 ?)

// We read through the file til we reach the end of the file, or we've found
// at least 2 frame headers
while (!feof($fh) && $count < 2) {
$chunk = fread($fh, 1024 * 100); //read 100kb at a time
$count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
}

fclose($fh);
return $count > 1;
}

/**
* @param string $src
* @return boolean|string
Expand Down Expand Up @@ -217,7 +330,7 @@ protected function setResponsiveImgHeightAndWidth(DOMQuery $el)
$wcss = new CssLengthAndUnit($el->attr('width'), false);
$hcss = new CssLengthAndUnit($el->attr('height'), false);

if ($wcss->is_set && $wcss->is_valid && $wcss->is_set && $wcss->is_valid && $wcss->unit == $hcss->unit) {
if ($wcss->is_set && $wcss->is_valid && $hcss->is_set && $hcss->is_valid && $wcss->unit == $hcss->unit) {
return true;
}

Expand Down
Loading

0 comments on commit 1decfbb

Please sign in to comment.