AMP for WP – Accelerated Mobile Pages - Version 1.0.77.7

Version Description

(21st June 2021) = * Added: AMP Optimizer (SSR) Support #5034

Download this release

Release Info

Developer mohammed_kaludi
Plugin Icon 128x128 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

Files changed (130) hide show
  1. README.md +4 -1
  2. accelerated-moblie-pages.php +7 -2
  3. changelog.txt +3 -0
  4. components/components-core.php +1 -1
  5. components/featured-image/featured-image.php +1 -1
  6. includes/amp-optimizer-addon.php +26 -0
  7. includes/features/performance/performance-functions.php +5 -8
  8. includes/options/admin-config.php +21 -0
  9. includes/vendor/amp/includes/amp-post-template-actions.php +1 -0
  10. includes/vendor/amp/includes/sanitizers/class-amp-allowed-tags-generated.php +2 -2
  11. includes/vendor/css-parser/autoload.php +1 -1
  12. includes/vendor/css-parser/composer.json +5 -0
  13. includes/vendor/css-parser/composer.lock +11 -6
  14. includes/vendor/css-parser/composer/ClassLoader.php +38 -3
  15. includes/vendor/css-parser/composer/InstalledVersions.php +311 -0
  16. includes/vendor/css-parser/composer/autoload_classmap.php +68 -0
  17. includes/vendor/css-parser/composer/autoload_real.php +11 -6
  18. includes/vendor/css-parser/composer/autoload_static.php +74 -2
  19. includes/vendor/css-parser/composer/installed.json +58 -49
  20. includes/vendor/css-parser/composer/installed.php +37 -0
  21. includes/vendor/css-parser/composer/platform_check.php +28 -0
  22. includes/vendor/tool/Amp.php +355 -0
  23. includes/vendor/tool/Attribute.php +123 -0
  24. includes/vendor/tool/Cli/AmpExecutable.php +74 -0
  25. includes/vendor/tool/Cli/Colors.php +177 -0
  26. includes/vendor/tool/Cli/Command.php +47 -0
  27. includes/vendor/tool/Cli/Command/Optimize.php +81 -0
  28. includes/vendor/tool/Cli/Executable.php +409 -0
  29. includes/vendor/tool/Cli/LogLevel.php +116 -0
  30. includes/vendor/tool/Cli/Options.php +558 -0
  31. includes/vendor/tool/Cli/TableFormatter.php +361 -0
  32. includes/vendor/tool/CssLength.php +179 -0
  33. includes/vendor/tool/DevMode.php +85 -0
  34. includes/vendor/tool/Dom/Document.php +2148 -0
  35. includes/vendor/tool/Dom/Document/Encoding.php +54 -0
  36. includes/vendor/tool/Dom/Document/Option.php +65 -0
  37. includes/vendor/tool/Dom/Element.php +281 -0
  38. includes/vendor/tool/Dom/ElementDump.php +102 -0
  39. includes/vendor/tool/Dom/NodeWalker.php +59 -0
  40. includes/vendor/tool/Exception/AmpCliException.php +61 -0
  41. includes/vendor/tool/Exception/AmpException.php +13 -0
  42. includes/vendor/tool/Exception/Cli/InvalidArgument.php +52 -0
  43. includes/vendor/tool/Exception/Cli/InvalidColor.php +28 -0
  44. includes/vendor/tool/Exception/Cli/InvalidColumnFormat.php +52 -0
  45. includes/vendor/tool/Exception/Cli/InvalidCommand.php +42 -0
  46. includes/vendor/tool/Exception/Cli/InvalidOption.php +28 -0
  47. includes/vendor/tool/Exception/Cli/InvalidSapi.php +28 -0
  48. includes/vendor/tool/Exception/Cli/MissingArgument.php +41 -0
  49. includes/vendor/tool/Exception/FailedRemoteRequest.php +13 -0
  50. includes/vendor/tool/Exception/FailedToGetCachedResponse.php +28 -0
  51. includes/vendor/tool/Exception/FailedToGetFromRemoteUrl.php +90 -0
  52. includes/vendor/tool/Exception/FailedToParseUrl.php +27 -0
  53. includes/vendor/tool/Exception/FailedToRetrieveRequiredDomElement.php +56 -0
  54. includes/vendor/tool/Exception/MaxCssByteCountExceeded.php +43 -0
  55. includes/vendor/tool/Extension.php +50 -0
  56. includes/vendor/tool/Layout.php +55 -0
  57. includes/vendor/tool/LengthUnit.php +222 -0
  58. includes/vendor/tool/Optimizer/Configuration.php +89 -0
  59. includes/vendor/tool/Optimizer/Configuration/AmpRuntimeCssConfiguration.php +109 -0
  60. includes/vendor/tool/Optimizer/Configuration/BaseTransformerConfiguration.php +98 -0
  61. includes/vendor/tool/Optimizer/Configuration/OptimizeAmpBindConfiguration.php +63 -0
  62. includes/vendor/tool/Optimizer/Configuration/PreloadHeroImageConfiguration.php +107 -0
  63. includes/vendor/tool/Optimizer/Configuration/RewriteAmpUrlsConfiguration.php +185 -0
  64. includes/vendor/tool/Optimizer/Configuration/TransformedIdentifierConfiguration.php +62 -0
  65. includes/vendor/tool/Optimizer/CssRule.php +344 -0
  66. includes/vendor/tool/Optimizer/CssRules.php +140 -0
  67. includes/vendor/tool/Optimizer/DefaultConfiguration.php +170 -0
  68. includes/vendor/tool/Optimizer/Error.php +26 -0
  69. includes/vendor/tool/Optimizer/Error/CannotAdaptDocumentForSelfHosting.php +41 -0
  70. includes/vendor/tool/Optimizer/Error/CannotInlineRuntimeCss.php +50 -0
  71. includes/vendor/tool/Optimizer/Error/CannotPerformServerSideRendering.php +71 -0
  72. includes/vendor/tool/Optimizer/Error/CannotPreloadImage.php +38 -0
  73. includes/vendor/tool/Optimizer/Error/CannotRemoveBoilerplate.php +98 -0
  74. includes/vendor/tool/Optimizer/Error/ErrorProperties.php +51 -0
  75. includes/vendor/tool/Optimizer/Error/TooManyHeroImages.php +28 -0
  76. includes/vendor/tool/Optimizer/Error/UnknownError.php +15 -0
  77. includes/vendor/tool/Optimizer/ErrorCollection.php +71 -0
  78. includes/vendor/tool/Optimizer/Exception/AmpOptimizerException.php +15 -0
  79. includes/vendor/tool/Optimizer/Exception/InvalidArgument.php +28 -0
  80. includes/vendor/tool/Optimizer/Exception/InvalidConfiguration.php +29 -0
  81. includes/vendor/tool/Optimizer/Exception/InvalidConfigurationKey.php +43 -0
  82. includes/vendor/tool/Optimizer/Exception/InvalidConfigurationValue.php +46 -0
  83. includes/vendor/tool/Optimizer/Exception/InvalidHtmlAttribute.php +30 -0
  84. includes/vendor/tool/Optimizer/Exception/UnknownConfigurationClass.php +27 -0
  85. includes/vendor/tool/Optimizer/Exception/UnknownConfigurationKey.php +44 -0
  86. includes/vendor/tool/Optimizer/HeroImage.php +100 -0
  87. includes/vendor/tool/Optimizer/ImageDimensions.php +433 -0
  88. includes/vendor/tool/Optimizer/LocalFallback.php +50 -0
  89. includes/vendor/tool/Optimizer/TransformationEngine.php +151 -0
  90. includes/vendor/tool/Optimizer/Transformer.php +23 -0
  91. includes/vendor/tool/Optimizer/Transformer/AmpBoilerplate.php +172 -0
  92. includes/vendor/tool/Optimizer/Transformer/AmpBoilerplateErrorHandler.php +54 -0
  93. includes/vendor/tool/Optimizer/Transformer/AmpRuntimeCss.php +198 -0
  94. includes/vendor/tool/Optimizer/Transformer/OptimizeAmpBind.php +115 -0
  95. includes/vendor/tool/Optimizer/Transformer/PreloadHeroImage.php +726 -0
  96. includes/vendor/tool/Optimizer/Transformer/ReorderHead.php +366 -0
  97. includes/vendor/tool/Optimizer/Transformer/RewriteAmpUrls.php +358 -0
  98. includes/vendor/tool/Optimizer/Transformer/ServerSideRendering.php +1045 -0
  99. includes/vendor/tool/Optimizer/Transformer/TransformedIdentifier.php +88 -0
  100. includes/vendor/tool/Optimizer/TransformerConfiguration.php +33 -0
  101. includes/vendor/tool/RemoteGetRequest.php +25 -0
  102. includes/vendor/tool/RemoteRequest/CurlRemoteGetRequest.php +155 -0
  103. includes/vendor/tool/RemoteRequest/FallbackRemoteGetRequest.php +83 -0
  104. includes/vendor/tool/RemoteRequest/FilesystemRemoteGetRequest.php +66 -0
  105. includes/vendor/tool/RemoteRequest/RemoteGetRequestResponse.php +184 -0
  106. includes/vendor/tool/RemoteRequest/StubbedRemoteGetRequest.php +50 -0
  107. includes/vendor/tool/RequestDestination.php +102 -0
  108. includes/vendor/tool/Response.php +98 -0
  109. includes/vendor/tool/Role.php +524 -0
  110. includes/vendor/tool/RuntimeVersion.php +116 -0
  111. includes/vendor/tool/Tag.php +92 -0
  112. includes/vendor/tool/Url.php +300 -0
  113. readme.txt +4 -1
  114. templates/design-manager/design-1/404.php +1 -1
  115. templates/design-manager/design-1/archive.php +1 -1
  116. templates/design-manager/design-1/index.php +1 -1
  117. templates/design-manager/design-1/search.php +1 -1
  118. templates/design-manager/design-1/single.php +1 -1
  119. templates/design-manager/design-2/404.php +1 -1
  120. templates/design-manager/design-2/archive.php +1 -1
  121. templates/design-manager/design-2/index.php +1 -1
  122. templates/design-manager/design-2/search.php +1 -1
  123. templates/design-manager/design-2/single.php +1 -1
  124. templates/design-manager/design-3/404.php +1 -1
  125. templates/design-manager/design-3/archive.php +1 -1
  126. templates/design-manager/design-3/index.php +1 -1
  127. templates/design-manager/design-3/search.php +1 -1
  128. templates/design-manager/design-3/single.php +1 -1
  129. templates/design-manager/swift/style.php +6 -6
  130. 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.6
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.6
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.6');
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="dns-prefetch" href="//cdn.ampproject.org">
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
- // return json_encode($datatrack);
342
-
343
  $data = $obj->get_stylesheets();
344
- //return json_encode($data);
345
-
346
  $comment = $obj->get_comments();
347
- //return json_encode($comment);
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' => 50000,
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' => 500000,
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 ComposerAutoloaderInit117444c18d5d7304d751024fddbe3a07::getLoader();
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.0",
12
  "source": {
13
  "type": "git",
14
  "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
15
- "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f"
16
  },
17
  "dist": {
18
  "type": "zip",
19
- "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f",
20
- "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f",
21
  "shasum": ""
22
  },
23
  "require": {
@@ -49,7 +49,11 @@
49
  "parser",
50
  "stylesheet"
51
  ],
52
- "time": "2019-02-22T07:42:52+00:00"
 
 
 
 
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 http://www.php-fig.org/psr/psr-0/
43
- * @see http://www.php-fig.org/psr/psr-4/
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 ComposerAutoloaderInit117444c18d5d7304d751024fddbe3a07
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
- spl_autoload_register(array('ComposerAutoloaderInit117444c18d5d7304d751024fddbe3a07', 'loadClassLoader'), true, true);
25
- self::$loader = $loader = new \Composer\Autoload\ClassLoader();
26
- spl_autoload_unregister(array('ComposerAutoloaderInit117444c18d5d7304d751024fddbe3a07', 'loadClassLoader'));
 
 
27
 
28
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
29
  if ($useStaticLoader) {
30
- require_once __DIR__ . '/autoload_static.php';
31
 
32
- call_user_func(\Composer\Autoload\ComposerStaticInit117444c18d5d7304d751024fddbe3a07::getInitializer($loader));
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 ComposerStaticInit117444c18d5d7304d751024fddbe3a07
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 = ComposerStaticInit117444c18d5d7304d751024fddbe3a07::$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
- "name": "sabberworm/php-css-parser",
4
- "version": "8.3.0",
5
- "version_normalized": "8.3.0.0",
6
- "source": {
7
- "type": "git",
8
- "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
9
- "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f"
10
- },
11
- "dist": {
12
- "type": "zip",
13
- "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f",
14
- "reference": "91bcc3e3fdb7386c9a2e0e0aa09ca75cc43f121f",
15
- "shasum": ""
16
- },
17
- "require": {
18
- "php": ">=5.3.2"
19
- },
20
- "require-dev": {
21
- "codacy/coverage": "^1.4",
22
- "phpunit/phpunit": "~4.8"
23
- },
24
- "time": "2019-02-22T07:42:52+00:00",
25
- "type": "library",
26
- "installation-source": "dist",
27
- "autoload": {
28
- "psr-0": {
29
- "Sabberworm\\CSS": "lib/"
30
- }
31
- },
32
- "notification-url": "https://packagist.org/downloads/",
33
- "license": [
34
- "MIT"
35
- ],
36
- "authors": [
37
- {
38
- "name": "Raphael Schweikert"
39
- }
40
- ],
41
- "description": "Parser for CSS Files written in PHP",
42
- "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
43
- "keywords": [
44
- "css",
45
- "parser",
46
- "stylesheet"
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.6
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" 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);
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" 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);
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" href="//cdn.ampproject.org">
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="dns-prefetch" 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);
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="dns-prefetch" href="//cdn.ampproject.org">
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: swap;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: swap;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: swap;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: swap;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: swap;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,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: swap;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') ){?>
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
  }