Version Description
(21st June 2021) = * Added: AMP Optimizer (SSR) Support #5034
Download this release
Release Info
Developer | mohammed_kaludi |
Plugin | AMP for WP – Accelerated Mobile Pages |
Version | 1.0.77.7 |
Comparing to | |
See all releases |
Code changes from version 1.0.77.6 to 1.0.77.7
- README.md +4 -1
- accelerated-moblie-pages.php +7 -2
- changelog.txt +3 -0
- components/components-core.php +1 -1
- components/featured-image/featured-image.php +1 -1
- includes/amp-optimizer-addon.php +26 -0
- includes/features/performance/performance-functions.php +5 -8
- includes/options/admin-config.php +21 -0
- includes/vendor/amp/includes/amp-post-template-actions.php +1 -0
- includes/vendor/amp/includes/sanitizers/class-amp-allowed-tags-generated.php +2 -2
- includes/vendor/css-parser/autoload.php +1 -1
- includes/vendor/css-parser/composer.json +5 -0
- includes/vendor/css-parser/composer.lock +11 -6
- includes/vendor/css-parser/composer/ClassLoader.php +38 -3
- includes/vendor/css-parser/composer/InstalledVersions.php +311 -0
- includes/vendor/css-parser/composer/autoload_classmap.php +68 -0
- includes/vendor/css-parser/composer/autoload_real.php +11 -6
- includes/vendor/css-parser/composer/autoload_static.php +74 -2
- includes/vendor/css-parser/composer/installed.json +58 -49
- includes/vendor/css-parser/composer/installed.php +37 -0
- includes/vendor/css-parser/composer/platform_check.php +28 -0
- includes/vendor/tool/Amp.php +355 -0
- includes/vendor/tool/Attribute.php +123 -0
- includes/vendor/tool/Cli/AmpExecutable.php +74 -0
- includes/vendor/tool/Cli/Colors.php +177 -0
- includes/vendor/tool/Cli/Command.php +47 -0
- includes/vendor/tool/Cli/Command/Optimize.php +81 -0
- includes/vendor/tool/Cli/Executable.php +409 -0
- includes/vendor/tool/Cli/LogLevel.php +116 -0
- includes/vendor/tool/Cli/Options.php +558 -0
- includes/vendor/tool/Cli/TableFormatter.php +361 -0
- includes/vendor/tool/CssLength.php +179 -0
- includes/vendor/tool/DevMode.php +85 -0
- includes/vendor/tool/Dom/Document.php +2148 -0
- includes/vendor/tool/Dom/Document/Encoding.php +54 -0
- includes/vendor/tool/Dom/Document/Option.php +65 -0
- includes/vendor/tool/Dom/Element.php +281 -0
- includes/vendor/tool/Dom/ElementDump.php +102 -0
- includes/vendor/tool/Dom/NodeWalker.php +59 -0
- includes/vendor/tool/Exception/AmpCliException.php +61 -0
- includes/vendor/tool/Exception/AmpException.php +13 -0
- includes/vendor/tool/Exception/Cli/InvalidArgument.php +52 -0
- includes/vendor/tool/Exception/Cli/InvalidColor.php +28 -0
- includes/vendor/tool/Exception/Cli/InvalidColumnFormat.php +52 -0
- includes/vendor/tool/Exception/Cli/InvalidCommand.php +42 -0
- includes/vendor/tool/Exception/Cli/InvalidOption.php +28 -0
- includes/vendor/tool/Exception/Cli/InvalidSapi.php +28 -0
- includes/vendor/tool/Exception/Cli/MissingArgument.php +41 -0
- includes/vendor/tool/Exception/FailedRemoteRequest.php +13 -0
- includes/vendor/tool/Exception/FailedToGetCachedResponse.php +28 -0
- includes/vendor/tool/Exception/FailedToGetFromRemoteUrl.php +90 -0
- includes/vendor/tool/Exception/FailedToParseUrl.php +27 -0
- includes/vendor/tool/Exception/FailedToRetrieveRequiredDomElement.php +56 -0
- includes/vendor/tool/Exception/MaxCssByteCountExceeded.php +43 -0
- includes/vendor/tool/Extension.php +50 -0
- includes/vendor/tool/Layout.php +55 -0
- includes/vendor/tool/LengthUnit.php +222 -0
- includes/vendor/tool/Optimizer/Configuration.php +89 -0
- includes/vendor/tool/Optimizer/Configuration/AmpRuntimeCssConfiguration.php +109 -0
- includes/vendor/tool/Optimizer/Configuration/BaseTransformerConfiguration.php +98 -0
- includes/vendor/tool/Optimizer/Configuration/OptimizeAmpBindConfiguration.php +63 -0
- includes/vendor/tool/Optimizer/Configuration/PreloadHeroImageConfiguration.php +107 -0
- includes/vendor/tool/Optimizer/Configuration/RewriteAmpUrlsConfiguration.php +185 -0
- includes/vendor/tool/Optimizer/Configuration/TransformedIdentifierConfiguration.php +62 -0
- includes/vendor/tool/Optimizer/CssRule.php +344 -0
- includes/vendor/tool/Optimizer/CssRules.php +140 -0
- includes/vendor/tool/Optimizer/DefaultConfiguration.php +170 -0
- includes/vendor/tool/Optimizer/Error.php +26 -0
- includes/vendor/tool/Optimizer/Error/CannotAdaptDocumentForSelfHosting.php +41 -0
- includes/vendor/tool/Optimizer/Error/CannotInlineRuntimeCss.php +50 -0
- includes/vendor/tool/Optimizer/Error/CannotPerformServerSideRendering.php +71 -0
- includes/vendor/tool/Optimizer/Error/CannotPreloadImage.php +38 -0
- includes/vendor/tool/Optimizer/Error/CannotRemoveBoilerplate.php +98 -0
- includes/vendor/tool/Optimizer/Error/ErrorProperties.php +51 -0
- includes/vendor/tool/Optimizer/Error/TooManyHeroImages.php +28 -0
- includes/vendor/tool/Optimizer/Error/UnknownError.php +15 -0
- includes/vendor/tool/Optimizer/ErrorCollection.php +71 -0
- includes/vendor/tool/Optimizer/Exception/AmpOptimizerException.php +15 -0
- includes/vendor/tool/Optimizer/Exception/InvalidArgument.php +28 -0
- includes/vendor/tool/Optimizer/Exception/InvalidConfiguration.php +29 -0
- includes/vendor/tool/Optimizer/Exception/InvalidConfigurationKey.php +43 -0
- includes/vendor/tool/Optimizer/Exception/InvalidConfigurationValue.php +46 -0
- includes/vendor/tool/Optimizer/Exception/InvalidHtmlAttribute.php +30 -0
- includes/vendor/tool/Optimizer/Exception/UnknownConfigurationClass.php +27 -0
- includes/vendor/tool/Optimizer/Exception/UnknownConfigurationKey.php +44 -0
- includes/vendor/tool/Optimizer/HeroImage.php +100 -0
- includes/vendor/tool/Optimizer/ImageDimensions.php +433 -0
- includes/vendor/tool/Optimizer/LocalFallback.php +50 -0
- includes/vendor/tool/Optimizer/TransformationEngine.php +151 -0
- includes/vendor/tool/Optimizer/Transformer.php +23 -0
- includes/vendor/tool/Optimizer/Transformer/AmpBoilerplate.php +172 -0
- includes/vendor/tool/Optimizer/Transformer/AmpBoilerplateErrorHandler.php +54 -0
- includes/vendor/tool/Optimizer/Transformer/AmpRuntimeCss.php +198 -0
- includes/vendor/tool/Optimizer/Transformer/OptimizeAmpBind.php +115 -0
- includes/vendor/tool/Optimizer/Transformer/PreloadHeroImage.php +726 -0
- includes/vendor/tool/Optimizer/Transformer/ReorderHead.php +366 -0
- includes/vendor/tool/Optimizer/Transformer/RewriteAmpUrls.php +358 -0
- includes/vendor/tool/Optimizer/Transformer/ServerSideRendering.php +1045 -0
- includes/vendor/tool/Optimizer/Transformer/TransformedIdentifier.php +88 -0
- includes/vendor/tool/Optimizer/TransformerConfiguration.php +33 -0
- includes/vendor/tool/RemoteGetRequest.php +25 -0
- includes/vendor/tool/RemoteRequest/CurlRemoteGetRequest.php +155 -0
- includes/vendor/tool/RemoteRequest/FallbackRemoteGetRequest.php +83 -0
- includes/vendor/tool/RemoteRequest/FilesystemRemoteGetRequest.php +66 -0
- includes/vendor/tool/RemoteRequest/RemoteGetRequestResponse.php +184 -0
- includes/vendor/tool/RemoteRequest/StubbedRemoteGetRequest.php +50 -0
- includes/vendor/tool/RequestDestination.php +102 -0
- includes/vendor/tool/Response.php +98 -0
- includes/vendor/tool/Role.php +524 -0
- includes/vendor/tool/RuntimeVersion.php +116 -0
- includes/vendor/tool/Tag.php +92 -0
- includes/vendor/tool/Url.php +300 -0
- readme.txt +4 -1
- templates/design-manager/design-1/404.php +1 -1
- templates/design-manager/design-1/archive.php +1 -1
- templates/design-manager/design-1/index.php +1 -1
- templates/design-manager/design-1/search.php +1 -1
- templates/design-manager/design-1/single.php +1 -1
- templates/design-manager/design-2/404.php +1 -1
- templates/design-manager/design-2/archive.php +1 -1
- templates/design-manager/design-2/index.php +1 -1
- templates/design-manager/design-2/search.php +1 -1
- templates/design-manager/design-2/single.php +1 -1
- templates/design-manager/design-3/404.php +1 -1
- templates/design-manager/design-3/archive.php +1 -1
- templates/design-manager/design-3/index.php +1 -1
- templates/design-manager/design-3/search.php +1 -1
- templates/design-manager/design-3/single.php +1 -1
- templates/design-manager/swift/style.php +6 -6
- templates/features.php +3 -0
README.md
CHANGED
@@ -4,7 +4,7 @@ Tags: AMP, accelerated mobile pages, mobile, amp project, google amp, amp wp, go
|
|
4 |
Donate link: https://www.paypal.me/Kaludi/25
|
5 |
Requires at least: 3.0
|
6 |
Tested up to: 5.7
|
7 |
-
Stable tag: 1.0.77.
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
|
@@ -193,6 +193,9 @@ Device testing done through [BrowserStack](https://www.browserstack.com)
|
|
193 |
|
194 |
== Changelog ==
|
195 |
|
|
|
|
|
|
|
196 |
= 1.0.77.6 (12th June 2021) =
|
197 |
* Fixed: Custom JavaScript is not allowed error when analytics added by All in one SEO #5062
|
198 |
|
4 |
Donate link: https://www.paypal.me/Kaludi/25
|
5 |
Requires at least: 3.0
|
6 |
Tested up to: 5.7
|
7 |
+
Stable tag: 1.0.77.7
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
|
193 |
|
194 |
== Changelog ==
|
195 |
|
196 |
+
= 1.0.77.7 (21st June 2021) =
|
197 |
+
* Added: AMP Optimizer (SSR) Support #5034
|
198 |
+
|
199 |
= 1.0.77.6 (12th June 2021) =
|
200 |
* Fixed: Custom JavaScript is not allowed error when analytics added by All in one SEO #5062
|
201 |
|
accelerated-moblie-pages.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
Plugin Name: Accelerated Mobile Pages
|
4 |
Plugin URI: https://wordpress.org/plugins/accelerated-mobile-pages/
|
5 |
Description: AMP for WP - Accelerated Mobile Pages for WordPress
|
6 |
-
Version: 1.0.77.
|
7 |
Author: Ahmed Kaludi, Mohammed Kaludi
|
8 |
Author URI: https://ampforwp.com/
|
9 |
Donate link: https://www.paypal.me/Kaludi/25
|
@@ -20,7 +20,7 @@ define('AMPFORWP_PLUGIN_DIR_URI', plugin_dir_url(__FILE__));
|
|
20 |
define('AMPFORWP_DISQUS_URL',plugin_dir_url(__FILE__).'includes/disqus.html');
|
21 |
define('AMPFORWP_IMAGE_DIR',plugin_dir_url(__FILE__).'images');
|
22 |
define('AMPFORWP_MAIN_PLUGIN_DIR', plugin_dir_path( __DIR__ ) );
|
23 |
-
define('AMPFORWP_VERSION','1.0.77.
|
24 |
define('AMPFORWP_EXTENSION_DIR',plugin_dir_path(__FILE__).'includes/options/extensions');
|
25 |
if(!defined('AMPFROWP_HOST_NAME')){
|
26 |
$urlinfo = get_bloginfo('url');
|
@@ -1560,4 +1560,9 @@ if(!function_exists('ampforwp_save_local_font')){
|
|
1560 |
}
|
1561 |
}
|
1562 |
}
|
|
|
|
|
|
|
|
|
|
|
1563 |
}
|
3 |
Plugin Name: Accelerated Mobile Pages
|
4 |
Plugin URI: https://wordpress.org/plugins/accelerated-mobile-pages/
|
5 |
Description: AMP for WP - Accelerated Mobile Pages for WordPress
|
6 |
+
Version: 1.0.77.7
|
7 |
Author: Ahmed Kaludi, Mohammed Kaludi
|
8 |
Author URI: https://ampforwp.com/
|
9 |
Donate link: https://www.paypal.me/Kaludi/25
|
20 |
define('AMPFORWP_DISQUS_URL',plugin_dir_url(__FILE__).'includes/disqus.html');
|
21 |
define('AMPFORWP_IMAGE_DIR',plugin_dir_url(__FILE__).'images');
|
22 |
define('AMPFORWP_MAIN_PLUGIN_DIR', plugin_dir_path( __DIR__ ) );
|
23 |
+
define('AMPFORWP_VERSION','1.0.77.7');
|
24 |
define('AMPFORWP_EXTENSION_DIR',plugin_dir_path(__FILE__).'includes/options/extensions');
|
25 |
if(!defined('AMPFROWP_HOST_NAME')){
|
26 |
$urlinfo = get_bloginfo('url');
|
1560 |
}
|
1561 |
}
|
1562 |
}
|
1563 |
+
}
|
1564 |
+
|
1565 |
+
add_action("amp_init", "ampforwp_amp_optimizer");
|
1566 |
+
function ampforwp_amp_optimizer(){
|
1567 |
+
require_once AMPFORWP_PLUGIN_DIR."/includes/amp-optimizer-addon.php";
|
1568 |
}
|
changelog.txt
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
== Changelog ==
|
2 |
|
|
|
|
|
|
|
3 |
= 1.0.77.6 (12th June 2021) =
|
4 |
* Fixed: Custom JavaScript is not allowed error when analytics added by All in one SEO #5062
|
5 |
|
1 |
== Changelog ==
|
2 |
|
3 |
+
= 1.0.77.7 (21st June 2021) =
|
4 |
+
* Added: AMP Optimizer (SSR) Support #5034
|
5 |
+
|
6 |
= 1.0.77.6 (12th June 2021) =
|
7 |
* Fixed: Custom JavaScript is not allowed error when analytics added by All in one SEO #5062
|
8 |
|
components/components-core.php
CHANGED
@@ -375,7 +375,7 @@ function amp_header_core(){
|
|
375 |
<head>
|
376 |
<meta charset="utf-8">
|
377 |
<?php do_action('amp_experiment_meta', $thisTemplate); ?>
|
378 |
-
<link rel="
|
379 |
<?php do_action( 'amp_meta', $thisTemplate ); ?>
|
380 |
<?php
|
381 |
if(ampforwp_amp_nonamp_convert("", "check")){
|
375 |
<head>
|
376 |
<meta charset="utf-8">
|
377 |
<?php do_action('amp_experiment_meta', $thisTemplate); ?>
|
378 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
379 |
<?php do_action( 'amp_meta', $thisTemplate ); ?>
|
380 |
<?php
|
381 |
if(ampforwp_amp_nonamp_convert("", "check")){
|
components/featured-image/featured-image.php
CHANGED
@@ -71,7 +71,7 @@ function ampforwp_framework_get_featured_image(){
|
|
71 |
if ( empty($srcet) ) {
|
72 |
$srcet = $image[0];
|
73 |
}
|
74 |
-
$amp_html = '<amp-img src="'.esc_url($image[0]).'" srcset="'.esc_html($srcet).'" width="'.esc_attr($image[1]).'" height="'.esc_attr($image[2]).'" layout=responsive alt="'.esc_attr($alt).'"></amp-img>';
|
75 |
}
|
76 |
}
|
77 |
elseif ( ampforwp_is_custom_field_featured_image() ) {
|
71 |
if ( empty($srcet) ) {
|
72 |
$srcet = $image[0];
|
73 |
}
|
74 |
+
$amp_html = '<amp-img data-hero src="'.esc_url($image[0]).'" srcset="'.esc_html($srcet).'" width="'.esc_attr($image[1]).'" height="'.esc_attr($image[2]).'" layout=responsive alt="'.esc_attr($alt).'"></amp-img>';
|
75 |
}
|
76 |
}
|
77 |
elseif ( ampforwp_is_custom_field_featured_image() ) {
|
includes/amp-optimizer-addon.php
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if (true == ampforwp_get_setting('amp-server-side-rendering')) {
|
3 |
+
add_filter('ampforwp_the_content_last_filter', 'ampforwp_add_optimizer_addon',12);// phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound
|
4 |
+
}
|
5 |
+
function ampforwp_add_optimizer_addon($output_buffer){
|
6 |
+
$ssr_settings = add_filter(
|
7 |
+
'amp_enable_ssr', true, defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound
|
8 |
+
);
|
9 |
+
if(!$ssr_settings){ return $output_buffer; }
|
10 |
+
if(!class_exists('AmpProject\Optimizer\Transformer\AmpRuntimeCss')){
|
11 |
+
require_once AMPFORWP_PLUGIN_DIR."/includes/vendor/css-parser/autoload.php";
|
12 |
+
}
|
13 |
+
|
14 |
+
$transformationEngine = new AmpProject\Optimizer\TransformationEngine(new AmpProject\Optimizer\DefaultConfiguration());
|
15 |
+
$errorCollection = new AmpProject\Optimizer\ErrorCollection;
|
16 |
+
|
17 |
+
$optimizedHtml = $transformationEngine->optimizeHtml( $output_buffer,$errorCollection);
|
18 |
+
|
19 |
+
//Handle and log the error
|
20 |
+
if($errorCollection->count()>0){
|
21 |
+
foreach ($errorCollection as $error) {
|
22 |
+
new WP_Error( $error->getCode(), $error->getMessage());
|
23 |
+
}
|
24 |
+
}
|
25 |
+
return $optimizedHtml;
|
26 |
+
}
|
includes/features/performance/performance-functions.php
CHANGED
@@ -2,11 +2,11 @@
|
|
2 |
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
exit;
|
4 |
}
|
5 |
-
use AMPforWP\AMPVendor\AMP_DOM_Utils;
|
6 |
// 86. minify the content of pages
|
7 |
add_filter('ampforwp_the_content_last_filter','ampforwp_minify_html_output');
|
8 |
function ampforwp_minify_html_output($content_buffer){
|
9 |
$content_buffer = str_replace('srcset=""', '', $content_buffer);
|
|
|
10 |
//Removed trbidi attribute #3687
|
11 |
$content_buffer = str_replace('trbidi="on"', '', $content_buffer);
|
12 |
$content_buffer = str_replace("trbidi='on'", '', $content_buffer);
|
@@ -316,7 +316,7 @@ if( !function_exists("ampforwp_tree_shaking_purify_amphtml") ){
|
|
316 |
$completeContent = str_replace(array('"\\', "'\\"), array('":backSlash:',"':backSlash:"), $completeContent);
|
317 |
/***Replacements***/
|
318 |
if(!empty($completeContent)){
|
319 |
-
$tmpDoc = AMP_DOM_Utils::get_dom_from_content($completeContent);
|
320 |
libxml_use_internal_errors(true);
|
321 |
$tmpDoc->loadHTML($completeContent);
|
322 |
$font_css = '';
|
@@ -338,14 +338,10 @@ if( !function_exists("ampforwp_tree_shaking_purify_amphtml") ){
|
|
338 |
$arg['allow_dirty_styles'] = false;
|
339 |
$obj = new AMPforWP\AMPVendor\AMP_treeshaking_Style_Sanitizer($tmpDoc, $arg);
|
340 |
$datatrack = $obj->sanitize();
|
341 |
-
|
342 |
-
|
343 |
$data = $obj->get_stylesheets();
|
344 |
-
//return json_encode($data);
|
345 |
-
|
346 |
$comment = $obj->get_comments();
|
347 |
-
|
348 |
-
|
349 |
foreach($data as $styles){
|
350 |
$sheet .= $styles;
|
351 |
}
|
@@ -355,6 +351,7 @@ if( !function_exists("ampforwp_tree_shaking_purify_amphtml") ){
|
|
355 |
if(strpos($sheet, '-keyframes')!==false){
|
356 |
$sheet = preg_replace("/@(-o-|-moz-|-webkit-|-ms-)*keyframes\s(.*?){([0-9%a-zA-Z,\s.]*{(.*?)})*[\s\n]*}/s", "", $sheet);
|
357 |
}
|
|
|
358 |
//TRANSPOSH PLUGIN RTL ISSUE FIXED #3895
|
359 |
if(class_exists('transposh_plugin')){
|
360 |
ampforwp_clear_css_on_transposh_rtl($sheet);
|
2 |
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
exit;
|
4 |
}
|
|
|
5 |
// 86. minify the content of pages
|
6 |
add_filter('ampforwp_the_content_last_filter','ampforwp_minify_html_output');
|
7 |
function ampforwp_minify_html_output($content_buffer){
|
8 |
$content_buffer = str_replace('srcset=""', '', $content_buffer);
|
9 |
+
$content_buffer = preg_replace('/<style amp-runtime(=""|)><\/style>/', '', $content_buffer);
|
10 |
//Removed trbidi attribute #3687
|
11 |
$content_buffer = str_replace('trbidi="on"', '', $content_buffer);
|
12 |
$content_buffer = str_replace("trbidi='on'", '', $content_buffer);
|
316 |
$completeContent = str_replace(array('"\\', "'\\"), array('":backSlash:',"':backSlash:"), $completeContent);
|
317 |
/***Replacements***/
|
318 |
if(!empty($completeContent)){
|
319 |
+
$tmpDoc = AMPforWP\AMPVendor\AMP_DOM_Utils::get_dom_from_content($completeContent);
|
320 |
libxml_use_internal_errors(true);
|
321 |
$tmpDoc->loadHTML($completeContent);
|
322 |
$font_css = '';
|
338 |
$arg['allow_dirty_styles'] = false;
|
339 |
$obj = new AMPforWP\AMPVendor\AMP_treeshaking_Style_Sanitizer($tmpDoc, $arg);
|
340 |
$datatrack = $obj->sanitize();
|
341 |
+
|
|
|
342 |
$data = $obj->get_stylesheets();
|
|
|
|
|
343 |
$comment = $obj->get_comments();
|
344 |
+
|
|
|
345 |
foreach($data as $styles){
|
346 |
$sheet .= $styles;
|
347 |
}
|
351 |
if(strpos($sheet, '-keyframes')!==false){
|
352 |
$sheet = preg_replace("/@(-o-|-moz-|-webkit-|-ms-)*keyframes\s(.*?){([0-9%a-zA-Z,\s.]*{(.*?)})*[\s\n]*}/s", "", $sheet);
|
353 |
}
|
354 |
+
|
355 |
//TRANSPOSH PLUGIN RTL ISSUE FIXED #3895
|
356 |
if(class_exists('transposh_plugin')){
|
357 |
ampforwp_clear_css_on_transposh_rtl($sheet);
|
includes/options/admin-config.php
CHANGED
@@ -3030,6 +3030,13 @@ Redux::setSection( $opt_name, array(
|
|
3030 |
'default' => 1,
|
3031 |
'required' => array( 'amp-mobile-redirection', '=' , 1 )
|
3032 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3033 |
array(
|
3034 |
'id' => 'amp-redirection-search',
|
3035 |
'type' => 'switch',
|
@@ -3100,6 +3107,20 @@ Redux::setSection( $opt_name, array(
|
|
3100 |
'tooltip-subtitle' => sprintf('%s <a href="%s" target="_blank">%s</a> %s',
|
3101 |
esc_html__('Enable this option if you want a disable the right click in AMP to protect your data from copying', 'accelerated-mobile-pages'), esc_url('https://ampforwp.com/tutorials/article/how-to-disable-right-click-in-amp/'),esc_html__('Click Here','accelerated-mobile-pages'), esc_html__('for more info','accelerated-mobile-pages')),
|
3102 |
'default' => false,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3103 |
),
|
3104 |
array(
|
3105 |
'id' => 'amp-header-text-area-for-html',
|
3030 |
'default' => 1,
|
3031 |
'required' => array( 'amp-mobile-redirection', '=' , 1 )
|
3032 |
),
|
3033 |
+
array(
|
3034 |
+
'id' => 'amp-server-side-rendering',
|
3035 |
+
'type' => 'switch',
|
3036 |
+
'title' => esc_html__('Server Side Rendering', 'accelerated-mobile-pages'),
|
3037 |
+
'tooltip-subtitle' => esc_html__('Improve the Google Page Speed and Loading time with Server Side Rendering', 'accelerated-mobile-pages'),
|
3038 |
+
'default' => 0
|
3039 |
+
),
|
3040 |
array(
|
3041 |
'id' => 'amp-redirection-search',
|
3042 |
'type' => 'switch',
|
3107 |
'tooltip-subtitle' => sprintf('%s <a href="%s" target="_blank">%s</a> %s',
|
3108 |
esc_html__('Enable this option if you want a disable the right click in AMP to protect your data from copying', 'accelerated-mobile-pages'), esc_url('https://ampforwp.com/tutorials/article/how-to-disable-right-click-in-amp/'),esc_html__('Click Here','accelerated-mobile-pages'), esc_html__('for more info','accelerated-mobile-pages')),
|
3109 |
'default' => false,
|
3110 |
+
),
|
3111 |
+
array(
|
3112 |
+
'id' => 'ampforwp-meta-viewport',
|
3113 |
+
'type' => 'switch',
|
3114 |
+
'title' => esc_html__('Full Meta Viewport', 'accelerated-mobile-pages'),
|
3115 |
+
'tooltip-subtitle' => esc_html__('Enable this option if you want full meta viewport','accelerated-mobile-pages'),
|
3116 |
+
'default' => 0,
|
3117 |
+
),
|
3118 |
+
array(
|
3119 |
+
'id' => 'ampforwp-meta-viewport-notice',
|
3120 |
+
'type' => 'info',
|
3121 |
+
'style' => 'info',
|
3122 |
+
'desc' => esc_html__('Enabling this causes a 300-350ms tap delay which can decrease FID ( First Input Delay ). Please use this with caution.', 'accelerated-mobile-pages'),
|
3123 |
+
'required' => array('ampforwp-meta-viewport', '=', 1)
|
3124 |
),
|
3125 |
array(
|
3126 |
'id' => 'amp-header-text-area-for-html',
|
includes/vendor/amp/includes/amp-post-template-actions.php
CHANGED
@@ -106,6 +106,7 @@ function amp_post_template_add_fonts( $amp_template ) {
|
|
106 |
add_action( 'amp_post_template_head', 'AMPforWP\\AMPVendor\\amp_post_template_add_boilerplate_css' );
|
107 |
function amp_post_template_add_boilerplate_css( $amp_template ) {
|
108 |
?>
|
|
|
109 |
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
|
110 |
<?php
|
111 |
}
|
106 |
add_action( 'amp_post_template_head', 'AMPforWP\\AMPVendor\\amp_post_template_add_boilerplate_css' );
|
107 |
function amp_post_template_add_boilerplate_css( $amp_template ) {
|
108 |
?>
|
109 |
+
<style amp-runtime></style>
|
110 |
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
|
111 |
<?php
|
112 |
}
|
includes/vendor/amp/includes/sanitizers/class-amp-allowed-tags-generated.php
CHANGED
@@ -10178,7 +10178,7 @@ class AMP_Allowed_Tags_Generated {
|
|
10178 |
'error_message' => 'CSS !important',
|
10179 |
'regex' => '!important',
|
10180 |
),
|
10181 |
-
'max_bytes' =>
|
10182 |
'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#maximum-size',
|
10183 |
),
|
10184 |
'tag_spec' => array(
|
@@ -10238,7 +10238,7 @@ class AMP_Allowed_Tags_Generated {
|
|
10238 |
),
|
10239 |
),
|
10240 |
'cdata' => array(
|
10241 |
-
'max_bytes' =>
|
10242 |
'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#keyframes-stylesheet',
|
10243 |
),
|
10244 |
'tag_spec' => array(
|
10178 |
'error_message' => 'CSS !important',
|
10179 |
'regex' => '!important',
|
10180 |
),
|
10181 |
+
'max_bytes' => 75000,
|
10182 |
'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#maximum-size',
|
10183 |
),
|
10184 |
'tag_spec' => array(
|
10238 |
),
|
10239 |
),
|
10240 |
'cdata' => array(
|
10241 |
+
'max_bytes' => 750000,
|
10242 |
'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#keyframes-stylesheet',
|
10243 |
),
|
10244 |
'tag_spec' => array(
|
includes/vendor/css-parser/autoload.php
CHANGED
@@ -6,4 +6,4 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
6 |
|
7 |
require_once __DIR__ . '/composer/autoload_real.php';
|
8 |
|
9 |
-
return
|
6 |
|
7 |
require_once __DIR__ . '/composer/autoload_real.php';
|
8 |
|
9 |
+
return ComposerAutoloaderInit36c553b877d93c41f97bfeeed7ef5433::getLoader();
|
includes/vendor/css-parser/composer.json
CHANGED
@@ -1,5 +1,10 @@
|
|
1 |
{
|
2 |
"require": {
|
3 |
"sabberworm/php-css-parser": "*"
|
|
|
|
|
|
|
|
|
|
|
4 |
}
|
5 |
}
|
1 |
{
|
2 |
"require": {
|
3 |
"sabberworm/php-css-parser": "*"
|
4 |
+
},
|
5 |
+
"autoload": {
|
6 |
+
"classmap": [
|
7 |
+
"../tool"
|
8 |
+
]
|
9 |
}
|
10 |
}
|
includes/vendor/css-parser/composer.lock
CHANGED
@@ -8,16 +8,16 @@
|
|
8 |
"packages": [
|
9 |
{
|
10 |
"name": "sabberworm/php-css-parser",
|
11 |
-
"version": "8.3.
|
12 |
"source": {
|
13 |
"type": "git",
|
14 |
"url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
|
15 |
-
"reference": "
|
16 |
},
|
17 |
"dist": {
|
18 |
"type": "zip",
|
19 |
-
"url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/
|
20 |
-
"reference": "
|
21 |
"shasum": ""
|
22 |
},
|
23 |
"require": {
|
@@ -49,7 +49,11 @@
|
|
49 |
"parser",
|
50 |
"stylesheet"
|
51 |
],
|
52 |
-
"
|
|
|
|
|
|
|
|
|
53 |
}
|
54 |
],
|
55 |
"packages-dev": [],
|
@@ -59,5 +63,6 @@
|
|
59 |
"prefer-stable": false,
|
60 |
"prefer-lowest": false,
|
61 |
"platform": [],
|
62 |
-
"platform-dev": []
|
|
|
63 |
}
|
8 |
"packages": [
|
9 |
{
|
10 |
"name": "sabberworm/php-css-parser",
|
11 |
+
"version": "8.3.1",
|
12 |
"source": {
|
13 |
"type": "git",
|
14 |
"url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
|
15 |
+
"reference": "d217848e1396ef962fb1997cf3e2421acba7f796"
|
16 |
},
|
17 |
"dist": {
|
18 |
"type": "zip",
|
19 |
+
"url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/d217848e1396ef962fb1997cf3e2421acba7f796",
|
20 |
+
"reference": "d217848e1396ef962fb1997cf3e2421acba7f796",
|
21 |
"shasum": ""
|
22 |
},
|
23 |
"require": {
|
49 |
"parser",
|
50 |
"stylesheet"
|
51 |
],
|
52 |
+
"support": {
|
53 |
+
"issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
|
54 |
+
"source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.3.1"
|
55 |
+
},
|
56 |
+
"time": "2020-06-01T09:10:00+00:00"
|
57 |
}
|
58 |
],
|
59 |
"packages-dev": [],
|
63 |
"prefer-stable": false,
|
64 |
"prefer-lowest": false,
|
65 |
"platform": [],
|
66 |
+
"platform-dev": [],
|
67 |
+
"plugin-api-version": "2.0.0"
|
68 |
}
|
includes/vendor/css-parser/composer/ClassLoader.php
CHANGED
@@ -14,6 +14,7 @@ namespace Composer\Autoload;
|
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit;
|
16 |
}
|
|
|
17 |
/**
|
18 |
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
19 |
*
|
@@ -39,11 +40,13 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
39 |
*
|
40 |
* @author Fabien Potencier <fabien@symfony.com>
|
41 |
* @author Jordi Boggiano <j.boggiano@seld.be>
|
42 |
-
* @see
|
43 |
-
* @see
|
44 |
*/
|
45 |
class ClassLoader
|
46 |
{
|
|
|
|
|
47 |
// PSR-4
|
48 |
private $prefixLengthsPsr4 = array();
|
49 |
private $prefixDirsPsr4 = array();
|
@@ -59,10 +62,17 @@ class ClassLoader
|
|
59 |
private $missingClasses = array();
|
60 |
private $apcuPrefix;
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
public function getPrefixes()
|
63 |
{
|
64 |
if (!empty($this->prefixesPsr0)) {
|
65 |
-
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
66 |
}
|
67 |
|
68 |
return array();
|
@@ -302,6 +312,17 @@ class ClassLoader
|
|
302 |
public function register($prepend = false)
|
303 |
{
|
304 |
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
}
|
306 |
|
307 |
/**
|
@@ -310,6 +331,10 @@ class ClassLoader
|
|
310 |
public function unregister()
|
311 |
{
|
312 |
spl_autoload_unregister(array($this, 'loadClass'));
|
|
|
|
|
|
|
|
|
313 |
}
|
314 |
|
315 |
/**
|
@@ -369,6 +394,16 @@ class ClassLoader
|
|
369 |
return $file;
|
370 |
}
|
371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
private function findFileWithExtension($class, $ext)
|
373 |
{
|
374 |
// PSR-4 lookup
|
14 |
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
exit;
|
16 |
}
|
17 |
+
|
18 |
/**
|
19 |
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
20 |
*
|
40 |
*
|
41 |
* @author Fabien Potencier <fabien@symfony.com>
|
42 |
* @author Jordi Boggiano <j.boggiano@seld.be>
|
43 |
+
* @see https://www.php-fig.org/psr/psr-0/
|
44 |
+
* @see https://www.php-fig.org/psr/psr-4/
|
45 |
*/
|
46 |
class ClassLoader
|
47 |
{
|
48 |
+
private $vendorDir;
|
49 |
+
|
50 |
// PSR-4
|
51 |
private $prefixLengthsPsr4 = array();
|
52 |
private $prefixDirsPsr4 = array();
|
62 |
private $missingClasses = array();
|
63 |
private $apcuPrefix;
|
64 |
|
65 |
+
private static $registeredLoaders = array();
|
66 |
+
|
67 |
+
public function __construct($vendorDir = null)
|
68 |
+
{
|
69 |
+
$this->vendorDir = $vendorDir;
|
70 |
+
}
|
71 |
+
|
72 |
public function getPrefixes()
|
73 |
{
|
74 |
if (!empty($this->prefixesPsr0)) {
|
75 |
+
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
76 |
}
|
77 |
|
78 |
return array();
|
312 |
public function register($prepend = false)
|
313 |
{
|
314 |
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
315 |
+
|
316 |
+
if (null === $this->vendorDir) {
|
317 |
+
return;
|
318 |
+
}
|
319 |
+
|
320 |
+
if ($prepend) {
|
321 |
+
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
322 |
+
} else {
|
323 |
+
unset(self::$registeredLoaders[$this->vendorDir]);
|
324 |
+
self::$registeredLoaders[$this->vendorDir] = $this;
|
325 |
+
}
|
326 |
}
|
327 |
|
328 |
/**
|
331 |
public function unregister()
|
332 |
{
|
333 |
spl_autoload_unregister(array($this, 'loadClass'));
|
334 |
+
|
335 |
+
if (null !== $this->vendorDir) {
|
336 |
+
unset(self::$registeredLoaders[$this->vendorDir]);
|
337 |
+
}
|
338 |
}
|
339 |
|
340 |
/**
|
394 |
return $file;
|
395 |
}
|
396 |
|
397 |
+
/**
|
398 |
+
* Returns the currently registered loaders indexed by their corresponding vendor directories.
|
399 |
+
*
|
400 |
+
* @return self[]
|
401 |
+
*/
|
402 |
+
public static function getRegisteredLoaders()
|
403 |
+
{
|
404 |
+
return self::$registeredLoaders;
|
405 |
+
}
|
406 |
+
|
407 |
private function findFileWithExtension($class, $ext)
|
408 |
{
|
409 |
// PSR-4 lookup
|
includes/vendor/css-parser/composer/InstalledVersions.php
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
|
4 |
+
|
5 |
+
|
6 |
+
|
7 |
+
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
|
12 |
+
|
13 |
+
namespace Composer;
|
14 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
15 |
+
exit;
|
16 |
+
}
|
17 |
+
|
18 |
+
use Composer\Autoload\ClassLoader;
|
19 |
+
use Composer\Semver\VersionParser;
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
|
24 |
+
|
25 |
+
|
26 |
+
|
27 |
+
|
28 |
+
class InstalledVersions
|
29 |
+
{
|
30 |
+
private static $installed = array (
|
31 |
+
'root' =>
|
32 |
+
array (
|
33 |
+
'pretty_version' => 'dev-1.0.77.5',
|
34 |
+
'version' => '1.0.77.5-dev',
|
35 |
+
'aliases' =>
|
36 |
+
array (
|
37 |
+
),
|
38 |
+
'reference' => '34136b1c4bddda73204cff45e139744bd24bc33a',
|
39 |
+
'name' => '__root__',
|
40 |
+
),
|
41 |
+
'versions' =>
|
42 |
+
array (
|
43 |
+
'__root__' =>
|
44 |
+
array (
|
45 |
+
'pretty_version' => 'dev-1.0.77.5',
|
46 |
+
'version' => '1.0.77.5-dev',
|
47 |
+
'aliases' =>
|
48 |
+
array (
|
49 |
+
),
|
50 |
+
'reference' => '34136b1c4bddda73204cff45e139744bd24bc33a',
|
51 |
+
),
|
52 |
+
'sabberworm/php-css-parser' =>
|
53 |
+
array (
|
54 |
+
'pretty_version' => '8.3.1',
|
55 |
+
'version' => '8.3.1.0',
|
56 |
+
'aliases' =>
|
57 |
+
array (
|
58 |
+
),
|
59 |
+
'reference' => 'd217848e1396ef962fb1997cf3e2421acba7f796',
|
60 |
+
),
|
61 |
+
),
|
62 |
+
);
|
63 |
+
private static $canGetVendors;
|
64 |
+
private static $installedByVendor = array();
|
65 |
+
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
|
70 |
+
|
71 |
+
|
72 |
+
public static function getInstalledPackages()
|
73 |
+
{
|
74 |
+
$packages = array();
|
75 |
+
foreach (self::getInstalled() as $installed) {
|
76 |
+
$packages[] = array_keys($installed['versions']);
|
77 |
+
}
|
78 |
+
|
79 |
+
if (1 === \count($packages)) {
|
80 |
+
return $packages[0];
|
81 |
+
}
|
82 |
+
|
83 |
+
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
84 |
+
}
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
|
89 |
+
|
90 |
+
|
91 |
+
|
92 |
+
|
93 |
+
|
94 |
+
public static function isInstalled($packageName)
|
95 |
+
{
|
96 |
+
foreach (self::getInstalled() as $installed) {
|
97 |
+
if (isset($installed['versions'][$packageName])) {
|
98 |
+
return true;
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
+
return false;
|
103 |
+
}
|
104 |
+
|
105 |
+
|
106 |
+
|
107 |
+
|
108 |
+
|
109 |
+
|
110 |
+
|
111 |
+
|
112 |
+
|
113 |
+
|
114 |
+
|
115 |
+
|
116 |
+
|
117 |
+
|
118 |
+
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
119 |
+
{
|
120 |
+
$constraint = $parser->parseConstraints($constraint);
|
121 |
+
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
122 |
+
|
123 |
+
return $provided->matches($constraint);
|
124 |
+
}
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
|
129 |
+
|
130 |
+
|
131 |
+
|
132 |
+
|
133 |
+
|
134 |
+
|
135 |
+
public static function getVersionRanges($packageName)
|
136 |
+
{
|
137 |
+
foreach (self::getInstalled() as $installed) {
|
138 |
+
if (!isset($installed['versions'][$packageName])) {
|
139 |
+
continue;
|
140 |
+
}
|
141 |
+
|
142 |
+
$ranges = array();
|
143 |
+
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
144 |
+
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
145 |
+
}
|
146 |
+
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
147 |
+
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
148 |
+
}
|
149 |
+
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
150 |
+
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
151 |
+
}
|
152 |
+
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
153 |
+
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
154 |
+
}
|
155 |
+
|
156 |
+
return implode(' || ', $ranges);
|
157 |
+
}
|
158 |
+
|
159 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
160 |
+
}
|
161 |
+
|
162 |
+
|
163 |
+
|
164 |
+
|
165 |
+
|
166 |
+
public static function getVersion($packageName)
|
167 |
+
{
|
168 |
+
foreach (self::getInstalled() as $installed) {
|
169 |
+
if (!isset($installed['versions'][$packageName])) {
|
170 |
+
continue;
|
171 |
+
}
|
172 |
+
|
173 |
+
if (!isset($installed['versions'][$packageName]['version'])) {
|
174 |
+
return null;
|
175 |
+
}
|
176 |
+
|
177 |
+
return $installed['versions'][$packageName]['version'];
|
178 |
+
}
|
179 |
+
|
180 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
181 |
+
}
|
182 |
+
|
183 |
+
|
184 |
+
|
185 |
+
|
186 |
+
|
187 |
+
public static function getPrettyVersion($packageName)
|
188 |
+
{
|
189 |
+
foreach (self::getInstalled() as $installed) {
|
190 |
+
if (!isset($installed['versions'][$packageName])) {
|
191 |
+
continue;
|
192 |
+
}
|
193 |
+
|
194 |
+
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
195 |
+
return null;
|
196 |
+
}
|
197 |
+
|
198 |
+
return $installed['versions'][$packageName]['pretty_version'];
|
199 |
+
}
|
200 |
+
|
201 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
202 |
+
}
|
203 |
+
|
204 |
+
|
205 |
+
|
206 |
+
|
207 |
+
|
208 |
+
public static function getReference($packageName)
|
209 |
+
{
|
210 |
+
foreach (self::getInstalled() as $installed) {
|
211 |
+
if (!isset($installed['versions'][$packageName])) {
|
212 |
+
continue;
|
213 |
+
}
|
214 |
+
|
215 |
+
if (!isset($installed['versions'][$packageName]['reference'])) {
|
216 |
+
return null;
|
217 |
+
}
|
218 |
+
|
219 |
+
return $installed['versions'][$packageName]['reference'];
|
220 |
+
}
|
221 |
+
|
222 |
+
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
223 |
+
}
|
224 |
+
|
225 |
+
|
226 |
+
|
227 |
+
|
228 |
+
|
229 |
+
public static function getRootPackage()
|
230 |
+
{
|
231 |
+
$installed = self::getInstalled();
|
232 |
+
|
233 |
+
return $installed[0]['root'];
|
234 |
+
}
|
235 |
+
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
|
240 |
+
|
241 |
+
|
242 |
+
|
243 |
+
public static function getRawData()
|
244 |
+
{
|
245 |
+
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
246 |
+
|
247 |
+
return self::$installed;
|
248 |
+
}
|
249 |
+
|
250 |
+
|
251 |
+
|
252 |
+
|
253 |
+
|
254 |
+
|
255 |
+
|
256 |
+
public static function getAllRawData()
|
257 |
+
{
|
258 |
+
return self::getInstalled();
|
259 |
+
}
|
260 |
+
|
261 |
+
|
262 |
+
|
263 |
+
|
264 |
+
|
265 |
+
|
266 |
+
|
267 |
+
|
268 |
+
|
269 |
+
|
270 |
+
|
271 |
+
|
272 |
+
|
273 |
+
|
274 |
+
|
275 |
+
|
276 |
+
|
277 |
+
|
278 |
+
|
279 |
+
public static function reload($data)
|
280 |
+
{
|
281 |
+
self::$installed = $data;
|
282 |
+
self::$installedByVendor = array();
|
283 |
+
}
|
284 |
+
|
285 |
+
|
286 |
+
|
287 |
+
|
288 |
+
|
289 |
+
private static function getInstalled()
|
290 |
+
{
|
291 |
+
if (null === self::$canGetVendors) {
|
292 |
+
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
293 |
+
}
|
294 |
+
|
295 |
+
$installed = array();
|
296 |
+
|
297 |
+
if (self::$canGetVendors) {
|
298 |
+
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
299 |
+
if (isset(self::$installedByVendor[$vendorDir])) {
|
300 |
+
$installed[] = self::$installedByVendor[$vendorDir];
|
301 |
+
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
302 |
+
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
303 |
+
}
|
304 |
+
}
|
305 |
+
}
|
306 |
+
|
307 |
+
$installed[] = self::$installed;
|
308 |
+
|
309 |
+
return $installed;
|
310 |
+
}
|
311 |
+
}
|
includes/vendor/css-parser/composer/autoload_classmap.php
CHANGED
@@ -8,4 +8,72 @@ $vendorDir = dirname(dirname(__FILE__));
|
|
8 |
$baseDir = dirname($vendorDir);
|
9 |
|
10 |
return array(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
);
|
8 |
$baseDir = dirname($vendorDir);
|
9 |
|
10 |
return array(
|
11 |
+
'AmpProject\\Amp' => $baseDir . '/tool/Amp.php',
|
12 |
+
'AmpProject\\Attribute' => $baseDir . '/tool/Attribute.php',
|
13 |
+
'AmpProject\\CssLength' => $baseDir . '/tool/CssLength.php',
|
14 |
+
'AmpProject\\DevMode' => $baseDir . '/tool/DevMode.php',
|
15 |
+
'AmpProject\\Dom\\Document' => $baseDir . '/tool/Dom/Document.php',
|
16 |
+
'AmpProject\\Dom\\Document\\Encoding' => $baseDir . '/tool/Dom/Document/Encoding.php',
|
17 |
+
'AmpProject\\Dom\\Document\\Option' => $baseDir . '/tool/Dom/Document/Option.php',
|
18 |
+
'AmpProject\\Dom\\Element' => $baseDir . '/tool/Dom/Element.php',
|
19 |
+
'AmpProject\\Dom\\ElementDump' => $baseDir . '/tool/Dom/ElementDump.php',
|
20 |
+
'AmpProject\\Dom\\NodeWalker' => $baseDir . '/tool/Dom/NodeWalker.php',
|
21 |
+
'AmpProject\\Extension' => $baseDir . '/tool/Extension.php',
|
22 |
+
'AmpProject\\Layout' => $baseDir . '/tool/Layout.php',
|
23 |
+
'AmpProject\\LengthUnit' => $baseDir . '/tool/LengthUnit.php',
|
24 |
+
'AmpProject\\Optimizer\\Configuration' => $baseDir . '/tool/Optimizer/Configuration.php',
|
25 |
+
'AmpProject\\Optimizer\\Configuration\\AmpRuntimeCssConfiguration' => $baseDir . '/tool/Optimizer/Configuration/AmpRuntimeCssConfiguration.php',
|
26 |
+
'AmpProject\\Optimizer\\Configuration\\BaseTransformerConfiguration' => $baseDir . '/tool/Optimizer/Configuration/BaseTransformerConfiguration.php',
|
27 |
+
'AmpProject\\Optimizer\\Configuration\\OptimizeAmpBindConfiguration' => $baseDir . '/tool/Optimizer/Configuration/OptimizeAmpBindConfiguration.php',
|
28 |
+
'AmpProject\\Optimizer\\Configuration\\PreloadHeroImageConfiguration' => $baseDir . '/tool/Optimizer/Configuration/PreloadHeroImageConfiguration.php',
|
29 |
+
'AmpProject\\Optimizer\\Configuration\\RewriteAmpUrlsConfiguration' => $baseDir . '/tool/Optimizer/Configuration/RewriteAmpUrlsConfiguration.php',
|
30 |
+
'AmpProject\\Optimizer\\Configuration\\TransformedIdentifierConfiguration' => $baseDir . '/tool/Optimizer/Configuration/TransformedIdentifierConfiguration.php',
|
31 |
+
'AmpProject\\Optimizer\\CssRule' => $baseDir . '/tool/Optimizer/CssRule.php',
|
32 |
+
'AmpProject\\Optimizer\\CssRules' => $baseDir . '/tool/Optimizer/CssRules.php',
|
33 |
+
'AmpProject\\Optimizer\\DefaultConfiguration' => $baseDir . '/tool/Optimizer/DefaultConfiguration.php',
|
34 |
+
'AmpProject\\Optimizer\\Error' => $baseDir . '/tool/Optimizer/Error.php',
|
35 |
+
'AmpProject\\Optimizer\\ErrorCollection' => $baseDir . '/tool/Optimizer/ErrorCollection.php',
|
36 |
+
'AmpProject\\Optimizer\\Error\\CannotAdaptDocumentForSelfHosting' => $baseDir . '/tool/Optimizer/Error/CannotAdaptDocumentForSelfHosting.php',
|
37 |
+
'AmpProject\\Optimizer\\Error\\CannotInlineRuntimeCss' => $baseDir . '/tool/Optimizer/Error/CannotInlineRuntimeCss.php',
|
38 |
+
'AmpProject\\Optimizer\\Error\\CannotPerformServerSideRendering' => $baseDir . '/tool/Optimizer/Error/CannotPerformServerSideRendering.php',
|
39 |
+
'AmpProject\\Optimizer\\Error\\CannotPreloadImage' => $baseDir . '/tool/Optimizer/Error/CannotPreloadImage.php',
|
40 |
+
'AmpProject\\Optimizer\\Error\\CannotRemoveBoilerplate' => $baseDir . '/tool/Optimizer/Error/CannotRemoveBoilerplate.php',
|
41 |
+
'AmpProject\\Optimizer\\Error\\ErrorProperties' => $baseDir . '/tool/Optimizer/Error/ErrorProperties.php',
|
42 |
+
'AmpProject\\Optimizer\\Error\\TooManyHeroImages' => $baseDir . '/tool/Optimizer/Error/TooManyHeroImages.php',
|
43 |
+
'AmpProject\\Optimizer\\Error\\UnknownError' => $baseDir . '/tool/Optimizer/Error/UnknownError.php',
|
44 |
+
'AmpProject\\Optimizer\\Exception\\AmpOptimizerException' => $baseDir . '/tool/Optimizer/Exception/AmpOptimizerException.php',
|
45 |
+
'AmpProject\\Optimizer\\Exception\\InvalidArgument' => $baseDir . '/tool/Optimizer/Exception/InvalidArgument.php',
|
46 |
+
'AmpProject\\Optimizer\\Exception\\InvalidConfiguration' => $baseDir . '/tool/Optimizer/Exception/InvalidConfiguration.php',
|
47 |
+
'AmpProject\\Optimizer\\Exception\\InvalidConfigurationKey' => $baseDir . '/tool/Optimizer/Exception/InvalidConfigurationKey.php',
|
48 |
+
'AmpProject\\Optimizer\\Exception\\InvalidConfigurationValue' => $baseDir . '/tool/Optimizer/Exception/InvalidConfigurationValue.php',
|
49 |
+
'AmpProject\\Optimizer\\Exception\\InvalidHtmlAttribute' => $baseDir . '/tool/Optimizer/Exception/InvalidHtmlAttribute.php',
|
50 |
+
'AmpProject\\Optimizer\\Exception\\UnknownConfigurationClass' => $baseDir . '/tool/Optimizer/Exception/UnknownConfigurationClass.php',
|
51 |
+
'AmpProject\\Optimizer\\Exception\\UnknownConfigurationKey' => $baseDir . '/tool/Optimizer/Exception/UnknownConfigurationKey.php',
|
52 |
+
'AmpProject\\Optimizer\\HeroImage' => $baseDir . '/tool/Optimizer/HeroImage.php',
|
53 |
+
'AmpProject\\Optimizer\\ImageDimensions' => $baseDir . '/tool/Optimizer/ImageDimensions.php',
|
54 |
+
'AmpProject\\Optimizer\\LocalFallback' => $baseDir . '/tool/Optimizer/LocalFallback.php',
|
55 |
+
'AmpProject\\Optimizer\\TransformationEngine' => $baseDir . '/tool/Optimizer/TransformationEngine.php',
|
56 |
+
'AmpProject\\Optimizer\\Transformer' => $baseDir . '/tool/Optimizer/Transformer.php',
|
57 |
+
'AmpProject\\Optimizer\\TransformerConfiguration' => $baseDir . '/tool/Optimizer/TransformerConfiguration.php',
|
58 |
+
'AmpProject\\Optimizer\\Transformer\\AmpBoilerplate' => $baseDir . '/tool/Optimizer/Transformer/AmpBoilerplate.php',
|
59 |
+
'AmpProject\\Optimizer\\Transformer\\AmpBoilerplateErrorHandler' => $baseDir . '/tool/Optimizer/Transformer/AmpBoilerplateErrorHandler.php',
|
60 |
+
'AmpProject\\Optimizer\\Transformer\\AmpRuntimeCss' => $baseDir . '/tool/Optimizer/Transformer/AmpRuntimeCss.php',
|
61 |
+
'AmpProject\\Optimizer\\Transformer\\OptimizeAmpBind' => $baseDir . '/tool/Optimizer/Transformer/OptimizeAmpBind.php',
|
62 |
+
'AmpProject\\Optimizer\\Transformer\\PreloadHeroImage' => $baseDir . '/tool/Optimizer/Transformer/PreloadHeroImage.php',
|
63 |
+
'AmpProject\\Optimizer\\Transformer\\ReorderHead' => $baseDir . '/tool/Optimizer/Transformer/ReorderHead.php',
|
64 |
+
'AmpProject\\Optimizer\\Transformer\\RewriteAmpUrls' => $baseDir . '/tool/Optimizer/Transformer/RewriteAmpUrls.php',
|
65 |
+
'AmpProject\\Optimizer\\Transformer\\ServerSideRendering' => $baseDir . '/tool/Optimizer/Transformer/ServerSideRendering.php',
|
66 |
+
'AmpProject\\Optimizer\\Transformer\\TransformedIdentifier' => $baseDir . '/tool/Optimizer/Transformer/TransformedIdentifier.php',
|
67 |
+
'AmpProject\\RemoteGetRequest' => $baseDir . '/tool/RemoteGetRequest.php',
|
68 |
+
'AmpProject\\RemoteRequest\\CurlRemoteGetRequest' => $baseDir . '/tool/RemoteRequest/CurlRemoteGetRequest.php',
|
69 |
+
'AmpProject\\RemoteRequest\\FallbackRemoteGetRequest' => $baseDir . '/tool/RemoteRequest/FallbackRemoteGetRequest.php',
|
70 |
+
'AmpProject\\RemoteRequest\\FilesystemRemoteGetRequest' => $baseDir . '/tool/RemoteRequest/FilesystemRemoteGetRequest.php',
|
71 |
+
'AmpProject\\RemoteRequest\\RemoteGetRequestResponse' => $baseDir . '/tool/RemoteRequest/RemoteGetRequestResponse.php',
|
72 |
+
'AmpProject\\RemoteRequest\\StubbedRemoteGetRequest' => $baseDir . '/tool/RemoteRequest/StubbedRemoteGetRequest.php',
|
73 |
+
'AmpProject\\Response' => $baseDir . '/tool/Response.php',
|
74 |
+
'AmpProject\\Role' => $baseDir . '/tool/Role.php',
|
75 |
+
'AmpProject\\RuntimeVersion' => $baseDir . '/tool/RuntimeVersion.php',
|
76 |
+
'AmpProject\\Tag' => $baseDir . '/tool/Tag.php',
|
77 |
+
'AmpProject\\Url' => $baseDir . '/tool/Url.php',
|
78 |
+
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
79 |
);
|
includes/vendor/css-parser/composer/autoload_real.php
CHANGED
@@ -4,7 +4,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
4 |
}
|
5 |
// autoload_real.php @generated by Composer
|
6 |
|
7 |
-
class
|
8 |
{
|
9 |
private static $loader;
|
10 |
|
@@ -15,21 +15,26 @@ class ComposerAutoloaderInit117444c18d5d7304d751024fddbe3a07
|
|
15 |
}
|
16 |
}
|
17 |
|
|
|
|
|
|
|
18 |
public static function getLoader()
|
19 |
{
|
20 |
if (null !== self::$loader) {
|
21 |
return self::$loader;
|
22 |
}
|
23 |
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
|
|
27 |
|
28 |
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
29 |
if ($useStaticLoader) {
|
30 |
-
|
31 |
|
32 |
-
call_user_func(\Composer\Autoload\
|
33 |
} else {
|
34 |
$map = require __DIR__ . '/autoload_namespaces.php';
|
35 |
foreach ($map as $namespace => $path) {
|
4 |
}
|
5 |
// autoload_real.php @generated by Composer
|
6 |
|
7 |
+
class ComposerAutoloaderInit36c553b877d93c41f97bfeeed7ef5433
|
8 |
{
|
9 |
private static $loader;
|
10 |
|
15 |
}
|
16 |
}
|
17 |
|
18 |
+
/**
|
19 |
+
* @return \Composer\Autoload\ClassLoader
|
20 |
+
*/
|
21 |
public static function getLoader()
|
22 |
{
|
23 |
if (null !== self::$loader) {
|
24 |
return self::$loader;
|
25 |
}
|
26 |
|
27 |
+
require __DIR__ . '/platform_check.php';
|
28 |
+
|
29 |
+
spl_autoload_register(array('ComposerAutoloaderInit36c553b877d93c41f97bfeeed7ef5433', 'loadClassLoader'), true, true);
|
30 |
+
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
|
31 |
+
spl_autoload_unregister(array('ComposerAutoloaderInit36c553b877d93c41f97bfeeed7ef5433', 'loadClassLoader'));
|
32 |
|
33 |
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
34 |
if ($useStaticLoader) {
|
35 |
+
require __DIR__ . '/autoload_static.php';
|
36 |
|
37 |
+
call_user_func(\Composer\Autoload\ComposerStaticInit36c553b877d93c41f97bfeeed7ef5433::getInitializer($loader));
|
38 |
} else {
|
39 |
$map = require __DIR__ . '/autoload_namespaces.php';
|
40 |
foreach ($map as $namespace => $path) {
|
includes/vendor/css-parser/composer/autoload_static.php
CHANGED
@@ -6,7 +6,7 @@ namespace Composer\Autoload;
|
|
6 |
if ( ! defined( 'ABSPATH' ) ) {
|
7 |
exit;
|
8 |
}
|
9 |
-
class
|
10 |
{
|
11 |
public static $prefixesPsr0 = array (
|
12 |
'S' =>
|
@@ -18,10 +18,82 @@ class ComposerStaticInit117444c18d5d7304d751024fddbe3a07
|
|
18 |
),
|
19 |
);
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
public static function getInitializer(ClassLoader $loader)
|
22 |
{
|
23 |
return \Closure::bind(function () use ($loader) {
|
24 |
-
$loader->prefixesPsr0 =
|
|
|
25 |
|
26 |
}, null, ClassLoader::class);
|
27 |
}
|
6 |
if ( ! defined( 'ABSPATH' ) ) {
|
7 |
exit;
|
8 |
}
|
9 |
+
class ComposerStaticInit36c553b877d93c41f97bfeeed7ef5433
|
10 |
{
|
11 |
public static $prefixesPsr0 = array (
|
12 |
'S' =>
|
18 |
),
|
19 |
);
|
20 |
|
21 |
+
public static $classMap = array (
|
22 |
+
'AmpProject\\Amp' => __DIR__ . '/../..' . '/tool/Amp.php',
|
23 |
+
'AmpProject\\Attribute' => __DIR__ . '/../..' . '/tool/Attribute.php',
|
24 |
+
'AmpProject\\CssLength' => __DIR__ . '/../..' . '/tool/CssLength.php',
|
25 |
+
'AmpProject\\DevMode' => __DIR__ . '/../..' . '/tool/DevMode.php',
|
26 |
+
'AmpProject\\Dom\\Document' => __DIR__ . '/../..' . '/tool/Dom/Document.php',
|
27 |
+
'AmpProject\\Dom\\Document\\Encoding' => __DIR__ . '/../..' . '/tool/Dom/Document/Encoding.php',
|
28 |
+
'AmpProject\\Dom\\Document\\Option' => __DIR__ . '/../..' . '/tool/Dom/Document/Option.php',
|
29 |
+
'AmpProject\\Dom\\Element' => __DIR__ . '/../..' . '/tool/Dom/Element.php',
|
30 |
+
'AmpProject\\Dom\\ElementDump' => __DIR__ . '/../..' . '/tool/Dom/ElementDump.php',
|
31 |
+
'AmpProject\\Dom\\NodeWalker' => __DIR__ . '/../..' . '/tool/Dom/NodeWalker.php',
|
32 |
+
'AmpProject\\Extension' => __DIR__ . '/../..' . '/tool/Extension.php',
|
33 |
+
'AmpProject\\Layout' => __DIR__ . '/../..' . '/tool/Layout.php',
|
34 |
+
'AmpProject\\LengthUnit' => __DIR__ . '/../..' . '/tool/LengthUnit.php',
|
35 |
+
'AmpProject\\Optimizer\\Configuration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration.php',
|
36 |
+
'AmpProject\\Optimizer\\Configuration\\AmpRuntimeCssConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration/AmpRuntimeCssConfiguration.php',
|
37 |
+
'AmpProject\\Optimizer\\Configuration\\BaseTransformerConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration/BaseTransformerConfiguration.php',
|
38 |
+
'AmpProject\\Optimizer\\Configuration\\OptimizeAmpBindConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration/OptimizeAmpBindConfiguration.php',
|
39 |
+
'AmpProject\\Optimizer\\Configuration\\PreloadHeroImageConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration/PreloadHeroImageConfiguration.php',
|
40 |
+
'AmpProject\\Optimizer\\Configuration\\RewriteAmpUrlsConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration/RewriteAmpUrlsConfiguration.php',
|
41 |
+
'AmpProject\\Optimizer\\Configuration\\TransformedIdentifierConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Configuration/TransformedIdentifierConfiguration.php',
|
42 |
+
'AmpProject\\Optimizer\\CssRule' => __DIR__ . '/../..' . '/tool/Optimizer/CssRule.php',
|
43 |
+
'AmpProject\\Optimizer\\CssRules' => __DIR__ . '/../..' . '/tool/Optimizer/CssRules.php',
|
44 |
+
'AmpProject\\Optimizer\\DefaultConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/DefaultConfiguration.php',
|
45 |
+
'AmpProject\\Optimizer\\Error' => __DIR__ . '/../..' . '/tool/Optimizer/Error.php',
|
46 |
+
'AmpProject\\Optimizer\\ErrorCollection' => __DIR__ . '/../..' . '/tool/Optimizer/ErrorCollection.php',
|
47 |
+
'AmpProject\\Optimizer\\Error\\CannotAdaptDocumentForSelfHosting' => __DIR__ . '/../..' . '/tool/Optimizer/Error/CannotAdaptDocumentForSelfHosting.php',
|
48 |
+
'AmpProject\\Optimizer\\Error\\CannotInlineRuntimeCss' => __DIR__ . '/../..' . '/tool/Optimizer/Error/CannotInlineRuntimeCss.php',
|
49 |
+
'AmpProject\\Optimizer\\Error\\CannotPerformServerSideRendering' => __DIR__ . '/../..' . '/tool/Optimizer/Error/CannotPerformServerSideRendering.php',
|
50 |
+
'AmpProject\\Optimizer\\Error\\CannotPreloadImage' => __DIR__ . '/../..' . '/tool/Optimizer/Error/CannotPreloadImage.php',
|
51 |
+
'AmpProject\\Optimizer\\Error\\CannotRemoveBoilerplate' => __DIR__ . '/../..' . '/tool/Optimizer/Error/CannotRemoveBoilerplate.php',
|
52 |
+
'AmpProject\\Optimizer\\Error\\ErrorProperties' => __DIR__ . '/../..' . '/tool/Optimizer/Error/ErrorProperties.php',
|
53 |
+
'AmpProject\\Optimizer\\Error\\TooManyHeroImages' => __DIR__ . '/../..' . '/tool/Optimizer/Error/TooManyHeroImages.php',
|
54 |
+
'AmpProject\\Optimizer\\Error\\UnknownError' => __DIR__ . '/../..' . '/tool/Optimizer/Error/UnknownError.php',
|
55 |
+
'AmpProject\\Optimizer\\Exception\\AmpOptimizerException' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/AmpOptimizerException.php',
|
56 |
+
'AmpProject\\Optimizer\\Exception\\InvalidArgument' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/InvalidArgument.php',
|
57 |
+
'AmpProject\\Optimizer\\Exception\\InvalidConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/InvalidConfiguration.php',
|
58 |
+
'AmpProject\\Optimizer\\Exception\\InvalidConfigurationKey' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/InvalidConfigurationKey.php',
|
59 |
+
'AmpProject\\Optimizer\\Exception\\InvalidConfigurationValue' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/InvalidConfigurationValue.php',
|
60 |
+
'AmpProject\\Optimizer\\Exception\\InvalidHtmlAttribute' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/InvalidHtmlAttribute.php',
|
61 |
+
'AmpProject\\Optimizer\\Exception\\UnknownConfigurationClass' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/UnknownConfigurationClass.php',
|
62 |
+
'AmpProject\\Optimizer\\Exception\\UnknownConfigurationKey' => __DIR__ . '/../..' . '/tool/Optimizer/Exception/UnknownConfigurationKey.php',
|
63 |
+
'AmpProject\\Optimizer\\HeroImage' => __DIR__ . '/../..' . '/tool/Optimizer/HeroImage.php',
|
64 |
+
'AmpProject\\Optimizer\\ImageDimensions' => __DIR__ . '/../..' . '/tool/Optimizer/ImageDimensions.php',
|
65 |
+
'AmpProject\\Optimizer\\LocalFallback' => __DIR__ . '/../..' . '/tool/Optimizer/LocalFallback.php',
|
66 |
+
'AmpProject\\Optimizer\\TransformationEngine' => __DIR__ . '/../..' . '/tool/Optimizer/TransformationEngine.php',
|
67 |
+
'AmpProject\\Optimizer\\Transformer' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer.php',
|
68 |
+
'AmpProject\\Optimizer\\TransformerConfiguration' => __DIR__ . '/../..' . '/tool/Optimizer/TransformerConfiguration.php',
|
69 |
+
'AmpProject\\Optimizer\\Transformer\\AmpBoilerplate' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/AmpBoilerplate.php',
|
70 |
+
'AmpProject\\Optimizer\\Transformer\\AmpBoilerplateErrorHandler' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/AmpBoilerplateErrorHandler.php',
|
71 |
+
'AmpProject\\Optimizer\\Transformer\\AmpRuntimeCss' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/AmpRuntimeCss.php',
|
72 |
+
'AmpProject\\Optimizer\\Transformer\\OptimizeAmpBind' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/OptimizeAmpBind.php',
|
73 |
+
'AmpProject\\Optimizer\\Transformer\\PreloadHeroImage' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/PreloadHeroImage.php',
|
74 |
+
'AmpProject\\Optimizer\\Transformer\\ReorderHead' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/ReorderHead.php',
|
75 |
+
'AmpProject\\Optimizer\\Transformer\\RewriteAmpUrls' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/RewriteAmpUrls.php',
|
76 |
+
'AmpProject\\Optimizer\\Transformer\\ServerSideRendering' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/ServerSideRendering.php',
|
77 |
+
'AmpProject\\Optimizer\\Transformer\\TransformedIdentifier' => __DIR__ . '/../..' . '/tool/Optimizer/Transformer/TransformedIdentifier.php',
|
78 |
+
'AmpProject\\RemoteGetRequest' => __DIR__ . '/../..' . '/tool/RemoteGetRequest.php',
|
79 |
+
'AmpProject\\RemoteRequest\\CurlRemoteGetRequest' => __DIR__ . '/../..' . '/tool/RemoteRequest/CurlRemoteGetRequest.php',
|
80 |
+
'AmpProject\\RemoteRequest\\FallbackRemoteGetRequest' => __DIR__ . '/../..' . '/tool/RemoteRequest/FallbackRemoteGetRequest.php',
|
81 |
+
'AmpProject\\RemoteRequest\\FilesystemRemoteGetRequest' => __DIR__ . '/../..' . '/tool/RemoteRequest/FilesystemRemoteGetRequest.php',
|
82 |
+
'AmpProject\\RemoteRequest\\RemoteGetRequestResponse' => __DIR__ . '/../..' . '/tool/RemoteRequest/RemoteGetRequestResponse.php',
|
83 |
+
'AmpProject\\RemoteRequest\\StubbedRemoteGetRequest' => __DIR__ . '/../..' . '/tool/RemoteRequest/StubbedRemoteGetRequest.php',
|
84 |
+
'AmpProject\\Response' => __DIR__ . '/../..' . '/tool/Response.php',
|
85 |
+
'AmpProject\\Role' => __DIR__ . '/../..' . '/tool/Role.php',
|
86 |
+
'AmpProject\\RuntimeVersion' => __DIR__ . '/../..' . '/tool/RuntimeVersion.php',
|
87 |
+
'AmpProject\\Tag' => __DIR__ . '/../..' . '/tool/Tag.php',
|
88 |
+
'AmpProject\\Url' => __DIR__ . '/../..' . '/tool/Url.php',
|
89 |
+
'Composer\\InstalledVersions' => __DIR__ . '/../..' . '/composer/InstalledVersions.php',
|
90 |
+
);
|
91 |
+
|
92 |
public static function getInitializer(ClassLoader $loader)
|
93 |
{
|
94 |
return \Closure::bind(function () use ($loader) {
|
95 |
+
$loader->prefixesPsr0 = ComposerStaticInit36c553b877d93c41f97bfeeed7ef5433::$prefixesPsr0;
|
96 |
+
$loader->classMap = ComposerStaticInit36c553b877d93c41f97bfeeed7ef5433::$classMap;
|
97 |
|
98 |
}, null, ClassLoader::class);
|
99 |
}
|
includes/vendor/css-parser/composer/installed.json
CHANGED
@@ -1,49 +1,58 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
"
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
"
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
"
|
19 |
-
|
20 |
-
|
21 |
-
"
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
"
|
29 |
-
"
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
"
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
"
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"packages": [
|
3 |
+
{
|
4 |
+
"name": "sabberworm/php-css-parser",
|
5 |
+
"version": "8.3.1",
|
6 |
+
"version_normalized": "8.3.1.0",
|
7 |
+
"source": {
|
8 |
+
"type": "git",
|
9 |
+
"url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
|
10 |
+
"reference": "d217848e1396ef962fb1997cf3e2421acba7f796"
|
11 |
+
},
|
12 |
+
"dist": {
|
13 |
+
"type": "zip",
|
14 |
+
"url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/d217848e1396ef962fb1997cf3e2421acba7f796",
|
15 |
+
"reference": "d217848e1396ef962fb1997cf3e2421acba7f796",
|
16 |
+
"shasum": ""
|
17 |
+
},
|
18 |
+
"require": {
|
19 |
+
"php": ">=5.3.2"
|
20 |
+
},
|
21 |
+
"require-dev": {
|
22 |
+
"codacy/coverage": "^1.4",
|
23 |
+
"phpunit/phpunit": "~4.8"
|
24 |
+
},
|
25 |
+
"time": "2020-06-01T09:10:00+00:00",
|
26 |
+
"type": "library",
|
27 |
+
"installation-source": "dist",
|
28 |
+
"autoload": {
|
29 |
+
"psr-0": {
|
30 |
+
"Sabberworm\\CSS": "lib/"
|
31 |
+
}
|
32 |
+
},
|
33 |
+
"notification-url": "https://packagist.org/downloads/",
|
34 |
+
"license": [
|
35 |
+
"MIT"
|
36 |
+
],
|
37 |
+
"authors": [
|
38 |
+
{
|
39 |
+
"name": "Raphael Schweikert"
|
40 |
+
}
|
41 |
+
],
|
42 |
+
"description": "Parser for CSS Files written in PHP",
|
43 |
+
"homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
|
44 |
+
"keywords": [
|
45 |
+
"css",
|
46 |
+
"parser",
|
47 |
+
"stylesheet"
|
48 |
+
],
|
49 |
+
"support": {
|
50 |
+
"issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
|
51 |
+
"source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.3.1"
|
52 |
+
},
|
53 |
+
"install-path": "../sabberworm/php-css-parser"
|
54 |
+
}
|
55 |
+
],
|
56 |
+
"dev": true,
|
57 |
+
"dev-package-names": []
|
58 |
+
}
|
includes/vendor/css-parser/composer/installed.php
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
return array (
|
6 |
+
'root' =>
|
7 |
+
array (
|
8 |
+
'pretty_version' => 'dev-1.0.77.5',
|
9 |
+
'version' => '1.0.77.5-dev',
|
10 |
+
'aliases' =>
|
11 |
+
array (
|
12 |
+
),
|
13 |
+
'reference' => '34136b1c4bddda73204cff45e139744bd24bc33a',
|
14 |
+
'name' => '__root__',
|
15 |
+
),
|
16 |
+
'versions' =>
|
17 |
+
array (
|
18 |
+
'__root__' =>
|
19 |
+
array (
|
20 |
+
'pretty_version' => 'dev-1.0.77.5',
|
21 |
+
'version' => '1.0.77.5-dev',
|
22 |
+
'aliases' =>
|
23 |
+
array (
|
24 |
+
),
|
25 |
+
'reference' => '34136b1c4bddda73204cff45e139744bd24bc33a',
|
26 |
+
),
|
27 |
+
'sabberworm/php-css-parser' =>
|
28 |
+
array (
|
29 |
+
'pretty_version' => '8.3.1',
|
30 |
+
'version' => '8.3.1.0',
|
31 |
+
'aliases' =>
|
32 |
+
array (
|
33 |
+
),
|
34 |
+
'reference' => 'd217848e1396ef962fb1997cf3e2421acba7f796',
|
35 |
+
),
|
36 |
+
),
|
37 |
+
);
|
includes/vendor/css-parser/composer/platform_check.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( ! defined( 'ABSPATH' ) ) {
|
3 |
+
exit;
|
4 |
+
}
|
5 |
+
// platform_check.php @generated by Composer
|
6 |
+
|
7 |
+
$issues = array();
|
8 |
+
|
9 |
+
if (!(PHP_VERSION_ID >= 50302)) {
|
10 |
+
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.3.2". You are running ' . PHP_VERSION . '.';
|
11 |
+
}
|
12 |
+
|
13 |
+
if ($issues) {
|
14 |
+
if (!headers_sent()) {
|
15 |
+
header('HTTP/1.1 500 Internal Server Error');
|
16 |
+
}
|
17 |
+
if (!ini_get('display_errors')) {
|
18 |
+
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
19 |
+
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
20 |
+
} elseif (!headers_sent()) {
|
21 |
+
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
22 |
+
}
|
23 |
+
}
|
24 |
+
trigger_error(
|
25 |
+
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
26 |
+
E_USER_ERROR
|
27 |
+
);
|
28 |
+
}
|
includes/vendor/tool/Amp.php
ADDED
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Document;
|
6 |
+
use AmpProject\Dom\Element;
|
7 |
+
use DOMNode;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Central helper functionality for all Amp-related PHP code.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class Amp
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* List of AMP attribute tags that can be appended to the <html> element.
|
19 |
+
*
|
20 |
+
* The *_ALT version represent a Unicode variation of the lightning emoji.
|
21 |
+
* @see https://github.com/ampproject/amphtml/issues/25990
|
22 |
+
*
|
23 |
+
* @var string[]
|
24 |
+
*/
|
25 |
+
const TAGS = [
|
26 |
+
Attribute::AMP,
|
27 |
+
Attribute::AMP_EMOJI,
|
28 |
+
Attribute::AMP_EMOJI_ALT,
|
29 |
+
Attribute::AMP4ADS,
|
30 |
+
Attribute::AMP4ADS_EMOJI,
|
31 |
+
Attribute::AMP4ADS_EMOJI_ALT,
|
32 |
+
Attribute::AMP4EMAIL,
|
33 |
+
Attribute::AMP4EMAIL_EMOJI,
|
34 |
+
Attribute::AMP4EMAIL_EMOJI_ALT,
|
35 |
+
];
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Host and scheme of the AMP cache.
|
39 |
+
*
|
40 |
+
* @var string
|
41 |
+
*/
|
42 |
+
const CACHE_HOST = 'https://cdn.ampproject.org';
|
43 |
+
|
44 |
+
/**
|
45 |
+
* URL of the AMP cache.
|
46 |
+
*
|
47 |
+
* @var string
|
48 |
+
*/
|
49 |
+
const CACHE_ROOT_URL = self::CACHE_HOST . '/';
|
50 |
+
|
51 |
+
/**
|
52 |
+
* List of valid AMP formats.
|
53 |
+
*
|
54 |
+
* @var string[]
|
55 |
+
*/
|
56 |
+
const FORMATS = ['AMP', 'AMP4EMAIL', 'AMP4ADS'];
|
57 |
+
|
58 |
+
/**
|
59 |
+
* List of dynamic components
|
60 |
+
*
|
61 |
+
* This list should be kept in sync with the list of dynamic components at:
|
62 |
+
*
|
63 |
+
* @see https://github.com/ampproject/amphtml/blob/292dc66b8c0bb078bbe3a1bca960e8f494f7fc8f/spec/amp-cache-guidelines.md#guidelines-adding-a-new-cache-to-the-amp-ecosystem
|
64 |
+
*
|
65 |
+
* @var array[]
|
66 |
+
*/
|
67 |
+
const DYNAMIC_COMPONENTS = [
|
68 |
+
Attribute::CUSTOM_ELEMENT => [Extension::GEO],
|
69 |
+
Attribute::CUSTOM_TEMPLATE => [],
|
70 |
+
];
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Array of custom element names that delay rendering.
|
74 |
+
*
|
75 |
+
* @var string[]
|
76 |
+
*/
|
77 |
+
const RENDER_DELAYING_EXTENSIONS = [
|
78 |
+
Extension::DYNAMIC_CSS_CLASSES,
|
79 |
+
Extension::EXPERIMENT,
|
80 |
+
Extension::STORY,
|
81 |
+
];
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Standard boilerplate CSS stylesheet.
|
85 |
+
*
|
86 |
+
* @var string
|
87 |
+
*/
|
88 |
+
const BOILERPLATE_CSS = 'body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}'; // phpcs:ignore Generic.Files.LineLength.TooLong
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Boilerplate CSS stylesheet for the <noscript> tag.
|
92 |
+
*
|
93 |
+
* @var string
|
94 |
+
*/
|
95 |
+
const BOILERPLATE_NOSCRIPT_CSS = 'body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}'; // phpcs:ignore Generic.Files.LineLength.TooLong
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Boilerplate CSS stylesheet for Amp4Ads & Amp4Email.
|
99 |
+
*
|
100 |
+
* @var string
|
101 |
+
*/
|
102 |
+
const AMP4ADS_AND_AMP4EMAIL_BOILERPLATE_CSS = 'body{visibility:hidden}';
|
103 |
+
|
104 |
+
/**
|
105 |
+
* AMP runtime tag name.
|
106 |
+
*
|
107 |
+
* @var string
|
108 |
+
*/
|
109 |
+
const RUNTIME = 'amp-runtime';
|
110 |
+
|
111 |
+
// AMP classes reserved for internal use.
|
112 |
+
const LAYOUT_ATTRIBUTE = 'i-amphtml-layout';
|
113 |
+
const NO_BOILERPLATE_ATTRIBUTE = 'i-amphtml-no-boilerplate';
|
114 |
+
const LAYOUT_CLASS_PREFIX = 'i-amphtml-layout-';
|
115 |
+
const LAYOUT_SIZE_DEFINED_CLASS = 'i-amphtml-layout-size-defined';
|
116 |
+
const SIZER_ELEMENT = 'i-amphtml-sizer';
|
117 |
+
const INTRINSIC_SIZER_ELEMENT = 'i-amphtml-intrinsic-sizer';
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Maximum size of the CSS styles in bytes.
|
121 |
+
*
|
122 |
+
* @todo Max size is hard-coded for now until we ported over the generated spec into a reusable package.
|
123 |
+
*
|
124 |
+
* @var int
|
125 |
+
*/
|
126 |
+
const MAX_CSS_BYTE_COUNT = 75000;
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Check if a given node is the AMP runtime script.
|
130 |
+
*
|
131 |
+
* The AMP runtime script node is of the form '<script async src="https://cdn.ampproject.org...v0.js"></script>'.
|
132 |
+
*
|
133 |
+
* @param DOMNode $node Node to check.
|
134 |
+
* @return bool Whether the given node is the AMP runtime script.
|
135 |
+
*/
|
136 |
+
public static function isRuntimeScript(DOMNode $node)
|
137 |
+
{
|
138 |
+
if (
|
139 |
+
! $node instanceof Element
|
140 |
+
|| ! self::isAsyncScript($node)
|
141 |
+
|| self::isExtension($node)
|
142 |
+
) {
|
143 |
+
return false;
|
144 |
+
}
|
145 |
+
|
146 |
+
$src = $node->getAttribute(Attribute::SRC);
|
147 |
+
|
148 |
+
if (strpos($src, self::CACHE_ROOT_URL) !== 0) {
|
149 |
+
return false;
|
150 |
+
}
|
151 |
+
|
152 |
+
if (
|
153 |
+
// @TODO Compare performance against single regex.
|
154 |
+
substr($src, -6) !== '/v0.js'
|
155 |
+
&& substr($src, -7) !== '/v0.mjs'
|
156 |
+
&& substr($src, -14) !== '/amp4ads-v0.js'
|
157 |
+
&& substr($src, -15) !== '/amp4ads-v0.mjs'
|
158 |
+
) {
|
159 |
+
return false;
|
160 |
+
}
|
161 |
+
|
162 |
+
return true;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Check if a given node is the AMP viewer script.
|
167 |
+
*
|
168 |
+
* The AMP viewer script node is of the form '<script async
|
169 |
+
* src="https://cdn.ampproject.org/v0/amp-viewer-integration-...js>"</script>'.
|
170 |
+
*
|
171 |
+
* @param DOMNode $node Node to check.
|
172 |
+
* @return bool Whether the given node is the AMP runtime script.
|
173 |
+
*/
|
174 |
+
public static function isViewerScript(DOMNode $node)
|
175 |
+
{
|
176 |
+
if (
|
177 |
+
! $node instanceof Element
|
178 |
+
|| ! self::isAsyncScript($node)
|
179 |
+
|| self::isExtension($node)
|
180 |
+
) {
|
181 |
+
return false;
|
182 |
+
}
|
183 |
+
|
184 |
+
$src = $node->getAttribute(Attribute::SRC);
|
185 |
+
|
186 |
+
if (strpos($src, self::CACHE_HOST . '/v0/amp-viewer-integration-') !== 0) {
|
187 |
+
return false;
|
188 |
+
}
|
189 |
+
|
190 |
+
if (substr($src, -3) !== '.js') {
|
191 |
+
return false;
|
192 |
+
}
|
193 |
+
|
194 |
+
return true;
|
195 |
+
}
|
196 |
+
|
197 |
+
/**
|
198 |
+
* Check if a given node is an AMP extension.
|
199 |
+
*
|
200 |
+
* @param DOMNode $node Node to check.
|
201 |
+
* @return bool Whether the given node is the AMP runtime script.
|
202 |
+
*/
|
203 |
+
public static function isExtension(DOMNode $node)
|
204 |
+
{
|
205 |
+
return ! empty(self::getExtensionName($node));
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* Get the name of the extension.
|
210 |
+
*
|
211 |
+
* Returns an empty string if the name of the extension could not be retrieved.
|
212 |
+
*
|
213 |
+
* @param DOMNode $node Node to get the name of.
|
214 |
+
* @return string Name of the custom node or template. Empty string if none found.
|
215 |
+
*/
|
216 |
+
public static function getExtensionName(DOMNode $node)
|
217 |
+
{
|
218 |
+
if (! $node instanceof Element || $node->tagName !== Tag::SCRIPT) {
|
219 |
+
return '';
|
220 |
+
}
|
221 |
+
|
222 |
+
if ($node->hasAttribute(Attribute::CUSTOM_ELEMENT)) {
|
223 |
+
return $node->getAttribute(Attribute::CUSTOM_ELEMENT);
|
224 |
+
}
|
225 |
+
|
226 |
+
if ($node->hasAttribute(Attribute::CUSTOM_TEMPLATE)) {
|
227 |
+
return $node->getAttribute(Attribute::CUSTOM_TEMPLATE);
|
228 |
+
}
|
229 |
+
|
230 |
+
if ($node->hasAttribute(Attribute::HOST_SERVICE)) {
|
231 |
+
return $node->getAttribute(Attribute::HOST_SERVICE);
|
232 |
+
}
|
233 |
+
|
234 |
+
return '';
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Check whether a given node is a script for a render-delaying extension.
|
239 |
+
*
|
240 |
+
* @param DOMNode $node Node to check.
|
241 |
+
* @return bool Whether the node is a script for a render-delaying extension.
|
242 |
+
*/
|
243 |
+
public static function isRenderDelayingExtension(DOMNode $node)
|
244 |
+
{
|
245 |
+
$extensionName = self::getExtensionName($node);
|
246 |
+
|
247 |
+
if (empty($extensionName)) {
|
248 |
+
return false;
|
249 |
+
}
|
250 |
+
|
251 |
+
return in_array($extensionName, self::RENDER_DELAYING_EXTENSIONS, true);
|
252 |
+
}
|
253 |
+
|
254 |
+
/**
|
255 |
+
* Check whether a given DOM node is an AMP custom element.
|
256 |
+
*
|
257 |
+
* @param DOMNode $node DOM node to check.
|
258 |
+
* @return bool Whether the checked DOM node is an AMP custom element.
|
259 |
+
*/
|
260 |
+
public static function isCustomElement(DOMNode $node)
|
261 |
+
{
|
262 |
+
return $node instanceof Element && strpos($node->tagName, Extension::PREFIX) === 0;
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Check whether the given document is an AMP story.
|
267 |
+
*
|
268 |
+
* @param Document $document Document of the page to check within.
|
269 |
+
* @return bool Whether the provided document is an AMP story.
|
270 |
+
*/
|
271 |
+
public static function isAmpStory(Document $document)
|
272 |
+
{
|
273 |
+
foreach ($document->head->childNodes as $node) {
|
274 |
+
if (
|
275 |
+
$node instanceof Element
|
276 |
+
&&
|
277 |
+
$node->tagName === Tag::SCRIPT
|
278 |
+
&&
|
279 |
+
$node->getAttribute(Attribute::CUSTOM_ELEMENT) === Extension::STORY
|
280 |
+
) {
|
281 |
+
return true;
|
282 |
+
}
|
283 |
+
}
|
284 |
+
|
285 |
+
return false;
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Check whether a given node is an AMP template.
|
290 |
+
*
|
291 |
+
* @param DOMNode $node Node to check.
|
292 |
+
* @return bool Whether the node is an AMP template.
|
293 |
+
*/
|
294 |
+
public static function isTemplate(DOMNode $node)
|
295 |
+
{
|
296 |
+
if (! $node instanceof Element) {
|
297 |
+
return false;
|
298 |
+
}
|
299 |
+
|
300 |
+
if ($node->tagName === Tag::TEMPLATE) {
|
301 |
+
return true;
|
302 |
+
}
|
303 |
+
|
304 |
+
if (
|
305 |
+
$node->tagName === Tag::SCRIPT
|
306 |
+
&& $node->hasAttribute(Attribute::TEMPLATE)
|
307 |
+
&& $node->getAttribute(Attribute::TEMPLATE) === Extension::MUSTACHE
|
308 |
+
) {
|
309 |
+
return true;
|
310 |
+
}
|
311 |
+
|
312 |
+
return false;
|
313 |
+
}
|
314 |
+
|
315 |
+
/**
|
316 |
+
* Check whether a given node is an async <script> element.
|
317 |
+
*
|
318 |
+
* @param DOMNode $node Node to check.
|
319 |
+
* @return bool Whether the given node is an async <script> element.
|
320 |
+
*/
|
321 |
+
private static function isAsyncScript(DOMNode $node)
|
322 |
+
{
|
323 |
+
if (
|
324 |
+
! $node instanceof Element
|
325 |
+
|| $node->tagName !== Tag::SCRIPT
|
326 |
+
) {
|
327 |
+
return false;
|
328 |
+
}
|
329 |
+
|
330 |
+
if (
|
331 |
+
! $node->hasAttribute(Attribute::SRC)
|
332 |
+
|| ! $node->hasAttribute(Attribute::ASYNC)
|
333 |
+
) {
|
334 |
+
return false;
|
335 |
+
}
|
336 |
+
|
337 |
+
return true;
|
338 |
+
}
|
339 |
+
|
340 |
+
/**
|
341 |
+
* Check whether a given node is an AMP iframe.
|
342 |
+
*
|
343 |
+
* @param DOMNode $node Node to check.
|
344 |
+
* @return bool Whether the node is an AMP iframe.
|
345 |
+
*/
|
346 |
+
public static function isAmpIframe(DOMNode $node)
|
347 |
+
{
|
348 |
+
if (! $node instanceof Element) {
|
349 |
+
return false;
|
350 |
+
}
|
351 |
+
|
352 |
+
return $node->tagName === Extension::IFRAME
|
353 |
+
|| $node->tagName === Extension::VIDEO_IFRAME;
|
354 |
+
}
|
355 |
+
}
|
includes/vendor/tool/Attribute.php
ADDED
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface with constants for the different types of attributes.
|
7 |
+
*
|
8 |
+
* phpcs:disable Generic.Files.LineLength.TooLong
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
interface Attribute
|
13 |
+
{
|
14 |
+
|
15 |
+
const ACTION = 'action';
|
16 |
+
const ALT = 'alt';
|
17 |
+
const AMP = 'amp';
|
18 |
+
const AMP4ADS = 'amp4ads';
|
19 |
+
const AMP4ADS_BOILERPLATE = 'amp4ads-boilerplate';
|
20 |
+
const AMP4ADS_EMOJI = self::AMP_EMOJI . '4ads';
|
21 |
+
const AMP4ADS_EMOJI_ALT = self::AMP_EMOJI_ALT . '4ads';
|
22 |
+
const AMP4EMAIL = 'amp4email';
|
23 |
+
const AMP4EMAIL_BOILERPLATE = 'amp4email-boilerplate';
|
24 |
+
const AMP4EMAIL_EMOJI = self::AMP_EMOJI . '4email';
|
25 |
+
const AMP4EMAIL_EMOJI_ALT = self::AMP_EMOJI_ALT . '4email';
|
26 |
+
const AMP_BOILERPLATE = 'amp-boilerplate';
|
27 |
+
const AMP_CUSTOM = 'amp-custom';
|
28 |
+
const AMP_EMOJI = "\xE2\x9A\xA1";
|
29 |
+
const AMP_EMOJI_ALT = "\xE2\x9A\xA1\xEF\xB8\x8F"; // See https://github.com/ampproject/amphtml/issues/25990.
|
30 |
+
const AMP_ONERROR = 'amp-onerror';
|
31 |
+
const AMP_RUNTIME = 'amp-runtime';
|
32 |
+
const AMP_SCRIPT_SRC = 'amp-script-src';
|
33 |
+
const ARIA_HIDDEN = 'aria-hidden';
|
34 |
+
const ARIA_LABEL = 'aria-label';
|
35 |
+
const AS_ = 'as'; // Underscore needed because 'as' is a PHP keyword.
|
36 |
+
const ASYNC = 'async';
|
37 |
+
const ATTRIBUTION = 'attribution';
|
38 |
+
const AUTOPLAY = 'autoplay';
|
39 |
+
const CHARSET = 'charset';
|
40 |
+
const CLASS_ = 'class'; // Underscore needed because 'class' is a PHP keyword.
|
41 |
+
const CONTENT = 'content';
|
42 |
+
const CROSSORIGIN = 'crossorigin';
|
43 |
+
const CUSTOM_ELEMENT = 'custom-element';
|
44 |
+
const CUSTOM_TEMPLATE = 'custom-template';
|
45 |
+
const DATA = 'data';
|
46 |
+
const DECODING = 'decoding';
|
47 |
+
const DISABLE_INLINE_WIDTH = 'disable-inline-width';
|
48 |
+
const HEIGHT = 'height';
|
49 |
+
const HEIGHTS = 'heights';
|
50 |
+
const HIDDEN = 'hidden';
|
51 |
+
const HOST_SERVICE = 'host-service';
|
52 |
+
const HREF = 'href';
|
53 |
+
const HTTP_EQUIV = 'http-equiv';
|
54 |
+
const I_AMPHTML_BINDING = 'i-amphtml-binding';
|
55 |
+
const I_AMPHTML_NO_BOILERPLATE = 'i-amphtml-no-boilerplate';
|
56 |
+
const I_AMPHTML_SSR = 'i-amphtml-ssr';
|
57 |
+
const I_AMPHTML_VERSION = 'i-amphtml-version';
|
58 |
+
const ID = 'id';
|
59 |
+
const IMAGESRCSET = 'imagesrcset';
|
60 |
+
const IMAGESIZES = 'imagesizes';
|
61 |
+
const IMPORTANCE = 'importance';
|
62 |
+
const INTRINSICSIZE = 'intrinsicsize';
|
63 |
+
const LAYOUT = 'layout';
|
64 |
+
const LIGHTBOX = 'lightbox';
|
65 |
+
const LOADING = 'loading';
|
66 |
+
const LOOP = 'loop';
|
67 |
+
const MEDIA = 'media';
|
68 |
+
const NAME = 'name';
|
69 |
+
const NOLOADING = 'noloading';
|
70 |
+
const NOMODULE = 'nomodule';
|
71 |
+
const OBJECT_FIT = 'object-fit';
|
72 |
+
const OBJECT_POSITION = 'object-position';
|
73 |
+
const ON = 'on';
|
74 |
+
const PLACEHOLDER = 'placeholder';
|
75 |
+
const POSTER = 'poster';
|
76 |
+
const PROFILE = 'profile';
|
77 |
+
const REFERRERPOLICY = 'referrerpolicy';
|
78 |
+
const REL = 'rel';
|
79 |
+
const ROLE = 'role';
|
80 |
+
const SRCSET = 'srcset';
|
81 |
+
const SIZES = 'sizes';
|
82 |
+
const STYLE = 'style';
|
83 |
+
const SRC = 'src';
|
84 |
+
const TABINDEX = 'tabindex';
|
85 |
+
const TEMPLATE = 'template';
|
86 |
+
const TITLE = 'title';
|
87 |
+
const TYPE = 'type';
|
88 |
+
const VALUE = 'value';
|
89 |
+
const VIEWPORT = 'viewport';
|
90 |
+
const WIDTH = 'width';
|
91 |
+
|
92 |
+
const ALL_AMP = [self::AMP, self::AMP_EMOJI, self::AMP_EMOJI_ALT];
|
93 |
+
const ALL_AMP4ADS = [self::AMP4ADS, self::AMP4ADS_EMOJI, self::AMP4ADS_EMOJI_ALT];
|
94 |
+
const ALL_AMP4EMAIL = [self::AMP4EMAIL, self::AMP4EMAIL_EMOJI, self::AMP4EMAIL_EMOJI_ALT];
|
95 |
+
|
96 |
+
const ALL_BOILERPLATES = [self::AMP_BOILERPLATE, self::AMP4ADS_BOILERPLATE, self::AMP4EMAIL_BOILERPLATE];
|
97 |
+
|
98 |
+
const TYPE_HTML = 'text/html';
|
99 |
+
const TYPE_JSON = 'application/json';
|
100 |
+
const TYPE_LD_JSON = 'application/ld+json';
|
101 |
+
const TYPE_PDF = 'application/pdf';
|
102 |
+
const TYPE_MODULE = 'module';
|
103 |
+
const TYPE_TEXT_PLAIN = 'text/plain';
|
104 |
+
|
105 |
+
const REL_AMPHTML = 'amphtml';
|
106 |
+
const REL_CANONICAL = 'canonical';
|
107 |
+
const REL_DNS_PREFETCH = 'dns-prefetch';
|
108 |
+
const REL_ICON = 'icon';
|
109 |
+
const REL_MODULEPRELOAD = 'modulepreload';
|
110 |
+
const REL_NOAMPHTML = 'noamphtml';
|
111 |
+
const REL_NOFOLLOW = 'nofollow';
|
112 |
+
const REL_PRECONNECT = 'preconnect';
|
113 |
+
const REL_PREFETCH = 'prefetch';
|
114 |
+
const REL_PRELOAD = 'preload';
|
115 |
+
const REL_PRERENDER = 'prerender';
|
116 |
+
const REL_STYLESHEET = 'stylesheet';
|
117 |
+
|
118 |
+
const DATA_AMP_STORY_PLAYER_POSTER_IMG = 'data-amp-story-player-poster-img';
|
119 |
+
const DATA_HERO = 'data-hero';
|
120 |
+
const DATA_HERO_CANDIDATE = 'data-hero-candidate';
|
121 |
+
|
122 |
+
const CROSSORIGIN_ANONYMOUS = 'anonymous';
|
123 |
+
}
|
includes/vendor/tool/Cli/AmpExecutable.php
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\Cli\InvalidCommand;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Executable that assembles all of the commands.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class AmpExecutable extends Executable
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Array of command classes to register.
|
17 |
+
*
|
18 |
+
* @var string[]
|
19 |
+
*/
|
20 |
+
const COMMAND_CLASSES = [
|
21 |
+
Command\Optimize::class,
|
22 |
+
];
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Array of command object instances.
|
26 |
+
*
|
27 |
+
* @var Command[]
|
28 |
+
*/
|
29 |
+
private $commandInstances = [];
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Register options and arguments on the given $options object.
|
33 |
+
*
|
34 |
+
* @param Options $options Options instance to register the commands with.
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
protected function setup(Options $options)
|
38 |
+
{
|
39 |
+
foreach (self::COMMAND_CLASSES as $commandClass) {
|
40 |
+
/** @var Command $command */
|
41 |
+
$command = new $commandClass();
|
42 |
+
|
43 |
+
$command->register($options);
|
44 |
+
|
45 |
+
$this->commandInstances[$command->getName()] = $command;
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Your main program.
|
51 |
+
*
|
52 |
+
* Arguments and options have been parsed when this is run.
|
53 |
+
*
|
54 |
+
* @param Options $options
|
55 |
+
* @return void
|
56 |
+
*/
|
57 |
+
protected function main(Options $options)
|
58 |
+
{
|
59 |
+
$commandName = $options->getCommand();
|
60 |
+
|
61 |
+
if (empty($commandName)) {
|
62 |
+
echo $this->options->help();
|
63 |
+
exit(1);
|
64 |
+
}
|
65 |
+
|
66 |
+
if (! array_key_exists($commandName, $this->commandInstances)) {
|
67 |
+
throw InvalidCommand::forUnregisteredCommand($commandName);
|
68 |
+
}
|
69 |
+
|
70 |
+
$command = $this->commandInstances[$commandName];
|
71 |
+
|
72 |
+
$command->process($options);
|
73 |
+
}
|
74 |
+
}
|
includes/vendor/tool/Cli/Colors.php
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\Cli\InvalidColor;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
9 |
+
* licensed under the MIT license.
|
10 |
+
*
|
11 |
+
* Source: https://github.com/splitbrain/php-cli/blob/8c2c001b1b55d194402cf18aad2757049ac6d575/src/Colors.php
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Handles color output on (Unix) terminals.
|
16 |
+
*
|
17 |
+
* @package ampproject/amp-toolbox
|
18 |
+
*/
|
19 |
+
class Colors
|
20 |
+
{
|
21 |
+
|
22 |
+
const C_BLACK = 'black';
|
23 |
+
const C_BLUE = 'blue';
|
24 |
+
const C_BROWN = 'brown';
|
25 |
+
const C_CYAN = 'cyan';
|
26 |
+
const C_DARKGRAY = 'darkgray';
|
27 |
+
const C_GREEN = 'green';
|
28 |
+
const C_LIGHTBLUE = 'lightblue';
|
29 |
+
const C_LIGHTCYAN = 'lightcyan';
|
30 |
+
const C_LIGHTGRAY = 'lightgray';
|
31 |
+
const C_LIGHTGREEN = 'lightgreen';
|
32 |
+
const C_LIGHTPURPLE = 'lightpurple';
|
33 |
+
const C_LIGHTRED = 'lightred';
|
34 |
+
const C_PURPLE = 'purple';
|
35 |
+
const C_RED = 'red';
|
36 |
+
const C_RESET = 'reset';
|
37 |
+
const C_WHITE = 'white';
|
38 |
+
const C_YELLOW = 'yellow';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Associative array of known color names.
|
42 |
+
*
|
43 |
+
* @var array<string>
|
44 |
+
*/
|
45 |
+
const KNOWN_COLORS = [
|
46 |
+
self::C_RESET => "\33[0m",
|
47 |
+
self::C_BLACK => "\33[0;30m",
|
48 |
+
self::C_DARKGRAY => "\33[1;30m",
|
49 |
+
self::C_BLUE => "\33[0;34m",
|
50 |
+
self::C_LIGHTBLUE => "\33[1;34m",
|
51 |
+
self::C_GREEN => "\33[0;32m",
|
52 |
+
self::C_LIGHTGREEN => "\33[1;32m",
|
53 |
+
self::C_CYAN => "\33[0;36m",
|
54 |
+
self::C_LIGHTCYAN => "\33[1;36m",
|
55 |
+
self::C_RED => "\33[0;31m",
|
56 |
+
self::C_LIGHTRED => "\33[1;31m",
|
57 |
+
self::C_PURPLE => "\33[0;35m",
|
58 |
+
self::C_LIGHTPURPLE => "\33[1;35m",
|
59 |
+
self::C_BROWN => "\33[0;33m",
|
60 |
+
self::C_YELLOW => "\33[1;33m",
|
61 |
+
self::C_LIGHTGRAY => "\33[0;37m",
|
62 |
+
self::C_WHITE => "\33[1;37m",
|
63 |
+
];
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Whether colors should be used.
|
67 |
+
*
|
68 |
+
* @var bool
|
69 |
+
*/
|
70 |
+
protected $enabled = true;
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Constructor.
|
74 |
+
*
|
75 |
+
* Tries to disable colors for non-terminals.
|
76 |
+
*/
|
77 |
+
public function __construct()
|
78 |
+
{
|
79 |
+
$this->enabled = getenv('TERM') || (function_exists('posix_isatty') && posix_isatty(STDOUT));
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Enable color output.
|
84 |
+
*/
|
85 |
+
public function enable()
|
86 |
+
{
|
87 |
+
$this->enabled = true;
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Disable color output.
|
92 |
+
*/
|
93 |
+
public function disable()
|
94 |
+
{
|
95 |
+
$this->enabled = false;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Check whether color support is enabled.
|
100 |
+
*
|
101 |
+
* @return bool
|
102 |
+
*/
|
103 |
+
public function isEnabled()
|
104 |
+
{
|
105 |
+
return $this->enabled;
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Convenience function to print a line in a given color.
|
110 |
+
*
|
111 |
+
* @param string $line The line to print. A new line is added automatically.
|
112 |
+
* @param string $color One of the available color names.
|
113 |
+
* @param resource $channel Optional. File descriptor to write to. Defaults to STDOUT.
|
114 |
+
* @throws InvalidColor If the requested color code is not known.
|
115 |
+
*/
|
116 |
+
public function line($line, $color, $channel = STDOUT)
|
117 |
+
{
|
118 |
+
$this->set($color);
|
119 |
+
fwrite($channel, rtrim($line) . "\n");
|
120 |
+
$this->reset();
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Returns the given text wrapped in the appropriate color and reset code
|
125 |
+
*
|
126 |
+
* @param string $text String to wrap.
|
127 |
+
* @param string $color One of the available color names.
|
128 |
+
* @return string The wrapped string.
|
129 |
+
* @throws InvalidColor If the requested color code is not known.
|
130 |
+
*/
|
131 |
+
public function wrap($text, $color)
|
132 |
+
{
|
133 |
+
return $this->getColorCode($color) . $text . $this->getColorCode(self::C_RESET);
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Gets the appropriate terminal code for the given color.
|
138 |
+
*
|
139 |
+
* @param string $color One of the available color names.
|
140 |
+
* @return string Color code.
|
141 |
+
* @throws InvalidColor If the requested color code is not known.
|
142 |
+
*/
|
143 |
+
public function getColorCode($color)
|
144 |
+
{
|
145 |
+
if (! array_key_exists($color, self::KNOWN_COLORS)) {
|
146 |
+
throw InvalidColor::forUnknownColor($color);
|
147 |
+
}
|
148 |
+
|
149 |
+
if (! $this->enabled) {
|
150 |
+
return '';
|
151 |
+
}
|
152 |
+
|
153 |
+
return self::KNOWN_COLORS[$color];
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Set the given color for consecutive output.
|
158 |
+
*
|
159 |
+
* @param string $color One of the supported color names.
|
160 |
+
* @param resource $channel Optional. File descriptor to write to. Defaults to STDOUT.
|
161 |
+
* @throws InvalidColor If the requested color code is not known.
|
162 |
+
*/
|
163 |
+
public function set($color, $channel = STDOUT)
|
164 |
+
{
|
165 |
+
fwrite($channel, $this->getColorCode($color));
|
166 |
+
}
|
167 |
+
|
168 |
+
/**
|
169 |
+
* Reset the terminal color.
|
170 |
+
*
|
171 |
+
* @param resource $channel Optional. File descriptor to write to. Defaults to STDOUT.
|
172 |
+
*/
|
173 |
+
public function reset($channel = STDOUT)
|
174 |
+
{
|
175 |
+
$this->set(self::C_RESET, $channel);
|
176 |
+
}
|
177 |
+
}
|
includes/vendor/tool/Cli/Command.php
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* A command that is registered with the amp executable.
|
7 |
+
*
|
8 |
+
* @package AmpProject\Cli
|
9 |
+
*/
|
10 |
+
abstract class Command
|
11 |
+
{
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Name of the command.
|
15 |
+
*
|
16 |
+
* This needs to be overridden in extending commands.
|
17 |
+
*
|
18 |
+
* @var string
|
19 |
+
*/
|
20 |
+
const NAME = '<unknown>';
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Get the name of the command.
|
24 |
+
*
|
25 |
+
* @return string Name of the command.
|
26 |
+
*/
|
27 |
+
public function getName()
|
28 |
+
{
|
29 |
+
return static::NAME;
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Register the command.
|
34 |
+
*
|
35 |
+
* @param Options $options Options instance to register the command with.
|
36 |
+
*/
|
37 |
+
abstract public function register(Options $options);
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Process the command.
|
41 |
+
*
|
42 |
+
* Arguments and options have been parsed when this is run.
|
43 |
+
*
|
44 |
+
* @param Options $options Options instance to process the command with.
|
45 |
+
*/
|
46 |
+
abstract public function process(Options $options);
|
47 |
+
}
|
includes/vendor/tool/Cli/Command/Optimize.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli\Command;
|
4 |
+
|
5 |
+
use AmpProject\Cli\Command;
|
6 |
+
use AmpProject\Cli\Options;
|
7 |
+
use AmpProject\Exception\Cli\InvalidArgument;
|
8 |
+
use AmpProject\Optimizer\ErrorCollection;
|
9 |
+
use AmpProject\Optimizer\TransformationEngine;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Optimize AMP HTML markup and return optimized markup.
|
13 |
+
*
|
14 |
+
* @package ampproject/amp-toolbox
|
15 |
+
*/
|
16 |
+
final class Optimize extends Command
|
17 |
+
{
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Name of the command.
|
21 |
+
*
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
const NAME = 'optimize';
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Help text of the command.
|
28 |
+
*
|
29 |
+
* @var string
|
30 |
+
*/
|
31 |
+
const HELP_TEXT = 'Optimize AMP HTML markup and return optimized markup.';
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Register the command.
|
35 |
+
*
|
36 |
+
* @param Options $options Options instance to register the command with.
|
37 |
+
*/
|
38 |
+
public function register(Options $options)
|
39 |
+
{
|
40 |
+
$options->registerCommand(self::NAME, self::HELP_TEXT);
|
41 |
+
|
42 |
+
$options->registerArgument('file', "File with unoptimized AMP markup. Use '-' for STDIN.", true, self::NAME);
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Process the command.
|
47 |
+
*
|
48 |
+
* Arguments and options have been parsed when this is run.
|
49 |
+
*
|
50 |
+
* @param Options $options Options instance to process the command with.
|
51 |
+
*
|
52 |
+
* @throws InvalidArgument If the provided file is not readable.
|
53 |
+
*/
|
54 |
+
public function process(Options $options)
|
55 |
+
{
|
56 |
+
list($file) = $options->getArguments();
|
57 |
+
|
58 |
+
if (
|
59 |
+
$file !== '-'
|
60 |
+
&&
|
61 |
+
(
|
62 |
+
!is_file($file)
|
63 |
+
||
|
64 |
+
!is_readable($file)
|
65 |
+
)
|
66 |
+
) {
|
67 |
+
throw InvalidArgument::forUnreadableFile($file);
|
68 |
+
}
|
69 |
+
|
70 |
+
if ($file === '-') {
|
71 |
+
$file = 'php://stdin';
|
72 |
+
}
|
73 |
+
|
74 |
+
$html = file_get_contents($file);
|
75 |
+
$optimizer = new TransformationEngine();
|
76 |
+
$errors = new ErrorCollection();
|
77 |
+
$optimizedHtml = $optimizer->optimizeHtml($html, $errors);
|
78 |
+
|
79 |
+
echo($optimizedHtml . PHP_EOL);
|
80 |
+
}
|
81 |
+
}
|
includes/vendor/tool/Cli/Executable.php
ADDED
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use AmpProject\Exception\Cli\InvalidSapi;
|
7 |
+
use Exception;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
11 |
+
* licensed under the MIT license.
|
12 |
+
*
|
13 |
+
* Source: https://github.com/splitbrain/php-cli/blob/fb4f888866d090b10e3e68292d197ca274cea626/src/CLI.php
|
14 |
+
*/
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Your commandline script should inherit from this class and implement the abstract methods.
|
18 |
+
*
|
19 |
+
* @package ampproject/amp-toolbox
|
20 |
+
*/
|
21 |
+
abstract class Executable
|
22 |
+
{
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Instance of the Colors helper object.
|
26 |
+
*
|
27 |
+
* @var Colors
|
28 |
+
*/
|
29 |
+
public $colors;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* The executable script itself.
|
33 |
+
*
|
34 |
+
* @var string
|
35 |
+
*/
|
36 |
+
protected $bin;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Instance of the options parser to use.
|
40 |
+
*
|
41 |
+
* @var Options
|
42 |
+
*/
|
43 |
+
protected $options;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* PSR-3 compatible log levels and their prefix, color, output channel.
|
47 |
+
*
|
48 |
+
* @var array<array>
|
49 |
+
*/
|
50 |
+
protected $loglevels = [
|
51 |
+
LogLevel::DEBUG => ['', Colors::C_RESET, STDOUT],
|
52 |
+
LogLevel::INFO => ['ℹ ', Colors::C_CYAN, STDOUT],
|
53 |
+
LogLevel::NOTICE => ['☛ ', Colors::C_CYAN, STDOUT],
|
54 |
+
LogLevel::SUCCESS => ['✓ ', Colors::C_GREEN, STDOUT],
|
55 |
+
LogLevel::WARNING => ['⚠ ', Colors::C_BROWN, STDERR],
|
56 |
+
LogLevel::ERROR => ['✗ ', Colors::C_RED, STDERR],
|
57 |
+
LogLevel::CRITICAL => ['☠ ', Colors::C_LIGHTRED, STDERR],
|
58 |
+
LogLevel::ALERT => ['✖ ', Colors::C_LIGHTRED, STDERR],
|
59 |
+
LogLevel::EMERGENCY => ['✘ ', Colors::C_LIGHTRED, STDERR],
|
60 |
+
];
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Default log level.
|
64 |
+
*
|
65 |
+
* @var string
|
66 |
+
*/
|
67 |
+
protected $loglevel = 'info';
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Constructor.
|
71 |
+
*
|
72 |
+
* Initialize the arguments, set up helper classes and set up the CLI environment.
|
73 |
+
*
|
74 |
+
* @param bool $autocatch Optional. Whether exceptions should be caught and handled automatically. Defaults
|
75 |
+
* to true.
|
76 |
+
* @param Options|null $options Optional. Instance of the Options object to use. Defaults to null to instantiate a
|
77 |
+
* new one.
|
78 |
+
* @param Colors|null $colors Optional. Instance of the Colors object to use. Defaults to null to instantiate a
|
79 |
+
* new one.
|
80 |
+
*/
|
81 |
+
public function __construct($autocatch = true, Options $options = null, Colors $colors = null)
|
82 |
+
{
|
83 |
+
if ($autocatch) {
|
84 |
+
set_exception_handler([$this, 'fatal']);
|
85 |
+
}
|
86 |
+
|
87 |
+
$this->colors = $colors instanceof Colors ? $colors : new Colors();
|
88 |
+
$this->options = $options instanceof Options ? $options : new Options($this->colors);
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Execute the CLI program.
|
93 |
+
*
|
94 |
+
* Executes the setup() routine, adds default options, initiate the options parsing and argument checking
|
95 |
+
* and finally executes main() - Each part is split into their own protected function below, so behaviour
|
96 |
+
* can easily be overwritten.
|
97 |
+
*
|
98 |
+
* @param bool $exitOnCompletion Optional. Whether to exit on completion. Defaults to true.
|
99 |
+
* @throws InvalidSapi If a SAPI other than 'cli' is detected.
|
100 |
+
*/
|
101 |
+
public function run($exitOnCompletion = true)
|
102 |
+
{
|
103 |
+
$sapi = php_sapi_name();
|
104 |
+
|
105 |
+
if ('cli' !== $sapi) {
|
106 |
+
throw InvalidSapi::forSapi($sapi);
|
107 |
+
}
|
108 |
+
|
109 |
+
$this->setup($this->options);
|
110 |
+
$this->registerDefaultOptions();
|
111 |
+
$this->parseOptions();
|
112 |
+
$this->handleDefaultOptions();
|
113 |
+
$this->setupLogging();
|
114 |
+
$this->checkArguments();
|
115 |
+
$this->execute();
|
116 |
+
|
117 |
+
if ($exitOnCompletion) {
|
118 |
+
exit(0);
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Exits the program on a fatal error.
|
124 |
+
*
|
125 |
+
* @param Exception|string $error Either an exception or an error message.
|
126 |
+
* @param array $context Optional. Associative array of contextual information. Defaults to an empty
|
127 |
+
* array.
|
128 |
+
*/
|
129 |
+
public function fatal($error, array $context = [])
|
130 |
+
{
|
131 |
+
$code = 0;
|
132 |
+
|
133 |
+
if ($error instanceof Exception) {
|
134 |
+
$this->debug(get_class($error) . ' caught in ' . $error->getFile() . ':' . $error->getLine());
|
135 |
+
$this->debug($error->getTraceAsString());
|
136 |
+
$code = $error->getCode();
|
137 |
+
$error = $error->getMessage();
|
138 |
+
}
|
139 |
+
|
140 |
+
if (! $code) {
|
141 |
+
$code = AmpCliException::E_ANY;
|
142 |
+
}
|
143 |
+
|
144 |
+
$this->critical($error, $context);
|
145 |
+
|
146 |
+
exit($code);
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* System is unusable.
|
151 |
+
*
|
152 |
+
* @param string $message Log message.
|
153 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
154 |
+
* @return void
|
155 |
+
*/
|
156 |
+
public function emergency($message, array $context = [])
|
157 |
+
{
|
158 |
+
$this->log(LogLevel::EMERGENCY, $message, $context);
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Action must be taken immediately.
|
163 |
+
*
|
164 |
+
* Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
|
165 |
+
*
|
166 |
+
* @param string $message Log message.
|
167 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
168 |
+
* @return void
|
169 |
+
*/
|
170 |
+
public function alert($message, array $context = [])
|
171 |
+
{
|
172 |
+
$this->log(LogLevel::ALERT, $message, $context);
|
173 |
+
}
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Critical conditions.
|
177 |
+
*
|
178 |
+
* Example: Application component unavailable, unexpected exception.
|
179 |
+
*
|
180 |
+
* @param string $message Log message.
|
181 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
182 |
+
* @return void
|
183 |
+
*/
|
184 |
+
public function critical($message, array $context = [])
|
185 |
+
{
|
186 |
+
$this->log(LogLevel::CRITICAL, $message, $context);
|
187 |
+
}
|
188 |
+
|
189 |
+
/**
|
190 |
+
* Runtime errors that do not require immediate action but should typically be logged and monitored.
|
191 |
+
*
|
192 |
+
* @param string $message Log message.
|
193 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
194 |
+
* @return void
|
195 |
+
*/
|
196 |
+
public function error($message, array $context = [])
|
197 |
+
{
|
198 |
+
$this->log(LogLevel::ERROR, $message, $context);
|
199 |
+
}
|
200 |
+
|
201 |
+
/**
|
202 |
+
* Exceptional occurrences that are not errors.
|
203 |
+
*
|
204 |
+
* Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
|
205 |
+
*
|
206 |
+
* @param string $message Log message.
|
207 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
208 |
+
* @return void
|
209 |
+
*/
|
210 |
+
public function warning($message, array $context = [])
|
211 |
+
{
|
212 |
+
$this->log(LogLevel::WARNING, $message, $context);
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Normal, positive outcome.
|
217 |
+
*
|
218 |
+
* @param string $string
|
219 |
+
* @param array $context
|
220 |
+
* @return void
|
221 |
+
*/
|
222 |
+
public function success($string, array $context = [])
|
223 |
+
{
|
224 |
+
$this->log(LogLevel::SUCCESS, $string, $context);
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Normal but significant events.
|
229 |
+
*
|
230 |
+
* @param string $message Log message.
|
231 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
232 |
+
* @return void
|
233 |
+
*/
|
234 |
+
public function notice($message, array $context = [])
|
235 |
+
{
|
236 |
+
$this->log(LogLevel::NOTICE, $message, $context);
|
237 |
+
}
|
238 |
+
|
239 |
+
/**
|
240 |
+
* Interesting events.
|
241 |
+
*
|
242 |
+
* Example: User logs in, SQL logs.
|
243 |
+
*
|
244 |
+
* @param string $message Log message.
|
245 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
246 |
+
* @return void
|
247 |
+
*/
|
248 |
+
public function info($message, array $context = [])
|
249 |
+
{
|
250 |
+
$this->log(LogLevel::INFO, $message, $context);
|
251 |
+
}
|
252 |
+
|
253 |
+
/**
|
254 |
+
* Detailed debug information.
|
255 |
+
*
|
256 |
+
* @param string $message Log message.
|
257 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
258 |
+
* @return void
|
259 |
+
*/
|
260 |
+
public function debug($message, array $context = [])
|
261 |
+
{
|
262 |
+
$this->log(LogLevel::DEBUG, $message, $context);
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Log a message of a given log level to the logs.
|
267 |
+
*
|
268 |
+
* @param string $level Log level to use.
|
269 |
+
* @param string $message Log message.
|
270 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
271 |
+
* @return void
|
272 |
+
*/
|
273 |
+
public function log($level, $message, array $context = [])
|
274 |
+
{
|
275 |
+
if (! LogLevel::matches($level, $this->options->getOption('loglevel', $this->loglevel))) {
|
276 |
+
return;
|
277 |
+
}
|
278 |
+
|
279 |
+
list($prefix, $color, $channel) = $this->loglevels[$level];
|
280 |
+
|
281 |
+
if (! $this->colors->isEnabled()) {
|
282 |
+
$prefix = '';
|
283 |
+
}
|
284 |
+
|
285 |
+
$message = $this->interpolate($message, $context);
|
286 |
+
|
287 |
+
$this->colors->line($prefix . $message, $color, $channel);
|
288 |
+
}
|
289 |
+
|
290 |
+
/**
|
291 |
+
* Interpolates context values into the message placeholders.
|
292 |
+
*
|
293 |
+
* @param string $message Message to interpolate.
|
294 |
+
* @param array $context Optional. Contextual information. Defaults to an empty array.
|
295 |
+
* @return string Interpolated string.
|
296 |
+
*/
|
297 |
+
protected function interpolate($message, array $context = [])
|
298 |
+
{
|
299 |
+
// Build a replacement array with braces around the context keys.
|
300 |
+
$replace = [];
|
301 |
+
foreach ($context as $key => $val) {
|
302 |
+
// Check that the value can be cast to string.
|
303 |
+
if (
|
304 |
+
! is_array($val)
|
305 |
+
&&
|
306 |
+
(
|
307 |
+
! is_object($val)
|
308 |
+
|| method_exists($val, '__toString')
|
309 |
+
)
|
310 |
+
) {
|
311 |
+
$replace['{' . $key . '}'] = $val;
|
312 |
+
}
|
313 |
+
}
|
314 |
+
|
315 |
+
// Interpolate replacement values into the message and return.
|
316 |
+
return strtr($message, $replace);
|
317 |
+
}
|
318 |
+
|
319 |
+
/**
|
320 |
+
* Add the default help, color and log options.
|
321 |
+
*/
|
322 |
+
protected function registerDefaultOptions()
|
323 |
+
{
|
324 |
+
$this->options->registerOption(
|
325 |
+
'help',
|
326 |
+
'Display this help screen and exit immediately.',
|
327 |
+
'h'
|
328 |
+
);
|
329 |
+
$this->options->registerOption(
|
330 |
+
'no-colors',
|
331 |
+
'Do not use any colors in output. Useful when piping output to other tools or files.'
|
332 |
+
);
|
333 |
+
$this->options->registerOption(
|
334 |
+
'loglevel',
|
335 |
+
"Minimum level of messages to display. Default is {$this->colors->wrap($this->loglevel, Colors::C_CYAN)}."
|
336 |
+
. ' Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.',
|
337 |
+
null,
|
338 |
+
'level'
|
339 |
+
);
|
340 |
+
}
|
341 |
+
|
342 |
+
/**
|
343 |
+
* Handle the default options.
|
344 |
+
*/
|
345 |
+
protected function handleDefaultOptions()
|
346 |
+
{
|
347 |
+
if ($this->options->getOption('no-colors')) {
|
348 |
+
$this->colors->disable();
|
349 |
+
}
|
350 |
+
if ($this->options->getOption('help')) {
|
351 |
+
echo $this->options->help();
|
352 |
+
exit(0);
|
353 |
+
}
|
354 |
+
}
|
355 |
+
|
356 |
+
/**
|
357 |
+
* Handle the logging options.
|
358 |
+
*/
|
359 |
+
protected function setupLogging()
|
360 |
+
{
|
361 |
+
$this->loglevel = $this->options->getOption('loglevel', $this->loglevel);
|
362 |
+
|
363 |
+
if (! in_array($this->loglevel, LogLevel::ORDER)) {
|
364 |
+
$this->fatal('Unknown log level');
|
365 |
+
}
|
366 |
+
}
|
367 |
+
|
368 |
+
/**
|
369 |
+
* Wrapper around the option parsing.
|
370 |
+
*/
|
371 |
+
protected function parseOptions()
|
372 |
+
{
|
373 |
+
$this->options->parseOptions();
|
374 |
+
}
|
375 |
+
|
376 |
+
/**
|
377 |
+
* Wrapper around the argument checking.
|
378 |
+
*/
|
379 |
+
protected function checkArguments()
|
380 |
+
{
|
381 |
+
$this->options->checkArguments();
|
382 |
+
}
|
383 |
+
|
384 |
+
/**
|
385 |
+
* Wrapper around main.
|
386 |
+
*/
|
387 |
+
protected function execute()
|
388 |
+
{
|
389 |
+
$this->main($this->options);
|
390 |
+
}
|
391 |
+
|
392 |
+
/**
|
393 |
+
* Register options and arguments on the given $options object.
|
394 |
+
*
|
395 |
+
* @param Options $options
|
396 |
+
* @return void
|
397 |
+
*/
|
398 |
+
abstract protected function setup(Options $options);
|
399 |
+
|
400 |
+
/**
|
401 |
+
* Main program routine.
|
402 |
+
*
|
403 |
+
* Arguments and options have been parsed when this is run.
|
404 |
+
*
|
405 |
+
* @param Options $options
|
406 |
+
* @return void
|
407 |
+
*/
|
408 |
+
abstract protected function main(Options $options);
|
409 |
+
}
|
includes/vendor/tool/Cli/LogLevel.php
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use AmpProject\Exception\Cli\InvalidSapi;
|
7 |
+
use Exception;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Abstract class with the individual log levels.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
abstract class LogLevel
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Detailed debug information.
|
19 |
+
*
|
20 |
+
* @var string
|
21 |
+
*/
|
22 |
+
const DEBUG = 'debug';
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Interesting events.
|
26 |
+
*
|
27 |
+
* Example: User logs in, SQL logs.
|
28 |
+
*
|
29 |
+
* @var string
|
30 |
+
*/
|
31 |
+
const INFO = 'info';
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Normal but significant events.
|
35 |
+
*
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
const NOTICE = 'notice';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Normal, positive outcome.
|
42 |
+
*
|
43 |
+
* @var string
|
44 |
+
*/
|
45 |
+
const SUCCESS = 'success';
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Exceptional occurrences that are not errors.
|
49 |
+
*
|
50 |
+
* Example: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
|
51 |
+
*
|
52 |
+
* @var string
|
53 |
+
*/
|
54 |
+
const WARNING = 'warning';
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Runtime errors that do not require immediate action but should typically be logged and monitored.
|
58 |
+
*
|
59 |
+
* @var string
|
60 |
+
*/
|
61 |
+
const ERROR = 'error';
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Critical conditions.
|
65 |
+
*
|
66 |
+
* Example: Application component unavailable, unexpected exception.
|
67 |
+
*
|
68 |
+
* @var string
|
69 |
+
*/
|
70 |
+
const CRITICAL = 'critical';
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Action must be taken immediately.
|
74 |
+
*
|
75 |
+
* Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
|
76 |
+
*
|
77 |
+
* @var string
|
78 |
+
*/
|
79 |
+
const ALERT = 'alert';
|
80 |
+
|
81 |
+
/**
|
82 |
+
* System is unusable.
|
83 |
+
*
|
84 |
+
* @var string
|
85 |
+
*/
|
86 |
+
const EMERGENCY = 'emergency';
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Ordering to use for log levels.
|
90 |
+
*
|
91 |
+
* @var string[]
|
92 |
+
*/
|
93 |
+
const ORDER = [
|
94 |
+
self::DEBUG,
|
95 |
+
self::INFO,
|
96 |
+
self::NOTICE,
|
97 |
+
self::SUCCESS,
|
98 |
+
self::WARNING,
|
99 |
+
self::ERROR,
|
100 |
+
self::CRITICAL,
|
101 |
+
self::ALERT,
|
102 |
+
self::EMERGENCY,
|
103 |
+
];
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Test whether a given log level matches the currently set threshold.
|
107 |
+
*
|
108 |
+
* @param string $logLevel Log level to check.
|
109 |
+
* @param string $threshold Log level threshold to check against.
|
110 |
+
* @return bool Whether the provided log level matches the threshold.
|
111 |
+
*/
|
112 |
+
public static function matches($logLevel, $threshold)
|
113 |
+
{
|
114 |
+
return array_search($logLevel, self::ORDER, true) >= array_search($threshold, self::ORDER, true);
|
115 |
+
}
|
116 |
+
}
|
includes/vendor/tool/Cli/Options.php
ADDED
@@ -0,0 +1,558 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\Cli\InvalidArgument;
|
6 |
+
use AmpProject\Exception\Cli\InvalidCommand;
|
7 |
+
use AmpProject\Exception\Cli\InvalidOption;
|
8 |
+
use AmpProject\Exception\Cli\MissingArgument;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
12 |
+
* licensed under the MIT license.
|
13 |
+
*
|
14 |
+
* Source: https://github.com/splitbrain/php-cli/blob/8c2c001b1b55d194402cf18aad2757049ac6d575/src/Options.php
|
15 |
+
*/
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and
|
19 |
+
* commands and even generates a help text from this setup.
|
20 |
+
*
|
21 |
+
* @package ampproject/amp-toolbox
|
22 |
+
*/
|
23 |
+
class Options
|
24 |
+
{
|
25 |
+
|
26 |
+
/**
|
27 |
+
* List of options to parse.
|
28 |
+
*
|
29 |
+
* @var array
|
30 |
+
*/
|
31 |
+
protected $setup;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Storage for parsed options.
|
35 |
+
*
|
36 |
+
* @var array
|
37 |
+
*/
|
38 |
+
protected $options = [];
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Currently parsed command if any.
|
42 |
+
*
|
43 |
+
* @var string
|
44 |
+
*/
|
45 |
+
protected $command = '';
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Passed non-option arguments.
|
49 |
+
*
|
50 |
+
* @var array
|
51 |
+
*/
|
52 |
+
protected $arguments = [];
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Name of the executed script.
|
56 |
+
*
|
57 |
+
* @var string
|
58 |
+
*/
|
59 |
+
protected $bin;
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Instance of the Colors helper object.
|
63 |
+
*
|
64 |
+
* @var Colors
|
65 |
+
*/
|
66 |
+
protected $colors;
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Newline used for spacing help texts.
|
70 |
+
*
|
71 |
+
* @var string
|
72 |
+
*/
|
73 |
+
protected $newline = "\n";
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Constructor.
|
77 |
+
*
|
78 |
+
* @param Colors $colors Optional. Configured color object.
|
79 |
+
* @throws InvalidArgument When arguments can't be read.
|
80 |
+
*/
|
81 |
+
public function __construct(Colors $colors = null)
|
82 |
+
{
|
83 |
+
$this->colors = $colors instanceof Colors ? $colors : new Colors();
|
84 |
+
|
85 |
+
$this->setup = [
|
86 |
+
'' => [
|
87 |
+
'options' => [],
|
88 |
+
'arguments' => [],
|
89 |
+
'help' => '',
|
90 |
+
'commandHelp' => 'This tool accepts a command as first parameter as outlined below:',
|
91 |
+
],
|
92 |
+
]; // Default command.
|
93 |
+
|
94 |
+
$this->arguments = $this->readPHPArgv();
|
95 |
+
$this->bin = basename(array_shift($this->arguments));
|
96 |
+
|
97 |
+
$this->options = [];
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Gets the name of the binary that was executed.
|
102 |
+
*
|
103 |
+
* @return string Name of the binary that was executed.
|
104 |
+
*/
|
105 |
+
public function getBin()
|
106 |
+
{
|
107 |
+
return $this->bin;
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Sets the help text for the tool itself.
|
112 |
+
*
|
113 |
+
* @param string $help Help text to set.
|
114 |
+
*/
|
115 |
+
public function setHelp($help)
|
116 |
+
{
|
117 |
+
$this->setup['']['help'] = $help;
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Sets the help text for the tools commands itself.
|
122 |
+
*
|
123 |
+
* @param string $help Help text to set.
|
124 |
+
*/
|
125 |
+
public function setCommandHelp($help)
|
126 |
+
{
|
127 |
+
$this->setup['']['commandHelp'] = $help;
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Use a more compact help screen with less new lines.
|
132 |
+
*
|
133 |
+
* @param bool $set Optional. Whether to set compact help or not. Defaults to true.
|
134 |
+
*/
|
135 |
+
public function useCompactHelp($set = true)
|
136 |
+
{
|
137 |
+
$this->newline = $set ? '' : "\n";
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Register the names of arguments for help generation and number checking.
|
142 |
+
*
|
143 |
+
* This has to be called in the order arguments are expected.
|
144 |
+
*
|
145 |
+
* @param string $name Name of the argument.
|
146 |
+
* @param string $help Help text.
|
147 |
+
* @param bool $required Optional. Whether this argument is required. Defaults to true.
|
148 |
+
* @param string $command Optional. Command this argument applies to. Empty string (default) for global arguments.
|
149 |
+
* @throws InvalidCommand If the referenced command is not registered.
|
150 |
+
*/
|
151 |
+
public function registerArgument($name, $help, $required = true, $command = '')
|
152 |
+
{
|
153 |
+
if (! isset($this->setup[$command])) {
|
154 |
+
throw InvalidCommand::forUnregisteredCommand($command);
|
155 |
+
}
|
156 |
+
|
157 |
+
$this->setup[$command]['arguments'][] = [
|
158 |
+
'name' => $name,
|
159 |
+
'help' => $help,
|
160 |
+
'required' => $required,
|
161 |
+
];
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Register a sub command.
|
166 |
+
*
|
167 |
+
* Sub commands have their own options and use their own function (not main()).
|
168 |
+
*
|
169 |
+
* @param string $name Name of the command to register.
|
170 |
+
* @param string $help Help text of the command.
|
171 |
+
* @throws InvalidCommand If the referenced command is already registered.
|
172 |
+
*/
|
173 |
+
public function registerCommand($name, $help)
|
174 |
+
{
|
175 |
+
if (isset($this->setup[$name])) {
|
176 |
+
throw InvalidCommand::forAlreadyRegisteredCommand($name);
|
177 |
+
}
|
178 |
+
|
179 |
+
$this->setup[$name] = [
|
180 |
+
'options' => [],
|
181 |
+
'arguments' => [],
|
182 |
+
'help' => $help,
|
183 |
+
];
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Register an option for option parsing and help generation.
|
188 |
+
*
|
189 |
+
* @param string $long Multi character option (specified with --).
|
190 |
+
* @param string $help Help text for this option.
|
191 |
+
* @param string|null $short Optional. One character option (specified with -). Disable with null (default).
|
192 |
+
* @param bool|string $needsArgument Optional. Whether this option requires an argument. Use a boolean value, or
|
193 |
+
* provide a string to require a specific argument by name. Defaults to false.
|
194 |
+
* @param string $command Optional. Name of the command this option applies to. Use an empty string for
|
195 |
+
* none (default).
|
196 |
+
* @throws InvalidCommand If the referenced command is not registered.
|
197 |
+
* @throws InvalidArgument If the short option is too long.
|
198 |
+
*/
|
199 |
+
public function registerOption($long, $help, $short = null, $needsArgument = false, $command = '')
|
200 |
+
{
|
201 |
+
if (! isset($this->setup[$command])) {
|
202 |
+
throw InvalidCommand::forUnregisteredCommand($command);
|
203 |
+
}
|
204 |
+
|
205 |
+
$this->setup[$command]['options'][$long] = [
|
206 |
+
'needsArgument' => $needsArgument,
|
207 |
+
'help' => $help,
|
208 |
+
'short' => $short,
|
209 |
+
];
|
210 |
+
|
211 |
+
if ($short) {
|
212 |
+
if (strlen($short) > 1) {
|
213 |
+
throw InvalidArgument::forMultiCharacterShortOption();
|
214 |
+
}
|
215 |
+
|
216 |
+
$this->setup[$command]['short'][$short] = $long;
|
217 |
+
}
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* Checks the actual number of arguments against the required number.
|
222 |
+
*
|
223 |
+
* This is run from CLI automatically and usually does not need to be called directly.
|
224 |
+
*
|
225 |
+
* @throws MissingArgument If not enough arguments were provided.
|
226 |
+
*/
|
227 |
+
public function checkArguments()
|
228 |
+
{
|
229 |
+
$argumentCount = count($this->arguments);
|
230 |
+
|
231 |
+
$required = 0;
|
232 |
+
foreach ($this->setup[$this->command]['arguments'] as $argument) {
|
233 |
+
if (! $argument['required']) {
|
234 |
+
break;
|
235 |
+
} // Last required arguments seen.
|
236 |
+
$required++;
|
237 |
+
}
|
238 |
+
|
239 |
+
if ($required > $argumentCount) {
|
240 |
+
throw MissingArgument::forNotEnoughArguments();
|
241 |
+
}
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* Parses the given arguments for known options and command.
|
246 |
+
*
|
247 |
+
* The given $arguments array should NOT contain the executed file as first item anymore! The $arguments
|
248 |
+
* array is stripped from any options and possible command. All found options can be accessed via the
|
249 |
+
* getOptions() function.
|
250 |
+
*
|
251 |
+
* Note that command options will overwrite any global options with the same name.
|
252 |
+
*
|
253 |
+
* This is run from CLI automatically and usually does not need to be called directly.
|
254 |
+
*
|
255 |
+
* @throws InvalidOption If an unknown option was provided.
|
256 |
+
* @throws MissingArgument If an argument is missing.
|
257 |
+
*/
|
258 |
+
public function parseOptions()
|
259 |
+
{
|
260 |
+
$nonOptions = [];
|
261 |
+
|
262 |
+
$argumentCount = count($this->arguments);
|
263 |
+
for ($index = 0; $index < $argumentCount; $index++) {
|
264 |
+
$argument = $this->arguments[$index];
|
265 |
+
|
266 |
+
// The special element '--' means explicit end of options. Treat the rest of the arguments as non-options
|
267 |
+
// and end the loop.
|
268 |
+
if ($argument == '--') {
|
269 |
+
$nonOptions = array_merge($nonOptions, array_slice($this->arguments, $index + 1));
|
270 |
+
break;
|
271 |
+
}
|
272 |
+
|
273 |
+
// '-' is stdin - a normal argument.
|
274 |
+
if ($argument == '-') {
|
275 |
+
$nonOptions = array_merge($nonOptions, array_slice($this->arguments, $index));
|
276 |
+
break;
|
277 |
+
}
|
278 |
+
|
279 |
+
// First non-option.
|
280 |
+
if ($argument[0] != '-') {
|
281 |
+
$nonOptions = array_merge($nonOptions, array_slice($this->arguments, $index));
|
282 |
+
break;
|
283 |
+
}
|
284 |
+
|
285 |
+
// Long option.
|
286 |
+
if (strlen($argument) > 1 && $argument[1] === '-') {
|
287 |
+
$argument = explode('=', substr($argument, 2), 2);
|
288 |
+
$option = array_shift($argument);
|
289 |
+
$value = array_shift($argument);
|
290 |
+
|
291 |
+
if (! isset($this->setup[$this->command]['options'][$option])) {
|
292 |
+
throw InvalidOption::forUnknownOption($option);
|
293 |
+
}
|
294 |
+
|
295 |
+
// Argument required?
|
296 |
+
if ($this->setup[$this->command]['options'][$option]['needsArgument']) {
|
297 |
+
if (
|
298 |
+
is_null($value) && $index + 1 < $argumentCount && ! preg_match(
|
299 |
+
'/^--?[\w]/',
|
300 |
+
$this->arguments[$index + 1]
|
301 |
+
)
|
302 |
+
) {
|
303 |
+
$value = $this->arguments[++$index];
|
304 |
+
}
|
305 |
+
if (is_null($value)) {
|
306 |
+
throw MissingArgument::forNoArgument($option);
|
307 |
+
}
|
308 |
+
$this->options[$option] = $value;
|
309 |
+
} else {
|
310 |
+
$this->options[$option] = true;
|
311 |
+
}
|
312 |
+
|
313 |
+
continue;
|
314 |
+
}
|
315 |
+
|
316 |
+
// Short option.
|
317 |
+
$option = substr($argument, 1);
|
318 |
+
if (! isset($this->setup[$this->command]['short'][$option])) {
|
319 |
+
throw InvalidOption::forUnknownOption($option);
|
320 |
+
} else {
|
321 |
+
$option = $this->setup[$this->command]['short'][$option]; // Store it under long name.
|
322 |
+
}
|
323 |
+
|
324 |
+
// Argument required?
|
325 |
+
if ($this->setup[$this->command]['options'][$option]['needsArgument']) {
|
326 |
+
$value = null;
|
327 |
+
if ($index + 1 < $argumentCount && ! preg_match('/^--?[\w]/', $this->arguments[$index + 1])) {
|
328 |
+
$value = $this->arguments[++$index];
|
329 |
+
}
|
330 |
+
if (is_null($value)) {
|
331 |
+
throw MissingArgument::forNoArgument($option);
|
332 |
+
}
|
333 |
+
$this->options[$option] = $value;
|
334 |
+
} else {
|
335 |
+
$this->options[$option] = true;
|
336 |
+
}
|
337 |
+
}
|
338 |
+
|
339 |
+
// Parsing is now done, update arguments array.
|
340 |
+
$this->arguments = $nonOptions;
|
341 |
+
|
342 |
+
// If not done yet, check if first argument is a command and re-execute argument parsing if it is.
|
343 |
+
if (! $this->command && $this->arguments && isset($this->setup[$this->arguments[0]])) {
|
344 |
+
// It is a command!
|
345 |
+
$this->command = array_shift($this->arguments);
|
346 |
+
$this->parseOptions(); // Second pass.
|
347 |
+
}
|
348 |
+
}
|
349 |
+
|
350 |
+
/**
|
351 |
+
* Get the value of the given option.
|
352 |
+
*
|
353 |
+
* Please note that all options are accessed by their long option names regardless of how they were
|
354 |
+
* specified on commandline.
|
355 |
+
*
|
356 |
+
* Can only be used after parseOptions() has been run.
|
357 |
+
*
|
358 |
+
* @param mixed $option Optional. Option to get. Use null to get all options (default).
|
359 |
+
* @param bool|string $default Optional. Default value to return if the option is not set. Defaults to false.
|
360 |
+
* @return bool|string|string[] Value of the option.
|
361 |
+
*/
|
362 |
+
public function getOption($option = null, $default = false)
|
363 |
+
{
|
364 |
+
if ($option === null) {
|
365 |
+
return $this->options;
|
366 |
+
}
|
367 |
+
|
368 |
+
if (isset($this->options[$option])) {
|
369 |
+
return $this->options[$option];
|
370 |
+
}
|
371 |
+
|
372 |
+
return $default;
|
373 |
+
}
|
374 |
+
|
375 |
+
/**
|
376 |
+
* Return the found command, if any.
|
377 |
+
*
|
378 |
+
* @return string Name of the command that was found.
|
379 |
+
*/
|
380 |
+
public function getCommand()
|
381 |
+
{
|
382 |
+
return $this->command;
|
383 |
+
}
|
384 |
+
|
385 |
+
/**
|
386 |
+
* Get all the arguments passed to the script.
|
387 |
+
*
|
388 |
+
* This will not contain any recognized options or the script name itself.
|
389 |
+
*
|
390 |
+
* @return array Associative array of arguments.
|
391 |
+
*/
|
392 |
+
public function getArguments()
|
393 |
+
{
|
394 |
+
return $this->arguments;
|
395 |
+
}
|
396 |
+
|
397 |
+
/**
|
398 |
+
* Builds a help screen from the available options.
|
399 |
+
*
|
400 |
+
* You may want to call it from -h or on error.
|
401 |
+
*
|
402 |
+
* @return string Help screen text.
|
403 |
+
*/
|
404 |
+
public function help()
|
405 |
+
{
|
406 |
+
$tableFormatter = new TableFormatter($this->colors);
|
407 |
+
$text = '';
|
408 |
+
|
409 |
+
$hasCommands = (count($this->setup) > 1);
|
410 |
+
$commandHelp = $this->setup['']['commandHelp'];
|
411 |
+
|
412 |
+
foreach ($this->setup as $command => $config) {
|
413 |
+
$hasOptions = (bool)$this->setup[$command]['options'];
|
414 |
+
$hasArguments = (bool)$this->setup[$command]['arguments'];
|
415 |
+
|
416 |
+
// Usage or command syntax line.
|
417 |
+
if (! $command) {
|
418 |
+
$text .= $this->colors->wrap('USAGE:', Colors::C_BROWN);
|
419 |
+
$text .= "\n";
|
420 |
+
$text .= ' ' . $this->bin;
|
421 |
+
$indentation = 2;
|
422 |
+
} else {
|
423 |
+
$text .= $this->newline;
|
424 |
+
$text .= $this->colors->wrap(' ' . $command, Colors::C_PURPLE);
|
425 |
+
$indentation = 4;
|
426 |
+
}
|
427 |
+
|
428 |
+
if ($hasOptions) {
|
429 |
+
$text .= ' ' . $this->colors->wrap('<OPTIONS>', Colors::C_GREEN);
|
430 |
+
}
|
431 |
+
|
432 |
+
if (! $command && $hasCommands) {
|
433 |
+
$text .= ' ' . $this->colors->wrap('<COMMAND> ...', Colors::C_PURPLE);
|
434 |
+
}
|
435 |
+
|
436 |
+
foreach ($this->setup[$command]['arguments'] as $argument) {
|
437 |
+
$output = $this->colors->wrap('<' . $argument['name'] . '>', Colors::C_CYAN);
|
438 |
+
|
439 |
+
if (! $argument['required']) {
|
440 |
+
$output = '[' . $output . ']';
|
441 |
+
}
|
442 |
+
$text .= ' ' . $output;
|
443 |
+
}
|
444 |
+
$text .= $this->newline;
|
445 |
+
|
446 |
+
// Usage or command intro.
|
447 |
+
if ($this->setup[$command]['help']) {
|
448 |
+
$text .= "\n";
|
449 |
+
$text .= $tableFormatter->format(
|
450 |
+
[$indentation, '*'],
|
451 |
+
['', $this->setup[$command]['help'] . $this->newline]
|
452 |
+
);
|
453 |
+
}
|
454 |
+
|
455 |
+
// Option description.
|
456 |
+
if ($hasOptions) {
|
457 |
+
if (! $command) {
|
458 |
+
$text .= "\n";
|
459 |
+
$text .= $this->colors->wrap('OPTIONS:', Colors::C_BROWN);
|
460 |
+
}
|
461 |
+
$text .= "\n";
|
462 |
+
foreach ($this->setup[$command]['options'] as $long => $option) {
|
463 |
+
$name = '';
|
464 |
+
if ($option['short']) {
|
465 |
+
$name .= '-' . $option['short'];
|
466 |
+
if ($option['needsArgument']) {
|
467 |
+
$name .= ' <' . $option['needsArgument'] . '>';
|
468 |
+
}
|
469 |
+
$name .= ', ';
|
470 |
+
}
|
471 |
+
$name .= "--$long";
|
472 |
+
if ($option['needsArgument']) {
|
473 |
+
$name .= ' <' . $option['needsArgument'] . '>';
|
474 |
+
}
|
475 |
+
|
476 |
+
$text .= $tableFormatter->format(
|
477 |
+
[$indentation, '30%', '*'],
|
478 |
+
['', $name, $option['help']],
|
479 |
+
['', 'green', '']
|
480 |
+
);
|
481 |
+
$text .= $this->newline;
|
482 |
+
}
|
483 |
+
}
|
484 |
+
|
485 |
+
// Argument description.
|
486 |
+
if ($hasArguments) {
|
487 |
+
if (! $command) {
|
488 |
+
$text .= "\n";
|
489 |
+
$text .= $this->colors->wrap('ARGUMENTS:', Colors::C_BROWN);
|
490 |
+
}
|
491 |
+
$text .= $this->newline;
|
492 |
+
foreach ($this->setup[$command]['arguments'] as $argument) {
|
493 |
+
$name = '<' . $argument['name'] . '>';
|
494 |
+
|
495 |
+
$text .= $tableFormatter->format(
|
496 |
+
[$indentation, '30%', '*'],
|
497 |
+
['', $name, $argument['help']],
|
498 |
+
['', 'cyan', '']
|
499 |
+
);
|
500 |
+
}
|
501 |
+
}
|
502 |
+
|
503 |
+
// Headline and intro for following command documentation.
|
504 |
+
if (! $command && $hasCommands) {
|
505 |
+
$text .= "\n";
|
506 |
+
$text .= $this->colors->wrap('COMMANDS:', Colors::C_BROWN);
|
507 |
+
$text .= "\n";
|
508 |
+
$text .= $tableFormatter->format(
|
509 |
+
[$indentation, '*'],
|
510 |
+
['', $commandHelp]
|
511 |
+
);
|
512 |
+
$text .= $this->newline;
|
513 |
+
}
|
514 |
+
}
|
515 |
+
|
516 |
+
return $text;
|
517 |
+
}
|
518 |
+
|
519 |
+
/**
|
520 |
+
* Safely read the $argv PHP array across different PHP configurations.
|
521 |
+
* Will take care of register_globals and register_argc_argv ini directives.
|
522 |
+
*
|
523 |
+
* @return array The $argv PHP array.
|
524 |
+
* @throws InvalidArgument If the $argv array could not be read.
|
525 |
+
*/
|
526 |
+
private function readPHPArgv()
|
527 |
+
{
|
528 |
+
global $argv;
|
529 |
+
|
530 |
+
if (is_array($argv)) {
|
531 |
+
return $argv;
|
532 |
+
}
|
533 |
+
|
534 |
+
if (
|
535 |
+
is_array($_SERVER)
|
536 |
+
&&
|
537 |
+
array_key_exists('argv', $_SERVER)
|
538 |
+
&&
|
539 |
+
is_array($_SERVER['argv'])
|
540 |
+
) {
|
541 |
+
return $_SERVER['argv'];
|
542 |
+
}
|
543 |
+
|
544 |
+
if (
|
545 |
+
array_key_exists('HTTP_SERVER_VARS', $GLOBALS)
|
546 |
+
&&
|
547 |
+
is_array($GLOBALS['HTTP_SERVER_VARS'])
|
548 |
+
&&
|
549 |
+
array_key_exists('argv', $GLOBALS['HTTP_SERVER_VARS'])
|
550 |
+
&&
|
551 |
+
is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])
|
552 |
+
) {
|
553 |
+
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
|
554 |
+
}
|
555 |
+
|
556 |
+
throw InvalidArgument::forUnreadableArguments();
|
557 |
+
}
|
558 |
+
}
|
includes/vendor/tool/Cli/TableFormatter.php
ADDED
@@ -0,0 +1,361 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\Cli\InvalidColumnFormat;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* This file is adapted from the splitbrain\php-cli library, which is authored by Andreas Gohr <andi@splitbrain.org> and
|
9 |
+
* licensed under the MIT license.
|
10 |
+
*
|
11 |
+
* Source: https://github.com/splitbrain/php-cli/blob/8c2c001b1b55d194402cf18aad2757049ac6d575/src/TableFormatter.php
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Output text in multiple columns.
|
16 |
+
*
|
17 |
+
* @package ampproject/amp-toolbox
|
18 |
+
*/
|
19 |
+
class TableFormatter
|
20 |
+
{
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Border between columns.
|
24 |
+
*
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
protected $border = ' ';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* The terminal width in characters.
|
31 |
+
*
|
32 |
+
* Falls back to 74 characters if it cannot be detected.
|
33 |
+
*
|
34 |
+
* @var int
|
35 |
+
*/
|
36 |
+
protected $maxWidth = 74;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Instance of the Colors helper object.
|
40 |
+
*
|
41 |
+
* @var Colors
|
42 |
+
*/
|
43 |
+
protected $colors;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* TableFormatter constructor.
|
47 |
+
*
|
48 |
+
* @param Colors|null $colors Optional. Instance of the Colors helper object.
|
49 |
+
*/
|
50 |
+
public function __construct(Colors $colors = null)
|
51 |
+
{
|
52 |
+
// Try to get terminal width.
|
53 |
+
$width = $this->getTerminalWidth();
|
54 |
+
|
55 |
+
if ($width) {
|
56 |
+
$this->maxWidth = $width - 1;
|
57 |
+
}
|
58 |
+
|
59 |
+
$this->colors = $colors instanceof Colors ? $colors : new Colors();
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* The currently set border.
|
64 |
+
*
|
65 |
+
* Defaults to ' '.
|
66 |
+
*
|
67 |
+
* @return string
|
68 |
+
*/
|
69 |
+
public function getBorder()
|
70 |
+
{
|
71 |
+
return $this->border;
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Set the border.
|
76 |
+
*
|
77 |
+
* The border is set between each column. Its width is added to the column widths.
|
78 |
+
*
|
79 |
+
* @param string $border Border to set.
|
80 |
+
*/
|
81 |
+
public function setBorder($border)
|
82 |
+
{
|
83 |
+
$this->border = $border;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Width of the terminal in characters.
|
88 |
+
*
|
89 |
+
* Initially auto-detected, with a fallback of 74 characters.
|
90 |
+
*
|
91 |
+
* @return int
|
92 |
+
*/
|
93 |
+
public function getMaxWidth()
|
94 |
+
{
|
95 |
+
return $this->maxWidth;
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Set the width of the terminal to assume (in characters).
|
100 |
+
*
|
101 |
+
* @param int $maxWidth Terminal width in characters.
|
102 |
+
*/
|
103 |
+
public function setMaxWidth($maxWidth)
|
104 |
+
{
|
105 |
+
$this->maxWidth = $maxWidth;
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Displays text in multiple word wrapped columns.
|
110 |
+
*
|
111 |
+
* @param array<int|string> $columns List of column widths (in characters, percent or '*').
|
112 |
+
* @param array<string> $texts List of texts for each column.
|
113 |
+
* @param array<string> $colors Optional. A list of color names to use for each column. Use empty string within
|
114 |
+
* the array for default. Defaults to an empty array.
|
115 |
+
* @return string Adapted text.
|
116 |
+
*/
|
117 |
+
public function format($columns, $texts, $colors = [])
|
118 |
+
{
|
119 |
+
$columns = $this->calculateColumnWidths($columns);
|
120 |
+
$wrapped = [];
|
121 |
+
$maxLength = 0;
|
122 |
+
|
123 |
+
foreach ($columns as $column => $width) {
|
124 |
+
$wrapped[$column] = explode("\n", $this->wordwrap($texts[$column], $width, "\n", true));
|
125 |
+
$length = count($wrapped[$column]);
|
126 |
+
if ($length > $maxLength) {
|
127 |
+
$maxLength = $length;
|
128 |
+
}
|
129 |
+
}
|
130 |
+
|
131 |
+
$last = count($columns) - 1;
|
132 |
+
$output = '';
|
133 |
+
for ($index = 0; $index < $maxLength; $index++) {
|
134 |
+
foreach ($columns as $column => $width) {
|
135 |
+
if (isset($wrapped[$column][$index])) {
|
136 |
+
$value = $wrapped[$column][$index];
|
137 |
+
} else {
|
138 |
+
$value = '';
|
139 |
+
}
|
140 |
+
$chunk = $this->pad($value, $width);
|
141 |
+
if (isset($colors[$column]) && $colors[$column]) {
|
142 |
+
$chunk = $this->colors->wrap($chunk, $colors[$column]);
|
143 |
+
}
|
144 |
+
$output .= $chunk;
|
145 |
+
|
146 |
+
// Add border in-between columns.
|
147 |
+
if ($column != $last) {
|
148 |
+
$output .= $this->border;
|
149 |
+
}
|
150 |
+
}
|
151 |
+
$output .= "\n";
|
152 |
+
}
|
153 |
+
|
154 |
+
return $output;
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Tries to figure out the width of the terminal.
|
159 |
+
*
|
160 |
+
* @return int Terminal width, 0 if unknown.
|
161 |
+
*/
|
162 |
+
protected function getTerminalWidth()
|
163 |
+
{
|
164 |
+
// From environment.
|
165 |
+
if (isset($_SERVER['COLUMNS'])) {
|
166 |
+
return (int)$_SERVER['COLUMNS'];
|
167 |
+
}
|
168 |
+
|
169 |
+
// Via tput.
|
170 |
+
$process = proc_open(
|
171 |
+
'tput cols',
|
172 |
+
[
|
173 |
+
1 => ['pipe', 'w'],
|
174 |
+
2 => ['pipe', 'w'],
|
175 |
+
],
|
176 |
+
$pipes
|
177 |
+
);
|
178 |
+
|
179 |
+
$width = (int)stream_get_contents($pipes[1]);
|
180 |
+
|
181 |
+
proc_close($process);
|
182 |
+
|
183 |
+
return $width;
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Takes an array with dynamic column width and calculates the correct widths.
|
188 |
+
*
|
189 |
+
* Column width can be given as fixed char widths, percentages and a single * width can be given
|
190 |
+
* for taking the remaining available space. When mixing percentages and fixed widths, percentages
|
191 |
+
* refer to the remaining space after allocating the fixed width.
|
192 |
+
*
|
193 |
+
* @param array $columns Columns to calculate the widths for.
|
194 |
+
* @return int[] Array of calculated column widths.
|
195 |
+
* @throws InvalidColumnFormat If the column format is not valid.
|
196 |
+
*/
|
197 |
+
protected function calculateColumnWidths($columns)
|
198 |
+
{
|
199 |
+
$index = 0;
|
200 |
+
$border = $this->strlen($this->border);
|
201 |
+
$fixed = (count($columns) - 1) * $border; // Borders are used already.
|
202 |
+
$fluid = -1;
|
203 |
+
|
204 |
+
// First pass for format check and fixed columns.
|
205 |
+
foreach ($columns as $index => $column) {
|
206 |
+
// Handle fixed columns.
|
207 |
+
if ((string)intval($column) === (string)$column) {
|
208 |
+
$fixed += $column;
|
209 |
+
continue;
|
210 |
+
}
|
211 |
+
// Check if other columns are using proper units.
|
212 |
+
if (substr($column, -1) === '%') {
|
213 |
+
continue;
|
214 |
+
}
|
215 |
+
if ($column === '*') {
|
216 |
+
// Only one fluid.
|
217 |
+
if ($fluid < 0) {
|
218 |
+
$fluid = $index;
|
219 |
+
continue;
|
220 |
+
} else {
|
221 |
+
throw InvalidColumnFormat::forMultipleFluidColumns();
|
222 |
+
}
|
223 |
+
}
|
224 |
+
throw InvalidColumnFormat::forUnknownColumnFormat($column);
|
225 |
+
}
|
226 |
+
|
227 |
+
$allocated = $fixed;
|
228 |
+
$remain = $this->maxWidth - $allocated;
|
229 |
+
|
230 |
+
// Second pass to handle percentages.
|
231 |
+
foreach ($columns as $index => $column) {
|
232 |
+
if (substr($column, -1) !== '%') {
|
233 |
+
continue;
|
234 |
+
}
|
235 |
+
$percent = floatval($column);
|
236 |
+
|
237 |
+
$real = (int)floor(($percent * $remain) / 100);
|
238 |
+
|
239 |
+
$columns[$index] = $real;
|
240 |
+
$allocated += $real;
|
241 |
+
}
|
242 |
+
|
243 |
+
$remain = $this->maxWidth - $allocated;
|
244 |
+
if ($remain < 0) {
|
245 |
+
throw InvalidColumnFormat::forExceededMaxWidth();
|
246 |
+
}
|
247 |
+
|
248 |
+
// Assign remaining space.
|
249 |
+
if ($fluid < 0) {
|
250 |
+
$columns[$index] += ($remain); // Add to last column.
|
251 |
+
} else {
|
252 |
+
$columns[$fluid] = $remain;
|
253 |
+
}
|
254 |
+
|
255 |
+
return $columns;
|
256 |
+
}
|
257 |
+
|
258 |
+
/**
|
259 |
+
* Pad the given string to the correct length.
|
260 |
+
*
|
261 |
+
* @param string $string String to pad.
|
262 |
+
* @param int $length Length to pad the string to.
|
263 |
+
* @return string Padded string.
|
264 |
+
*/
|
265 |
+
protected function pad($string, $length)
|
266 |
+
{
|
267 |
+
$strlen = $this->strlen($string);
|
268 |
+
|
269 |
+
if ($strlen > $length) {
|
270 |
+
return $string;
|
271 |
+
}
|
272 |
+
|
273 |
+
$pad = $length - $strlen;
|
274 |
+
|
275 |
+
return $string . str_pad('', $pad, ' ');
|
276 |
+
}
|
277 |
+
|
278 |
+
/**
|
279 |
+
* Measures character length in UTF-8 when possible.
|
280 |
+
*
|
281 |
+
* @param string $string String to measure the character length of.
|
282 |
+
* @return int Count of characters.
|
283 |
+
*/
|
284 |
+
protected function strlen($string)
|
285 |
+
{
|
286 |
+
// Don't count color codes.
|
287 |
+
$string = preg_replace("/\33\\[\\d+(;\\d+)?m/", '', $string);
|
288 |
+
|
289 |
+
if (function_exists('mb_strlen')) {
|
290 |
+
return mb_strlen($string, 'utf-8');
|
291 |
+
}
|
292 |
+
|
293 |
+
return strlen($string);
|
294 |
+
}
|
295 |
+
|
296 |
+
/**
|
297 |
+
* Extract a substring in UTF-8 if possible.
|
298 |
+
* @param string $string String to extract a substring out of.
|
299 |
+
* @param int $start Optional. Starting index to extract from. Defaults to 0.
|
300 |
+
* @param int|null $length Optional. Length to extract. Set to null to use the remainder of the string (default).
|
301 |
+
* @return string Extracted substring.
|
302 |
+
*/
|
303 |
+
protected function substr($string, $start = 0, $length = null)
|
304 |
+
{
|
305 |
+
if (function_exists('mb_substr')) {
|
306 |
+
return mb_substr($string, $start, $length);
|
307 |
+
}
|
308 |
+
|
309 |
+
// mb_substr() treats $length differently than substr().
|
310 |
+
if ($length) {
|
311 |
+
return substr($string, $start, $length);
|
312 |
+
}
|
313 |
+
|
314 |
+
return substr($string, $start);
|
315 |
+
}
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Wrap words of a string into a requested width.
|
319 |
+
*
|
320 |
+
* @param string $string String to wrap.
|
321 |
+
* @param int $width Optional. Width to warp the string into. Defaults to 75.
|
322 |
+
* @param string $break Optional. Character to use for wrapping. Defaults to a newline character. Defaults to the
|
323 |
+
* newline character.
|
324 |
+
* @param bool $cut Optional. Whether to cut longer words to enforce the width. Defaults to false.
|
325 |
+
* @return string Word-wrapped string.
|
326 |
+
* @link http://stackoverflow.com/a/4988494
|
327 |
+
*/
|
328 |
+
protected function wordwrap($string, $width = 75, $break = "\n", $cut = false)
|
329 |
+
{
|
330 |
+
$lines = explode($break, $string);
|
331 |
+
foreach ($lines as &$line) {
|
332 |
+
$line = rtrim($line);
|
333 |
+
if ($this->strlen($line) <= $width) {
|
334 |
+
continue;
|
335 |
+
}
|
336 |
+
$words = explode(' ', $line);
|
337 |
+
$line = '';
|
338 |
+
$actual = '';
|
339 |
+
foreach ($words as $word) {
|
340 |
+
if ($this->strlen($actual . $word) <= $width) {
|
341 |
+
$actual .= $word . ' ';
|
342 |
+
} else {
|
343 |
+
if ($actual != '') {
|
344 |
+
$line .= rtrim($actual) . $break;
|
345 |
+
}
|
346 |
+
$actual = $word;
|
347 |
+
if ($cut) {
|
348 |
+
while ($this->strlen($actual) > $width) {
|
349 |
+
$line .= $this->substr($actual, 0, $width) . $break;
|
350 |
+
$actual = $this->substr($actual, $width);
|
351 |
+
}
|
352 |
+
}
|
353 |
+
$actual .= ' ';
|
354 |
+
}
|
355 |
+
}
|
356 |
+
$line .= trim($actual);
|
357 |
+
}
|
358 |
+
|
359 |
+
return implode($break, $lines);
|
360 |
+
}
|
361 |
+
}
|
includes/vendor/tool/CssLength.php
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Flexible unit of measure for CSS dimensions.
|
7 |
+
*
|
8 |
+
* Adapted from the `amp.validator.CssLength` class found in `validator.js` from the `ampproject/amphtml` project on
|
9 |
+
* GitHub.
|
10 |
+
*
|
11 |
+
* @version 1911070201440
|
12 |
+
* @link https://github.com/ampproject/amphtml/blob/1911070201440/validator/engine/validator.js#L3351
|
13 |
+
*
|
14 |
+
* @package ampproject/amp-toolbox
|
15 |
+
*/
|
16 |
+
final class CssLength
|
17 |
+
{
|
18 |
+
|
19 |
+
// Special attribute values.
|
20 |
+
const AUTO = 'auto';
|
21 |
+
const FLUID = 'fluid';
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Whether the value or unit is invalid. Note that passing an empty value as `$attr_value` is considered valid.
|
25 |
+
*
|
26 |
+
* @var bool
|
27 |
+
*/
|
28 |
+
protected $isValid = false;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Whether the attribute value is set.
|
32 |
+
*
|
33 |
+
* @var bool
|
34 |
+
*/
|
35 |
+
protected $isDefined = false;
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Whether the attribute value is 'auto'. This is a special value that indicates that the value gets derived from
|
39 |
+
* the context. In practice that's only ever the case for a width.
|
40 |
+
*
|
41 |
+
* @var bool
|
42 |
+
*/
|
43 |
+
protected $isAuto = false;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Whether the attribute value is 'fluid'.
|
47 |
+
*
|
48 |
+
* @var bool
|
49 |
+
*/
|
50 |
+
protected $isFluid = false;
|
51 |
+
|
52 |
+
/**
|
53 |
+
* The numeric value.
|
54 |
+
*
|
55 |
+
* @var float
|
56 |
+
*/
|
57 |
+
protected $numeral = 0;
|
58 |
+
|
59 |
+
/**
|
60 |
+
* The unit, 'px' being the default in case it's absent.
|
61 |
+
*
|
62 |
+
* @var string
|
63 |
+
*/
|
64 |
+
protected $unit = 'px';
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Value of attribute.
|
68 |
+
*
|
69 |
+
* @var string
|
70 |
+
*/
|
71 |
+
protected $attrValue;
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Instantiate a CssLength object.
|
75 |
+
*
|
76 |
+
* @param string|null $attrValue Attribute value to be parsed.
|
77 |
+
*/
|
78 |
+
public function __construct($attrValue)
|
79 |
+
{
|
80 |
+
if (null === $attrValue) {
|
81 |
+
$this->isValid = true;
|
82 |
+
return;
|
83 |
+
}
|
84 |
+
|
85 |
+
$this->attrValue = $attrValue;
|
86 |
+
$this->isDefined = true;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Validate the attribute value.
|
91 |
+
*
|
92 |
+
* @param bool $allowAuto Whether or not to allow the 'auto' value as a value.
|
93 |
+
* @param bool $allowFluid Whether or not to allow the 'fluid' value as a value.
|
94 |
+
*/
|
95 |
+
public function validate($allowAuto, $allowFluid)
|
96 |
+
{
|
97 |
+
if ($this->isValid()) {
|
98 |
+
return;
|
99 |
+
}
|
100 |
+
|
101 |
+
if (self::AUTO === $this->attrValue) {
|
102 |
+
$this->isAuto = true;
|
103 |
+
$this->isValid = $allowAuto;
|
104 |
+
return;
|
105 |
+
}
|
106 |
+
|
107 |
+
if (self::FLUID === $this->attrValue) {
|
108 |
+
$this->isFluid = true;
|
109 |
+
$this->isValid = $allowFluid;
|
110 |
+
}
|
111 |
+
|
112 |
+
$pattern = '/^(?<numeral>\d+(?:\.\d+)?)(?<unit>px|em|rem|vh|vw|vmin|vmax)?$/';
|
113 |
+
if (preg_match($pattern, $this->attrValue, $match)) {
|
114 |
+
$this->isValid = true;
|
115 |
+
$this->numeral = isset($match['numeral']) ? (float)$match['numeral'] : $this->numeral;
|
116 |
+
$this->unit = isset($match['unit']) ? $match['unit'] : $this->unit;
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Whether or not the attribute value is valid.
|
122 |
+
*
|
123 |
+
* @return bool
|
124 |
+
*/
|
125 |
+
public function isValid()
|
126 |
+
{
|
127 |
+
return $this->isValid;
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Whether the attribute value is set.
|
132 |
+
*
|
133 |
+
* @return bool
|
134 |
+
*/
|
135 |
+
public function isDefined()
|
136 |
+
{
|
137 |
+
return $this->isDefined;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Whether the attribute value is 'fluid'.
|
142 |
+
*
|
143 |
+
* @return bool
|
144 |
+
*/
|
145 |
+
public function isFluid()
|
146 |
+
{
|
147 |
+
return $this->isFluid;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Whether the attribute value is 'auto'.
|
152 |
+
*
|
153 |
+
* @return bool
|
154 |
+
*/
|
155 |
+
public function isAuto()
|
156 |
+
{
|
157 |
+
return $this->isAuto;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* The unit of the attribute.
|
162 |
+
*
|
163 |
+
* @return string
|
164 |
+
*/
|
165 |
+
public function getUnit()
|
166 |
+
{
|
167 |
+
return $this->unit;
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* The numeral of the attribute.
|
172 |
+
*
|
173 |
+
* @return float
|
174 |
+
*/
|
175 |
+
public function getNumeral()
|
176 |
+
{
|
177 |
+
return $this->numeral;
|
178 |
+
}
|
179 |
+
}
|
includes/vendor/tool/DevMode.php
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Document;
|
6 |
+
use AmpProject\Dom\Element;
|
7 |
+
use DOMNode;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Helper functionality to deal with AMP dev-mode.
|
11 |
+
*
|
12 |
+
* @link https://github.com/ampproject/amphtml/issues/20974
|
13 |
+
*
|
14 |
+
* @package ampproject/amp-toolbox
|
15 |
+
*/
|
16 |
+
final class DevMode
|
17 |
+
{
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Attribute name for AMP dev mode.
|
21 |
+
*
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
const DEV_MODE_ATTRIBUTE = 'data-ampdevmode';
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Check whether the provided document is in dev mode.
|
28 |
+
*
|
29 |
+
* @param Document $document Document for which to check whether dev mode is active.
|
30 |
+
* @return bool Whether the document is in dev mode.
|
31 |
+
*/
|
32 |
+
public static function isActiveForDocument(Document $document)
|
33 |
+
{
|
34 |
+
return $document->documentElement->hasAttribute(self::DEV_MODE_ATTRIBUTE);
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Check whether a node is exempt from validation during dev mode.
|
39 |
+
*
|
40 |
+
* @param DOMNode $node Node to check.
|
41 |
+
* @return bool Whether the node should be exempt during dev mode.
|
42 |
+
*/
|
43 |
+
public static function hasExemptionForNode(DOMNode $node)
|
44 |
+
{
|
45 |
+
if (! $node instanceof Element) {
|
46 |
+
return false;
|
47 |
+
}
|
48 |
+
|
49 |
+
$document = self::getDocument($node);
|
50 |
+
|
51 |
+
if ($node === $document->documentElement) {
|
52 |
+
return $document->hasInitialAmpDevMode();
|
53 |
+
}
|
54 |
+
|
55 |
+
return $node->hasAttribute(self::DEV_MODE_ATTRIBUTE);
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Check whether a certain node should be exempt from validation.
|
60 |
+
*
|
61 |
+
* @param DOMNode $node Node to check.
|
62 |
+
* @return bool Whether the node should be exempt from validation.
|
63 |
+
*/
|
64 |
+
public static function isExemptFromValidation(DOMNode $node)
|
65 |
+
{
|
66 |
+
$document = self::getDocument($node);
|
67 |
+
return self::isActiveForDocument($document) && self::hasExemptionForNode($node);
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Get the document from the specified node.
|
72 |
+
*
|
73 |
+
* @param DOMNode $node
|
74 |
+
* @return Document
|
75 |
+
*/
|
76 |
+
private static function getDocument(DOMNode $node)
|
77 |
+
{
|
78 |
+
$document = $node->ownerDocument;
|
79 |
+
if (! $document instanceof Document) {
|
80 |
+
$document = Document::fromNode($node);
|
81 |
+
}
|
82 |
+
|
83 |
+
return $document;
|
84 |
+
}
|
85 |
+
}
|
includes/vendor/tool/Dom/Document.php
ADDED
@@ -0,0 +1,2148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Dom;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\DevMode;
|
8 |
+
use AmpProject\Dom\Document\Encoding;
|
9 |
+
use AmpProject\Dom\Document\Option;
|
10 |
+
use AmpProject\Exception\FailedToRetrieveRequiredDomElement;
|
11 |
+
use AmpProject\Exception\MaxCssByteCountExceeded;
|
12 |
+
use AmpProject\Optimizer\CssRule;
|
13 |
+
use AmpProject\Tag;
|
14 |
+
use DOMAttr;
|
15 |
+
use DOMComment;
|
16 |
+
use DOMDocument;
|
17 |
+
use DOMElement;
|
18 |
+
use DOMNode;
|
19 |
+
use DOMNodeList;
|
20 |
+
use DOMText;
|
21 |
+
use DOMXPath;
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Abstract away some of the difficulties of working with PHP's DOMDocument.
|
25 |
+
*
|
26 |
+
* @property DOMXPath $xpath XPath query object for this document.
|
27 |
+
* @property Element $html The document's <html> element.
|
28 |
+
* @property Element $head The document's <head> element.
|
29 |
+
* @property Element $body The document's <body> element.
|
30 |
+
* @property Element|null $viewport The document's viewport meta element.
|
31 |
+
* @property DOMNodeList $ampElements The document's <amp-*> elements.
|
32 |
+
* @property Element $ampCustomStyle The document's <style amp-custom> element.
|
33 |
+
* @property int $ampCustomStyleByteCount Count of bytes of the CSS in the <style amp-custom> tag.
|
34 |
+
* @property int $inlineStyleByteCount Count of bytes of the CSS in all of the inline style attributes.
|
35 |
+
*
|
36 |
+
* @package ampproject/amp-toolbox
|
37 |
+
*/
|
38 |
+
final class Document extends DOMDocument
|
39 |
+
{
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Default document type to use.
|
43 |
+
*
|
44 |
+
* @var string
|
45 |
+
*/
|
46 |
+
const DEFAULT_DOCTYPE = '<!DOCTYPE html>';
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Regular expression to match the HTML doctype.
|
50 |
+
*
|
51 |
+
* @var string
|
52 |
+
*/
|
53 |
+
const HTML_DOCTYPE_REGEX_PATTERN = '#<!doctype\s+html[^>]+?>#si';
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Attribute prefix for AMP-bind data attributes.
|
57 |
+
*
|
58 |
+
* @var string
|
59 |
+
*/
|
60 |
+
const AMP_BIND_DATA_ATTR_PREFIX = 'data-amp-bind-';
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Pattern for HTML attribute accounting for binding attr name in square brackets syntax, boolean attribute,
|
64 |
+
* single/double-quoted attribute value, and unquoted attribute values.
|
65 |
+
*
|
66 |
+
* @var string
|
67 |
+
*/
|
68 |
+
const AMP_BIND_SQUARE_BRACKETS_ATTR_PATTERN = '#^\s+(?P<name>\[?[a-zA-Z0-9_\-]+\]?)'
|
69 |
+
. '(?P<value>=(?>"[^"]*+"|\'[^\']*+\'|[^\'"\s]+))?#';
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Pattern for HTML attribute accounting for binding attr name in data attribute syntax, boolean attribute,
|
73 |
+
* single/double-quoted attribute value, and unquoted attribute values.
|
74 |
+
*
|
75 |
+
* @var string
|
76 |
+
*/
|
77 |
+
const AMP_BIND_DATA_ATTRIBUTE_ATTR_PATTERN = '#^\s+(?P<name>(?:'
|
78 |
+
. self::AMP_BIND_DATA_ATTR_PREFIX
|
79 |
+
. ')?[a-zA-Z0-9_\-]+)'
|
80 |
+
. '(?P<value>=(?>"[^"]*+"|\'[^\']*+\'|[^\'"\s]+))?#';
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Match all start tags that contain a binding attribute in square brackets syntax.
|
84 |
+
*
|
85 |
+
* @var string
|
86 |
+
*/
|
87 |
+
const AMP_BIND_SQUARE_START_PATTERN = '#<'
|
88 |
+
. '(?P<name>[a-zA-Z0-9_\-]+)' // Tag name.
|
89 |
+
. '(?P<attrs>\s+' // Attributes.
|
90 |
+
. '(?>[^>"\'\[\]]+|"[^"]*+"|\'[^\']*+\')*+' // Non-binding attributes tokens.
|
91 |
+
. '\[[a-zA-Z0-9_\-]+\]' // One binding attribute key.
|
92 |
+
. '(?>[^>"\']+|"[^"]*+"|\'[^\']*+\')*+' // Any attribute tokens, including
|
93 |
+
// binding ones.
|
94 |
+
. ')>#s';
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Match all start tags that contain a binding attribute in data attribute syntax.
|
98 |
+
*
|
99 |
+
* @var string
|
100 |
+
*/
|
101 |
+
const AMP_BIND_DATA_START_PATTERN = '#<'
|
102 |
+
. '(?P<name>[a-zA-Z0-9_\-]+)' // Tag name.
|
103 |
+
. '(?P<attrs>\s+' // Attributes.
|
104 |
+
. '(?>' // Match at least one attribute
|
105 |
+
. '(?>' // prefixed with "data-amp-bind-".
|
106 |
+
. '(?![a-zA-Z0-9_\-\s]*'
|
107 |
+
. self::AMP_BIND_DATA_ATTR_PREFIX
|
108 |
+
. '[a-zA-Z0-9_\-]+="[^"]*+"|\'[^\']*+\')'
|
109 |
+
. '[^>"\']+|"[^"]*+"|\'[^\']*+\''
|
110 |
+
. ')*+'
|
111 |
+
. '(?>[a-zA-Z0-9_\-\s]*'
|
112 |
+
. self::AMP_BIND_DATA_ATTR_PREFIX
|
113 |
+
. '[a-zA-Z0-9_\-]+'
|
114 |
+
. ')'
|
115 |
+
. ')+'
|
116 |
+
. '(?>[^>"\']+|"[^"]*+"|\'[^\']*+\')*+' // Any attribute tokens, including
|
117 |
+
// binding ones.
|
118 |
+
. ')>#is';
|
119 |
+
|
120 |
+
/*
|
121 |
+
* Regular expressions to fetch the individual structural tags.
|
122 |
+
* These patterns were optimized to avoid extreme backtracking on large documents.
|
123 |
+
*/
|
124 |
+
const HTML_STRUCTURE_DOCTYPE_PATTERN = '/^(?<doctype>[^<]*(?>\s*<!--.*?-->\s*)*<!doctype(?>\s+[^>]+)?>)/is';
|
125 |
+
const HTML_STRUCTURE_HTML_START_TAG = '/^(?<html_start>[^<]*(?>\s*<!--.*?-->\s*)*<html(?>\s+[^>]*)?>)/is';
|
126 |
+
const HTML_STRUCTURE_HTML_END_TAG = '/(?<html_end><\/html(?>\s+[^>]*)?>.*)$/is';
|
127 |
+
const HTML_STRUCTURE_HEAD_START_TAG = '/^[^<]*(?><!--.*?-->\s*)*(?><head(?>\s+[^>]*)?>)/is';
|
128 |
+
const HTML_STRUCTURE_BODY_START_TAG = '/^[^<]*(?><!--.*-->\s*)*(?><body(?>\s+[^>]*)?>)/is';
|
129 |
+
const HTML_STRUCTURE_BODY_END_TAG = '/(?><\/body(?>\s+[^>]*)?>.*)$/is';
|
130 |
+
const HTML_STRUCTURE_HEAD_TAG = '/^(?>[^<]*(?><head(?>\s+[^>]*)?>).*?<\/head(?>\s+[^>]*)?>)/is';
|
131 |
+
|
132 |
+
// Regex patterns used for securing and restoring the doctype node.
|
133 |
+
const HTML_SECURE_DOCTYPE_IF_NOT_FIRST_PATTERN = '/(^[^<]*(?>\s*<!--[^>]*>\s*)+<)(!)(doctype)(\s+[^>]+?)(>)/i';
|
134 |
+
const HTML_RESTORE_DOCTYPE_PATTERN = '/(^[^<]*(?>\s*<!--[^>]*>\s*)+<)'
|
135 |
+
. '(!--amp-)(doctype)(\s+[^>]+?)(-->)/i';
|
136 |
+
|
137 |
+
// Regex pattern used for removing Internet Explorer conditional comments.
|
138 |
+
const HTML_IE_CONDITIONAL_COMMENTS_PATTERN = '/<!--(?>\[if\s|<!\[endif)(?>[^>]+(?<!--)>)*(?>[^>]+(?<=--)>)/i';
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Xpath query to fetch the attributes that are being URL-encoded by saveHTML().
|
142 |
+
*
|
143 |
+
* @var string
|
144 |
+
*/
|
145 |
+
const XPATH_URL_ENCODED_ATTRIBUTES_QUERY = './/*/@src|.//*/@href|.//*/@action';
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Xpath query to fetch the elements containing Mustache templates (both <template type=amp-mustache> and
|
149 |
+
* <script type=text/plain template=amp-mustache>).
|
150 |
+
*
|
151 |
+
* @var string
|
152 |
+
*/
|
153 |
+
const XPATH_MUSTACHE_TEMPLATE_ELEMENTS_QUERY = './/self::template[ @type = "amp-mustache" ]'
|
154 |
+
. '|//self::script[ @type = "text/plain" '
|
155 |
+
. 'and @template = "amp-mustache" ]';
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Error message to use when the __get() is triggered for an unknown property.
|
159 |
+
*
|
160 |
+
* @var string
|
161 |
+
*/
|
162 |
+
const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Dom\\Document::';
|
163 |
+
|
164 |
+
/**
|
165 |
+
* Charset compatibility tag for making DOMDocument behave.
|
166 |
+
*
|
167 |
+
* See: http://php.net/manual/en/domdocument.loadhtml.php#78243.
|
168 |
+
*
|
169 |
+
* @var string
|
170 |
+
*/
|
171 |
+
const HTTP_EQUIV_META_TAG = '<meta http-equiv="content-type" content="text/html; charset=utf-8">';
|
172 |
+
|
173 |
+
// Regex patterns and values used for adding and removing http-equiv charsets for compatibility.
|
174 |
+
// The opening tag pattern contains a comment to make sure we don't match a <head> tag within a comment.
|
175 |
+
|
176 |
+
const HTML_GET_HEAD_OPENING_TAG_PATTERN = '/(?><!--.*?-->\s*)*<head(?>\s+[^>]*)?>/is';
|
177 |
+
const HTML_GET_HEAD_OPENING_TAG_REPLACEMENT = '$0' . self::HTTP_EQUIV_META_TAG;
|
178 |
+
const HTML_GET_HTML_OPENING_TAG_PATTERN = '/(?><!--.*?-->\s*)*<html(?>\s+[^>]*)?>/is';
|
179 |
+
const HTML_GET_HTML_OPENING_TAG_REPLACEMENT = '$0<head>' . self::HTTP_EQUIV_META_TAG . '</head>';
|
180 |
+
const HTML_GET_HTTP_EQUIV_TAG_PATTERN = '#<meta http-equiv=([\'"])content-type\1 '
|
181 |
+
. 'content=([\'"])text/html; '
|
182 |
+
. 'charset=utf-8\2>#i';
|
183 |
+
const HTML_HTTP_EQUIV_VALUE = 'content-type';
|
184 |
+
const HTML_HTTP_EQUIV_CONTENT_VALUE = 'text/html; charset=utf-8';
|
185 |
+
|
186 |
+
// Regex patterns used for finding tags or extracting attribute values in an HTML string.
|
187 |
+
const HTML_FIND_TAG_WITHOUT_ATTRIBUTE_PATTERN = '/<%1$s[^>]*?>[^<]*(?><\/%1$s>)?/i';
|
188 |
+
const HTML_FIND_TAG_WITH_ATTRIBUTE_PATTERN = '/<%1$s [^>]*?\s*%2$s\s*=[^>]*?>[^<]*(?><\/%1$s>)?/i';
|
189 |
+
const HTML_EXTRACT_ATTRIBUTE_VALUE_PATTERN = '/%s=(?>([\'"])(?<full>.*)?\1|(?<partial>[^ \'";]+))/';
|
190 |
+
const HTML_FIND_TAG_DELIMITER = '/';
|
191 |
+
|
192 |
+
/**
|
193 |
+
* Pattern to match an AMP emoji together with its variant (amp4ads, amp4email, ...).
|
194 |
+
*
|
195 |
+
* @var string
|
196 |
+
*/
|
197 |
+
const AMP_EMOJI_ATTRIBUTE_PATTERN = '/<html\s([^>]*?(?:'
|
198 |
+
. Attribute::AMP_EMOJI_ALT
|
199 |
+
. '|'
|
200 |
+
. Attribute::AMP_EMOJI
|
201 |
+
. ')(4(?:ads|email))?[^>]*?)>/i';
|
202 |
+
|
203 |
+
// Attribute to use as a placeholder to move the emoji AMP symbol (⚡) over to DOM.
|
204 |
+
const EMOJI_AMP_ATTRIBUTE_PLACEHOLDER = 'emoji-amp';
|
205 |
+
|
206 |
+
// Patterns used for fixing the mangled encoding of src attributes with SVG data.
|
207 |
+
const I_AMPHTML_SIZER_REGEX_PATTERN = '/(?<before_src><i-amphtml-sizer\s+[^>]*>\s*<img\s+[^>]*?\s+src=([\'"]))'
|
208 |
+
. '(?<src>.*?)'
|
209 |
+
. '(?<after_src>\2><\/i-amphtml-sizer>)/i';
|
210 |
+
const SRC_SVG_REGEX_PATTERN = '/^\s*(?<type>[^<]+)(?<value><svg[^>]+>)\s*$/i';
|
211 |
+
|
212 |
+
/**
|
213 |
+
* XPath query to retrieve all <amp-*> tags, relative to the <body> node.
|
214 |
+
*
|
215 |
+
* @var string
|
216 |
+
*/
|
217 |
+
const XPATH_AMP_ELEMENTS_QUERY = ".//*[starts-with(name(), 'amp-')]";
|
218 |
+
|
219 |
+
/**
|
220 |
+
* XPath query to retrieve the <style amp-custom> tag, relative to the <head> node.
|
221 |
+
*
|
222 |
+
* @var string
|
223 |
+
*/
|
224 |
+
const XPATH_AMP_CUSTOM_STYLE_QUERY = './/style[@amp-custom]';
|
225 |
+
|
226 |
+
/**
|
227 |
+
* XPath query to fetch the inline style attributes, relative to the <body> node.
|
228 |
+
*
|
229 |
+
* @var string
|
230 |
+
*/
|
231 |
+
const XPATH_INLINE_STYLE_ATTRIBUTES_QUERY = './/@style';
|
232 |
+
|
233 |
+
/**
|
234 |
+
* Associative array of options to configure the behavior of the DOM document abstraction.
|
235 |
+
*
|
236 |
+
* @see Option::DEFAULTS For a list of available options.
|
237 |
+
*
|
238 |
+
* @var array
|
239 |
+
*/
|
240 |
+
private $options;
|
241 |
+
|
242 |
+
/**
|
243 |
+
* Whether `data-ampdevmode` was initially set on the the document element.
|
244 |
+
*
|
245 |
+
* @var bool
|
246 |
+
*/
|
247 |
+
private $hasInitialAmpDevMode = false;
|
248 |
+
|
249 |
+
/**
|
250 |
+
* The original encoding of how the AmpProject\Dom\Document was created.
|
251 |
+
*
|
252 |
+
* This is stored to do an automatic conversion to UTF-8, which is
|
253 |
+
* a requirement for AMP.
|
254 |
+
*
|
255 |
+
* @var string
|
256 |
+
*/
|
257 |
+
private $originalEncoding;
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Store the <noscript> markup that was extracted to preserve it during parsing.
|
261 |
+
*
|
262 |
+
* The array keys are the element IDs for placeholder <meta> tags.
|
263 |
+
*
|
264 |
+
* @see maybeReplaceNoscriptElements()
|
265 |
+
* @see maybeRestoreNoscriptElements()
|
266 |
+
*
|
267 |
+
* @var string[]
|
268 |
+
*/
|
269 |
+
private $noscriptPlaceholderComments = [];
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Store whether mustache template tags were replaced and need to be restored.
|
273 |
+
*
|
274 |
+
* @see replaceMustacheTemplateTokens()
|
275 |
+
*
|
276 |
+
* @var bool
|
277 |
+
*/
|
278 |
+
private $mustacheTagsReplaced = false;
|
279 |
+
|
280 |
+
/**
|
281 |
+
* Whether we had secured a doctype that needs restoring or not.
|
282 |
+
*
|
283 |
+
* This is an int as it receives the $count from the preg_replace().
|
284 |
+
*
|
285 |
+
* @var int
|
286 |
+
*/
|
287 |
+
private $securedDoctype = 0;
|
288 |
+
|
289 |
+
/**
|
290 |
+
* Whether the self-closing tags were transformed and need to be restored.
|
291 |
+
*
|
292 |
+
* This avoids duplicating this effort (maybe corrupting the DOM) on multiple calls to saveHTML().
|
293 |
+
*
|
294 |
+
* @var bool
|
295 |
+
*/
|
296 |
+
private $selfClosingTagsTransformed = false;
|
297 |
+
|
298 |
+
/**
|
299 |
+
* Store the emoji that was used to represent the AMP attribute.
|
300 |
+
*
|
301 |
+
* There are a few variations, so we want to keep track of this.
|
302 |
+
*
|
303 |
+
* @see https://github.com/ampproject/amphtml/issues/25990
|
304 |
+
*
|
305 |
+
* @var string
|
306 |
+
*/
|
307 |
+
private $usedAmpEmoji;
|
308 |
+
|
309 |
+
/**
|
310 |
+
* Store the current index by prefix.
|
311 |
+
*
|
312 |
+
* This is used to generate unique-per-prefix IDs.
|
313 |
+
*
|
314 |
+
* @var int[]
|
315 |
+
*/
|
316 |
+
private $indexCounter = [];
|
317 |
+
|
318 |
+
/**
|
319 |
+
* The maximum number of bytes of CSS that is enforced.
|
320 |
+
*
|
321 |
+
* A negative number will disable the byte count limit.
|
322 |
+
*
|
323 |
+
* @var int
|
324 |
+
*/
|
325 |
+
private $cssMaxByteCountEnforced = -1;
|
326 |
+
|
327 |
+
/**
|
328 |
+
* Store the names of the amp-bind attributes that were converted so that we can restore them later on.
|
329 |
+
*
|
330 |
+
* @var array<string>
|
331 |
+
*/
|
332 |
+
private $convertedAmpBindAttributes = [];
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Creates a new AmpProject\Dom\Document object
|
336 |
+
*
|
337 |
+
* @link https://php.net/manual/domdocument.construct.php
|
338 |
+
*
|
339 |
+
* @param string $version Optional. The version number of the document as part of the XML declaration.
|
340 |
+
* @param string $encoding Optional. The encoding of the document as part of the XML declaration.
|
341 |
+
*/
|
342 |
+
public function __construct($version = '', $encoding = null)
|
343 |
+
{
|
344 |
+
$this->originalEncoding = (string)$encoding ?: Encoding::UNKNOWN;
|
345 |
+
parent::__construct($version ?: '1.0', Encoding::AMP);
|
346 |
+
$this->registerNodeClass(DOMElement::class, Element::class);
|
347 |
+
$this->options = Option::DEFAULTS;
|
348 |
+
}
|
349 |
+
|
350 |
+
/**
|
351 |
+
* Named constructor to provide convenient way of transforming HTML into DOM.
|
352 |
+
*
|
353 |
+
* @param string $html HTML to turn into a DOM.
|
354 |
+
* @param array|string $options Optional. Array of options to configure the document. Used as encoding if a string
|
355 |
+
* is passed. Defaults to an empty array.
|
356 |
+
* @return Document|false DOM generated from provided HTML, or false if the transformation failed.
|
357 |
+
*/
|
358 |
+
public static function fromHtml($html, $options = [])
|
359 |
+
{
|
360 |
+
// Assume options are the encoding if a string is passed, for BC reasons.
|
361 |
+
if (is_string($options)) {
|
362 |
+
$options = [Option::ENCODING => $options];
|
363 |
+
}
|
364 |
+
|
365 |
+
$encoding = isset($options[Option::ENCODING]) ? $options[Option::ENCODING] : null;
|
366 |
+
|
367 |
+
$dom = new self('', $encoding);
|
368 |
+
|
369 |
+
if (! $dom->loadHTML($html, $options)) {
|
370 |
+
return false;
|
371 |
+
}
|
372 |
+
|
373 |
+
return $dom;
|
374 |
+
}
|
375 |
+
|
376 |
+
/**
|
377 |
+
* Named constructor to provide convenient way of transforming a HTML fragment into DOM.
|
378 |
+
*
|
379 |
+
* The difference to Document::fromHtml() is that fragments are not normalized as to their structure.
|
380 |
+
*
|
381 |
+
* @param string $html HTML to turn into a DOM.
|
382 |
+
* @param array|string $options Optional. Array of options to configure the document. Used as encoding if a string
|
383 |
+
* is passed. Defaults to an empty array.
|
384 |
+
* @return Document|false DOM generated from provided HTML, or false if the transformation failed.
|
385 |
+
*/
|
386 |
+
public static function fromHtmlFragment($html, $options = [])
|
387 |
+
{
|
388 |
+
// Assume options are the encoding if a string is passed, for BC reasons.
|
389 |
+
if (is_string($options)) {
|
390 |
+
$options = [Option::ENCODING => $options];
|
391 |
+
}
|
392 |
+
|
393 |
+
$encoding = isset($options[Option::ENCODING]) ? $options[Option::ENCODING] : null;
|
394 |
+
|
395 |
+
$dom = new self('', $encoding);
|
396 |
+
|
397 |
+
if (! $dom->loadHTMLFragment($html, $options)) {
|
398 |
+
return false;
|
399 |
+
}
|
400 |
+
|
401 |
+
return $dom;
|
402 |
+
}
|
403 |
+
|
404 |
+
/**
|
405 |
+
* Named constructor to provide convenient way of retrieving the DOM from a node.
|
406 |
+
*
|
407 |
+
* @param DOMNode $node Node to retrieve the DOM from. This is being modified by reference (!).
|
408 |
+
* @return Document DOM generated from provided HTML, or false if the transformation failed.
|
409 |
+
*/
|
410 |
+
public static function fromNode(DOMNode &$node)
|
411 |
+
{
|
412 |
+
/**
|
413 |
+
* Document of the node.
|
414 |
+
*
|
415 |
+
* If the node->ownerDocument returns null, the node is the document.
|
416 |
+
*
|
417 |
+
* @var DOMDocument
|
418 |
+
*/
|
419 |
+
$root = $node->ownerDocument === null ? $node : $node->ownerDocument;
|
420 |
+
|
421 |
+
if ($root instanceof self) {
|
422 |
+
return $root;
|
423 |
+
}
|
424 |
+
|
425 |
+
$dom = new self();
|
426 |
+
|
427 |
+
// We replace the $node by reference, to make sure the next lines of code will
|
428 |
+
// work as expected with the new document.
|
429 |
+
// Otherwise $dom and $node would refer to two different DOMDocuments.
|
430 |
+
$node = $dom->importNode($node, true);
|
431 |
+
$dom->appendChild($node);
|
432 |
+
|
433 |
+
$dom->hasInitialAmpDevMode = $dom->documentElement->hasAttribute(DevMode::DEV_MODE_ATTRIBUTE);
|
434 |
+
|
435 |
+
return $dom;
|
436 |
+
}
|
437 |
+
|
438 |
+
/**
|
439 |
+
* Reset the internal optimizations of the Document object.
|
440 |
+
*
|
441 |
+
* This might be needed if you are doing an operation that causes the cached
|
442 |
+
* nodes and XPath objects to point to the wrong document.
|
443 |
+
*
|
444 |
+
* @return self Reset version of the Document object.
|
445 |
+
*/
|
446 |
+
private function reset()
|
447 |
+
{
|
448 |
+
// Drop references to old DOM document.
|
449 |
+
unset($this->xpath, $this->head, $this->body);
|
450 |
+
|
451 |
+
// Reference of the document itself doesn't change here, but might need to change in the future.
|
452 |
+
return $this;
|
453 |
+
}
|
454 |
+
|
455 |
+
/**
|
456 |
+
* Load HTML from a string.
|
457 |
+
*
|
458 |
+
* @link https://php.net/manual/domdocument.loadhtml.php
|
459 |
+
*
|
460 |
+
* @param string $source The HTML string.
|
461 |
+
* @param array|int|string $options Optional. Array of options to configure the document. Used as additional Libxml
|
462 |
+
* parameters if an int or string is passed. Defaults to an empty array.
|
463 |
+
* @return bool true on success or false on failure.
|
464 |
+
*/
|
465 |
+
public function loadHTML($source, $options = [])
|
466 |
+
{
|
467 |
+
$source = $this->normalizeDocumentStructure($source);
|
468 |
+
$success = $this->loadHTMLFragment($source, $options);
|
469 |
+
|
470 |
+
if ($success) {
|
471 |
+
$this->insertMissingCharset();
|
472 |
+
|
473 |
+
// Do some further clean-up.
|
474 |
+
$this->deduplicateTag(Tag::HEAD);
|
475 |
+
$this->deduplicateTag(Tag::BODY);
|
476 |
+
$this->moveInvalidHeadNodesToBody();
|
477 |
+
$this->movePostBodyNodesToBody();
|
478 |
+
$this->convertHeadProfileToLink();
|
479 |
+
}
|
480 |
+
|
481 |
+
return $success;
|
482 |
+
}
|
483 |
+
|
484 |
+
/**
|
485 |
+
* Load a HTML fragment from a string.
|
486 |
+
*
|
487 |
+
* @param string $source The HTML fragment string.
|
488 |
+
* @param array|int|string $options Optional. Array of options to configure the document. Used as additional Libxml
|
489 |
+
* parameters if an int or string is passed. Defaults to an empty array.
|
490 |
+
* @return bool true on success or false on failure.
|
491 |
+
*/
|
492 |
+
public function loadHTMLFragment($source, $options = [])
|
493 |
+
{
|
494 |
+
// Assume options are the additional libxml flags if a string or int is passed, for BC reasons.
|
495 |
+
if (is_string($options)) {
|
496 |
+
$options = (int) $options;
|
497 |
+
}
|
498 |
+
if (is_int($options)) {
|
499 |
+
$options = [Option::LIBXML_FLAGS => $options];
|
500 |
+
}
|
501 |
+
|
502 |
+
$this->options = array_merge($this->options, $options);
|
503 |
+
|
504 |
+
$this->reset();
|
505 |
+
|
506 |
+
$source = $this->convertAmpEmojiAttribute($source);
|
507 |
+
$source = $this->convertAmpBindAttributes($source);
|
508 |
+
$source = $this->replaceSelfClosingTags($source);
|
509 |
+
$source = $this->maybeReplaceNoscriptElements($source);
|
510 |
+
$source = $this->secureMustacheScriptTemplates($source);
|
511 |
+
$source = $this->secureDoctypeNode($source);
|
512 |
+
|
513 |
+
list($source, $this->originalEncoding) = $this->detectAndStripEncoding($source);
|
514 |
+
|
515 |
+
if (Encoding::AMP !== strtolower($this->originalEncoding)) {
|
516 |
+
$source = $this->adaptEncoding($source);
|
517 |
+
}
|
518 |
+
|
519 |
+
$source = $this->addHttpEquivCharset($source);
|
520 |
+
|
521 |
+
$libxml_previous_state = libxml_use_internal_errors(true);
|
522 |
+
|
523 |
+
$this->options[Option::LIBXML_FLAGS] |= LIBXML_COMPACT;
|
524 |
+
|
525 |
+
/*
|
526 |
+
* LIBXML_HTML_NODEFDTD is only available for libxml 2.7.8+.
|
527 |
+
* This should be the case for PHP 5.4+, but some systems seem to compile against a custom libxml version that
|
528 |
+
* is lower than expected.
|
529 |
+
*/
|
530 |
+
if (defined('LIBXML_HTML_NODEFDTD')) {
|
531 |
+
$this->options[Option::LIBXML_FLAGS] |= constant('LIBXML_HTML_NODEFDTD');
|
532 |
+
}
|
533 |
+
|
534 |
+
$success = parent::loadHTML($source, $this->options[Option::LIBXML_FLAGS]);
|
535 |
+
|
536 |
+
libxml_clear_errors();
|
537 |
+
libxml_use_internal_errors($libxml_previous_state);
|
538 |
+
|
539 |
+
if ($success) {
|
540 |
+
$this->normalizeHtmlAttributes();
|
541 |
+
$this->restoreMustacheScriptTemplates();
|
542 |
+
$this->maybeRestoreNoscriptElements();
|
543 |
+
|
544 |
+
// Remove http-equiv charset again.
|
545 |
+
$meta = $this->head->firstChild;
|
546 |
+
|
547 |
+
// We might have leading comments we need to preserve here.
|
548 |
+
while ($meta instanceof DOMComment) {
|
549 |
+
$meta = $meta->nextSibling;
|
550 |
+
}
|
551 |
+
|
552 |
+
if (
|
553 |
+
$meta instanceof Element
|
554 |
+
&& Tag::META === $meta->tagName
|
555 |
+
&& self::HTML_HTTP_EQUIV_VALUE === $meta->getAttribute(Attribute::HTTP_EQUIV)
|
556 |
+
&& (self::HTML_HTTP_EQUIV_CONTENT_VALUE) === $meta->getAttribute(Attribute::CONTENT)
|
557 |
+
) {
|
558 |
+
$this->head->removeChild($meta);
|
559 |
+
}
|
560 |
+
|
561 |
+
$this->hasInitialAmpDevMode = $this->documentElement->hasAttribute(DevMode::DEV_MODE_ATTRIBUTE);
|
562 |
+
}
|
563 |
+
|
564 |
+
return $success;
|
565 |
+
}
|
566 |
+
|
567 |
+
/**
|
568 |
+
* Dumps the internal document into a string using HTML formatting.
|
569 |
+
*
|
570 |
+
* @link https://php.net/manual/domdocument.savehtml.php
|
571 |
+
*
|
572 |
+
* @param DOMNode|null $node Optional. Parameter to output a subset of the document.
|
573 |
+
* @return string The HTML, or false if an error occurred.
|
574 |
+
*/
|
575 |
+
public function saveHTML(DOMNode $node = null)
|
576 |
+
{
|
577 |
+
return $this->saveHTMLFragment($node);
|
578 |
+
}
|
579 |
+
|
580 |
+
/**
|
581 |
+
* Dumps the internal document fragment into a string using HTML formatting.
|
582 |
+
*
|
583 |
+
* @param DOMNode|null $node Optional. Parameter to output a subset of the document.
|
584 |
+
* @return string The HTML fragment, or false if an error occurred.
|
585 |
+
*/
|
586 |
+
public function saveHTMLFragment(DOMNode $node = null)
|
587 |
+
{
|
588 |
+
$this->replaceMustacheTemplateTokens();
|
589 |
+
|
590 |
+
// Force-add http-equiv charset to make DOMDocument behave as it should.
|
591 |
+
// See: http://php.net/manual/en/domdocument.loadhtml.php#78243.
|
592 |
+
$charset = $this->createElement(Tag::META);
|
593 |
+
$charset->setAttribute(Attribute::HTTP_EQUIV, self::HTML_HTTP_EQUIV_VALUE);
|
594 |
+
$charset->setAttribute(Attribute::CONTENT, self::HTML_HTTP_EQUIV_CONTENT_VALUE);
|
595 |
+
$this->head->insertBefore($charset, $this->head->firstChild);
|
596 |
+
|
597 |
+
if (null === $node || PHP_VERSION_ID >= 70300) {
|
598 |
+
$html = parent::saveHTML($node);
|
599 |
+
} else {
|
600 |
+
$html = $this->extractNodeViaFragmentBoundaries($node);
|
601 |
+
}
|
602 |
+
|
603 |
+
// Remove http-equiv charset again.
|
604 |
+
// It is also removed from the DOM again in case saveHTML() is used multiple times.
|
605 |
+
$this->head->removeChild($charset);
|
606 |
+
$html = preg_replace(self::HTML_GET_HTTP_EQUIV_TAG_PATTERN, '', $html, 1);
|
607 |
+
|
608 |
+
$html = $this->restoreDoctypeNode($html);
|
609 |
+
$html = $this->restoreMustacheTemplateTokens($html);
|
610 |
+
$html = $this->restoreSelfClosingTags($html);
|
611 |
+
$html = $this->restoreAmpBindAttributes($html);
|
612 |
+
$html = $this->restoreAmpEmojiAttribute($html);
|
613 |
+
$html = $this->fixSvgSourceAttributeEncoding($html);
|
614 |
+
|
615 |
+
// Whitespace just causes unit tests to fail... so whitespace begone.
|
616 |
+
if ('' === trim($html)) {
|
617 |
+
return '';
|
618 |
+
}
|
619 |
+
|
620 |
+
return $html;
|
621 |
+
}
|
622 |
+
|
623 |
+
/**
|
624 |
+
* Get the current options of the Document instance.
|
625 |
+
*
|
626 |
+
* @return array
|
627 |
+
*/
|
628 |
+
public function getOptions()
|
629 |
+
{
|
630 |
+
return $this->options;
|
631 |
+
}
|
632 |
+
|
633 |
+
/**
|
634 |
+
* Add the required utf-8 meta charset tag if it is still missing.
|
635 |
+
*/
|
636 |
+
private function insertMissingCharset()
|
637 |
+
{
|
638 |
+
// Bail if a charset tag is already present.
|
639 |
+
if ($this->xpath->query('.//meta[ @charset ]')->item(0)) {
|
640 |
+
return;
|
641 |
+
}
|
642 |
+
|
643 |
+
$charset = $this->createElement(Tag::META);
|
644 |
+
$charset->setAttribute(Attribute::CHARSET, Encoding::AMP);
|
645 |
+
$this->head->insertBefore($charset, $this->head->firstChild);
|
646 |
+
}
|
647 |
+
|
648 |
+
/**
|
649 |
+
* Extract a node's HTML via fragment boundaries.
|
650 |
+
*
|
651 |
+
* Temporarily adds fragment boundary comments in order to locate the desired node to extract from
|
652 |
+
* the given HTML document. This is required because libxml seems to only preserve whitespace when
|
653 |
+
* serializing when calling DOMDocument::saveHTML() on the entire document. If you pass the element
|
654 |
+
* to DOMDocument::saveHTML() then formatting whitespace gets added unexpectedly. This is seen to
|
655 |
+
* be fixed in PHP 7.3, but for older versions of PHP the following workaround is needed.
|
656 |
+
*
|
657 |
+
* @param DOMNode $node Node to extract the HTML for.
|
658 |
+
* @return string Extracted HTML string.
|
659 |
+
*/
|
660 |
+
private function extractNodeViaFragmentBoundaries(DOMNode $node)
|
661 |
+
{
|
662 |
+
$boundary = $this->getUniqueId('fragment_boundary');
|
663 |
+
$startBoundary = $boundary . ':start';
|
664 |
+
$endBoundary = $boundary . ':end';
|
665 |
+
$commentStart = $this->createComment($startBoundary);
|
666 |
+
$commentEnd = $this->createComment($endBoundary);
|
667 |
+
|
668 |
+
$node->parentNode->insertBefore($commentStart, $node);
|
669 |
+
$node->parentNode->insertBefore($commentEnd, $node->nextSibling);
|
670 |
+
|
671 |
+
$pattern = '/^.*?'
|
672 |
+
. preg_quote("<!--{$startBoundary}-->", '/')
|
673 |
+
. '(.*)'
|
674 |
+
. preg_quote("<!--{$endBoundary}-->", '/')
|
675 |
+
. '.*?\s*$/s';
|
676 |
+
|
677 |
+
$html = preg_replace($pattern, '$1', parent::saveHTML());
|
678 |
+
|
679 |
+
$node->parentNode->removeChild($commentStart);
|
680 |
+
$node->parentNode->removeChild($commentEnd);
|
681 |
+
|
682 |
+
return $html;
|
683 |
+
}
|
684 |
+
|
685 |
+
/**
|
686 |
+
* Normalize the document structure.
|
687 |
+
*
|
688 |
+
* This makes sure the document adheres to the general structure that AMP requires:
|
689 |
+
* ```
|
690 |
+
* <!DOCTYPE html>
|
691 |
+
* <html>
|
692 |
+
* <head>
|
693 |
+
* <meta charset="utf-8">
|
694 |
+
* </head>
|
695 |
+
* <body>
|
696 |
+
* </body>
|
697 |
+
* </html>
|
698 |
+
* ```
|
699 |
+
*
|
700 |
+
* @param string $content Content to normalize the structure of.
|
701 |
+
* @return string Normalized content.
|
702 |
+
*/
|
703 |
+
private function normalizeDocumentStructure($content)
|
704 |
+
{
|
705 |
+
$matches = [];
|
706 |
+
$doctype = self::DEFAULT_DOCTYPE;
|
707 |
+
$htmlStart = '<html>';
|
708 |
+
$htmlEnd = '</html>';
|
709 |
+
|
710 |
+
// Strip IE conditional comments, which are supported by IE 5-9 only (which AMP doesn't support).
|
711 |
+
$content = preg_replace(self::HTML_IE_CONDITIONAL_COMMENTS_PATTERN, '', $content);
|
712 |
+
|
713 |
+
// Detect and strip <!doctype> tags.
|
714 |
+
if (preg_match(self::HTML_STRUCTURE_DOCTYPE_PATTERN, $content, $matches)) {
|
715 |
+
$doctype = $matches['doctype'];
|
716 |
+
$content = preg_replace(self::HTML_STRUCTURE_DOCTYPE_PATTERN, '', $content, 1);
|
717 |
+
}
|
718 |
+
|
719 |
+
// Detect and strip <html> tags.
|
720 |
+
if (preg_match(self::HTML_STRUCTURE_HTML_START_TAG, $content, $matches)) {
|
721 |
+
$htmlStart = $matches['html_start'];
|
722 |
+
$content = preg_replace(self::HTML_STRUCTURE_HTML_START_TAG, '', $content, 1);
|
723 |
+
|
724 |
+
preg_match(self::HTML_STRUCTURE_HTML_END_TAG, $content, $matches);
|
725 |
+
$htmlEnd = isset($matches['html_end']) ? $matches['html_end'] : $htmlEnd;
|
726 |
+
$content = preg_replace(self::HTML_STRUCTURE_HTML_END_TAG, '', $content, 1);
|
727 |
+
}
|
728 |
+
|
729 |
+
// Detect <head> and <body> tags and add as needed.
|
730 |
+
if (! preg_match(self::HTML_STRUCTURE_HEAD_START_TAG, $content, $matches)) {
|
731 |
+
if (! preg_match(self::HTML_STRUCTURE_BODY_START_TAG, $content, $matches)) {
|
732 |
+
// Both <head> and <body> missing.
|
733 |
+
$content = "<head></head><body>{$content}</body>";
|
734 |
+
} else {
|
735 |
+
// Only <head> missing.
|
736 |
+
$content = "<head></head>{$content}";
|
737 |
+
}
|
738 |
+
} elseif (! preg_match(self::HTML_STRUCTURE_BODY_END_TAG, $content, $matches)) {
|
739 |
+
// Only <body> missing.
|
740 |
+
// @todo This is an expensive regex operation, look into further optimization.
|
741 |
+
$content = preg_replace(self::HTML_STRUCTURE_HEAD_TAG, '$0<body>', $content, 1);
|
742 |
+
$content .= '</body>';
|
743 |
+
}
|
744 |
+
|
745 |
+
$content = "{$htmlStart}{$content}{$htmlEnd}";
|
746 |
+
|
747 |
+
// Reinsert a standard doctype (while preserving any potentially leading comments).
|
748 |
+
$doctype = preg_replace(self::HTML_DOCTYPE_REGEX_PATTERN, self::DEFAULT_DOCTYPE, $doctype);
|
749 |
+
$content = "{$doctype}{$content}";
|
750 |
+
|
751 |
+
return $content;
|
752 |
+
}
|
753 |
+
|
754 |
+
/**
|
755 |
+
* Normalize the structure of the document if it was already provided as a DOM.
|
756 |
+
*/
|
757 |
+
public function normalizeDomStructure()
|
758 |
+
{
|
759 |
+
if (! $this->documentElement) {
|
760 |
+
$this->appendChild($this->createElement(Tag::HTML));
|
761 |
+
}
|
762 |
+
|
763 |
+
if (Tag::HTML !== $this->documentElement->nodeName) {
|
764 |
+
$nextSibling = $this->documentElement->nextSibling;
|
765 |
+
/**
|
766 |
+
* The old document element that we need to remove and replace as we cannot just move it around.
|
767 |
+
*
|
768 |
+
* @var Element
|
769 |
+
*/
|
770 |
+
$oldDocumentElement = $this->removeChild($this->documentElement);
|
771 |
+
$html = $this->createElement(Tag::HTML);
|
772 |
+
$this->insertBefore($html, $nextSibling);
|
773 |
+
|
774 |
+
if ($oldDocumentElement->nodeName === Tag::HEAD) {
|
775 |
+
$head = $oldDocumentElement;
|
776 |
+
} else {
|
777 |
+
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
778 |
+
if (!$head) {
|
779 |
+
$head = $this->createElement(Tag::HEAD);
|
780 |
+
}
|
781 |
+
}
|
782 |
+
|
783 |
+
if (!$head instanceof Element) {
|
784 |
+
throw FailedToRetrieveRequiredDomElement::forHeadElement($head);
|
785 |
+
}
|
786 |
+
|
787 |
+
$this->head = $head;
|
788 |
+
$html->appendChild($this->head);
|
789 |
+
|
790 |
+
if ($oldDocumentElement->nodeName === Tag::BODY) {
|
791 |
+
$body = $oldDocumentElement;
|
792 |
+
} else {
|
793 |
+
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
794 |
+
if (!$body) {
|
795 |
+
$body = $this->createElement(Tag::BODY);
|
796 |
+
}
|
797 |
+
}
|
798 |
+
|
799 |
+
if (!$body instanceof Element) {
|
800 |
+
throw FailedToRetrieveRequiredDomElement::forBodyElement($body);
|
801 |
+
}
|
802 |
+
|
803 |
+
$this->body = $body;
|
804 |
+
$html->appendChild($this->body);
|
805 |
+
|
806 |
+
if ($oldDocumentElement !== $this->body && $oldDocumentElement !== $this->head) {
|
807 |
+
$this->body->appendChild($oldDocumentElement);
|
808 |
+
}
|
809 |
+
} else {
|
810 |
+
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
811 |
+
if (!$head) {
|
812 |
+
$this->head = $this->createElement(Tag::HEAD);
|
813 |
+
$this->documentElement->insertBefore($this->head, $this->documentElement->firstChild);
|
814 |
+
}
|
815 |
+
|
816 |
+
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
817 |
+
if (!$body) {
|
818 |
+
$this->body = $this->createElement(Tag::BODY);
|
819 |
+
$this->documentElement->appendChild($this->body);
|
820 |
+
}
|
821 |
+
}
|
822 |
+
|
823 |
+
$this->moveInvalidHeadNodesToBody();
|
824 |
+
$this->movePostBodyNodesToBody();
|
825 |
+
}
|
826 |
+
|
827 |
+
/**
|
828 |
+
* Normalizes HTML attributes to be HTML5 compatible.
|
829 |
+
*
|
830 |
+
* Conditionally removes html[xmlns], and converts html[xml:lang] to html[lang].
|
831 |
+
*/
|
832 |
+
private function normalizeHtmlAttributes()
|
833 |
+
{
|
834 |
+
if (! $this->html->hasAttributes()) {
|
835 |
+
return;
|
836 |
+
}
|
837 |
+
|
838 |
+
$xmlns = $this->html->attributes->getNamedItem('xmlns');
|
839 |
+
if ($xmlns instanceof DOMAttr && 'http://www.w3.org/1999/xhtml' === $xmlns->nodeValue) {
|
840 |
+
$this->html->removeAttributeNode($xmlns);
|
841 |
+
}
|
842 |
+
|
843 |
+
$xml_lang = $this->html->attributes->getNamedItem('xml:lang');
|
844 |
+
if ($xml_lang instanceof DOMAttr) {
|
845 |
+
$lang_node = $this->html->attributes->getNamedItem('lang');
|
846 |
+
if ((! $lang_node || ! $lang_node->nodeValue) && $xml_lang->nodeValue) {
|
847 |
+
// Move the html[xml:lang] value to html[lang].
|
848 |
+
$this->html->setAttribute('lang', $xml_lang->nodeValue);
|
849 |
+
}
|
850 |
+
$this->html->removeAttributeNode($xml_lang);
|
851 |
+
}
|
852 |
+
}
|
853 |
+
|
854 |
+
/**
|
855 |
+
* Move invalid head nodes back to the body.
|
856 |
+
*/
|
857 |
+
private function moveInvalidHeadNodesToBody()
|
858 |
+
{
|
859 |
+
// Walking backwards makes it easier to move elements in the expected order.
|
860 |
+
$node = $this->head->lastChild;
|
861 |
+
while ($node) {
|
862 |
+
$nextSibling = $node->previousSibling;
|
863 |
+
if (! $this->isValidHeadNode($node)) {
|
864 |
+
$this->body->insertBefore($this->head->removeChild($node), $this->body->firstChild);
|
865 |
+
}
|
866 |
+
$node = $nextSibling;
|
867 |
+
}
|
868 |
+
}
|
869 |
+
|
870 |
+
/**
|
871 |
+
* Converts a possible head[profile] attribute to link[rel=profile].
|
872 |
+
*
|
873 |
+
* The head[profile] attribute is only valid in HTML4, not HTML5.
|
874 |
+
* So if it exists and isn't empty, add it to the <head> as a link[rel=profile] and strip the attribute.
|
875 |
+
*/
|
876 |
+
private function convertHeadProfileToLink()
|
877 |
+
{
|
878 |
+
if (! $this->head->hasAttribute(Attribute::PROFILE)) {
|
879 |
+
return;
|
880 |
+
}
|
881 |
+
|
882 |
+
$profile = $this->head->getAttribute(Attribute::PROFILE);
|
883 |
+
if ($profile) {
|
884 |
+
$link = $this->createElement(Tag::LINK);
|
885 |
+
$link->setAttribute(Attribute::REL, Attribute::PROFILE);
|
886 |
+
$link->setAttribute(Attribute::HREF, $profile);
|
887 |
+
$this->head->appendChild($link);
|
888 |
+
}
|
889 |
+
|
890 |
+
$this->head->removeAttribute(Attribute::PROFILE);
|
891 |
+
}
|
892 |
+
|
893 |
+
/**
|
894 |
+
* Move any nodes appearing after </body> or </html> to be appended to the <body>.
|
895 |
+
*
|
896 |
+
* This accounts for markup that is output at shutdown, such markup from Query Monitor. Not only is elements after
|
897 |
+
* the </body> not valid in AMP, but trailing elements after </html> will get wrapped in additional <html> elements.
|
898 |
+
* While comment nodes would be allowed in AMP, everything is moved regardless so that source stack comments will
|
899 |
+
* retain their relative position with the element nodes they annotate.
|
900 |
+
*/
|
901 |
+
private function movePostBodyNodesToBody()
|
902 |
+
{
|
903 |
+
// Move nodes (likely comments) from after the </body>.
|
904 |
+
while ($this->body->nextSibling) {
|
905 |
+
$this->body->appendChild($this->body->nextSibling);
|
906 |
+
}
|
907 |
+
|
908 |
+
// Move nodes from after the </html>.
|
909 |
+
while ($this->documentElement->nextSibling) {
|
910 |
+
$nextSibling = $this->documentElement->nextSibling;
|
911 |
+
if ($nextSibling instanceof Element && Tag::HTML === $nextSibling->nodeName) {
|
912 |
+
// Handle trailing elements getting wrapped in implicit duplicate <html>.
|
913 |
+
while ($nextSibling->firstChild) {
|
914 |
+
$this->body->appendChild($nextSibling->firstChild);
|
915 |
+
}
|
916 |
+
$nextSibling->parentNode->removeChild($nextSibling); // Discard now-empty implicit <html>.
|
917 |
+
} else {
|
918 |
+
$this->body->appendChild($this->documentElement->nextSibling);
|
919 |
+
}
|
920 |
+
}
|
921 |
+
}
|
922 |
+
|
923 |
+
/**
|
924 |
+
* Force all self-closing tags to have closing tags.
|
925 |
+
*
|
926 |
+
* This is needed because DOMDocument isn't fully aware of these.
|
927 |
+
*
|
928 |
+
* @param string $html HTML string to adapt.
|
929 |
+
* @return string Adapted HTML string.
|
930 |
+
* @see restoreSelfClosingTags() Reciprocal function.
|
931 |
+
*
|
932 |
+
*/
|
933 |
+
private function replaceSelfClosingTags($html)
|
934 |
+
{
|
935 |
+
static $regexPattern = null;
|
936 |
+
|
937 |
+
if (null === $regexPattern) {
|
938 |
+
$regexPattern = '#<(' . implode('|', Tag::SELF_CLOSING_TAGS) . ')([^>]*?)(?>\s*(?<!\\\\)\/)?>(?!</\1>)#';
|
939 |
+
}
|
940 |
+
|
941 |
+
$this->selfClosingTagsTransformed = true;
|
942 |
+
|
943 |
+
return preg_replace($regexPattern, '<$1$2></$1>', $html);
|
944 |
+
}
|
945 |
+
|
946 |
+
/**
|
947 |
+
* Restore all self-closing tags again.
|
948 |
+
*
|
949 |
+
* @param string $html HTML string to adapt.
|
950 |
+
* @return string Adapted HTML string.
|
951 |
+
* @see replaceSelfClosingTags Reciprocal function.
|
952 |
+
*
|
953 |
+
*/
|
954 |
+
private function restoreSelfClosingTags($html)
|
955 |
+
{
|
956 |
+
static $regexPattern = null;
|
957 |
+
|
958 |
+
if (! $this->selfClosingTagsTransformed) {
|
959 |
+
return $html;
|
960 |
+
}
|
961 |
+
|
962 |
+
if (null === $regexPattern) {
|
963 |
+
$regexPattern = '#</(' . implode('|', Tag::SELF_CLOSING_TAGS) . ')>#i';
|
964 |
+
}
|
965 |
+
|
966 |
+
$this->selfClosingTagsTransformed = false;
|
967 |
+
|
968 |
+
return preg_replace($regexPattern, '', $html);
|
969 |
+
}
|
970 |
+
|
971 |
+
/**
|
972 |
+
* Maybe replace noscript elements with placeholders.
|
973 |
+
*
|
974 |
+
* This is done because libxml<2.8 might parse them incorrectly.
|
975 |
+
* When appearing in the head element, a noscript can cause the head to close prematurely
|
976 |
+
* and the noscript gets moved to the body and anything after it which was in the head.
|
977 |
+
* See <https://stackoverflow.com/questions/39013102/why-does-noscript-move-into-body-tag-instead-of-head-tag>.
|
978 |
+
* This is limited to only running in the head element because this is where the problem lies,
|
979 |
+
* and it is important for the AMP_Script_Sanitizer to be able to access the noscript elements
|
980 |
+
* in the body otherwise.
|
981 |
+
*
|
982 |
+
* @param string $html HTML string to adapt.
|
983 |
+
* @return string Adapted HTML string.
|
984 |
+
* @see maybeRestoreNoscriptElements() Reciprocal function.
|
985 |
+
*/
|
986 |
+
private function maybeReplaceNoscriptElements($html)
|
987 |
+
{
|
988 |
+
if (version_compare(LIBXML_DOTTED_VERSION, '2.8', '<')) {
|
989 |
+
$html = preg_replace_callback(
|
990 |
+
'#^.+?(?=<body)#is',
|
991 |
+
function ($headMatches) {
|
992 |
+
return preg_replace_callback(
|
993 |
+
'#<noscript[^>]*>.*?</noscript>#si',
|
994 |
+
function ($noscriptMatches) {
|
995 |
+
$id = $this->getUniqueId('noscript');
|
996 |
+
$this->noscriptPlaceholderComments[$id] = $noscriptMatches[0];
|
997 |
+
return sprintf('<meta class="noscript-placeholder" id="%s">', $id);
|
998 |
+
},
|
999 |
+
$headMatches[0]
|
1000 |
+
);
|
1001 |
+
},
|
1002 |
+
$html
|
1003 |
+
);
|
1004 |
+
}
|
1005 |
+
|
1006 |
+
return $html;
|
1007 |
+
}
|
1008 |
+
|
1009 |
+
/**
|
1010 |
+
* Maybe restore noscript elements with placeholders.
|
1011 |
+
*
|
1012 |
+
* This is done because libxml<2.8 might parse them incorrectly.
|
1013 |
+
* When appearing in the head element, a noscript can cause the head to close prematurely
|
1014 |
+
* and the noscript gets moved to the body and anything after it which was in the head.
|
1015 |
+
* See <https://stackoverflow.com/questions/39013102/why-does-noscript-move-into-body-tag-instead-of-head-tag>.
|
1016 |
+
* This is limited to only running in the head element because this is where the problem lies,
|
1017 |
+
* and it is important for the AMP_Script_Sanitizer to be able to access the noscript elements
|
1018 |
+
* in the body otherwise.
|
1019 |
+
*
|
1020 |
+
* @see maybeReplaceNoscriptElements() Reciprocal function.
|
1021 |
+
*/
|
1022 |
+
private function maybeRestoreNoscriptElements()
|
1023 |
+
{
|
1024 |
+
foreach ($this->noscriptPlaceholderComments as $id => $noscriptHtmlFragment) {
|
1025 |
+
$placeholderElement = $this->getElementById($id);
|
1026 |
+
if (!$placeholderElement || !$placeholderElement->parentNode) {
|
1027 |
+
continue;
|
1028 |
+
}
|
1029 |
+
$noscriptFragmentDocument = self::fromHtmlFragment($noscriptHtmlFragment);
|
1030 |
+
if (!$noscriptFragmentDocument) {
|
1031 |
+
continue;
|
1032 |
+
}
|
1033 |
+
$exportBody = $noscriptFragmentDocument->getElementsByTagName(Tag::BODY)->item(0);
|
1034 |
+
if (!$exportBody) {
|
1035 |
+
continue;
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
$importFragment = $this->createDocumentFragment();
|
1039 |
+
while ($exportBody->firstChild) {
|
1040 |
+
$importNode = $exportBody->removeChild($exportBody->firstChild);
|
1041 |
+
$importNode = $this->importNode($importNode, true);
|
1042 |
+
$importFragment->appendChild($importNode);
|
1043 |
+
}
|
1044 |
+
|
1045 |
+
$placeholderElement->parentNode->replaceChild($importFragment, $placeholderElement);
|
1046 |
+
}
|
1047 |
+
}
|
1048 |
+
|
1049 |
+
/**
|
1050 |
+
* Secures instances of script[template="amp-mustache"] by renaming element to tmp-script, as a workaround to a
|
1051 |
+
* libxml parsing issue.
|
1052 |
+
*
|
1053 |
+
* This script can have closing tags of its children table and td stripped.
|
1054 |
+
* So this changes its name from script to tmp-script to avoid this.
|
1055 |
+
*
|
1056 |
+
* @link https://github.com/ampproject/amp-wp/issues/4254
|
1057 |
+
* @see restoreMustacheScriptTemplates() Reciprocal function.
|
1058 |
+
*
|
1059 |
+
* @param string $html To replace the tag name that contains the mustache templates.
|
1060 |
+
* @return string The HTML, with the tag name of the mustache templates replaced.
|
1061 |
+
*/
|
1062 |
+
private function secureMustacheScriptTemplates($html)
|
1063 |
+
{
|
1064 |
+
return preg_replace(
|
1065 |
+
'#<script(\s[^>]*?template=(["\']?)amp-mustache\2[^>]*)>(.*?)</script\s*?>#is',
|
1066 |
+
'<tmp-script$1>$3</tmp-script>',
|
1067 |
+
$html
|
1068 |
+
);
|
1069 |
+
}
|
1070 |
+
|
1071 |
+
/**
|
1072 |
+
* Restores the tag names of script[template="amp-mustache"] elements that were replaced earlier.
|
1073 |
+
*
|
1074 |
+
* @see secureMustacheScriptTemplates() Reciprocal function.
|
1075 |
+
*/
|
1076 |
+
private function restoreMustacheScriptTemplates()
|
1077 |
+
{
|
1078 |
+
$tmp_script_elements = iterator_to_array($this->getElementsByTagName('tmp-script'));
|
1079 |
+
foreach ($tmp_script_elements as $tmp_script_element) {
|
1080 |
+
$script = $this->createElement(Tag::SCRIPT);
|
1081 |
+
foreach ($tmp_script_element->attributes as $attr) {
|
1082 |
+
$script->setAttribute($attr->nodeName, $attr->nodeValue);
|
1083 |
+
}
|
1084 |
+
while ($tmp_script_element->firstChild) {
|
1085 |
+
$script->appendChild($tmp_script_element->firstChild);
|
1086 |
+
}
|
1087 |
+
$tmp_script_element->parentNode->replaceChild($script, $tmp_script_element);
|
1088 |
+
}
|
1089 |
+
}
|
1090 |
+
|
1091 |
+
/**
|
1092 |
+
* Replace AMP binding attributes with something that libxml can parse (as HTML5 data-* attributes).
|
1093 |
+
*
|
1094 |
+
* This is necessary because attributes in square brackets are not understood in PHP and
|
1095 |
+
* get dropped with an error raised:
|
1096 |
+
* > Warning: DOMDocument::loadHTML(): error parsing attribute name
|
1097 |
+
*
|
1098 |
+
* @see restoreAmpBindAttributes() Reciprocal function.
|
1099 |
+
* @link https://www.ampproject.org/docs/reference/components/amp-bind
|
1100 |
+
*
|
1101 |
+
* @param string $html HTML containing amp-bind attributes.
|
1102 |
+
* @return string HTML with AMP binding attributes replaced with HTML5 data-* attributes.
|
1103 |
+
*/
|
1104 |
+
private function convertAmpBindAttributes($html)
|
1105 |
+
{
|
1106 |
+
/**
|
1107 |
+
* Replace callback.
|
1108 |
+
*
|
1109 |
+
* @param array $tagMatches Tag matches.
|
1110 |
+
* @return string Replacement.
|
1111 |
+
*/
|
1112 |
+
$replaceCallback = function ($tagMatches) {
|
1113 |
+
|
1114 |
+
// Strip the self-closing slash as long as it is not an attribute value, like for the href attribute.
|
1115 |
+
$oldAttrs = rtrim(preg_replace('#(?<!=)/$#', '', $tagMatches['attrs']));
|
1116 |
+
|
1117 |
+
$newAttrs = '';
|
1118 |
+
$offset = 0;
|
1119 |
+
while (preg_match(self::AMP_BIND_SQUARE_BRACKETS_ATTR_PATTERN, substr($oldAttrs, $offset), $attrMatches)) {
|
1120 |
+
$offset += strlen($attrMatches[0]);
|
1121 |
+
|
1122 |
+
if ('[' === $attrMatches['name'][0]) {
|
1123 |
+
$attrName = trim($attrMatches['name'], '[]');
|
1124 |
+
$newAttrs .= ' ' . self::AMP_BIND_DATA_ATTR_PREFIX . $attrName;
|
1125 |
+
if (isset($attrMatches['value'])) {
|
1126 |
+
$newAttrs .= $attrMatches['value'];
|
1127 |
+
}
|
1128 |
+
$this->convertedAmpBindAttributes[] = $attrName;
|
1129 |
+
} else {
|
1130 |
+
$newAttrs .= $attrMatches[0];
|
1131 |
+
}
|
1132 |
+
}
|
1133 |
+
|
1134 |
+
// Bail on parse error which occurs when the regex isn't able to consume the entire $newAttrs string.
|
1135 |
+
if (strlen($oldAttrs) !== $offset) {
|
1136 |
+
return $tagMatches[0];
|
1137 |
+
}
|
1138 |
+
|
1139 |
+
return '<' . $tagMatches['name'] . $newAttrs . '>';
|
1140 |
+
};
|
1141 |
+
|
1142 |
+
$converted = preg_replace_callback(
|
1143 |
+
self::AMP_BIND_SQUARE_START_PATTERN,
|
1144 |
+
$replaceCallback,
|
1145 |
+
$html
|
1146 |
+
);
|
1147 |
+
|
1148 |
+
/*
|
1149 |
+
* If the regex engine incurred an error during processing, for example exceeding the backtrack
|
1150 |
+
* limit, $converted will be null. In this case we return the originally passed document to allow
|
1151 |
+
* DOMDocument to attempt to load it. If the AMP HTML doesn't make use of amp-bind or similar
|
1152 |
+
* attributes, then everything should still work.
|
1153 |
+
*
|
1154 |
+
* See https://github.com/ampproject/amp-wp/issues/993 for additional context on this issue.
|
1155 |
+
* See http://php.net/manual/en/pcre.constants.php for additional info on PCRE errors.
|
1156 |
+
*/
|
1157 |
+
return (null !== $converted) ? $converted : $html;
|
1158 |
+
}
|
1159 |
+
|
1160 |
+
/**
|
1161 |
+
* Convert AMP bind-attributes back to their original syntax.
|
1162 |
+
*
|
1163 |
+
* This is not guaranteed to produce the exact same result as the initial markup, as it is more of a best guess.
|
1164 |
+
* It can end up replacing the wrong attributes if the initial markup had inconsistent styling, mixing both syntaxes
|
1165 |
+
* for the same attribute. In either case, it will always produce working markup, so this is not that big of a deal.
|
1166 |
+
*
|
1167 |
+
* @see convertAmpBindAttributes() Reciprocal function.
|
1168 |
+
* @link https://www.ampproject.org/docs/reference/components/amp-bind
|
1169 |
+
*
|
1170 |
+
* @param string $html HTML with amp-bind attributes converted.
|
1171 |
+
* @return string HTML with amp-bind attributes restored.
|
1172 |
+
*/
|
1173 |
+
public function restoreAmpBindAttributes($html)
|
1174 |
+
{
|
1175 |
+
if ($this->options[Option::AMP_BIND_SYNTAX] === Option::AMP_BIND_SYNTAX_DATA_ATTRIBUTE) {
|
1176 |
+
// All amp-bind attributes should remain in their converted data attribute form.
|
1177 |
+
return $html;
|
1178 |
+
}
|
1179 |
+
|
1180 |
+
if (
|
1181 |
+
$this->options[Option::AMP_BIND_SYNTAX] === Option::AMP_BIND_SYNTAX_AUTO
|
1182 |
+
&&
|
1183 |
+
empty($this->convertedAmpBindAttributes)
|
1184 |
+
) {
|
1185 |
+
// Only previously converted amp-bind attributes should be restored, but none were converted.
|
1186 |
+
return $html;
|
1187 |
+
}
|
1188 |
+
|
1189 |
+
/**
|
1190 |
+
* Replace callback.
|
1191 |
+
*
|
1192 |
+
* @param array $tagMatches Tag matches.
|
1193 |
+
* @return string Replacement.
|
1194 |
+
*/
|
1195 |
+
$replaceCallback = function ($tagMatches) {
|
1196 |
+
|
1197 |
+
// Strip the self-closing slash as long as it is not an attribute value, like for the href attribute.
|
1198 |
+
$oldAttrs = rtrim(preg_replace('#(?<!=)/$#', '', $tagMatches['attrs']));
|
1199 |
+
|
1200 |
+
$newAttrs = '';
|
1201 |
+
$offset = 0;
|
1202 |
+
while (preg_match(self::AMP_BIND_DATA_ATTRIBUTE_ATTR_PATTERN, substr($oldAttrs, $offset), $attrMatches)) {
|
1203 |
+
$offset += strlen($attrMatches[0]);
|
1204 |
+
|
1205 |
+
$attrName = substr($attrMatches['name'], strlen(self::AMP_BIND_DATA_ATTR_PREFIX));
|
1206 |
+
if (
|
1207 |
+
$this->options[Option::AMP_BIND_SYNTAX] === Option::AMP_BIND_SYNTAX_SQUARE_BRACKETS
|
1208 |
+
||
|
1209 |
+
in_array($attrName, $this->convertedAmpBindAttributes, true)
|
1210 |
+
) {
|
1211 |
+
$attrValue = isset($attrMatches['value']) ? $attrMatches['value'] : '=""';
|
1212 |
+
$newAttrs .= " [{$attrName}]{$attrValue}";
|
1213 |
+
} else {
|
1214 |
+
$newAttrs .= $attrMatches[0];
|
1215 |
+
}
|
1216 |
+
}
|
1217 |
+
|
1218 |
+
// Bail on parse error which occurs when the regex isn't able to consume the entire $newAttrs string.
|
1219 |
+
if (strlen($oldAttrs) !== $offset) {
|
1220 |
+
return $tagMatches[0];
|
1221 |
+
}
|
1222 |
+
|
1223 |
+
return '<' . $tagMatches['name'] . $newAttrs . '>';
|
1224 |
+
};
|
1225 |
+
|
1226 |
+
$converted = preg_replace_callback(
|
1227 |
+
self::AMP_BIND_DATA_START_PATTERN,
|
1228 |
+
$replaceCallback,
|
1229 |
+
$html
|
1230 |
+
);
|
1231 |
+
|
1232 |
+
/*
|
1233 |
+
* If the regex engine incurred an error during processing, for example exceeding the backtrack
|
1234 |
+
* limit, $converted will be null. In this case we return the originally passed document to allow
|
1235 |
+
* DOMDocument to attempt to load it. If the AMP HTML doesn't make use of amp-bind or similar
|
1236 |
+
* attributes, then everything should still work.
|
1237 |
+
*
|
1238 |
+
* See https://github.com/ampproject/amp-wp/issues/993 for additional context on this issue.
|
1239 |
+
* See http://php.net/manual/en/pcre.constants.php for additional info on PCRE errors.
|
1240 |
+
*/
|
1241 |
+
return (null !== $converted) ? $converted : $html;
|
1242 |
+
}
|
1243 |
+
|
1244 |
+
/**
|
1245 |
+
* Adapt the encoding of the content.
|
1246 |
+
*
|
1247 |
+
* @param string $source Source content to adapt the encoding of.
|
1248 |
+
* @return string Adapted content.
|
1249 |
+
*/
|
1250 |
+
private function adaptEncoding($source)
|
1251 |
+
{
|
1252 |
+
// No encoding was provided, so we need to guess.
|
1253 |
+
if (Encoding::UNKNOWN === $this->originalEncoding && function_exists('mb_detect_encoding')) {
|
1254 |
+
$this->originalEncoding = mb_detect_encoding($source, Encoding::DETECTION_ORDER, true);
|
1255 |
+
}
|
1256 |
+
|
1257 |
+
// Guessing the encoding seems to have failed, so we assume UTF-8 instead.
|
1258 |
+
// In my testing, this was not possible as long as one ISO-8859-x is in the detection order.
|
1259 |
+
if (empty($this->originalEncoding)) {
|
1260 |
+
$this->originalEncoding = Encoding::AMP; // @codeCoverageIgnore
|
1261 |
+
}
|
1262 |
+
|
1263 |
+
$this->originalEncoding = $this->sanitizeEncoding($this->originalEncoding);
|
1264 |
+
|
1265 |
+
// Sanitization failed, so we do a last effort to auto-detect.
|
1266 |
+
if (Encoding::UNKNOWN === $this->originalEncoding && function_exists('mb_detect_encoding')) {
|
1267 |
+
$detectedEncoding = mb_detect_encoding($source, Encoding::DETECTION_ORDER, true);
|
1268 |
+
if ($detectedEncoding !== false) {
|
1269 |
+
$this->originalEncoding = $detectedEncoding;
|
1270 |
+
}
|
1271 |
+
}
|
1272 |
+
|
1273 |
+
$target = false;
|
1274 |
+
if (Encoding::AMP !== strtolower($this->originalEncoding)) {
|
1275 |
+
$target = function_exists('mb_convert_encoding')
|
1276 |
+
? mb_convert_encoding($source, Encoding::AMP, $this->originalEncoding)
|
1277 |
+
: false;
|
1278 |
+
}
|
1279 |
+
|
1280 |
+
return false !== $target ? $target : $source;
|
1281 |
+
}
|
1282 |
+
|
1283 |
+
/**
|
1284 |
+
* Detect the encoding of the document.
|
1285 |
+
*
|
1286 |
+
* @param string $content Content of which to detect the encoding.
|
1287 |
+
* @return array {
|
1288 |
+
* Detected encoding of the document, or false if none.
|
1289 |
+
*
|
1290 |
+
* @type string $content Potentially modified content.
|
1291 |
+
* @type string|false $encoding Encoding of the content, or false if not detected.
|
1292 |
+
* }
|
1293 |
+
*/
|
1294 |
+
private function detectAndStripEncoding($content)
|
1295 |
+
{
|
1296 |
+
$encoding = $this->originalEncoding;
|
1297 |
+
|
1298 |
+
// Check for HTML 4 http-equiv meta tags.
|
1299 |
+
foreach ($this->findTags($content, Tag::META, Attribute::HTTP_EQUIV) as $potentialHttpEquivTag) {
|
1300 |
+
$encoding = $this->extractValue($potentialHttpEquivTag, Attribute::CHARSET);
|
1301 |
+
if (false !== $encoding) {
|
1302 |
+
$httpEquivTag = $potentialHttpEquivTag;
|
1303 |
+
}
|
1304 |
+
}
|
1305 |
+
|
1306 |
+
// Strip all charset tags.
|
1307 |
+
if (isset($httpEquivTag)) {
|
1308 |
+
$content = str_replace($httpEquivTag, '', $content);
|
1309 |
+
}
|
1310 |
+
|
1311 |
+
// Check for HTML 5 charset meta tag. This overrides the HTML 4 charset.
|
1312 |
+
$charsetTag = $this->findTag($content, Tag::META, Attribute::CHARSET);
|
1313 |
+
if ($charsetTag) {
|
1314 |
+
$encoding = $this->extractValue($charsetTag, Attribute::CHARSET);
|
1315 |
+
|
1316 |
+
// Strip the encoding if it is not the required one.
|
1317 |
+
if (strtolower($encoding) !== Encoding::AMP) {
|
1318 |
+
$content = str_replace($charsetTag, '', $content);
|
1319 |
+
}
|
1320 |
+
}
|
1321 |
+
|
1322 |
+
return [$content, $encoding];
|
1323 |
+
}
|
1324 |
+
|
1325 |
+
/**
|
1326 |
+
* Find a given tag with a given attribute.
|
1327 |
+
*
|
1328 |
+
* If multiple tags match, this method will only return the first one.
|
1329 |
+
*
|
1330 |
+
* @param string $content Content in which to find the tag.
|
1331 |
+
* @param string $element Element of the tag.
|
1332 |
+
* @param string $attribute Attribute that the tag contains.
|
1333 |
+
* @return string[] The requested tags. Returns an empty array if none found.
|
1334 |
+
*/
|
1335 |
+
private function findTags($content, $element, $attribute = null)
|
1336 |
+
{
|
1337 |
+
$matches = [];
|
1338 |
+
$pattern = empty($attribute)
|
1339 |
+
? sprintf(
|
1340 |
+
self::HTML_FIND_TAG_WITHOUT_ATTRIBUTE_PATTERN,
|
1341 |
+
preg_quote($element, self::HTML_FIND_TAG_DELIMITER)
|
1342 |
+
)
|
1343 |
+
: sprintf(
|
1344 |
+
self::HTML_FIND_TAG_WITH_ATTRIBUTE_PATTERN,
|
1345 |
+
preg_quote($element, self::HTML_FIND_TAG_DELIMITER),
|
1346 |
+
preg_quote($attribute, self::HTML_FIND_TAG_DELIMITER)
|
1347 |
+
);
|
1348 |
+
|
1349 |
+
if (preg_match($pattern, $content, $matches)) {
|
1350 |
+
return $matches;
|
1351 |
+
}
|
1352 |
+
|
1353 |
+
return [];
|
1354 |
+
}
|
1355 |
+
|
1356 |
+
/**
|
1357 |
+
* Find a given tag with a given attribute.
|
1358 |
+
*
|
1359 |
+
* If multiple tags match, this method will only return the first one.
|
1360 |
+
*
|
1361 |
+
* @param string $content Content in which to find the tag.
|
1362 |
+
* @param string $element Element of the tag.
|
1363 |
+
* @param string $attribute Attribute that the tag contains.
|
1364 |
+
* @return string|false The requested tag, or false if not found.
|
1365 |
+
*/
|
1366 |
+
private function findTag($content, $element, $attribute = null)
|
1367 |
+
{
|
1368 |
+
$matches = $this->findTags($content, $element, $attribute);
|
1369 |
+
|
1370 |
+
if (empty($matches)) {
|
1371 |
+
return false;
|
1372 |
+
}
|
1373 |
+
|
1374 |
+
return $matches[0];
|
1375 |
+
}
|
1376 |
+
|
1377 |
+
/**
|
1378 |
+
* Extract an attribute value from an HTML tag.
|
1379 |
+
*
|
1380 |
+
* @param string $tag Tag from which to extract the attribute.
|
1381 |
+
* @param string $attribute Attribute of which to extract the value.
|
1382 |
+
* @return string|false Extracted attribute value, false if not found.
|
1383 |
+
*/
|
1384 |
+
private function extractValue($tag, $attribute)
|
1385 |
+
{
|
1386 |
+
$matches = [];
|
1387 |
+
$pattern = sprintf(
|
1388 |
+
self::HTML_EXTRACT_ATTRIBUTE_VALUE_PATTERN,
|
1389 |
+
preg_quote($attribute, self::HTML_FIND_TAG_DELIMITER)
|
1390 |
+
);
|
1391 |
+
|
1392 |
+
if (preg_match($pattern, $tag, $matches)) {
|
1393 |
+
return empty($matches['full']) ? $matches['partial'] : $matches['full'];
|
1394 |
+
}
|
1395 |
+
|
1396 |
+
return false;
|
1397 |
+
}
|
1398 |
+
|
1399 |
+
/**
|
1400 |
+
* Sanitize the encoding that was detected.
|
1401 |
+
*
|
1402 |
+
* If sanitization fails, it will return 'auto', letting the conversion
|
1403 |
+
* logic try to figure it out itself.
|
1404 |
+
*
|
1405 |
+
* @param string $encoding Encoding to sanitize.
|
1406 |
+
* @return string Sanitized encoding. Falls back to 'auto' on failure.
|
1407 |
+
*/
|
1408 |
+
private function sanitizeEncoding($encoding)
|
1409 |
+
{
|
1410 |
+
$encoding = strtolower($encoding);
|
1411 |
+
|
1412 |
+
if ($encoding === Encoding::AMP) {
|
1413 |
+
return $encoding;
|
1414 |
+
}
|
1415 |
+
|
1416 |
+
if (! function_exists('mb_list_encodings')) {
|
1417 |
+
return $encoding;
|
1418 |
+
}
|
1419 |
+
|
1420 |
+
static $knownEncodings = null;
|
1421 |
+
|
1422 |
+
if (null === $knownEncodings) {
|
1423 |
+
$knownEncodings = array_map('strtolower', mb_list_encodings());
|
1424 |
+
}
|
1425 |
+
|
1426 |
+
if (array_key_exists($encoding, Encoding::MAPPINGS)) {
|
1427 |
+
$encoding = Encoding::MAPPINGS[$encoding];
|
1428 |
+
}
|
1429 |
+
|
1430 |
+
if (! in_array($encoding, $knownEncodings, true)) {
|
1431 |
+
return Encoding::UNKNOWN;
|
1432 |
+
}
|
1433 |
+
|
1434 |
+
return $encoding;
|
1435 |
+
}
|
1436 |
+
|
1437 |
+
/**
|
1438 |
+
* Replace Mustache template tokens to safeguard them from turning into HTML entities.
|
1439 |
+
*
|
1440 |
+
* Prevents amp-mustache syntax from getting URL-encoded in attributes when saveHTML is done.
|
1441 |
+
* While this is applying to the entire document, it only really matters inside of <template>
|
1442 |
+
* elements, since URL-encoding of curly braces in href attributes would not normally matter.
|
1443 |
+
* But when this is done inside of a <template> then it breaks Mustache. Since Mustache
|
1444 |
+
* is logic-less and curly braces are not unsafe for HTML, we can do a global replacement.
|
1445 |
+
* The replacement is done on the entire HTML document instead of just inside of the <template>
|
1446 |
+
* elements since it is faster and wouldn't change the outcome.
|
1447 |
+
*
|
1448 |
+
* @see restoreMustacheTemplateTokens() Reciprocal function.
|
1449 |
+
*/
|
1450 |
+
private function replaceMustacheTemplateTokens()
|
1451 |
+
{
|
1452 |
+
$templates = $this->xpath->query(self::XPATH_MUSTACHE_TEMPLATE_ELEMENTS_QUERY, $this->body);
|
1453 |
+
if (0 === $templates->length) {
|
1454 |
+
return;
|
1455 |
+
}
|
1456 |
+
|
1457 |
+
$mustacheTagPlaceholders = $this->getMustacheTagPlaceholders();
|
1458 |
+
|
1459 |
+
foreach ($templates as $template) {
|
1460 |
+
foreach ($this->xpath->query(self::XPATH_URL_ENCODED_ATTRIBUTES_QUERY, $template) as $attribute) {
|
1461 |
+
$value = preg_replace_callback(
|
1462 |
+
$this->getMustacheTagPattern(),
|
1463 |
+
static function ($matches) use ($mustacheTagPlaceholders) {
|
1464 |
+
return $mustacheTagPlaceholders[trim($matches[0])];
|
1465 |
+
},
|
1466 |
+
$attribute->nodeValue,
|
1467 |
+
-1,
|
1468 |
+
$count
|
1469 |
+
);
|
1470 |
+
|
1471 |
+
if ($count) {
|
1472 |
+
// Note we cannot do `$attribute->nodeValue = $value` because the PHP DOM will try to parse any
|
1473 |
+
// entities. In the case of a URL value like '/foo/?bar=1&baz=2' the result is a warning for an
|
1474 |
+
// unterminated entity reference "baz". When the attribute value is updated via setAttribute() this
|
1475 |
+
// same problem does not occur, so that is why the following is used.
|
1476 |
+
$attribute->parentNode->setAttribute($attribute->nodeName, $value);
|
1477 |
+
|
1478 |
+
$this->mustacheTagsReplaced = true;
|
1479 |
+
}
|
1480 |
+
}
|
1481 |
+
}
|
1482 |
+
}
|
1483 |
+
|
1484 |
+
/**
|
1485 |
+
* Restore Mustache template tokens that were previously replaced.
|
1486 |
+
*
|
1487 |
+
* @param string $html HTML string to adapt.
|
1488 |
+
* @return string Adapted HTML string.
|
1489 |
+
* @see replaceMustacheTemplateTokens() Reciprocal function.
|
1490 |
+
*
|
1491 |
+
*/
|
1492 |
+
private function restoreMustacheTemplateTokens($html)
|
1493 |
+
{
|
1494 |
+
if (! $this->mustacheTagsReplaced) {
|
1495 |
+
return $html;
|
1496 |
+
}
|
1497 |
+
|
1498 |
+
$mustacheTagPlaceholders = $this->getMustacheTagPlaceholders();
|
1499 |
+
|
1500 |
+
return str_replace(
|
1501 |
+
$mustacheTagPlaceholders,
|
1502 |
+
array_keys($mustacheTagPlaceholders),
|
1503 |
+
$html
|
1504 |
+
);
|
1505 |
+
}
|
1506 |
+
|
1507 |
+
/**
|
1508 |
+
* Get amp-mustache tag/placeholder mappings.
|
1509 |
+
*
|
1510 |
+
* @return string[] Mapping of mustache tag token to its placeholder.
|
1511 |
+
* @see \wpdb::placeholder_escape()
|
1512 |
+
*/
|
1513 |
+
private function getMustacheTagPlaceholders()
|
1514 |
+
{
|
1515 |
+
static $placeholders = null;
|
1516 |
+
|
1517 |
+
if (null === $placeholders) {
|
1518 |
+
$placeholders = [];
|
1519 |
+
|
1520 |
+
// Note: The order of these tokens is important, as it determines the order of the replacements.
|
1521 |
+
$tokens = [
|
1522 |
+
'{{{',
|
1523 |
+
'}}}',
|
1524 |
+
'{{#',
|
1525 |
+
'{{^',
|
1526 |
+
'{{/',
|
1527 |
+
'{{',
|
1528 |
+
'}}',
|
1529 |
+
];
|
1530 |
+
|
1531 |
+
foreach ($tokens as $token) {
|
1532 |
+
$placeholders[$token] = '_amp_mustache_' . md5(uniqid($token));
|
1533 |
+
}
|
1534 |
+
}
|
1535 |
+
|
1536 |
+
return $placeholders;
|
1537 |
+
}
|
1538 |
+
|
1539 |
+
/**
|
1540 |
+
* Get a regular expression that matches all amp-mustache tags while consuming whitespace.
|
1541 |
+
*
|
1542 |
+
* Removing whitespace is needed to avoid DOMDocument turning whitespace into entities, like %20 for spaces.
|
1543 |
+
*
|
1544 |
+
* @return string Regex pattern to match amp-mustache tags with whitespace.
|
1545 |
+
*/
|
1546 |
+
private function getMustacheTagPattern()
|
1547 |
+
{
|
1548 |
+
static $tagPattern = null;
|
1549 |
+
|
1550 |
+
if (null === $tagPattern) {
|
1551 |
+
$delimiter = ':';
|
1552 |
+
$tags = [];
|
1553 |
+
|
1554 |
+
foreach (array_keys($this->getMustacheTagPlaceholders()) as $token) {
|
1555 |
+
if ('{' === $token[0]) {
|
1556 |
+
$tags[] = preg_quote($token, $delimiter) . '\s*';
|
1557 |
+
} else {
|
1558 |
+
$tags[] = '\s*' . preg_quote($token, $delimiter);
|
1559 |
+
}
|
1560 |
+
}
|
1561 |
+
|
1562 |
+
$tagPattern = $delimiter . implode('|', $tags) . $delimiter;
|
1563 |
+
}
|
1564 |
+
|
1565 |
+
return $tagPattern;
|
1566 |
+
}
|
1567 |
+
|
1568 |
+
/**
|
1569 |
+
* Covert the emoji AMP symbol (⚡) into pure text.
|
1570 |
+
*
|
1571 |
+
* The emoji symbol gets stripped by DOMDocument::loadHTML().
|
1572 |
+
*
|
1573 |
+
* @param string $source Source HTML string to convert the emoji AMP symbol in.
|
1574 |
+
* @return string Adapted source HTML string.
|
1575 |
+
*/
|
1576 |
+
private function convertAmpEmojiAttribute($source)
|
1577 |
+
{
|
1578 |
+
$this->usedAmpEmoji = '';
|
1579 |
+
|
1580 |
+
return preg_replace_callback(
|
1581 |
+
self::AMP_EMOJI_ATTRIBUTE_PATTERN,
|
1582 |
+
function ($matches) {
|
1583 |
+
// Split into individual attributes.
|
1584 |
+
$attributes = array_map(
|
1585 |
+
'trim',
|
1586 |
+
array_filter(
|
1587 |
+
preg_split(
|
1588 |
+
'#(\s+[^"\'\s=]+(?:=(?:"[^"]+"|\'[^\']+\'|[^"\'\s]+))?)#',
|
1589 |
+
$matches[1],
|
1590 |
+
-1,
|
1591 |
+
PREG_SPLIT_DELIM_CAPTURE
|
1592 |
+
)
|
1593 |
+
)
|
1594 |
+
);
|
1595 |
+
|
1596 |
+
foreach ($attributes as $index => $attribute) {
|
1597 |
+
$attributeMatches = [];
|
1598 |
+
if (
|
1599 |
+
preg_match(
|
1600 |
+
'/^(' . Attribute::AMP_EMOJI_ALT . '|' . Attribute::AMP_EMOJI . ')(4(?:ads|email))?$/i',
|
1601 |
+
$attribute,
|
1602 |
+
$attributeMatches
|
1603 |
+
)
|
1604 |
+
) {
|
1605 |
+
$this->usedAmpEmoji = $attributeMatches[1];
|
1606 |
+
$variant = ! empty($attributeMatches[2]) ? $attributeMatches[2] : '';
|
1607 |
+
$attributes[$index] = self::EMOJI_AMP_ATTRIBUTE_PLACEHOLDER . "=\"{$variant}\"";
|
1608 |
+
break;
|
1609 |
+
}
|
1610 |
+
}
|
1611 |
+
|
1612 |
+
return '<html ' . implode(' ', $attributes) . '>';
|
1613 |
+
},
|
1614 |
+
$source,
|
1615 |
+
1
|
1616 |
+
);
|
1617 |
+
}
|
1618 |
+
|
1619 |
+
/**
|
1620 |
+
* Restore the emoji AMP symbol (⚡) from its pure text placeholder.
|
1621 |
+
*
|
1622 |
+
* @param string $html HTML string to restore the AMP emoji symbol in.
|
1623 |
+
* @return string Adapted HTML string.
|
1624 |
+
*/
|
1625 |
+
private function restoreAmpEmojiAttribute($html)
|
1626 |
+
{
|
1627 |
+
if (empty($this->usedAmpEmoji)) {
|
1628 |
+
return $html;
|
1629 |
+
}
|
1630 |
+
|
1631 |
+
return preg_replace(
|
1632 |
+
'/(<html\s[^>]*?)' . preg_quote(self::EMOJI_AMP_ATTRIBUTE_PLACEHOLDER, '/') . '="([^"]*)"/i',
|
1633 |
+
'\1' . $this->usedAmpEmoji . '\2',
|
1634 |
+
$html,
|
1635 |
+
1
|
1636 |
+
);
|
1637 |
+
}
|
1638 |
+
|
1639 |
+
/**
|
1640 |
+
* Secure the original doctype node.
|
1641 |
+
*
|
1642 |
+
* We need to keep elements around that were prepended to the doctype, like comment node used for source-tracking.
|
1643 |
+
* As DOM_Document prepends a new doctype node and removes the old one if the first element is not the doctype, we
|
1644 |
+
* need to ensure the original one is not stripped (by changing its node type) and restore it later on.
|
1645 |
+
*
|
1646 |
+
* @param string $html HTML string to adapt.
|
1647 |
+
* @return string Adapted HTML string.
|
1648 |
+
* @see restoreDoctypeNode() Reciprocal function.
|
1649 |
+
*
|
1650 |
+
*/
|
1651 |
+
private function secureDoctypeNode($html)
|
1652 |
+
{
|
1653 |
+
return preg_replace(
|
1654 |
+
self::HTML_SECURE_DOCTYPE_IF_NOT_FIRST_PATTERN,
|
1655 |
+
'\1!--amp-\3\4-->',
|
1656 |
+
$html,
|
1657 |
+
1,
|
1658 |
+
$this->securedDoctype
|
1659 |
+
);
|
1660 |
+
}
|
1661 |
+
|
1662 |
+
/**
|
1663 |
+
* Restore the original doctype node.
|
1664 |
+
*
|
1665 |
+
* @param string $html HTML string to adapt.
|
1666 |
+
* @return string Adapted HTML string.
|
1667 |
+
* @see secureDoctypeNode() Reciprocal function.
|
1668 |
+
*
|
1669 |
+
*/
|
1670 |
+
private function restoreDoctypeNode($html)
|
1671 |
+
{
|
1672 |
+
if (! $this->securedDoctype) {
|
1673 |
+
return $html;
|
1674 |
+
}
|
1675 |
+
|
1676 |
+
return preg_replace(self::HTML_RESTORE_DOCTYPE_PATTERN, '\1!\3\4>', $html, 1);
|
1677 |
+
}
|
1678 |
+
|
1679 |
+
/**
|
1680 |
+
* Process the HTML output string and tweak it as needed.
|
1681 |
+
*
|
1682 |
+
* @param string $html HTML output string to tweak.
|
1683 |
+
* @return string Tweaked HTML output string.
|
1684 |
+
*/
|
1685 |
+
public function fixSvgSourceAttributeEncoding($html)
|
1686 |
+
{
|
1687 |
+
return preg_replace_callback(self::I_AMPHTML_SIZER_REGEX_PATTERN, [$this, 'adaptSizer'], $html);
|
1688 |
+
}
|
1689 |
+
|
1690 |
+
/**
|
1691 |
+
* Adapt the sizer element so that it validates against the AMP spec.
|
1692 |
+
*
|
1693 |
+
* @param array $matches Matches that the regular expression collected.
|
1694 |
+
* @return string Adapted string to use as replacement.
|
1695 |
+
*/
|
1696 |
+
private function adaptSizer($matches)
|
1697 |
+
{
|
1698 |
+
$src = $matches['src'];
|
1699 |
+
$src = htmlspecialchars_decode($src, ENT_NOQUOTES);
|
1700 |
+
$src = preg_replace_callback(self::SRC_SVG_REGEX_PATTERN, [$this, 'adaptSvg'], $src);
|
1701 |
+
return $matches['before_src'] . $src . $matches['after_src'];
|
1702 |
+
}
|
1703 |
+
|
1704 |
+
/**
|
1705 |
+
* Adapt the SVG syntax within the sizer element so that it validates against the AMP spec.
|
1706 |
+
*
|
1707 |
+
* @param array $matches Matches that the regular expression collected.
|
1708 |
+
* @return string Adapted string to use as replacement.
|
1709 |
+
*/
|
1710 |
+
private function adaptSvg($matches)
|
1711 |
+
{
|
1712 |
+
return $matches['type'] . urldecode($matches['value']);
|
1713 |
+
}
|
1714 |
+
|
1715 |
+
/**
|
1716 |
+
* Deduplicate a given tag.
|
1717 |
+
*
|
1718 |
+
* This keeps the first tag as the main tag and moves over all child nodes and attribute nodes from any subsequent
|
1719 |
+
* same tags over to remove them.
|
1720 |
+
*
|
1721 |
+
* @param string $tagName Name of the tag to deduplicate.
|
1722 |
+
*/
|
1723 |
+
public function deduplicateTag($tagName)
|
1724 |
+
{
|
1725 |
+
$tags = $this->getElementsByTagName($tagName);
|
1726 |
+
|
1727 |
+
/**
|
1728 |
+
* Main tag to keep.
|
1729 |
+
*
|
1730 |
+
* @var Element|null $mainTag
|
1731 |
+
*/
|
1732 |
+
$mainTag = $tags->item(0);
|
1733 |
+
|
1734 |
+
if (null === $mainTag) {
|
1735 |
+
return;
|
1736 |
+
}
|
1737 |
+
|
1738 |
+
while ($tags->length > 1) {
|
1739 |
+
/**
|
1740 |
+
* Tag to remove.
|
1741 |
+
*
|
1742 |
+
* @var Element $tagToRemove
|
1743 |
+
*/
|
1744 |
+
$tagToRemove = $tags->item(1);
|
1745 |
+
|
1746 |
+
foreach ($tagToRemove->childNodes as $childNode) {
|
1747 |
+
$mainTag->appendChild($childNode->parentNode->removeChild($childNode));
|
1748 |
+
}
|
1749 |
+
|
1750 |
+
while ($tagToRemove->hasAttributes()) {
|
1751 |
+
/**
|
1752 |
+
* Attribute node to move over to the main tag.
|
1753 |
+
*
|
1754 |
+
* @var DOMAttr $attribute
|
1755 |
+
*/
|
1756 |
+
$attribute = $tagToRemove->attributes->item(0);
|
1757 |
+
$tagToRemove->removeAttributeNode($attribute);
|
1758 |
+
|
1759 |
+
// @TODO This doesn't deal properly with attributes present on both tags. Maybe overkill to add?
|
1760 |
+
// We could move over the copy_attributes from AMP_DOM_Utils to do this.
|
1761 |
+
$mainTag->setAttributeNode($attribute);
|
1762 |
+
}
|
1763 |
+
|
1764 |
+
$tagToRemove->parentNode->removeChild($tagToRemove);
|
1765 |
+
}
|
1766 |
+
|
1767 |
+
// Avoid doing the above query again if possible.
|
1768 |
+
if (in_array($tagName, [Tag::HEAD, Tag::BODY], true)) {
|
1769 |
+
$this->$tagName = $mainTag;
|
1770 |
+
}
|
1771 |
+
}
|
1772 |
+
|
1773 |
+
/**
|
1774 |
+
* Determine whether a node can be in the head.
|
1775 |
+
*
|
1776 |
+
* @link https://github.com/ampproject/amphtml/blob/445d6e3be8a5063e2738c6f90fdcd57f2b6208be/validator/engine/htmlparser.js#L83-L100
|
1777 |
+
* @link https://www.w3.org/TR/html5/document-metadata.html
|
1778 |
+
*
|
1779 |
+
* @param DOMNode $node Node.
|
1780 |
+
* @return bool Whether valid head node.
|
1781 |
+
*/
|
1782 |
+
public function isValidHeadNode(DOMNode $node)
|
1783 |
+
{
|
1784 |
+
return (
|
1785 |
+
($node instanceof Element && in_array($node->nodeName, Tag::ELEMENTS_ALLOWED_IN_HEAD, true))
|
1786 |
+
||
|
1787 |
+
($node instanceof DOMText && preg_match('/^\s*$/', $node->nodeValue)) // Whitespace text nodes are OK.
|
1788 |
+
||
|
1789 |
+
$node instanceof DOMComment
|
1790 |
+
);
|
1791 |
+
}
|
1792 |
+
|
1793 |
+
/**
|
1794 |
+
* Get auto-incremented ID unique to this class's instantiation.
|
1795 |
+
*
|
1796 |
+
* @param string $prefix Prefix.
|
1797 |
+
* @return string ID.
|
1798 |
+
*/
|
1799 |
+
private function getUniqueId($prefix = '')
|
1800 |
+
{
|
1801 |
+
if (array_key_exists($prefix, $this->indexCounter)) {
|
1802 |
+
++$this->indexCounter[$prefix];
|
1803 |
+
} else {
|
1804 |
+
$this->indexCounter[$prefix] = 0;
|
1805 |
+
}
|
1806 |
+
$uniqueId = (string)$this->indexCounter[$prefix];
|
1807 |
+
if ($prefix) {
|
1808 |
+
$uniqueId = "{$prefix}-{$uniqueId}";
|
1809 |
+
}
|
1810 |
+
return $uniqueId;
|
1811 |
+
}
|
1812 |
+
|
1813 |
+
/**
|
1814 |
+
* Get the ID for an element.
|
1815 |
+
*
|
1816 |
+
* If the element does not have an ID, create one first.
|
1817 |
+
*
|
1818 |
+
* @param Element $element Element to get the ID for.
|
1819 |
+
* @param string $prefix Optional. The prefix to use (should not have a trailing dash). Defaults to 'i-amp-id'.
|
1820 |
+
* @return string ID to use.
|
1821 |
+
*/
|
1822 |
+
public function getElementId(Element $element, $prefix = 'i-amp')
|
1823 |
+
{
|
1824 |
+
if ($element->hasAttribute(Attribute::ID)) {
|
1825 |
+
return $element->getAttribute(Attribute::ID);
|
1826 |
+
}
|
1827 |
+
|
1828 |
+
$id = $this->getUniqueId($prefix);
|
1829 |
+
while ($this->getElementById($id) instanceof Element) {
|
1830 |
+
$id = $this->getUniqueId($prefix);
|
1831 |
+
}
|
1832 |
+
|
1833 |
+
$element->setAttribute(Attribute::ID, $id);
|
1834 |
+
|
1835 |
+
return $id;
|
1836 |
+
}
|
1837 |
+
|
1838 |
+
/**
|
1839 |
+
* Determine whether `data-ampdevmode` was initially set on the document element.
|
1840 |
+
*
|
1841 |
+
* @return bool
|
1842 |
+
*/
|
1843 |
+
public function hasInitialAmpDevMode()
|
1844 |
+
{
|
1845 |
+
return $this->hasInitialAmpDevMode;
|
1846 |
+
}
|
1847 |
+
|
1848 |
+
/**
|
1849 |
+
* Add style(s) to the <style amp-custom> tag.
|
1850 |
+
*
|
1851 |
+
* @param string $style Style to add.
|
1852 |
+
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
1853 |
+
*/
|
1854 |
+
public function addAmpCustomStyle($style)
|
1855 |
+
{
|
1856 |
+
$style = trim($style, CssRule::CSS_TRIM_CHARACTERS);
|
1857 |
+
$existingStyle = (string)$this->ampCustomStyle->textContent;
|
1858 |
+
|
1859 |
+
// Inject new styles before any potential source map annotation comment like: /*# sourceURL=amp-custom.css */.
|
1860 |
+
// If not present, then just put it at the end of the stylesheet. This isn't strictly required, but putting the
|
1861 |
+
// source map comments at the end is the convention.
|
1862 |
+
$newStyle = preg_replace(
|
1863 |
+
':(?=\s+/\*#[^*]+?\*/\s*$|$):s',
|
1864 |
+
$style,
|
1865 |
+
$existingStyle,
|
1866 |
+
1
|
1867 |
+
);
|
1868 |
+
|
1869 |
+
$newByteCount = strlen($newStyle);
|
1870 |
+
|
1871 |
+
if ($this->getRemainingCustomCssSpace() < ($newByteCount - $this->ampCustomStyleByteCount)) {
|
1872 |
+
throw MaxCssByteCountExceeded::forAmpCustom($newStyle);
|
1873 |
+
}
|
1874 |
+
|
1875 |
+
$this->ampCustomStyle->textContent = $newStyle;
|
1876 |
+
$this->ampCustomStyleByteCount = $newByteCount;
|
1877 |
+
}
|
1878 |
+
|
1879 |
+
/**
|
1880 |
+
* Add the given number of bytes ot the total inline style byte count.
|
1881 |
+
*
|
1882 |
+
* @param int $byteCount Bytes to add.
|
1883 |
+
*/
|
1884 |
+
public function addInlineStyleByteCount($byteCount)
|
1885 |
+
{
|
1886 |
+
$this->inlineStyleByteCount += $byteCount;
|
1887 |
+
}
|
1888 |
+
|
1889 |
+
/**
|
1890 |
+
* Get the remaining number bytes allowed for custom CSS.
|
1891 |
+
*
|
1892 |
+
* @return int
|
1893 |
+
*/
|
1894 |
+
public function getRemainingCustomCssSpace()
|
1895 |
+
{
|
1896 |
+
if ($this->cssMaxByteCountEnforced < 0) {
|
1897 |
+
// No CSS byte count limit is being enforced, so return the next best thing to +∞.
|
1898 |
+
return PHP_INT_MAX;
|
1899 |
+
}
|
1900 |
+
|
1901 |
+
return max(
|
1902 |
+
0,
|
1903 |
+
$this->cssMaxByteCountEnforced - (int)$this->ampCustomStyleByteCount - (int)$this->inlineStyleByteCount
|
1904 |
+
);
|
1905 |
+
}
|
1906 |
+
|
1907 |
+
/**
|
1908 |
+
* Magic getter to implement lazily-created, cached properties for the document.
|
1909 |
+
*
|
1910 |
+
* @param string $name Name of the property to get.
|
1911 |
+
* @return mixed Value of the property, or null if unknown property was requested.
|
1912 |
+
*/
|
1913 |
+
public function __get($name)
|
1914 |
+
{
|
1915 |
+
switch ($name) {
|
1916 |
+
case 'xpath':
|
1917 |
+
$this->xpath = new DOMXPath($this);
|
1918 |
+
return $this->xpath;
|
1919 |
+
case Tag::HTML:
|
1920 |
+
$html = $this->getElementsByTagName(Tag::HTML)->item(0);
|
1921 |
+
|
1922 |
+
if ($html === null) {
|
1923 |
+
// Document was assembled manually and bypassed normalisation.
|
1924 |
+
$this->normalizeDomStructure();
|
1925 |
+
$html = $this->getElementsByTagName(Tag::HTML)->item(0);
|
1926 |
+
}
|
1927 |
+
|
1928 |
+
if (!$html instanceof Element) {
|
1929 |
+
throw FailedToRetrieveRequiredDomElement::forHtmlElement($html);
|
1930 |
+
}
|
1931 |
+
|
1932 |
+
$this->html = $html;
|
1933 |
+
return $this->html;
|
1934 |
+
case Tag::HEAD:
|
1935 |
+
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
1936 |
+
|
1937 |
+
if ($head === null) {
|
1938 |
+
// Document was assembled manually and bypassed normalisation.
|
1939 |
+
$this->normalizeDomStructure();
|
1940 |
+
$head = $this->getElementsByTagName(Tag::HEAD)->item(0);
|
1941 |
+
}
|
1942 |
+
|
1943 |
+
if (!$head instanceof Element) {
|
1944 |
+
throw FailedToRetrieveRequiredDomElement::forHeadElement($head);
|
1945 |
+
}
|
1946 |
+
|
1947 |
+
$this->head = $head;
|
1948 |
+
return $this->head;
|
1949 |
+
case Tag::BODY:
|
1950 |
+
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
1951 |
+
|
1952 |
+
if ($body === null) {
|
1953 |
+
// Document was assembled manually and bypassed normalisation.
|
1954 |
+
$this->normalizeDomStructure();
|
1955 |
+
$body = $this->getElementsByTagName(Tag::BODY)->item(0);
|
1956 |
+
}
|
1957 |
+
|
1958 |
+
if (!$body instanceof Element) {
|
1959 |
+
throw FailedToRetrieveRequiredDomElement::forBodyElement($body);
|
1960 |
+
}
|
1961 |
+
|
1962 |
+
$this->body = $body;
|
1963 |
+
return $this->body;
|
1964 |
+
case Attribute::VIEWPORT:
|
1965 |
+
// This is not cached as it could potentially be requested too early, before the viewport was added, and
|
1966 |
+
// the cache would then store null without rechecking later on after the viewport has been added.
|
1967 |
+
for ($node = $this->head->firstChild; $node !== null; $node = $node->nextSibling) {
|
1968 |
+
if (
|
1969 |
+
$node instanceof Element
|
1970 |
+
&& $node->tagName === Tag::META
|
1971 |
+
&& $node->getAttribute(Attribute::NAME) === Attribute::VIEWPORT
|
1972 |
+
) {
|
1973 |
+
return $node;
|
1974 |
+
}
|
1975 |
+
}
|
1976 |
+
return null;
|
1977 |
+
case 'ampElements':
|
1978 |
+
// This is not cached as we clone some elements during SSR transformations to avoid ending up with
|
1979 |
+
// partially transformed, broken elements.
|
1980 |
+
return $this->xpath->query(self::XPATH_AMP_ELEMENTS_QUERY, $this->body)
|
1981 |
+
?: new DOMNodeList();
|
1982 |
+
|
1983 |
+
case 'ampCustomStyle':
|
1984 |
+
$ampCustomStyle = $this->xpath->query(self::XPATH_AMP_CUSTOM_STYLE_QUERY, $this->head)->item(0);
|
1985 |
+
if (!$ampCustomStyle instanceof Element) {
|
1986 |
+
$ampCustomStyle = $this->createElement(Tag::STYLE);
|
1987 |
+
$ampCustomStyle->appendChild($this->createAttribute(Attribute::AMP_CUSTOM));
|
1988 |
+
$this->head->appendChild($ampCustomStyle);
|
1989 |
+
}
|
1990 |
+
|
1991 |
+
$this->ampCustomStyle = $ampCustomStyle;
|
1992 |
+
|
1993 |
+
return $this->ampCustomStyle;
|
1994 |
+
|
1995 |
+
case 'ampCustomStyleByteCount':
|
1996 |
+
if (!isset($this->ampCustomStyle)) {
|
1997 |
+
$ampCustomStyle = $this->xpath->query(self::XPATH_AMP_CUSTOM_STYLE_QUERY, $this->head)->item(0);
|
1998 |
+
if (!$ampCustomStyle instanceof Element) {
|
1999 |
+
return 0;
|
2000 |
+
} else {
|
2001 |
+
$this->ampCustomStyle = $ampCustomStyle;
|
2002 |
+
}
|
2003 |
+
}
|
2004 |
+
|
2005 |
+
if (!isset($this->ampCustomStyleByteCount)) {
|
2006 |
+
$this->ampCustomStyleByteCount = strlen($this->ampCustomStyle->textContent);
|
2007 |
+
}
|
2008 |
+
|
2009 |
+
return $this->ampCustomStyleByteCount;
|
2010 |
+
|
2011 |
+
case 'inlineStyleByteCount':
|
2012 |
+
if (!isset($this->inlineStyleByteCount)) {
|
2013 |
+
$this->inlineStyleByteCount = 0;
|
2014 |
+
$attributes = $this->xpath->query(self::XPATH_INLINE_STYLE_ATTRIBUTES_QUERY, $this->body);
|
2015 |
+
foreach ($attributes as $attribute) {
|
2016 |
+
$this->inlineStyleByteCount += strlen($attribute->textContent);
|
2017 |
+
}
|
2018 |
+
}
|
2019 |
+
|
2020 |
+
return $this->inlineStyleByteCount;
|
2021 |
+
}
|
2022 |
+
|
2023 |
+
// Mimic regular PHP behavior for missing notices.
|
2024 |
+
trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, E_USER_NOTICE);
|
2025 |
+
return null;
|
2026 |
+
}
|
2027 |
+
|
2028 |
+
/**
|
2029 |
+
* Make sure we properly reinitialize on clone.
|
2030 |
+
*
|
2031 |
+
* @return void
|
2032 |
+
*/
|
2033 |
+
public function __clone()
|
2034 |
+
{
|
2035 |
+
$this->reset();
|
2036 |
+
}
|
2037 |
+
|
2038 |
+
/**
|
2039 |
+
* Create new element node.
|
2040 |
+
*
|
2041 |
+
* @link https://php.net/manual/domdocument.createelement.php
|
2042 |
+
*
|
2043 |
+
* This override only serves to provide the correct object type-hint for our extended Dom/Element class.
|
2044 |
+
*
|
2045 |
+
* @param string $name The tag name of the element.
|
2046 |
+
* @param string $value Optional. The value of the element. By default, an empty element will be created.
|
2047 |
+
* You can also set the value later with Element->nodeValue.
|
2048 |
+
* @return Element|false A new instance of class Element or false if an error occurred.
|
2049 |
+
*/
|
2050 |
+
public function createElement($name, $value = null)
|
2051 |
+
{
|
2052 |
+
$element = parent::createElement($name, $value);
|
2053 |
+
|
2054 |
+
if (!$element instanceof Element) {
|
2055 |
+
return false;
|
2056 |
+
}
|
2057 |
+
|
2058 |
+
return $element;
|
2059 |
+
}
|
2060 |
+
|
2061 |
+
/**
|
2062 |
+
* Create new element node.
|
2063 |
+
*
|
2064 |
+
* @link https://php.net/manual/domdocument.createelement.php
|
2065 |
+
*
|
2066 |
+
* This override only serves to provide the correct object type-hint for our extended Dom/Element class.
|
2067 |
+
*
|
2068 |
+
* @param string $name The tag name of the element.
|
2069 |
+
* @param array $attributes Attributes to add to the newly created element.
|
2070 |
+
* @param string $value Optional. The value of the element. By default, an empty element will be created.
|
2071 |
+
* You can also set the value later with Element->nodeValue.
|
2072 |
+
* @return Element|false A new instance of class Element or false if an error occurred.
|
2073 |
+
*/
|
2074 |
+
public function createElementWithAttributes($name, $attributes, $value = null)
|
2075 |
+
{
|
2076 |
+
$element = parent::createElement($name, $value);
|
2077 |
+
|
2078 |
+
if (!$element instanceof Element) {
|
2079 |
+
return false;
|
2080 |
+
}
|
2081 |
+
|
2082 |
+
$element->setAttributes($attributes);
|
2083 |
+
|
2084 |
+
return $element;
|
2085 |
+
}
|
2086 |
+
|
2087 |
+
/**
|
2088 |
+
* Check whether the CSS maximum byte count is enforced.
|
2089 |
+
*
|
2090 |
+
* @return bool Whether the CSS maximum byte count is enforced.
|
2091 |
+
*/
|
2092 |
+
public function isCssMaxByteCountEnforced()
|
2093 |
+
{
|
2094 |
+
return $this->cssMaxByteCountEnforced >= 0;
|
2095 |
+
}
|
2096 |
+
|
2097 |
+
/**
|
2098 |
+
* Enforce a maximum number of bytes for the CSS.
|
2099 |
+
*
|
2100 |
+
* @param int $maxByteCount Maximum number of bytes to limit the CSS to. A negative number disables the limit.
|
2101 |
+
*/
|
2102 |
+
public function enforceCssMaxByteCount($maxByteCount = Amp::MAX_CSS_BYTE_COUNT)
|
2103 |
+
{
|
2104 |
+
$this->cssMaxByteCountEnforced = $maxByteCount;
|
2105 |
+
}
|
2106 |
+
|
2107 |
+
/**
|
2108 |
+
* Add a http-equiv charset meta tag to the document's <head> node.
|
2109 |
+
*
|
2110 |
+
* This is needed to make the DOMDocument behave as it should in terms of encoding.
|
2111 |
+
* See: http://php.net/manual/en/domdocument.loadhtml.php#78243.
|
2112 |
+
*
|
2113 |
+
* @param string $html HTML string to add the http-equiv charset to.
|
2114 |
+
* @return string Adapted string of HTML.
|
2115 |
+
*/
|
2116 |
+
private function addHttpEquivCharset($html)
|
2117 |
+
{
|
2118 |
+
$count = 0;
|
2119 |
+
|
2120 |
+
// We try first to detect an existing <head> node.
|
2121 |
+
$html = preg_replace(
|
2122 |
+
self::HTML_GET_HEAD_OPENING_TAG_PATTERN,
|
2123 |
+
self::HTML_GET_HEAD_OPENING_TAG_REPLACEMENT,
|
2124 |
+
$html,
|
2125 |
+
1,
|
2126 |
+
$count
|
2127 |
+
);
|
2128 |
+
|
2129 |
+
|
2130 |
+
// If no <head> was found, we look for the <html> tag instead.
|
2131 |
+
if ($count < 1) {
|
2132 |
+
$html = preg_replace(
|
2133 |
+
self::HTML_GET_HTML_OPENING_TAG_PATTERN,
|
2134 |
+
self::HTML_GET_HTML_OPENING_TAG_REPLACEMENT,
|
2135 |
+
$html,
|
2136 |
+
1,
|
2137 |
+
$count
|
2138 |
+
);
|
2139 |
+
}
|
2140 |
+
|
2141 |
+
// Finally, we just prepend the head with the required http-equiv charset.
|
2142 |
+
if ($count < 1) {
|
2143 |
+
$html = '<head>' . self::HTTP_EQUIV_META_TAG . '</head>' . $html;
|
2144 |
+
}
|
2145 |
+
|
2146 |
+
return $html;
|
2147 |
+
}
|
2148 |
+
}
|
includes/vendor/tool/Dom/Document/Encoding.php
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Dom\Document;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Encoding constants that are used to control Dom\Document encoding.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface Encoding
|
11 |
+
{
|
12 |
+
|
13 |
+
/**
|
14 |
+
* AMP requires the HTML markup to be encoded in UTF-8.
|
15 |
+
*
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
const AMP = 'utf-8';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Encoding detection order in case we have to guess.
|
22 |
+
*
|
23 |
+
* This list of encoding detection order is just a wild guess and might need fine-tuning over time.
|
24 |
+
* If the charset was not provided explicitly, we can really only guess, as the detection can
|
25 |
+
* never be 100% accurate and reliable.
|
26 |
+
*
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
const DETECTION_ORDER = 'UTF-8, EUC-JP, eucJP-win, JIS, ISO-2022-JP, ISO-8859-15, ISO-8859-1, ASCII';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Associative array of encoding mappings.
|
33 |
+
*
|
34 |
+
* Translates HTML charsets into encodings PHP can understand.
|
35 |
+
*
|
36 |
+
* @var string[]
|
37 |
+
*/
|
38 |
+
const MAPPINGS = [
|
39 |
+
// Assume ISO-8859-1 for some charsets.
|
40 |
+
'latin-1' => 'ISO-8859-1',
|
41 |
+
// US-ASCII is one of the most popular ASCII names and used as HTML charset,
|
42 |
+
// but mb_list_encodings contains only "ASCII".
|
43 |
+
'us-ascii' => 'ascii',
|
44 |
+
];
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Encoding identifier to use for an unknown encoding.
|
48 |
+
*
|
49 |
+
* "auto" is recognized by mb_convert_encoding() as a special value.
|
50 |
+
*
|
51 |
+
* @var string
|
52 |
+
*/
|
53 |
+
const UNKNOWN = 'auto';
|
54 |
+
}
|
includes/vendor/tool/Dom/Document/Option.php
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Dom\Document;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Option constants that can be used to configure a Dom\Document instance.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface Option
|
11 |
+
{
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Option to configure the preferred amp-bind syntax.
|
15 |
+
*
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
const AMP_BIND_SYNTAX = 'amp_bind_syntax';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Option to provide the encoding of the document.
|
22 |
+
*
|
23 |
+
* @var string
|
24 |
+
*/
|
25 |
+
const ENCODING = 'encoding';
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Option to provide additional libxml flags to configure parsing of the document.
|
29 |
+
*
|
30 |
+
* @var string
|
31 |
+
*/
|
32 |
+
const LIBXML_FLAGS = 'libxml_flags';
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Associative array of known options and their respective default value.
|
36 |
+
*
|
37 |
+
* @var array
|
38 |
+
*/
|
39 |
+
const DEFAULTS = [
|
40 |
+
self::AMP_BIND_SYNTAX => self::AMP_BIND_SYNTAX_AUTO,
|
41 |
+
self::ENCODING => null,
|
42 |
+
self::LIBXML_FLAGS => 0,
|
43 |
+
];
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Possible value 'auto' for the 'amp_bind_syntax' option.
|
47 |
+
*
|
48 |
+
* @var string
|
49 |
+
*/
|
50 |
+
const AMP_BIND_SYNTAX_AUTO = 'auto';
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Possible value 'data_attribute' for the 'amp_bind_syntax' option.
|
54 |
+
*
|
55 |
+
* @var string
|
56 |
+
*/
|
57 |
+
const AMP_BIND_SYNTAX_DATA_ATTRIBUTE = 'data_attribute';
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Possible value 'square_brackets' for the 'amp_bind_syntax' option.
|
61 |
+
*
|
62 |
+
* @var string
|
63 |
+
*/
|
64 |
+
const AMP_BIND_SYNTAX_SQUARE_BRACKETS = 'square_brackets';
|
65 |
+
}
|
includes/vendor/tool/Dom/Element.php
ADDED
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Dom;
|
4 |
+
|
5 |
+
use AmpProject\Attribute;
|
6 |
+
use AmpProject\Exception\MaxCssByteCountExceeded;
|
7 |
+
use AmpProject\Optimizer\CssRule;
|
8 |
+
use DOMAttr;
|
9 |
+
use DOMElement;
|
10 |
+
use DOMException;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Abstract away some convenience logic for handling DOMElement objects.
|
14 |
+
*
|
15 |
+
* @property Document $ownerDocument The ownerDocument for these elements should always be a Dom\Document.
|
16 |
+
* @property int $inlineStyleByteCount Number of bytes that are consumed by the inline style attribute.
|
17 |
+
*
|
18 |
+
* @package ampproject/amp-toolbox
|
19 |
+
*/
|
20 |
+
final class Element extends DOMElement
|
21 |
+
{
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Regular expression pattern to match events and actions within an 'on' attribute.
|
25 |
+
*
|
26 |
+
* @var string
|
27 |
+
*/
|
28 |
+
const AMP_EVENT_ACTIONS_REGEX_PATTERN = '/((?<event>[^:;]+):(?<actions>(?:[^;,\(]+(?:\([^\)]+\))?,?)+))+?/';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Regular expression pattern to match individual actions within an event.
|
32 |
+
*
|
33 |
+
* @var string
|
34 |
+
*/
|
35 |
+
const AMP_ACTION_REGEX_PATTERN = '/(?<action>[^(),\s]+(?:\([^\)]+\))?)+/';
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Error message to use when the __get() is triggered for an unknown property.
|
39 |
+
*
|
40 |
+
* @var string
|
41 |
+
*/
|
42 |
+
const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Dom\\Element::';
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Add CSS styles to the element as an inline style attribute.
|
46 |
+
*
|
47 |
+
* @param string $style CSS style(s) to add to the inline style attribute.
|
48 |
+
* @return DOMAttr|false The new or modified DOMAttr or false if an error occurred.
|
49 |
+
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
50 |
+
*/
|
51 |
+
public function addInlineStyle($style)
|
52 |
+
{
|
53 |
+
$style = trim($style, CssRule::CSS_TRIM_CHARACTERS);
|
54 |
+
|
55 |
+
$existingStyle = (string)trim($this->getAttribute(Attribute::STYLE));
|
56 |
+
if (!empty($existingStyle)) {
|
57 |
+
$existingStyle = rtrim($existingStyle, ';') . ';';
|
58 |
+
}
|
59 |
+
|
60 |
+
$newStyle = $existingStyle . $style;
|
61 |
+
|
62 |
+
return $this->setAttribute(Attribute::STYLE, $newStyle);
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Sets or modifies an attribute.
|
67 |
+
*
|
68 |
+
* @link https://php.net/manual/en/domelement.setattribute.php
|
69 |
+
* @param string $name The name of the attribute.
|
70 |
+
* @param string $value The value of the attribute.
|
71 |
+
* @return DOMAttr|false The new or modified DOMAttr or false if an error occurred.
|
72 |
+
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
73 |
+
*/
|
74 |
+
public function setAttribute($name, $value)
|
75 |
+
{
|
76 |
+
if (
|
77 |
+
$name === Attribute::STYLE
|
78 |
+
&& $this->ownerDocument->isCssMaxByteCountEnforced()
|
79 |
+
) {
|
80 |
+
$newByteCount = strlen($value);
|
81 |
+
|
82 |
+
if ($this->ownerDocument->getRemainingCustomCssSpace() < ($newByteCount - $this->inlineStyleByteCount)) {
|
83 |
+
throw MaxCssByteCountExceeded::forInlineStyle($this, $value);
|
84 |
+
}
|
85 |
+
|
86 |
+
$this->ownerDocument->addInlineStyleByteCount($newByteCount - $this->inlineStyleByteCount);
|
87 |
+
|
88 |
+
$this->inlineStyleByteCount = $newByteCount;
|
89 |
+
return parent::setAttribute(Attribute::STYLE, $value);
|
90 |
+
}
|
91 |
+
|
92 |
+
return parent::setAttribute($name, $value);
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Adds a boolean attribute without value.
|
97 |
+
*
|
98 |
+
* @param string $name The name of the attribute.
|
99 |
+
* @return DOMAttr|false The new or modified DOMAttr or false if an error occurred.
|
100 |
+
* @throws MaxCssByteCountExceeded If the allowed max byte count is exceeded.
|
101 |
+
*/
|
102 |
+
public function addBooleanAttribute($name)
|
103 |
+
{
|
104 |
+
$attribute = new DOMAttr($name);
|
105 |
+
$result = $this->setAttributeNode($attribute);
|
106 |
+
|
107 |
+
if (!$result instanceof DOMAttr) {
|
108 |
+
return false;
|
109 |
+
}
|
110 |
+
|
111 |
+
return $result;
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Copy one or more attributes from this element to another element.
|
116 |
+
*
|
117 |
+
* @param array|string $attributes Attribute name or array of attribute names to copy.
|
118 |
+
* @param Element $target Target Dom\Element to copy the attributes to.
|
119 |
+
* @param string $defaultSeparator Default separator to use for multiple values if the attribute is not known.
|
120 |
+
*/
|
121 |
+
public function copyAttributes($attributes, Element $target, $defaultSeparator = ',')
|
122 |
+
{
|
123 |
+
foreach ((array) $attributes as $attribute) {
|
124 |
+
if ($this->hasAttribute($attribute)) {
|
125 |
+
$values = $this->getAttribute($attribute);
|
126 |
+
if ($target->hasAttribute($attribute)) {
|
127 |
+
switch ($attribute) {
|
128 |
+
case Attribute::ON:
|
129 |
+
$values = self::mergeAmpActions($target->getAttribute($attribute), $values);
|
130 |
+
break;
|
131 |
+
case Attribute::CLASS_:
|
132 |
+
$values = $target->getAttribute($attribute) . ' ' . $values;
|
133 |
+
break;
|
134 |
+
default:
|
135 |
+
$values = $target->getAttribute($attribute) . $defaultSeparator . $values;
|
136 |
+
}
|
137 |
+
}
|
138 |
+
$target->setAttribute($attribute, $values);
|
139 |
+
}
|
140 |
+
}
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Register an AMP action to an event.
|
145 |
+
*
|
146 |
+
* If the element already contains one or more events or actions, the method
|
147 |
+
* will assemble them in a smart way.
|
148 |
+
*
|
149 |
+
* @param string $event Event to trigger the action on.
|
150 |
+
* @param string $action Action to add.
|
151 |
+
*/
|
152 |
+
public function addAmpAction($event, $action)
|
153 |
+
{
|
154 |
+
$eventActionString = "{$event}:{$action}";
|
155 |
+
|
156 |
+
if (! $this->hasAttribute(Attribute::ON)) {
|
157 |
+
// There's no "on" attribute yet, so just add it and be done.
|
158 |
+
$this->setAttribute(Attribute::ON, $eventActionString);
|
159 |
+
return;
|
160 |
+
}
|
161 |
+
|
162 |
+
$this->setAttribute(
|
163 |
+
Attribute::ON,
|
164 |
+
self::mergeAmpActions(
|
165 |
+
$this->getAttribute(Attribute::ON),
|
166 |
+
$eventActionString
|
167 |
+
)
|
168 |
+
);
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Merge two sets of AMP events & actions.
|
173 |
+
*
|
174 |
+
* @param string $first First event/action string.
|
175 |
+
* @param string $second First event/action string.
|
176 |
+
* @return string Merged event/action string.
|
177 |
+
*/
|
178 |
+
public static function mergeAmpActions($first, $second)
|
179 |
+
{
|
180 |
+
$events = [];
|
181 |
+
foreach ([$first, $second] as $eventActionString) {
|
182 |
+
$matches = [];
|
183 |
+
$results = preg_match_all(self::AMP_EVENT_ACTIONS_REGEX_PATTERN, $eventActionString, $matches);
|
184 |
+
|
185 |
+
if (! $results || ! isset($matches['event'])) {
|
186 |
+
continue;
|
187 |
+
}
|
188 |
+
|
189 |
+
foreach ($matches['event'] as $index => $event) {
|
190 |
+
$events[$event][] = $matches['actions'][ $index ];
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
$valueStrings = [];
|
195 |
+
foreach ($events as $event => $actionStringsArray) {
|
196 |
+
$actionsArray = [];
|
197 |
+
array_walk(
|
198 |
+
$actionStringsArray,
|
199 |
+
static function ($actions) use (&$actionsArray) {
|
200 |
+
$matches = [];
|
201 |
+
$results = preg_match_all(self::AMP_ACTION_REGEX_PATTERN, $actions, $matches);
|
202 |
+
|
203 |
+
if (! $results || ! isset($matches['action'])) {
|
204 |
+
$actionsArray[] = $actions;
|
205 |
+
return;
|
206 |
+
}
|
207 |
+
|
208 |
+
$actionsArray = array_merge($actionsArray, $matches['action']);
|
209 |
+
}
|
210 |
+
);
|
211 |
+
|
212 |
+
$actions = implode(',', array_unique(array_filter($actionsArray)));
|
213 |
+
$valueStrings[] = "{$event}:{$actions}";
|
214 |
+
}
|
215 |
+
|
216 |
+
return implode(';', $valueStrings);
|
217 |
+
}
|
218 |
+
|
219 |
+
/**
|
220 |
+
* Extract this element's HTML attributes and return as an associative array.
|
221 |
+
*
|
222 |
+
* @return string[] The attributes for the passed node, or an empty array if it has no attributes.
|
223 |
+
*/
|
224 |
+
public function getAttributesAsAssocArray()
|
225 |
+
{
|
226 |
+
$attributes = [];
|
227 |
+
if (! $this->hasAttributes()) {
|
228 |
+
return $attributes;
|
229 |
+
}
|
230 |
+
|
231 |
+
foreach ($this->attributes as $attribute) {
|
232 |
+
$attributes[ $attribute->nodeName ] = $attribute->nodeValue;
|
233 |
+
}
|
234 |
+
|
235 |
+
return $attributes;
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Add one or more HTML element attributes to this element.
|
240 |
+
*
|
241 |
+
* @param string[] $attributes One or more attributes for the node's HTML element.
|
242 |
+
*/
|
243 |
+
public function setAttributes($attributes)
|
244 |
+
{
|
245 |
+
foreach ($attributes as $name => $value) {
|
246 |
+
try {
|
247 |
+
$this->setAttribute($name, $value);
|
248 |
+
} catch (DOMException $e) {
|
249 |
+
/*
|
250 |
+
* Catch a "Invalid Character Error" when libxml is able to parse attributes with invalid characters,
|
251 |
+
* but it throws error when attempting to set them via DOM methods. For example, '...this' can be parsed
|
252 |
+
* as an attribute but it will throw an exception when attempting to setAttribute().
|
253 |
+
*/
|
254 |
+
continue;
|
255 |
+
}
|
256 |
+
}
|
257 |
+
}
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Magic getter to implement lazily-created, cached properties for the element.
|
261 |
+
*
|
262 |
+
* @param string $name Name of the property to get.
|
263 |
+
* @return mixed Value of the property, or null if unknown property was requested.
|
264 |
+
*/
|
265 |
+
public function __get($name)
|
266 |
+
{
|
267 |
+
switch ($name) {
|
268 |
+
case 'inlineStyleByteCount':
|
269 |
+
if (!isset($this->inlineStyleByteCount)) {
|
270 |
+
$this->inlineStyleByteCount = strlen((string)$this->getAttribute(Attribute::STYLE));
|
271 |
+
}
|
272 |
+
|
273 |
+
return $this->inlineStyleByteCount;
|
274 |
+
}
|
275 |
+
|
276 |
+
// Mimic regular PHP behavior for missing notices.
|
277 |
+
trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, E_USER_NOTICE);
|
278 |
+
|
279 |
+
return null;
|
280 |
+
}
|
281 |
+
}
|
includes/vendor/tool/Dom/ElementDump.php
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Dom;
|
4 |
+
|
5 |
+
use DOMAttr;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Dump an element with its attributes.
|
9 |
+
*
|
10 |
+
* This is meant for quick identification of an element and does not dump the child element or other inner content
|
11 |
+
* from that element.
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class ElementDump
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Element to dump.
|
20 |
+
*
|
21 |
+
* @var Element
|
22 |
+
*/
|
23 |
+
private $element;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Maximum length to truncate attributes and textContent to.
|
27 |
+
*
|
28 |
+
* Defaults to 120.
|
29 |
+
*
|
30 |
+
* @var int
|
31 |
+
*/
|
32 |
+
private $truncate;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Instantiate an ElementDump object.
|
36 |
+
*
|
37 |
+
* The object is meant to be cast to a string to do its magic.
|
38 |
+
*
|
39 |
+
* @param Element $element Element to dump.
|
40 |
+
* @param int $truncate Optional. Maximum length to truncate attributes and textContent to. Defaults to 120.
|
41 |
+
*/
|
42 |
+
public function __construct(Element $element, $truncate = 120)
|
43 |
+
{
|
44 |
+
$this->element = $element;
|
45 |
+
$this->truncate = $truncate;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Dump the provided element into a string.
|
50 |
+
*
|
51 |
+
* @return string Dump of the element.
|
52 |
+
*/
|
53 |
+
public function __toString()
|
54 |
+
{
|
55 |
+
$attributes = $this->maybeTruncate(
|
56 |
+
array_reduce(
|
57 |
+
iterator_to_array($this->element->attributes, true),
|
58 |
+
static function ($text, DOMAttr $attribute) {
|
59 |
+
return $text . " {$attribute->nodeName}=\"{$attribute->value}\"";
|
60 |
+
},
|
61 |
+
''
|
62 |
+
)
|
63 |
+
);
|
64 |
+
|
65 |
+
$textContent = $this->maybeTruncate($this->element->textContent);
|
66 |
+
|
67 |
+
return sprintf(
|
68 |
+
'<%1$s%2$s>%3$s</%1$s>',
|
69 |
+
$this->element->tagName,
|
70 |
+
$attributes,
|
71 |
+
$textContent
|
72 |
+
);
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Truncate the provided text if needed.
|
77 |
+
*
|
78 |
+
* @param string $text Text to truncate.
|
79 |
+
* @return string Potentially truncated text.
|
80 |
+
*/
|
81 |
+
private function maybeTruncate($text)
|
82 |
+
{
|
83 |
+
if ($this->truncate <= 0) {
|
84 |
+
return $text;
|
85 |
+
}
|
86 |
+
|
87 |
+
// Not checking for both mb_* functions, as we assume that if one mb_* function exists, the extension is
|
88 |
+
// available and all mb_* functions will exist.
|
89 |
+
if (function_exists('mb_strlen')) {
|
90 |
+
if (mb_strlen($text) > $this->truncate) {
|
91 |
+
return mb_substr($text, 0, $this->truncate - 1) . '…';
|
92 |
+
}
|
93 |
+
} elseif (strlen($text) > $this->truncate) {
|
94 |
+
// Fall back to regular string functions, knowing that this might potentially miscalculate and/or split
|
95 |
+
// multi-byte chars. We do so as we assume a site with special character encoding needs will have the
|
96 |
+
// multi-byte extension loaded anyway.
|
97 |
+
return substr($text, 0, $this->truncate - 1) . '…';
|
98 |
+
}
|
99 |
+
|
100 |
+
return $text;
|
101 |
+
}
|
102 |
+
}
|
includes/vendor/tool/Dom/NodeWalker.php
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Dom;
|
4 |
+
|
5 |
+
use DOMNode;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Walk a hierarchical tree of nodes in a sequential manner.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class NodeWalker
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Depth-first walk through the DOM tree.
|
17 |
+
*
|
18 |
+
* @param DOMNode $node Node to start walking from.
|
19 |
+
* @return DOMNode|null Next node, or null if none found.
|
20 |
+
*/
|
21 |
+
public static function nextNode(DOMNode $node)
|
22 |
+
{
|
23 |
+
// Walk downwards if there are children.
|
24 |
+
if ($node->firstChild) {
|
25 |
+
return $node->firstChild;
|
26 |
+
}
|
27 |
+
|
28 |
+
// Return direct sibling or walk upwards until we find a node with a sibling.
|
29 |
+
while ($node) {
|
30 |
+
if ($node->nextSibling) {
|
31 |
+
return $node->nextSibling;
|
32 |
+
}
|
33 |
+
|
34 |
+
$node = $node->parentNode;
|
35 |
+
}
|
36 |
+
|
37 |
+
// Out of nodes, so we're done.
|
38 |
+
return null;
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Skip the subtree that is descending from the provided node.
|
43 |
+
*
|
44 |
+
* @param DOMNode $node Node to skip the subtree of.
|
45 |
+
* @return DOMNode|null The appropriate "next" node that will skip the current subtree, null if none found.
|
46 |
+
*/
|
47 |
+
public static function skipNodeAndChildren(DOMNode $node)
|
48 |
+
{
|
49 |
+
if ($node->nextSibling) {
|
50 |
+
return $node->nextSibling;
|
51 |
+
}
|
52 |
+
|
53 |
+
if ($node->parentNode === null) {
|
54 |
+
return null;
|
55 |
+
}
|
56 |
+
|
57 |
+
return self::skipNodeAndChildren($node->parentNode);
|
58 |
+
}
|
59 |
+
}
|
includes/vendor/tool/Exception/AmpCliException.php
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Marker interface to distinguish exceptions for the CLI.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface AmpCliException extends AmpException
|
11 |
+
{
|
12 |
+
|
13 |
+
/**
|
14 |
+
* No error code specified.
|
15 |
+
*
|
16 |
+
* @var int
|
17 |
+
*/
|
18 |
+
const E_ANY = -1;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Command is not valid.
|
22 |
+
*
|
23 |
+
* @var int
|
24 |
+
*/
|
25 |
+
const E_INVALID_CMD = 6;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Could not read or parse arguments.
|
29 |
+
*
|
30 |
+
* @var int
|
31 |
+
*/
|
32 |
+
const E_ARG_READ = 5;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Option requires an argument.
|
36 |
+
*
|
37 |
+
* @var int
|
38 |
+
*/
|
39 |
+
const E_OPT_ABIGUOUS = 4;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Argument not allowed for option.
|
43 |
+
*
|
44 |
+
* @var int
|
45 |
+
*/
|
46 |
+
const E_OPT_ARG_DENIED = 3;
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Option ambiguous.
|
50 |
+
*
|
51 |
+
* @var int
|
52 |
+
*/
|
53 |
+
const E_OPT_ARG_REQUIRED = 2;
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Option unknown.
|
57 |
+
*
|
58 |
+
* @var int
|
59 |
+
*/
|
60 |
+
const E_UNKNOWN_OPT = 1;
|
61 |
+
}
|
includes/vendor/tool/Exception/AmpException.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Marker interface to enable consumers to catch all exceptions for this particular library.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface AmpException
|
11 |
+
{
|
12 |
+
|
13 |
+
}
|
includes/vendor/tool/Exception/Cli/InvalidArgument.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use InvalidArgumentException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid argument was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class InvalidArgument extends InvalidArgumentException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate an InvalidArgument exception when arguments could not be read.
|
18 |
+
*
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function forUnreadableArguments()
|
22 |
+
{
|
23 |
+
$message = 'Could not read command arguments. Is register_argc_argv off?';
|
24 |
+
|
25 |
+
return new self($message, AmpCliException::E_ARG_READ);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Instantiate an InvalidArgument exception when a short option is too long.
|
30 |
+
*
|
31 |
+
* @return self
|
32 |
+
*/
|
33 |
+
public static function forMultiCharacterShortOption()
|
34 |
+
{
|
35 |
+
$message = 'Short options should be exactly one ASCII character.';
|
36 |
+
|
37 |
+
return new self($message, AmpCliException::E_OPT_ARG_DENIED);
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Instantiate an InvalidArgument exception for file that could not be read.
|
42 |
+
*
|
43 |
+
* @param string $file File that could not be read.
|
44 |
+
* @return self
|
45 |
+
*/
|
46 |
+
public static function forUnreadableFile($file)
|
47 |
+
{
|
48 |
+
$message = "Could not read file: '{$file}'.";
|
49 |
+
|
50 |
+
return new self($message, AmpCliException::E_OPT_ARG_DENIED);
|
51 |
+
}
|
52 |
+
}
|
includes/vendor/tool/Exception/Cli/InvalidColor.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use OutOfBoundsException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid color was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class InvalidColor extends OutOfBoundsException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate an InvalidColor exception for an unknown color that was passed to the CLI.
|
18 |
+
*
|
19 |
+
* @param string $color Unknown color that was passed to the CLI.
|
20 |
+
* @return self
|
21 |
+
*/
|
22 |
+
public static function forUnknownColor($color)
|
23 |
+
{
|
24 |
+
$message = "Unknown color: '{$color}'.";
|
25 |
+
|
26 |
+
return new self($message, AmpCliException::E_ANY);
|
27 |
+
}
|
28 |
+
}
|
includes/vendor/tool/Exception/Cli/InvalidColumnFormat.php
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use OutOfBoundsException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid option was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class InvalidColumnFormat extends OutOfBoundsException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate an InvalidColumn exception for multiple fluid columns.
|
18 |
+
*
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function forMultipleFluidColumns()
|
22 |
+
{
|
23 |
+
$message = 'Only one fluid column allowed.';
|
24 |
+
|
25 |
+
return new self($message, AmpCliException::E_ANY);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Instantiate an InvalidColumn exception for an unknown column format.
|
30 |
+
*
|
31 |
+
* @param string $column Unknown column format.
|
32 |
+
* @return self
|
33 |
+
*/
|
34 |
+
public static function forUnknownColumnFormat($column)
|
35 |
+
{
|
36 |
+
$message = "Unknown column format: '{$column}'.";
|
37 |
+
|
38 |
+
return new self($message, AmpCliException::E_ANY);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Instantiate an InvalidColumn exception for an unknown column format.
|
43 |
+
*
|
44 |
+
* @return self
|
45 |
+
*/
|
46 |
+
public static function forExceededMaxWidth()
|
47 |
+
{
|
48 |
+
$message = 'Total of requested column widths exceeds available space.';
|
49 |
+
|
50 |
+
return new self($message, AmpCliException::E_ANY);
|
51 |
+
}
|
52 |
+
}
|
includes/vendor/tool/Exception/Cli/InvalidCommand.php
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use InvalidArgumentException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid command was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class InvalidCommand extends InvalidArgumentException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate an InvalidCommand exception for an unregistered command that is being referenced.
|
18 |
+
*
|
19 |
+
* @param string $command Unregistered command that is being referenced.
|
20 |
+
* @return self
|
21 |
+
*/
|
22 |
+
public static function forUnregisteredCommand($command)
|
23 |
+
{
|
24 |
+
$message = "Command not registered: '{$command}'.";
|
25 |
+
|
26 |
+
return new self($message, AmpCliException::E_INVALID_CMD);
|
27 |
+
}
|
28 |
+
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Instantiate an InvalidCommand exception for an already registered command that is to be re-registered.
|
32 |
+
*
|
33 |
+
* @param string $command Already registered command that is supposed to be registered.
|
34 |
+
* @return self
|
35 |
+
*/
|
36 |
+
public static function forAlreadyRegisteredCommand($command)
|
37 |
+
{
|
38 |
+
$message = "Command already registered: '{$command}'.";
|
39 |
+
|
40 |
+
return new self($message, AmpCliException::E_INVALID_CMD);
|
41 |
+
}
|
42 |
+
}
|
includes/vendor/tool/Exception/Cli/InvalidOption.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use OutOfBoundsException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid option was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class InvalidOption extends OutOfBoundsException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate an InvalidOption exception for an unknown option that was passed to the CLI.
|
18 |
+
*
|
19 |
+
* @param string $option Unknown option that was passed to the CLI.
|
20 |
+
* @return self
|
21 |
+
*/
|
22 |
+
public static function forUnknownOption($option)
|
23 |
+
{
|
24 |
+
$message = "Unknown option: '{$option}'.";
|
25 |
+
|
26 |
+
return new self($message, AmpCliException::E_UNKNOWN_OPT);
|
27 |
+
}
|
28 |
+
}
|
includes/vendor/tool/Exception/Cli/InvalidSapi.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use OutOfBoundsException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid option was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class InvalidSapi extends OutOfBoundsException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate an InvalidSapi exception for a SAPI other than 'cli'.
|
18 |
+
*
|
19 |
+
* @param string $sapi Invalid SAPI that was detected.
|
20 |
+
* @return self
|
21 |
+
*/
|
22 |
+
public static function forSapi($sapi)
|
23 |
+
{
|
24 |
+
$message = "This has to be run from the command line (detected SAPI '{$sapi}').";
|
25 |
+
|
26 |
+
return new self($message, AmpCliException::E_ANY);
|
27 |
+
}
|
28 |
+
}
|
includes/vendor/tool/Exception/Cli/MissingArgument.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception\Cli;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpCliException;
|
6 |
+
use DomainException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when an invalid argument was provided to the CLI.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class MissingArgument extends DomainException implements AmpCliException
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Instantiate a MissingArgument exception for an argument that is required but missing.
|
18 |
+
*
|
19 |
+
* @param string $option Option for which the argument is missing.
|
20 |
+
*
|
21 |
+
* @return self
|
22 |
+
*/
|
23 |
+
public static function forNoArgument($option)
|
24 |
+
{
|
25 |
+
$message = "Option '{$option}' requires an argument.";
|
26 |
+
|
27 |
+
return new self($message, AmpCliException::E_OPT_ARG_REQUIRED);
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Instantiate a MissingArgument exception for when too few arguments were passed.
|
32 |
+
*
|
33 |
+
* @return self
|
34 |
+
*/
|
35 |
+
public static function forNotEnoughArguments()
|
36 |
+
{
|
37 |
+
$message = 'Not enough arguments provided.';
|
38 |
+
|
39 |
+
return new self($message, AmpCliException::E_OPT_ARG_REQUIRED);
|
40 |
+
}
|
41 |
+
}
|
includes/vendor/tool/Exception/FailedRemoteRequest.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Marker interface to enable consumers to catch all exceptions for failed remote requests.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface FailedRemoteRequest extends AmpException
|
11 |
+
{
|
12 |
+
|
13 |
+
}
|
includes/vendor/tool/Exception/FailedToGetCachedResponse.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
use RuntimeException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when a cached remote response could not be retrieved.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class FailedToGetCachedResponse extends RuntimeException implements FailedRemoteRequest
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate a FailedToGetCachedResponse exception for a URL if the cached response data could not be
|
17 |
+
* retrieved.
|
18 |
+
*
|
19 |
+
* @param string $url URL that failed to be fetched.
|
20 |
+
* @return self
|
21 |
+
*/
|
22 |
+
public static function withUrl($url)
|
23 |
+
{
|
24 |
+
$message = "Failed to retrieve the cached response for the URL '{$url}'.";
|
25 |
+
|
26 |
+
return new self($message);
|
27 |
+
}
|
28 |
+
}
|
includes/vendor/tool/Exception/FailedToGetFromRemoteUrl.php
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
use Exception;
|
6 |
+
use RuntimeException;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Exception thrown when a remote request failed.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class FailedToGetFromRemoteUrl extends RuntimeException implements FailedRemoteRequest
|
14 |
+
{
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Status code of the failed request.
|
18 |
+
*
|
19 |
+
* This is not always set.
|
20 |
+
*
|
21 |
+
* @var int|null
|
22 |
+
*/
|
23 |
+
private $statusCode;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Instantiate a FailedToGetFromRemoteUrl exception for a URL if an HTTP status code is available.
|
27 |
+
*
|
28 |
+
* @param string $url URL that failed to be fetched.
|
29 |
+
* @param int $status HTTP Status that was returned.
|
30 |
+
* @return self
|
31 |
+
*/
|
32 |
+
public static function withHttpStatus($url, $status)
|
33 |
+
{
|
34 |
+
$message = "Failed to fetch the contents from the URL '{$url}' as it returned HTTP status {$status}.";
|
35 |
+
|
36 |
+
$exception = new self($message);
|
37 |
+
$exception->statusCode = $status;
|
38 |
+
|
39 |
+
return $exception;
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Instantiate a FailedToGetFromRemoteUrl exception for a URL if an HTTP status code is not available.
|
44 |
+
*
|
45 |
+
* @param string $url URL that failed to be fetched.
|
46 |
+
* @return self
|
47 |
+
*/
|
48 |
+
public static function withoutHttpStatus($url)
|
49 |
+
{
|
50 |
+
$message = "Failed to fetch the contents from the URL '{$url}'.";
|
51 |
+
|
52 |
+
return new self($message);
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Instantiate a FailedToGetFromRemoteUrl exception for a URL if an exception was thrown.
|
57 |
+
*
|
58 |
+
* @param string $url URL that failed to be fetched.
|
59 |
+
* @param Exception $exception Exception that was thrown.
|
60 |
+
* @return self
|
61 |
+
*/
|
62 |
+
public static function withException($url, Exception $exception)
|
63 |
+
{
|
64 |
+
$message = "Failed to fetch the contents from the URL '{$url}': {$exception->getMessage()}.";
|
65 |
+
|
66 |
+
return new self($message, 0, $exception);
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Check whether the status code is set for this exception.
|
71 |
+
*
|
72 |
+
* @return bool
|
73 |
+
*/
|
74 |
+
public function hasStatusCode()
|
75 |
+
{
|
76 |
+
return isset($this->statusCode);
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Get the HTTP status code associated with this exception.
|
81 |
+
*
|
82 |
+
* Returns -1 if no status code was provided.
|
83 |
+
*
|
84 |
+
* @return int
|
85 |
+
*/
|
86 |
+
public function getStatusCode()
|
87 |
+
{
|
88 |
+
return $this->hasStatusCode() ? $this->statusCode : -1;
|
89 |
+
}
|
90 |
+
}
|
includes/vendor/tool/Exception/FailedToParseUrl.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
use InvalidArgumentException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when a URL could not be parsed.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class FailedToParseUrl extends InvalidArgumentException implements AmpException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate a FailedToParseUrl exception for a URL that could not be parsed.
|
17 |
+
*
|
18 |
+
* @param string $url URL that failed to be parsed.
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function forUrl($url)
|
22 |
+
{
|
23 |
+
$message = "Failed to parse the URL '{$url}'.";
|
24 |
+
|
25 |
+
return new self($message);
|
26 |
+
}
|
27 |
+
}
|
includes/vendor/tool/Exception/FailedToRetrieveRequiredDomElement.php
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
use LogicException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when a required DOM element could not be retrieved from the document.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class FailedToRetrieveRequiredDomElement extends LogicException implements AmpException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate a FailedToRetrieveRequiredDomElement exception for the <html> DOM element.
|
17 |
+
*
|
18 |
+
* @param mixed $retrievedElement What was returned when trying to retrieve the element.
|
19 |
+
* @return FailedToRetrieveRequiredDomElement
|
20 |
+
*/
|
21 |
+
public static function forHtmlElement($retrievedElement)
|
22 |
+
{
|
23 |
+
$type = is_object($retrievedElement) ? get_class($retrievedElement) : gettype($retrievedElement);
|
24 |
+
$message = "Failed to retrieve required <html> DOM element, got '{$type}' instead.";
|
25 |
+
|
26 |
+
return new self($message);
|
27 |
+
}
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Instantiate a FailedToRetrieveRequiredDomElement exception for the <head> DOM element.
|
31 |
+
*
|
32 |
+
* @param mixed $retrievedElement What was returned when trying to retrieve the element.
|
33 |
+
* @return FailedToRetrieveRequiredDomElement
|
34 |
+
*/
|
35 |
+
public static function forHeadElement($retrievedElement)
|
36 |
+
{
|
37 |
+
$type = is_object($retrievedElement) ? get_class($retrievedElement) : gettype($retrievedElement);
|
38 |
+
$message = "Failed to retrieve required <head> DOM element, got '{$type}' instead.";
|
39 |
+
|
40 |
+
return new self($message);
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Instantiate a FailedToRetrieveRequiredDomElement exception for the <body> DOM element.
|
45 |
+
*
|
46 |
+
* @param mixed $retrievedElement What was returned when trying to retrieve the element.
|
47 |
+
* @return FailedToRetrieveRequiredDomElement
|
48 |
+
*/
|
49 |
+
public static function forBodyElement($retrievedElement)
|
50 |
+
{
|
51 |
+
$type = is_object($retrievedElement) ? get_class($retrievedElement) : gettype($retrievedElement);
|
52 |
+
$message = "Failed to retrieve required <body> DOM element, got '{$type}' instead.";
|
53 |
+
|
54 |
+
return new self($message);
|
55 |
+
}
|
56 |
+
}
|
includes/vendor/tool/Exception/MaxCssByteCountExceeded.php
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Exception;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
use AmpProject\Dom\ElementDump;
|
7 |
+
use OverflowException;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Exception thrown when the maximum CSS byte count has been exceeded.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class MaxCssByteCountExceeded extends OverflowException implements AmpException
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Instantiate a MaxCssByteCountExceeded exception for an inline style that exceeds the maximum byte count.
|
19 |
+
*
|
20 |
+
* @param Element $element Element that was supposed to receive the inline style.
|
21 |
+
* @param string $style Inline style that was supposed to be added.
|
22 |
+
* @return self
|
23 |
+
*/
|
24 |
+
public static function forInlineStyle(Element $element, $style)
|
25 |
+
{
|
26 |
+
$message = "Maximum allowed CSS byte count exceeded for inline style '{$style}': " . new ElementDump($element);
|
27 |
+
|
28 |
+
return new self($message);
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Instantiate a MaxCssByteCountExceeded exception for an amp-custom style that exceeds the maximum byte count.
|
33 |
+
*
|
34 |
+
* @param string $style Amp-custom style that was supposed to be added.
|
35 |
+
* @return self
|
36 |
+
*/
|
37 |
+
public static function forAmpCustom($style)
|
38 |
+
{
|
39 |
+
$message = "Maximum allowed CSS byte count exceeded for amp-custom style '{$style}'";
|
40 |
+
|
41 |
+
return new self($message);
|
42 |
+
}
|
43 |
+
}
|
includes/vendor/tool/Extension.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface with constants for AMP extensions.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface Extension
|
11 |
+
{
|
12 |
+
|
13 |
+
const AD = 'amp-ad';
|
14 |
+
const ANALYTICS = 'amp-analytics';
|
15 |
+
const ANIM = 'amp-anim';
|
16 |
+
const AUDIO = 'amp-audio';
|
17 |
+
const BIND = 'amp-bind';
|
18 |
+
const BRIGHTCOVE = 'amp-brightcove';
|
19 |
+
const DAILYMOTION = 'amp-dailymotion';
|
20 |
+
const DELIGHT_PLAYER = 'amp-delight-player';
|
21 |
+
const DYNAMIC_CSS_CLASSES = 'amp-dynamic-css-classes';
|
22 |
+
const EXPERIMENT = 'amp-experiment';
|
23 |
+
const FACEBOOK = 'amp-facebook';
|
24 |
+
const GEO = 'amp-geo';
|
25 |
+
const GFYCAT = 'amp-gfycat';
|
26 |
+
const GOOGLE_DOCUMENT_EMBED = 'amp-google-document-embed';
|
27 |
+
const IFRAME = 'amp-iframe';
|
28 |
+
const IMG = 'amp-img';
|
29 |
+
const IMGUR = 'amp-imgur';
|
30 |
+
const INSTAGRAM = 'amp-instagram';
|
31 |
+
const MUSTACHE = 'amp-mustache';
|
32 |
+
const PINTEREST = 'amp-pinterest';
|
33 |
+
const PIXEL = 'amp-pixel';
|
34 |
+
const REDDIT = 'amp-reddit';
|
35 |
+
const SOCIAL_SHARE = 'amp-social-share';
|
36 |
+
const STORY = 'amp-story';
|
37 |
+
const TWITTER = 'amp-twitter';
|
38 |
+
const VIDEO = 'amp-video';
|
39 |
+
const VIDEO_IFRAME = 'amp-video-iframe';
|
40 |
+
const VIMEO = 'amp-vimeo';
|
41 |
+
const YOUTUBE = 'amp-youtube';
|
42 |
+
const WISTIA_PLAYER = 'amp-wistia-player';
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Prefix of an AMP extension.
|
46 |
+
*
|
47 |
+
* @var string.
|
48 |
+
*/
|
49 |
+
const PREFIX = 'amp-';
|
50 |
+
}
|
includes/vendor/tool/Layout.php
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface with constants for the different layouts.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface Layout
|
11 |
+
{
|
12 |
+
|
13 |
+
const NODISPLAY = 'nodisplay';
|
14 |
+
const FIXED = 'fixed';
|
15 |
+
const RESPONSIVE = 'responsive';
|
16 |
+
const FIXED_HEIGHT = 'fixed-height';
|
17 |
+
const FILL = 'fill';
|
18 |
+
const CONTAINER = 'container';
|
19 |
+
const FLEX_ITEM = 'flex-item';
|
20 |
+
const FLUID = 'fluid';
|
21 |
+
const INTRINSIC = 'intrinsic';
|
22 |
+
|
23 |
+
const FROM_SPEC = [
|
24 |
+
1 => self::NODISPLAY,
|
25 |
+
2 => self::FIXED,
|
26 |
+
3 => self::FIXED_HEIGHT,
|
27 |
+
4 => self::RESPONSIVE,
|
28 |
+
5 => self::CONTAINER,
|
29 |
+
6 => self::FILL,
|
30 |
+
7 => self::FLEX_ITEM,
|
31 |
+
8 => self::FLUID,
|
32 |
+
9 => self::INTRINSIC,
|
33 |
+
];
|
34 |
+
|
35 |
+
const TO_SPEC = [
|
36 |
+
self::NODISPLAY => 1,
|
37 |
+
self::FIXED => 2,
|
38 |
+
self::FIXED_HEIGHT => 3,
|
39 |
+
self::RESPONSIVE => 4,
|
40 |
+
self::CONTAINER => 5,
|
41 |
+
self::FILL => 6,
|
42 |
+
self::FLEX_ITEM => 7,
|
43 |
+
self::FLUID => 8,
|
44 |
+
self::INTRINSIC => 9,
|
45 |
+
];
|
46 |
+
|
47 |
+
const SIZE_DEFINED_LAYOUTS = [
|
48 |
+
self::FIXED,
|
49 |
+
self::FIXED_HEIGHT,
|
50 |
+
self::RESPONSIVE,
|
51 |
+
self::FILL,
|
52 |
+
self::FLEX_ITEM,
|
53 |
+
self::INTRINSIC,
|
54 |
+
];
|
55 |
+
}
|
includes/vendor/tool/LengthUnit.php
ADDED
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Unit of a length.
|
7 |
+
*
|
8 |
+
* This interface defines the available units that can be recognized in HTML and/or CSS dimensions.
|
9 |
+
*
|
10 |
+
* @see https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class LengthUnit
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Centimeters.
|
19 |
+
*
|
20 |
+
* 1cm = 96px/2.54.
|
21 |
+
*
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
const CM = 'cm';
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Millimeters.
|
28 |
+
*
|
29 |
+
* 1mm = 1/10th of 1cm.
|
30 |
+
*
|
31 |
+
* @var string
|
32 |
+
*/
|
33 |
+
const MM = 'mm';
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Quarter-millimeters.
|
37 |
+
*
|
38 |
+
* 1Q = 1/40th of 1cm.
|
39 |
+
*
|
40 |
+
* @var string
|
41 |
+
*/
|
42 |
+
const Q = 'q';
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Inches.
|
46 |
+
*
|
47 |
+
* 1in = 2.54cm = 96px.
|
48 |
+
*
|
49 |
+
* @var string
|
50 |
+
*/
|
51 |
+
const IN = 'in';
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Picas.
|
55 |
+
*
|
56 |
+
* 1pc = 1/6th of 1in.
|
57 |
+
*
|
58 |
+
* @var string
|
59 |
+
*/
|
60 |
+
const PC = 'pc';
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Points.
|
64 |
+
*
|
65 |
+
* 1pt = 1/72th of 1in.
|
66 |
+
*
|
67 |
+
* @var string
|
68 |
+
*/
|
69 |
+
const PT = 'pt';
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Pixels.
|
73 |
+
*
|
74 |
+
* 1px = 1/96th of 1in.
|
75 |
+
*
|
76 |
+
* @var string
|
77 |
+
*/
|
78 |
+
const PX = 'px';
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Font size of the parent, in the case of typographical properties like font-size, and font size of the element
|
82 |
+
* itself, in the case of other properties like width.
|
83 |
+
*
|
84 |
+
* @var string
|
85 |
+
*/
|
86 |
+
const EM = 'em';
|
87 |
+
|
88 |
+
/**
|
89 |
+
* x-height of the element's font.
|
90 |
+
*
|
91 |
+
* @var string
|
92 |
+
*/
|
93 |
+
const EX = 'ex';
|
94 |
+
|
95 |
+
/**
|
96 |
+
* The advance measure (width) of the glyph "0" of the element's font.
|
97 |
+
*
|
98 |
+
* @var string
|
99 |
+
*/
|
100 |
+
const CH = 'ch';
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Font size of the root element.
|
104 |
+
*
|
105 |
+
* @var string
|
106 |
+
*/
|
107 |
+
const REM = 'rem';
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Line height of the element.
|
111 |
+
*
|
112 |
+
* @var string
|
113 |
+
*/
|
114 |
+
const LH = 'lh';
|
115 |
+
|
116 |
+
/**
|
117 |
+
* 1% of the viewport's width.
|
118 |
+
*
|
119 |
+
* @var string
|
120 |
+
*/
|
121 |
+
const VW = 'vw';
|
122 |
+
|
123 |
+
/**
|
124 |
+
* 1% of the viewport's height.
|
125 |
+
*
|
126 |
+
* @var string
|
127 |
+
*/
|
128 |
+
const VH = 'vh';
|
129 |
+
|
130 |
+
/**
|
131 |
+
* 1% of the viewport's smaller dimension.
|
132 |
+
*
|
133 |
+
* @var string
|
134 |
+
*/
|
135 |
+
const VMIN = 'vmin';
|
136 |
+
|
137 |
+
/**
|
138 |
+
* 1% of the viewport's larger dimension.
|
139 |
+
*
|
140 |
+
* @var string
|
141 |
+
*/
|
142 |
+
const VMAX = 'vmax';
|
143 |
+
|
144 |
+
/**
|
145 |
+
* Set of known absolute units.
|
146 |
+
*
|
147 |
+
* @var string[]
|
148 |
+
*/
|
149 |
+
const ABSOLUTE_UNITS = [
|
150 |
+
self::CM,
|
151 |
+
self::MM,
|
152 |
+
self::Q,
|
153 |
+
self::IN,
|
154 |
+
self::PC,
|
155 |
+
self::PT,
|
156 |
+
self::PX,
|
157 |
+
];
|
158 |
+
|
159 |
+
/**
|
160 |
+
* Set of known relative units.
|
161 |
+
*
|
162 |
+
* @var string[]
|
163 |
+
*/
|
164 |
+
const RELATIVE_UNITS = [
|
165 |
+
self::EM,
|
166 |
+
self::EX,
|
167 |
+
self::CH,
|
168 |
+
self::REM,
|
169 |
+
self::LH,
|
170 |
+
self::VW,
|
171 |
+
self::VH,
|
172 |
+
self::VMIN,
|
173 |
+
self::VMAX,
|
174 |
+
];
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Pixels per inch resolution to use for conversions.
|
178 |
+
*
|
179 |
+
* @var int
|
180 |
+
*/
|
181 |
+
const PPI = 96;
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Centimeters per inch.
|
185 |
+
*
|
186 |
+
* @var float
|
187 |
+
*/
|
188 |
+
const CM_PER_IN = 2.54;
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Convert a unit-based length into a number of pixels.
|
192 |
+
*
|
193 |
+
* @param int|float $value Value to convert.
|
194 |
+
* @param string $unit Unit of the value.
|
195 |
+
* @return int|float|false Converted value, or false if it could not be converted.
|
196 |
+
*/
|
197 |
+
public static function convertIntoPixels($value, $unit)
|
198 |
+
{
|
199 |
+
if (0 === $value) {
|
200 |
+
return 0;
|
201 |
+
}
|
202 |
+
switch ($unit) {
|
203 |
+
case self::CM:
|
204 |
+
return $value * self::PPI / self::CM_PER_IN;
|
205 |
+
case self::MM:
|
206 |
+
return $value * self::PPI / self::CM_PER_IN / 10;
|
207 |
+
case self::Q:
|
208 |
+
return $value * self::PPI / self::CM_PER_IN / 40;
|
209 |
+
case self::IN:
|
210 |
+
return $value * self::PPI;
|
211 |
+
case self::PC:
|
212 |
+
return $value * self::PPI / 6;
|
213 |
+
case self::PT:
|
214 |
+
return $value * self::PPI / 72;
|
215 |
+
case self::PX:
|
216 |
+
// No conversion needed for pixel values.
|
217 |
+
return $value;
|
218 |
+
default:
|
219 |
+
return false;
|
220 |
+
}
|
221 |
+
}
|
222 |
+
}
|
includes/vendor/tool/Optimizer/Configuration.php
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\UnknownConfigurationKey;
|
6 |
+
use AmpProject\Optimizer\Transformer\AmpBoilerplate;
|
7 |
+
use AmpProject\Optimizer\Transformer\AmpBoilerplateErrorHandler;
|
8 |
+
use AmpProject\Optimizer\Transformer\AmpRuntimeCss;
|
9 |
+
use AmpProject\Optimizer\Transformer\OptimizeAmpBind;
|
10 |
+
use AmpProject\Optimizer\Transformer\PreloadHeroImage;
|
11 |
+
use AmpProject\Optimizer\Transformer\ReorderHead;
|
12 |
+
use AmpProject\Optimizer\Transformer\RewriteAmpUrls;
|
13 |
+
use AmpProject\Optimizer\Transformer\ServerSideRendering;
|
14 |
+
use AmpProject\Optimizer\Transformer\TransformedIdentifier;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Interface for a configuration object that validates and stores configuration settings.
|
18 |
+
*
|
19 |
+
* @package ampproject/amp-toolbox
|
20 |
+
*/
|
21 |
+
interface Configuration
|
22 |
+
{
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Key to use for managing the array of active transformers.
|
26 |
+
*
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
const KEY_TRANSFORMERS = 'transformers';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Array of known configuration keys and their default values.
|
33 |
+
*
|
34 |
+
* @var string[]
|
35 |
+
*/
|
36 |
+
const DEFAULTS = [
|
37 |
+
self::KEY_TRANSFORMERS => self::DEFAULT_TRANSFORMERS,
|
38 |
+
];
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Array of FQCNs of transformers to use for the default setup.
|
42 |
+
*
|
43 |
+
* @var string[]
|
44 |
+
*/
|
45 |
+
const DEFAULT_TRANSFORMERS = [
|
46 |
+
AmpBoilerplate::class,
|
47 |
+
ServerSideRendering::class,
|
48 |
+
AmpRuntimeCss::class,
|
49 |
+
AmpBoilerplateErrorHandler::class,
|
50 |
+
TransformedIdentifier::class,
|
51 |
+
PreloadHeroImage::class,
|
52 |
+
RewriteAmpUrls::class,
|
53 |
+
ReorderHead::class,
|
54 |
+
OptimizeAmpBind::class,
|
55 |
+
];
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Register a new configuration class to use for a given transformer.
|
59 |
+
*
|
60 |
+
* @param string $transformerClass FQCN of the transformer to register a configuration class for.
|
61 |
+
* @param string $configurationClass FQCN of the configuration to use.
|
62 |
+
*/
|
63 |
+
public function registerConfigurationClass($transformerClass, $configurationClass);
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Check whether the configuration has a given setting.
|
67 |
+
*
|
68 |
+
* @param string $key Configuration key to look for.
|
69 |
+
* @return bool Whether the requested configuration key was found or not.
|
70 |
+
*/
|
71 |
+
public function has($key);
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Get the value for a given key from the configuration.
|
75 |
+
*
|
76 |
+
* @param string $key Configuration key to get the value for.
|
77 |
+
* @return mixed Configuration value for the requested key.
|
78 |
+
* @throws UnknownConfigurationKey If the key was not found.
|
79 |
+
*/
|
80 |
+
public function get($key);
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Get the transformer-specific configuration for the requested transformer.
|
84 |
+
*
|
85 |
+
* @param string $transformer FQCN of the transformer to get the configuration for.
|
86 |
+
* @return TransformerConfiguration Transformer-specific configuration.
|
87 |
+
*/
|
88 |
+
public function getTransformerConfiguration($transformer);
|
89 |
+
}
|
includes/vendor/tool/Optimizer/Configuration/AmpRuntimeCssConfiguration.php
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Configuration;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationValue;
|
6 |
+
use AmpProject\RuntimeVersion;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Configuration for the AmpRuntimeCss transformer.
|
10 |
+
*
|
11 |
+
* @property bool $canary Whether to use the canary version or not. Defaults to false.
|
12 |
+
* @property string $styles Runtime styles to use.
|
13 |
+
* @property string $version Version string to use. Defaults to an empty string.
|
14 |
+
*
|
15 |
+
* @package ampproject/amp-toolbox
|
16 |
+
*/
|
17 |
+
final class AmpRuntimeCssConfiguration extends BaseTransformerConfiguration
|
18 |
+
{
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Configuration key that holds the flag for the canary version of the runtime style.
|
22 |
+
*
|
23 |
+
* @var string
|
24 |
+
*/
|
25 |
+
const CANARY = RuntimeVersion::OPTION_CANARY;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Configuration key that holds the actual runtime CSS styles to use.
|
29 |
+
*
|
30 |
+
* If the styles are not provided, the latest runtime styles are fetched from cdn.ampproject.org.
|
31 |
+
*
|
32 |
+
* @var string
|
33 |
+
*/
|
34 |
+
const STYLES = 'styles';
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Configuration key that holds the version number to use.
|
38 |
+
*
|
39 |
+
* If the version is not provided, the latest runtime version is fetched from cdn.ampproject.org.
|
40 |
+
*
|
41 |
+
* @var string
|
42 |
+
*/
|
43 |
+
const VERSION = 'version';
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Get the associative array of allowed keys and their respective default values.
|
47 |
+
*
|
48 |
+
* The array index is the key and the array value is the key's default value.
|
49 |
+
*
|
50 |
+
* @return array Associative array of allowed keys and their respective default values.
|
51 |
+
*/
|
52 |
+
protected function getAllowedKeys()
|
53 |
+
{
|
54 |
+
return [
|
55 |
+
self::CANARY => false,
|
56 |
+
self::STYLES => '',
|
57 |
+
self::VERSION => '',
|
58 |
+
];
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Validate an individual configuration entry.
|
63 |
+
*
|
64 |
+
* @param string $key Key of the configuration entry to validate.
|
65 |
+
* @param mixed $value Value of the configuration entry to validate.
|
66 |
+
* @return mixed Validated value.
|
67 |
+
*/
|
68 |
+
protected function validate($key, $value)
|
69 |
+
{
|
70 |
+
switch ($key) {
|
71 |
+
case self::CANARY:
|
72 |
+
if (! is_bool($value)) {
|
73 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
74 |
+
self::class,
|
75 |
+
self::CANARY,
|
76 |
+
'boolean',
|
77 |
+
is_object($value) ? get_class($value) : gettype($value)
|
78 |
+
);
|
79 |
+
}
|
80 |
+
break;
|
81 |
+
|
82 |
+
case self::STYLES:
|
83 |
+
if (! is_string($value)) {
|
84 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
85 |
+
self::class,
|
86 |
+
self::STYLES,
|
87 |
+
'string',
|
88 |
+
is_object($value) ? get_class($value) : gettype($value)
|
89 |
+
);
|
90 |
+
}
|
91 |
+
$value = trim($value);
|
92 |
+
break;
|
93 |
+
|
94 |
+
case self::VERSION:
|
95 |
+
if (! is_string($value)) {
|
96 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
97 |
+
self::class,
|
98 |
+
self::VERSION,
|
99 |
+
'string',
|
100 |
+
is_object($value) ? get_class($value) : gettype($value)
|
101 |
+
);
|
102 |
+
}
|
103 |
+
$value = trim($value);
|
104 |
+
break;
|
105 |
+
}
|
106 |
+
|
107 |
+
return $value;
|
108 |
+
}
|
109 |
+
}
|
includes/vendor/tool/Optimizer/Configuration/BaseTransformerConfiguration.php
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Configuration;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationKey;
|
6 |
+
use AmpProject\Optimizer\Exception\UnknownConfigurationKey;
|
7 |
+
use AmpProject\Optimizer\TransformerConfiguration;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Configuration for the AmpRuntimeCss transformer.
|
11 |
+
*
|
12 |
+
* @property string $version Version string to use. Defaults to an empty string.
|
13 |
+
* @property boolean $canary Whether to use the canary version or not. Defaults to false.
|
14 |
+
*
|
15 |
+
* @package ampproject/amp-toolbox
|
16 |
+
*/
|
17 |
+
abstract class BaseTransformerConfiguration implements TransformerConfiguration
|
18 |
+
{
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Associative array of allowed keys and their respective default values.
|
22 |
+
*
|
23 |
+
* @var array
|
24 |
+
*/
|
25 |
+
private $allowedKeys;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Instantiate an AmpRuntimeCssConfiguration object.
|
29 |
+
*
|
30 |
+
* @param array $configuration Optional. Associative array of configuration data. Defaults to an empty array.
|
31 |
+
*/
|
32 |
+
public function __construct($configuration = [])
|
33 |
+
{
|
34 |
+
$this->allowedKeys = $this->getAllowedKeys();
|
35 |
+
$configuration = array_merge($this->allowedKeys, $configuration);
|
36 |
+
|
37 |
+
foreach ($configuration as $key => $value) {
|
38 |
+
if (! array_key_exists($key, $this->allowedKeys)) {
|
39 |
+
throw InvalidConfigurationKey::fromTransformerKey(static::class, $key);
|
40 |
+
}
|
41 |
+
$this->$key = $this->validate($key, $value);
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Get the value for a given key.
|
47 |
+
*
|
48 |
+
* The key is assumed to exist and will throw an exception if it can't be retrieved.
|
49 |
+
* This means that all configuration entries should come with a default value.
|
50 |
+
*
|
51 |
+
* @param string $key Key of the configuration entry to retrieve.
|
52 |
+
* @return mixed Value stored under the given configuration key.
|
53 |
+
* @throws UnknownConfigurationKey If an unknown key was provided.
|
54 |
+
*/
|
55 |
+
public function get($key)
|
56 |
+
{
|
57 |
+
if (! array_key_exists($key, $this->allowedKeys)) {
|
58 |
+
throw UnknownConfigurationKey::fromTransformerKey(static::class, $key);
|
59 |
+
}
|
60 |
+
|
61 |
+
// At this point, the configuration should either have received this value or filled it with a default.
|
62 |
+
return $this->$key;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Get an array of configuration entries for this transformer configuration.
|
67 |
+
*
|
68 |
+
* @return array Associative array of configuration entries.
|
69 |
+
*/
|
70 |
+
public function toArray()
|
71 |
+
{
|
72 |
+
$configArray = [];
|
73 |
+
|
74 |
+
foreach (array_keys($this->allowedKeys) as $key) {
|
75 |
+
$configArray[$key] = $this->$key;
|
76 |
+
}
|
77 |
+
|
78 |
+
return $configArray;
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Get the associative array of allowed keys and their respective default values.
|
83 |
+
*
|
84 |
+
* The array index is the key and the array value is the key's default value.
|
85 |
+
*
|
86 |
+
* @return array Associative array of allowed keys and their respective default values.
|
87 |
+
*/
|
88 |
+
abstract protected function getAllowedKeys();
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Validate an individual configuration entry.
|
92 |
+
*
|
93 |
+
* @param string $key Key of the configuration entry to validate.
|
94 |
+
* @param mixed $value Value of the configuration entry to validate.
|
95 |
+
* @return mixed Validated value.
|
96 |
+
*/
|
97 |
+
abstract protected function validate($key, $value);
|
98 |
+
}
|
includes/vendor/tool/Optimizer/Configuration/OptimizeAmpBindConfiguration.php
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Configuration;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationValue;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Configuration for the OptimizeAmpBind transformer.
|
9 |
+
*
|
10 |
+
* @property bool $enabled Whether the amp-bind optimizer is enabled.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class OptimizeAmpBindConfiguration extends BaseTransformerConfiguration
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Whether the amp-bind optimizer is enabled.
|
19 |
+
*
|
20 |
+
* @var bool
|
21 |
+
*/
|
22 |
+
const ENABLED = 'enabled';
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Get the associative array of allowed keys and their respective default values.
|
26 |
+
*
|
27 |
+
* The array index is the key and the array value is the key's default value.
|
28 |
+
*
|
29 |
+
* @return array Associative array of allowed keys and their respective default values.
|
30 |
+
*/
|
31 |
+
protected function getAllowedKeys()
|
32 |
+
{
|
33 |
+
return [
|
34 |
+
self::ENABLED => true,
|
35 |
+
];
|
36 |
+
}
|
37 |
+
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Validate an individual configuration entry.
|
41 |
+
*
|
42 |
+
* @param string $key Key of the configuration entry to validate.
|
43 |
+
* @param mixed $value Value of the configuration entry to validate.
|
44 |
+
* @return mixed Validated value.
|
45 |
+
*/
|
46 |
+
protected function validate($key, $value)
|
47 |
+
{
|
48 |
+
switch ($key) {
|
49 |
+
case self::ENABLED:
|
50 |
+
if (! is_bool($value)) {
|
51 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
52 |
+
self::class,
|
53 |
+
self::ENABLED,
|
54 |
+
'boolean',
|
55 |
+
is_object($value) ? get_class($value) : gettype($value)
|
56 |
+
);
|
57 |
+
}
|
58 |
+
break;
|
59 |
+
}
|
60 |
+
|
61 |
+
return $value;
|
62 |
+
}
|
63 |
+
}
|
includes/vendor/tool/Optimizer/Configuration/PreloadHeroImageConfiguration.php
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Configuration;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationValue;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Configuration for the PreloadHeroImage transformer.
|
9 |
+
*
|
10 |
+
* @property string $inlineStyleBackupAttribute Name of the attribute that is used to store inline styles that were
|
11 |
+
* moved to <style amp-custom>
|
12 |
+
* @property bool $preloadHeroImage Whether to preload hero images. Defaults to true.
|
13 |
+
* @property bool $preloadSrcset Whether to enable preloading of images with a srcset attribute. Defaults
|
14 |
+
* to false.
|
15 |
+
*
|
16 |
+
* @package ampproject/amp-toolbox
|
17 |
+
*/
|
18 |
+
final class PreloadHeroImageConfiguration extends BaseTransformerConfiguration
|
19 |
+
{
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Configuration key that holds the attribute that is used to store inline styles that
|
23 |
+
* were moved to <style amp-custom>.
|
24 |
+
*
|
25 |
+
* An empty string signifies that no backup is available.
|
26 |
+
*
|
27 |
+
* @var string.
|
28 |
+
*/
|
29 |
+
const INLINE_STYLE_BACKUP_ATTRIBUTE = 'inlineStyleBackupAttribute';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Configuration key that holds the switch to disable preloading of hero images.
|
33 |
+
*
|
34 |
+
* @var string
|
35 |
+
*/
|
36 |
+
const PRELOAD_HERO_IMAGE = 'preloadHeroImage';
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Configuration key that holds the switch to enable preloading of images with a srcset attribute.
|
40 |
+
*
|
41 |
+
* @var string
|
42 |
+
*/
|
43 |
+
const PRELOAD_SRCSET = 'preloadSrcset';
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Get the associative array of allowed keys and their respective default values.
|
47 |
+
*
|
48 |
+
* The array index is the key and the array value is the key's default value.
|
49 |
+
*
|
50 |
+
* @return array Associative array of allowed keys and their respective default values.
|
51 |
+
*/
|
52 |
+
protected function getAllowedKeys()
|
53 |
+
{
|
54 |
+
return [
|
55 |
+
self::INLINE_STYLE_BACKUP_ATTRIBUTE => '',
|
56 |
+
self::PRELOAD_HERO_IMAGE => true,
|
57 |
+
self::PRELOAD_SRCSET => false,
|
58 |
+
];
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Validate an individual configuration entry.
|
63 |
+
*
|
64 |
+
* @param string $key Key of the configuration entry to validate.
|
65 |
+
* @param mixed $value Value of the configuration entry to validate.
|
66 |
+
* @return mixed Validated value.
|
67 |
+
*/
|
68 |
+
protected function validate($key, $value)
|
69 |
+
{
|
70 |
+
switch ($key) {
|
71 |
+
case self::INLINE_STYLE_BACKUP_ATTRIBUTE:
|
72 |
+
if (! is_string($value)) {
|
73 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
74 |
+
self::class,
|
75 |
+
self::INLINE_STYLE_BACKUP_ATTRIBUTE,
|
76 |
+
'string',
|
77 |
+
is_object($value) ? get_class($value) : gettype($value)
|
78 |
+
);
|
79 |
+
}
|
80 |
+
break;
|
81 |
+
|
82 |
+
case self::PRELOAD_HERO_IMAGE:
|
83 |
+
if (! is_bool($value)) {
|
84 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
85 |
+
self::class,
|
86 |
+
self::PRELOAD_HERO_IMAGE,
|
87 |
+
'boolean',
|
88 |
+
is_object($value) ? get_class($value) : gettype($value)
|
89 |
+
);
|
90 |
+
}
|
91 |
+
break;
|
92 |
+
|
93 |
+
case self::PRELOAD_SRCSET:
|
94 |
+
if (! is_bool($value)) {
|
95 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
96 |
+
self::class,
|
97 |
+
self::PRELOAD_SRCSET,
|
98 |
+
'boolean',
|
99 |
+
is_object($value) ? get_class($value) : gettype($value)
|
100 |
+
);
|
101 |
+
}
|
102 |
+
break;
|
103 |
+
}
|
104 |
+
|
105 |
+
return $value;
|
106 |
+
}
|
107 |
+
}
|
includes/vendor/tool/Optimizer/Configuration/RewriteAmpUrlsConfiguration.php
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Configuration;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationValue;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Configuration for the RewriteAmpUrls transformer.
|
10 |
+
*
|
11 |
+
* @property string $ampRuntimeVersion Specifies a specific version of the AMP runtime.
|
12 |
+
* @property string $ampUrlPrefix Specifies an URL prefix for AMP runtime URLs.
|
13 |
+
* @property bool $esmModulesEnabled Whether to use ES modules for loading the AMP runtime and components.
|
14 |
+
* @property string $geoApiUrl Specifies amp-geo API URL to use as a fallback.
|
15 |
+
* @property bool $lts Use long-term stable URLs.
|
16 |
+
* @property bool $rtv Append the runtime version to the rewritten URLs.
|
17 |
+
*
|
18 |
+
* @package ampproject/amp-toolbox
|
19 |
+
*/
|
20 |
+
final class RewriteAmpUrlsConfiguration extends BaseTransformerConfiguration
|
21 |
+
{
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Specifies a specific version of the AMP runtime.
|
25 |
+
*
|
26 |
+
* For example: `ampRuntimeVersion: "001515617716922"` will result in AMP runtime URLs being re-written from
|
27 |
+
* `https://cdn.ampproject.org/v0.js` to `https://cdn.ampproject.org/rtv/001515617716922/v0.js`.
|
28 |
+
*
|
29 |
+
* @var string
|
30 |
+
*/
|
31 |
+
const AMP_RUNTIME_VERSION = 'ampRuntimeVersion';
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Specifies an URL prefix for AMP runtime URLs.
|
35 |
+
*
|
36 |
+
* For example: `ampUrlPrefix: "/amp"` will result in AMP runtime URLs being re-written from
|
37 |
+
* `https://cdn.ampproject.org/v0.js` to `/amp/v0.js`. This option is experimental and not recommended.
|
38 |
+
*
|
39 |
+
* @var string
|
40 |
+
*/
|
41 |
+
const AMP_URL_PREFIX = 'ampUrlPrefix';
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Whether to use ES modules for loading the AMP runtime and components.
|
45 |
+
*
|
46 |
+
* @var string
|
47 |
+
*/
|
48 |
+
const ESM_MODULES_ENABLED = 'esmModulesEnabled';
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Specifies amp-geo API URL to use as a fallback when `amp-geo-0.1.js` is served unpatched.
|
52 |
+
*
|
53 |
+
* This is used when `{{AMP_ISO_COUNTRY_HOTPATCH}}` is not replaced dynamically.
|
54 |
+
*
|
55 |
+
* @var string
|
56 |
+
*/
|
57 |
+
const GEO_API_URL = 'geoApiUrl';
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Use long-term stable URLs.
|
61 |
+
*
|
62 |
+
* This option is not compatible with `rtv`, `ampRuntimeVersion` or `ampUrlPrefix`; an error will be thrown if these
|
63 |
+
* options are included together.
|
64 |
+
*
|
65 |
+
* Similarly, the `geoApiUrl` option is ineffective with the `lts` flag, but will simply be ignored rather than
|
66 |
+
* throwing an error.
|
67 |
+
*
|
68 |
+
* @var string
|
69 |
+
*/
|
70 |
+
const LTS = 'lts';
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Append the runtime version to the rewritten URLs.
|
74 |
+
*
|
75 |
+
* This option is not compatible with `lts`.
|
76 |
+
*
|
77 |
+
* @var string
|
78 |
+
*/
|
79 |
+
const RTV = 'rtv';
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Get the associative array of allowed keys and their respective default
|
83 |
+
* values.
|
84 |
+
*
|
85 |
+
* The array index is the key and the array value is the key's default
|
86 |
+
* value.
|
87 |
+
*
|
88 |
+
* @return array Associative array of allowed keys and their respective
|
89 |
+
* default values.
|
90 |
+
*/
|
91 |
+
protected function getAllowedKeys()
|
92 |
+
{
|
93 |
+
return [
|
94 |
+
self::AMP_RUNTIME_VERSION => '',
|
95 |
+
self::AMP_URL_PREFIX => Amp::CACHE_HOST,
|
96 |
+
self::ESM_MODULES_ENABLED => true,
|
97 |
+
self::GEO_API_URL => '',
|
98 |
+
self::LTS => false,
|
99 |
+
self::RTV => false,
|
100 |
+
];
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Validate an individual configuration entry.
|
105 |
+
*
|
106 |
+
* @param string $key Key of the configuration entry to validate.
|
107 |
+
* @param mixed $value Value of the configuration entry to validate.
|
108 |
+
* @return mixed Validated value.
|
109 |
+
*/
|
110 |
+
protected function validate($key, $value)
|
111 |
+
{
|
112 |
+
switch ($key) {
|
113 |
+
case self::AMP_RUNTIME_VERSION:
|
114 |
+
if (! is_string($value)) {
|
115 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
116 |
+
self::class,
|
117 |
+
self::AMP_RUNTIME_VERSION,
|
118 |
+
'string',
|
119 |
+
is_object($value) ? get_class($value) : gettype($value)
|
120 |
+
);
|
121 |
+
}
|
122 |
+
$value = trim($value);
|
123 |
+
break;
|
124 |
+
|
125 |
+
case self::AMP_URL_PREFIX:
|
126 |
+
if (! is_string($value)) {
|
127 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
128 |
+
self::class,
|
129 |
+
self::AMP_URL_PREFIX,
|
130 |
+
'string',
|
131 |
+
is_object($value) ? get_class($value) : gettype($value)
|
132 |
+
);
|
133 |
+
}
|
134 |
+
$value = trim($value);
|
135 |
+
break;
|
136 |
+
|
137 |
+
case self::ESM_MODULES_ENABLED:
|
138 |
+
if (! is_bool($value)) {
|
139 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
140 |
+
self::class,
|
141 |
+
self::ESM_MODULES_ENABLED,
|
142 |
+
'boolean',
|
143 |
+
is_object($value) ? get_class($value) : gettype($value)
|
144 |
+
);
|
145 |
+
}
|
146 |
+
break;
|
147 |
+
|
148 |
+
case self::GEO_API_URL:
|
149 |
+
if (! is_string($value)) {
|
150 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
151 |
+
self::class,
|
152 |
+
self::GEO_API_URL,
|
153 |
+
'string',
|
154 |
+
is_object($value) ? get_class($value) : gettype($value)
|
155 |
+
);
|
156 |
+
}
|
157 |
+
$value = trim($value);
|
158 |
+
break;
|
159 |
+
|
160 |
+
case self::LTS:
|
161 |
+
if (! is_bool($value)) {
|
162 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
163 |
+
self::class,
|
164 |
+
self::LTS,
|
165 |
+
'boolean',
|
166 |
+
is_object($value) ? get_class($value) : gettype($value)
|
167 |
+
);
|
168 |
+
}
|
169 |
+
break;
|
170 |
+
|
171 |
+
case self::RTV:
|
172 |
+
if (! is_bool($value)) {
|
173 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
174 |
+
self::class,
|
175 |
+
self::RTV,
|
176 |
+
'boolean',
|
177 |
+
is_object($value) ? get_class($value) : gettype($value)
|
178 |
+
);
|
179 |
+
}
|
180 |
+
break;
|
181 |
+
}
|
182 |
+
|
183 |
+
return $value;
|
184 |
+
}
|
185 |
+
}
|
includes/vendor/tool/Optimizer/Configuration/TransformedIdentifierConfiguration.php
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Configuration;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationValue;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Configuration for the TransformedIdentifier transformer.
|
9 |
+
*
|
10 |
+
* @property int $version Version number to use. Defaults to 0.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class TransformedIdentifierConfiguration extends BaseTransformerConfiguration
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Configuration key that holds the version number to use.
|
19 |
+
*
|
20 |
+
* @var string
|
21 |
+
*/
|
22 |
+
const VERSION = 'version';
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Get the associative array of allowed keys and their respective default values.
|
26 |
+
*
|
27 |
+
* The array index is the key and the array value is the key's default value.
|
28 |
+
*
|
29 |
+
* @return array Associative array of allowed keys and their respective default values.
|
30 |
+
*/
|
31 |
+
protected function getAllowedKeys()
|
32 |
+
{
|
33 |
+
return [
|
34 |
+
self::VERSION => 1,
|
35 |
+
];
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Validate an individual configuration entry.
|
40 |
+
*
|
41 |
+
* @param string $key Key of the configuration entry to validate.
|
42 |
+
* @param mixed $value Value of the configuration entry to validate.
|
43 |
+
* @return mixed Validated value.
|
44 |
+
*/
|
45 |
+
protected function validate($key, $value)
|
46 |
+
{
|
47 |
+
switch ($key) {
|
48 |
+
case self::VERSION:
|
49 |
+
if (! is_int($value)) {
|
50 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
51 |
+
self::class,
|
52 |
+
self::VERSION,
|
53 |
+
'integer',
|
54 |
+
is_object($value) ? get_class($value) : gettype($value)
|
55 |
+
);
|
56 |
+
}
|
57 |
+
break;
|
58 |
+
}
|
59 |
+
|
60 |
+
return $value;
|
61 |
+
}
|
62 |
+
}
|
includes/vendor/tool/Optimizer/CssRule.php
ADDED
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* CSS rule that provides semantic handling of media queries, selectors and properties.
|
7 |
+
*
|
8 |
+
* This is used in conjunction with CssRules for deduplication of CSS when adding styles during transformations.
|
9 |
+
*
|
10 |
+
* Note: This is a simplistic representation of CSS rules built for a specific purpose.
|
11 |
+
* Make sure it supports a given use case before including in new code!
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class CssRule
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Characters to use for trimming CSS values.
|
20 |
+
*
|
21 |
+
* @var string
|
22 |
+
*/
|
23 |
+
const CSS_TRIM_CHARACTERS = " \t\n\r\0\x0B;";
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Placeholder to use in the rule to denote an ID that has yet to be defined.
|
27 |
+
*
|
28 |
+
* Use applyId() to finalize the CSS rule.
|
29 |
+
*
|
30 |
+
* @var string.
|
31 |
+
*/
|
32 |
+
const ID_PLACEHOLDER = '__ID__';
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Selector(s) to use for the CSS rule.
|
36 |
+
*
|
37 |
+
* @var string[]
|
38 |
+
*/
|
39 |
+
private $selectors;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Properties to apply to the selector(s).
|
43 |
+
*
|
44 |
+
* @var string[]
|
45 |
+
*/
|
46 |
+
private $properties;
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Media query that wraps the CSS rule.
|
50 |
+
*
|
51 |
+
* @var string
|
52 |
+
*/
|
53 |
+
private $mediaQuery = '';
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Rendered CSS cache.
|
57 |
+
*
|
58 |
+
* @var string|null
|
59 |
+
*/
|
60 |
+
private $renderedCss;
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Byte count cache.
|
64 |
+
*
|
65 |
+
* @var int|null
|
66 |
+
*/
|
67 |
+
private $byteCount;
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Instantiate a CssRule object.
|
71 |
+
*
|
72 |
+
* @param string|string[] $selectors One or more selectors to use.
|
73 |
+
* @param string|string[] $properties One or more properties to apply to the selector(s).
|
74 |
+
*/
|
75 |
+
public function __construct($selectors, $properties)
|
76 |
+
{
|
77 |
+
$this->selectors = array_values(
|
78 |
+
array_unique(
|
79 |
+
array_filter(
|
80 |
+
array_map(
|
81 |
+
[$this, 'normalizeSelector'],
|
82 |
+
$this->separateSelectors($selectors)
|
83 |
+
)
|
84 |
+
)
|
85 |
+
)
|
86 |
+
);
|
87 |
+
|
88 |
+
$this->properties = array_values(
|
89 |
+
array_unique(
|
90 |
+
array_filter(
|
91 |
+
array_map(
|
92 |
+
[$this, 'normalizeProperty'],
|
93 |
+
$this->separateProperties($properties)
|
94 |
+
)
|
95 |
+
)
|
96 |
+
)
|
97 |
+
);
|
98 |
+
|
99 |
+
sort($this->properties);
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Create a new CSS rule that is wrapped in a media query.
|
104 |
+
*
|
105 |
+
* @param string $mediaQuery Media query to wrap the CSS rule in.
|
106 |
+
* @param string|string[] $selectors One or more selectors to use.
|
107 |
+
* @param string|string[] $properties One or more properties to apply to the selector(s).
|
108 |
+
* @return self CSS rule wrapped in a media query.
|
109 |
+
*/
|
110 |
+
public static function withMediaQuery($mediaQuery, $selectors, $properties)
|
111 |
+
{
|
112 |
+
$cssRule = new self($selectors, $properties);
|
113 |
+
$cssRule->mediaQuery = $mediaQuery;
|
114 |
+
return $cssRule;
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Get the selector(s) for this CSS rule.
|
119 |
+
*
|
120 |
+
* @return string[] Selector(s) of the CSS rule.
|
121 |
+
*/
|
122 |
+
public function getSelectors()
|
123 |
+
{
|
124 |
+
return $this->selectors;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Get the properties for this CSS rule.
|
129 |
+
*
|
130 |
+
* @return string[] Properties of the CSS rule.
|
131 |
+
*/
|
132 |
+
public function getProperties()
|
133 |
+
{
|
134 |
+
return $this->properties;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Get the media query for this CSS rule.
|
139 |
+
*
|
140 |
+
* @return string Media query for the CSS rule or an empty string if none is set.
|
141 |
+
*/
|
142 |
+
public function getMediaQuery()
|
143 |
+
{
|
144 |
+
return $this->mediaQuery;
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Get the CSS for this CSS rule.
|
149 |
+
*
|
150 |
+
* @return string CSS for this CSS rule.
|
151 |
+
*/
|
152 |
+
public function getCss()
|
153 |
+
{
|
154 |
+
if ($this->renderedCss === null) {
|
155 |
+
$selectors = implode(',', $this->selectors);
|
156 |
+
|
157 |
+
$properties = implode(
|
158 |
+
';',
|
159 |
+
array_map(
|
160 |
+
static function ($property) {
|
161 |
+
return trim($property, self::CSS_TRIM_CHARACTERS);
|
162 |
+
},
|
163 |
+
$this->properties
|
164 |
+
)
|
165 |
+
);
|
166 |
+
|
167 |
+
if (empty($selectors) || empty($properties)) {
|
168 |
+
$this->renderedCss = '';
|
169 |
+
} else {
|
170 |
+
$this->renderedCss = "{$selectors}{{$properties}}";
|
171 |
+
|
172 |
+
if (! empty($this->mediaQuery)) {
|
173 |
+
$this->renderedCss = "{$this->mediaQuery}{{$this->renderedCss}}";
|
174 |
+
}
|
175 |
+
}
|
176 |
+
}
|
177 |
+
|
178 |
+
return $this->renderedCss;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Apply the provided ID across all ID placeholders.
|
183 |
+
*
|
184 |
+
* @param string $id ID to apply.
|
185 |
+
* @return self
|
186 |
+
*/
|
187 |
+
public function applyID($id)
|
188 |
+
{
|
189 |
+
$replacement_callback = static function ($css) use ($id) {
|
190 |
+
return str_replace(self::ID_PLACEHOLDER, $id, $css);
|
191 |
+
};
|
192 |
+
|
193 |
+
$this->selectors = array_map($replacement_callback, $this->selectors);
|
194 |
+
$this->properties = array_map($replacement_callback, $this->properties);
|
195 |
+
|
196 |
+
// Reset caches so they will need to be rebuilt.
|
197 |
+
$this->renderedCss = null;
|
198 |
+
$this->byteCount = null;
|
199 |
+
|
200 |
+
return $this;
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Check if the CSS rule can be merged with another provided CSS rule.
|
205 |
+
*
|
206 |
+
* @param CssRule $that CSS rule to check against.
|
207 |
+
* @return bool Whether the two CSS rules can be merged.
|
208 |
+
*/
|
209 |
+
public function canBeMerged(CssRule $that)
|
210 |
+
{
|
211 |
+
if ($this->mediaQuery !== $that->mediaQuery) {
|
212 |
+
return false;
|
213 |
+
}
|
214 |
+
|
215 |
+
if (
|
216 |
+
count($this->properties) !== count($that->properties)
|
217 |
+
|| array_diff($this->properties, $that->properties)
|
218 |
+
) {
|
219 |
+
return false;
|
220 |
+
}
|
221 |
+
|
222 |
+
return true;
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Merge this CSS rule with another CSS rule.
|
227 |
+
*
|
228 |
+
* This should only be done to same-properties rules within the same media query,
|
229 |
+
* as the result will be wild otherwise.
|
230 |
+
*
|
231 |
+
* @param CssRule $that CSS rule to merge the current one with.
|
232 |
+
* @return CssRule Merged Css rule.
|
233 |
+
*/
|
234 |
+
public function mergeWith(CssRule $that)
|
235 |
+
{
|
236 |
+
$cssRule = new self(
|
237 |
+
array_merge($this->selectors, $that->selectors),
|
238 |
+
array_merge($this->properties, $that->properties)
|
239 |
+
);
|
240 |
+
|
241 |
+
$cssRule->mediaQuery = $this->mediaQuery;
|
242 |
+
|
243 |
+
return $cssRule;
|
244 |
+
}
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Get the byte count for the CSS rule.
|
248 |
+
*
|
249 |
+
* @return int Byte count of the CSS rule.
|
250 |
+
*/
|
251 |
+
public function getByteCount()
|
252 |
+
{
|
253 |
+
if ($this->byteCount === null) {
|
254 |
+
$this->byteCount = strlen($this->getCss());
|
255 |
+
}
|
256 |
+
|
257 |
+
return $this->byteCount;
|
258 |
+
}
|
259 |
+
|
260 |
+
/**
|
261 |
+
* Normalize a single selector.
|
262 |
+
*
|
263 |
+
* @param string $selector Selector to normalize.
|
264 |
+
* @return string Normalized selector.
|
265 |
+
*/
|
266 |
+
private function normalizeSelector($selector)
|
267 |
+
{
|
268 |
+
// Turn all series of whitespace into single spaces.
|
269 |
+
$selector = preg_replace('/\s+/', ' ', $selector);
|
270 |
+
|
271 |
+
// Remove spaces around selector qualifiers to keep properties compact.
|
272 |
+
$selector = preg_replace('/ ?([>+~]) ?/', '$1', $selector);
|
273 |
+
|
274 |
+
// Remove leading and trailing whitespace and commas.
|
275 |
+
$selector = trim($selector, self::CSS_TRIM_CHARACTERS);
|
276 |
+
|
277 |
+
return $selector;
|
278 |
+
}
|
279 |
+
|
280 |
+
/**
|
281 |
+
* Normalize single property.
|
282 |
+
*
|
283 |
+
* @param string $property Property to normalize.
|
284 |
+
* @return string Normalized property.
|
285 |
+
*/
|
286 |
+
private function normalizeProperty($property)
|
287 |
+
{
|
288 |
+
// Turn all series of whitespace into single spaces.
|
289 |
+
$property = preg_replace('/\s+/', ' ', $property);
|
290 |
+
|
291 |
+
// Remove spaces around colons and semicolons to keep properties compact.
|
292 |
+
$property = preg_replace('/ ?([:;]) ?/', '$1', $property);
|
293 |
+
|
294 |
+
// Deduplicate semicolons.
|
295 |
+
$property = preg_replace('/([;]+)/', ';', $property);
|
296 |
+
|
297 |
+
// Remove leading and trailing whitespace and semicolons.
|
298 |
+
$property = trim($property, self::CSS_TRIM_CHARACTERS);
|
299 |
+
|
300 |
+
return $property;
|
301 |
+
}
|
302 |
+
|
303 |
+
/**
|
304 |
+
* Separate selectors into individual values.
|
305 |
+
*
|
306 |
+
* @param string|string[]|array[] $selectors Selectors to separate.
|
307 |
+
* @return string[] Separated selectors.
|
308 |
+
*/
|
309 |
+
private function separateSelectors($selectors)
|
310 |
+
{
|
311 |
+
$separatedSelectors = [];
|
312 |
+
|
313 |
+
foreach ((array)$selectors as $selectorString) {
|
314 |
+
if (is_array($selectorString)) {
|
315 |
+
$separatedSelectors = array_merge($separatedSelectors, $this->separateSelectors($selectorString));
|
316 |
+
} else {
|
317 |
+
$separatedSelectors = array_merge($separatedSelectors, explode(',', $selectorString));
|
318 |
+
}
|
319 |
+
}
|
320 |
+
|
321 |
+
return $separatedSelectors;
|
322 |
+
}
|
323 |
+
|
324 |
+
/**
|
325 |
+
* Separate properties into individual values.
|
326 |
+
*
|
327 |
+
* @param string|string[]|array[] $properties Properties to separate.
|
328 |
+
* @return string[] Separated properties.
|
329 |
+
*/
|
330 |
+
private function separateProperties($properties)
|
331 |
+
{
|
332 |
+
$separatedProperties = [];
|
333 |
+
|
334 |
+
foreach ((array)$properties as $propertyString) {
|
335 |
+
if (is_array($propertyString)) {
|
336 |
+
$separatedProperties = array_merge($separatedProperties, $this->separateProperties($propertyString));
|
337 |
+
} else {
|
338 |
+
$separatedProperties = array_merge($separatedProperties, explode(';', $propertyString));
|
339 |
+
}
|
340 |
+
}
|
341 |
+
|
342 |
+
return $separatedProperties;
|
343 |
+
}
|
344 |
+
}
|
includes/vendor/tool/Optimizer/CssRules.php
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Collection of CSS rules.
|
7 |
+
*
|
8 |
+
* This is used in conjunction with CssRule for deduplication of CSS when adding styles during transformations.
|
9 |
+
*
|
10 |
+
* Note: This is a simplistic representation of CSS rules built for a specific purpose.
|
11 |
+
* Make sure it supports a given use case before including in new code!
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class CssRules
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Internal array of CssRule objects.
|
20 |
+
*
|
21 |
+
* @var CssRule[]
|
22 |
+
*/
|
23 |
+
private $cssRules = [];
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Rendered CSS cache.
|
27 |
+
*
|
28 |
+
* @var string|null
|
29 |
+
*/
|
30 |
+
private $renderedCss;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Byte count cache.
|
34 |
+
*
|
35 |
+
* @var int|null
|
36 |
+
*/
|
37 |
+
private $byteCount;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Create a new CssRules collection from an array of CssRule objects.
|
41 |
+
*
|
42 |
+
* @param CssRule[] $cssRuleArray Array of CssRule objects.
|
43 |
+
* @return CssRules CSS rules collection.
|
44 |
+
*/
|
45 |
+
public static function fromCssRuleArray($cssRuleArray)
|
46 |
+
{
|
47 |
+
$cssRules = new self();
|
48 |
+
|
49 |
+
array_walk(
|
50 |
+
$cssRuleArray,
|
51 |
+
static function (CssRule $cssRule) use (&$cssRules) {
|
52 |
+
$cssRules = $cssRules->add($cssRule);
|
53 |
+
}
|
54 |
+
);
|
55 |
+
|
56 |
+
return $cssRules;
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Add a CSS rule to the collection.
|
61 |
+
*
|
62 |
+
* @param CssRule $cssRule
|
63 |
+
* @return CssRules Adapted collection with the added CSS rule.
|
64 |
+
*/
|
65 |
+
public function add(CssRule $cssRule)
|
66 |
+
{
|
67 |
+
$clone = clone $this;
|
68 |
+
|
69 |
+
if (empty($clone->cssRules)) {
|
70 |
+
$clone->cssRules = [$cssRule];
|
71 |
+
|
72 |
+
return $clone;
|
73 |
+
}
|
74 |
+
|
75 |
+
foreach ($clone->cssRules as $index => $existingCssRule) {
|
76 |
+
if ($existingCssRule->canBeMerged($cssRule)) {
|
77 |
+
$clone->cssRules[$index] = $existingCssRule->mergeWith($cssRule);
|
78 |
+
// Rendered CSS and byte count need to be rebuilt, as some previously rendered CSS rule has changed.
|
79 |
+
$clone->renderedCss = null;
|
80 |
+
$clone->byteCount = null;
|
81 |
+
|
82 |
+
return $clone;
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
$clone->cssRules[] = $cssRule;
|
87 |
+
|
88 |
+
if ($clone->renderedCss !== null) {
|
89 |
+
// As we didn't merge, we can save rerendering and just concat the single rule.
|
90 |
+
$clone->renderedCss .= $cssRule->getCss();
|
91 |
+
}
|
92 |
+
|
93 |
+
if ($clone->byteCount !== null) {
|
94 |
+
// As we didn't merge, we can save recounting and just add the bytes of the single rule.
|
95 |
+
$clone->byteCount += $cssRule->getByteCount();
|
96 |
+
}
|
97 |
+
|
98 |
+
return $clone;
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Get the CSS for the entire collection of CSS rules.
|
103 |
+
*
|
104 |
+
* @return string String representation of the collection of CSS rules.
|
105 |
+
*/
|
106 |
+
public function getCss()
|
107 |
+
{
|
108 |
+
if ($this->renderedCss === null) {
|
109 |
+
$this->renderedCss = array_reduce(
|
110 |
+
$this->cssRules,
|
111 |
+
static function ($css, CssRule $cssRule) {
|
112 |
+
return $css . $cssRule->getCss();
|
113 |
+
},
|
114 |
+
''
|
115 |
+
);
|
116 |
+
}
|
117 |
+
|
118 |
+
return $this->renderedCss;
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Get the byte count for the entire collection of CSS rules.
|
123 |
+
*
|
124 |
+
* @return int Byte count of the collection of CSS rules.
|
125 |
+
*/
|
126 |
+
public function getByteCount()
|
127 |
+
{
|
128 |
+
if ($this->byteCount === null) {
|
129 |
+
$this->byteCount = array_reduce(
|
130 |
+
$this->cssRules,
|
131 |
+
static function ($byteCount, CssRule $cssRule) {
|
132 |
+
return $byteCount + $cssRule->getByteCount();
|
133 |
+
},
|
134 |
+
0
|
135 |
+
);
|
136 |
+
}
|
137 |
+
|
138 |
+
return $this->byteCount;
|
139 |
+
}
|
140 |
+
}
|
includes/vendor/tool/Optimizer/DefaultConfiguration.php
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Configuration\AmpRuntimeCssConfiguration;
|
6 |
+
use AmpProject\Optimizer\Configuration\OptimizeAmpBindConfiguration;
|
7 |
+
use AmpProject\Optimizer\Configuration\PreloadHeroImageConfiguration;
|
8 |
+
use AmpProject\Optimizer\Configuration\RewriteAmpUrlsConfiguration;
|
9 |
+
use AmpProject\Optimizer\Configuration\TransformedIdentifierConfiguration;
|
10 |
+
use AmpProject\Optimizer\Exception\InvalidConfigurationValue;
|
11 |
+
use AmpProject\Optimizer\Exception\UnknownConfigurationClass;
|
12 |
+
use AmpProject\Optimizer\Exception\UnknownConfigurationKey;
|
13 |
+
use AmpProject\Optimizer\Transformer\AmpRuntimeCss;
|
14 |
+
use AmpProject\Optimizer\Transformer\OptimizeAmpBind;
|
15 |
+
use AmpProject\Optimizer\Transformer\PreloadHeroImage;
|
16 |
+
use AmpProject\Optimizer\Transformer\RewriteAmpUrls;
|
17 |
+
use AmpProject\Optimizer\Transformer\TransformedIdentifier;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Configuration object that validates and stores configuration settings.
|
21 |
+
*
|
22 |
+
* @package ampproject/amp-toolbox
|
23 |
+
*/
|
24 |
+
class DefaultConfiguration implements Configuration
|
25 |
+
{
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Associative array of already validated configuration settings.
|
29 |
+
*
|
30 |
+
* @var array
|
31 |
+
*/
|
32 |
+
protected $configuration;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Associative array mapping the transformer classes to their configuration classes.
|
36 |
+
*
|
37 |
+
* This can be extended by third-parties via:
|
38 |
+
*
|
39 |
+
* @see registerConfigurationClass()
|
40 |
+
*
|
41 |
+
* @var array
|
42 |
+
*/
|
43 |
+
protected $transformerConfigurationClasses = [
|
44 |
+
AmpRuntimeCss::class => AmpRuntimeCssConfiguration::class,
|
45 |
+
OptimizeAmpBind::class => OptimizeAmpBindConfiguration::class,
|
46 |
+
PreloadHeroImage::class => PreloadHeroImageConfiguration::class,
|
47 |
+
RewriteAmpUrls::class => RewriteAmpUrlsConfiguration::class,
|
48 |
+
TransformedIdentifier::class => TransformedIdentifierConfiguration::class,
|
49 |
+
];
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Instantiate a Configuration object.
|
53 |
+
*
|
54 |
+
* @param array $configurationData Optional. Associative array of configuration data to use. This will be merged
|
55 |
+
* with the default configuration and take precedence.
|
56 |
+
*/
|
57 |
+
public function __construct($configurationData = [])
|
58 |
+
{
|
59 |
+
$this->configuration = array_merge(
|
60 |
+
static::DEFAULTS,
|
61 |
+
$this->validateConfigurationKeys($configurationData)
|
62 |
+
);
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Register a new configuration class to use for a given transformer.
|
67 |
+
*
|
68 |
+
* @param string $transformerClass FQCN of the transformer to register a configuration class for.
|
69 |
+
* @param string $configurationClass FQCN of the configuration to use.
|
70 |
+
*/
|
71 |
+
public function registerConfigurationClass($transformerClass, $configurationClass)
|
72 |
+
{
|
73 |
+
$this->transformerConfigurationClasses[$transformerClass] = $configurationClass;
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Validate an array of configuration settings.
|
78 |
+
*
|
79 |
+
* @param array $configurationData Associative array of configuration data to validate.
|
80 |
+
* @return array Associative array of validated configuration data.
|
81 |
+
*/
|
82 |
+
protected function validateConfigurationKeys($configurationData)
|
83 |
+
{
|
84 |
+
foreach ($configurationData as $key => $value) {
|
85 |
+
$configurationData[$key] = $this->validate($key, $value);
|
86 |
+
}
|
87 |
+
|
88 |
+
return $configurationData;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Validate an individual configuration setting.
|
93 |
+
*
|
94 |
+
* @param string $key Key of the configuration setting.
|
95 |
+
* @param mixed $value Value of the configuration setting.
|
96 |
+
* @return mixed Validated value for the provided configuration setting.
|
97 |
+
* @throws InvalidConfigurationValue If the configuration value could not be validated.
|
98 |
+
*/
|
99 |
+
protected function validate($key, $value)
|
100 |
+
{
|
101 |
+
switch ($key) {
|
102 |
+
case Configuration::KEY_TRANSFORMERS:
|
103 |
+
if (! is_array($value)) {
|
104 |
+
throw InvalidConfigurationValue::forInvalidValueType(
|
105 |
+
Configuration::KEY_TRANSFORMERS,
|
106 |
+
'array',
|
107 |
+
gettype($value)
|
108 |
+
);
|
109 |
+
}
|
110 |
+
|
111 |
+
foreach ($value as $index => $entry) {
|
112 |
+
if (! is_string($entry)) {
|
113 |
+
throw InvalidConfigurationValue::forInvalidSubValueType(
|
114 |
+
Configuration::KEY_TRANSFORMERS,
|
115 |
+
$index,
|
116 |
+
'string',
|
117 |
+
gettype($entry)
|
118 |
+
);
|
119 |
+
}
|
120 |
+
}
|
121 |
+
}
|
122 |
+
|
123 |
+
return $value;
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Check whether the configuration has a given setting.
|
128 |
+
*
|
129 |
+
* @param string $key Configuration key to look for.
|
130 |
+
* @return bool Whether the requested configuration key was found or not.
|
131 |
+
*/
|
132 |
+
public function has($key)
|
133 |
+
{
|
134 |
+
return array_key_exists($key, $this->configuration);
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Get the value for a given key from the configuration.
|
139 |
+
*
|
140 |
+
* @param string $key Configuration key to get the value for.
|
141 |
+
* @return mixed Configuration value for the requested key.
|
142 |
+
* @throws UnknownConfigurationKey If the key was not found.
|
143 |
+
*/
|
144 |
+
public function get($key)
|
145 |
+
{
|
146 |
+
if (! array_key_exists($key, $this->configuration)) {
|
147 |
+
throw UnknownConfigurationKey::fromKey($key);
|
148 |
+
}
|
149 |
+
|
150 |
+
return $this->configuration[$key];
|
151 |
+
}
|
152 |
+
|
153 |
+
/**
|
154 |
+
* Get the transformer-specific configuration for the requested transformer.
|
155 |
+
*
|
156 |
+
* @param string $transformer FQCN of the transformer to get the configuration for.
|
157 |
+
* @return TransformerConfiguration Transformer-specific configuration.
|
158 |
+
*/
|
159 |
+
public function getTransformerConfiguration($transformer)
|
160 |
+
{
|
161 |
+
if (! array_key_exists($transformer, $this->transformerConfigurationClasses)) {
|
162 |
+
throw UnknownConfigurationClass::fromTransformerClass($transformer);
|
163 |
+
}
|
164 |
+
|
165 |
+
$configuration = $this->has($transformer) ? $this->get($transformer) : [];
|
166 |
+
$configurationClass = $this->transformerConfigurationClasses[$transformer];
|
167 |
+
|
168 |
+
return new $configurationClass($configuration);
|
169 |
+
}
|
170 |
+
}
|
includes/vendor/tool/Optimizer/Error.php
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Error object to transport optimization errors.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface Error
|
11 |
+
{
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Get the code of the error.
|
15 |
+
*
|
16 |
+
* @return string Code of the error.
|
17 |
+
*/
|
18 |
+
public function getCode();
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Get the message of the error.
|
22 |
+
*
|
23 |
+
* @return string Message of the error.
|
24 |
+
*/
|
25 |
+
public function getMessage();
|
26 |
+
}
|
includes/vendor/tool/Optimizer/Error/CannotAdaptDocumentForSelfHosting.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Error;
|
6 |
+
use Exception;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Optimizer error object for when a document cannot be adapted for self-hosting the AMP runtime.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class CannotAdaptDocumentForSelfHosting implements Error
|
14 |
+
{
|
15 |
+
use ErrorProperties;
|
16 |
+
|
17 |
+
const FAILED_TO_ADAPT_WITH_EXCEPTION = 'Cannot adapt document for a self-hosted runtime: ';
|
18 |
+
const FAILED_TO_ADAPT_FOR_NON_ABSOLUTE_URL = 'Cannot add runtime host, ampUrlPrefix must be an absolute URL, got: ';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Instantiate a CannotAdaptDocumentForSelfHosting object for an exception that blocked adapting the document.
|
22 |
+
*
|
23 |
+
* @param Exception $exception Exception that was caught and that blocked adapting the document.
|
24 |
+
* @return self
|
25 |
+
*/
|
26 |
+
public static function fromException(Exception $exception)
|
27 |
+
{
|
28 |
+
return new self(self::FAILED_TO_ADAPT_WITH_EXCEPTION . $exception->getMessage());
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Instantiate a CannotAdaptDocumentForSelfHosting object for a non-absolute URL provided via ampUrlPrefix.
|
33 |
+
*
|
34 |
+
* @param string $url URL that was provided.
|
35 |
+
* @return self
|
36 |
+
*/
|
37 |
+
public static function forNonAbsoluteUrl($url)
|
38 |
+
{
|
39 |
+
return new self(self::FAILED_TO_ADAPT_FOR_NON_ABSOLUTE_URL . $url);
|
40 |
+
}
|
41 |
+
}
|
includes/vendor/tool/Optimizer/Error/CannotInlineRuntimeCss.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
use AmpProject\Dom\ElementDump;
|
7 |
+
use AmpProject\Optimizer\Error;
|
8 |
+
use Exception;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Optimizer error object for when the AMP runtime CSS cannot be inlined.
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class CannotInlineRuntimeCss implements Error
|
16 |
+
{
|
17 |
+
use ErrorProperties;
|
18 |
+
|
19 |
+
const EXCEPTION_STRING = 'Cannot inline the amp-runtime CSS in %3$s into %2$s: %1$s.';
|
20 |
+
const MISSING_AMP_RUNTIME_STYLE_STRING = 'Cannot inline the amp-runtime CSS in %s: '
|
21 |
+
. 'the <style amp-runtime> element is missing.';
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Instantiate a CannotInlineRuntimeCss object for an exception that was thrown.
|
25 |
+
*
|
26 |
+
* @param Exception $exception Exception that was thrown.
|
27 |
+
* @param Element $ampRuntimeStyle DOM element of the <style amp-runtime> tag that was targeted.
|
28 |
+
* @param string $version Version string that was meant to be used.
|
29 |
+
* @return self
|
30 |
+
*/
|
31 |
+
public static function fromException(Exception $exception, Element $ampRuntimeStyle, $version)
|
32 |
+
{
|
33 |
+
$version = empty($version) ? 'unspecified version' : "version {$version}";
|
34 |
+
|
35 |
+
return new self(sprintf(self::EXCEPTION_STRING, $exception, new ElementDump($ampRuntimeStyle), $version));
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Instantiate a CannotInlineRuntimeCss object for a missing <style amp-runtime> element.
|
40 |
+
*
|
41 |
+
* @param string $version Version string that was meant to be used.
|
42 |
+
* @return self
|
43 |
+
*/
|
44 |
+
public static function fromMissingAmpRuntimeStyle($version)
|
45 |
+
{
|
46 |
+
$version = empty($version) ? 'unspecified version' : "version {$version}";
|
47 |
+
|
48 |
+
return new self(sprintf(self::MISSING_AMP_RUNTIME_STYLE_STRING, $version));
|
49 |
+
}
|
50 |
+
}
|
includes/vendor/tool/Optimizer/Error/CannotPerformServerSideRendering.php
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
use AmpProject\Dom\ElementDump;
|
7 |
+
use AmpProject\Exception\MaxCssByteCountExceeded;
|
8 |
+
use AmpProject\Optimizer\Error;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Optimizer error object for when server-side rendering cannot be performed.
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class CannotPerformServerSideRendering implements Error
|
16 |
+
{
|
17 |
+
use ErrorProperties;
|
18 |
+
|
19 |
+
const INVALID_INPUT_WIDTH = 'Cannot perform serverside rendering, invalid input width: ';
|
20 |
+
const INVALID_INPUT_HEIGHT = 'Cannot perform serverside rendering, invalid input height: ';
|
21 |
+
const UNSUPPORTED_LAYOUT = 'Cannot perform serverside rendering, unsupported layout: ';
|
22 |
+
const EXCEEDED_MAX_CSS_BYTE_COUNT = 'Cannot perform serverside rendering, exceeded maximum CSS byte count: ';
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Instantiate a CannotPerformServerSideRendering object for an element with an invalid input width.
|
26 |
+
*
|
27 |
+
* @param Element $element Element that has an invalid input width.
|
28 |
+
* @return self
|
29 |
+
*/
|
30 |
+
public static function fromInvalidInputWidth(Element $element)
|
31 |
+
{
|
32 |
+
return new self(self::INVALID_INPUT_WIDTH . new ElementDump($element));
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Instantiate a CannotPerformServerSideRendering object for an element with an invalid input height.
|
37 |
+
*
|
38 |
+
* @param Element $element Element that has an invalid input height.
|
39 |
+
* @return self
|
40 |
+
*/
|
41 |
+
public static function fromInvalidInputHeight(Element $element)
|
42 |
+
{
|
43 |
+
return new self(self::INVALID_INPUT_HEIGHT . new ElementDump($element));
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Instantiate a CannotPerformServerSideRendering object for an element with an invalid input height.
|
48 |
+
*
|
49 |
+
* @param Element $element Element that has an invalid input height.
|
50 |
+
* @param string $layout Resulting layout.
|
51 |
+
* @return self
|
52 |
+
*/
|
53 |
+
public static function fromUnsupportedLayout(Element $element, $layout)
|
54 |
+
{
|
55 |
+
return new self(self::UNSUPPORTED_LAYOUT . new ElementDump($element) . " => {$layout}");
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Instantiate a CannotPerformServerSideRendering object for a MaxCssByteCountExceeded exception.
|
60 |
+
*
|
61 |
+
* @param MaxCssByteCountExceeded $exception Caught exception.
|
62 |
+
* @param Element $element Element that caused the exception.
|
63 |
+
* @return self
|
64 |
+
*/
|
65 |
+
public static function fromMaxCssByteCountExceededException(MaxCssByteCountExceeded $exception, Element $element)
|
66 |
+
{
|
67 |
+
return new self(
|
68 |
+
self::EXCEEDED_MAX_CSS_BYTE_COUNT . new ElementDump($element) . " => {$exception->getMessage()}"
|
69 |
+
);
|
70 |
+
}
|
71 |
+
}
|
includes/vendor/tool/Optimizer/Error/CannotPreloadImage.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
use AmpProject\Dom\ElementDump;
|
7 |
+
use AmpProject\Optimizer\Error;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Optimizer error object for when a hero image cannot be preloaded.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class CannotPreloadImage implements Error
|
15 |
+
{
|
16 |
+
use ErrorProperties;
|
17 |
+
|
18 |
+
const SRCSET_STRING = 'Not preloading the hero image because of the presence of a "srcset" attribute, which '
|
19 |
+
. 'can currently only be preloaded by Chromium-based browsers '
|
20 |
+
. '(see https://web.dev/preload-responsive-images/).';
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Instantiate a CannotPreloadImage object for an image with a srcset attribute.
|
24 |
+
*
|
25 |
+
* @param Element|null $element Optional. Image element that has the srcset attribute, or null if no element.
|
26 |
+
* @return self
|
27 |
+
*/
|
28 |
+
public static function fromImageWithSrcsetAttribute(Element $element = null)
|
29 |
+
{
|
30 |
+
$message = self::SRCSET_STRING;
|
31 |
+
|
32 |
+
if ($element !== null) {
|
33 |
+
$message .= "\n" . new ElementDump($element);
|
34 |
+
}
|
35 |
+
|
36 |
+
return new self($message);
|
37 |
+
}
|
38 |
+
}
|
includes/vendor/tool/Optimizer/Error/CannotRemoveBoilerplate.php
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
use AmpProject\Dom\ElementDump;
|
7 |
+
use AmpProject\Optimizer\Error;
|
8 |
+
use Exception;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Optimizer error object for when the AMP boilerplate style cannot be removed.
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class CannotRemoveBoilerplate implements Error
|
16 |
+
{
|
17 |
+
use ErrorProperties;
|
18 |
+
|
19 |
+
const ATTRIBUTES_STRING = 'Cannot remove boilerplate as either heights, media or sizes attribute is '
|
20 |
+
. 'set and cannot be adapted: ';
|
21 |
+
const ATTRIBUTES_EXCEPTION_STRING = 'Cannot remove boilerplate as the removal of either heights, media or sizes '
|
22 |
+
. 'attribute produced an error: ';
|
23 |
+
const RENDER_DELAYING_SCRIPT_STRING = 'Cannot remove boilerplate because the document contains a render-delaying '
|
24 |
+
. 'extension: ';
|
25 |
+
const AMP_AUDIO_STRING = 'Cannot remove boilerplate because the document contains an extension that '
|
26 |
+
. 'needs to know the dimensions of the browser: ';
|
27 |
+
const UNSUPPORTED_LAYOUT_STRING = 'Cannot remove boilerplate because of an unsupported layout: ';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Instantiate a CannotRemoveBoilerplate object for attributes that require the boilerplate to be around.
|
31 |
+
*
|
32 |
+
* @param Element $element Element that contains the attributes that need the boilerplate.
|
33 |
+
* @return self
|
34 |
+
*/
|
35 |
+
public static function fromAttributesRequiringBoilerplate(Element $element)
|
36 |
+
{
|
37 |
+
return new self(self::ATTRIBUTES_STRING . new ElementDump($element));
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Instantiate a CannotRemoveBoilerplate object for attributes that require the boilerplate to be around.
|
42 |
+
*
|
43 |
+
* @param Exception $exception Exception being thrown.
|
44 |
+
* @return self
|
45 |
+
*/
|
46 |
+
public static function fromAttributeThrowingException($exception)
|
47 |
+
{
|
48 |
+
return new self(self::ATTRIBUTES_EXCEPTION_STRING . $exception->getMessage());
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Instantiate a CannotRemoveBoilerplate object for an amp-experiment element.
|
53 |
+
*
|
54 |
+
* @param Element $element amp-experiment element.
|
55 |
+
* @return self
|
56 |
+
*/
|
57 |
+
public static function fromAmpExperiment(Element $element)
|
58 |
+
{
|
59 |
+
return new self(self::RENDER_DELAYING_SCRIPT_STRING . $element->tagName);
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Instantiate a CannotRemoveBoilerplate object for an amp-audio element.
|
64 |
+
*
|
65 |
+
* @param Element $element amp-audio element.
|
66 |
+
* @return self
|
67 |
+
*/
|
68 |
+
public static function fromAmpAudio(Element $element)
|
69 |
+
{
|
70 |
+
return new self(self::AMP_AUDIO_STRING . new ElementDump($element));
|
71 |
+
}
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Instantiate a CannotRemoveBoilerplate object for an element with an unsupported layout.
|
75 |
+
*
|
76 |
+
* @param Element $element Element with an unsupported layout.
|
77 |
+
* @return self
|
78 |
+
*/
|
79 |
+
public static function fromUnsupportedLayout(Element $element)
|
80 |
+
{
|
81 |
+
return new self(self::UNSUPPORTED_LAYOUT_STRING . new ElementDump($element));
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Instantiate a CannotRemoveBoilerplate object for render-delaying script element.
|
86 |
+
*
|
87 |
+
* @param Element $element Element with an unsupported layout.
|
88 |
+
* @return self
|
89 |
+
*/
|
90 |
+
public static function fromRenderDelayingScript(Element $element)
|
91 |
+
{
|
92 |
+
$elementName = $element->hasAttribute('custom-element')
|
93 |
+
? $element->getAttribute('custom-element')
|
94 |
+
: '<unknown>';
|
95 |
+
|
96 |
+
return new self(self::UNSUPPORTED_LAYOUT_STRING . $elementName);
|
97 |
+
}
|
98 |
+
}
|
includes/vendor/tool/Optimizer/Error/ErrorProperties.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use ReflectionClass;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Default set of properties and methods to use for errors.
|
9 |
+
*
|
10 |
+
* @package AmpProject\Optimizer
|
11 |
+
*/
|
12 |
+
trait ErrorProperties
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an Error object.
|
17 |
+
*
|
18 |
+
* @param string $message Message for the error.
|
19 |
+
*/
|
20 |
+
public function __construct($message)
|
21 |
+
{
|
22 |
+
$this->message = $message;
|
23 |
+
}
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Message of the error.
|
27 |
+
*
|
28 |
+
* @var string
|
29 |
+
*/
|
30 |
+
protected $message;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Get the code of the error.
|
34 |
+
*
|
35 |
+
* @return string Code of the error.
|
36 |
+
*/
|
37 |
+
public function getCode()
|
38 |
+
{
|
39 |
+
return (new ReflectionClass($this))->getShortName();
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Get the message of the error.
|
44 |
+
*
|
45 |
+
* @return string Message of the error.
|
46 |
+
*/
|
47 |
+
public function getMessage()
|
48 |
+
{
|
49 |
+
return $this->message;
|
50 |
+
}
|
51 |
+
}
|
includes/vendor/tool/Optimizer/Error/TooManyHeroImages.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Error;
|
6 |
+
use AmpProject\Optimizer\Transformer\PreloadHeroImage;
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Optimizer error object for when too many images are marked for being optimized as hero images.
|
10 |
+
*
|
11 |
+
* @package ampproject/amp-toolbox
|
12 |
+
*/
|
13 |
+
final class TooManyHeroImages implements Error
|
14 |
+
{
|
15 |
+
use ErrorProperties;
|
16 |
+
|
17 |
+
const PAST_MAX_STRING = 'Too many images with the "data-hero" attribute were detected, the maximum allowed is %d.';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Instantiate a TooManyHeroImages object for when a hero image was detected past the maximum allowed.
|
21 |
+
*
|
22 |
+
* @return self
|
23 |
+
*/
|
24 |
+
public static function whenPastMaximum()
|
25 |
+
{
|
26 |
+
return new self(sprintf(self::PAST_MAX_STRING, PreloadHeroImage::DATA_HERO_MAX));
|
27 |
+
}
|
28 |
+
}
|
includes/vendor/tool/Optimizer/Error/UnknownError.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Error;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Error;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Optimizer error object for when an unknown error has occurred.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class UnknownError implements Error
|
13 |
+
{
|
14 |
+
use ErrorProperties;
|
15 |
+
}
|
includes/vendor/tool/Optimizer/ErrorCollection.php
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use ArrayIterator;
|
6 |
+
use Countable;
|
7 |
+
use IteratorAggregate;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Collection of error objects to pass around the transformation engine.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class ErrorCollection implements Countable, IteratorAggregate
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Internal storage for the errors that were added.
|
19 |
+
*
|
20 |
+
* @var Error[]
|
21 |
+
*/
|
22 |
+
private $errors = [];
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Add an error to the error collection.
|
26 |
+
*
|
27 |
+
* @param Error $error Error to add.
|
28 |
+
* @return void
|
29 |
+
*/
|
30 |
+
public function add(Error $error)
|
31 |
+
{
|
32 |
+
$this->errors[] = $error;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Check whether the error collection contains an error for the given code.
|
37 |
+
*
|
38 |
+
* @param string $code Code of the error.
|
39 |
+
* @return bool Whether the error collection contains an error with the given code.
|
40 |
+
*/
|
41 |
+
public function has($code)
|
42 |
+
{
|
43 |
+
foreach ($this->errors as $error) {
|
44 |
+
if ($error->getCode() === $code) {
|
45 |
+
return true;
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
return false;
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Get the iterator for iterating over the collection.
|
54 |
+
*
|
55 |
+
* @return ArrayIterator Iterator for the contained errors.
|
56 |
+
*/
|
57 |
+
public function getIterator()
|
58 |
+
{
|
59 |
+
return new ArrayIterator($this->errors);
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Count how many errors are contained within the error collection.
|
64 |
+
*
|
65 |
+
* @return int Number of contained errors.
|
66 |
+
*/
|
67 |
+
public function count()
|
68 |
+
{
|
69 |
+
return count($this->errors);
|
70 |
+
}
|
71 |
+
}
|
includes/vendor/tool/Optimizer/Exception/AmpOptimizerException.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use AmpProject\Exception\AmpException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Marker interface to enable consumers to catch all exceptions for this particular library.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
interface AmpOptimizerException extends AmpException
|
13 |
+
{
|
14 |
+
|
15 |
+
}
|
includes/vendor/tool/Optimizer/Exception/InvalidArgument.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use InvalidArgumentException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when an invalid HTML attribute was detected.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class InvalidArgument extends InvalidArgumentException implements AmpOptimizerException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an InvalidArgument exception for an invalid argument type for numeric comparison.
|
17 |
+
*
|
18 |
+
* @param mixed $argument Argument that was of an invalid type.
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function forNumericComparison($argument)
|
22 |
+
{
|
23 |
+
$type = is_object($argument) ? get_class($argument) : gettype($argument);
|
24 |
+
$message = "Invalid argument type '{$type}' provided for a numeric comparison.";
|
25 |
+
|
26 |
+
return new self($message);
|
27 |
+
}
|
28 |
+
}
|
includes/vendor/tool/Optimizer/Exception/InvalidConfiguration.php
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use DomainException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when an invalid configuration is provided, like in the case of mutually exclusive flags.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class InvalidConfiguration extends DomainException implements AmpOptimizerException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an InvalidConfiguration exception for two mutually exclusive flags.
|
17 |
+
*
|
18 |
+
* @param string $flagA First flag that was used.
|
19 |
+
* @param string $flagB Second flag that was used.
|
20 |
+
* @return self
|
21 |
+
*/
|
22 |
+
public static function forMutuallyExclusiveFlags($flagA, $flagB)
|
23 |
+
{
|
24 |
+
$message = "The configuration flags '{$flagA}' and '{$flagB}' are mutually exclusive "
|
25 |
+
. 'and cannot be set at the same time.';
|
26 |
+
|
27 |
+
return new self($message);
|
28 |
+
}
|
29 |
+
}
|
includes/vendor/tool/Optimizer/Exception/InvalidConfigurationKey.php
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use OutOfBoundsException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when an invalid configuration key was provided.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class InvalidConfigurationKey extends OutOfBoundsException implements AmpOptimizerException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an InvalidConfigurationKey exception for an invalid key.
|
17 |
+
*
|
18 |
+
* @param string $key Key that was invalid.
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function fromKey($key)
|
22 |
+
{
|
23 |
+
$message = "The provided configuration key '{$key}' is not valid.";
|
24 |
+
|
25 |
+
return new self($message);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Instantiate an InvalidConfigurationKey exception for an invalid transformer configuration key.
|
30 |
+
*
|
31 |
+
* @param string $transformer Transformer class or identifier.
|
32 |
+
* @param string $key Key that was invalid.
|
33 |
+
* @return self
|
34 |
+
*/
|
35 |
+
public static function fromTransformerKey($transformer, $key)
|
36 |
+
{
|
37 |
+
$parts = explode('\\', $transformer);
|
38 |
+
$transformer = array_pop($parts);
|
39 |
+
$message = "The provided configuration key '{$key}' is not valid for the transformer '{$transformer}'.";
|
40 |
+
|
41 |
+
return new self($message);
|
42 |
+
}
|
43 |
+
}
|
includes/vendor/tool/Optimizer/Exception/InvalidConfigurationValue.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use InvalidArgumentException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when an invalid configuration value was provided.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class InvalidConfigurationValue extends InvalidArgumentException implements AmpOptimizerException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an InvalidConfigurationValue exception for an invalid value type.
|
17 |
+
*
|
18 |
+
* @param string $key Key that was invalid.
|
19 |
+
* @param string $expected Value type that was expected.
|
20 |
+
* @param string $actual Value type that was actually provided.
|
21 |
+
* @return self
|
22 |
+
*/
|
23 |
+
public static function forInvalidValueType($key, $expected, $actual)
|
24 |
+
{
|
25 |
+
$message = "The configuration key '{$key}' expected a value of type '{$expected}', got '{$actual}' instead.";
|
26 |
+
|
27 |
+
return new self($message);
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Instantiate an InvalidConfigurationValue exception for an invalid value type.
|
32 |
+
*
|
33 |
+
* @param string $key Key that was invalid.
|
34 |
+
* @param string|int $index Index of the sub-value that was invalid.
|
35 |
+
* @param string $expected Value type that was expected.
|
36 |
+
* @param string $actual Value type that was actually provided.
|
37 |
+
* @return self
|
38 |
+
*/
|
39 |
+
public static function forInvalidSubValueType($key, $index, $expected, $actual)
|
40 |
+
{
|
41 |
+
$message = "The configuration value '{$index}' for the key '{$key}' expected a value of type '{$expected}', "
|
42 |
+
. "got '{$actual}' instead.";
|
43 |
+
|
44 |
+
return new self($message);
|
45 |
+
}
|
46 |
+
}
|
includes/vendor/tool/Optimizer/Exception/InvalidHtmlAttribute.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
use AmpProject\Dom\ElementDump;
|
7 |
+
use DomainException;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Exception thrown when an invalid HTML attribute was detected.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class InvalidHtmlAttribute extends DomainException implements AmpOptimizerException
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Instantiate an InvalidHtmlAttribute exception for an invalid attribute value.
|
19 |
+
*
|
20 |
+
* @param string $attributeName Name of the attribute.
|
21 |
+
* @param Element $element Element that contains the invalid attribute.
|
22 |
+
* @return self
|
23 |
+
*/
|
24 |
+
public static function fromAttribute($attributeName, Element $element)
|
25 |
+
{
|
26 |
+
$message = "Invalid value detected for attribute '{$attributeName}': " . new ElementDump($element);
|
27 |
+
|
28 |
+
return new self($message);
|
29 |
+
}
|
30 |
+
}
|
includes/vendor/tool/Optimizer/Exception/UnknownConfigurationClass.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use InvalidArgumentException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when an unknown configuration key was requested.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class UnknownConfigurationClass extends InvalidArgumentException implements AmpOptimizerException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an UnknownConfigurationClass exception for an unknown configuration class.
|
17 |
+
*
|
18 |
+
* @param string $transformerClass Key that was unknown.
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function fromTransformerClass($transformerClass)
|
22 |
+
{
|
23 |
+
$message = "No configuration class was registered for the transformer '{$transformerClass}'.";
|
24 |
+
|
25 |
+
return new self($message);
|
26 |
+
}
|
27 |
+
}
|
includes/vendor/tool/Optimizer/Exception/UnknownConfigurationKey.php
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Exception;
|
4 |
+
|
5 |
+
use InvalidArgumentException;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Exception thrown when an unknown configuration key was requested.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class UnknownConfigurationKey extends InvalidArgumentException implements AmpOptimizerException
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Instantiate an UnknownConfigurationKey exception for an unknown key.
|
17 |
+
*
|
18 |
+
* @param string $key Key that was unknown.
|
19 |
+
* @return self
|
20 |
+
*/
|
21 |
+
public static function fromKey($key)
|
22 |
+
{
|
23 |
+
$message = "The configuration does not contain the requested key '{$key}'.";
|
24 |
+
|
25 |
+
return new self($message);
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Instantiate an UnknownConfigurationKey exception for an unknown transformer configuration key.
|
30 |
+
*
|
31 |
+
* @param string $transformer Transformer class or identifier.
|
32 |
+
* @param string $key Key that was unknown.
|
33 |
+
* @return self
|
34 |
+
*/
|
35 |
+
public static function fromTransformerKey($transformer, $key)
|
36 |
+
{
|
37 |
+
$parts = explode('\\', $transformer);
|
38 |
+
$transformer = array_pop($parts);
|
39 |
+
$message = "The configuration of the transformer '{$transformer}' does not contain "
|
40 |
+
. "the requested key '{$key}'.";
|
41 |
+
|
42 |
+
return new self($message);
|
43 |
+
}
|
44 |
+
}
|
includes/vendor/tool/Optimizer/HeroImage.php
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Element;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Representation of a hero image.
|
9 |
+
*
|
10 |
+
* This is used by the PreloadHeroImages transformer to store (potential) hero images to optimize.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
final class HeroImage
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* <amp-img> element wrapping the actual hero image.
|
19 |
+
*
|
20 |
+
* @var Element|null
|
21 |
+
*/
|
22 |
+
private $ampImg;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Image src tag pointing to the image file.
|
26 |
+
*
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
private $src;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Image media attribute.
|
33 |
+
*
|
34 |
+
* @var string
|
35 |
+
*/
|
36 |
+
private $media;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Image srcset attribute.
|
40 |
+
*
|
41 |
+
* @var string
|
42 |
+
*/
|
43 |
+
private $srcset;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* HeroImage constructor.
|
47 |
+
*
|
48 |
+
* @param string $src Image src tag pointing to the image file.
|
49 |
+
* @param string $media Image media attribute.
|
50 |
+
* @param string $srcset Image srcset attribute.
|
51 |
+
* @param Element|null $ampImg <amp-img> element wrapping the actual hero image, or null if none.
|
52 |
+
*/
|
53 |
+
public function __construct($src, $media, $srcset, $ampImg = null)
|
54 |
+
{
|
55 |
+
$this->src = $src;
|
56 |
+
$this->media = $media;
|
57 |
+
$this->srcset = $srcset;
|
58 |
+
$this->ampImg = $ampImg;
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Get the <amp-img> element wrapping the actual hero image.
|
63 |
+
*
|
64 |
+
* @return Element|null AMP image element or null if none.
|
65 |
+
*/
|
66 |
+
public function getAmpImg()
|
67 |
+
{
|
68 |
+
return $this->ampImg;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Get the image src tag pointing to the image file.
|
73 |
+
*
|
74 |
+
* @return string Image src tag pointing to the image file.
|
75 |
+
*/
|
76 |
+
public function getSrc()
|
77 |
+
{
|
78 |
+
return $this->src;
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Get the image media attribute.
|
83 |
+
*
|
84 |
+
* @return string Image media attribute.
|
85 |
+
*/
|
86 |
+
public function getMedia()
|
87 |
+
{
|
88 |
+
return $this->media;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Get the image srcset attribute.
|
93 |
+
*
|
94 |
+
* @return string Image srcset attribute.
|
95 |
+
*/
|
96 |
+
public function getSrcset()
|
97 |
+
{
|
98 |
+
return $this->srcset;
|
99 |
+
}
|
100 |
+
}
|
includes/vendor/tool/Optimizer/ImageDimensions.php
ADDED
@@ -0,0 +1,433 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Attribute;
|
6 |
+
use AmpProject\Dom\Element;
|
7 |
+
use AmpProject\Layout;
|
8 |
+
use AmpProject\LengthUnit;
|
9 |
+
|
10 |
+
final class ImageDimensions
|
11 |
+
{
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Regular expression pattern to match the trailing unit of a dimension.
|
15 |
+
*
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
const UNIT_REGEX_PATTERN = '/[0-9]+(?<unit>(?:[a-z]+|%))$/i';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Images smaller than 150px are considered tiny.
|
22 |
+
*
|
23 |
+
* @var int
|
24 |
+
*/
|
25 |
+
const TINY_THRESHOLD = 150;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Image for which this represents the dimensions.
|
29 |
+
*
|
30 |
+
* @var Element
|
31 |
+
*/
|
32 |
+
private $image;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Width of the image.
|
36 |
+
*
|
37 |
+
* @var int|float|string|null
|
38 |
+
*/
|
39 |
+
private $width;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Height of the image.
|
43 |
+
*
|
44 |
+
* @var int|float|string|null
|
45 |
+
*/
|
46 |
+
private $height;
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Unit of the width of the image.
|
50 |
+
*
|
51 |
+
* @var int|float|string|null
|
52 |
+
*/
|
53 |
+
private $widthUnit;
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Unit of the height of the image.
|
57 |
+
*
|
58 |
+
* @var int|float|string|null
|
59 |
+
*/
|
60 |
+
private $heightUnit;
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Layout of the image.
|
64 |
+
*
|
65 |
+
* @var string|null
|
66 |
+
*/
|
67 |
+
private $layout;
|
68 |
+
|
69 |
+
/**
|
70 |
+
* ImageDimensions constructor.
|
71 |
+
*
|
72 |
+
* @param Element $image Image to represent the dimensions of.
|
73 |
+
*/
|
74 |
+
public function __construct(Element $image)
|
75 |
+
{
|
76 |
+
$this->image = $image;
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Get the dimensions to use from an element's parent(s).
|
81 |
+
*
|
82 |
+
* @return int[] Array containing the width and the height.
|
83 |
+
*/
|
84 |
+
public function getDimensionsFromParent()
|
85 |
+
{
|
86 |
+
$level = 0;
|
87 |
+
$element = $this->image;
|
88 |
+
while ($element->parentNode && ++$level < 3) {
|
89 |
+
$element = $element->parentNode;
|
90 |
+
|
91 |
+
if (! $element instanceof Element) {
|
92 |
+
continue;
|
93 |
+
}
|
94 |
+
|
95 |
+
$width = $element->hasAttribute(Attribute::WIDTH)
|
96 |
+
? $element->getAttribute(Attribute::WIDTH)
|
97 |
+
: -1;
|
98 |
+
|
99 |
+
$height = $element->hasAttribute(Attribute::HEIGHT)
|
100 |
+
? $element->getAttribute(Attribute::HEIGHT)
|
101 |
+
: -1;
|
102 |
+
|
103 |
+
if (empty($width)) {
|
104 |
+
$width = -1;
|
105 |
+
}
|
106 |
+
|
107 |
+
if (empty($height)) {
|
108 |
+
$height = -1;
|
109 |
+
}
|
110 |
+
|
111 |
+
// Skip elements that don't provide any dimensions.
|
112 |
+
if ($width === -1 && $height === -1) {
|
113 |
+
continue;
|
114 |
+
}
|
115 |
+
|
116 |
+
// If layout is responsive, consider dimensions to be unbounded.
|
117 |
+
if (Layout::RESPONSIVE === $element->getAttribute(Attribute::LAYOUT)) {
|
118 |
+
return [PHP_INT_MAX, PHP_INT_MAX];
|
119 |
+
}
|
120 |
+
|
121 |
+
return [(int)$width, (int)$height];
|
122 |
+
}
|
123 |
+
|
124 |
+
return [-1, -1];
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Check whether the image is to be considered tiny and should be ignored.
|
129 |
+
*
|
130 |
+
* A tiny image is any image with width or height less than 150 pixels and a non-responsive layout.
|
131 |
+
*
|
132 |
+
* @param int|null $threshold Optional. Threshold to use. Defaults to 150 pixels.
|
133 |
+
* @return bool Whether the image is tiny.
|
134 |
+
*/
|
135 |
+
public function isTiny($threshold = self::TINY_THRESHOLD)
|
136 |
+
{
|
137 |
+
// Make sure we have a valid threshold to compare against.
|
138 |
+
if ($threshold === null) {
|
139 |
+
$threshold = self::TINY_THRESHOLD;
|
140 |
+
}
|
141 |
+
|
142 |
+
// For the 'fill' layout, we need to look at the parent container's dimensions.
|
143 |
+
if (! $this->hasWidth() && ! $this->hasHeight()) {
|
144 |
+
if ($this->getLayout() === Layout::FILL) {
|
145 |
+
list($this->width, $this->height) = $this->getDimensionsFromParent();
|
146 |
+
} else {
|
147 |
+
return true;
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
$width = $this->getWidth();
|
152 |
+
$height = $this->getHeight();
|
153 |
+
|
154 |
+
// If one or both of the dimensions are missing, we cannot deduce an aspect ratio.
|
155 |
+
if ($width === null || $height === null) {
|
156 |
+
return true;
|
157 |
+
}
|
158 |
+
|
159 |
+
// If one or both of the dimensions are zero, the entire image will be invisible.
|
160 |
+
if (
|
161 |
+
(is_numeric($width) && $width <= 0)
|
162 |
+
|| (is_numeric($height) && $height <= 0)
|
163 |
+
) {
|
164 |
+
return true;
|
165 |
+
}
|
166 |
+
|
167 |
+
$widthUnit = $this->getWidthUnit();
|
168 |
+
$heightUnit = $this->getHeightUnit();
|
169 |
+
|
170 |
+
// Try to convert absolute units into their equivalent pixel value.
|
171 |
+
if (!empty($widthUnit)) {
|
172 |
+
$numericWidth = $this->getNumericWidth();
|
173 |
+
if (false !== $numericWidth) {
|
174 |
+
$width = $numericWidth;
|
175 |
+
$widthUnit = '';
|
176 |
+
}
|
177 |
+
}
|
178 |
+
if (!empty($heightUnit)) {
|
179 |
+
$numericHeight = $this->getNumericHeight();
|
180 |
+
if (false !== $numericHeight) {
|
181 |
+
$height = $numericHeight;
|
182 |
+
$heightUnit = '';
|
183 |
+
}
|
184 |
+
}
|
185 |
+
|
186 |
+
// If only relative units are in use, we cannot assume much about the final dimensions.
|
187 |
+
if (
|
188 |
+
in_array($widthUnit, LengthUnit::RELATIVE_UNITS, true)
|
189 |
+
&& in_array($heightUnit, LengthUnit::RELATIVE_UNITS, true)
|
190 |
+
) {
|
191 |
+
return false;
|
192 |
+
}
|
193 |
+
|
194 |
+
// If only one of the units is relative, compare the other against the threshold.
|
195 |
+
if (in_array($widthUnit, LengthUnit::RELATIVE_UNITS, true)) {
|
196 |
+
return is_numeric($height) && $height < $threshold;
|
197 |
+
} elseif (in_array($heightUnit, LengthUnit::RELATIVE_UNITS, true)) {
|
198 |
+
return is_numeric($width) && $width < $threshold;
|
199 |
+
}
|
200 |
+
|
201 |
+
switch ($this->getLayout()) {
|
202 |
+
// For 'responsive' layout, the image adapts to the container and can grow beyond its dimensions.
|
203 |
+
case Layout::RESPONSIVE:
|
204 |
+
return false;
|
205 |
+
|
206 |
+
// For 'fixed-height' layout, the width can grow and shrink, so we only compare the height.
|
207 |
+
case Layout::FIXED_HEIGHT:
|
208 |
+
return is_numeric($height) && $height < $threshold;
|
209 |
+
|
210 |
+
// By default, we compare the dimensions against the provided threshold.
|
211 |
+
default:
|
212 |
+
return (is_numeric($width) && $width < $threshold)
|
213 |
+
|| (is_numeric($height) && $height < $threshold);
|
214 |
+
}
|
215 |
+
}
|
216 |
+
|
217 |
+
/**
|
218 |
+
* Check whether the image has a width.
|
219 |
+
*
|
220 |
+
* @return bool Whether the image has a width.
|
221 |
+
*/
|
222 |
+
public function hasWidth()
|
223 |
+
{
|
224 |
+
return $this->getWidth() !== null;
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Check whether the image has a height.
|
229 |
+
*
|
230 |
+
* @return bool Whether the image has a height.
|
231 |
+
*/
|
232 |
+
public function hasHeight()
|
233 |
+
{
|
234 |
+
return $this->getHeight() !== null;
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Check whether the image has a layout.
|
239 |
+
*
|
240 |
+
* @return bool Whether the image has a layout.
|
241 |
+
*/
|
242 |
+
public function hasLayout()
|
243 |
+
{
|
244 |
+
return $this->getLayout() !== '';
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Get the width of the image.
|
249 |
+
*
|
250 |
+
* @return int|float|string|null Width of the image, or null if the image has no width.
|
251 |
+
*/
|
252 |
+
public function getWidth()
|
253 |
+
{
|
254 |
+
if ($this->width === null) {
|
255 |
+
$this->width = -1;
|
256 |
+
$width = $this->image->getAttribute(Attribute::WIDTH);
|
257 |
+
if (trim($width) !== '') {
|
258 |
+
if (is_numeric($width)) {
|
259 |
+
$intWidth = (int)$width;
|
260 |
+
$floatWidth = (float)$width;
|
261 |
+
$this->width = $intWidth == $floatWidth ? $intWidth : $floatWidth;
|
262 |
+
} else {
|
263 |
+
$this->width = $width;
|
264 |
+
}
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
return $this->width !== -1 ? $this->width : null;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Get the height of the image.
|
273 |
+
*
|
274 |
+
* @return int|float|string|null Height of the image, or null if the image has no width.
|
275 |
+
*/
|
276 |
+
public function getHeight()
|
277 |
+
{
|
278 |
+
if ($this->height === null) {
|
279 |
+
$this->height = -1;
|
280 |
+
$height = $this->image->getAttribute(Attribute::HEIGHT);
|
281 |
+
if (trim($height) !== '') {
|
282 |
+
if (is_numeric($height)) {
|
283 |
+
$intHeight = (int)$height;
|
284 |
+
$floatHeight = (float)$height;
|
285 |
+
$this->height = $intHeight == $floatHeight ? $intHeight : $floatHeight;
|
286 |
+
} else {
|
287 |
+
$this->height = $height;
|
288 |
+
}
|
289 |
+
}
|
290 |
+
}
|
291 |
+
|
292 |
+
return $this->height !== -1 ? $this->height : null;
|
293 |
+
}
|
294 |
+
|
295 |
+
/**
|
296 |
+
* Get the numeric width of the image.
|
297 |
+
*
|
298 |
+
* This automatically converts some of the units into numeric pixel values.
|
299 |
+
*
|
300 |
+
* @return int|float|false Numeric width of the image, or false if the width is not numeric.
|
301 |
+
*/
|
302 |
+
public function getNumericWidth()
|
303 |
+
{
|
304 |
+
$width = $this->getWidth();
|
305 |
+
$widthUnit = $this->getWidthUnit();
|
306 |
+
|
307 |
+
if (is_numeric($width)) {
|
308 |
+
return $width;
|
309 |
+
}
|
310 |
+
|
311 |
+
if (!is_string($width) || empty($widthUnit)) {
|
312 |
+
return false;
|
313 |
+
}
|
314 |
+
|
315 |
+
$width = trim(str_replace($widthUnit, '', $width));
|
316 |
+
|
317 |
+
if (!is_numeric($width)) {
|
318 |
+
return false;
|
319 |
+
}
|
320 |
+
|
321 |
+
$intWidth = (int)$width;
|
322 |
+
$floatWidth = (float)$width;
|
323 |
+
$width = $intWidth == $floatWidth ? $intWidth : $floatWidth;
|
324 |
+
|
325 |
+
return LengthUnit::convertIntoPixels($width, $widthUnit);
|
326 |
+
}
|
327 |
+
|
328 |
+
/**
|
329 |
+
* Get the numeric height of the image.
|
330 |
+
*
|
331 |
+
* This automatically converts some of the units into numeric pixel values.
|
332 |
+
*
|
333 |
+
* @return int|float|false Numeric height of the image, or false if the height is not numeric.
|
334 |
+
*/
|
335 |
+
public function getNumericHeight()
|
336 |
+
{
|
337 |
+
$height = $this->getHeight();
|
338 |
+
$heightUnit = $this->getHeightUnit();
|
339 |
+
|
340 |
+
if (is_numeric($height)) {
|
341 |
+
return $height;
|
342 |
+
}
|
343 |
+
|
344 |
+
if (!is_string($height) || empty($heightUnit)) {
|
345 |
+
return false;
|
346 |
+
}
|
347 |
+
|
348 |
+
$height = trim(str_replace($heightUnit, '', $height));
|
349 |
+
|
350 |
+
if (!is_numeric($height)) {
|
351 |
+
return false;
|
352 |
+
}
|
353 |
+
|
354 |
+
$intHeight = (int)$height;
|
355 |
+
$floatHeight = (float)$height;
|
356 |
+
$height = $intHeight == $floatHeight ? $intHeight : $floatHeight;
|
357 |
+
|
358 |
+
return LengthUnit::convertIntoPixels($height, $heightUnit);
|
359 |
+
}
|
360 |
+
|
361 |
+
/**
|
362 |
+
* Get the unit of the width.
|
363 |
+
*
|
364 |
+
* @return string Unit of the width, or an empty string if none found.
|
365 |
+
*/
|
366 |
+
public function getWidthUnit()
|
367 |
+
{
|
368 |
+
if ($this->widthUnit !== null) {
|
369 |
+
return $this->widthUnit;
|
370 |
+
}
|
371 |
+
$width = $this->getWidth();
|
372 |
+
|
373 |
+
if (!is_string($width)) {
|
374 |
+
$this->widthUnit = '';
|
375 |
+
return $this->widthUnit;
|
376 |
+
}
|
377 |
+
|
378 |
+
$matches = [];
|
379 |
+
|
380 |
+
if (!preg_match(self::UNIT_REGEX_PATTERN, $width, $matches)) {
|
381 |
+
$this->widthUnit = '';
|
382 |
+
return $this->widthUnit;
|
383 |
+
}
|
384 |
+
|
385 |
+
$this->widthUnit = strtolower(trim($matches['unit']));
|
386 |
+
return $this->widthUnit;
|
387 |
+
}
|
388 |
+
|
389 |
+
|
390 |
+
/**
|
391 |
+
* Get the unit of the height.
|
392 |
+
*
|
393 |
+
* @return string Unit of the height, or an empty string if none found.
|
394 |
+
*/
|
395 |
+
public function getHeightUnit()
|
396 |
+
{
|
397 |
+
if ($this->heightUnit !== null) {
|
398 |
+
return $this->heightUnit;
|
399 |
+
}
|
400 |
+
$height = $this->getHeight();
|
401 |
+
|
402 |
+
if (!is_string($height)) {
|
403 |
+
$this->heightUnit = '';
|
404 |
+
return $this->heightUnit;
|
405 |
+
}
|
406 |
+
|
407 |
+
$matches = [];
|
408 |
+
|
409 |
+
if (!preg_match(self::UNIT_REGEX_PATTERN, $height, $matches)) {
|
410 |
+
$this->heightUnit = '';
|
411 |
+
return $this->heightUnit;
|
412 |
+
}
|
413 |
+
|
414 |
+
$this->heightUnit = strtolower(trim($matches['unit']));
|
415 |
+
return $this->heightUnit;
|
416 |
+
}
|
417 |
+
|
418 |
+
/**
|
419 |
+
* Get the layout of the image.
|
420 |
+
*
|
421 |
+
* @return string Layout of the image, or an empty string if the image has no layout.
|
422 |
+
*/
|
423 |
+
public function getLayout()
|
424 |
+
{
|
425 |
+
if ($this->layout === null) {
|
426 |
+
$this->layout = $this->image->hasAttribute(Attribute::LAYOUT)
|
427 |
+
? (string)$this->image->getAttribute(Attribute::LAYOUT)
|
428 |
+
: '';
|
429 |
+
}
|
430 |
+
|
431 |
+
return $this->layout;
|
432 |
+
}
|
433 |
+
}
|
includes/vendor/tool/Optimizer/LocalFallback.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
|
7 |
+
final class LocalFallback
|
8 |
+
{
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Domain for which the mapped files will have a local fallback.
|
12 |
+
*/
|
13 |
+
const MAPPED_DOMAIN = Amp::CACHE_HOST;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Root folder where the local fallback files are stored.
|
17 |
+
*
|
18 |
+
* @var string
|
19 |
+
*/
|
20 |
+
const ROOT_FOLDER = __DIR__ . '/../../resources/local_fallback';
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Array of mapped files for which a local fallback is provided.
|
24 |
+
*
|
25 |
+
* @var string[]
|
26 |
+
*/
|
27 |
+
const MAPPED_FILES = [
|
28 |
+
'rtv/metadata',
|
29 |
+
'v0.css',
|
30 |
+
];
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Get the mappings that are provided as local fallbacks.
|
34 |
+
*
|
35 |
+
* @return array Associative array of mappings mapping a URL to a filepath.
|
36 |
+
*/
|
37 |
+
public static function getMappings()
|
38 |
+
{
|
39 |
+
static $mappings = null;
|
40 |
+
|
41 |
+
if ($mappings === null) {
|
42 |
+
$rootFolder = realpath(self::ROOT_FOLDER);
|
43 |
+
foreach (self::MAPPED_FILES as $mappedFile) {
|
44 |
+
$mappings[self::MAPPED_DOMAIN . '/' . $mappedFile] = "{$rootFolder}/{$mappedFile}";
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
return $mappings;
|
49 |
+
}
|
50 |
+
}
|
includes/vendor/tool/Optimizer/TransformationEngine.php
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Document;
|
6 |
+
use AmpProject\RemoteGetRequest;
|
7 |
+
use AmpProject\RemoteRequest\CurlRemoteGetRequest;
|
8 |
+
use ReflectionClass;
|
9 |
+
use ReflectionException;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Transformation engine that accepts HTML and returns optimized HTML.
|
13 |
+
*
|
14 |
+
* @package ampproject/amp-toolbox
|
15 |
+
*/
|
16 |
+
final class TransformationEngine
|
17 |
+
{
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Internal storage for the configuration settings.
|
21 |
+
*
|
22 |
+
* @var Configuration
|
23 |
+
*/
|
24 |
+
private $configuration;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Transport to use for remote requests.
|
28 |
+
*
|
29 |
+
* @var RemoteGetRequest
|
30 |
+
*/
|
31 |
+
private $remoteRequest;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Collection of transformers that were initialized.
|
35 |
+
*
|
36 |
+
* @var Transformer[]
|
37 |
+
*/
|
38 |
+
private $transformers;
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Instantiate a TransformationEngine object.
|
42 |
+
*
|
43 |
+
* @param Configuration|null $configuration Optional. Configuration data to use for setting up the transformers.
|
44 |
+
* @param RemoteGetRequest|null $remoteRequest Optional. Transport to use for remote requests. Defaults to the
|
45 |
+
* CurlRemoteGetRequest implementation shipped with the library.
|
46 |
+
*/
|
47 |
+
public function __construct(Configuration $configuration = null, RemoteGetRequest $remoteRequest = null)
|
48 |
+
{
|
49 |
+
$this->configuration = isset($configuration) ? $configuration : new DefaultConfiguration();
|
50 |
+
$this->remoteRequest = isset($remoteRequest) ? $remoteRequest : new CurlRemoteGetRequest();
|
51 |
+
|
52 |
+
$this->initializeTransformers();
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Apply transformations to the provided DOM document.
|
57 |
+
*
|
58 |
+
* @param Document $document DOM document to apply the transformations to.
|
59 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
60 |
+
* @return void
|
61 |
+
*/
|
62 |
+
public function optimizeDom(Document $document, ErrorCollection $errors)
|
63 |
+
{
|
64 |
+
foreach ($this->transformers as $transformer) {
|
65 |
+
$transformer->transform($document, $errors);
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Apply transformations to the provided string of HTML markup.
|
71 |
+
*
|
72 |
+
* @param string $html HTML markup to apply the transformations to.
|
73 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
74 |
+
* @return string Optimized HTML string.
|
75 |
+
*/
|
76 |
+
public function optimizeHtml($html, ErrorCollection $errors)
|
77 |
+
{
|
78 |
+
$dom = Document::fromHtml($html);
|
79 |
+
$this->optimizeDom($dom, $errors);
|
80 |
+
|
81 |
+
return $dom->saveHTML();
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* Initialize the array of transformers to use.
|
86 |
+
*/
|
87 |
+
private function initializeTransformers()
|
88 |
+
{
|
89 |
+
$this->transformers = [];
|
90 |
+
|
91 |
+
foreach ($this->configuration->get(Configuration::KEY_TRANSFORMERS) as $transformerClass) {
|
92 |
+
$this->transformers[$transformerClass] = new $transformerClass(
|
93 |
+
...$this->getTransformerDependencies($transformerClass)
|
94 |
+
);
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Get the dependencies of a transformer and put them in the correct order.
|
100 |
+
*
|
101 |
+
* @param string $transformerClass Class of the transformer to get the dependencies for.
|
102 |
+
* @return array Array of dependencies in the order as they appear in the transformer's constructor.
|
103 |
+
* @throws ReflectionException If the transformer could not be reflected upon.
|
104 |
+
*/
|
105 |
+
private function getTransformerDependencies($transformerClass)
|
106 |
+
{
|
107 |
+
$constructor = (new ReflectionClass($transformerClass))->getConstructor();
|
108 |
+
|
109 |
+
if ($constructor === null) {
|
110 |
+
return [];
|
111 |
+
}
|
112 |
+
|
113 |
+
$dependencies = [];
|
114 |
+
foreach ($constructor->getParameters() as $parameter) {
|
115 |
+
$dependencyType = null;
|
116 |
+
|
117 |
+
// The use of `ReflectionParameter::getClass()` is deprecated in PHP 8, and is superseded
|
118 |
+
// by `ReflectionParameter::getType()`. See https://github.com/php/php-src/pull/5209.
|
119 |
+
if (PHP_VERSION_ID >= 70100) {
|
120 |
+
if ($parameter->getType()) {
|
121 |
+
/** @var \ReflectionNamedType $returnType */
|
122 |
+
$returnType = $parameter->getType();
|
123 |
+
$dependencyType = new ReflectionClass($returnType->getName());
|
124 |
+
}
|
125 |
+
} else {
|
126 |
+
$dependencyType = $parameter->getClass();
|
127 |
+
}
|
128 |
+
|
129 |
+
if ($dependencyType === null) {
|
130 |
+
// No type provided, so we pass `null` in the hopes that the argument is optional.
|
131 |
+
$dependencies[] = null;
|
132 |
+
continue;
|
133 |
+
}
|
134 |
+
|
135 |
+
if (is_a($dependencyType->name, TransformerConfiguration::class, true)) {
|
136 |
+
$dependencies[] = $this->configuration->getTransformerConfiguration($transformerClass);
|
137 |
+
continue;
|
138 |
+
}
|
139 |
+
|
140 |
+
if (is_a($dependencyType->name, RemoteGetRequest::class, true)) {
|
141 |
+
$dependencies[] = $this->remoteRequest;
|
142 |
+
continue;
|
143 |
+
}
|
144 |
+
|
145 |
+
// Unknown dependency type, so we pass `null` in the hopes that the argument is optional.
|
146 |
+
$dependencies[] = null;
|
147 |
+
}
|
148 |
+
|
149 |
+
return $dependencies;
|
150 |
+
}
|
151 |
+
}
|
includes/vendor/tool/Optimizer/Transformer.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Document;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* A singular transformer that is part of the transformation engine.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
interface Transformer
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Apply transformations to the provided DOM document.
|
17 |
+
*
|
18 |
+
* @param Document $document DOM document to apply the transformations to.
|
19 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
20 |
+
* @return void
|
21 |
+
*/
|
22 |
+
public function transform(Document $document, ErrorCollection $errors);
|
23 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/AmpBoilerplate.php
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\Dom\Document;
|
8 |
+
use AmpProject\Dom\Element;
|
9 |
+
use AmpProject\Optimizer\ErrorCollection;
|
10 |
+
use AmpProject\Optimizer\Transformer;
|
11 |
+
use AmpProject\Tag;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Transformer that removes AMP boilerplate <style> and <noscript> tags in <head>, keeping only the amp-custom <style>
|
15 |
+
* tag. It then (re-)inserts the amp-boilerplate unless the document is marked with the i-amphtml-no-boilerplate
|
16 |
+
* attribute.
|
17 |
+
*
|
18 |
+
* This is ported from the Go optimizer.
|
19 |
+
*
|
20 |
+
* Go:
|
21 |
+
* @version c9993b8ac4d17d1f05d3a1289956dadf3f9c370a
|
22 |
+
* @link https://github.com/ampproject/amppackager/blob/c9993b8ac4d17d1f05d3a1289956dadf3f9c370a/transformer/transformers/ampboilerplate.go
|
23 |
+
*
|
24 |
+
* @package ampproject/amp-toolbox
|
25 |
+
*/
|
26 |
+
final class AmpBoilerplate implements Transformer
|
27 |
+
{
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Apply transformations to the provided DOM document.
|
31 |
+
*
|
32 |
+
* @param Document $document DOM document to apply the transformations to.
|
33 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
34 |
+
* @return void
|
35 |
+
*/
|
36 |
+
public function transform(Document $document, ErrorCollection $errors)
|
37 |
+
{
|
38 |
+
$this->removeStyleAndNoscriptTags($document);
|
39 |
+
|
40 |
+
if ($this->hasNoBoilerplateAttribute($document)) {
|
41 |
+
return;
|
42 |
+
}
|
43 |
+
|
44 |
+
list($boilerplate, $css) = $this->determineBoilerplateAndCss($document->html);
|
45 |
+
|
46 |
+
$styleNode = $document->createElement(Tag::STYLE);
|
47 |
+
$styleNode->setAttribute($boilerplate, '');
|
48 |
+
$document->head->appendChild($styleNode);
|
49 |
+
|
50 |
+
$cssNode = $document->createTextNode($css);
|
51 |
+
$styleNode->appendChild($cssNode);
|
52 |
+
|
53 |
+
if ($boilerplate !== Attribute::AMP_BOILERPLATE) {
|
54 |
+
return;
|
55 |
+
}
|
56 |
+
|
57 |
+
// Regular AMP boilerplate also includes a <noscript> element.
|
58 |
+
$noscriptNode = $document->createElement(Tag::NOSCRIPT);
|
59 |
+
$document->head->appendChild($noscriptNode);
|
60 |
+
|
61 |
+
$noscriptStyleNode = $document->createElement(Tag::STYLE);
|
62 |
+
$noscriptStyleNode->setAttribute($boilerplate, '');
|
63 |
+
$noscriptNode->appendChild($noscriptStyleNode);
|
64 |
+
|
65 |
+
$noscriptCssNode = $document->createTextNode(Amp::BOILERPLATE_NOSCRIPT_CSS);
|
66 |
+
$noscriptStyleNode->appendChild($noscriptCssNode);
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Remove all <style> and <noscript> tags which are for the boilerplate.
|
71 |
+
*
|
72 |
+
* @param Document $document Document to remove the tags from.
|
73 |
+
*/
|
74 |
+
private function removeStyleAndNoscriptTags(Document $document)
|
75 |
+
{
|
76 |
+
/**
|
77 |
+
* Style element.
|
78 |
+
*
|
79 |
+
* @var Element $style
|
80 |
+
*/
|
81 |
+
foreach (iterator_to_array($document->head->getElementsByTagName(Tag::STYLE)) as $style) {
|
82 |
+
if (! $this->isBoilerplateStyle($style)) {
|
83 |
+
continue;
|
84 |
+
}
|
85 |
+
if (Tag::NOSCRIPT === $style->parentNode->nodeName) {
|
86 |
+
$style->parentNode->parentNode->removeChild($style->parentNode);
|
87 |
+
} else {
|
88 |
+
$style->parentNode->removeChild($style);
|
89 |
+
}
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Check whether an element is a boilerplate style.
|
95 |
+
*
|
96 |
+
* @param Element $element Element to check.
|
97 |
+
* @return bool Whether the element is a boilerplate style.
|
98 |
+
*/
|
99 |
+
private function isBoilerplateStyle(Element $element)
|
100 |
+
{
|
101 |
+
foreach (Attribute::ALL_BOILERPLATES as $boilerplate) {
|
102 |
+
if ($element->hasAttribute($boilerplate)) {
|
103 |
+
return true;
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
return false;
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Check whether it was already determined the boilerplate should be removed.
|
112 |
+
*
|
113 |
+
* We want to ensure we don't apply re-add the boilerplate again if it was already removed via SSR.
|
114 |
+
*
|
115 |
+
* @param Document $document DOM document to check for the attribute.
|
116 |
+
* @return bool Whether it was determined that the boilerplate should be removed.
|
117 |
+
*/
|
118 |
+
private function hasNoBoilerplateAttribute(Document $document)
|
119 |
+
{
|
120 |
+
if ($document->html->hasAttribute(Amp::NO_BOILERPLATE_ATTRIBUTE)) {
|
121 |
+
return true;
|
122 |
+
}
|
123 |
+
|
124 |
+
return false;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Determine and return the boilerplate attribute and inline CSS to use.
|
129 |
+
*
|
130 |
+
* @param Element $htmlElement HTML DOM element to check against.
|
131 |
+
* @return array Tuple containing the $boilerplate and $css to use.
|
132 |
+
*/
|
133 |
+
private function determineBoilerplateAndCss(Element $htmlElement)
|
134 |
+
{
|
135 |
+
$boilerplate = Attribute::AMP_BOILERPLATE;
|
136 |
+
$css = Amp::BOILERPLATE_CSS;
|
137 |
+
|
138 |
+
foreach (Attribute::ALL_AMP4ADS as $attribute) {
|
139 |
+
if (
|
140 |
+
$htmlElement->hasAttribute($attribute)
|
141 |
+
|| (
|
142 |
+
$htmlElement->getAttribute(Document::EMOJI_AMP_ATTRIBUTE_PLACEHOLDER) === str_replace(
|
143 |
+
Attribute::AMP_EMOJI,
|
144 |
+
'',
|
145 |
+
$attribute
|
146 |
+
)
|
147 |
+
)
|
148 |
+
) {
|
149 |
+
$boilerplate = Attribute::AMP4ADS_BOILERPLATE;
|
150 |
+
$css = Amp::AMP4ADS_AND_AMP4EMAIL_BOILERPLATE_CSS;
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
foreach (Attribute::ALL_AMP4EMAIL as $attribute) {
|
155 |
+
if (
|
156 |
+
$htmlElement->hasAttribute($attribute)
|
157 |
+
|| (
|
158 |
+
$htmlElement->getAttribute(Document::EMOJI_AMP_ATTRIBUTE_PLACEHOLDER) === str_replace(
|
159 |
+
Attribute::AMP_EMOJI,
|
160 |
+
'',
|
161 |
+
$attribute
|
162 |
+
)
|
163 |
+
)
|
164 |
+
) {
|
165 |
+
$boilerplate = Attribute::AMP4EMAIL_BOILERPLATE;
|
166 |
+
$css = Amp::AMP4ADS_AND_AMP4EMAIL_BOILERPLATE_CSS;
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
return [$boilerplate, $css];
|
171 |
+
}
|
172 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/AmpBoilerplateErrorHandler.php
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Attribute;
|
6 |
+
use AmpProject\Dom\Document;
|
7 |
+
use AmpProject\Optimizer\ErrorCollection;
|
8 |
+
use AmpProject\Optimizer\Transformer;
|
9 |
+
use AmpProject\Tag;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* AmpBoilerplateErrorHandler - adds amp-onerror handler to disable boilerplate early on runtime error.
|
13 |
+
*
|
14 |
+
* This ensures that the boilerplate does not hide the content for several seconds if an error occurred
|
15 |
+
* while loading the AMP runtime that could already be detected much earlier.
|
16 |
+
*
|
17 |
+
* @package ampproject/amp-toolbox
|
18 |
+
*/
|
19 |
+
final class AmpBoilerplateErrorHandler implements Transformer
|
20 |
+
{
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Error handler script to be added to the document's <head>.
|
24 |
+
*
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
const ERROR_HANDLER = 'document.querySelector("script[src*=\'/v0.js\']").onerror=function(){'
|
28 |
+
. 'document.querySelector(\'style[amp-boilerplate]\').textContent=\'\'}';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Apply transformations to the provided DOM document.
|
32 |
+
*
|
33 |
+
* @param Document $document DOM document to apply the transformations to.
|
34 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
public function transform(Document $document, ErrorCollection $errors)
|
38 |
+
{
|
39 |
+
if ($document->html->hasAttribute(Attribute::I_AMPHTML_NO_BOILERPLATE)) {
|
40 |
+
// Boilerplate was removed, so no need for the amp-onerror handler.
|
41 |
+
return;
|
42 |
+
}
|
43 |
+
|
44 |
+
$document->head->appendChild(
|
45 |
+
$document->createElementWithAttributes(
|
46 |
+
Tag::SCRIPT,
|
47 |
+
[
|
48 |
+
Attribute::AMP_ONERROR => null,
|
49 |
+
],
|
50 |
+
self::ERROR_HANDLER
|
51 |
+
)
|
52 |
+
);
|
53 |
+
}
|
54 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/AmpRuntimeCss.php
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\Dom\Document;
|
8 |
+
use AmpProject\Dom\Element;
|
9 |
+
use AmpProject\Optimizer\Configuration\AmpRuntimeCssConfiguration;
|
10 |
+
use AmpProject\Optimizer\Error;
|
11 |
+
use AmpProject\Optimizer\ErrorCollection;
|
12 |
+
use AmpProject\Optimizer\TransformerConfiguration;
|
13 |
+
use AmpProject\RemoteGetRequest;
|
14 |
+
use AmpProject\Optimizer\Transformer;
|
15 |
+
use AmpProject\RuntimeVersion;
|
16 |
+
use AmpProject\Tag;
|
17 |
+
use Exception;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Transformer adding https://cdn.ampproject.org/v0.css if server-side-rendering is applied (known by the presence of
|
21 |
+
* <style amp-runtime> tag). AMP runtime css (v0.css) will always be inlined as it'll get automatically updated to the
|
22 |
+
* latest version once the AMP runtime has loaded.
|
23 |
+
*
|
24 |
+
* This is ported from the NodeJS optimizer while verifying against the Go version.
|
25 |
+
*
|
26 |
+
* NodeJS:
|
27 |
+
* @version 6f465eb24b05acf74d39541151c17b8d8d97450d
|
28 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/6f465eb24b05acf74d39541151c17b8d8d97450d/packages/optimizer/lib/transformers/AmpBoilerplateTransformer.js
|
29 |
+
*
|
30 |
+
* Go:
|
31 |
+
* @version c9993b8ac4d17d1f05d3a1289956dadf3f9c370a
|
32 |
+
* @link https://github.com/ampproject/amppackager/blob/c9993b8ac4d17d1f05d3a1289956dadf3f9c370a/transformer/transformers/ampruntimecss.go
|
33 |
+
*
|
34 |
+
* @package ampproject/amp-toolbox
|
35 |
+
*/
|
36 |
+
final class AmpRuntimeCss implements Transformer
|
37 |
+
{
|
38 |
+
|
39 |
+
/**
|
40 |
+
* XPath query to fetch the <style amp-runtime> element.
|
41 |
+
*
|
42 |
+
* @var string
|
43 |
+
*/
|
44 |
+
const AMP_RUNTIME_STYLE_XPATH = './/style[ @amp-runtime ]';
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Name of the boilerplate style file.
|
48 |
+
*
|
49 |
+
* @var string
|
50 |
+
*/
|
51 |
+
const V0_CSS = 'v0.css';
|
52 |
+
|
53 |
+
/**
|
54 |
+
* URL of the boilerplate style file.
|
55 |
+
*
|
56 |
+
* @var string
|
57 |
+
*/
|
58 |
+
const V0_CSS_URL = Amp::CACHE_HOST . '/' . self::V0_CSS;
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Configuration store to use.
|
62 |
+
*
|
63 |
+
* @var TransformerConfiguration
|
64 |
+
*/
|
65 |
+
private $configuration;
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Transport to use for remote requests.
|
69 |
+
*
|
70 |
+
* @var RemoteGetRequest
|
71 |
+
*/
|
72 |
+
private $remoteRequest;
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Instantiate an AmpRuntimeCss object.
|
76 |
+
*
|
77 |
+
* @param TransformerConfiguration $configuration Configuration store to use.
|
78 |
+
* @param RemoteGetRequest $remoteRequest Transport to use for remote requests.
|
79 |
+
*/
|
80 |
+
public function __construct(TransformerConfiguration $configuration, RemoteGetRequest $remoteRequest)
|
81 |
+
{
|
82 |
+
$this->configuration = $configuration;
|
83 |
+
$this->remoteRequest = $remoteRequest;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Apply transformations to the provided DOM document.
|
88 |
+
*
|
89 |
+
* @param Document $document DOM document to apply the transformations to.
|
90 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
91 |
+
* @return void
|
92 |
+
*/
|
93 |
+
public function transform(Document $document, ErrorCollection $errors)
|
94 |
+
{
|
95 |
+
$ampRuntimeStyle = $this->findAmpRuntimeStyle($document, $errors);
|
96 |
+
|
97 |
+
if (! $ampRuntimeStyle) {
|
98 |
+
return;
|
99 |
+
}
|
100 |
+
|
101 |
+
$this->addStaticCss($document, $ampRuntimeStyle, $errors);
|
102 |
+
}
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Find the <style amp-runtime> element.
|
106 |
+
*
|
107 |
+
* @param Document $document Document to find the element in.
|
108 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
109 |
+
* @return Element|false DOM element for the <style amp-runtime> tag, or false if not found.
|
110 |
+
*/
|
111 |
+
private function findAmpRuntimeStyle(Document $document, ErrorCollection $errors)
|
112 |
+
{
|
113 |
+
$ampRuntimeStyle = $document->xpath
|
114 |
+
->query(self::AMP_RUNTIME_STYLE_XPATH, $document->head)
|
115 |
+
->item(0);
|
116 |
+
|
117 |
+
if (! $ampRuntimeStyle instanceof Element) {
|
118 |
+
$version = $this->configuration->get(AmpRuntimeCssConfiguration::VERSION);
|
119 |
+
$errors->add(Error\CannotInlineRuntimeCss::fromMissingAmpRuntimeStyle($version));
|
120 |
+
return false;
|
121 |
+
}
|
122 |
+
|
123 |
+
return $ampRuntimeStyle;
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Add the static boilerplate CSS to the <style amp-runtime> element.
|
128 |
+
*
|
129 |
+
* @param Document $document Document to add the static CSS to.
|
130 |
+
* @param Element $ampRuntimeStyle DOM element for the <style amp-runtime> tag to add the static CSS to.
|
131 |
+
* @param ErrorCollection $errors Error collection to add errors to.
|
132 |
+
*/
|
133 |
+
private function addStaticCss(Document $document, Element $ampRuntimeStyle, ErrorCollection $errors)
|
134 |
+
{
|
135 |
+
$version = $this->configuration->get(AmpRuntimeCssConfiguration::VERSION);
|
136 |
+
|
137 |
+
// We can always inline v0.css as the AMP runtime will take care of keeping v0.css in sync.
|
138 |
+
try {
|
139 |
+
$this->inlineCss($ampRuntimeStyle, $version);
|
140 |
+
} catch (Exception $exception) {
|
141 |
+
$errors->add(Error\CannotInlineRuntimeCss::fromException($exception, $ampRuntimeStyle, $version));
|
142 |
+
$this->linkCss($document, $ampRuntimeStyle);
|
143 |
+
$ampRuntimeStyle->parentNode->removeChild($ampRuntimeStyle);
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Insert the boilerplate style as inline CSS.
|
149 |
+
*
|
150 |
+
* @param Element $ampRuntimeStyle DOM element for the <style amp-runtime> tag to inline the CSS into.
|
151 |
+
* @param string $version Version of the boilerplate style to use.
|
152 |
+
*/
|
153 |
+
private function inlineCss(Element $ampRuntimeStyle, $version)
|
154 |
+
{
|
155 |
+
// Use version passed in via params if available, otherwise fetch the current prod version.
|
156 |
+
if (! empty($version)) {
|
157 |
+
$v0CssUrl = RuntimeVersion::appendRuntimeVersion(Amp::CACHE_HOST, $version) . '/' . self::V0_CSS;
|
158 |
+
} else {
|
159 |
+
$v0CssUrl = self::V0_CSS_URL;
|
160 |
+
$options = [
|
161 |
+
RuntimeVersion::OPTION_CANARY => $this->configuration->get(AmpRuntimeCssConfiguration::CANARY)
|
162 |
+
];
|
163 |
+
$version = (new RuntimeVersion($this->remoteRequest))->currentVersion($options);
|
164 |
+
}
|
165 |
+
|
166 |
+
$ampRuntimeStyle->setAttribute(Attribute::I_AMPHTML_VERSION, $version);
|
167 |
+
|
168 |
+
$styles = $this->configuration->get(AmpRuntimeCssConfiguration::STYLES);
|
169 |
+
|
170 |
+
if (empty($styles)) {
|
171 |
+
$response = $this->remoteRequest->get($v0CssUrl);
|
172 |
+
$statusCode = $response->getStatusCode();
|
173 |
+
|
174 |
+
if ($statusCode < 200 || $statusCode >= 300) {
|
175 |
+
return;
|
176 |
+
}
|
177 |
+
|
178 |
+
$styles = $response->getBody();
|
179 |
+
}
|
180 |
+
|
181 |
+
$ampRuntimeStyle->textContent = $styles;
|
182 |
+
}
|
183 |
+
|
184 |
+
/**
|
185 |
+
* Insert the boilerplate style as inline CSS.
|
186 |
+
*
|
187 |
+
* @param Document $document Document to link the CSS in.
|
188 |
+
* @param Element $ampRuntimeStyle DOM element for the <style amp-runtime> tag to inline the CSS into.
|
189 |
+
*/
|
190 |
+
private function linkCss(Document $document, Element $ampRuntimeStyle)
|
191 |
+
{
|
192 |
+
$cssStyleNode = $document->createElement(Tag::LINK);
|
193 |
+
$cssStyleNode->setAttribute(Attribute::REL, Attribute::REL_STYLESHEET);
|
194 |
+
$cssStyleNode->setAttribute(Attribute::HREF, self::V0_CSS_URL);
|
195 |
+
|
196 |
+
$ampRuntimeStyle->parentNode->insertBefore($cssStyleNode, $ampRuntimeStyle);
|
197 |
+
}
|
198 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/OptimizeAmpBind.php
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\Dom\Document;
|
8 |
+
use AmpProject\Dom\Element;
|
9 |
+
use AmpProject\Dom\NodeWalker;
|
10 |
+
use AmpProject\Extension;
|
11 |
+
use AmpProject\Optimizer\Configuration\OptimizeAmpBindConfiguration;
|
12 |
+
use AmpProject\Optimizer\ErrorCollection;
|
13 |
+
use AmpProject\Optimizer\Transformer;
|
14 |
+
use AmpProject\Optimizer\TransformerConfiguration;
|
15 |
+
use AmpProject\Tag;
|
16 |
+
use DOMAttr;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* OptimizeAmpBind - inject a querySelectorAll query-able i-amphtml-binding attribute on elements with bindings.
|
20 |
+
*
|
21 |
+
* This is ported from the NodeJS optimizer.
|
22 |
+
*
|
23 |
+
* NodeJS:
|
24 |
+
*
|
25 |
+
* @version 4aa99eb6e16a39bb562acb67efdfd3ee3d993a98
|
26 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/4aa99eb6e16a39bb562acb67efdfd3ee3d993a98/packages/optimizer/lib/transformers/OptimizeAmpBind.js
|
27 |
+
*
|
28 |
+
* @package ampproject/amp-toolbox
|
29 |
+
*/
|
30 |
+
final class OptimizeAmpBind implements Transformer
|
31 |
+
{
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Configuration store to use.
|
35 |
+
*
|
36 |
+
* @var TransformerConfiguration
|
37 |
+
*/
|
38 |
+
private $configuration;
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Instantiate an OptimizeAmpBind object.
|
42 |
+
*
|
43 |
+
* @param TransformerConfiguration $configuration Configuration store to use.
|
44 |
+
*/
|
45 |
+
public function __construct(TransformerConfiguration $configuration)
|
46 |
+
{
|
47 |
+
$this->configuration = $configuration;
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Apply transformations to the provided DOM document.
|
52 |
+
*
|
53 |
+
* @param Document $document DOM document to apply the transformations to.
|
54 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
55 |
+
* @return void
|
56 |
+
*/
|
57 |
+
public function transform(Document $document, ErrorCollection $errors)
|
58 |
+
{
|
59 |
+
if ($this->configuration->get(OptimizeAmpBindConfiguration::ENABLED) === false) {
|
60 |
+
return;
|
61 |
+
}
|
62 |
+
|
63 |
+
if (!$this->hasAmpBindScriptElement($document)) {
|
64 |
+
return;
|
65 |
+
}
|
66 |
+
|
67 |
+
$document->html->addBooleanAttribute(Attribute::I_AMPHTML_BINDING);
|
68 |
+
|
69 |
+
for ($node = $document->html; $node !== null; $node = NodeWalker::nextNode($node)) {
|
70 |
+
if (!$node instanceof Element) {
|
71 |
+
continue;
|
72 |
+
}
|
73 |
+
|
74 |
+
if (Amp::isTemplate($node)) {
|
75 |
+
$node = NodeWalker::skipNodeAndChildren($node);
|
76 |
+
continue;
|
77 |
+
}
|
78 |
+
|
79 |
+
/** @var DOMAttr $attribute */
|
80 |
+
foreach ($node->attributes as $attribute) {
|
81 |
+
if (strpos($attribute->name, Document::AMP_BIND_DATA_ATTR_PREFIX) === 0) {
|
82 |
+
$node->addBooleanAttribute(Attribute::I_AMPHTML_BINDING);
|
83 |
+
break;
|
84 |
+
}
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Check whether the document has an amp-bind script element.
|
91 |
+
*
|
92 |
+
* @param Document $document Document to check.
|
93 |
+
* @return bool Whether the document has an amp-bind script element.
|
94 |
+
*/
|
95 |
+
private function hasAmpBindScriptElement(Document $document)
|
96 |
+
{
|
97 |
+
for ($element = $document->head->firstChild; $element !== null; $element = $element->nextSibling) {
|
98 |
+
if (!$element instanceof Element) {
|
99 |
+
continue;
|
100 |
+
}
|
101 |
+
|
102 |
+
if ($element->tagName !== Tag::SCRIPT) {
|
103 |
+
continue;
|
104 |
+
}
|
105 |
+
|
106 |
+
if ($element->getAttribute(Attribute::CUSTOM_ELEMENT) !== Extension::BIND) {
|
107 |
+
continue;
|
108 |
+
}
|
109 |
+
|
110 |
+
return true;
|
111 |
+
}
|
112 |
+
|
113 |
+
return false;
|
114 |
+
}
|
115 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/PreloadHeroImage.php
ADDED
@@ -0,0 +1,726 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\Dom\Document;
|
8 |
+
use AmpProject\Dom\Element;
|
9 |
+
use AmpProject\Dom\NodeWalker;
|
10 |
+
use AmpProject\Exception\FailedToParseUrl;
|
11 |
+
use AmpProject\Extension;
|
12 |
+
use AmpProject\Layout;
|
13 |
+
use AmpProject\Optimizer\Configuration\PreloadHeroImageConfiguration;
|
14 |
+
use AmpProject\Optimizer\Error;
|
15 |
+
use AmpProject\Optimizer\ErrorCollection;
|
16 |
+
use AmpProject\Optimizer\HeroImage;
|
17 |
+
use AmpProject\Optimizer\ImageDimensions;
|
18 |
+
use AmpProject\Optimizer\Transformer;
|
19 |
+
use AmpProject\Optimizer\TransformerConfiguration;
|
20 |
+
use AmpProject\RequestDestination;
|
21 |
+
use AmpProject\Tag;
|
22 |
+
use AmpProject\Url;
|
23 |
+
use DOMNode;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* PreloadHeroImage - this transformer optimizes image rendering times for hero images. For hero images it will:
|
27 |
+
*
|
28 |
+
* 1. Inject a preload hint (if possible)
|
29 |
+
* 2. Generate an img tag enabling the browser to render the image without the AMP runtime being loaded.
|
30 |
+
*
|
31 |
+
* Hero images are either identified automatically or can be explicitly defined by adding an `data-hero` attribute to
|
32 |
+
* the element.
|
33 |
+
*
|
34 |
+
* This transformer supports the following options:
|
35 |
+
*
|
36 |
+
* * `preloadHeroImage`: [true|false] - enables or disables hero image preloading. The default is `true`.
|
37 |
+
*
|
38 |
+
* This is ported from the NodeJS optimizer.
|
39 |
+
*
|
40 |
+
* @version 3429af9d91e2c9efe1af85757499e5a308755f5f
|
41 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/3429af9d91e2c9efe1af85757499e5a308755f5f/packages/optimizer/lib/transformers/PreloadHeroImage.js
|
42 |
+
*
|
43 |
+
* @package ampproject/amp-toolbox
|
44 |
+
*/
|
45 |
+
final class PreloadHeroImage implements Transformer
|
46 |
+
{
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Class(es) to apply to a serverside-rendered image element.
|
50 |
+
*
|
51 |
+
* @var string
|
52 |
+
*/
|
53 |
+
const SSR_IMAGE_CLASS = 'i-amphtml-fill-content i-amphtml-replaced-content';
|
54 |
+
|
55 |
+
/**
|
56 |
+
* List of attributes to copy onto an SSR'ed image.
|
57 |
+
*
|
58 |
+
* @var string[]
|
59 |
+
*/
|
60 |
+
const ATTRIBUTES_TO_COPY = [
|
61 |
+
Attribute::ALT,
|
62 |
+
Attribute::ATTRIBUTION,
|
63 |
+
Attribute::REFERRERPOLICY,
|
64 |
+
Attribute::SRC,
|
65 |
+
Attribute::SRCSET,
|
66 |
+
Attribute::SIZES,
|
67 |
+
Attribute::TITLE,
|
68 |
+
];
|
69 |
+
|
70 |
+
/**
|
71 |
+
* List of attributes to inline onto an SSR'ed image.
|
72 |
+
*
|
73 |
+
* @var string[]
|
74 |
+
*/
|
75 |
+
const ATTRIBUTES_TO_INLINE = [
|
76 |
+
Attribute::OBJECT_FIT,
|
77 |
+
Attribute::OBJECT_POSITION,
|
78 |
+
];
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Maximum number of hero images defined via data-hero attribute.
|
82 |
+
*
|
83 |
+
* @var int
|
84 |
+
*/
|
85 |
+
const DATA_HERO_MAX = 2;
|
86 |
+
|
87 |
+
/**
|
88 |
+
* List of AMP elements that are an embed that can have a placeholder.
|
89 |
+
*
|
90 |
+
* The array has values assigned so that we can do a fast hash lookup on the element name.
|
91 |
+
*
|
92 |
+
* @var bool[]
|
93 |
+
*/
|
94 |
+
const AMP_EMBEDS = [
|
95 |
+
Extension::AD => true,
|
96 |
+
Extension::ANIM => true,
|
97 |
+
Extension::BRIGHTCOVE => true,
|
98 |
+
Extension::DAILYMOTION => true,
|
99 |
+
Extension::FACEBOOK => true,
|
100 |
+
Extension::GFYCAT => true,
|
101 |
+
Extension::IFRAME => true,
|
102 |
+
Extension::IMGUR => true,
|
103 |
+
Extension::INSTAGRAM => true,
|
104 |
+
Extension::PINTEREST => true,
|
105 |
+
Extension::REDDIT => true,
|
106 |
+
Extension::TWITTER => true,
|
107 |
+
Extension::VIDEO => true,
|
108 |
+
Extension::VIDEO_IFRAME => true,
|
109 |
+
Extension::VIMEO => true,
|
110 |
+
Extension::WISTIA_PLAYER => true,
|
111 |
+
Extension::YOUTUBE => true,
|
112 |
+
];
|
113 |
+
|
114 |
+
/**
|
115 |
+
* XPath query to relatively fetch all noscript > img elements.
|
116 |
+
*
|
117 |
+
* @var string
|
118 |
+
*/
|
119 |
+
const NOSCRIPT_IMG_XPATH_QUERY = './noscript/img';
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Regular expression pattern to extract the URL from a CSS background-image property.
|
123 |
+
*
|
124 |
+
* @var string
|
125 |
+
*/
|
126 |
+
const CSS_BACKGROUND_IMAGE_URL_REGEX_PATTERN = '/background-image\s*:\s*url\(\s*(?<url>[^)]*\s*)/i';
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Configuration store to use.
|
130 |
+
*
|
131 |
+
* @var TransformerConfiguration
|
132 |
+
*/
|
133 |
+
private $configuration;
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Reference node to attach preload links to.
|
137 |
+
*
|
138 |
+
* @var Element|null
|
139 |
+
*/
|
140 |
+
private $preloadReferenceNode;
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Inline style backup attribute that stores inline styles that are being moved to <style amp-custom>.
|
144 |
+
*
|
145 |
+
* An empty string signifies that no inline style backup is available.
|
146 |
+
*
|
147 |
+
* @var string
|
148 |
+
*/
|
149 |
+
private $inlineStyleBackupAttribute;
|
150 |
+
|
151 |
+
/**
|
152 |
+
* Instantiate a PreloadHeroImage object.
|
153 |
+
*
|
154 |
+
* @param TransformerConfiguration $configuration Configuration store to use.
|
155 |
+
*/
|
156 |
+
public function __construct(TransformerConfiguration $configuration)
|
157 |
+
{
|
158 |
+
$this->configuration = $configuration;
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Apply transformations to the provided DOM document.
|
163 |
+
*
|
164 |
+
* @param Document $document DOM document to apply the transformations to.
|
165 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
166 |
+
* @return void
|
167 |
+
*/
|
168 |
+
public function transform(Document $document, ErrorCollection $errors)
|
169 |
+
{
|
170 |
+
if ($this->configuration->get(PreloadHeroImageConfiguration::PRELOAD_HERO_IMAGE) === false) {
|
171 |
+
return;
|
172 |
+
}
|
173 |
+
|
174 |
+
$this->inlineStyleBackupAttribute = $this->configuration->get(
|
175 |
+
PreloadHeroImageConfiguration::INLINE_STYLE_BACKUP_ATTRIBUTE
|
176 |
+
);
|
177 |
+
|
178 |
+
$heroImages = $this->findHeroImages($document);
|
179 |
+
$heroImageCount = count($heroImages);
|
180 |
+
if ($heroImageCount > self::DATA_HERO_MAX) {
|
181 |
+
$errors->add(Error\TooManyHeroImages::whenPastMaximum());
|
182 |
+
$heroImageCount = self::DATA_HERO_MAX;
|
183 |
+
}
|
184 |
+
|
185 |
+
for ($index = 0; $index < $heroImageCount; $index++) {
|
186 |
+
$this->removeLazyLoading($heroImages[$index]);
|
187 |
+
$this->generatePreload($heroImages[$index], $document, $errors);
|
188 |
+
$this->generateImg($heroImages[$index], $document);
|
189 |
+
}
|
190 |
+
}
|
191 |
+
|
192 |
+
/**
|
193 |
+
* Find the hero images to optimize.
|
194 |
+
*
|
195 |
+
* @param Document $document Document to look for hero images in.
|
196 |
+
* @return HeroImage[] Array of hero images to optimize.
|
197 |
+
*/
|
198 |
+
private function findHeroImages(Document $document)
|
199 |
+
{
|
200 |
+
$heroImages = [];
|
201 |
+
$heroImageCandidates = [];
|
202 |
+
$heroImageFallbacks = [];
|
203 |
+
$previousHeroImageFallback = null;
|
204 |
+
$seenParagraphCount = 0;
|
205 |
+
$node = $document->body;
|
206 |
+
|
207 |
+
while ($node !== null) {
|
208 |
+
if (! $node instanceof Element) {
|
209 |
+
$node = NodeWalker::nextNode($node);
|
210 |
+
continue;
|
211 |
+
}
|
212 |
+
|
213 |
+
if ($node->tagName === Tag::P) {
|
214 |
+
$seenParagraphCount++;
|
215 |
+
}
|
216 |
+
|
217 |
+
$heroImage = $this->detectImageWithAttribute($node, Attribute::DATA_HERO);
|
218 |
+
if ($heroImage) {
|
219 |
+
$heroImages[] = $heroImage;
|
220 |
+
} elseif (count($heroImageCandidates) < self::DATA_HERO_MAX) {
|
221 |
+
$heroImageCandidate = $this->detectImageWithAttribute($node, Attribute::DATA_HERO_CANDIDATE);
|
222 |
+
if ($heroImageCandidate) {
|
223 |
+
$heroImageCandidates[] = $heroImageCandidate;
|
224 |
+
} elseif ($seenParagraphCount < 2 && count($heroImageFallbacks) < self::DATA_HERO_MAX) {
|
225 |
+
$heroImageFallback = $this->detectPossibleHeroImageFallbacks($node);
|
226 |
+
|
227 |
+
// Ensure we don't flag the same image twice. This can happen for placeholder images, which are
|
228 |
+
// flagged on their own and as their parent's placeholder.
|
229 |
+
if (
|
230 |
+
$heroImageFallback
|
231 |
+
&& (
|
232 |
+
! $previousHeroImageFallback
|
233 |
+
|| $heroImageFallback->getAmpImg() !== $previousHeroImageFallback->getAmpImg()
|
234 |
+
)
|
235 |
+
) {
|
236 |
+
$heroImageFallbacks[] = $heroImageFallback;
|
237 |
+
$previousHeroImageFallback = $heroImageFallback;
|
238 |
+
}
|
239 |
+
}
|
240 |
+
}
|
241 |
+
|
242 |
+
if (Amp::isTemplate($node)) {
|
243 |
+
// Ignore images inside templates.
|
244 |
+
$node = NodeWalker::skipNodeAndChildren($node);
|
245 |
+
} else {
|
246 |
+
$node = NodeWalker::nextNode($node);
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
if (count($heroImages) > 0) {
|
251 |
+
return $heroImages;
|
252 |
+
}
|
253 |
+
|
254 |
+
while (count($heroImages) < self::DATA_HERO_MAX && count($heroImageCandidates) > 0) {
|
255 |
+
$heroImages[] = array_shift($heroImageCandidates);
|
256 |
+
}
|
257 |
+
|
258 |
+
if (count($heroImages) < 1 && count($heroImageFallbacks) > 0) {
|
259 |
+
$heroImages[] = array_shift($heroImageFallbacks);
|
260 |
+
}
|
261 |
+
|
262 |
+
return $heroImages;
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Detect a hero image with a specific attribute.
|
267 |
+
*
|
268 |
+
* This is used for detecting an image marked with data-hero or data-hero-candidate
|
269 |
+
*
|
270 |
+
* @param Element $element Element to detect for.
|
271 |
+
* @param string $attribute Attribute to look for.
|
272 |
+
* @return HeroImage|null Detected hero image, or null if none detected.
|
273 |
+
* @throws FailedToParseUrl Exception when the URL or Base URL is malformed.
|
274 |
+
*/
|
275 |
+
private function detectImageWithAttribute(Element $element, $attribute)
|
276 |
+
{
|
277 |
+
if (!$element->hasAttribute($attribute)) {
|
278 |
+
return null;
|
279 |
+
}
|
280 |
+
|
281 |
+
$src = $element->getAttribute(Attribute::SRC);
|
282 |
+
if ($element->tagName === Extension::IMG && (new Url($src))->isValidNonDataUrl()) {
|
283 |
+
return new HeroImage(
|
284 |
+
$src,
|
285 |
+
$element->getAttribute(Attribute::MEDIA),
|
286 |
+
$element->getAttribute(Attribute::SRCSET),
|
287 |
+
$element
|
288 |
+
);
|
289 |
+
}
|
290 |
+
|
291 |
+
if ($this->isAmpEmbed($element)) {
|
292 |
+
$placeholderImage = $this->getPlaceholderImage($element);
|
293 |
+
if (null !== $placeholderImage) {
|
294 |
+
return $placeholderImage;
|
295 |
+
}
|
296 |
+
}
|
297 |
+
|
298 |
+
$cssBackgroundImage = $this->getCssBackgroundImageUrl($element);
|
299 |
+
|
300 |
+
if ((new Url($cssBackgroundImage))->isValidNonDataUrl()) {
|
301 |
+
return new HeroImage(
|
302 |
+
$cssBackgroundImage,
|
303 |
+
$element->getAttribute(Attribute::MEDIA),
|
304 |
+
$element->getAttribute(Attribute::SRCSET),
|
305 |
+
$element
|
306 |
+
);
|
307 |
+
}
|
308 |
+
|
309 |
+
return null;
|
310 |
+
}
|
311 |
+
|
312 |
+
/**
|
313 |
+
* Detect a possible hero image fallback.
|
314 |
+
*
|
315 |
+
* The hero image here can come from one of <amp-img>, <amp-video>, <amp-iframe>, <amp-video-iframe>.
|
316 |
+
*
|
317 |
+
* @param Element $element Element to detect for.
|
318 |
+
* @return HeroImage|null Detected hero image fallback, or null if none detected.
|
319 |
+
*/
|
320 |
+
private function detectPossibleHeroImageFallbacks(Element $element)
|
321 |
+
{
|
322 |
+
if (
|
323 |
+
$element->hasAttribute(Attribute::LAYOUT)
|
324 |
+
&& $element->getAttribute(Attribute::LAYOUT) === Layout::NODISPLAY
|
325 |
+
) {
|
326 |
+
return null;
|
327 |
+
}
|
328 |
+
|
329 |
+
if ($element->tagName === Extension::IMG || $element->tagName === Tag::IMG) {
|
330 |
+
return $this->detectPossibleHeroImageFallbackForAmpImg($element);
|
331 |
+
}
|
332 |
+
|
333 |
+
if ($element->tagName === Extension::VIDEO) {
|
334 |
+
return $this->detectPossibleHeroImageFallbackForPosterImage($element);
|
335 |
+
}
|
336 |
+
|
337 |
+
if ($this->isAmpEmbed($element)) {
|
338 |
+
return $this->detectPossibleHeroImageFallbackForPlaceholderImage($element);
|
339 |
+
}
|
340 |
+
|
341 |
+
return null;
|
342 |
+
}
|
343 |
+
|
344 |
+
/**
|
345 |
+
* Detect a possible hero image fallback from an <amp-img> element.
|
346 |
+
*
|
347 |
+
* @param Element $element Element to detect for.
|
348 |
+
* @return HeroImage|null Detected hero image fallback, or null if none detected.
|
349 |
+
*/
|
350 |
+
private function detectPossibleHeroImageFallbackForAmpImg(Element $element)
|
351 |
+
{
|
352 |
+
$src = $element->getAttribute(Attribute::SRC);
|
353 |
+
|
354 |
+
if (empty($src)) {
|
355 |
+
return null;
|
356 |
+
}
|
357 |
+
|
358 |
+
if (! (new Url($src))->isValidNonDataUrl()) {
|
359 |
+
return null;
|
360 |
+
}
|
361 |
+
|
362 |
+
if ((new ImageDimensions($element))->isTiny()) {
|
363 |
+
return null;
|
364 |
+
}
|
365 |
+
|
366 |
+
$srcset = $element->getAttribute(Attribute::SRCSET);
|
367 |
+
$media = $element->getAttribute(Attribute::MEDIA);
|
368 |
+
|
369 |
+
return new HeroImage($src, $media, $srcset, $element);
|
370 |
+
}
|
371 |
+
|
372 |
+
/**
|
373 |
+
* Detect a possible hero image fallback from a video's poster (= placeholder) image.
|
374 |
+
*
|
375 |
+
* @param Element $element Element to detect for.
|
376 |
+
* @return HeroImage|null Detected hero image fallback, or null if none detected.
|
377 |
+
*/
|
378 |
+
private function detectPossibleHeroImageFallbackForPosterImage(Element $element)
|
379 |
+
{
|
380 |
+
$poster = $element->getAttribute(Attribute::POSTER);
|
381 |
+
|
382 |
+
if (! $poster) {
|
383 |
+
return null;
|
384 |
+
}
|
385 |
+
|
386 |
+
if (! (new Url($poster))->isValidNonDataUrl()) {
|
387 |
+
return null;
|
388 |
+
}
|
389 |
+
|
390 |
+
if ((new ImageDimensions($element))->isTiny()) {
|
391 |
+
return null;
|
392 |
+
}
|
393 |
+
|
394 |
+
$media = $element->getAttribute(Attribute::MEDIA);
|
395 |
+
|
396 |
+
return new HeroImage($poster, $media, '');
|
397 |
+
}
|
398 |
+
|
399 |
+
/**
|
400 |
+
* Detect a possible hero image fallback from a placeholder image.
|
401 |
+
*
|
402 |
+
* @param Element $element Element to detect for.
|
403 |
+
* @return HeroImage|null Detected hero image fallback, or null if none detected.
|
404 |
+
*/
|
405 |
+
private function detectPossibleHeroImageFallbackForPlaceholderImage(Element $element)
|
406 |
+
{
|
407 |
+
// The placeholder will be a child node of the element.
|
408 |
+
if (! $element->hasChildNodes()) {
|
409 |
+
return null;
|
410 |
+
}
|
411 |
+
|
412 |
+
// Don't bother if the element is too small.
|
413 |
+
if ((new ImageDimensions($element))->isTiny()) {
|
414 |
+
return null;
|
415 |
+
}
|
416 |
+
|
417 |
+
return $this->getPlaceholderImage($element);
|
418 |
+
}
|
419 |
+
|
420 |
+
/**
|
421 |
+
* Get the placeholder image for a given element.
|
422 |
+
*
|
423 |
+
* @param Element $element Element to check the placeholder image for.
|
424 |
+
* @return HeroImage|null Placeholder image to use or null if none found.
|
425 |
+
*/
|
426 |
+
private function getPlaceholderImage(Element $element)
|
427 |
+
{
|
428 |
+
foreach ($element->childNodes as $childNode) {
|
429 |
+
if (
|
430 |
+
! $childNode instanceof Element
|
431 |
+
|| ! $childNode->hasAttribute(Attribute::PLACEHOLDER)
|
432 |
+
) {
|
433 |
+
continue;
|
434 |
+
}
|
435 |
+
|
436 |
+
$placeholder = $childNode;
|
437 |
+
|
438 |
+
while ($placeholder !== null) {
|
439 |
+
if (! $placeholder instanceof Element) {
|
440 |
+
$placeholder = NodeWalker::nextNode($placeholder);
|
441 |
+
continue;
|
442 |
+
}
|
443 |
+
|
444 |
+
if (
|
445 |
+
$placeholder->tagName === Extension::IMG
|
446 |
+
|| $placeholder->tagName === Tag::IMG
|
447 |
+
) {
|
448 |
+
// Found valid candidate for placeholder image.
|
449 |
+
break;
|
450 |
+
}
|
451 |
+
|
452 |
+
if (Amp::isTemplate($placeholder)) {
|
453 |
+
// Ignore images inside templates.
|
454 |
+
$placeholder = NodeWalker::skipNodeAndChildren($placeholder);
|
455 |
+
} else {
|
456 |
+
$placeholder = NodeWalker::nextNode($placeholder);
|
457 |
+
}
|
458 |
+
}
|
459 |
+
|
460 |
+
if (!$placeholder instanceof Element) {
|
461 |
+
break;
|
462 |
+
}
|
463 |
+
|
464 |
+
$src = $placeholder->getAttribute(Attribute::SRC);
|
465 |
+
|
466 |
+
if (! (new Url($src))->isValidNonDataUrl()) {
|
467 |
+
break;
|
468 |
+
}
|
469 |
+
|
470 |
+
return new HeroImage(
|
471 |
+
$src,
|
472 |
+
$element->getAttribute(Attribute::MEDIA),
|
473 |
+
$placeholder->getAttribute(Attribute::SRCSET),
|
474 |
+
$placeholder
|
475 |
+
);
|
476 |
+
}
|
477 |
+
|
478 |
+
return null;
|
479 |
+
}
|
480 |
+
|
481 |
+
/**
|
482 |
+
* Remove the lazy loading from the hero image.
|
483 |
+
*
|
484 |
+
* @param HeroImage $heroImage Hero image to remove the lazy loading for.
|
485 |
+
*/
|
486 |
+
private function removeLazyLoading(HeroImage $heroImage)
|
487 |
+
{
|
488 |
+
$img = $heroImage->getAmpImg();
|
489 |
+
|
490 |
+
if (
|
491 |
+
$img && $img->getAttribute(Attribute::LOADING) === 'lazy'
|
492 |
+
&&
|
493 |
+
! $img->hasAttribute(Attribute::DATA_AMP_STORY_PLAYER_POSTER_IMG)
|
494 |
+
) {
|
495 |
+
$img->removeAttribute(Attribute::LOADING);
|
496 |
+
}
|
497 |
+
}
|
498 |
+
|
499 |
+
/**
|
500 |
+
* Generate the preload link for a given hero image.
|
501 |
+
*
|
502 |
+
* @param HeroImage $heroImage Hero image to generate the preload link for.
|
503 |
+
* @param Document $document Document to generate the preload link in.
|
504 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
505 |
+
*/
|
506 |
+
private function generatePreload(HeroImage $heroImage, Document $document, ErrorCollection $errors)
|
507 |
+
{
|
508 |
+
if (empty($heroImage->getMedia())) {
|
509 |
+
// We can only safely preload a hero image if there's a media attribute
|
510 |
+
// as we can't detect whether it's hidden on certain viewport sizes otherwise.
|
511 |
+
return;
|
512 |
+
}
|
513 |
+
|
514 |
+
if ($heroImage->getSrcset() && ! $this->supportsSrcset()) {
|
515 |
+
$errors->add(Error\CannotPreloadImage::fromImageWithSrcsetAttribute($heroImage->getAmpImg()));
|
516 |
+
return;
|
517 |
+
}
|
518 |
+
|
519 |
+
if ($this->hasExistingImagePreload($document, $heroImage->getSrc())) {
|
520 |
+
return;
|
521 |
+
}
|
522 |
+
|
523 |
+
if ($this->preloadReferenceNode === null) {
|
524 |
+
$this->preloadReferenceNode = $document->viewport;
|
525 |
+
}
|
526 |
+
|
527 |
+
$preload = $document->createElement(Tag::LINK);
|
528 |
+
$preload->setAttribute(Attribute::REL, Attribute::REL_PRELOAD);
|
529 |
+
$preload->setAttribute(Attribute::HREF, $heroImage->getSrc());
|
530 |
+
$preload->setAttribute(Attribute::AS_, RequestDestination::IMAGE);
|
531 |
+
$preload->appendChild($document->createAttribute(Attribute::DATA_HERO));
|
532 |
+
if ($heroImage->getSrcset()) {
|
533 |
+
$preload->setAttribute(Attribute::IMAGESRCSET, $heroImage->getSrcset());
|
534 |
+
$img = $heroImage->getAmpImg();
|
535 |
+
if ($img && $img->hasAttribute(Attribute::SIZES)) {
|
536 |
+
$preload->setAttribute(Attribute::IMAGESIZES, $img->getAttribute(Attribute::SIZES));
|
537 |
+
}
|
538 |
+
}
|
539 |
+
|
540 |
+
$preload->setAttribute(Attribute::MEDIA, $heroImage->getMedia());
|
541 |
+
|
542 |
+
if ($this->preloadReferenceNode) {
|
543 |
+
$this->preloadReferenceNode->parentNode->insertBefore(
|
544 |
+
$preload,
|
545 |
+
$this->preloadReferenceNode->nextSibling
|
546 |
+
);
|
547 |
+
} else {
|
548 |
+
$document->head->appendChild($preload);
|
549 |
+
}
|
550 |
+
|
551 |
+
$this->preloadReferenceNode = $preload;
|
552 |
+
}
|
553 |
+
|
554 |
+
/**
|
555 |
+
* Generate the SSR image element for the hero image.
|
556 |
+
*
|
557 |
+
* @param HeroImage $heroImage Hero image to generate the SSR image element for.
|
558 |
+
* @param Document $document Document in which to generate the SSR image element in.
|
559 |
+
*/
|
560 |
+
private function generateImg(HeroImage $heroImage, Document $document)
|
561 |
+
{
|
562 |
+
$element = $heroImage->getAmpImg();
|
563 |
+
|
564 |
+
if (! $element || $element->tagName !== Extension::IMG) {
|
565 |
+
return;
|
566 |
+
}
|
567 |
+
|
568 |
+
$imgElement = $document->createElement(Tag::IMG);
|
569 |
+
$imgElement->setAttribute(Attribute::CLASS_, self::SSR_IMAGE_CLASS);
|
570 |
+
$imgElement->setAttribute(Attribute::DECODING, 'async');
|
571 |
+
|
572 |
+
// If the image was detected as hero image candidate (and thus lacks an explicit data-hero), mark it as a hero
|
573 |
+
// and add loading=lazy to guard against making the page performance even worse by eagerly loading an image
|
574 |
+
// outside the viewport. But if there is a noscript > img then preserve its original loading attribute.
|
575 |
+
$noscript_img = $document->xpath->query(self::NOSCRIPT_IMG_XPATH_QUERY, $element)->item(0);
|
576 |
+
if ($noscript_img instanceof Element) {
|
577 |
+
// Preserve the original loading attribute from the noscript fallback img.
|
578 |
+
if ($noscript_img->hasAttribute(Attribute::LOADING)) {
|
579 |
+
$imgElement->setAttribute(Attribute::LOADING, $noscript_img->getAttribute(Attribute::LOADING));
|
580 |
+
}
|
581 |
+
|
582 |
+
// Remove any noscript>img when an amp-img is pre-rendered.
|
583 |
+
$noscript_img->parentNode->parentNode->removeChild($noscript_img->parentNode);
|
584 |
+
} elseif (! $this->isMarkedAsHeroImage($element)) {
|
585 |
+
$imgElement->setAttribute(Attribute::LOADING, 'lazy');
|
586 |
+
}
|
587 |
+
|
588 |
+
if (!$element->hasAttribute(Attribute::DATA_HERO)) {
|
589 |
+
$element->appendChild($document->createAttribute(Attribute::DATA_HERO));
|
590 |
+
}
|
591 |
+
|
592 |
+
foreach (self::ATTRIBUTES_TO_COPY as $attribute) {
|
593 |
+
if ($element->hasAttribute($attribute)) {
|
594 |
+
$imgElement->setAttribute($attribute, $element->getAttribute($attribute));
|
595 |
+
}
|
596 |
+
}
|
597 |
+
|
598 |
+
foreach (self::ATTRIBUTES_TO_INLINE as $attribute) {
|
599 |
+
if ($element->hasAttribute($attribute)) {
|
600 |
+
$value = $element->getAttribute($attribute);
|
601 |
+
$style = empty($value) ? '' : "{$attribute}:{$element->getAttribute($attribute)}";
|
602 |
+
$imgElement->addInlineStyle($style);
|
603 |
+
}
|
604 |
+
}
|
605 |
+
|
606 |
+
$element->appendChild($document->createAttribute(Attribute::I_AMPHTML_SSR));
|
607 |
+
|
608 |
+
$element->appendChild($imgElement);
|
609 |
+
}
|
610 |
+
|
611 |
+
/**
|
612 |
+
* Check whether an existing preload link exists for a given src.
|
613 |
+
*
|
614 |
+
* @param Document $document Document in which to check for an existing preload.
|
615 |
+
* @param string $src Preload URL to look for.
|
616 |
+
* @return bool Whether an existing preload already exists.
|
617 |
+
*/
|
618 |
+
private function hasExistingImagePreload(Document $document, $src)
|
619 |
+
{
|
620 |
+
foreach ($document->head->childNodes as $node) {
|
621 |
+
if (! $node instanceof Element) {
|
622 |
+
continue;
|
623 |
+
}
|
624 |
+
|
625 |
+
if ($node->getAttribute(Attribute::REL) !== Attribute::REL_PRELOAD) {
|
626 |
+
continue;
|
627 |
+
}
|
628 |
+
|
629 |
+
if ($node->getAttribute(Attribute::AS_) !== RequestDestination::IMAGE) {
|
630 |
+
continue;
|
631 |
+
}
|
632 |
+
|
633 |
+
if ($node->getAttribute(Attribute::HREF) === $src) {
|
634 |
+
return true;
|
635 |
+
}
|
636 |
+
}
|
637 |
+
|
638 |
+
return false;
|
639 |
+
}
|
640 |
+
|
641 |
+
/**
|
642 |
+
* Check whether a given element is an AMP embed.
|
643 |
+
*
|
644 |
+
* @param Element $element Element to check.
|
645 |
+
* @return bool Whether the given element is an AMP embed.
|
646 |
+
*/
|
647 |
+
private function isAmpEmbed(Element $element)
|
648 |
+
{
|
649 |
+
return array_key_exists($element->tagName, self::AMP_EMBEDS);
|
650 |
+
}
|
651 |
+
|
652 |
+
/**
|
653 |
+
* Get the URL of the CSS background-image property.
|
654 |
+
*
|
655 |
+
* This falls back to the data-amp-original-style attribute if the inline
|
656 |
+
* style was already extracted by the CSS tree-shaking.
|
657 |
+
*
|
658 |
+
* @param Element $element
|
659 |
+
* @return string URL of the background image, or an empty string if not found.
|
660 |
+
*/
|
661 |
+
private function getCssBackgroundImageUrl(Element $element)
|
662 |
+
{
|
663 |
+
$matches = [];
|
664 |
+
|
665 |
+
if (
|
666 |
+
preg_match(
|
667 |
+
self::CSS_BACKGROUND_IMAGE_URL_REGEX_PATTERN,
|
668 |
+
$element->getAttribute(Attribute::STYLE),
|
669 |
+
$matches
|
670 |
+
)
|
671 |
+
) {
|
672 |
+
return trim($matches['url'], '\'" ');
|
673 |
+
}
|
674 |
+
|
675 |
+
if (
|
676 |
+
!empty($this->inlineStyleBackupAttribute)
|
677 |
+
&& preg_match(
|
678 |
+
self::CSS_BACKGROUND_IMAGE_URL_REGEX_PATTERN,
|
679 |
+
$element->getAttribute($this->inlineStyleBackupAttribute),
|
680 |
+
$matches
|
681 |
+
)
|
682 |
+
) {
|
683 |
+
return trim($matches['url'], '\'" ');
|
684 |
+
}
|
685 |
+
|
686 |
+
return '';
|
687 |
+
}
|
688 |
+
|
689 |
+
/**
|
690 |
+
* Whether srcset preloading is supported.
|
691 |
+
*
|
692 |
+
* @return bool
|
693 |
+
*/
|
694 |
+
private function supportsSrcset()
|
695 |
+
{
|
696 |
+
return $this->configuration->get(PreloadHeroImageConfiguration::PRELOAD_SRCSET);
|
697 |
+
}
|
698 |
+
|
699 |
+
/**
|
700 |
+
* Check if an element or its ancestors is marked as a hero image.
|
701 |
+
*
|
702 |
+
* @param Element $element Element to check.
|
703 |
+
* @return bool Whether the element or one of its ancestors is marked as a hero image.
|
704 |
+
*/
|
705 |
+
private function isMarkedAsHeroImage(Element $element)
|
706 |
+
{
|
707 |
+
while ($element) {
|
708 |
+
if (!$element instanceof Element) {
|
709 |
+
$element = $element->parentNode;
|
710 |
+
continue;
|
711 |
+
}
|
712 |
+
|
713 |
+
if ($element->hasAttribute(Attribute::DATA_HERO)) {
|
714 |
+
return true;
|
715 |
+
}
|
716 |
+
|
717 |
+
if ($element->tagName === Tag::BODY || $element->tagName === Tag::HTML) {
|
718 |
+
return false;
|
719 |
+
}
|
720 |
+
|
721 |
+
$element = $element->parentNode;
|
722 |
+
}
|
723 |
+
|
724 |
+
return false;
|
725 |
+
}
|
726 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/ReorderHead.php
ADDED
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\Dom\Document;
|
8 |
+
use AmpProject\Dom\Element;
|
9 |
+
use AmpProject\Optimizer\ErrorCollection;
|
10 |
+
use AmpProject\Optimizer\Transformer;
|
11 |
+
use AmpProject\Tag;
|
12 |
+
use DOMNode;
|
13 |
+
use DOMNodeList;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Transformer applying the head reordering transformations to the HTML input.
|
17 |
+
*
|
18 |
+
* ReorderHead reorders the children of <head>. Specifically, it
|
19 |
+
* orders the <head> like so:
|
20 |
+
* (0) <meta charset> tag
|
21 |
+
* (1) <style amp-runtime> (inserted by AmpRuntimeCss transformer)
|
22 |
+
* (2) remaining <meta> tags (those other than <meta charset>)
|
23 |
+
* (3) AMP runtime .js <script> tag
|
24 |
+
* (4) AMP viewer runtime .js <script>
|
25 |
+
* (5) <script> tags that are render delaying
|
26 |
+
* (6) <script> tags for remaining extensions
|
27 |
+
* (7) <link> tag for favicons
|
28 |
+
* (8) <link> tag for resource hints
|
29 |
+
* (9) <link rel=stylesheet> tags before <style amp-custom>
|
30 |
+
* (10) <style amp-custom>
|
31 |
+
* (11) any other tags allowed in <head>
|
32 |
+
* (12) AMP boilerplate (first style amp-boilerplate, then noscript)
|
33 |
+
*
|
34 |
+
* This is ported from the NodeJS optimizer while verifying against the Go version.
|
35 |
+
*
|
36 |
+
* NodeJS:
|
37 |
+
* @version c92d6023ea4c9edadff593742a992da2b400a75d
|
38 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/c92d6023ea4c9edadff593742a992da2b400a75d/packages/optimizer/lib/transformers/ReorderHeadTransformer.js
|
39 |
+
*
|
40 |
+
* Go:
|
41 |
+
* @version ea0959046c179953de43077eafaeb720f9b20bdf
|
42 |
+
* @link https://github.com/ampproject/amppackager/blob/ea0959046c179953de43077eafaeb720f9b20bdf/transformer/transformers/reorderhead.go
|
43 |
+
*
|
44 |
+
* @package ampproject/amp-toolbox
|
45 |
+
*/
|
46 |
+
final class ReorderHead implements Transformer
|
47 |
+
{
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Regular expression pattern to match resource hints pointing to an AMP resource.
|
51 |
+
*/
|
52 |
+
const AMP_RESOURCE_HINT_SRC_PATTERN = '#(^|[\b/])cdn\.ampproject\.org($|[\b/])#i';
|
53 |
+
|
54 |
+
/*
|
55 |
+
* Different categories of <head> tags to track and reorder.
|
56 |
+
*/
|
57 |
+
private $ampResourceHints = [];
|
58 |
+
private $linkIcons = [];
|
59 |
+
private $linkStyleAmpRuntime = null;
|
60 |
+
private $linkStylesheetsBeforeAmpCustom = [];
|
61 |
+
private $metaCharset = null;
|
62 |
+
private $metaViewport = null;
|
63 |
+
private $metaOther = [];
|
64 |
+
private $noscript = null;
|
65 |
+
private $others = [];
|
66 |
+
private $resourceHintLinks = [];
|
67 |
+
private $scriptAmpRuntime = [];
|
68 |
+
private $scriptAmpViewer = [];
|
69 |
+
private $scriptNonRenderDelayingExtensions = [];
|
70 |
+
private $scriptRenderDelayingExtensions = [];
|
71 |
+
private $styleAmpBoilerplate = null;
|
72 |
+
private $styleAmpCustom = null;
|
73 |
+
private $styleAmpRuntime = null;
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Apply transformations to the provided DOM document.
|
77 |
+
*
|
78 |
+
* @param Document $document DOM document to apply the transformations to.
|
79 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
80 |
+
* @return void
|
81 |
+
*/
|
82 |
+
public function transform(Document $document, ErrorCollection $errors)
|
83 |
+
{
|
84 |
+
$nodes = $document->head->childNodes;
|
85 |
+
|
86 |
+
if (! $nodes instanceof DOMNodeList || $nodes->length === 0) {
|
87 |
+
return;
|
88 |
+
}
|
89 |
+
|
90 |
+
while ($document->head->hasChildNodes()) {
|
91 |
+
$node = $document->head->removeChild($document->head->firstChild);
|
92 |
+
$this->registerNode($node);
|
93 |
+
}
|
94 |
+
|
95 |
+
$this->appendToHead($document);
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Register a given node in the appropriate category.
|
100 |
+
*
|
101 |
+
* @param DOMNode $node Node to register.
|
102 |
+
*/
|
103 |
+
private function registerNode(DOMNode $node)
|
104 |
+
{
|
105 |
+
if (! $node instanceof Element) {
|
106 |
+
if ($node->nodeType === XML_TEXT_NODE) {
|
107 |
+
$nodeContent = trim($node->textContent);
|
108 |
+
if (empty($nodeContent)) {
|
109 |
+
return;
|
110 |
+
}
|
111 |
+
}
|
112 |
+
$this->others[] = $node;
|
113 |
+
return;
|
114 |
+
}
|
115 |
+
|
116 |
+
switch ($node->tagName) {
|
117 |
+
case Tag::META:
|
118 |
+
$this->registerMeta($node);
|
119 |
+
break;
|
120 |
+
case Tag::SCRIPT:
|
121 |
+
$this->registerScript($node);
|
122 |
+
break;
|
123 |
+
case Tag::STYLE:
|
124 |
+
$this->registerStyle($node);
|
125 |
+
break;
|
126 |
+
case Tag::LINK:
|
127 |
+
$this->registerLink($node);
|
128 |
+
break;
|
129 |
+
case Tag::NOSCRIPT:
|
130 |
+
$this->noscript = $node; // @todo Make this an array.
|
131 |
+
break;
|
132 |
+
default:
|
133 |
+
$this->others[] = $node;
|
134 |
+
}
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Register a <meta> node.
|
139 |
+
*
|
140 |
+
* @param Element $node Node to register.
|
141 |
+
*/
|
142 |
+
private function registerMeta(Element $node)
|
143 |
+
{
|
144 |
+
if ($node->hasAttribute(Attribute::CHARSET)) {
|
145 |
+
$this->metaCharset = $node;
|
146 |
+
return;
|
147 |
+
}
|
148 |
+
|
149 |
+
if ($node->getAttribute(Attribute::NAME) === Attribute::VIEWPORT) {
|
150 |
+
$this->metaViewport = $node;
|
151 |
+
return;
|
152 |
+
}
|
153 |
+
|
154 |
+
$this->metaOther[] = $node;
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Register a <script> node.
|
159 |
+
*
|
160 |
+
* @param Element $node Node to register.
|
161 |
+
*/
|
162 |
+
private function registerScript(Element $node)
|
163 |
+
{
|
164 |
+
$nomodule = (int)$node->hasAttribute('nomodule');
|
165 |
+
|
166 |
+
if (Amp::isRuntimeScript($node)) {
|
167 |
+
$this->scriptAmpRuntime[$nomodule] = $node;
|
168 |
+
return;
|
169 |
+
}
|
170 |
+
|
171 |
+
if (Amp::isViewerScript($node)) {
|
172 |
+
$this->scriptAmpViewer[$nomodule] = $node;
|
173 |
+
return;
|
174 |
+
}
|
175 |
+
|
176 |
+
if ($node->hasAttribute(Attribute::CUSTOM_ELEMENT)) {
|
177 |
+
$name = $node->getAttribute(Attribute::CUSTOM_ELEMENT);
|
178 |
+
if (Amp::isRenderDelayingExtension($node)) {
|
179 |
+
$this->scriptRenderDelayingExtensions[$name][$nomodule] = $node;
|
180 |
+
return;
|
181 |
+
}
|
182 |
+
$this->scriptNonRenderDelayingExtensions[$name][$nomodule] = $node;
|
183 |
+
return;
|
184 |
+
}
|
185 |
+
|
186 |
+
if ($node->hasAttribute(Attribute::CUSTOM_TEMPLATE)) {
|
187 |
+
$name = $node->getAttribute(Attribute::CUSTOM_TEMPLATE);
|
188 |
+
$this->scriptNonRenderDelayingExtensions[$name][$nomodule] = $node;
|
189 |
+
return;
|
190 |
+
}
|
191 |
+
|
192 |
+
if ($node->hasAttribute(Attribute::HOST_SERVICE)) {
|
193 |
+
$name = $node->getAttribute(Attribute::HOST_SERVICE);
|
194 |
+
$this->scriptNonRenderDelayingExtensions[$name][$nomodule] = $node;
|
195 |
+
return;
|
196 |
+
}
|
197 |
+
|
198 |
+
$this->others[] = $node;
|
199 |
+
}
|
200 |
+
|
201 |
+
/**
|
202 |
+
* Register a <style> node.
|
203 |
+
*
|
204 |
+
* @param Element $node Node to register.
|
205 |
+
*/
|
206 |
+
private function registerStyle(Element $node)
|
207 |
+
{
|
208 |
+
if ($node->hasAttribute(Attribute::AMP_RUNTIME)) {
|
209 |
+
$this->styleAmpRuntime = $node;
|
210 |
+
return;
|
211 |
+
}
|
212 |
+
|
213 |
+
if ($node->hasAttribute(Attribute::AMP_CUSTOM)) {
|
214 |
+
$this->styleAmpCustom = $node;
|
215 |
+
return;
|
216 |
+
}
|
217 |
+
|
218 |
+
if (
|
219 |
+
$node->hasAttribute(Attribute::AMP_BOILERPLATE)
|
220 |
+
|| $node->hasAttribute(Attribute::AMP4ADS_BOILERPLATE)
|
221 |
+
) {
|
222 |
+
$this->styleAmpBoilerplate = $node;
|
223 |
+
return;
|
224 |
+
}
|
225 |
+
|
226 |
+
$this->others[] = $node;
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Register a <link> node.
|
231 |
+
*
|
232 |
+
* @param Element $node Node to register.
|
233 |
+
*/
|
234 |
+
private function registerLink(Element $node)
|
235 |
+
{
|
236 |
+
$rel = $node->getAttribute(Attribute::REL);
|
237 |
+
|
238 |
+
if ($this->containsWord($rel, Attribute::REL_STYLESHEET)) {
|
239 |
+
$href = $node->getAttribute(Attribute::HREF);
|
240 |
+
if ($href && substr($href, -7) === '/v0.css') {
|
241 |
+
$this->linkStyleAmpRuntime = $node;
|
242 |
+
return;
|
243 |
+
}
|
244 |
+
if (! $this->styleAmpCustom) {
|
245 |
+
// We haven't seen amp-custom yet.
|
246 |
+
$this->linkStylesheetsBeforeAmpCustom[] = $node;
|
247 |
+
return;
|
248 |
+
}
|
249 |
+
}
|
250 |
+
|
251 |
+
if ($this->containsWord($rel, Attribute::REL_ICON)) {
|
252 |
+
$this->linkIcons[] = $node;
|
253 |
+
return;
|
254 |
+
}
|
255 |
+
|
256 |
+
if (
|
257 |
+
$this->containsWord($rel, Attribute::REL_PRELOAD)
|
258 |
+
|| $this->containsWord($rel, Attribute::REL_PREFETCH)
|
259 |
+
|| $this->containsWord($rel, Attribute::REL_DNS_PREFETCH)
|
260 |
+
|| $this->containsWord($rel, Attribute::REL_PRECONNECT)
|
261 |
+
|| $this->containsWord($rel, Attribute::REL_MODULEPRELOAD)
|
262 |
+
) {
|
263 |
+
if ($this->isHintForAmp($node)) {
|
264 |
+
$this->ampResourceHints[] = $node;
|
265 |
+
} else {
|
266 |
+
$this->resourceHintLinks[] = $node;
|
267 |
+
}
|
268 |
+
return;
|
269 |
+
}
|
270 |
+
|
271 |
+
$this->others[] = $node;
|
272 |
+
}
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Append all registered nodes to the <head> node.
|
276 |
+
*
|
277 |
+
* @param Document $document Document to append the nodes to.
|
278 |
+
*/
|
279 |
+
private function appendToHead(Document $document)
|
280 |
+
{
|
281 |
+
$categories = [
|
282 |
+
'metaCharset',
|
283 |
+
'metaViewport',
|
284 |
+
'ampResourceHints',
|
285 |
+
'linkStyleAmpRuntime',
|
286 |
+
'styleAmpRuntime',
|
287 |
+
'metaOther',
|
288 |
+
'resourceHintLinks',
|
289 |
+
'scriptAmpRuntime',
|
290 |
+
'scriptAmpViewer',
|
291 |
+
'scriptRenderDelayingExtensions',
|
292 |
+
'scriptNonRenderDelayingExtensions',
|
293 |
+
'linkIcons',
|
294 |
+
'linkStylesheetsBeforeAmpCustom',
|
295 |
+
'styleAmpCustom',
|
296 |
+
'others',
|
297 |
+
'styleAmpBoilerplate',
|
298 |
+
'noscript',
|
299 |
+
];
|
300 |
+
|
301 |
+
foreach ($categories as $category) {
|
302 |
+
if ($this->$category === null) {
|
303 |
+
continue;
|
304 |
+
}
|
305 |
+
|
306 |
+
if ($this->$category instanceof DOMNode) {
|
307 |
+
$node = $document->importNode($this->$category);
|
308 |
+
$document->head->appendChild($node);
|
309 |
+
} elseif (is_array($this->$category)) {
|
310 |
+
$this->recursiveKeySort($this->$category);
|
311 |
+
array_walk_recursive(
|
312 |
+
$this->$category,
|
313 |
+
static function (DOMNode $node) use ($document) {
|
314 |
+
$node = $document->importNode($node);
|
315 |
+
$document->head->appendChild($node);
|
316 |
+
}
|
317 |
+
);
|
318 |
+
}
|
319 |
+
}
|
320 |
+
}
|
321 |
+
|
322 |
+
/**
|
323 |
+
* Sort array keys recursively.
|
324 |
+
*
|
325 |
+
* @param array|mixed $item Item.
|
326 |
+
*/
|
327 |
+
private function recursiveKeySort(&$item)
|
328 |
+
{
|
329 |
+
if (is_array($item)) {
|
330 |
+
ksort($item);
|
331 |
+
array_walk($item, [$this, 'recursiveKeySort']);
|
332 |
+
}
|
333 |
+
}
|
334 |
+
|
335 |
+
/**
|
336 |
+
* Check if a given string contains another string, respecting word boundaries..
|
337 |
+
*
|
338 |
+
* @param string $haystack Haystack string to look in.
|
339 |
+
* @param string $needle Needle string to search for.
|
340 |
+
* @return bool Whether the needle was found in the haystack.
|
341 |
+
*/
|
342 |
+
private function containsWord($haystack, $needle)
|
343 |
+
{
|
344 |
+
if (empty($haystack) || empty($needle)) {
|
345 |
+
return false;
|
346 |
+
}
|
347 |
+
|
348 |
+
return preg_match('/(^|\s)' . preg_quote($needle, '/') . '(\s|$)/i', $haystack);
|
349 |
+
}
|
350 |
+
|
351 |
+
/**
|
352 |
+
* Check whether a given resource hint link element is pointing to an AMP resource.
|
353 |
+
*
|
354 |
+
* @param Element $node Link element to check.
|
355 |
+
* @return bool Whether the link element is pointing to an AMP resource.
|
356 |
+
*/
|
357 |
+
private function isHintForAmp(Element $node)
|
358 |
+
{
|
359 |
+
$href = $node->getAttribute(Attribute::HREF);
|
360 |
+
if (empty($href)) {
|
361 |
+
return false;
|
362 |
+
}
|
363 |
+
|
364 |
+
return (bool)preg_match(self::AMP_RESOURCE_HINT_SRC_PATTERN, $href);
|
365 |
+
}
|
366 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/RewriteAmpUrls.php
ADDED
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\Dom\Document;
|
8 |
+
use AmpProject\Dom\Element;
|
9 |
+
use AmpProject\Optimizer\Configuration\RewriteAmpUrlsConfiguration;
|
10 |
+
use AmpProject\Optimizer\Error\CannotAdaptDocumentForSelfHosting;
|
11 |
+
use AmpProject\Optimizer\ErrorCollection;
|
12 |
+
use AmpProject\Optimizer\Exception\InvalidConfiguration;
|
13 |
+
use AmpProject\Optimizer\Transformer;
|
14 |
+
use AmpProject\Optimizer\TransformerConfiguration;
|
15 |
+
use AmpProject\RuntimeVersion;
|
16 |
+
use AmpProject\Tag;
|
17 |
+
use AmpProject\Url;
|
18 |
+
use DOMNode;
|
19 |
+
use Exception;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* RewriteAmpUrls - rewrites AMP runtime URLs.
|
23 |
+
*
|
24 |
+
* This transformer supports five parameters:
|
25 |
+
*
|
26 |
+
* * `ampRuntimeVersion`: specifies a
|
27 |
+
* [specific version](https://github.com/ampproject/amp-toolbox/tree/main/runtime-version)
|
28 |
+
* version of the AMP runtime. For example: `ampRuntimeVersion: "001515617716922"` will result in AMP runtime URLs
|
29 |
+
* being re-written from `https://cdn.ampproject.org/v0.js` to `https://cdn.ampproject.org/rtv/001515617716922/v0.js`.
|
30 |
+
*
|
31 |
+
* * `ampUrlPrefix`: specifies an URL prefix for AMP runtime URLs. For example: `ampUrlPrefix: "/amp"` will result in
|
32 |
+
* AMP runtime URLs being re-written from `https://cdn.ampproject.org/v0.js` to `/amp/v0.js`. This option is
|
33 |
+
* experimental and not recommended.
|
34 |
+
*
|
35 |
+
* * `geoApiUrl`: specifies amp-geo API URL to use as a fallback when `amp-geo-0.1.js` is served unpatched, i.e. when
|
36 |
+
* `{{AMP_ISO_COUNTRY_HOTPATCH}}` is not replaced dynamically.
|
37 |
+
*
|
38 |
+
* * `lts`: Use long-term stable URLs. This option is not compatible with `rtv`, `ampRuntimeVersion` or `ampUrlPrefix`;
|
39 |
+
* an error will be thrown if these options are included together. Similarly, the `geoApiUrl` option is ineffective
|
40 |
+
* with the `lts` flag, but will simply be ignored rather than throwing an error.
|
41 |
+
*
|
42 |
+
* * `rtv`: Append the runtime version to the rewritten URLs. This option is not compatible with `lts`.
|
43 |
+
*
|
44 |
+
* * `esmModulesEnabled`: Use ES modules for loading the AMP runtime and components. Defaults to true.
|
45 |
+
*
|
46 |
+
* All parameters are optional. If no option is provided, runtime URLs won't be re-written. You can combine
|
47 |
+
* `ampRuntimeVersion` and `ampUrlPrefix` to rewrite AMP runtime URLs to versioned URLs on a different origin.
|
48 |
+
*
|
49 |
+
* This transformer also adds a preload header for the AMP runtime (v0.js) to trigger HTTP/2 push for CDNs (see
|
50 |
+
* https://www.w3.org/TR/preload/#server-push-(http/2)).
|
51 |
+
*
|
52 |
+
* This is ported from the NodeJS optimizer while verifying against the Go version.
|
53 |
+
*
|
54 |
+
* NodeJS:
|
55 |
+
* @version 7fbf187b3c7f07100e8911a52582b640b23490e5
|
56 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/7fbf187b3c7f07100e8911a52582b640b23490e5/packages/optimizer/lib/transformers/RewriteAmpUrls.js
|
57 |
+
*
|
58 |
+
* @package ampproject/amp-toolbox
|
59 |
+
*/
|
60 |
+
final class RewriteAmpUrls implements Transformer
|
61 |
+
{
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Configuration to use.
|
65 |
+
*
|
66 |
+
* @var TransformerConfiguration
|
67 |
+
*/
|
68 |
+
private $configuration;
|
69 |
+
|
70 |
+
/**
|
71 |
+
* RewriteAmpUrls constructor.
|
72 |
+
*
|
73 |
+
* @param TransformerConfiguration $configuration Configuration to use.
|
74 |
+
*/
|
75 |
+
public function __construct(TransformerConfiguration $configuration)
|
76 |
+
{
|
77 |
+
$this->configuration = $configuration;
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Apply transformations to the provided DOM document.
|
82 |
+
*
|
83 |
+
* @param Document $document DOM document to apply the
|
84 |
+
* transformations to.
|
85 |
+
* @param ErrorCollection $errors Collection of errors that are collected
|
86 |
+
* during transformation.
|
87 |
+
* @return void
|
88 |
+
*/
|
89 |
+
public function transform(Document $document, ErrorCollection $errors)
|
90 |
+
{
|
91 |
+
$host = $this->calculateHost();
|
92 |
+
$referenceNode = $document->viewport;
|
93 |
+
|
94 |
+
$preloadNodes = array_filter($this->collectPreloadNodes($document, $host));
|
95 |
+
foreach ($preloadNodes as $preloadNode) {
|
96 |
+
$document->head->insertBefore(
|
97 |
+
$preloadNode,
|
98 |
+
$referenceNode instanceof DOMNode ? $referenceNode->nextSibling : null
|
99 |
+
);
|
100 |
+
|
101 |
+
$referenceNode = $preloadNode;
|
102 |
+
}
|
103 |
+
|
104 |
+
$this->adaptForSelfHosting($document, $host, $errors);
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Collect all the preload nodes to be added to the <head>.
|
109 |
+
*
|
110 |
+
* @param Document $document Document to collect preload nodes for.
|
111 |
+
* @param string $host Host URL to use.
|
112 |
+
* @return Element[] Preload nodes.
|
113 |
+
*/
|
114 |
+
private function collectPreloadNodes(Document $document, $host)
|
115 |
+
{
|
116 |
+
$usesEsm = $this->configuration->get(RewriteAmpUrlsConfiguration::ESM_MODULES_ENABLED);
|
117 |
+
$preloadNodes = [];
|
118 |
+
|
119 |
+
$node = $document->head->firstChild;
|
120 |
+
while ($node) {
|
121 |
+
$nextSibling = $node->nextSibling;
|
122 |
+
if (! $node instanceof Element) {
|
123 |
+
$node = $nextSibling;
|
124 |
+
continue;
|
125 |
+
}
|
126 |
+
|
127 |
+
$src = $node->getAttribute(Attribute::SRC);
|
128 |
+
$href = $node->getAttribute(Attribute::HREF);
|
129 |
+
if ($node->tagName === Tag::SCRIPT && $this->usesAmpCacheUrl($src)) {
|
130 |
+
$newUrl = $this->replaceUrl($src, $host);
|
131 |
+
$node->setAttribute(Attribute::SRC, $this->replaceUrl($src, $host));
|
132 |
+
if ($usesEsm) {
|
133 |
+
$preloadNodes[] = $this->addEsm($document, $node);
|
134 |
+
} else {
|
135 |
+
$preloadNodes[] = $this->createPreload($document, $newUrl, Tag::SCRIPT);
|
136 |
+
}
|
137 |
+
} elseif (
|
138 |
+
$node->tagName === Tag::LINK
|
139 |
+
&&
|
140 |
+
$node->getAttribute(Attribute::REL) === Attribute::REL_STYLESHEET
|
141 |
+
&&
|
142 |
+
$this->usesAmpCacheUrl($href)
|
143 |
+
) {
|
144 |
+
$newUrl = $this->replaceUrl($href, $host);
|
145 |
+
$node->setAttribute(Attribute::HREF, $newUrl);
|
146 |
+
$preloadNodes[] = $this->createPreload($document, $newUrl, Tag::STYLE);
|
147 |
+
} elseif (
|
148 |
+
$node->tagName === Tag::LINK
|
149 |
+
&&
|
150 |
+
$node->getAttribute(Attribute::REL) === Attribute::REL_PRELOAD
|
151 |
+
&&
|
152 |
+
$this->usesAmpCacheUrl($href)
|
153 |
+
) {
|
154 |
+
if ($usesEsm && $this->shouldPreload($href)) {
|
155 |
+
// Only preload .mjs runtime in ESM mode.
|
156 |
+
$node->parentNode->removeChild($node);
|
157 |
+
} else {
|
158 |
+
$node->setAttribute(Attribute::HREF, $this->replaceUrl($href, $host));
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
$node = $nextSibling;
|
163 |
+
}
|
164 |
+
|
165 |
+
return $preloadNodes;
|
166 |
+
}
|
167 |
+
|
168 |
+
/**
|
169 |
+
* Check if a given URL uses the AMP Cache.
|
170 |
+
*
|
171 |
+
* @param string $url Url to check.
|
172 |
+
* @return bool Whether the provided URL uses the AMP Cache.
|
173 |
+
*/
|
174 |
+
private function usesAmpCacheUrl($url)
|
175 |
+
{
|
176 |
+
if (! $url) {
|
177 |
+
return false;
|
178 |
+
}
|
179 |
+
|
180 |
+
return strpos($url, Amp::CACHE_HOST) === 0;
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Replace URL root with provided host.
|
185 |
+
*
|
186 |
+
* @param string $url Url to replace.
|
187 |
+
* @param string $host Host to use.
|
188 |
+
* @return string Adapted URL.
|
189 |
+
*/
|
190 |
+
private function replaceUrl($url, $host)
|
191 |
+
{
|
192 |
+
return str_replace(Amp::CACHE_HOST, $host, $url);
|
193 |
+
}
|
194 |
+
|
195 |
+
/**
|
196 |
+
* Replace <script> elements with their ES module counterparts.
|
197 |
+
*
|
198 |
+
* @param Document $document Document to add the ES module scripts to.
|
199 |
+
* @param Element $scriptNode Script element to replace.
|
200 |
+
* @return Element|null Preload element that was added, or null if none was required or an error occurred.
|
201 |
+
*/
|
202 |
+
private function addEsm(Document $document, Element $scriptNode)
|
203 |
+
{
|
204 |
+
$preloadNode = null;
|
205 |
+
$scriptUrl = $scriptNode->getAttribute(Attribute::SRC);
|
206 |
+
$esmScriptUrl = preg_replace('/\.js$/', '.mjs', $scriptUrl);
|
207 |
+
|
208 |
+
if ($this->shouldPreload($scriptUrl)) {
|
209 |
+
$preloadNode = $document->createElement(Tag::LINK);
|
210 |
+
$preloadNode->setAttribute(Attribute::AS_, Tag::SCRIPT);
|
211 |
+
$preloadNode->setAttribute(Attribute::CROSSORIGIN, Attribute::CROSSORIGIN_ANONYMOUS);
|
212 |
+
$preloadNode->setAttribute(Attribute::HREF, $esmScriptUrl);
|
213 |
+
$preloadNode->setAttribute(Attribute::REL, Attribute::REL_MODULEPRELOAD);
|
214 |
+
}
|
215 |
+
|
216 |
+
$nomoduleNode = $document->createElement(Tag::SCRIPT);
|
217 |
+
$nomoduleNode->addBooleanAttribute(Attribute::ASYNC);
|
218 |
+
$nomoduleNode->addBooleanAttribute(Attribute::NOMODULE);
|
219 |
+
$nomoduleNode->setAttribute(Attribute::SRC, $scriptUrl);
|
220 |
+
$nomoduleNode->setAttribute(Attribute::CROSSORIGIN, Attribute::CROSSORIGIN_ANONYMOUS);
|
221 |
+
|
222 |
+
$scriptNode->copyAttributes([Attribute::CUSTOM_ELEMENT, Attribute::CUSTOM_TEMPLATE], $nomoduleNode);
|
223 |
+
|
224 |
+
$scriptNode->parentNode->insertBefore($nomoduleNode, $scriptNode);
|
225 |
+
|
226 |
+
$scriptNode->setAttribute(Attribute::TYPE, Attribute::TYPE_MODULE);
|
227 |
+
// Without crossorigin=anonymous browser loads the script twice because
|
228 |
+
// of preload.
|
229 |
+
$scriptNode->setAttribute(Attribute::CROSSORIGIN, Attribute::CROSSORIGIN_ANONYMOUS);
|
230 |
+
$scriptNode->setAttribute(Attribute::SRC, $esmScriptUrl);
|
231 |
+
|
232 |
+
return $preloadNode instanceof Element ? $preloadNode : null;
|
233 |
+
}
|
234 |
+
|
235 |
+
/**
|
236 |
+
* Create a preload element to add to the head.
|
237 |
+
*
|
238 |
+
* @param Document $document Document to create the element in.
|
239 |
+
* @param string $href Href to use for the preload.
|
240 |
+
* @param string $type Type to use for the preload.
|
241 |
+
* @return Element|null Preload element, or null if not created.
|
242 |
+
*/
|
243 |
+
private function createPreload(Document $document, $href, $type)
|
244 |
+
{
|
245 |
+
if (! $this->shouldPreload($href)) {
|
246 |
+
return null;
|
247 |
+
}
|
248 |
+
|
249 |
+
$preloadNode = $document->createElement(Tag::LINK);
|
250 |
+
|
251 |
+
$preloadNode->setAttribute(Attribute::REL, Attribute::REL_PRELOAD);
|
252 |
+
$preloadNode->setAttribute(Attribute::HREF, $href);
|
253 |
+
$preloadNode->setAttribute(Attribute::AS_, $type);
|
254 |
+
|
255 |
+
return $preloadNode;
|
256 |
+
}
|
257 |
+
|
258 |
+
/**
|
259 |
+
* Add meta tags as needed to adapt for self-hosting the AMP runtime.
|
260 |
+
*
|
261 |
+
* @param Document $document Document to add the meta tags to.
|
262 |
+
* @param string $host Host URL to use.
|
263 |
+
* @param ErrorCollection $errors Error collection to add potential errors to.
|
264 |
+
*/
|
265 |
+
private function adaptForSelfHosting(Document $document, $host, $errors)
|
266 |
+
{
|
267 |
+
// runtime-host and amp-geo-api meta tags should appear before the first script.
|
268 |
+
if (
|
269 |
+
! $this->usesAmpCacheUrl($host)
|
270 |
+
&&
|
271 |
+
! $this->configuration->get(RewriteAmpUrlsConfiguration::LTS)
|
272 |
+
) {
|
273 |
+
try {
|
274 |
+
$url = new Url($host);
|
275 |
+
|
276 |
+
if (!empty($url->scheme) && !empty($url->host)) {
|
277 |
+
$origin = "{$url->scheme}://{$url->host}";
|
278 |
+
$this->addMeta($document, 'runtime-host', $origin);
|
279 |
+
} else {
|
280 |
+
$errors->add(CannotAdaptDocumentForSelfHosting::forNonAbsoluteUrl($host));
|
281 |
+
}
|
282 |
+
} catch (Exception $exception) {
|
283 |
+
$errors->add(CannotAdaptDocumentForSelfHosting::fromException($exception));
|
284 |
+
}
|
285 |
+
}
|
286 |
+
if (
|
287 |
+
! empty($this->configuration->get(RewriteAmpUrlsConfiguration::GEO_API_URL))
|
288 |
+
&&
|
289 |
+
! $this->configuration->get(RewriteAmpUrlsConfiguration::LTS)
|
290 |
+
) {
|
291 |
+
$this->addMeta(
|
292 |
+
$document,
|
293 |
+
'amp-geo-api',
|
294 |
+
$this->configuration->get(RewriteAmpUrlsConfiguration::GEO_API_URL)
|
295 |
+
);
|
296 |
+
}
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Check whether a given URL should be preloaded.
|
301 |
+
*
|
302 |
+
* @param string $url Url to check.
|
303 |
+
* @return bool Whether the provided URL should be preloaded.
|
304 |
+
*/
|
305 |
+
private function shouldPreload($url)
|
306 |
+
{
|
307 |
+
return substr_compare($url, 'v0.js', -5) === 0
|
308 |
+
||
|
309 |
+
substr_compare($url, 'v0.css', -6) === 0;
|
310 |
+
}
|
311 |
+
|
312 |
+
/**
|
313 |
+
* Calculate the host string to use.
|
314 |
+
*
|
315 |
+
* @return string Host to use.
|
316 |
+
*/
|
317 |
+
private function calculateHost()
|
318 |
+
{
|
319 |
+
$lts = $this->configuration->get(RewriteAmpUrlsConfiguration::LTS);
|
320 |
+
$rtv = $this->configuration->get(RewriteAmpUrlsConfiguration::RTV);
|
321 |
+
|
322 |
+
if ($lts && $rtv) {
|
323 |
+
throw InvalidConfiguration::forMutuallyExclusiveFlags(
|
324 |
+
RewriteAmpUrlsConfiguration::LTS,
|
325 |
+
RewriteAmpUrlsConfiguration::RTV
|
326 |
+
);
|
327 |
+
}
|
328 |
+
|
329 |
+
$ampUrlPrefix = $this->configuration->get(RewriteAmpUrlsConfiguration::AMP_URL_PREFIX);
|
330 |
+
$ampRuntimeVersion = $this->configuration->get(RewriteAmpUrlsConfiguration::AMP_RUNTIME_VERSION);
|
331 |
+
|
332 |
+
$ampUrlPrefix = rtrim($ampUrlPrefix, '/');
|
333 |
+
|
334 |
+
if ($ampRuntimeVersion && $rtv) {
|
335 |
+
$ampUrlPrefix = RuntimeVersion::appendRuntimeVersion($ampUrlPrefix, $ampRuntimeVersion);
|
336 |
+
} elseif ($lts) {
|
337 |
+
$ampUrlPrefix .= '/lts';
|
338 |
+
}
|
339 |
+
|
340 |
+
return $ampUrlPrefix;
|
341 |
+
}
|
342 |
+
|
343 |
+
/**
|
344 |
+
* Add meta element to the document head.
|
345 |
+
*
|
346 |
+
* @param Document $document Document to add the meta to.
|
347 |
+
* @param string $name Name of the meta element.
|
348 |
+
* @param string $content Value of the meta element.
|
349 |
+
*/
|
350 |
+
private function addMeta(Document $document, $name, $content)
|
351 |
+
{
|
352 |
+
$meta = $document->createElement(Tag::META);
|
353 |
+
$meta->setAttribute(Attribute::NAME, $name);
|
354 |
+
$meta->setAttribute(Attribute::CONTENT, $content);
|
355 |
+
$firstScript = $document->xpath->query('./script', $document->head)->item(0);
|
356 |
+
$document->head->insertBefore($meta, $firstScript);
|
357 |
+
}
|
358 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/ServerSideRendering.php
ADDED
@@ -0,0 +1,1045 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Amp;
|
6 |
+
use AmpProject\Attribute;
|
7 |
+
use AmpProject\CssLength;
|
8 |
+
use AmpProject\Dom\Document;
|
9 |
+
use AmpProject\Dom\Element;
|
10 |
+
use AmpProject\Exception\MaxCssByteCountExceeded;
|
11 |
+
use AmpProject\Extension;
|
12 |
+
use AmpProject\Layout;
|
13 |
+
use AmpProject\Optimizer\CssRule;
|
14 |
+
use AmpProject\Optimizer\CssRules;
|
15 |
+
use AmpProject\Optimizer\Error;
|
16 |
+
use AmpProject\Optimizer\ErrorCollection;
|
17 |
+
use AmpProject\Optimizer\Exception\InvalidArgument;
|
18 |
+
use AmpProject\Optimizer\Exception\InvalidHtmlAttribute;
|
19 |
+
use AmpProject\Optimizer\Transformer;
|
20 |
+
use AmpProject\Role;
|
21 |
+
use AmpProject\Tag;
|
22 |
+
use DOMAttr;
|
23 |
+
use Exception;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Transformer applying the server-side rendering transformations to the HTML input.
|
27 |
+
*
|
28 |
+
* This is ported from the NodeJS optimizer while verifying against the Go version.
|
29 |
+
*
|
30 |
+
* NodeJS:
|
31 |
+
* @version c92d6023ea4c9edadff593742a992da2b400a75d
|
32 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/c92d6023ea4c9edadff593742a992da2b400a75d/packages/optimizer/lib/transformers/ServerSideRendering.js
|
33 |
+
*
|
34 |
+
* Go:
|
35 |
+
* @version ea0959046c179953de43077eafaeb720f9b20bdf
|
36 |
+
* @link https://github.com/ampproject/amppackager/blob/ea0959046c179953de43077eafaeb720f9b20bdf/transformer/transformers/transformedidentifier.go
|
37 |
+
*
|
38 |
+
* @package ampproject/amp-toolbox
|
39 |
+
*/
|
40 |
+
final class ServerSideRendering implements Transformer
|
41 |
+
{
|
42 |
+
|
43 |
+
/**
|
44 |
+
* List of layouts that support server-side rendering.
|
45 |
+
*
|
46 |
+
* @var string[]
|
47 |
+
*/
|
48 |
+
const SUPPORTED_LAYOUTS = [
|
49 |
+
'',
|
50 |
+
Layout::NODISPLAY,
|
51 |
+
Layout::FIXED,
|
52 |
+
Layout::FIXED_HEIGHT,
|
53 |
+
Layout::RESPONSIVE,
|
54 |
+
Layout::CONTAINER,
|
55 |
+
Layout::FILL,
|
56 |
+
Layout::FLEX_ITEM,
|
57 |
+
Layout::INTRINSIC,
|
58 |
+
];
|
59 |
+
|
60 |
+
/**
|
61 |
+
* List of elements to exclude from rendering their layout at the server.
|
62 |
+
*
|
63 |
+
* @var string[]
|
64 |
+
*/
|
65 |
+
const EXCLUDED_ELEMENTS = [
|
66 |
+
'amp-audio',
|
67 |
+
];
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Regex pattern to match a CSS Dimension with an associated media condition.
|
71 |
+
*
|
72 |
+
* @var string
|
73 |
+
*/
|
74 |
+
const CSS_DIMENSION_WITH_MEDIA_CONDITION_REGEX_PATTERN = '/\s*(?<media_condition>\(.*\))\s+(?<dimension>.*)\s*/m';
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Smallest acceptable difference in floating point comparisons.
|
78 |
+
*
|
79 |
+
* @var float
|
80 |
+
*/
|
81 |
+
const FLOATING_POINT_EPSILON = 0.00001;
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Associative array of custom sizer styles where the key is the ID of the associated element.
|
85 |
+
*
|
86 |
+
* @var string[]
|
87 |
+
*/
|
88 |
+
private $customSizerStyles = [];
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Custom CSS rules that were extracted to remove blocking attributes.
|
92 |
+
*
|
93 |
+
* @var CssRules
|
94 |
+
*/
|
95 |
+
private $customCss;
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Apply transformations to the provided DOM document.
|
99 |
+
*
|
100 |
+
* @param Document $document DOM document to apply the transformations to.
|
101 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
102 |
+
* @return void
|
103 |
+
*/
|
104 |
+
public function transform(Document $document, ErrorCollection $errors)
|
105 |
+
{
|
106 |
+
if ($this->isAlreadyTransformed($document)) {
|
107 |
+
return;
|
108 |
+
}
|
109 |
+
|
110 |
+
// Reset internal state for a new transform.
|
111 |
+
$this->customCss = new CssRules();
|
112 |
+
|
113 |
+
/*
|
114 |
+
* Within the loop we apply the layout to the custom tags (amp-foo...) where possible, but while we're at this
|
115 |
+
* we also look for reasons not to remove the boilerplate.
|
116 |
+
*/
|
117 |
+
$canRemoveBoilerplate = true;
|
118 |
+
foreach ($document->ampElements as $ampElement) {
|
119 |
+
// Make sure we only deal with valid elements.
|
120 |
+
if (! $ampElement instanceof Element) {
|
121 |
+
continue;
|
122 |
+
}
|
123 |
+
|
124 |
+
// Skip tags inside a template tag.
|
125 |
+
if ($this->isWithinTemplate($ampElement)) {
|
126 |
+
continue;
|
127 |
+
}
|
128 |
+
|
129 |
+
/*
|
130 |
+
* amp-experiment is a render delaying extension iff the tag is used in the doc. We check for that here
|
131 |
+
* rather than checking for the existence of the amp-experiment script in IsRenderDelayingExtension below.
|
132 |
+
*/
|
133 |
+
if ($ampElement->tagName === Extension::EXPERIMENT && $this->isAmpExperimentUsed($ampElement)) {
|
134 |
+
$errors->add(Error\CannotRemoveBoilerplate::fromAmpExperiment($ampElement));
|
135 |
+
$canRemoveBoilerplate = false;
|
136 |
+
}
|
137 |
+
|
138 |
+
/*
|
139 |
+
* amp-audio requires knowing the dimensions of the browser. Do not remove the boilerplate or apply layout
|
140 |
+
* if amp-audio is present in the document.
|
141 |
+
*/
|
142 |
+
if ($ampElement->tagName === Extension::AUDIO) {
|
143 |
+
$errors->add(Error\CannotRemoveBoilerplate::fromAmpAudio($ampElement));
|
144 |
+
$canRemoveBoilerplate = false;
|
145 |
+
}
|
146 |
+
|
147 |
+
/*
|
148 |
+
* Try to adapt 'sizes', 'heights' and 'media' attribute to turn them from blocking attributes into
|
149 |
+
* CSS styles we add to <style amp-custom>.
|
150 |
+
*/
|
151 |
+
$attributesToRemove = $this->adaptBlockingAttributes($document, $ampElement, $errors);
|
152 |
+
if ($attributesToRemove === false) {
|
153 |
+
$canRemoveBoilerplate = false;
|
154 |
+
}
|
155 |
+
|
156 |
+
/*
|
157 |
+
* Now apply the layout to the custom elements. If we encounter any unsupported layout, the applyLayout()
|
158 |
+
* method returns false and we can't remove the boilerplate.
|
159 |
+
*/
|
160 |
+
$adaptedElement = $this->applyLayout($document, $ampElement, $errors);
|
161 |
+
if ($adaptedElement === false) {
|
162 |
+
$errors->add(Error\CannotRemoveBoilerplate::fromUnsupportedLayout($ampElement));
|
163 |
+
$canRemoveBoilerplate = false;
|
164 |
+
}
|
165 |
+
|
166 |
+
// Removal of attributes is deferred as layout application needs them.
|
167 |
+
if (is_array($attributesToRemove)) {
|
168 |
+
foreach ($attributesToRemove as $attributeToRemove) {
|
169 |
+
$adaptedElement->removeAttribute($attributeToRemove);
|
170 |
+
}
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
$this->renderCustomCss($document);
|
175 |
+
|
176 |
+
// Emit the amp-runtime marker to indicate that we're applying server side rendering in the document.
|
177 |
+
$ampRuntimeMarker = $document->createElement(Tag::STYLE);
|
178 |
+
$ampRuntimeMarker->setAttribute(Attribute::AMP_RUNTIME, '');
|
179 |
+
$document->head->insertBefore(
|
180 |
+
$ampRuntimeMarker,
|
181 |
+
$document->head->hasChildNodes()
|
182 |
+
? $document->head->firstChild
|
183 |
+
: null
|
184 |
+
);
|
185 |
+
|
186 |
+
foreach ($document->xpath->query('.//script[ @custom-element ]', $document->head) as $customElementScript) {
|
187 |
+
/** @var Element $customElementScript */
|
188 |
+
// amp-experiment is a render delaying extension iff the tag is used in the doc, which we checked for above.
|
189 |
+
if (
|
190 |
+
$customElementScript->getAttribute(Attribute::CUSTOM_ELEMENT) !== Extension::EXPERIMENT
|
191 |
+
&& Amp::isRenderDelayingExtension($customElementScript)
|
192 |
+
) {
|
193 |
+
$errors->add(Error\CannotRemoveBoilerplate::fromRenderDelayingScript($customElementScript));
|
194 |
+
$canRemoveBoilerplate = false;
|
195 |
+
}
|
196 |
+
}
|
197 |
+
|
198 |
+
/*
|
199 |
+
* Below, we're only concerned about removing the boilerplate.
|
200 |
+
* If we've already determined that we can't, we're done here.
|
201 |
+
*/
|
202 |
+
if (! $canRemoveBoilerplate) {
|
203 |
+
return;
|
204 |
+
}
|
205 |
+
|
206 |
+
// The boilerplate can be removed, note it on the <html> tag.
|
207 |
+
$document->html->setAttribute(Amp::NO_BOILERPLATE_ATTRIBUTE, '');
|
208 |
+
|
209 |
+
/*
|
210 |
+
* Find the boilerplate and remove it.
|
211 |
+
* The following code assumes that the <noscript> tag in the head is only ever used for boilerplate.
|
212 |
+
*/
|
213 |
+
foreach ($document->xpath->query('.//noscript', $document->head) as $noscriptTagInHead) {
|
214 |
+
/** @var Element $noscriptTagInHead */
|
215 |
+
$noscriptTagInHead->parentNode->removeChild($noscriptTagInHead);
|
216 |
+
}
|
217 |
+
|
218 |
+
$boilerplateStyleTags = $document->xpath->query(
|
219 |
+
'.//style[ @amp-boilerplate or @amp4ads-boilerplate or @amp4email-boilerplate ]',
|
220 |
+
$document->head
|
221 |
+
);
|
222 |
+
|
223 |
+
foreach ($boilerplateStyleTags as $boilerplateStyleTag) {
|
224 |
+
/** @var Element $boilerplateStyleTag */
|
225 |
+
$boilerplateStyleTag->parentNode->removeChild($boilerplateStyleTag);
|
226 |
+
}
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Check whether the document was already transformed.
|
231 |
+
*
|
232 |
+
* We want to ensure we don't apply server-side rendering modifications more than once.
|
233 |
+
*
|
234 |
+
* @param Document $document DOM document to apply the transformations to.
|
235 |
+
* @return bool Whether the document was already transformed.
|
236 |
+
*/
|
237 |
+
private function isAlreadyTransformed(Document $document)
|
238 |
+
{
|
239 |
+
if ($document->html->hasAttribute(Amp::LAYOUT_ATTRIBUTE)) {
|
240 |
+
return true;
|
241 |
+
}
|
242 |
+
|
243 |
+
// Mark the document as "already transformed".
|
244 |
+
$document->html->setAttribute(Amp::LAYOUT_ATTRIBUTE, '');
|
245 |
+
|
246 |
+
return false;
|
247 |
+
}
|
248 |
+
|
249 |
+
/**
|
250 |
+
* Apply the adequate layout to a custom element.
|
251 |
+
*
|
252 |
+
* @param Element $element Element to apply the layout to.
|
253 |
+
* @param Document $document DOM document to apply the transformations to.
|
254 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
255 |
+
* @return Element|false Adapted element, or false if the layout could not be applied.
|
256 |
+
*/
|
257 |
+
private function applyLayout(Document $document, Element $element, ErrorCollection $errors)
|
258 |
+
{
|
259 |
+
$ampLayout = $this->parseLayout($element->getAttribute(Attribute::LAYOUT));
|
260 |
+
|
261 |
+
$attrWidth = $element->hasAttribute(Attribute::WIDTH) ? $element->getAttribute(Attribute::WIDTH) : null;
|
262 |
+
$inputWidth = new CssLength($attrWidth);
|
263 |
+
$inputWidth->validate(/* $allowAuto */ true, /* $allowFluid */ false);
|
264 |
+
if (! $inputWidth->isValid()) {
|
265 |
+
$errors->add(Error\CannotPerformServerSideRendering::fromInvalidInputWidth($element));
|
266 |
+
return false;
|
267 |
+
}
|
268 |
+
|
269 |
+
$attrHeight = $element->hasAttribute(Attribute::HEIGHT) ? $element->getAttribute(Attribute::HEIGHT) : null;
|
270 |
+
$inputHeight = new CssLength($attrHeight);
|
271 |
+
$inputHeight->validate(/* $allowAuto */ true, /* $allowFluid */ $ampLayout === Layout::FLUID);
|
272 |
+
if (! $inputHeight->isValid()) {
|
273 |
+
$errors->add(Error\CannotPerformServerSideRendering::fromInvalidInputHeight($element));
|
274 |
+
return false;
|
275 |
+
}
|
276 |
+
|
277 |
+
// Calculate effective width, height and layout.
|
278 |
+
$width = $this->calculateWidth($ampLayout, $inputWidth, $element->tagName);
|
279 |
+
$height = $this->calculateHeight($ampLayout, $inputHeight, $element->tagName);
|
280 |
+
$layout = $this->calculateLayout(
|
281 |
+
$ampLayout,
|
282 |
+
$width,
|
283 |
+
$height,
|
284 |
+
$element->getAttribute(Attribute::SIZES),
|
285 |
+
$element->getAttribute(Attribute::HEIGHTS)
|
286 |
+
);
|
287 |
+
|
288 |
+
if (! $this->isSupportedLayout($layout)) {
|
289 |
+
$errors->add(Error\CannotPerformServerSideRendering::fromUnsupportedLayout($element, $layout));
|
290 |
+
return false;
|
291 |
+
}
|
292 |
+
|
293 |
+
try {
|
294 |
+
/** @var Element $newElement */
|
295 |
+
$newElement = $element->cloneNode(false);
|
296 |
+
|
297 |
+
// Transformed AMP validation requires layout attribute to be set.
|
298 |
+
// See https://github.com/ampproject/amp-toolbox/issues/959
|
299 |
+
if ($layout && $layout === Layout::RESPONSIVE) {
|
300 |
+
$newElement->setAttribute(Attribute::LAYOUT, $layout);
|
301 |
+
}
|
302 |
+
|
303 |
+
$this->applyLayoutAttributes($newElement, $layout, $width, $height);
|
304 |
+
$this->maybeAddSizerInto($document, $newElement, $layout, $width, $height);
|
305 |
+
$element->parentNode->replaceChild($newElement, $element);
|
306 |
+
while ($element->firstChild) {
|
307 |
+
$newElement->appendChild($element->removeChild($element->firstChild));
|
308 |
+
}
|
309 |
+
} catch (MaxCssByteCountExceeded $exception) {
|
310 |
+
$errors->add(
|
311 |
+
Error\CannotPerformServerSideRendering::fromMaxCssByteCountExceededException($exception, $element)
|
312 |
+
);
|
313 |
+
return false;
|
314 |
+
}
|
315 |
+
|
316 |
+
return $newElement;
|
317 |
+
}
|
318 |
+
|
319 |
+
/**
|
320 |
+
* Parse the layout attribute value.
|
321 |
+
*
|
322 |
+
* @param string $layout Layout attribute value.
|
323 |
+
* @return string Validated AMP layout, or empty string if none.
|
324 |
+
*/
|
325 |
+
private function parseLayout($layout)
|
326 |
+
{
|
327 |
+
if (empty($layout)) {
|
328 |
+
return '';
|
329 |
+
}
|
330 |
+
|
331 |
+
$layout = strtolower($layout);
|
332 |
+
|
333 |
+
if (array_key_exists($layout, Layout::TO_SPEC)) {
|
334 |
+
return $layout;
|
335 |
+
}
|
336 |
+
|
337 |
+
return '';
|
338 |
+
}
|
339 |
+
|
340 |
+
/**
|
341 |
+
* Calculate the width of an element for its requested layout.
|
342 |
+
*
|
343 |
+
* @param string $inputLayout Requested layout.
|
344 |
+
* @param CssLength $inputWidth Input value for the width.
|
345 |
+
* @param string $tagName Tag name of the element.
|
346 |
+
* @return CssLength Calculated Width.
|
347 |
+
*/
|
348 |
+
private function calculateWidth($inputLayout, CssLength $inputWidth, $tagName)
|
349 |
+
{
|
350 |
+
if ((empty($inputLayout) || $inputLayout === Layout::FIXED) && ! $inputWidth->isDefined()) {
|
351 |
+
// These values come from AMP's runtime and can be found in
|
352 |
+
// https://github.com/ampproject/amphtml/blob/292dc66b8c0bb078bbe3a1bca960e8f494f7fc8f/src/layout.js#L70-L86
|
353 |
+
switch ($tagName) {
|
354 |
+
case Extension::ANALYTICS:
|
355 |
+
case Extension::PIXEL:
|
356 |
+
$width = new CssLength('1px');
|
357 |
+
$width->validate(/* $allowAuto */ false, /* $allowFluid */ false);
|
358 |
+
return $width;
|
359 |
+
case Extension::AUDIO:
|
360 |
+
$width = new CssLength(CssLength::AUTO);
|
361 |
+
$width->validate(/* $allowAuto */ true, /* $allowFluid */ false);
|
362 |
+
return $width;
|
363 |
+
case Extension::SOCIAL_SHARE:
|
364 |
+
$width = new CssLength('60px');
|
365 |
+
$width->validate(/* $allowAuto */ false, /* $allowFluid */ false);
|
366 |
+
return $width;
|
367 |
+
}
|
368 |
+
}
|
369 |
+
|
370 |
+
return $inputWidth;
|
371 |
+
}
|
372 |
+
|
373 |
+
/**
|
374 |
+
* Calculate the height of an element for its requested layout.
|
375 |
+
*
|
376 |
+
* @param string $inputLayout Requested layout.
|
377 |
+
* @param CssLength $inputHeight Input value for the height.
|
378 |
+
* @param string $tagName Tag name of the element.
|
379 |
+
* @return CssLength Calculated Height.
|
380 |
+
*/
|
381 |
+
private function calculateHeight($inputLayout, CssLength $inputHeight, $tagName)
|
382 |
+
{
|
383 |
+
if (
|
384 |
+
(
|
385 |
+
empty($inputLayout)
|
386 |
+
|| $inputLayout === Layout::FIXED
|
387 |
+
|| $inputLayout === Layout::FIXED_HEIGHT
|
388 |
+
) && ! $inputHeight->isDefined()
|
389 |
+
) {
|
390 |
+
// These values come from AMP's runtime and can be found in
|
391 |
+
// https://github.com/ampproject/amphtml/blob/292dc66b8c0bb078bbe3a1bca960e8f494f7fc8f/src/layout.js#L70-L86
|
392 |
+
switch ($tagName) {
|
393 |
+
case Extension::ANALYTICS:
|
394 |
+
case Extension::PIXEL:
|
395 |
+
$height = new CssLength('1px');
|
396 |
+
$height->validate(/* $allowAuto */ false, /* $allowFluid */ false);
|
397 |
+
return $height;
|
398 |
+
case Extension::AUDIO:
|
399 |
+
$height = new CssLength(CssLength::AUTO);
|
400 |
+
$height->validate(/* $allowAuto */ true, /* $allowFluid */ false);
|
401 |
+
return $height;
|
402 |
+
case Extension::SOCIAL_SHARE:
|
403 |
+
$height = new CssLength('44px');
|
404 |
+
$height->validate(/* $allowAuto */ false, /* $allowFluid */ false);
|
405 |
+
return $height;
|
406 |
+
}
|
407 |
+
}
|
408 |
+
|
409 |
+
return $inputHeight;
|
410 |
+
}
|
411 |
+
|
412 |
+
/**
|
413 |
+
* Calculate the final AMP layout attribute for an element.
|
414 |
+
*
|
415 |
+
* @param string $inputLayout Requested layout.
|
416 |
+
* @param CssLength $width Calculated width.
|
417 |
+
* @param CssLength $height Calculated height.
|
418 |
+
* @param string $sizesAttr Sizes attribute value.
|
419 |
+
* @param string $heightsAttr Heights attribute value.
|
420 |
+
* @return string Calculated layout.
|
421 |
+
*/
|
422 |
+
private function calculateLayout(
|
423 |
+
$inputLayout,
|
424 |
+
CssLength $width,
|
425 |
+
CssLength $height,
|
426 |
+
$sizesAttr,
|
427 |
+
$heightsAttr
|
428 |
+
) {
|
429 |
+
if (! empty($inputLayout)) {
|
430 |
+
return $inputLayout;
|
431 |
+
}
|
432 |
+
|
433 |
+
if (! $width->isDefined() && ! $height->isDefined()) {
|
434 |
+
return Layout::CONTAINER;
|
435 |
+
}
|
436 |
+
|
437 |
+
if ($height->isDefined() && (! $width->isDefined() || $width->isAuto())) {
|
438 |
+
return Layout::FIXED_HEIGHT;
|
439 |
+
}
|
440 |
+
|
441 |
+
if ($height->isDefined() && $width->isDefined() && (! empty($sizesAttr) || ! empty($heightsAttr))) {
|
442 |
+
return Layout::RESPONSIVE;
|
443 |
+
}
|
444 |
+
|
445 |
+
return Layout::FIXED;
|
446 |
+
}
|
447 |
+
|
448 |
+
/**
|
449 |
+
* Check whether a layout is support for SSR.
|
450 |
+
*
|
451 |
+
* @param string $layout Layout to check.
|
452 |
+
* @return bool Whether the layout is supported for SSR.
|
453 |
+
*/
|
454 |
+
private function isSupportedLayout($layout)
|
455 |
+
{
|
456 |
+
return in_array($layout, self::SUPPORTED_LAYOUTS, true);
|
457 |
+
}
|
458 |
+
|
459 |
+
/**
|
460 |
+
* Apply the calculated layout attributes to an element.
|
461 |
+
*
|
462 |
+
* @param Element $element Element to apply the layout attributes to.
|
463 |
+
* @param string $layout Final layout.
|
464 |
+
* @param CssLength $width Calculated width.
|
465 |
+
* @param CssLength $height Calculated height.
|
466 |
+
*/
|
467 |
+
private function applyLayoutAttributes(Element $element, $layout, CssLength $width, CssLength $height)
|
468 |
+
{
|
469 |
+
if ($this->isExcludedElement($element)) {
|
470 |
+
return;
|
471 |
+
}
|
472 |
+
|
473 |
+
$this->addClass($element, $this->getLayoutClass($layout));
|
474 |
+
|
475 |
+
if ($this->isLayoutSizeDefined($layout)) {
|
476 |
+
$this->addClass($element, Amp::LAYOUT_SIZE_DEFINED_CLASS);
|
477 |
+
}
|
478 |
+
|
479 |
+
$styles = '';
|
480 |
+
switch ($layout) {
|
481 |
+
case Layout::NODISPLAY:
|
482 |
+
$element->setAttribute(Attribute::HIDDEN, Attribute::HIDDEN);
|
483 |
+
break;
|
484 |
+
case Layout::FIXED:
|
485 |
+
$styles = "width:{$width->getNumeral()}{$width->getUnit()};"
|
486 |
+
. "height:{$height->getNumeral()}{$height->getUnit()};";
|
487 |
+
break;
|
488 |
+
case Layout::FIXED_HEIGHT:
|
489 |
+
$styles = "height:{$height->getNumeral()}{$height->getUnit()};";
|
490 |
+
break;
|
491 |
+
case Layout::RESPONSIVE:
|
492 |
+
case Layout::INTRINSIC:
|
493 |
+
// Do nothing here but emit <i-amphtml-sizer> later.
|
494 |
+
break;
|
495 |
+
case Layout::FILL:
|
496 |
+
case Layout::CONTAINER:
|
497 |
+
// Do nothing here.
|
498 |
+
break;
|
499 |
+
case Layout::FLEX_ITEM:
|
500 |
+
if ($width->isDefined()) {
|
501 |
+
$styles = "width:{$width->getNumeral()}{$width->getUnit()};";
|
502 |
+
}
|
503 |
+
if ($height->isDefined()) {
|
504 |
+
$styles .= "height:{$height->getNumeral()}{$height->getUnit()};";
|
505 |
+
}
|
506 |
+
break;
|
507 |
+
}
|
508 |
+
|
509 |
+
if (!empty($styles)) {
|
510 |
+
$element->addInlineStyle($styles);
|
511 |
+
}
|
512 |
+
|
513 |
+
$element->setAttribute(Amp::LAYOUT_ATTRIBUTE, $layout);
|
514 |
+
}
|
515 |
+
|
516 |
+
/**
|
517 |
+
* Get the class to use for a given layout.
|
518 |
+
*
|
519 |
+
* @param string $layout Layout to get the class for.
|
520 |
+
* @return string Class name to use for the layout.
|
521 |
+
*/
|
522 |
+
private function getLayoutClass($layout)
|
523 |
+
{
|
524 |
+
if (empty($layout)) {
|
525 |
+
return '';
|
526 |
+
}
|
527 |
+
|
528 |
+
return Amp::LAYOUT_CLASS_PREFIX . $layout;
|
529 |
+
}
|
530 |
+
|
531 |
+
/**
|
532 |
+
* Add a class to an element.
|
533 |
+
*
|
534 |
+
* This makes sure we keep existing classes on the element.
|
535 |
+
*
|
536 |
+
* @param Element $element Element to add a class to.
|
537 |
+
* @param string $class Class to add.
|
538 |
+
*/
|
539 |
+
private function addClass(Element $element, $class)
|
540 |
+
{
|
541 |
+
if ($element->hasAttribute(Attribute::CLASS_) && ! empty($element->getAttribute(Attribute::CLASS_))) {
|
542 |
+
$class = "{$element->getAttribute(Attribute::CLASS_)} {$class}";
|
543 |
+
}
|
544 |
+
|
545 |
+
$element->setAttribute(Attribute::CLASS_, $class);
|
546 |
+
}
|
547 |
+
|
548 |
+
/**
|
549 |
+
* Check whether the provided layout is a layout with a defined size.
|
550 |
+
*
|
551 |
+
* @param string $layout Layout to check.
|
552 |
+
* @return bool Whether the layout has a defined size.
|
553 |
+
*/
|
554 |
+
private function isLayoutSizeDefined($layout)
|
555 |
+
{
|
556 |
+
return in_array($layout, Layout::SIZE_DEFINED_LAYOUTS, true);
|
557 |
+
}
|
558 |
+
|
559 |
+
/**
|
560 |
+
* Insert a sizer element if one is required.
|
561 |
+
*
|
562 |
+
* @param Document $document DOM document to add a sizer to.
|
563 |
+
* @param Element $element Element to add a sizer to.
|
564 |
+
* @param string $layout Calculated layout of the element.
|
565 |
+
* @param CssLength $width Calculated width of the element.
|
566 |
+
* @param CssLength $height Calculated height of the element.
|
567 |
+
*/
|
568 |
+
private function maybeAddSizerInto(
|
569 |
+
Document $document,
|
570 |
+
Element $element,
|
571 |
+
$layout,
|
572 |
+
CssLength $width,
|
573 |
+
CssLength $height
|
574 |
+
) {
|
575 |
+
if (
|
576 |
+
! $width->isDefined()
|
577 |
+
|| $this->isZero($width->getNumeral())
|
578 |
+
|| ! $height->isDefined()
|
579 |
+
|| $width->getUnit() !== $height->getUnit()
|
580 |
+
) {
|
581 |
+
return;
|
582 |
+
}
|
583 |
+
|
584 |
+
$sizer = null;
|
585 |
+
|
586 |
+
if ($layout === Layout::RESPONSIVE) {
|
587 |
+
$elementId = $element->getAttribute(Attribute::ID);
|
588 |
+
if (!empty($elementId) && array_key_exists($elementId, $this->customSizerStyles)) {
|
589 |
+
$sizer = $this->createResponsiveSizer($document, $width, $height, $this->customSizerStyles[$elementId]);
|
590 |
+
} else {
|
591 |
+
$sizer = $this->createResponsiveSizer($document, $width, $height);
|
592 |
+
}
|
593 |
+
} elseif ($layout === Layout::INTRINSIC) {
|
594 |
+
$sizer = $this->createIntrinsicSizer($document, $width, $height);
|
595 |
+
}
|
596 |
+
|
597 |
+
if ($sizer) {
|
598 |
+
$element->insertBefore($sizer, $element->firstChild);
|
599 |
+
}
|
600 |
+
}
|
601 |
+
|
602 |
+
/**
|
603 |
+
* Create a sizer element for a responsive layout.
|
604 |
+
*
|
605 |
+
* @param Document $document DOM document to create the sizer for.
|
606 |
+
* @param CssLength $width Calculated width of the element.
|
607 |
+
* @param CssLength $height Calculated height of the element.
|
608 |
+
* @param string $style Style to use for the sizer. Defaults to padding-top in percentage.
|
609 |
+
* @return Element
|
610 |
+
*/
|
611 |
+
private function createResponsiveSizer(
|
612 |
+
Document $document,
|
613 |
+
CssLength $width,
|
614 |
+
CssLength $height,
|
615 |
+
$style = 'padding-top:%s%%'
|
616 |
+
) {
|
617 |
+
$padding = $height->getNumeral() / $width->getNumeral() * 100;
|
618 |
+
$paddingString = rtrim(rtrim(sprintf('%.4F', round($padding, 4)), '0'), '.');
|
619 |
+
|
620 |
+
$style = empty($style) ? 'display:block' : "display:block;{$style}";
|
621 |
+
|
622 |
+
$sizer = $document->createElement(Amp::SIZER_ELEMENT);
|
623 |
+
$sizer->addInlineStyle(sprintf($style, $paddingString));
|
624 |
+
|
625 |
+
return $sizer;
|
626 |
+
}
|
627 |
+
|
628 |
+
/**
|
629 |
+
* Create a sizer element for an intrinsic layout.
|
630 |
+
*
|
631 |
+
* Intrinsic uses an svg inside the sizer element rather than the padding trick.
|
632 |
+
* Note: a naked svg won't work because other things expect the i-amphtml-sizer element.
|
633 |
+
*
|
634 |
+
* @param Document $document DOM document to create the sizer for.
|
635 |
+
* @param CssLength $width Calculated width of the element.
|
636 |
+
* @param CssLength $height Calculated height of the element.
|
637 |
+
* @return Element
|
638 |
+
*/
|
639 |
+
private function createIntrinsicSizer(Document $document, CssLength $width, CssLength $height)
|
640 |
+
{
|
641 |
+
$sizer = $document->createElement(Amp::SIZER_ELEMENT);
|
642 |
+
$sizer->setAttribute(Attribute::CLASS_, Amp::SIZER_ELEMENT);
|
643 |
+
|
644 |
+
$sizer_img = $document->createElement(Tag::IMG);
|
645 |
+
$sizer_img->setAttribute(Attribute::ALT, '');
|
646 |
+
$sizer_img->setAttribute(Attribute::ARIA_HIDDEN, 'true');
|
647 |
+
$sizer_img->setAttribute(Attribute::CLASS_, Amp::INTRINSIC_SIZER_ELEMENT);
|
648 |
+
$sizer_img->setAttribute(Attribute::ROLE, Role::PRESENTATION);
|
649 |
+
|
650 |
+
$sizer_img->setAttribute(
|
651 |
+
Attribute::SRC,
|
652 |
+
sprintf(
|
653 |
+
'data:image/svg+xml;base64,%s',
|
654 |
+
base64_encode("<svg height=\"{$height->getNumeral()}\" width=\"{$width->getNumeral()}\" "
|
655 |
+
. "xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"/>")
|
656 |
+
)
|
657 |
+
);
|
658 |
+
|
659 |
+
$sizer->appendChild($sizer_img);
|
660 |
+
|
661 |
+
return $sizer;
|
662 |
+
}
|
663 |
+
|
664 |
+
/**
|
665 |
+
* Check whether the element is within a template.
|
666 |
+
*
|
667 |
+
* @param Element $element Element to check.
|
668 |
+
* @return bool Whether the element is within a template.
|
669 |
+
*/
|
670 |
+
private function isWithinTemplate(Element $element)
|
671 |
+
{
|
672 |
+
$parent = $element->parentNode;
|
673 |
+
while ($parent !== null) {
|
674 |
+
if ($parent instanceof Element) {
|
675 |
+
if ($parent->tagName === Tag::TEMPLATE) {
|
676 |
+
return true;
|
677 |
+
}
|
678 |
+
|
679 |
+
if ($parent->tagName === Tag::SCRIPT && $parent->hasAttribute(Attribute::TEMPLATE)) {
|
680 |
+
return true;
|
681 |
+
}
|
682 |
+
}
|
683 |
+
$parent = $parent->parentNode;
|
684 |
+
}
|
685 |
+
|
686 |
+
return false;
|
687 |
+
}
|
688 |
+
|
689 |
+
/**
|
690 |
+
* Check if the amp-experiment element is actually used.
|
691 |
+
*
|
692 |
+
* This checks if amp-experiment has one child that is a script/json tag with a textnode that is parsable JSON and
|
693 |
+
* not empty. The validator ensures that the script/json is parsable but since transformers may be used outside of
|
694 |
+
* validation it is checked here as well.
|
695 |
+
*
|
696 |
+
* @param Element $element Element to check.
|
697 |
+
* @return bool Whether the amp-experiment element is actually used.
|
698 |
+
*/
|
699 |
+
private function isAmpExperimentUsed(Element $element)
|
700 |
+
{
|
701 |
+
$script = null;
|
702 |
+
$child = $element->firstChild;
|
703 |
+
|
704 |
+
while ($child) {
|
705 |
+
if (
|
706 |
+
$child instanceof Element
|
707 |
+
&& $child->tagName === Tag::SCRIPT
|
708 |
+
&& strtolower($child->getAttribute(Attribute::TYPE)) === Attribute::TYPE_JSON
|
709 |
+
) {
|
710 |
+
$script = $child;
|
711 |
+
break;
|
712 |
+
}
|
713 |
+
$child = $child->nextSibling;
|
714 |
+
}
|
715 |
+
|
716 |
+
// If not script/json tag, then not used.
|
717 |
+
if ($script === null) {
|
718 |
+
return false;
|
719 |
+
}
|
720 |
+
|
721 |
+
// If not exactly one child is present, then not used.
|
722 |
+
if ($script->childNodes->length !== 1) {
|
723 |
+
return false;
|
724 |
+
}
|
725 |
+
|
726 |
+
$child = $script->firstChild;
|
727 |
+
|
728 |
+
// If child is not a text node or CDATA section, then not used.
|
729 |
+
if ($child->nodeType !== XML_TEXT_NODE && $child->nodeType !== XML_CDATA_SECTION_NODE) {
|
730 |
+
return false;
|
731 |
+
}
|
732 |
+
|
733 |
+
$json = $child->textContent;
|
734 |
+
|
735 |
+
// If textnode is not JSON parsable, then not used.
|
736 |
+
$experiment = json_decode($json, /*$assoc*/ true);
|
737 |
+
if ($experiment === null) {
|
738 |
+
return false;
|
739 |
+
}
|
740 |
+
|
741 |
+
// If JSON is empty, then not used.
|
742 |
+
if (empty($experiment)) {
|
743 |
+
return false;
|
744 |
+
}
|
745 |
+
|
746 |
+
// Otherwise, used.
|
747 |
+
return true;
|
748 |
+
}
|
749 |
+
|
750 |
+
/**
|
751 |
+
* Adapt blocking attributes so that they allow for boilerplate removal.
|
752 |
+
*
|
753 |
+
* Blocking attributes that need special attention are `sizes`, `heights` and `media`.
|
754 |
+
*
|
755 |
+
* @see https://github.com/ampproject/amp-wp/issues/4439
|
756 |
+
*
|
757 |
+
* @param Document $document DOM document to apply the transformations to.
|
758 |
+
* @param Element $ampElement Element to adapt.
|
759 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
760 |
+
* @return string[]|false Attribute names to remove, or false if attributes could not be adapted.
|
761 |
+
*/
|
762 |
+
private function adaptBlockingAttributes(Document $document, Element $ampElement, ErrorCollection $errors)
|
763 |
+
{
|
764 |
+
$attributes = $ampElement->attributes;
|
765 |
+
|
766 |
+
$customCss = [];
|
767 |
+
$attributesToRemove = [];
|
768 |
+
|
769 |
+
foreach ($attributes as $attribute) {
|
770 |
+
/**
|
771 |
+
* Attribute to check.
|
772 |
+
*
|
773 |
+
* @var DOMAttr $attribute
|
774 |
+
*/
|
775 |
+
$normalizedAttributeName = strtolower($attribute->name);
|
776 |
+
|
777 |
+
try {
|
778 |
+
switch ($normalizedAttributeName) {
|
779 |
+
case Attribute::SIZES:
|
780 |
+
if ($ampElement->hasAttribute(Attribute::DISABLE_INLINE_WIDTH)) {
|
781 |
+
// Don't remove sizes when disable-inline-width is set.
|
782 |
+
// @see https://github.com/ampproject/amphtml/pull/27083
|
783 |
+
break;
|
784 |
+
}
|
785 |
+
|
786 |
+
$customCss = array_merge(
|
787 |
+
$customCss,
|
788 |
+
$this->extractSizesAttributeCss($document, $ampElement, $attribute)
|
789 |
+
);
|
790 |
+
$attributesToRemove[] = $attribute->name;
|
791 |
+
break;
|
792 |
+
|
793 |
+
case Attribute::HEIGHTS:
|
794 |
+
$customCss = array_merge(
|
795 |
+
$customCss,
|
796 |
+
$this->extractHeightsAttributeCss($document, $ampElement, $attribute)
|
797 |
+
);
|
798 |
+
$attributesToRemove[] = $attribute->name;
|
799 |
+
break;
|
800 |
+
|
801 |
+
case Attribute::MEDIA:
|
802 |
+
$customCss = array_merge(
|
803 |
+
$customCss,
|
804 |
+
$this->extractMediaAttributeCss($document, $ampElement, $attribute)
|
805 |
+
);
|
806 |
+
$attributesToRemove[] = $attribute->name;
|
807 |
+
break;
|
808 |
+
}
|
809 |
+
} catch (Exception $exception) {
|
810 |
+
$errors->add(Error\CannotRemoveBoilerplate::fromAttributeThrowingException($exception));
|
811 |
+
return false;
|
812 |
+
}
|
813 |
+
}
|
814 |
+
|
815 |
+
foreach ($customCss as $cssRule) {
|
816 |
+
if ($document->getRemainingCustomCssSpace() < $cssRule->getByteCount()) {
|
817 |
+
$errors->add(Error\CannotRemoveBoilerplate::fromAttributesRequiringBoilerplate($ampElement));
|
818 |
+
return false;
|
819 |
+
}
|
820 |
+
|
821 |
+
$this->customCss = $this->customCss->add($cssRule);
|
822 |
+
}
|
823 |
+
|
824 |
+
return $attributesToRemove;
|
825 |
+
}
|
826 |
+
|
827 |
+
/**
|
828 |
+
* Render the custom CSS styling into the document.
|
829 |
+
*
|
830 |
+
* @param Document $document Document to add the custom CSS styling to.
|
831 |
+
*/
|
832 |
+
private function renderCustomCss(Document $document)
|
833 |
+
{
|
834 |
+
$customCss = $this->customCss->getCss();
|
835 |
+
|
836 |
+
if (empty($customCss)) {
|
837 |
+
return;
|
838 |
+
}
|
839 |
+
|
840 |
+
$document->addAmpCustomStyle($customCss);
|
841 |
+
}
|
842 |
+
|
843 |
+
/**
|
844 |
+
* Extract the custom CSS styling from a 'sizes' attribute.
|
845 |
+
*
|
846 |
+
* __sizes__
|
847 |
+
* "One or more strings separated by commas, indicating a set of source sizes. Each source size consists of:
|
848 |
+
* - A media condition. This must be omitted for the last item in the list.
|
849 |
+
* - A source size value."
|
850 |
+
*
|
851 |
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes
|
852 |
+
*
|
853 |
+
* @param Document $document Document containing the element to adapt.
|
854 |
+
* @param Element $element Element to adapt.
|
855 |
+
* @param DOMAttr $attribute Attribute to be extracted.
|
856 |
+
* @return CssRule[] Extract custom CSS styling.
|
857 |
+
*/
|
858 |
+
private function extractSizesAttributeCss(Document $document, Element $element, DOMAttr $attribute)
|
859 |
+
{
|
860 |
+
if (!$element->hasAttribute(Attribute::SRCSET) || empty($element->getAttribute(Attribute::SRCSET))) {
|
861 |
+
// According to the Mozilla docs, a sizes attribute without a valid srcset attribute should have no effect.
|
862 |
+
// Therefore, it should simply be stripped, without producing media queries.
|
863 |
+
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes
|
864 |
+
return [];
|
865 |
+
}
|
866 |
+
|
867 |
+
return $this->extractAttributeCss(
|
868 |
+
$document,
|
869 |
+
$element,
|
870 |
+
$attribute,
|
871 |
+
['#__ID__', 'width:%s'],
|
872 |
+
['@media %s', '#__ID__', 'width:%s']
|
873 |
+
);
|
874 |
+
}
|
875 |
+
|
876 |
+
/**
|
877 |
+
* Extract the custom CSS styling from a 'heights' attribute.
|
878 |
+
*
|
879 |
+
* __heights__ (AMP-specific)
|
880 |
+
* "The value of this attribute is a sizes expression based on media expressions, similar to the sizes attribute on
|
881 |
+
* img tags but with two key differences:
|
882 |
+
* - The value applies to the height, not the width of the element.
|
883 |
+
* - Percent values are allowed. A percent value indicates the percent of the element's width. For example, a value
|
884 |
+
* of 80% indicates that the height of the element will be 80% of the element's width."
|
885 |
+
*
|
886 |
+
* @see https://amp.dev/documentation/guides-and-tutorials/learn/common_attributes/#heights
|
887 |
+
*
|
888 |
+
* @param Document $document Document containing the element to adapt.
|
889 |
+
* @param Element $element Element to adapt.
|
890 |
+
* @param DOMAttr $attribute Attribute to be extracted.
|
891 |
+
* @return CssRule[] Extract custom CSS styling.
|
892 |
+
*/
|
893 |
+
private function extractHeightsAttributeCss(Document $document, Element $element, DOMAttr $attribute)
|
894 |
+
{
|
895 |
+
// TODO: I'm not sure why I initially added this here, it looks very intentional.
|
896 |
+
// However, it doesn't match what the NodeJS version does, which is to add padding-top
|
897 |
+
// to the inline style of the element.
|
898 |
+
// $this->customSizerStyles[$document->getElementId($element)] = '';
|
899 |
+
|
900 |
+
return $this->extractAttributeCss(
|
901 |
+
$document,
|
902 |
+
$element,
|
903 |
+
$attribute,
|
904 |
+
['#__ID__>:first-child', 'padding-top:%s'],
|
905 |
+
['@media %s', '#__ID__>:first-child', 'padding-top:%s']
|
906 |
+
);
|
907 |
+
}
|
908 |
+
|
909 |
+
/**
|
910 |
+
* Extract the custom CSS styling from an attribute and turn into a templated CSS style string.
|
911 |
+
*
|
912 |
+
* @param Document $document Document containing the element to adapt.
|
913 |
+
* @param Element $element Element to adapt.
|
914 |
+
* @param DOMAttr $attribute Attribute to be extracted.
|
915 |
+
* @param string[] $mainStyle CSS rule template for the main style.
|
916 |
+
* @param string[] $mediaQueryStyle CSS rule template for a media query style.
|
917 |
+
* @return CssRule[] Array of CSS rules to use.
|
918 |
+
*/
|
919 |
+
private function extractAttributeCss(
|
920 |
+
Document $document,
|
921 |
+
Element $element,
|
922 |
+
DOMAttr $attribute,
|
923 |
+
$mainStyle,
|
924 |
+
$mediaQueryStyle
|
925 |
+
) {
|
926 |
+
if (empty($attribute->nodeValue)) {
|
927 |
+
return [];
|
928 |
+
}
|
929 |
+
|
930 |
+
$sourceSizes = explode(',', $attribute->nodeValue);
|
931 |
+
$lastItem = trim(array_pop($sourceSizes), CssRule::CSS_TRIM_CHARACTERS);
|
932 |
+
|
933 |
+
if (empty($lastItem)) {
|
934 |
+
throw InvalidHtmlAttribute::fromAttribute($attribute->nodeName, $element);
|
935 |
+
}
|
936 |
+
|
937 |
+
$cssRules = [];
|
938 |
+
$cssRules[] = new CssRule($mainStyle[0], sprintf($mainStyle[1], $lastItem));
|
939 |
+
|
940 |
+
foreach (array_reverse($sourceSizes) as $sourceSize) {
|
941 |
+
$matches = [];
|
942 |
+
if (!preg_match(self::CSS_DIMENSION_WITH_MEDIA_CONDITION_REGEX_PATTERN, $sourceSize, $matches)) {
|
943 |
+
throw InvalidHtmlAttribute::fromAttribute($attribute->nodeName, $element);
|
944 |
+
}
|
945 |
+
|
946 |
+
$mediaCondition = trim($matches['media_condition'], CssRule::CSS_TRIM_CHARACTERS);
|
947 |
+
|
948 |
+
if (empty($mediaCondition)) {
|
949 |
+
throw InvalidHtmlAttribute::fromAttribute($attribute->nodeName, $element);
|
950 |
+
}
|
951 |
+
|
952 |
+
$dimension = trim($matches['dimension'], CssRule::CSS_TRIM_CHARACTERS);
|
953 |
+
|
954 |
+
if (empty($dimension)) {
|
955 |
+
throw InvalidHtmlAttribute::fromAttribute($attribute->nodeName, $element);
|
956 |
+
}
|
957 |
+
|
958 |
+
$cssRules[] = CssRule::withMediaQuery(
|
959 |
+
sprintf($mediaQueryStyle[0], $mediaCondition),
|
960 |
+
$mediaQueryStyle[1],
|
961 |
+
sprintf($mediaQueryStyle[2], $dimension)
|
962 |
+
);
|
963 |
+
}
|
964 |
+
|
965 |
+
$elementId = $document->getElementId($element);
|
966 |
+
$cssRules = array_map(
|
967 |
+
static function (CssRule $cssRule) use ($elementId) {
|
968 |
+
return $cssRule->applyID($elementId);
|
969 |
+
},
|
970 |
+
$cssRules
|
971 |
+
);
|
972 |
+
|
973 |
+
return $cssRules;
|
974 |
+
}
|
975 |
+
|
976 |
+
/**
|
977 |
+
* Extract the custom CSS styling from a 'media' attribute.
|
978 |
+
*
|
979 |
+
* __media__
|
980 |
+
* "The value of media is a media query. If the query does not match, the element is not rendered and its resources
|
981 |
+
* and potentially its child resources will not be fetched. If the browser window changes size or orientation, the
|
982 |
+
* media queries are re-evaluated and elements are hidden and shown based on the new results."
|
983 |
+
*
|
984 |
+
* @param Document $document Document containing the element to adapt.
|
985 |
+
* @param Element $element Element to adapt.
|
986 |
+
* @param DOMAttr $attribute Attribute to be extracted.
|
987 |
+
* @return CssRule[] Extract custom CSS styling.
|
988 |
+
*/
|
989 |
+
private function extractMediaAttributeCss(Document $document, Element $element, DOMAttr $attribute)
|
990 |
+
{
|
991 |
+
$attributeValue = trim($attribute->nodeValue, CssRule::CSS_TRIM_CHARACTERS);
|
992 |
+
|
993 |
+
if (empty($attributeValue)) {
|
994 |
+
return [];
|
995 |
+
}
|
996 |
+
|
997 |
+
$notFound = 0;
|
998 |
+
$attributeValue = preg_replace('/^not\s+/i', '', $attributeValue, 1, $notFound);
|
999 |
+
$not = $notFound ? '' : 'not ';
|
1000 |
+
|
1001 |
+
if ($attributeValue[0] === '(' && ! $notFound) {
|
1002 |
+
// 'not' can only be used with a media type, so we use 'all' as media type if it is missing.
|
1003 |
+
// See quirksmode.org/blog/archives/2012/11/what_the_hells.html#c15586
|
1004 |
+
$attributeValue = 'all and ' . $attributeValue;
|
1005 |
+
}
|
1006 |
+
|
1007 |
+
return [
|
1008 |
+
CssRule::withMediaQuery("@media {$not}{$attributeValue}", '#__ID__', 'display:none')
|
1009 |
+
->applyID($document->getElementId($element)),
|
1010 |
+
];
|
1011 |
+
}
|
1012 |
+
|
1013 |
+
/**
|
1014 |
+
* Check whether a given element should be excluded from applying its layout on the server.
|
1015 |
+
*
|
1016 |
+
* @param Element $element Element to check.
|
1017 |
+
* @return bool Whether to exclude the element or not.
|
1018 |
+
*/
|
1019 |
+
private function isExcludedElement(Element $element)
|
1020 |
+
{
|
1021 |
+
return in_array($element->tagName, self::EXCLUDED_ELEMENTS, true);
|
1022 |
+
}
|
1023 |
+
|
1024 |
+
/**
|
1025 |
+
* Check if a number is zero.
|
1026 |
+
*
|
1027 |
+
* This works correctly with both integer and float values.
|
1028 |
+
*
|
1029 |
+
* @param int|float $number Number to check for zero.
|
1030 |
+
* @return bool Whether the provided number is zero.
|
1031 |
+
* @throws InvalidArgument When an unsupported number type is provided.
|
1032 |
+
*/
|
1033 |
+
private function isZero($number)
|
1034 |
+
{
|
1035 |
+
if (is_int($number)) {
|
1036 |
+
return $number === 0;
|
1037 |
+
}
|
1038 |
+
|
1039 |
+
if (!is_float($number)) {
|
1040 |
+
throw InvalidArgument::forNumericComparison($number);
|
1041 |
+
}
|
1042 |
+
|
1043 |
+
return abs($number) < self::FLOATING_POINT_EPSILON;
|
1044 |
+
}
|
1045 |
+
}
|
includes/vendor/tool/Optimizer/Transformer/TransformedIdentifier.php
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer\Transformer;
|
4 |
+
|
5 |
+
use AmpProject\Dom\Document;
|
6 |
+
use AmpProject\Optimizer\Configuration\TransformedIdentifierConfiguration;
|
7 |
+
use AmpProject\Optimizer\ErrorCollection;
|
8 |
+
use AmpProject\Optimizer\Transformer;
|
9 |
+
use AmpProject\Optimizer\TransformerConfiguration;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Transformer applying the transformed identifier transformations to the HTML input.
|
13 |
+
*
|
14 |
+
* This is ported from the NodeJS optimizer while verifying against the Go version.
|
15 |
+
*
|
16 |
+
* NodeJS:
|
17 |
+
* @version 2ca65a94b77130c91ac11fcc32c94b93cbd2b7a0
|
18 |
+
* @link https://github.com/ampproject/amp-toolbox/blob/2ca65a94b77130c91ac11fcc32c94b93cbd2b7a0/packages/optimizer/lib/transformers/AddTransformedFlag.js
|
19 |
+
*
|
20 |
+
* Go:
|
21 |
+
* @version b26a35142e0ed1458158435b252a0fcd659f93c4
|
22 |
+
* @link https://github.com/ampproject/amppackager/blob/b26a35142e0ed1458158435b252a0fcd659f93c4/transformer/transformers/transformedidentifier.go
|
23 |
+
*
|
24 |
+
* @package ampproject/amp-toolbox
|
25 |
+
*/
|
26 |
+
final class TransformedIdentifier implements Transformer
|
27 |
+
{
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Attribute name of the "transformed" identifier.
|
31 |
+
*
|
32 |
+
* @var string
|
33 |
+
*/
|
34 |
+
const TRANSFORMED_ATTRIBUTE = 'transformed';
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Origin to use for the "transformed" identifier.
|
38 |
+
*
|
39 |
+
* @var string
|
40 |
+
*/
|
41 |
+
const TRANSFORMED_ORIGIN = 'self';
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Configuration store to use.
|
45 |
+
*
|
46 |
+
* @var TransformerConfiguration
|
47 |
+
*/
|
48 |
+
private $configuration;
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Instantiate a TransformedIdentifier object.
|
52 |
+
*
|
53 |
+
* @param TransformerConfiguration $configuration Configuration store to use.
|
54 |
+
*/
|
55 |
+
public function __construct(TransformerConfiguration $configuration)
|
56 |
+
{
|
57 |
+
$this->configuration = $configuration;
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Apply transformations to the provided DOM document.
|
62 |
+
*
|
63 |
+
* @param Document $document DOM document to apply the transformations to.
|
64 |
+
* @param ErrorCollection $errors Collection of errors that are collected during transformation.
|
65 |
+
* @return void
|
66 |
+
*/
|
67 |
+
public function transform(Document $document, ErrorCollection $errors)
|
68 |
+
{
|
69 |
+
$document->html->setAttribute(self::TRANSFORMED_ATTRIBUTE, $this->getOrigin());
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Get the origin that transformed the AMP document.
|
74 |
+
*
|
75 |
+
* @return string Origin of the transformation.
|
76 |
+
*/
|
77 |
+
private function getOrigin()
|
78 |
+
{
|
79 |
+
$version = $this->configuration->get(TransformedIdentifierConfiguration::VERSION);
|
80 |
+
$origin = self::TRANSFORMED_ORIGIN;
|
81 |
+
|
82 |
+
if ($version > 0) {
|
83 |
+
$origin .= ";v={$version}";
|
84 |
+
}
|
85 |
+
|
86 |
+
return $origin;
|
87 |
+
}
|
88 |
+
}
|
includes/vendor/tool/Optimizer/TransformerConfiguration.php
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\Optimizer;
|
4 |
+
|
5 |
+
use AmpProject\Optimizer\Exception\UnknownConfigurationKey;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Interface for a configuration that validates and stores configuration settings for an individual transformer.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
interface TransformerConfiguration
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Get the value for a given key.
|
17 |
+
*
|
18 |
+
* The key is assumed to exist and will throw an exception if it can't be retrieved.
|
19 |
+
* This means that all configuration entries should come with a default value.
|
20 |
+
*
|
21 |
+
* @param string $key Key of the configuration entry to retrieve.
|
22 |
+
* @return mixed Value stored under the given configuration key.
|
23 |
+
* @throws UnknownConfigurationKey If an unknown key was provided.
|
24 |
+
*/
|
25 |
+
public function get($key);
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Get an array of configuration entries for this transformer configuration.
|
29 |
+
*
|
30 |
+
* @return array Associative array of configuration entries.
|
31 |
+
*/
|
32 |
+
public function toArray();
|
33 |
+
}
|
includes/vendor/tool/RemoteGetRequest.php
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
use AmpProject\Exception\FailedRemoteRequest;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Interface for abstracting away the transport that is being used for making remote requests.
|
9 |
+
*
|
10 |
+
* This allows external code to replace the transport and tests to mock it.
|
11 |
+
*
|
12 |
+
* @package ampproject/amp-toolbox
|
13 |
+
*/
|
14 |
+
interface RemoteGetRequest
|
15 |
+
{
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Do a GET request to retrieve the contents of a remote URL.
|
19 |
+
*
|
20 |
+
* @param string $url URL to get.
|
21 |
+
* @return Response Response for the executed request.
|
22 |
+
* @throws FailedRemoteRequest If retrieving the contents from the URL failed.
|
23 |
+
*/
|
24 |
+
public function get($url);
|
25 |
+
}
|
includes/vendor/tool/RemoteRequest/CurlRemoteGetRequest.php
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\RemoteRequest;
|
4 |
+
|
5 |
+
use AmpProject\Exception\FailedRemoteRequest;
|
6 |
+
use AmpProject\Exception\FailedToGetFromRemoteUrl;
|
7 |
+
use AmpProject\RemoteGetRequest;
|
8 |
+
use AmpProject\Response;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Remote request transport using cURL.
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class CurlRemoteGetRequest implements RemoteGetRequest
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Default timeout value to use in seconds.
|
20 |
+
*
|
21 |
+
* @var int
|
22 |
+
*/
|
23 |
+
const DEFAULT_TIMEOUT = 10;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Default number of retry attempts to do.
|
27 |
+
*
|
28 |
+
* @var int
|
29 |
+
*/
|
30 |
+
const DEFAULT_RETRIES = 2;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* List of cURL error codes that are worth retrying for.
|
34 |
+
*
|
35 |
+
* @var int[]
|
36 |
+
*/
|
37 |
+
const RETRYABLE_ERROR_CODES = [
|
38 |
+
CURLE_COULDNT_RESOLVE_HOST,
|
39 |
+
CURLE_COULDNT_CONNECT,
|
40 |
+
CURLE_HTTP_NOT_FOUND,
|
41 |
+
CURLE_READ_ERROR,
|
42 |
+
CURLE_OPERATION_TIMEOUTED,
|
43 |
+
CURLE_HTTP_POST_ERROR,
|
44 |
+
CURLE_SSL_CONNECT_ERROR,
|
45 |
+
];
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Whether to verify SSL certificates or not.
|
49 |
+
*
|
50 |
+
* @var boolean
|
51 |
+
*/
|
52 |
+
private $sslVerify;
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Timeout value to use in seconds.
|
56 |
+
*
|
57 |
+
* @var int
|
58 |
+
*/
|
59 |
+
private $timeout;
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Number of retry attempts to do for an error that is worth retrying.
|
63 |
+
*
|
64 |
+
* @var int
|
65 |
+
*/
|
66 |
+
private $retries;
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Instantiate a CurlRemoteGetRequest object.
|
70 |
+
*
|
71 |
+
* @param bool $sslVerify Optional. Whether to verify SSL certificates. Defaults to true.
|
72 |
+
* @param int $timeout Optional. Timeout value to use in seconds. Defaults to 10.
|
73 |
+
* @param int $retries Optional. Number of retry attempts to do if an error code was thrown that is worth
|
74 |
+
* retrying. Defaults to 2.
|
75 |
+
*/
|
76 |
+
public function __construct($sslVerify = true, $timeout = self::DEFAULT_TIMEOUT, $retries = self::DEFAULT_RETRIES)
|
77 |
+
{
|
78 |
+
if (! is_int($timeout) || $timeout < 0) {
|
79 |
+
$timeout = self::DEFAULT_TIMEOUT;
|
80 |
+
}
|
81 |
+
|
82 |
+
if (! is_int($retries) || $retries < 0) {
|
83 |
+
$retries = self::DEFAULT_RETRIES;
|
84 |
+
}
|
85 |
+
|
86 |
+
$this->sslVerify = $sslVerify;
|
87 |
+
$this->timeout = $timeout;
|
88 |
+
$this->retries = $retries;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Do a GET request to retrieve the contents of a remote URL.
|
93 |
+
*
|
94 |
+
* @param string $url URL to get.
|
95 |
+
* @return Response Response for the executed request.
|
96 |
+
* @throws FailedRemoteRequest If retrieving the contents from the URL failed.
|
97 |
+
*/
|
98 |
+
public function get($url)
|
99 |
+
{
|
100 |
+
$retriesLeft = $this->retries;
|
101 |
+
do {
|
102 |
+
$curlHandle = curl_init();
|
103 |
+
$headers = [];
|
104 |
+
|
105 |
+
curl_setopt($curlHandle, CURLOPT_URL, $url);
|
106 |
+
curl_setopt($curlHandle, CURLOPT_HEADER, 0);
|
107 |
+
curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, true);
|
108 |
+
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, $this->sslVerify ? 1 : 0);
|
109 |
+
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, $this->sslVerify ? 2 : 0);
|
110 |
+
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
|
111 |
+
curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, $this->timeout);
|
112 |
+
curl_setopt($curlHandle, CURLOPT_TIMEOUT, $this->timeout);
|
113 |
+
|
114 |
+
curl_setopt(
|
115 |
+
$curlHandle,
|
116 |
+
CURLOPT_HEADERFUNCTION,
|
117 |
+
static function ($curl, $header) use (&$headers) {
|
118 |
+
$length = strlen($header);
|
119 |
+
$header = array_map('trim', explode(':', $header, 2));
|
120 |
+
|
121 |
+
// Only store valid headers, discard invalid ones that choke on the explode.
|
122 |
+
if (count($header) === 2) {
|
123 |
+
$headers[$header[0]][] = $header[1];
|
124 |
+
}
|
125 |
+
|
126 |
+
return $length;
|
127 |
+
}
|
128 |
+
);
|
129 |
+
|
130 |
+
$body = curl_exec($curlHandle);
|
131 |
+
$status = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
|
132 |
+
|
133 |
+
$curlErrno = curl_errno($curlHandle);
|
134 |
+
curl_close($curlHandle);
|
135 |
+
|
136 |
+
if ($body === false) {
|
137 |
+
if (! $retriesLeft || in_array($curlErrno, self::RETRYABLE_ERROR_CODES, true) === false) {
|
138 |
+
if (! empty($status) && is_numeric($status)) {
|
139 |
+
throw FailedToGetFromRemoteUrl::withHttpStatus($url, (int) $status);
|
140 |
+
}
|
141 |
+
|
142 |
+
throw FailedToGetFromRemoteUrl::withoutHttpStatus($url);
|
143 |
+
}
|
144 |
+
|
145 |
+
continue;
|
146 |
+
}
|
147 |
+
|
148 |
+
return new RemoteGetRequestResponse($body, $headers, (int) $status);
|
149 |
+
} while ($retriesLeft--);
|
150 |
+
|
151 |
+
// This should never be triggered, but we want to ensure we always have a typed return value,
|
152 |
+
// to make PHPStan happy.
|
153 |
+
return new RemoteGetRequestResponse('', [], 500);
|
154 |
+
}
|
155 |
+
}
|
includes/vendor/tool/RemoteRequest/FallbackRemoteGetRequest.php
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\RemoteRequest;
|
4 |
+
|
5 |
+
use AmpProject\Exception\FailedRemoteRequest;
|
6 |
+
use AmpProject\RemoteGetRequest;
|
7 |
+
use AmpProject\Response;
|
8 |
+
use Exception;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Fallback pipeline implementation to go through a series of fallback requests until a request succeeds.
|
12 |
+
*
|
13 |
+
* The request will be tried with the first instance provided, and follow the instance series from one to the next until
|
14 |
+
* a successful response was returned.
|
15 |
+
*
|
16 |
+
* A successful response is a response that doesn't return boolean false and doesn't throw an exception.
|
17 |
+
*
|
18 |
+
* @package ampproject/amp-toolbox
|
19 |
+
*/
|
20 |
+
final class FallbackRemoteGetRequest implements RemoteGetRequest
|
21 |
+
{
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Array of RemoteGetRequest instances to churn through.
|
25 |
+
*
|
26 |
+
* @var RemoteGetRequest[]
|
27 |
+
*/
|
28 |
+
private $pipeline;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Instantiate a FallbackRemoteGetRequest object.
|
32 |
+
*
|
33 |
+
* @param RemoteGetRequest ...$pipeline Variadic array of RemoteGetRequest instances to use as consecutive
|
34 |
+
* fallbacks.
|
35 |
+
*/
|
36 |
+
public function __construct(RemoteGetRequest ...$pipeline)
|
37 |
+
{
|
38 |
+
array_walk($pipeline, [$this, 'addRemoteGetRequestInstance']);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Add a single RemoteGetRequest instance to the pipeline.
|
43 |
+
*
|
44 |
+
* This adds strong typing to the variadic $pipeline argument in the constructor.
|
45 |
+
*
|
46 |
+
* @param RemoteGetRequest $remoteGetRequest
|
47 |
+
*/
|
48 |
+
private function addRemoteGetRequestInstance(RemoteGetRequest $remoteGetRequest)
|
49 |
+
{
|
50 |
+
$this->pipeline[] = $remoteGetRequest;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Do a GET request to retrieve the contents of a remote URL.
|
55 |
+
*
|
56 |
+
* @param string $url URL to get.
|
57 |
+
* @return Response Response for the executed request.
|
58 |
+
* @throws FailedRemoteRequest If retrieving the contents from the URL failed.
|
59 |
+
*/
|
60 |
+
public function get($url)
|
61 |
+
{
|
62 |
+
foreach ($this->pipeline as $remoteGetRequest) {
|
63 |
+
try {
|
64 |
+
$response = $remoteGetRequest->get($url);
|
65 |
+
|
66 |
+
if (! $response instanceof RemoteGetRequestResponse) {
|
67 |
+
continue;
|
68 |
+
}
|
69 |
+
|
70 |
+
$statusCode = $response->getStatusCode();
|
71 |
+
|
72 |
+
if (200 <= $statusCode && $statusCode < 300) {
|
73 |
+
return $response;
|
74 |
+
}
|
75 |
+
} catch (Exception $exception) {
|
76 |
+
// Don't let exceptions bubble up, just continue with the next instance in the pipeline.
|
77 |
+
}
|
78 |
+
}
|
79 |
+
|
80 |
+
// @todo Not sure what status code to use here. "503 Service Unavailable" is a temporary server-side error.
|
81 |
+
return new RemoteGetRequestResponse('', [], 503);
|
82 |
+
}
|
83 |
+
}
|
includes/vendor/tool/RemoteRequest/FilesystemRemoteGetRequest.php
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\RemoteRequest;
|
4 |
+
|
5 |
+
use AmpProject\Exception\FailedRemoteRequest;
|
6 |
+
use AmpProject\Exception\FailedToGetFromRemoteUrl;
|
7 |
+
use AmpProject\RemoteGetRequest;
|
8 |
+
use AmpProject\Response;
|
9 |
+
use Exception;
|
10 |
+
use LogicException;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Fetch the response for a remote request from the local filesystem instead.
|
14 |
+
*
|
15 |
+
* This can be used to provide offline fallbacks.
|
16 |
+
*
|
17 |
+
* @package ampproject/amp-toolbox
|
18 |
+
*/
|
19 |
+
final class FilesystemRemoteGetRequest implements RemoteGetRequest
|
20 |
+
{
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Associative array of data for mapping between arguments and filepaths pointing to the results to return.
|
24 |
+
*
|
25 |
+
* @var array
|
26 |
+
*/
|
27 |
+
private $argumentMap;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Instantiate a FilesystemRemoteGetRequest object.
|
31 |
+
*
|
32 |
+
* @param array $argumentMap Associative array of data for mapping between arguments and filepaths pointing to the
|
33 |
+
* results to return.
|
34 |
+
*/
|
35 |
+
public function __construct($argumentMap)
|
36 |
+
{
|
37 |
+
$this->argumentMap = $argumentMap;
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Do a GET request to retrieve the contents of a remote URL.
|
42 |
+
*
|
43 |
+
* @param string $url URL to get.
|
44 |
+
* @return Response Response for the executed request.
|
45 |
+
* @throws FailedRemoteRequest If retrieving the contents from the URL failed.
|
46 |
+
*/
|
47 |
+
public function get($url)
|
48 |
+
{
|
49 |
+
if (! array_key_exists($url, $this->argumentMap)) {
|
50 |
+
throw new LogicException("Trying to get a remote request from the filesystem for an unknown URL: {$url}.");
|
51 |
+
}
|
52 |
+
|
53 |
+
if (! file_exists($this->argumentMap[$url]) || ! is_readable($this->argumentMap[$url])) {
|
54 |
+
throw new LogicException(
|
55 |
+
'Trying to get a remote request from the filesystem for a file that is not accessible: '
|
56 |
+
. "{$url} => {$this->argumentMap[$url]}."
|
57 |
+
);
|
58 |
+
}
|
59 |
+
|
60 |
+
try {
|
61 |
+
return new RemoteGetRequestResponse(file_get_contents($this->argumentMap[$url]));
|
62 |
+
} catch (Exception $exception) {
|
63 |
+
throw FailedToGetFromRemoteUrl::withException($url, $exception);
|
64 |
+
}
|
65 |
+
}
|
66 |
+
}
|
includes/vendor/tool/RemoteRequest/RemoteGetRequestResponse.php
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\RemoteRequest;
|
4 |
+
|
5 |
+
use AmpProject\Response;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Stub for simulating remote requests.
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
final class RemoteGetRequestResponse implements Response
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Body of the response.
|
17 |
+
*
|
18 |
+
* @var mixed
|
19 |
+
*/
|
20 |
+
private $body;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Headers of the response.
|
24 |
+
*
|
25 |
+
* @var string[][]
|
26 |
+
*/
|
27 |
+
private $headers;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Index mapping lowercase keys to actual keys in $this->headers.
|
31 |
+
*
|
32 |
+
* This is used for case-insensitive lookups.
|
33 |
+
*
|
34 |
+
* @var string[]
|
35 |
+
*/
|
36 |
+
private $headersIndex;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Status code of the response.
|
40 |
+
*
|
41 |
+
* @var int
|
42 |
+
*/
|
43 |
+
private $statusCode;
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Instantiate a RemoteGetRequestResponse object.
|
47 |
+
*
|
48 |
+
* @param mixed $body Body of the response.
|
49 |
+
* @param string[][] $headers Headers of the response.
|
50 |
+
* @param int $statusCode Status code of the response.
|
51 |
+
*/
|
52 |
+
public function __construct($body, $headers = [], $statusCode = 200)
|
53 |
+
{
|
54 |
+
$this->body = $body;
|
55 |
+
$this->headers = $headers;
|
56 |
+
$this->statusCode = $statusCode;
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Retrieves all message header values.
|
61 |
+
*
|
62 |
+
* The keys represent the header name as it will be sent over the wire, and each value is an array of strings
|
63 |
+
* associated with the header.
|
64 |
+
*
|
65 |
+
* // Represent the headers as a string
|
66 |
+
* foreach ($message->getHeaders() as $name => $values) {
|
67 |
+
* echo $name . ': ' . implode(', ', $values);
|
68 |
+
* }
|
69 |
+
*
|
70 |
+
* // Emit headers iteratively:
|
71 |
+
* foreach ($message->getHeaders() as $name => $values) {
|
72 |
+
* foreach ($values as $value) {
|
73 |
+
* header(sprintf('%s: %s', $name, $value), false);
|
74 |
+
* }
|
75 |
+
* }
|
76 |
+
*
|
77 |
+
* While header names are not case-sensitive, getHeaders() will preserve the exact case in which headers were
|
78 |
+
* originally specified.
|
79 |
+
*
|
80 |
+
* @return string[][] Returns an associative array of the message's headers. Each key MUST be a header name, and
|
81 |
+
* each value MUST be an array of strings for that header.
|
82 |
+
*/
|
83 |
+
public function getHeaders()
|
84 |
+
{
|
85 |
+
return $this->headers;
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Checks if a header exists by the given case-insensitive name.
|
90 |
+
*
|
91 |
+
* @param string $name Case-insensitive header field name.
|
92 |
+
* @return bool Returns true if any header names match the given header name using a case-insensitive string
|
93 |
+
* comparison. Returns false if no matching header name is found in the message.
|
94 |
+
*/
|
95 |
+
public function hasHeader($name)
|
96 |
+
{
|
97 |
+
$this->maybeInitHeadersIndex();
|
98 |
+
return array_key_exists(strtolower($name), $this->headersIndex);
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Retrieves a message header value by the given case-insensitive name.
|
103 |
+
*
|
104 |
+
* This method returns an array of all the header values of the given case-insensitive header name.
|
105 |
+
*
|
106 |
+
* If the header does not appear in the message, this method MUST return an empty array.
|
107 |
+
*
|
108 |
+
* @param string $name Case-insensitive header field name.
|
109 |
+
* @return string[] An array of string values as provided for the given header. If the header does not appear in
|
110 |
+
* the message, this method MUST return an empty array.
|
111 |
+
*/
|
112 |
+
public function getHeader($name)
|
113 |
+
{
|
114 |
+
$this->maybeInitHeadersIndex();
|
115 |
+
$key = strtolower($name);
|
116 |
+
|
117 |
+
if (! array_key_exists($key, $this->headersIndex)) {
|
118 |
+
return [];
|
119 |
+
}
|
120 |
+
|
121 |
+
return (array) $this->headers[$this->headersIndex[$key]];
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* Retrieves a comma-separated string of the values for a single header.
|
126 |
+
*
|
127 |
+
* This method returns all of the header values of the given case-insensitive header name as a string concatenated
|
128 |
+
* together using a comma.
|
129 |
+
*
|
130 |
+
* NOTE: Not all header values may be appropriately represented using comma concatenation. For such headers, use
|
131 |
+
* getHeader() instead and supply your own delimiter when concatenating.
|
132 |
+
*
|
133 |
+
* If the header does not appear in the message, this method MUST return an empty string.
|
134 |
+
*
|
135 |
+
* @param string $name Case-insensitive header field name.
|
136 |
+
* @return string A string of values as provided for the given header concatenated together using a comma. If the
|
137 |
+
* header does not appear in the message, this method MUST return an empty string.
|
138 |
+
*/
|
139 |
+
public function getHeaderLine($name)
|
140 |
+
{
|
141 |
+
$key = strtolower($name);
|
142 |
+
|
143 |
+
if (! array_key_exists($key, $this->headersIndex)) {
|
144 |
+
return '';
|
145 |
+
}
|
146 |
+
|
147 |
+
return implode(',', (array)$this->headers[$this->headersIndex[$key]]);
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Gets the response status code.
|
152 |
+
*
|
153 |
+
* The status code is a 3-digit integer result code of the server's attempt to understand and satisfy the request.
|
154 |
+
*
|
155 |
+
* @return int Status code.
|
156 |
+
*/
|
157 |
+
public function getStatusCode()
|
158 |
+
{
|
159 |
+
return $this->statusCode;
|
160 |
+
}
|
161 |
+
|
162 |
+
/**
|
163 |
+
* Get the body of the response.
|
164 |
+
*
|
165 |
+
* @return mixed Body of the response.
|
166 |
+
*/
|
167 |
+
public function getBody()
|
168 |
+
{
|
169 |
+
return $this->body;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Initial the headersIndex for case-insensitive lookups if that hasn't been done yet.
|
174 |
+
*/
|
175 |
+
private function maybeInitHeadersIndex()
|
176 |
+
{
|
177 |
+
if ($this->headersIndex !== null) {
|
178 |
+
return;
|
179 |
+
}
|
180 |
+
|
181 |
+
$this->headersIndex = array_combine(array_keys($this->headers), array_keys($this->headers));
|
182 |
+
$this->headersIndex = array_change_key_case($this->headersIndex, CASE_LOWER);
|
183 |
+
}
|
184 |
+
}
|
includes/vendor/tool/RemoteRequest/StubbedRemoteGetRequest.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject\RemoteRequest;
|
4 |
+
|
5 |
+
use AmpProject\Exception\FailedRemoteRequest;
|
6 |
+
use AmpProject\RemoteGetRequest;
|
7 |
+
use AmpProject\Response;
|
8 |
+
use LogicException;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Stub for simulating remote requests.
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
final class StubbedRemoteGetRequest implements RemoteGetRequest
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Associative array of data for mapping between arguments and returned results.
|
20 |
+
*
|
21 |
+
* @var array
|
22 |
+
*/
|
23 |
+
private $argumentMap;
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Instantiate a StubbedRemoteGetRequest object.
|
27 |
+
*
|
28 |
+
* @param array $argumentMap Associative array of data for mapping between arguments and returned results.
|
29 |
+
*/
|
30 |
+
public function __construct($argumentMap)
|
31 |
+
{
|
32 |
+
$this->argumentMap = $argumentMap;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Do a GET request to retrieve the contents of a remote URL.
|
37 |
+
*
|
38 |
+
* @param string $url URL to get.
|
39 |
+
* @return Response Response for the executed request.
|
40 |
+
* @throws FailedRemoteRequest If retrieving the contents from the URL failed.
|
41 |
+
*/
|
42 |
+
public function get($url)
|
43 |
+
{
|
44 |
+
if (! array_key_exists($url, $this->argumentMap)) {
|
45 |
+
throw new LogicException("Trying to stub a remote request for an unknown URL: {$url}.");
|
46 |
+
}
|
47 |
+
|
48 |
+
return new RemoteGetRequestResponse($this->argumentMap[$url]);
|
49 |
+
}
|
50 |
+
}
|
includes/vendor/tool/RequestDestination.php
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface with constants for the different request destinations that are supported.
|
7 |
+
*
|
8 |
+
* For the purposes of the AMP implementation, we are only interested in the
|
9 |
+
* request destinations that are valid values for the 'as' attribute in preloads.
|
10 |
+
*
|
11 |
+
* Full list of request destinations:
|
12 |
+
* @see https://fetch.spec.whatwg.org/#concept-request-destination
|
13 |
+
*
|
14 |
+
* @package ampproject/amp-toolbox
|
15 |
+
*/
|
16 |
+
interface RequestDestination
|
17 |
+
{
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Audio file, as typically used in <audio>.
|
21 |
+
*
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
const AUDIO = 'audio';
|
25 |
+
|
26 |
+
/**
|
27 |
+
* An HTML document intended to be embedded by a <frame> or <iframe>.
|
28 |
+
*
|
29 |
+
* @var string
|
30 |
+
*/
|
31 |
+
const DOCUMENT = 'document';
|
32 |
+
|
33 |
+
/**
|
34 |
+
* A resource to be embedded inside an <embed> element.
|
35 |
+
*
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
const EMBED = 'embed';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Resource to be accessed by a fetch or XHR request, such as an ArrayBuffer or JSON file.
|
42 |
+
*
|
43 |
+
* @var string
|
44 |
+
*/
|
45 |
+
const FETCH = 'fetch';
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Font file.
|
49 |
+
*
|
50 |
+
* @var string
|
51 |
+
*/
|
52 |
+
const FONT = 'font';
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Image file.
|
56 |
+
*
|
57 |
+
* @var string
|
58 |
+
*/
|
59 |
+
const IMAGE = 'image';
|
60 |
+
|
61 |
+
/**
|
62 |
+
* A resource to be embedded inside an <object> element.
|
63 |
+
*
|
64 |
+
* @var string
|
65 |
+
*/
|
66 |
+
const OBJECT = 'object';
|
67 |
+
|
68 |
+
/**
|
69 |
+
* JavaScript file.
|
70 |
+
*
|
71 |
+
* @var string
|
72 |
+
*/
|
73 |
+
const SCRIPT = 'script';
|
74 |
+
|
75 |
+
/**
|
76 |
+
* CSS stylesheet.
|
77 |
+
*
|
78 |
+
* @var string
|
79 |
+
*/
|
80 |
+
const STYLE = 'style';
|
81 |
+
|
82 |
+
/**
|
83 |
+
* WebVTT file.
|
84 |
+
*
|
85 |
+
* @var string
|
86 |
+
*/
|
87 |
+
const TRACK = 'track';
|
88 |
+
|
89 |
+
/**
|
90 |
+
* A JavaScript web worker or shared worker.
|
91 |
+
*
|
92 |
+
* @var string
|
93 |
+
*/
|
94 |
+
const WORKER = 'worker';
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Video file, as typically used in <video>.
|
98 |
+
*
|
99 |
+
* @var string
|
100 |
+
*/
|
101 |
+
const VIDEO = 'video';
|
102 |
+
}
|
includes/vendor/tool/Response.php
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Response that was returned from a RemoteRequest execution.
|
7 |
+
*
|
8 |
+
* The method signatures are mostly a subset of PSR-7:
|
9 |
+
* @see https://www.php-fig.org/psr/psr-7/
|
10 |
+
*
|
11 |
+
* @todo Consider using PSR-7 directly (both interfaces and a library that implements them).
|
12 |
+
*
|
13 |
+
* @package ampproject/amp-toolbox
|
14 |
+
*/
|
15 |
+
interface Response
|
16 |
+
{
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Retrieves all message header values.
|
20 |
+
*
|
21 |
+
* The keys represent the header name as it will be sent over the wire, and each value is an array of strings
|
22 |
+
* associated with the header.
|
23 |
+
*
|
24 |
+
* // Represent the headers as a string
|
25 |
+
* foreach ($message->getHeaders() as $name => $values) {
|
26 |
+
* echo $name . ': ' . implode(', ', $values);
|
27 |
+
* }
|
28 |
+
*
|
29 |
+
* // Emit headers iteratively:
|
30 |
+
* foreach ($message->getHeaders() as $name => $values) {
|
31 |
+
* foreach ($values as $value) {
|
32 |
+
* header(sprintf('%s: %s', $name, $value), false);
|
33 |
+
* }
|
34 |
+
* }
|
35 |
+
*
|
36 |
+
* While header names are not case-sensitive, getHeaders() will preserve the exact case in which headers were
|
37 |
+
* originally specified.
|
38 |
+
*
|
39 |
+
* @return string[][] Returns an associative array of the message's headers. Each key MUST be a header name, and
|
40 |
+
* each value MUST be an array of strings for that header.
|
41 |
+
*/
|
42 |
+
public function getHeaders();
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Checks if a header exists by the given case-insensitive name.
|
46 |
+
*
|
47 |
+
* @param string $name Case-insensitive header field name.
|
48 |
+
* @return bool Returns true if any header names match the given header name using a case-insensitive string
|
49 |
+
* comparison. Returns false if no matching header name is found in the message.
|
50 |
+
*/
|
51 |
+
public function hasHeader($name);
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Retrieves a message header value by the given case-insensitive name.
|
55 |
+
*
|
56 |
+
* This method returns an array of all the header values of the given case-insensitive header name.
|
57 |
+
*
|
58 |
+
* If the header does not appear in the message, this method MUST return an empty array.
|
59 |
+
*
|
60 |
+
* @param string $name Case-insensitive header field name.
|
61 |
+
* @return string[] An array of string values as provided for the given header. If the header does not appear in
|
62 |
+
* the message, this method MUST return an empty array.
|
63 |
+
*/
|
64 |
+
public function getHeader($name);
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Retrieves a comma-separated string of the values for a single header.
|
68 |
+
*
|
69 |
+
* This method returns all of the header values of the given case-insensitive header name as a string concatenated
|
70 |
+
* together using a comma.
|
71 |
+
*
|
72 |
+
* NOTE: Not all header values may be appropriately represented using comma concatenation. For such headers, use
|
73 |
+
* getHeader() instead and supply your own delimiter when concatenating.
|
74 |
+
*
|
75 |
+
* If the header does not appear in the message, this method MUST return an empty string.
|
76 |
+
*
|
77 |
+
* @param string $name Case-insensitive header field name.
|
78 |
+
* @return string A string of values as provided for the given header concatenated together using a comma. If the
|
79 |
+
* header does not appear in the message, this method MUST return an empty string.
|
80 |
+
*/
|
81 |
+
public function getHeaderLine($name);
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Gets the response status code.
|
85 |
+
*
|
86 |
+
* The status code is a 3-digit integer result code of the server's attempt to understand and satisfy the request.
|
87 |
+
*
|
88 |
+
* @return int Status code.
|
89 |
+
*/
|
90 |
+
public function getStatusCode();
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Get the body of the response.
|
94 |
+
*
|
95 |
+
* @return mixed Body of the response.
|
96 |
+
*/
|
97 |
+
public function getBody();
|
98 |
+
}
|
includes/vendor/tool/Role.php
ADDED
@@ -0,0 +1,524 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface with constants for the different types of accessibility roles.
|
7 |
+
*
|
8 |
+
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques
|
9 |
+
*
|
10 |
+
* @package ampproject/amp-toolbox
|
11 |
+
*/
|
12 |
+
interface Role
|
13 |
+
{
|
14 |
+
|
15 |
+
/**
|
16 |
+
* A message with an alert or error information.
|
17 |
+
*
|
18 |
+
* @var string
|
19 |
+
*/
|
20 |
+
const ALERT = 'alert';
|
21 |
+
|
22 |
+
/**
|
23 |
+
* A separate window with an alert or error information.
|
24 |
+
*
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
const ALERTDIALOG = 'alertdialog';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* A software unit executing a set of tasks for its users.
|
31 |
+
*
|
32 |
+
* @var string
|
33 |
+
*/
|
34 |
+
const APPLICATION = 'application';
|
35 |
+
|
36 |
+
/**
|
37 |
+
* A section of a page that could easily stand on its own on a page, in a document, or on a website.
|
38 |
+
*
|
39 |
+
* @var string
|
40 |
+
*/
|
41 |
+
const ARTICLE = 'article';
|
42 |
+
|
43 |
+
/**
|
44 |
+
* A region that contains mostly site-oriented content, rather than page-specific content.
|
45 |
+
*
|
46 |
+
* @var string
|
47 |
+
*/
|
48 |
+
const BANNER = 'banner';
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Allows for user-triggered actions.
|
52 |
+
*
|
53 |
+
* @var string
|
54 |
+
*/
|
55 |
+
const BUTTON = 'button';
|
56 |
+
|
57 |
+
/**
|
58 |
+
* An element as being a cell in a tabular container that does not contain column or row header information.
|
59 |
+
*
|
60 |
+
* @var string
|
61 |
+
*/
|
62 |
+
const CELL = 'cell';
|
63 |
+
|
64 |
+
/**
|
65 |
+
* A control that has three possible values, (true, false, mixed).
|
66 |
+
*
|
67 |
+
* @var string
|
68 |
+
*/
|
69 |
+
const CHECKBOX = 'checkbox';
|
70 |
+
|
71 |
+
/**
|
72 |
+
* A table cell containing header information for a column.
|
73 |
+
*
|
74 |
+
* @var string
|
75 |
+
*/
|
76 |
+
const COLUMNHEADER = 'columnheader';
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Combobox is a presentation of a select, where users can type to locate a selected item.
|
80 |
+
*
|
81 |
+
* @var string
|
82 |
+
*/
|
83 |
+
const COMBOBOX = 'combobox';
|
84 |
+
|
85 |
+
/**
|
86 |
+
* A supporting section of the document, designed to be complementary to the main content at a similar level in the
|
87 |
+
* DOM hierarchy, but remains meaningful when separated from the main content.
|
88 |
+
*
|
89 |
+
* @var string
|
90 |
+
*/
|
91 |
+
const COMPLEMENTARY = 'complementary';
|
92 |
+
|
93 |
+
/**
|
94 |
+
* A large perceivable region that contains information about the parent document.
|
95 |
+
*
|
96 |
+
* @var string
|
97 |
+
*/
|
98 |
+
const CONTENTINFO = 'contentinfo';
|
99 |
+
|
100 |
+
/**
|
101 |
+
* A definition of a term or concept.
|
102 |
+
*
|
103 |
+
* @var string
|
104 |
+
*/
|
105 |
+
const DEFINITION = 'definition';
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Descriptive content for a page element which references this element via describedby.
|
109 |
+
*
|
110 |
+
* @var string
|
111 |
+
*/
|
112 |
+
const DESCRIPTION = 'description';
|
113 |
+
|
114 |
+
/**
|
115 |
+
* A dialog is a small application window that sits above the application and is designed to interrupt the current
|
116 |
+
* processing of an application in order to prompt the user to enter information or require a response.
|
117 |
+
*
|
118 |
+
* @var string
|
119 |
+
*/
|
120 |
+
const DIALOG = 'dialog';
|
121 |
+
|
122 |
+
/**
|
123 |
+
* A list of references to members of a single group.
|
124 |
+
*
|
125 |
+
* @var string
|
126 |
+
*/
|
127 |
+
const DIRECTORY = 'directory';
|
128 |
+
|
129 |
+
/**
|
130 |
+
* Content that contains related information, such as a book.
|
131 |
+
*
|
132 |
+
* @var string
|
133 |
+
*/
|
134 |
+
const DOCUMENT = 'document';
|
135 |
+
|
136 |
+
/**
|
137 |
+
* A scrollable list of articles where scrolling may cause articles to be added to or removed from either end of the
|
138 |
+
* list.
|
139 |
+
*
|
140 |
+
* @var string
|
141 |
+
*/
|
142 |
+
const FEED = 'feed';
|
143 |
+
|
144 |
+
/**
|
145 |
+
* A figure inside page content where appropriate semantics do not already exist.
|
146 |
+
*
|
147 |
+
* @var string
|
148 |
+
*/
|
149 |
+
const FIGURE = 'figure';
|
150 |
+
|
151 |
+
/**
|
152 |
+
* A landmark region that contains a collection of items and objects that, as a whole, combine to create a form.
|
153 |
+
*
|
154 |
+
* @var string
|
155 |
+
*/
|
156 |
+
const FORM = 'form';
|
157 |
+
|
158 |
+
/**
|
159 |
+
* A grid contains cells of tabular data arranged in rows and columns (e.g., a table).
|
160 |
+
*
|
161 |
+
* @var string
|
162 |
+
*/
|
163 |
+
const GRID = 'grid';
|
164 |
+
|
165 |
+
/**
|
166 |
+
* A gridcell is a table cell in a grid. Gridcells may be active, editable, and selectable. Cells may have
|
167 |
+
* relationships such as controls to address the application of functional relationships.
|
168 |
+
*
|
169 |
+
* @var string
|
170 |
+
*/
|
171 |
+
const GRIDCELL = 'gridcell';
|
172 |
+
|
173 |
+
/**
|
174 |
+
* A group is a section of user interface objects which would not be included in a page summary or table of contents
|
175 |
+
* by an assistive technology. See region for sections of user interface objects that should be included in a page
|
176 |
+
* summary or table of contents.
|
177 |
+
*
|
178 |
+
* @var string
|
179 |
+
*/
|
180 |
+
const GROUP = 'group';
|
181 |
+
|
182 |
+
/**
|
183 |
+
* A heading for a section of the page.
|
184 |
+
*
|
185 |
+
* @var string
|
186 |
+
*/
|
187 |
+
const HEADING = 'heading';
|
188 |
+
|
189 |
+
/**
|
190 |
+
* An img is a container for a collection elements that form an image.
|
191 |
+
*
|
192 |
+
* @var string
|
193 |
+
*/
|
194 |
+
const IMG = 'img';
|
195 |
+
|
196 |
+
/**
|
197 |
+
* Interactive reference to a resource (note, that in XHTML 2.0 any element can have an href attribute and thus be a
|
198 |
+
* link)
|
199 |
+
*
|
200 |
+
* @var string
|
201 |
+
*/
|
202 |
+
const LINK = 'link';
|
203 |
+
|
204 |
+
/**
|
205 |
+
* Group of non-interactive list items. Lists contain children whose role is listitem.
|
206 |
+
*
|
207 |
+
* Uses an underscore as "list" is a conflicting PHP keyword.
|
208 |
+
*
|
209 |
+
* @var string
|
210 |
+
*/
|
211 |
+
const LIST_ = 'list';
|
212 |
+
|
213 |
+
/**
|
214 |
+
* A list box is a widget that allows the user to select one or more items from a list. Items within the list are
|
215 |
+
* static and may contain images. List boxes contain children whose role is option.
|
216 |
+
*
|
217 |
+
* @var string
|
218 |
+
*/
|
219 |
+
const LISTBOX = 'listbox';
|
220 |
+
|
221 |
+
/**
|
222 |
+
* A single item in a list.
|
223 |
+
*
|
224 |
+
* @var string
|
225 |
+
*/
|
226 |
+
const LISTITEM = 'listitem';
|
227 |
+
|
228 |
+
/**
|
229 |
+
* A region where new information is added and old information may disappear such as chat logs, messaging, game log
|
230 |
+
* or an error log. In contrast to other regions, in this role there is a relationship between the arrival of new
|
231 |
+
* items in the log and the reading order. The log contains a meaningful sequence and new information is added only
|
232 |
+
* to the end of the log, not at arbitrary points.
|
233 |
+
*
|
234 |
+
* @var string
|
235 |
+
*/
|
236 |
+
const LOG = 'log';
|
237 |
+
|
238 |
+
/**
|
239 |
+
* The main content of a document.
|
240 |
+
*
|
241 |
+
* @var string
|
242 |
+
*/
|
243 |
+
const MAIN = 'main';
|
244 |
+
|
245 |
+
/**
|
246 |
+
* A marquee is used to scroll text across the page.
|
247 |
+
*
|
248 |
+
* @var string
|
249 |
+
*/
|
250 |
+
const MARQUEE = 'marquee';
|
251 |
+
|
252 |
+
/**
|
253 |
+
* Content that represents a mathematical expression.
|
254 |
+
*
|
255 |
+
* @var string
|
256 |
+
*/
|
257 |
+
const MATH = 'math';
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Offers a list of choices to the user.
|
261 |
+
*
|
262 |
+
* @var string
|
263 |
+
*/
|
264 |
+
const MENU = 'menu';
|
265 |
+
|
266 |
+
/**
|
267 |
+
* A menubar is a container of menu items. Each menu item may activate a new sub-menu. Navigation behavior should be
|
268 |
+
* similar to the typical menu bar graphical user interface.
|
269 |
+
*
|
270 |
+
* @var string
|
271 |
+
*/
|
272 |
+
const MENUBAR = 'menubar';
|
273 |
+
|
274 |
+
/**
|
275 |
+
* A link in a menu. This is an option in a group of choices contained in a menu.
|
276 |
+
*
|
277 |
+
* @var string
|
278 |
+
*/
|
279 |
+
const MENUITEM = 'menuitem';
|
280 |
+
|
281 |
+
/**
|
282 |
+
* Defines a menuitem which is checkable (tri-state).
|
283 |
+
*
|
284 |
+
* @var string
|
285 |
+
*/
|
286 |
+
const MENUITEMCHECKBOX = 'menuitemcheckbox';
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Indicates a menu item which is part of a group of menuitemradio roles.
|
290 |
+
*
|
291 |
+
* @var string
|
292 |
+
*/
|
293 |
+
const MENUITEMRADIO = 'menuitemradio';
|
294 |
+
|
295 |
+
/**
|
296 |
+
* A collection of navigational elements (usually links) for navigating the document or related documents.
|
297 |
+
*
|
298 |
+
* @var string
|
299 |
+
*/
|
300 |
+
const NAVIGATION = 'navigation';
|
301 |
+
|
302 |
+
/**
|
303 |
+
* An element whose implicit native role semantics will not be mapped to the accessibility API.
|
304 |
+
*
|
305 |
+
* @var string
|
306 |
+
*/
|
307 |
+
const NONE = 'none';
|
308 |
+
|
309 |
+
/**
|
310 |
+
* A section whose content is parenthetic or ancillary to the main content of the resource.
|
311 |
+
*
|
312 |
+
* @var string
|
313 |
+
*/
|
314 |
+
const NOTE = 'note';
|
315 |
+
|
316 |
+
/**
|
317 |
+
* A selectable item in a list represented by a select.
|
318 |
+
*
|
319 |
+
* @var string
|
320 |
+
*/
|
321 |
+
const OPTION = 'option';
|
322 |
+
|
323 |
+
/**
|
324 |
+
* An element whose role is presentational does not need to be mapped to the accessibility API.
|
325 |
+
*
|
326 |
+
* @var string
|
327 |
+
*/
|
328 |
+
const PRESENTATION = 'presentation';
|
329 |
+
|
330 |
+
/**
|
331 |
+
* Used by applications for tasks that take a long time to execute, to show the execution progress.
|
332 |
+
*
|
333 |
+
* @var string
|
334 |
+
*/
|
335 |
+
const PROGRESSBAR = 'progressbar';
|
336 |
+
|
337 |
+
/**
|
338 |
+
* A radio is an option in single-select list. Only one radio control in a radiogroup can be selected at the same
|
339 |
+
* time.
|
340 |
+
*
|
341 |
+
* @var string
|
342 |
+
*/
|
343 |
+
const RADIO = 'radio';
|
344 |
+
|
345 |
+
/**
|
346 |
+
* A group of radio controls.
|
347 |
+
*
|
348 |
+
* @var string
|
349 |
+
*/
|
350 |
+
const RADIOGROUP = 'radiogroup';
|
351 |
+
|
352 |
+
/**
|
353 |
+
* Region is a large perceivable section on the web page.
|
354 |
+
*
|
355 |
+
* @var string
|
356 |
+
*/
|
357 |
+
const REGION = 'region';
|
358 |
+
|
359 |
+
/**
|
360 |
+
* A row of table cells.
|
361 |
+
*
|
362 |
+
* @var string
|
363 |
+
*/
|
364 |
+
const ROW = 'row';
|
365 |
+
|
366 |
+
/**
|
367 |
+
* A structure containing one or more row elements in a tabular container.
|
368 |
+
*
|
369 |
+
* @var string
|
370 |
+
*/
|
371 |
+
const ROWGROUP = 'rowgroup';
|
372 |
+
|
373 |
+
/**
|
374 |
+
* A table cell containing header information for a row.
|
375 |
+
*
|
376 |
+
* @var string
|
377 |
+
*/
|
378 |
+
const ROWHEADER = 'rowheader';
|
379 |
+
|
380 |
+
/**
|
381 |
+
* Scroll bar to navigate the horizontal or vertical dimensions of the page.
|
382 |
+
*
|
383 |
+
* @var string
|
384 |
+
*/
|
385 |
+
const SCROLLBAR = 'scrollbar';
|
386 |
+
|
387 |
+
/**
|
388 |
+
* A section of the page used to search the page, site, or collection of sites.
|
389 |
+
*
|
390 |
+
* @var string
|
391 |
+
*/
|
392 |
+
const SEARCH = 'search';
|
393 |
+
|
394 |
+
/**
|
395 |
+
* An entry field to provide a query to search for.
|
396 |
+
*
|
397 |
+
* @var string
|
398 |
+
*/
|
399 |
+
const SEARCHBOX = 'searchbox';
|
400 |
+
|
401 |
+
/**
|
402 |
+
* A line or bar that separates and distinguishes sections of content.
|
403 |
+
*
|
404 |
+
* @var string
|
405 |
+
*/
|
406 |
+
const SEPARATOR = 'separator';
|
407 |
+
|
408 |
+
/**
|
409 |
+
* A user input where the user selects an input in a given range. This form of range expects an analog keyboard
|
410 |
+
* interface.
|
411 |
+
*
|
412 |
+
* @var string
|
413 |
+
*/
|
414 |
+
const SLIDER = 'slider';
|
415 |
+
|
416 |
+
/**
|
417 |
+
* A form of Range that expects a user selecting from discrete choices.
|
418 |
+
*
|
419 |
+
* @var string
|
420 |
+
*/
|
421 |
+
const SPINBUTTON = 'spinbutton';
|
422 |
+
|
423 |
+
/**
|
424 |
+
* This is a container for process advisory information to give feedback to the user.
|
425 |
+
*
|
426 |
+
* @var string
|
427 |
+
*/
|
428 |
+
const STATUS = 'status';
|
429 |
+
|
430 |
+
/**
|
431 |
+
* Functionally identical to a checkbox but represents the states "on"/"off" instead of "checked"/"unchecked".
|
432 |
+
*
|
433 |
+
* Uses an underscore as "list" is a conflicting PHP keyword.
|
434 |
+
*
|
435 |
+
* @var string
|
436 |
+
*/
|
437 |
+
const SWITCH_ = 'switch';
|
438 |
+
|
439 |
+
/**
|
440 |
+
* A header for a tabpanel.
|
441 |
+
*
|
442 |
+
* @var string
|
443 |
+
*/
|
444 |
+
const TAB = 'tab';
|
445 |
+
|
446 |
+
/**
|
447 |
+
* A non-interactive table structure containing data arranged in rows and columns.
|
448 |
+
*
|
449 |
+
* @var string
|
450 |
+
*/
|
451 |
+
const TABLE = 'table';
|
452 |
+
|
453 |
+
/**
|
454 |
+
* A list of tabs, which are references to tabpanels.
|
455 |
+
*
|
456 |
+
* @var string
|
457 |
+
*/
|
458 |
+
const TABLIST = 'tablist';
|
459 |
+
|
460 |
+
/**
|
461 |
+
* Tabpanel is a container for the resources associated with a tab.
|
462 |
+
*
|
463 |
+
* @var string
|
464 |
+
*/
|
465 |
+
const TABPANEL = 'tabpanel';
|
466 |
+
|
467 |
+
/**
|
468 |
+
* A word or phrase with a corresponding definition.
|
469 |
+
*
|
470 |
+
* @var string
|
471 |
+
*/
|
472 |
+
const TERM = 'term';
|
473 |
+
|
474 |
+
/**
|
475 |
+
* Inputs that allow free-form text as their value.
|
476 |
+
*
|
477 |
+
* @var string
|
478 |
+
*/
|
479 |
+
const TEXTBOX = 'textbox';
|
480 |
+
|
481 |
+
/**
|
482 |
+
* A numerical counter which indicates an amount of elapsed time from a start point, or the time remaining until an
|
483 |
+
* end point.
|
484 |
+
*
|
485 |
+
* @var string
|
486 |
+
*/
|
487 |
+
const TIMER = 'timer';
|
488 |
+
|
489 |
+
/**
|
490 |
+
* A toolbar is a collection of commonly used functions represented in compact visual form.
|
491 |
+
*
|
492 |
+
* @var string
|
493 |
+
*/
|
494 |
+
const TOOLBAR = 'toolbar';
|
495 |
+
|
496 |
+
/**
|
497 |
+
* A popup that displays a description for an element when a user passes over or rests on that element. Supplement
|
498 |
+
* to the normal tooltip processing of the user agent.
|
499 |
+
*
|
500 |
+
* @var string
|
501 |
+
*/
|
502 |
+
const TOOLTIP = 'tooltip';
|
503 |
+
|
504 |
+
/**
|
505 |
+
* A form of a list having groups inside groups, where sub trees can be collapsed and expanded.
|
506 |
+
*
|
507 |
+
* @var string
|
508 |
+
*/
|
509 |
+
const TREE = 'tree';
|
510 |
+
|
511 |
+
/**
|
512 |
+
* A grid whose rows can be expanded and collapsed in the same manner as for a tree.
|
513 |
+
*
|
514 |
+
* @var string
|
515 |
+
*/
|
516 |
+
const TREEGRID = 'treegrid';
|
517 |
+
|
518 |
+
/**
|
519 |
+
* An option item of a tree. This is an element within a tree that may be expanded or collapsed.
|
520 |
+
*
|
521 |
+
* @var string
|
522 |
+
*/
|
523 |
+
const TREEITEM = 'treeitem';
|
524 |
+
}
|
includes/vendor/tool/RuntimeVersion.php
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Queries https://cdn.ampproject.org/rtv/metadata for the latest AMP runtime version. Uses a stale-while-revalidate
|
7 |
+
* caching strategy to avoid refreshing the version.
|
8 |
+
*
|
9 |
+
* More details: https://cdn.ampproject.org/rtv/metadata returns the following metadata:
|
10 |
+
*
|
11 |
+
* <pre>
|
12 |
+
* {
|
13 |
+
* "ampRuntimeVersion": "CURRENT_PROD",
|
14 |
+
* "ampCssUrl": "https://cdn.ampproject.org/rtv/CURRENT_PROD/v0.css",
|
15 |
+
* "canaryPercentage": "0.1",
|
16 |
+
* "diversions": [
|
17 |
+
* "CURRENT_OPTIN",
|
18 |
+
* "CURRENT_1%",
|
19 |
+
* "CURRENT_CONTROL"
|
20 |
+
* ]
|
21 |
+
* }
|
22 |
+
* </pre>
|
23 |
+
*
|
24 |
+
* where:
|
25 |
+
*
|
26 |
+
* <ul>
|
27 |
+
* <li> CURRENT_OPTIN: is when you go to https://cdn.ampproject.org/experiments.html and toggle "dev-channel". It's
|
28 |
+
* the earliest possible time to get new code.</li>
|
29 |
+
* <li> CURRENT_1%: 1% is the same code as opt-in that we're now comfortable releasing to 1% of the population.</li>
|
30 |
+
* <li> CURRENT_CONTROL is the same thing as production, but with a different URL. This is to compare experiments
|
31 |
+
* against, since prod's immutable caching would affect metrics.</li>
|
32 |
+
* </ul>
|
33 |
+
*
|
34 |
+
* @package ampproject/amp-toolbox
|
35 |
+
*/
|
36 |
+
final class RuntimeVersion
|
37 |
+
{
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Option to retrieve the latest canary version data instead of the production version data.
|
41 |
+
*
|
42 |
+
* @var string
|
43 |
+
*/
|
44 |
+
const OPTION_CANARY = 'canary';
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Endpoint to query for retrieving the runtime version data.
|
48 |
+
*/
|
49 |
+
const RUNTIME_METADATA_ENDPOINT = 'https://cdn.ampproject.org/rtv/metadata';
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Transport to use for remote requests.
|
53 |
+
*
|
54 |
+
* @var RemoteGetRequest
|
55 |
+
*/
|
56 |
+
private $remoteRequest;
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Instantiate a RuntimeVersion object.
|
60 |
+
*
|
61 |
+
* @param RemoteGetRequest $remoteRequest Transport to use for remote requests.
|
62 |
+
*/
|
63 |
+
public function __construct(RemoteGetRequest $remoteRequest)
|
64 |
+
{
|
65 |
+
$this->remoteRequest = $remoteRequest;
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Returns the version of the current AMP runtime release.
|
70 |
+
*
|
71 |
+
* Pass [ canary => true ] to get the latest canary version.
|
72 |
+
*
|
73 |
+
* @param array $options Optional. Associative array of options.
|
74 |
+
* @return string Version string of the AMP runtime.
|
75 |
+
*/
|
76 |
+
public function currentVersion($options = [])
|
77 |
+
{
|
78 |
+
$response = $this->remoteRequest->get(self::RUNTIME_METADATA_ENDPOINT);
|
79 |
+
$statusCode = $response->getStatusCode();
|
80 |
+
|
81 |
+
if ($statusCode < 200 || $statusCode >= 300) {
|
82 |
+
return '0';
|
83 |
+
}
|
84 |
+
|
85 |
+
$metadata = json_decode($response->getBody());
|
86 |
+
|
87 |
+
$version = (! empty($options['canary']))
|
88 |
+
? $metadata->diversions[0]
|
89 |
+
: $metadata->ampRuntimeVersion;
|
90 |
+
|
91 |
+
return $this->padVersionString($version);
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Pad the version string to the required length.
|
96 |
+
*
|
97 |
+
* @param string $version Version string to pad.
|
98 |
+
* @return string Padded version string.
|
99 |
+
*/
|
100 |
+
private function padVersionString($version)
|
101 |
+
{
|
102 |
+
return str_pad($version, 15, '0');
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Append the runtime version to the host URL.
|
107 |
+
*
|
108 |
+
* @param string $host Host domain to use.
|
109 |
+
* @param string $version Version to use.
|
110 |
+
* @return string Runtime version URL.
|
111 |
+
*/
|
112 |
+
public static function appendRuntimeVersion($host, $version)
|
113 |
+
{
|
114 |
+
return "{$host}/rtv/{$version}";
|
115 |
+
}
|
116 |
+
}
|
includes/vendor/tool/Tag.php
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Interface with constants for the different types of tags.
|
7 |
+
*
|
8 |
+
* @package ampproject/amp-toolbox
|
9 |
+
*/
|
10 |
+
interface Tag
|
11 |
+
{
|
12 |
+
|
13 |
+
const A = 'a';
|
14 |
+
const AREA = 'area';
|
15 |
+
const BASE = 'base';
|
16 |
+
const BASEFONT = 'basefont';
|
17 |
+
const BGSOUND = 'bgsound';
|
18 |
+
const BODY = 'body';
|
19 |
+
const BR = 'br';
|
20 |
+
const COL = 'col';
|
21 |
+
const DIV = 'div';
|
22 |
+
const EMBED = 'embed';
|
23 |
+
const FIGCAPTION = 'figcaption';
|
24 |
+
const FIGURE = 'figure';
|
25 |
+
const FORM = 'form';
|
26 |
+
const FRAME = 'frame';
|
27 |
+
const HEAD = 'head';
|
28 |
+
const HR = 'hr';
|
29 |
+
const HTML = 'html';
|
30 |
+
const IMG = 'img';
|
31 |
+
const INPUT = 'input';
|
32 |
+
const KEYGEN = 'keygen';
|
33 |
+
const LINK = 'link';
|
34 |
+
const META = 'meta';
|
35 |
+
const NOSCRIPT = 'noscript';
|
36 |
+
const OBJECT = 'object';
|
37 |
+
const P = 'p';
|
38 |
+
const PARAM = 'param';
|
39 |
+
const SCRIPT = 'script';
|
40 |
+
const SOURCE = 'source';
|
41 |
+
const STYLE = 'style';
|
42 |
+
const TEMPLATE = 'template';
|
43 |
+
const TITLE = 'title';
|
44 |
+
const TRACK = 'track';
|
45 |
+
const WBR = 'wbr';
|
46 |
+
|
47 |
+
/**
|
48 |
+
* HTML elements that are self-closing.
|
49 |
+
*
|
50 |
+
* @link https://www.w3.org/TR/html5/syntax.html#serializing-html-fragments
|
51 |
+
*
|
52 |
+
* @var string[]
|
53 |
+
*/
|
54 |
+
const SELF_CLOSING_TAGS = [
|
55 |
+
self::AREA,
|
56 |
+
self::BASE,
|
57 |
+
self::BASEFONT,
|
58 |
+
self::BGSOUND,
|
59 |
+
self::BR,
|
60 |
+
self::COL,
|
61 |
+
self::EMBED,
|
62 |
+
self::FRAME,
|
63 |
+
self::HR,
|
64 |
+
self::IMG,
|
65 |
+
self::INPUT,
|
66 |
+
self::KEYGEN,
|
67 |
+
self::LINK,
|
68 |
+
self::META,
|
69 |
+
self::PARAM,
|
70 |
+
self::SOURCE,
|
71 |
+
self::TRACK,
|
72 |
+
self::WBR,
|
73 |
+
];
|
74 |
+
|
75 |
+
/**
|
76 |
+
* List of elements allowed in head.
|
77 |
+
*
|
78 |
+
* @link https://github.com/ampproject/amphtml/blob/445d6e3be8a5063e2738c6f90fdcd57f2b6208be/validator/engine/htmlparser.js#L83-L100
|
79 |
+
* @link https://www.w3.org/TR/html5/document-metadata.html
|
80 |
+
*
|
81 |
+
* @var string[]
|
82 |
+
*/
|
83 |
+
const ELEMENTS_ALLOWED_IN_HEAD = [
|
84 |
+
self::TITLE,
|
85 |
+
self::BASE,
|
86 |
+
self::LINK,
|
87 |
+
self::META,
|
88 |
+
self::STYLE,
|
89 |
+
self::NOSCRIPT,
|
90 |
+
self::SCRIPT,
|
91 |
+
];
|
92 |
+
}
|
includes/vendor/tool/Url.php
ADDED
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace AmpProject;
|
4 |
+
|
5 |
+
use AmpProject\Exception\FailedToParseUrl;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Helper class to work with URLs.
|
9 |
+
*
|
10 |
+
* @property-read string|null $scheme
|
11 |
+
* @property-read string|null $host
|
12 |
+
* @property-read string|null $port
|
13 |
+
* @property-read string|null $user
|
14 |
+
* @property-read string|null $pass
|
15 |
+
* @property-read string|null $path
|
16 |
+
* @property-read string|null $query
|
17 |
+
* @property-read string|null $fragment
|
18 |
+
*
|
19 |
+
* @package ampproject/amp-toolbox
|
20 |
+
*/
|
21 |
+
final class Url
|
22 |
+
{
|
23 |
+
|
24 |
+
const SCHEME = 'scheme';
|
25 |
+
const HOST = 'host';
|
26 |
+
const PORT = 'port';
|
27 |
+
const USER = 'user';
|
28 |
+
const PASS = 'pass';
|
29 |
+
const PATH = 'path';
|
30 |
+
const QUERY = 'query';
|
31 |
+
const FRAGMENT = 'fragment';
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Regular expression pattern used to collapse the current path ('.').
|
35 |
+
*
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
const COLLAPSE_CURRENT_PATHS_REGEX_PATTERN = '#/\./#';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Regular expression pattern used to collapse a relative path ('..').
|
42 |
+
*
|
43 |
+
* @var string
|
44 |
+
*/
|
45 |
+
const COLLAPSE_RELATIVE_PATHS_REGEX_PATTERN = '#(?<=/)(?!\.\./)[^/]+/\.\./#';
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Error message to use when the __get() is triggered for an unknown property.
|
49 |
+
*
|
50 |
+
* @var string
|
51 |
+
*/
|
52 |
+
const PROPERTY_GETTER_ERROR_MESSAGE = 'Undefined property: AmpProject\\Url::';
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Scheme.
|
56 |
+
*
|
57 |
+
* @var string|null
|
58 |
+
*/
|
59 |
+
private $scheme;
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Host.
|
63 |
+
*
|
64 |
+
* @var string|null
|
65 |
+
*/
|
66 |
+
private $host;
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Port.
|
70 |
+
*
|
71 |
+
* @var string|null
|
72 |
+
*/
|
73 |
+
private $port;
|
74 |
+
|
75 |
+
/**
|
76 |
+
* User.
|
77 |
+
*
|
78 |
+
* @var string|null
|
79 |
+
*/
|
80 |
+
private $user;
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Password.
|
84 |
+
*
|
85 |
+
* @var string|null
|
86 |
+
*/
|
87 |
+
private $pass;
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Query string.
|
91 |
+
*
|
92 |
+
* @var string|null
|
93 |
+
*/
|
94 |
+
private $query;
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Path.
|
98 |
+
*
|
99 |
+
* @var string|null
|
100 |
+
*/
|
101 |
+
private $path;
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Fragment.
|
105 |
+
*
|
106 |
+
* @var string|null
|
107 |
+
*/
|
108 |
+
private $fragment;
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Default URL parts to use when constructing an absolute URL out of a relative one.
|
112 |
+
*
|
113 |
+
* @var string[]
|
114 |
+
*/
|
115 |
+
const URL_DEFAULT_PARTS = [
|
116 |
+
self::SCHEME => 'https',
|
117 |
+
self::HOST => 'example.com',
|
118 |
+
self::PORT => null,
|
119 |
+
self::USER => null,
|
120 |
+
self::PASS => null,
|
121 |
+
self::PATH => null,
|
122 |
+
self::QUERY => null,
|
123 |
+
self::FRAGMENT => null,
|
124 |
+
];
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Url constructor.
|
128 |
+
*
|
129 |
+
* @param string|null $url URL.
|
130 |
+
* @param Url|null $baseUrl Base URL.
|
131 |
+
* @throws FailedToParseUrl Exception when the URL or Base URL is malformed.
|
132 |
+
*/
|
133 |
+
public function __construct($url = null, Url $baseUrl = null)
|
134 |
+
{
|
135 |
+
$parsedUrl = [];
|
136 |
+
|
137 |
+
if ($url !== null) {
|
138 |
+
$parsedUrl = parse_url($url);
|
139 |
+
if (false === $parsedUrl) {
|
140 |
+
throw FailedToParseUrl::forUrl($url);
|
141 |
+
}
|
142 |
+
}
|
143 |
+
|
144 |
+
if ($baseUrl) {
|
145 |
+
if (isset($parsedUrl[self::PATH]) && 0 !== strpos($parsedUrl[self::PATH], '/')) {
|
146 |
+
$root = rtrim($baseUrl->path, '/');
|
147 |
+
$parsedUrl[self::PATH] = $this->unrelativizePath("{$root}/{$parsedUrl[self::PATH]}");
|
148 |
+
}
|
149 |
+
$parsedUrl = array_merge($baseUrl->toArray(), $parsedUrl);
|
150 |
+
}
|
151 |
+
|
152 |
+
if ($parsedUrl) {
|
153 |
+
foreach ($parsedUrl as $key => $value) {
|
154 |
+
if (property_exists($this, $key)) {
|
155 |
+
$this->$key = $value;
|
156 |
+
}
|
157 |
+
}
|
158 |
+
}
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Eliminate relative segments (../ and ./) from a path.
|
163 |
+
*
|
164 |
+
* @param string $path Path with relative segments. This is not a URL, so no host and no query string.
|
165 |
+
* @return string Unrelativized path.
|
166 |
+
*/
|
167 |
+
private function unrelativizePath($path)
|
168 |
+
{
|
169 |
+
|
170 |
+
// Eliminate current directory relative paths, like <foo/./bar/./baz.css> => <foo/bar/baz.css>.
|
171 |
+
do {
|
172 |
+
$path = preg_replace(
|
173 |
+
self::COLLAPSE_CURRENT_PATHS_REGEX_PATTERN,
|
174 |
+
'/',
|
175 |
+
$path,
|
176 |
+
-1,
|
177 |
+
$count
|
178 |
+
);
|
179 |
+
} while (0 !== $count);
|
180 |
+
|
181 |
+
// Collapse relative paths, like <foo/bar/../../baz.css> => <baz.css>.
|
182 |
+
do {
|
183 |
+
$path = preg_replace(
|
184 |
+
self::COLLAPSE_RELATIVE_PATHS_REGEX_PATTERN,
|
185 |
+
'',
|
186 |
+
$path,
|
187 |
+
1,
|
188 |
+
$count
|
189 |
+
);
|
190 |
+
} while (0 !== $count);
|
191 |
+
|
192 |
+
return $path;
|
193 |
+
}
|
194 |
+
|
195 |
+
/**
|
196 |
+
* Check whether the URL is a valid image source URL.
|
197 |
+
*
|
198 |
+
* @return bool Whether the src string is a valid image source URL.
|
199 |
+
*/
|
200 |
+
public function isValidNonDataUrl()
|
201 |
+
{
|
202 |
+
// Bail early on 'data:' assets.
|
203 |
+
if ($this->scheme === 'data') {
|
204 |
+
return false;
|
205 |
+
}
|
206 |
+
|
207 |
+
// @TODO: This probably needs additional logic.
|
208 |
+
|
209 |
+
$parts = array_merge(self::URL_DEFAULT_PARTS, $this->toArray(true));
|
210 |
+
|
211 |
+
return (bool)filter_var(self::toString($parts), FILTER_VALIDATE_URL);
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Get the URL parts as an associative array.
|
216 |
+
*
|
217 |
+
* @param bool $sparse Whether to only include parts with non-empty values.
|
218 |
+
*
|
219 |
+
* @return array Associative array with URL parts.
|
220 |
+
*/
|
221 |
+
public function toArray($sparse = false)
|
222 |
+
{
|
223 |
+
$array = [
|
224 |
+
self::SCHEME => $this->scheme,
|
225 |
+
self::HOST => $this->host,
|
226 |
+
self::PORT => $this->port,
|
227 |
+
self::USER => $this->user,
|
228 |
+
self::PASS => $this->pass,
|
229 |
+
self::PATH => $this->path,
|
230 |
+
self::QUERY => $this->query,
|
231 |
+
self::FRAGMENT => $this->fragment,
|
232 |
+
];
|
233 |
+
|
234 |
+
return $sparse ? array_filter($array) : $array;
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Serialize to string.
|
239 |
+
*
|
240 |
+
* @return string URL.
|
241 |
+
*/
|
242 |
+
public function __toString()
|
243 |
+
{
|
244 |
+
return self::toString($this->toArray());
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Return a provided URL parsed into parts as an assembled string.
|
249 |
+
*
|
250 |
+
* @param array $parts Parts of the URL to assemble.
|
251 |
+
* @return string Assembled URL string.
|
252 |
+
*/
|
253 |
+
public static function toString($parts)
|
254 |
+
{
|
255 |
+
$url = ! empty($parts[self::SCHEME]) ? "{$parts[self::SCHEME]}://" : '//';
|
256 |
+
|
257 |
+
if (! empty($parts[self::USER])) {
|
258 |
+
$url .= "{$parts[self::USER]}";
|
259 |
+
if (! empty($parts[self::PASS])) {
|
260 |
+
$url .= ":{$parts[self::PASS]}";
|
261 |
+
}
|
262 |
+
$url .= '@';
|
263 |
+
}
|
264 |
+
|
265 |
+
$url .= ! empty($parts[self::HOST]) ? "{$parts[self::HOST]}" : 'localhost';
|
266 |
+
|
267 |
+
if (! empty($parts[self::PORT])) {
|
268 |
+
$url .= ":{$parts[self::PORT]}";
|
269 |
+
}
|
270 |
+
|
271 |
+
$url .= ! empty($parts[self::PATH]) ? "{$parts[self::PATH]}" : '/';
|
272 |
+
|
273 |
+
if (! empty($parts[self::QUERY])) {
|
274 |
+
$url .= "?{$parts[self::QUERY]}";
|
275 |
+
}
|
276 |
+
|
277 |
+
if (! empty($parts[self::FRAGMENT])) {
|
278 |
+
$url .= "#{$parts[self::FRAGMENT]}";
|
279 |
+
}
|
280 |
+
|
281 |
+
return $url;
|
282 |
+
}
|
283 |
+
|
284 |
+
/**
|
285 |
+
* Magic getter to return the individual parts.
|
286 |
+
*
|
287 |
+
* @param string $name Name of the part to return.
|
288 |
+
* @return string|null Part string or null if it was not found during parsing.
|
289 |
+
*/
|
290 |
+
public function __get($name)
|
291 |
+
{
|
292 |
+
if (array_key_exists($name, self::URL_DEFAULT_PARTS)) {
|
293 |
+
return $this->$name;
|
294 |
+
}
|
295 |
+
|
296 |
+
// Mimic regular PHP behavior for missing notices.
|
297 |
+
trigger_error(self::PROPERTY_GETTER_ERROR_MESSAGE . $name, E_USER_NOTICE);
|
298 |
+
return null;
|
299 |
+
}
|
300 |
+
}
|
readme.txt
CHANGED
@@ -4,7 +4,7 @@ Tags: AMP, accelerated mobile pages, mobile, amp project, google amp, amp wp, go
|
|
4 |
Donate link: https://www.paypal.me/Kaludi/25
|
5 |
Requires at least: 3.0
|
6 |
Tested up to: 5.7
|
7 |
-
Stable tag: 1.0.77.
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
|
@@ -190,6 +190,9 @@ You can contact us from [here](https://ampforwp.com/contact/)
|
|
190 |
|
191 |
== Changelog ==
|
192 |
|
|
|
|
|
|
|
193 |
= 1.0.77.6 (12th June 2021) =
|
194 |
* Fixed: Custom JavaScript is not allowed error when analytics added by All in one SEO #5062
|
195 |
|
4 |
Donate link: https://www.paypal.me/Kaludi/25
|
5 |
Requires at least: 3.0
|
6 |
Tested up to: 5.7
|
7 |
+
Stable tag: 1.0.77.7
|
8 |
License: GPLv2 or later
|
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
|
190 |
|
191 |
== Changelog ==
|
192 |
|
193 |
+
= 1.0.77.7 (21st June 2021) =
|
194 |
+
* Added: AMP Optimizer (SSR) Support #5034
|
195 |
+
|
196 |
= 1.0.77.6 (12th June 2021) =
|
197 |
* Fixed: Custom JavaScript is not allowed error when analytics added by All in one SEO #5062
|
198 |
|
templates/design-manager/design-1/404.php
CHANGED
@@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
7 |
<html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
|
8 |
<head>
|
9 |
<meta charset="utf-8">
|
10 |
-
<link rel="
|
11 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
12 |
<style amp-custom>
|
13 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
7 |
<html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
|
8 |
<head>
|
9 |
<meta charset="utf-8">
|
10 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
11 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
12 |
<style amp-custom>
|
13 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-1/archive.php
CHANGED
@@ -8,7 +8,7 @@ global $redux_builder_amp, $wp; ?>
|
|
8 |
<head>
|
9 |
<meta charset="utf-8">
|
10 |
<?php do_action('amp_experiment_meta', $this); ?>
|
11 |
-
<link rel="
|
12 |
<?php
|
13 |
if ( is_archive() ) {
|
14 |
$description = get_the_archive_description();
|
8 |
<head>
|
9 |
<meta charset="utf-8">
|
10 |
<?php do_action('amp_experiment_meta', $this); ?>
|
11 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
12 |
<?php
|
13 |
if ( is_archive() ) {
|
14 |
$description = get_the_archive_description();
|
templates/design-manager/design-1/index.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-1/search.php
CHANGED
@@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
10 |
<meta charset="utf-8">
|
11 |
<?php if(is_search() && false == ampforwp_get_setting('amp-inspection-tool') && false == ampforwp_get_setting('ampforwp-robots-search-pages')){?>
|
12 |
<meta name="robots" content="noindex,nofollow"/><?php } ?>
|
13 |
-
<link rel="
|
14 |
<?php $paged = get_query_var( 'paged' );
|
15 |
$current_search_url =trailingslashit(get_home_url())."?s=".get_search_query();
|
16 |
$amp_url = untrailingslashit($current_search_url);
|
10 |
<meta charset="utf-8">
|
11 |
<?php if(is_search() && false == ampforwp_get_setting('amp-inspection-tool') && false == ampforwp_get_setting('ampforwp-robots-search-pages')){?>
|
12 |
<meta name="robots" content="noindex,nofollow"/><?php } ?>
|
13 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
14 |
<?php $paged = get_query_var( 'paged' );
|
15 |
$current_search_url =trailingslashit(get_home_url())."?s=".get_search_query();
|
16 |
$amp_url = untrailingslashit($current_search_url);
|
templates/design-manager/design-1/single.php
CHANGED
@@ -8,7 +8,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
8 |
<head>
|
9 |
<meta charset="utf-8">
|
10 |
<?php do_action('amp_experiment_meta', $this); ?>
|
11 |
-
<link rel="
|
12 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
13 |
<style amp-custom>
|
14 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
8 |
<head>
|
9 |
<meta charset="utf-8">
|
10 |
<?php do_action('amp_experiment_meta', $this); ?>
|
11 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
12 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
13 |
<style amp-custom>
|
14 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-2/404.php
CHANGED
@@ -8,7 +8,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
8 |
<html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
-
<link rel="
|
12 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
13 |
<style amp-custom>
|
14 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
8 |
<html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
12 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
13 |
<style amp-custom>
|
14 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-2/archive.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php
|
14 |
if ( is_archive() ) {
|
15 |
$description = get_the_archive_description();
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php
|
14 |
if ( is_archive() ) {
|
15 |
$description = get_the_archive_description();
|
templates/design-manager/design-2/index.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-2/search.php
CHANGED
@@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
10 |
<meta charset="utf-8">
|
11 |
<?php if(is_search() && false == ampforwp_get_setting('amp-inspection-tool') && false == ampforwp_get_setting('ampforwp-robots-search-pages')){?>
|
12 |
<meta name="robots" content="noindex,nofollow"/><?php } ?>
|
13 |
-
<link rel="
|
14 |
<?php $paged = get_query_var( 'paged' );
|
15 |
$current_search_url =trailingslashit(get_home_url())."?s=".get_search_query();
|
16 |
$amp_url = untrailingslashit($current_search_url);
|
10 |
<meta charset="utf-8">
|
11 |
<?php if(is_search() && false == ampforwp_get_setting('amp-inspection-tool') && false == ampforwp_get_setting('ampforwp-robots-search-pages')){?>
|
12 |
<meta name="robots" content="noindex,nofollow"/><?php } ?>
|
13 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
14 |
<?php $paged = get_query_var( 'paged' );
|
15 |
$current_search_url =trailingslashit(get_home_url())."?s=".get_search_query();
|
16 |
$amp_url = untrailingslashit($current_search_url);
|
templates/design-manager/design-2/single.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-3/404.php
CHANGED
@@ -8,7 +8,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
8 |
<html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
-
<link rel="
|
12 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
13 |
<style amp-custom>
|
14 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
8 |
<html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
12 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
13 |
<style amp-custom>
|
14 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/design-3/archive.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php
|
14 |
if ( is_archive() ) {
|
15 |
$description = get_the_archive_description();
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php
|
14 |
if ( is_archive() ) {
|
15 |
$description = get_the_archive_description();
|
templates/design-manager/design-3/index.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
|
15 |
<style amp-custom>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
|
15 |
<style amp-custom>
|
templates/design-manager/design-3/search.php
CHANGED
@@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
10 |
<meta charset="utf-8">
|
11 |
<?php if(is_search() && false == ampforwp_get_setting('amp-inspection-tool') && false == ampforwp_get_setting('ampforwp-robots-search-pages')){?>
|
12 |
<meta name="robots" content="noindex,nofollow"/><?php } ?>
|
13 |
-
<link rel="
|
14 |
<?php $paged = get_query_var( 'paged' );
|
15 |
$current_search_url =trailingslashit(get_home_url())."?s=".get_search_query();
|
16 |
$amp_url = untrailingslashit($current_search_url);
|
10 |
<meta charset="utf-8">
|
11 |
<?php if(is_search() && false == ampforwp_get_setting('amp-inspection-tool') && false == ampforwp_get_setting('ampforwp-robots-search-pages')){?>
|
12 |
<meta name="robots" content="noindex,nofollow"/><?php } ?>
|
13 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
14 |
<?php $paged = get_query_var( 'paged' );
|
15 |
$current_search_url =trailingslashit(get_home_url())."?s=".get_search_query();
|
16 |
$amp_url = untrailingslashit($current_search_url);
|
templates/design-manager/design-3/single.php
CHANGED
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
-
<link rel="
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
9 |
<head>
|
10 |
<meta charset="utf-8">
|
11 |
<?php do_action('amp_experiment_meta', $this); ?>
|
12 |
+
<link rel="preconnect" href="//cdn.ampproject.org">
|
13 |
<?php do_action( 'amp_post_template_head', $this ); ?>
|
14 |
<style amp-custom>
|
15 |
<?php $this->load_parts( array( 'style' ) ); ?>
|
templates/design-manager/swift/style.php
CHANGED
@@ -34,11 +34,11 @@ if ( empty($ampforwp_font_icon) ) {
|
|
34 |
?>
|
35 |
<?php if(1==ampforwp_get_setting('ampforwp-google-font-switch') && ( !isset($redux_builder_amp['amp_font_selector']) || $redux_builder_amp['amp_font_selector'] == 1 || empty($redux_builder_amp['amp_font_selector']) ) ) {
|
36 |
if(!ampforwp_levelup_compatibility('levelup_theme_and_elementor')){ // Level up Condition starts ?>
|
37 |
-
@font-face {font-family: 'Poppins';font-display:
|
38 |
-
@font-face {font-family: 'Poppins';font-display:
|
39 |
-
@font-face {font-family: 'Poppins';font-display:
|
40 |
-
@font-face {font-family: 'Poppins';font-display:
|
41 |
-
@font-face {font-family: 'Poppins';font-display:
|
42 |
<?php } // Level up Condition ends
|
43 |
} ?>
|
44 |
<?php
|
@@ -102,7 +102,7 @@ if( class_exists('\Elementor\Plugin') && \Elementor\Plugin::$instance->db->is_bu
|
|
102 |
else{ ?>.cntr {max-width: 1100px;margin: 0 auto;width:100%;padding:0px 20px} <?php } ?>
|
103 |
<?php if(!ampforwp_levelup_compatibility('levelup_elementor') ){ // Level up Condition starts
|
104 |
if ( $ampforwp_font_icon == 'swift-icons' || ( $ampforwp_font_icon == 'fontawesome-icons' && checkAMPforPageBuilderStatus(ampforwp_get_the_ID()) ) ){ ?>
|
105 |
-
@font-face {font-family: 'icomoon';font-display:
|
106 |
<?php } // Swift icomoon icons condition ends ?>
|
107 |
header .cntr{
|
108 |
<?php if( ampforwp_get_setting('swift-width-control') ){?>
|
34 |
?>
|
35 |
<?php if(1==ampforwp_get_setting('ampforwp-google-font-switch') && ( !isset($redux_builder_amp['amp_font_selector']) || $redux_builder_amp['amp_font_selector'] == 1 || empty($redux_builder_amp['amp_font_selector']) ) ) {
|
36 |
if(!ampforwp_levelup_compatibility('levelup_theme_and_elementor')){ // Level up Condition starts ?>
|
37 |
+
@font-face {font-family: 'Poppins';font-display: optional;font-style: normal;font-weight: 300;src: local('Poppins Light'), local('Poppins-Light'), url('<?php echo $icon_url ?>fonts/Poppins-Light.ttf');}
|
38 |
+
@font-face {font-family: 'Poppins';font-display: optional;font-style: normal;font-weight: 400;src: local('Poppins Regular'), local('Poppins-Regular'), url('<?php echo $icon_url ?>fonts/Poppins-Regular.ttf');}
|
39 |
+
@font-face {font-family: 'Poppins';font-display: optional;font-style: normal;font-weight: 500;src: local('Poppins Medium'), local('Poppins-Medium'), url('<?php echo $icon_url ?>fonts/Poppins-Medium.ttf');}
|
40 |
+
@font-face {font-family: 'Poppins';font-display: optional;font-style: normal;font-weight: 600;src: local('Poppins SemiBold'), local('Poppins-SemiBold'), url('<?php echo $icon_url ?>fonts/Poppins-SemiBold.ttf'); }
|
41 |
+
@font-face {font-family: 'Poppins';font-display: optional;font-style: normal;font-weight: 700;src: local('Poppins Bold'), local('Poppins-Bold'), url('<?php echo $icon_url ?>fonts/Poppins-Bold.ttf'); }
|
42 |
<?php } // Level up Condition ends
|
43 |
} ?>
|
44 |
<?php
|
102 |
else{ ?>.cntr {max-width: 1100px;margin: 0 auto;width:100%;padding:0px 20px} <?php } ?>
|
103 |
<?php if(!ampforwp_levelup_compatibility('levelup_elementor') ){ // Level up Condition starts
|
104 |
if ( $ampforwp_font_icon == 'swift-icons' || ( $ampforwp_font_icon == 'fontawesome-icons' && checkAMPforPageBuilderStatus(ampforwp_get_the_ID()) ) ){ ?>
|
105 |
+
@font-face {font-family: 'icomoon';font-display: optional;font-style: normal;font-weight: normal;src: local('icomoon'), local('icomoon'), url('<?php echo $icon_url ?>fonts/icomoon.ttf');}
|
106 |
<?php } // Swift icomoon icons condition ends ?>
|
107 |
header .cntr{
|
108 |
<?php if( ampforwp_get_setting('swift-width-control') ){?>
|
templates/features.php
CHANGED
@@ -3606,6 +3606,9 @@ function ampforwp_add_meta_viewport() {
|
|
3606 |
$output = '';
|
3607 |
$output = '<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=2,user-scalable=yes">
|
3608 |
';
|
|
|
|
|
|
|
3609 |
if(!class_exists( 'AMPforWP_Mobile_Detect') && !ampforwp_get_setting('amp-mobile-redirection')){
|
3610 |
ampforwp_require_file( AMPFORWP_PLUGIN_DIR.'/includes/vendor/Mobile_Detect.php ');
|
3611 |
}
|
3606 |
$output = '';
|
3607 |
$output = '<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=2,user-scalable=yes">
|
3608 |
';
|
3609 |
+
if (ampforwp_get_setting('ampforwp-meta-viewport') == false) {
|
3610 |
+
$output = '<meta name="viewport" content="width=device-width">';
|
3611 |
+
}
|
3612 |
if(!class_exists( 'AMPforWP_Mobile_Detect') && !ampforwp_get_setting('amp-mobile-redirection')){
|
3613 |
ampforwp_require_file( AMPFORWP_PLUGIN_DIR.'/includes/vendor/Mobile_Detect.php ');
|
3614 |
}
|