AMP for WordPress - Version 1.0.0

Version Description

(2018-12-05) =

To learn how to use the new features in this release, please see the wiki pages for Adding Theme Support and Implementing Interactivity.

  • Add runtime CSS minification, !important replacement, and tree shaking. See #1048, #1111, #1142, #1320, #1073. Props westonruter, hellofromtonya, amedina, pbakaus, igrigorik, camelburrito.
  • Keep track of new validation errors and add ability to accept/reject in order to allow or block AMP for a given URL. See #1003. Props westonruter.
  • Redesign screens for Invalid URLs and Error Index. See #1394, #1361, #1444, #1448, #1452, #1397, #1446, #1364, #1449, #1418, #1451, #1429, #1408, #1414, #1409, #1373, #1462, #1471, #1485. Props kienstra, johnwatkins0, miina, jacobschweitzer, westonruter.
  • Extend admin screen options to add amp theme support without any coding required. Toggle between classic, paired, and native. Includes options for whether sanitization should be done by default and whether tree shaking should always be allowed. See #1199, #1291, #1264. Props westonruter, AdelDima.
  • Add support for allowing a site subset to be native AMP. See #1235. Props westonruter.
  • Add an admin pointer for updated AMP settings screen for version 1.0. See #1271, #1254. Props kienstra.
  • Add support for three core themes (Twenty Fifteen, Twenty Sixteen, Twenty Seventeen) so that they can be used out of the box with AMP theme support added without needing to create a child theme. See #1074. Props westonruter, DavidCramer, kienstra.
  • Add AMP support for Twenty Nineteen. See #1587, #1619. Props westonruter.
  • Add AMP menu item to admin bar on frontend with indication of AMP validation status; accessing an AMP URL that has unaccepted validation errors will redirect to the non-AMP page and cause the AMP admin bar item to indicate the failure, along with a link to access the validation results. See #1199. Props westonruter.
  • Add dynamic handling of validation errors. See #1093, #1063, #1087. Props westonruter.
  • Add AMP validation of blocks. See #1019. Props westonruter.
  • Add AMP-specific functionality to core blocks. See #1026, #1008. Props miina.
  • Add AMP media blocks (when in native AMP mode). See #1155. Props miina.
  • Add embed handler for Gfycat. See #1136. Props miina.
  • Add amp-mathml block. See #1165. Props miina.
  • Add Gutenberg amp-timeago block. See #1168. Props miina.
  • Add amp-fit-text support to text blocks. See #1151. Props miina.
  • Fix handling of font stylesheets with non-HTTPS scheme or scheme-less URLs. See #1077. Props westonruter.
  • Fix issues in displaying native blocks. See #1022. Props miina.
  • Gutenberg: Add AMP Carousel for Gallery and AMP Lightbox features for Gallery and Image. See #1121, #1065, #1187. Props miina, westonruter.
  • Add "Enable AMP" toggle in Gutenberg editor. See #1275, #1230. Props kienstra.
  • Cache post processor response. See #1156, #959. Props ThierryA.
  • Add preload links & resource hints, and optimize order of elements in head. See #1295. Props westonruter.
  • Automatically redirect to ?amp from /amp/ URLs when amp theme support is present. See #1203, #1194. Props westonruter.
  • Incorporate Server Timing API. See #990. Props westonruter.
  • Add information about stylesheets included and excluded in style[amp-custom]. See #1135. Props westonruter.
  • Fetch (local) stylesheets with @import, instead of removing them. See #1181. Props miina.
  • Fetch external stylesheets (which aren't from whitelisted font CDNs) to include in amp-custom style. See #1174. Props miina.
  • Transform CSS selectors according to sanitizer HTML element to AMP component conversions. See #1175. Props miina, westonruter.
  • Rework displaying block validation messages. See #1682. Props miina.
  • Ensure layout attributes are only allowed on supporting elements. See #1075. Props westonruter.
  • Correct the width attribute in col tags to the equivalent CSS rule. See #1064. Props amedina.
  • Ensure that video source elements use HTTPS. See #1274, #976. Props hellofromtonya.
  • Preserve whitespace when serializing the DOM as HTML. See #1309, #1304. Props westonruter.
  • Fix reporting the removal of unrecognized elements. See #1287, #1100. Props hellofromtonya.
  • Remove space from data: url() in stylesheets. See #1164, #1089. Props amedina, JonHendershot, westonruter, mehigh, davisshaver, Mte90.
  • Fix inconsistency between singular and plural. See #1114. Props garrett-eclipse.
  • Disable AMP admin menu option when the AMP Customizer is not enabled or theme support is enabled. See #1080. Props oscarssanchez.
  • Allow spaces around commas in value property lists. See #1112. Props westonruter.
  • Restore admin bar on AMP pages and improve AMP menu items. See #1219. Props westonruter.
  • Remove empty media queries. See #1423. Props korobochkin, westonruter.
  • Update PHP-CSS-Parser and include tree shaker effectiveness in style[amp-custom] manifest comment. See #1650. Props westonruter.
  • Display admin notice if there's no persistent object caching. See #1050. Props oscarssanchez.
  • Re-use styling for unmoderated comments to apply to new accepted/rejected validation errors. See #1458. Props westonruter, johnwatkins0, jacobschweitzer.
  • Update PHP-CSS-Parser to use new calc() support. See #1116, #1284. Props westonruter.
  • Fix parsing CSS selectors which contain commas. See #1286. Props westonruter.
  • Add sanitizer to support amp-o2-player. See #1202. Props juanchaur1.
  • Update contributing.md and add code of conduct. See #1649. Props amedina.
  • Add AMP_Embed_Sanitizer. See #1128. Props juanchaur1.
  • Add AMP_Script_Sanitizer to replace noscript elements with their contents. See #1226. Props westonruter.
  • Update generated tags file to 767. See #1665. Props miina.
  • Fix header image filtering and YouTube header video detection. See #1208. Props westonruter.
  • Improve support for Hulu & Imgur embeds. See #1218. Props miina.
  • Fix integration with WordPress 5.0. See #1520. Props miina.
  • Update spec generated from amphtml to file revision 675 and AMP v1531357871900. See #1312. Props westonruter.
  • Opt-in to CORS mode for external font stylesheet links. See #1289. Props westonruter.
  • PHPCS fixes, including PHP DocBlocks and strict comparisons. See #1002. Props paulschreiber.
  • Fix generation of validation error when element has multiple invalid attributes. See #1461. Props westonruter.
  • Prevent empty term status from being interpreted as new-rejected during bulk change. See #1460. Props westonruter.
  • Add script to create built tag. See #1209. Props westonruter.
  • Fix handling of amp-bind attributes to ensure that > can appear inside attribute values. See #1119. Props westonruter.
  • Tree-shake CSS selectors for HTML elements that target non-active languages. See #1221. Props westonruter.
  • Redirect to post list table in case of admin bar validate request failure. See #1229. Props westonruter.
  • Amend AMP style elements with sourceURL comment for DevTools to be able to perform CSS code coverage. See #1584. Props westonruter.
  • Prevent erroneously tree-shaking keyframe selectors like from, to, and percentages. See #1211. Props westonruter.
  • Add caching of redirect to non-AMP URL when validation errors present. See #1207. Props westonruter.
  • Discontinue using 'latest' version of component scripts. See #1464. Props westonruter.
  • Ensure font stylesheets are requested in CORS mode in both AMP and non-AMP documents. See #1486. Props westonruter.
  • Move any content output during shutdown to be injected before closing body tag. See #1102. Props westonruter.
  • Fix obtaining source for widgets. See #1212. Props westonruter.
  • Address issue where <ul> is converted to an <amp-carousel>. See #1529. Props kienstra.
  • Construct schema.org meta script by appending text node. See #1220. Props westonruter.
  • Eliminate amp-wp-enforced-sizes style from theme support stylesheet. See #1153. Props westonruter.
  • Add support for extracting (pixel) dimensions from SVG images. See #1150. Props westonruter.
  • Ensure redirect is only done if there are unsanitized errors. See #1241. Props westonruter.
  • Deprecate AMP_WP_Utils, in favor of wp_parse_url(). See #995. Props paulschreiber.
  • Add WP-CLI script to test support for blocks. See #845. Props kienstra.
  • Ensure translatable strings in blocks can actually be translated. See #1173. Props miina, swissspidy, westonruter.
  • Look in entire document for Schema.org metadata not just head. See #1664. Props westonruter.
  • Fix title display of Invalid URL page. See #1463. Props amedina.
  • Add native/paired/classic mode to AMP generator meta. See #1465. Props westonruter.
  • Prevent is_amp_endpoint() from triggering notice when called on login, signup, or activate screens. See #1250. Props felixarntz.
  • Support extracting dimensions for single URLs. See #793. Props mjangda, mdbitz.
  • Improve validation and presentation of analytics form. See #1299, #1133, #1296. Props westonruter, AdelDima.
  • Prevent validation of auto-drafts, including when merely accessing New Post screen. See #1301. Props westonruter.
  • Fix inability to move link element due to assigned parent. See #1322. Props westonruter.
  • Gutenberg: Remove 'type' from attributes where 'source' is set. See #1622. Props miina.
  • Gutenberg: Fix displaying validation warning and usage of PHP function. See #1612. Props miina.
  • Fix stretched images in Twenty Seventeen them and Gutenberg. See #1321, #1281, #1237. Props hellofromtonya.
  • Fix image dimension extractor so it does not disregard duplicate images. See #1314. Props lukas9393.
  • Improve organization of third party code. See #1657. Props westonruter.
  • Short-circuit polldaddy shortcode when no poll or survey supplied. See #1621. Props westonruter.
  • Remove redundant version from composer.json and add PHP version requirement. See #1333, #1328, #1334, #1332. Props swissspidy.
  • Add warning when AMP plugin is installed in incorrect directory. See #1593. Props westonruter.
  • Store validation errors in order of occurrence in document. See #1335. Props westonruter.
  • Add .editorconfig file. See #1336, #51. Props swissspidy.
  • Update i18n to make use of updated WP-CLI command. See #1329, #1327, #1341, #1345, #1393. Props swissspidy, felixarntz, westonruter.
  • Use all eligible post types when all_templates_supported is selected. See #1338, #1302, #1344. Props hellofromtonya, westonruter.
  • Address an issue with an invalid embed. See #1661. Props kienstra.
  • Do not show fallback source as active theme if no validation errors. See #1592. Props westonruter.
  • Respect default AMP enabled status when creating a new post in Gutenberg. See #1339. Props hellofromtonya.
  • Fix incorrect attribution of theme as source for content validation errors. See #1467. Props westonruter.
  • Move AMP Settings in editor to after default settings. See #1652. Props miina.
  • Fix conversion of video to amp-video. See #1477. Props westonruter.
  • Add new icon, text, and style to splash notice. See #1470. Props jacobschweitzer.
  • Normalize 'ver' query param in script/style validation errors to prevent recurrence after accepted. See #1346. Props westonruter.
  • Add bing-amp.com to the list of AMP Cache hosts. See #1447. Props westonruter.
  • Add missing tabindex attribute to lightbox images. See #1350. Props amedina.
  • Update AMP spec to 757 (v1811091519050). See #1588. Props westonruter, kienstra.
  • Detect ineffectual post-processor response cache due to high MISS rates and auto-disable. See #1325, #1239. Props hellofromtonya, westonruter.
  • Update regex for tag selectors. See #1534. Props swissspidy, westonruter.
  • Update the validator spec version to 720 and AMP v1534879991178; add support for reference points. See #1315, #1386, #1330. Props westonruter.
  • Update spec from revision 720 to 734. See #1475. Props kienstra.
  • Fix form sanitizer's handling of relative actions by making them absolute. See #1352, #1349. Props ricardobrg.
  • Skip Server-Timing header if not WP_DEBUG and user cannot manage_options. See #1354. Props westonruter.
  • Fetch CSS over HTTP when URL lacks extension; convert font CDN stylesheets @imports to convert to links instead of fetching. See #1357, #1317. Props westonruter.
  • Add WP-CLI command for testing the AMP compatibility of an entire site. See #1183, #1007. Props kienstra, westonruter.
  • Update screenshots. See #1701. Props westonruter, amedina.
  • Update the description of the AMP project in readme file. See #1693. Props amedina.
  • Use new banner images. See #1692. Props cathibosco.
  • Display when validation results are stale due to active theme/plugin changes. See #1375. Props westonruter.
  • Fix displaying of expected notices when theme support enabled by theme. See #1374, #1358. Props westonruter.
  • Update native mode description to mention AMP-first. See #1703. Props westonruter.
  • Fix handling responses to form submissions from an AMP Cache. See #1382, #1356.
  • Replace Gutenberg's deprecated isCleanNewPost selector. See #1387. Props miina.
  • Updates php-css-parser to include fix for parsing calc() with negative values. See #1392. Props westonruter.
  • Add embed support for Twitter timelines via new amp-twitter attributes. See #1396. Props felixarntz.
  • Eliminate obsolete sudo:false from Travis config. See #1651. Props westonruter.
  • Fix tooltip position. See #1472. Props jacobschweitzer.
  • Add error type filters on validation error and invalid URL screens. See #1373. Props kienstra.
  • Default to auto sanitization and tree shaking being enabled. See #1402. Props westonruter.
  • Prevent the admin pointer from staying below the viewport. See #1694. Props kienstra.
  • Omit validation errors sanitized by filter or tree-shaking option; since sanitization is forced, there is no point to store. See (#1413)[https://github.com/ampproject/amp-wp/pull/1413]. Props westonruter.
  • Prevent URL validation from happening during bulk imports. See #1424, #1404. Props westonruter.
  • Normalize invalid URL stored for amp_validated_url post type. See #1436. Props westonruter.
  • Make the default layout responsive for the <amp-ooyala-player> block. See #1585. Props kienstra.
  • Add default values for AMP Timeago block. See #1586. Props kienstra.
  • Expose and store queried object for validated URL; show edit link. See #1426, #1428, #1433. Props westonruter.
  • Re-validate the site when switching modes and show the results in a notice. See #1443. Props kienstra, westonruter.
  • Improve access to AMP admin screens for users who are not administrators. #1437. Props westonruter.
  • Display a welcome notice on the main 'AMP Settings' page. See #1442. Props kienstra.
  • Fix URL protocol validation and parsing attribute values with multiple URLs. See #1411, #1410. Props westonruter.
  • Prevent a notice from appearing in the Compatibility Tool meta box. See #1605. Props kienstra.
  • Restore ability to customize 'amp' query var when theme support added. #1455. Props westonruter.
  • Add slug constants for theme support and post type support. #1456. Props westonruter.
  • Fix ability to add AMP support for custom post types. See #1441. Props westonruter.
  • Fix stretched logo and header issues in Twenty Seventeen. #1419. Props westonruter.
  • Add caption support to all amp-instagram embeds. See #1438, #822. Props chandrapatel.
  • Fix PHP warning generated by calls to idn_to_utf8(). See #1440, #1439. Props kraftbj.
  • Fix PHP fatal error during AMP validation when a plugin uses a class method as an output buffer callback. #1453. Props westonruter.
  • Update minimum PHP version from 5.3.2 to 5.3.6. See #1407, #1406. Props westonruter.
  • Improve package.json and composer.json. See #1405. Props swissspidy.
  • Ensure PHP file generated for use by translate.wordpress.org is free of syntax errors. See #1427, #1416. Props swissspidy, westonruter.

For a full list of the closed issues and merged pull requests in this release, see the 1.0 milestone.

Contributors in this release, including design, development, testing, and project management: Adel Tahri (AdelDima), Alberto Medina (amedina), Anne Louise Currie (alcurrie), Brandon Kraft (kraftbj), Cathi Bosco (cathibosco), Chandra Patel (chandrapatel), Claudio Sossi, Daniel Walmsley (gravityrail), David Cramer (DavidCramer), Felix Arntz (felixarntz), Garrett Hyder (garrett-eclipse), Jacob Schweitzer (jacobschweitzer), John Watkins0 (johnwatkins0), Joshua Wold (jwold), Juan Chaur (juanchaur1), Kevin Coleman (kevincoleman), Leo Postovoit (postphotos), Lukas Hettwer (lukas9393), Mackenzie Hartung (MackenzieHartung), Matthew Denton (mdbitz), Miina Sikk (miina), Mohammad Jangda (mjangda), Pascal Birchler (swissspidy), Oscar Snchez (oscarssanchez), Paul Schreiber (paulschreiber), Ricardo Gonalves (ricardobrg), Ryan Kienstra (kienstra), Thierry Muller (ThierryA), Tonya Mork (hellofromtonya), Weston Ruter (westonruter).

Download this release

Release Info

Developer westonruter
Plugin Icon 128x128 AMP for WordPress
Version 1.0.0
Comparing to
See all releases

Code changes from version 0.7.2 to 1.0.0

Files changed (83) hide show
  1. amp.php +230 -38
  2. assets/css/admin-bar.css +1058 -0
  3. assets/css/admin-tables.css +57 -0
  4. assets/css/amp-default.css +21 -7
  5. assets/css/amp-editor-blocks.css +4 -0
  6. assets/css/amp-post-meta-box.css +13 -0
  7. assets/css/amp-validation-error-taxonomy.css +279 -0
  8. assets/css/amp-validation-single-error-url.css +164 -0
  9. assets/css/amp-validation-tooltips.css +7 -0
  10. assets/images/amp-logo-icon.svg +10 -0
  11. assets/images/amp-welcome-icon.svg +139 -0
  12. assets/images/baseline-check-circle-green.svg +4 -0
  13. assets/images/baseline-error-green.svg +12 -0
  14. assets/images/baseline-error-red.svg +12 -0
  15. assets/images/baseline-error.svg +12 -0
  16. assets/images/down-triangle.svg +3 -0
  17. assets/images/editor-help.svg +1 -0
  18. assets/images/error-rejected.svg +1 -0
  19. assets/js/amp-admin-pointer.js +37 -0
  20. assets/js/amp-block-editor-toggle-compiled.js +76 -0
  21. assets/js/amp-block-validation.js +540 -0
  22. assets/js/amp-blocks-compiled.js +151 -0
  23. assets/js/amp-customize-controls.js +12 -13
  24. assets/js/amp-customize-preview.js +2 -3
  25. assets/js/amp-customizer-design-preview.js +3 -4
  26. assets/js/amp-editor-blocks.js +852 -0
  27. assets/js/amp-post-meta-box.js +7 -7
  28. assets/js/amp-validated-url-post-edit-screen.js +409 -0
  29. assets/js/amp-validated-urls-index.js +34 -0
  30. assets/js/amp-validation-detail-toggle-compiled.js +91 -0
  31. assets/js/amp-validation-single-error-url-details-compiled.js +84 -0
  32. assets/js/amp-validation-tooltips-compiled.js +84 -0
  33. assets/src/amp-block-editor-toggle.js +100 -0
  34. assets/src/amp-validation-detail-toggle.js +102 -0
  35. assets/src/amp-validation-single-error-url-details.js +159 -0
  36. assets/src/amp-validation-tooltips.js +22 -0
  37. back-compat/templates-v0-3/meta-time.php +2 -1
  38. includes/admin/class-amp-admin-pointer.php +143 -0
  39. includes/admin/class-amp-customizer.php +42 -23
  40. includes/admin/class-amp-editor-blocks.php +201 -0
  41. includes/admin/class-amp-post-meta-box.php +146 -7
  42. includes/admin/functions.php +48 -7
  43. includes/amp-frontend-actions.php +7 -24
  44. includes/amp-helper-functions.php +315 -86
  45. includes/amp-post-template-actions.php +7 -9
  46. includes/class-amp-autoloader.php +29 -11
  47. includes/class-amp-cli.php +680 -0
  48. includes/class-amp-http.php +441 -0
  49. includes/class-amp-post-type-support.php +64 -18
  50. includes/class-amp-theme-support.php +1279 -444
  51. includes/{lib/fasterimage/amp-fasterimage.php → deprecated.php} +5 -4
  52. includes/embeds/class-amp-base-embed-handler.php +38 -4
  53. includes/embeds/class-amp-core-block-handler.php +117 -0
  54. includes/embeds/class-amp-dailymotion-embed.php +83 -21
  55. includes/embeds/class-amp-facebook-embed.php +93 -6
  56. includes/embeds/class-amp-gallery-embed.php +85 -15
  57. includes/embeds/class-amp-gfycat-embed-handler.php +81 -0
  58. includes/embeds/class-amp-hulu-embed-handler.php +87 -0
  59. includes/embeds/class-amp-imgur-embed-handler.php +148 -0
  60. includes/embeds/class-amp-instagram-embed.php +122 -7
  61. includes/embeds/class-amp-soundcloud-embed.php +1 -1
  62. includes/embeds/class-amp-twitter-embed.php +244 -13
  63. includes/embeds/class-amp-vimeo-embed.php +87 -28
  64. includes/embeds/class-amp-youtube-embed.php +89 -23
  65. includes/options/class-amp-analytics-options-submenu.php +2 -2
  66. includes/options/class-amp-options-manager.php +369 -14
  67. includes/options/class-amp-options-menu.php +490 -39
  68. includes/options/views/class-amp-analytics-options-submenu-page.php +91 -6
  69. includes/sanitizers/class-amp-allowed-tags-generated.php +4928 -1080
  70. includes/sanitizers/class-amp-audio-sanitizer.php +29 -0
  71. includes/sanitizers/class-amp-base-sanitizer.php +275 -36
  72. includes/sanitizers/class-amp-block-sanitizer.php +137 -0
  73. includes/sanitizers/class-amp-core-theme-sanitizer.php +1151 -0
  74. includes/sanitizers/class-amp-embed-sanitizer.php +47 -0
  75. includes/sanitizers/class-amp-form-sanitizer.php +15 -0
  76. includes/sanitizers/class-amp-gallery-block-sanitizer.php +207 -0
  77. includes/sanitizers/class-amp-iframe-sanitizer.php +32 -34
  78. includes/sanitizers/class-amp-img-sanitizer.php +92 -12
  79. includes/sanitizers/class-amp-o2-player-sanitizer.php +140 -0
  80. includes/sanitizers/class-amp-playbuzz-sanitizer.php +14 -5
  81. includes/sanitizers/class-amp-rule-spec.php +13 -28
  82. includes/sanitizers/class-amp-script-sanitizer.php +49 -0
  83. includes/sanitizers/class-amp-style-sanitizer.php +1402 -240
amp.php CHANGED
@@ -1,11 +1,11 @@
1
  <?php
2
  /**
3
  * Plugin Name: AMP
4
- * Description: Add AMP support to your WordPress site.
5
- * Plugin URI: https://github.com/automattic/amp-wp
6
  * Author: WordPress.com VIP, XWP, Google, and contributors
7
- * Author URI: https://github.com/Automattic/amp-wp/graphs/contributors
8
- * Version: 0.7.2
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
@@ -21,18 +21,80 @@
21
  function _amp_print_php_version_admin_notice() {
22
  ?>
23
  <div class="notice notice-error">
24
- <p><?php esc_html_e( 'The AMP plugin requires PHP 5.3+. Please contact your host to update your PHP version.', 'amp' ); ?></p>
25
- </div>
26
  <?php
27
  }
28
- if ( version_compare( phpversion(), '5.3', '<' ) ) {
29
  add_action( 'admin_notices', '_amp_print_php_version_admin_notice' );
30
  return;
31
  }
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  define( 'AMP__FILE__', __FILE__ );
34
  define( 'AMP__DIR__', dirname( __FILE__ ) );
35
- define( 'AMP__VERSION', '0.7.2' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  require_once AMP__DIR__ . '/includes/class-amp-autoloader.php';
38
  AMP_Autoloader::register();
@@ -42,6 +104,12 @@ require_once AMP__DIR__ . '/includes/amp-helper-functions.php';
42
  require_once AMP__DIR__ . '/includes/admin/functions.php';
43
 
44
  register_activation_hook( __FILE__, 'amp_activate' );
 
 
 
 
 
 
45
  function amp_activate() {
46
  amp_after_setup_theme();
47
  if ( ! did_action( 'amp_init' ) ) {
@@ -51,8 +119,14 @@ function amp_activate() {
51
  }
52
 
53
  register_deactivation_hook( __FILE__, 'amp_deactivate' );
 
 
 
 
 
 
54
  function amp_deactivate() {
55
- // We need to manually remove the amp endpoint
56
  global $wp_rewrite;
57
  foreach ( $wp_rewrite->endpoints as $index => $endpoint ) {
58
  if ( amp_get_slug() === $endpoint[1] ) {
@@ -73,6 +147,9 @@ add_action( 'wp_default_scripts', 'amp_register_default_scripts' );
73
  // Ensure async and custom-element/custom-template attributes are present on script tags.
74
  add_filter( 'script_loader_tag', 'amp_filter_script_loader_tag', PHP_INT_MAX, 2 );
75
 
 
 
 
76
  /**
77
  * Set up AMP.
78
  *
@@ -84,6 +161,13 @@ add_filter( 'script_loader_tag', 'amp_filter_script_loader_tag', PHP_INT_MAX, 2
84
  function amp_after_setup_theme() {
85
  amp_get_slug(); // Ensure AMP_QUERY_VAR is set.
86
 
 
 
 
 
 
 
 
87
  if ( false === apply_filters( 'amp_is_enabled', true ) ) {
88
  return;
89
  }
@@ -106,16 +190,27 @@ function amp_init() {
106
  */
107
  do_action( 'amp_init' );
108
 
109
- load_plugin_textdomain( 'amp', false, plugin_basename( AMP__DIR__ ) . '/languages' );
110
-
111
  add_rewrite_endpoint( amp_get_slug(), EP_PERMALINK );
112
 
 
 
 
 
113
  AMP_Theme_Support::init();
114
- AMP_Post_Type_Support::add_post_type_support();
 
 
 
 
 
 
115
  add_filter( 'request', 'amp_force_query_var_value' );
116
  add_action( 'admin_init', 'AMP_Options_Manager::register_settings' );
 
117
  add_action( 'wp_loaded', 'amp_post_meta_box' );
 
118
  add_action( 'wp_loaded', 'amp_add_options_menu' );
 
119
  add_action( 'parse_query', 'amp_correct_query_when_is_front_page' );
120
 
121
  // Redirect the old url of amp page to the updated url.
@@ -127,10 +222,36 @@ function amp_init() {
127
 
128
  // Add actions for legacy post templates.
129
  add_action( 'wp', 'amp_maybe_add_actions' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  }
131
 
132
- // Make sure the `amp` query var has an explicit value.
133
- // Avoids issues when filtering the deprecated `query_string` hook.
 
 
 
 
 
 
 
 
134
  function amp_force_query_var_value( $query_vars ) {
135
  if ( isset( $query_vars[ amp_get_slug() ] ) && '' === $query_vars[ amp_get_slug() ] ) {
136
  $query_vars[ amp_get_slug() ] = 1;
@@ -144,6 +265,7 @@ function amp_force_query_var_value( $query_vars ) {
144
  * If the request is for an AMP page and this is in 'canonical mode,' redirect to the non-AMP page.
145
  * It won't need this plugin's template system, nor the frontend actions like the 'rel' link.
146
  *
 
147
  * @global WP_Query $wp_query
148
  * @since 0.2
149
  * @return void
@@ -151,7 +273,7 @@ function amp_force_query_var_value( $query_vars ) {
151
  function amp_maybe_add_actions() {
152
 
153
  // Short-circuit when theme supports AMP, as everything is handled by AMP_Theme_Support.
154
- if ( current_theme_supports( 'amp' ) ) {
155
  return;
156
  }
157
 
@@ -225,52 +347,111 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) {
225
  }
226
 
227
  /**
228
- * Whether this is in 'canonical mode.'
 
 
229
  *
230
- * Themes can register support for this with `add_theme_support( 'amp' )`.
231
- * Then, this will change the plugin from 'paired mode,' and it won't use its own templates.
232
- * Nor output frontend markup like the 'rel' link. If the theme registers support for AMP with:
233
- * `add_theme_support( 'amp', array( 'template_dir' => 'my-amp-templates' ) )`
234
- * it will retain 'paired mode.
235
  *
236
- * @return boolean Whether this is in AMP 'canonical mode'.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  */
238
  function amp_is_canonical() {
239
- $support = get_theme_support( 'amp' );
240
- if ( true === $support ) {
241
- return true;
242
  }
243
- if ( is_array( $support ) ) {
244
- $args = array_shift( $support );
245
- if ( empty( $args['template_dir'] ) ) {
246
- return true;
247
- }
248
  }
249
- return false;
 
 
250
  }
251
 
 
 
 
 
 
 
252
  function amp_load_classes() {
253
  _deprecated_function( __FUNCTION__, '0.6' );
254
  }
255
 
 
 
 
 
 
256
  function amp_add_frontend_actions() {
257
- require_once AMP__DIR__ . '/includes/amp-frontend-actions.php';
258
  }
259
 
 
 
 
 
 
 
260
  function amp_add_post_template_actions() {
261
  require_once AMP__DIR__ . '/includes/amp-post-template-actions.php';
262
  require_once AMP__DIR__ . '/includes/amp-post-template-functions.php';
263
  amp_post_template_init_hooks();
264
  }
265
 
 
 
 
 
 
 
 
266
  function amp_prepare_render() {
267
- add_action( 'template_redirect', 'amp_render' );
268
  }
269
 
270
  /**
271
  * Render AMP for queried post.
272
  *
273
  * @since 0.1
 
274
  */
275
  function amp_render() {
276
  // Note that queried object is used instead of the ID so that the_preview for the queried post can apply.
@@ -285,6 +466,8 @@ function amp_render() {
285
  * Render AMP post template.
286
  *
287
  * @since 0.5
 
 
288
  * @param WP_Post|int $post Post.
289
  * @global WP_Query $wp_query
290
  */
@@ -317,6 +500,8 @@ function amp_render_post( $post ) {
317
  /**
318
  * Fires before rendering a post in AMP.
319
  *
 
 
320
  * @since 0.2
321
  *
322
  * @param int $post_id Post ID.
@@ -338,7 +523,7 @@ function amp_render_post( $post ) {
338
  * Uses the priority of 12 for the 'after_setup_theme' action.
339
  * Many themes run `add_theme_support()` on the 'after_setup_theme' hook, at the default priority of 10.
340
  * And that function's documentation suggests adding it to that action.
341
- * So this enables themes to `add_theme_support( 'amp' )`.
342
  * And `amp_init_customizer()` will be able to recognize theme support by calling `amp_is_canonical()`.
343
  *
344
  * @since 0.4
@@ -350,16 +535,23 @@ add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 ); // Should be hoo
350
 
351
  /**
352
  * Redirects the old AMP URL to the new AMP URL.
 
353
  * If post slug is updated the amp page with old post slug will be redirected to the updated url.
354
  *
355
- * @param string $link New URL of the post.
 
356
  *
357
- * @return string $link URL to be redirected.
 
358
  */
359
  function amp_redirect_old_slug_to_new_url( $link ) {
360
 
361
- if ( is_amp_endpoint() ) {
362
- $link = trailingslashit( trailingslashit( $link ) . amp_get_slug() );
 
 
 
 
363
  }
364
 
365
  return $link;
1
  <?php
2
  /**
3
  * Plugin Name: AMP
4
+ * Description: Enable AMP on your WordPress site, the WordPress way.
5
+ * Plugin URI: https://amp-wp.org
6
  * Author: WordPress.com VIP, XWP, Google, and contributors
7
+ * Author URI: https://github.com/ampproject/amp-wp/graphs/contributors
8
+ * Version: 1.0.0
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
21
  function _amp_print_php_version_admin_notice() {
22
  ?>
23
  <div class="notice notice-error">
24
+ <p><?php esc_html_e( 'The AMP plugin requires PHP 5.3+. Please contact your host to update your PHP version.', 'amp' ); ?></p>
25
+ </div>
26
  <?php
27
  }
28
+ if ( version_compare( phpversion(), '5.3.6', '<' ) ) {
29
  add_action( 'admin_notices', '_amp_print_php_version_admin_notice' );
30
  return;
31
  }
32
 
33
+ /**
34
+ * Print admin notice regarding DOM extension is not installed.
35
+ *
36
+ * @since 1.1
37
+ */
38
+ function _amp_print_php_dom_document_notice() {
39
+ ?>
40
+ <div class="notice notice-error">
41
+ <p><?php esc_html_e( 'The AMP plugin requires DOM extension in PHP. Please contact your host to install DOM extension.', 'amp' ); ?></p>
42
+ </div>
43
+ <?php
44
+ }
45
+ if ( ! class_exists( 'DOMDocument' ) ) {
46
+ add_action( 'admin_notices', '_amp_print_php_dom_document_notice' );
47
+ return;
48
+ }
49
+
50
+ /**
51
+ * Print admin notice when composer install has not been performed.
52
+ *
53
+ * @since 1.0
54
+ */
55
+ function _amp_print_composer_install_admin_notice() {
56
+ ?>
57
+ <div class="notice notice-error">
58
+ <p><?php esc_html_e( 'You appear to be running the AMP plugin from source. Please do `composer install` to finish installation.', 'amp' ); ?></p>
59
+ </div>
60
+ <?php
61
+ }
62
+ if ( ! file_exists( __DIR__ . '/vendor/autoload.php' ) || ! file_exists( __DIR__ . '/vendor/sabberworm/php-css-parser' ) ) {
63
+ add_action( 'admin_notices', '_amp_print_composer_install_admin_notice' );
64
+ return;
65
+ }
66
+
67
  define( 'AMP__FILE__', __FILE__ );
68
  define( 'AMP__DIR__', dirname( __FILE__ ) );
69
+ define( 'AMP__VERSION', '1.0.0' );
70
+
71
+ /**
72
+ * Print admin notice if plugin installed with incorrect slug (which impacts WordPress's auto-update system).
73
+ *
74
+ * @since 1.0
75
+ */
76
+ function _amp_incorrect_plugin_slug_admin_notice() {
77
+ $actual_slug = basename( AMP__DIR__ );
78
+ ?>
79
+ <div class="notice notice-warning">
80
+ <p>
81
+ <?php
82
+ echo wp_kses_post(
83
+ sprintf(
84
+ /* translators: %1$s is the current directory name, and %2$s is the required directory name */
85
+ __( 'You appear to have installed the AMP plugin incorrectly. It is currently installed in the <code>%1$s</code> directory, but it needs to be placed in a directory named <code>%2$s</code>. Please rename the directory. This is important for WordPress plugin auto-updates.', 'amp' ),
86
+ $actual_slug,
87
+ 'amp'
88
+ )
89
+ );
90
+ ?>
91
+ </p>
92
+ </div>
93
+ <?php
94
+ }
95
+ if ( 'amp' !== basename( AMP__DIR__ ) ) {
96
+ add_action( 'admin_notices', '_amp_incorrect_plugin_slug_admin_notice' );
97
+ }
98
 
99
  require_once AMP__DIR__ . '/includes/class-amp-autoloader.php';
100
  AMP_Autoloader::register();
104
  require_once AMP__DIR__ . '/includes/admin/functions.php';
105
 
106
  register_activation_hook( __FILE__, 'amp_activate' );
107
+
108
+ /**
109
+ * Handle activation of plugin.
110
+ *
111
+ * @since 0.2
112
+ */
113
  function amp_activate() {
114
  amp_after_setup_theme();
115
  if ( ! did_action( 'amp_init' ) ) {
119
  }
120
 
121
  register_deactivation_hook( __FILE__, 'amp_deactivate' );
122
+
123
+ /**
124
+ * Handle deactivation of plugin.
125
+ *
126
+ * @since 0.2
127
+ */
128
  function amp_deactivate() {
129
+ // We need to manually remove the amp endpoint.
130
  global $wp_rewrite;
131
  foreach ( $wp_rewrite->endpoints as $index => $endpoint ) {
132
  if ( amp_get_slug() === $endpoint[1] ) {
147
  // Ensure async and custom-element/custom-template attributes are present on script tags.
148
  add_filter( 'script_loader_tag', 'amp_filter_script_loader_tag', PHP_INT_MAX, 2 );
149
 
150
+ // Ensure crossorigin=anonymous is added to font links.
151
+ add_filter( 'style_loader_tag', 'amp_filter_font_style_loader_tag_with_crossorigin_anonymous', 10, 4 );
152
+
153
  /**
154
  * Set up AMP.
155
  *
161
  function amp_after_setup_theme() {
162
  amp_get_slug(); // Ensure AMP_QUERY_VAR is set.
163
 
164
+ /**
165
+ * Filters whether AMP is enabled on the current site.
166
+ *
167
+ * Useful if the plugin is network activated and you want to turn it off on select sites.
168
+ *
169
+ * @since 0.2
170
+ */
171
  if ( false === apply_filters( 'amp_is_enabled', true ) ) {
172
  return;
173
  }
190
  */
191
  do_action( 'amp_init' );
192
 
 
 
193
  add_rewrite_endpoint( amp_get_slug(), EP_PERMALINK );
194
 
195
+ add_filter( 'allowed_redirect_hosts', array( 'AMP_HTTP', 'filter_allowed_redirect_hosts' ) );
196
+ AMP_HTTP::purge_amp_query_vars();
197
+ AMP_HTTP::send_cors_headers();
198
+ AMP_HTTP::handle_xhr_request();
199
  AMP_Theme_Support::init();
200
+ AMP_Validation_Manager::init();
201
+ add_action( 'init', array( 'AMP_Post_Type_Support', 'add_post_type_support' ), 1000 ); // After post types have been defined.
202
+
203
+ if ( defined( 'WP_CLI' ) ) {
204
+ WP_CLI::add_command( 'amp', new AMP_CLI() );
205
+ }
206
+
207
  add_filter( 'request', 'amp_force_query_var_value' );
208
  add_action( 'admin_init', 'AMP_Options_Manager::register_settings' );
209
+ add_action( 'wp_loaded', 'amp_editor_core_blocks' );
210
  add_action( 'wp_loaded', 'amp_post_meta_box' );
211
+ add_action( 'wp_loaded', 'amp_editor_core_blocks' );
212
  add_action( 'wp_loaded', 'amp_add_options_menu' );
213
+ add_action( 'wp_loaded', 'amp_admin_pointer' );
214
  add_action( 'parse_query', 'amp_correct_query_when_is_front_page' );
215
 
216
  // Redirect the old url of amp page to the updated url.
222
 
223
  // Add actions for legacy post templates.
224
  add_action( 'wp', 'amp_maybe_add_actions' );
225
+
226
+ /*
227
+ * Broadcast plugin updates.
228
+ * Note that AMP_Options_Manager::get_option( 'version', '0.0' ) cannot be used because
229
+ * version was new option added, and in that case default would never be used for a site
230
+ * upgrading from a version prior to 1.0. So this is why get_option() is currently used.
231
+ */
232
+ $options = get_option( AMP_Options_Manager::OPTION_NAME, array() );
233
+ $old_version = isset( $options['version'] ) ? $options['version'] : '0.0';
234
+ if ( AMP__VERSION !== $old_version ) {
235
+ /**
236
+ * Triggers when after amp_init when the plugin version has updated.
237
+ *
238
+ * @param string $old_version Old version.
239
+ */
240
+ do_action( 'amp_plugin_update', $old_version );
241
+ AMP_Options_Manager::update_option( 'version', AMP__VERSION );
242
+ }
243
  }
244
 
245
+ /**
246
+ * Make sure the `amp` query var has an explicit value.
247
+ *
248
+ * This avoids issues when filtering the deprecated `query_string` hook.
249
+ *
250
+ * @since 0.3.3
251
+ *
252
+ * @param array $query_vars Query vars.
253
+ * @return array Query vars.
254
+ */
255
  function amp_force_query_var_value( $query_vars ) {
256
  if ( isset( $query_vars[ amp_get_slug() ] ) && '' === $query_vars[ amp_get_slug() ] ) {
257
  $query_vars[ amp_get_slug() ] = 1;
265
  * If the request is for an AMP page and this is in 'canonical mode,' redirect to the non-AMP page.
266
  * It won't need this plugin's template system, nor the frontend actions like the 'rel' link.
267
  *
268
+ * @deprecated This function is not used when 'amp' theme support is added.
269
  * @global WP_Query $wp_query
270
  * @since 0.2
271
  * @return void
273
  function amp_maybe_add_actions() {
274
 
275
  // Short-circuit when theme supports AMP, as everything is handled by AMP_Theme_Support.
276
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
277
  return;
278
  }
279
 
347
  }
348
 
349
  /**
350
+ * Whether this is in 'canonical mode'.
351
+ *
352
+ * Themes can register support for this with `add_theme_support( AMP_Theme_Support::SLUG )`:
353
  *
354
+ * add_theme_support( AMP_Theme_Support::SLUG );
 
 
 
 
355
  *
356
+ * This will serve templates in native AMP, allowing you to use AMP components in your theme templates.
357
+ * If you want to make available in paired mode, where templates are served in AMP or non-AMP documents, do:
358
+ *
359
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
360
+ * 'paired' => true,
361
+ * ) );
362
+ *
363
+ * Paired mode is also implied if you define a template_dir:
364
+ *
365
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
366
+ * 'template_dir' => 'amp',
367
+ * ) );
368
+ *
369
+ * If you want to have AMP-specific templates in addition to serving native AMP, do:
370
+ *
371
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
372
+ * 'paired' => false,
373
+ * 'template_dir' => 'amp',
374
+ * ) );
375
+ *
376
+ * If you want to force AMP to always be served on a given template, you can use the templates_supported arg,
377
+ * for example to always serve the Category template in AMP:
378
+ *
379
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
380
+ * 'templates_supported' => array(
381
+ * 'is_category' => true,
382
+ * ),
383
+ * ) );
384
+ *
385
+ * Or if you want to force AMP to be used on all templates:
386
+ *
387
+ * add_theme_support( AMP_Theme_Support::SLUG, array(
388
+ * 'templates_supported' => 'all',
389
+ * ) );
390
+ *
391
+ * @see AMP_Theme_Support::read_theme_support()
392
+ * @return boolean Whether this is in AMP 'canonical' mode, that is whether it is native and there is not separate AMP URL current URL.
393
  */
394
  function amp_is_canonical() {
395
+ if ( ! current_theme_supports( AMP_Theme_Support::SLUG ) ) {
396
+ return false;
 
397
  }
398
+
399
+ $args = AMP_Theme_Support::get_theme_support_args();
400
+ if ( isset( $args['paired'] ) ) {
401
+ return empty( $args['paired'] );
 
402
  }
403
+
404
+ // If there is a template_dir, then paired mode is implied.
405
+ return empty( $args['template_dir'] );
406
  }
407
 
408
+ /**
409
+ * Load classes.
410
+ *
411
+ * @since 0.2
412
+ * @deprecated As of 0.6 since autoloading is now employed.
413
+ */
414
  function amp_load_classes() {
415
  _deprecated_function( __FUNCTION__, '0.6' );
416
  }
417
 
418
+ /**
419
+ * Add frontend actions.
420
+ *
421
+ * @since 0.2
422
+ */
423
  function amp_add_frontend_actions() {
424
+ add_action( 'wp_head', 'amp_add_amphtml_link' );
425
  }
426
 
427
+ /**
428
+ * Add post template actions.
429
+ *
430
+ * @since 0.2
431
+ * @deprecated This function is not used when 'amp' theme support is added.
432
+ */
433
  function amp_add_post_template_actions() {
434
  require_once AMP__DIR__ . '/includes/amp-post-template-actions.php';
435
  require_once AMP__DIR__ . '/includes/amp-post-template-functions.php';
436
  amp_post_template_init_hooks();
437
  }
438
 
439
+ /**
440
+ * Add action to do post template rendering at template_redirect action.
441
+ *
442
+ * @since 0.2
443
+ * @since 1.0 The amp_render() function is called at template_redirect action priority 11 instead of priority 10.
444
+ * @deprecated This function is not used when 'amp' theme support is added.
445
+ */
446
  function amp_prepare_render() {
447
+ add_action( 'template_redirect', 'amp_render', 11 );
448
  }
449
 
450
  /**
451
  * Render AMP for queried post.
452
  *
453
  * @since 0.1
454
+ * @deprecated This function is not used when 'amp' theme support is added.
455
  */
456
  function amp_render() {
457
  // Note that queried object is used instead of the ID so that the_preview for the queried post can apply.
466
  * Render AMP post template.
467
  *
468
  * @since 0.5
469
+ * @deprecated This function is not used when 'amp' theme support is added.
470
+ *
471
  * @param WP_Post|int $post Post.
472
  * @global WP_Query $wp_query
473
  */
500
  /**
501
  * Fires before rendering a post in AMP.
502
  *
503
+ * This action is not triggered when 'amp' theme support is present. Instead, you should use 'template_redirect' action and check if `is_amp_endpoint()`.
504
+ *
505
  * @since 0.2
506
  *
507
  * @param int $post_id Post ID.
523
  * Uses the priority of 12 for the 'after_setup_theme' action.
524
  * Many themes run `add_theme_support()` on the 'after_setup_theme' hook, at the default priority of 10.
525
  * And that function's documentation suggests adding it to that action.
526
+ * So this enables themes to `add_theme_support( AMP_Theme_Support::SLUG )`.
527
  * And `amp_init_customizer()` will be able to recognize theme support by calling `amp_is_canonical()`.
528
  *
529
  * @since 0.4
535
 
536
  /**
537
  * Redirects the old AMP URL to the new AMP URL.
538
+ *
539
  * If post slug is updated the amp page with old post slug will be redirected to the updated url.
540
  *
541
+ * @since 0.5
542
+ * @deprecated This function is irrelevant when 'amp' theme support is added.
543
  *
544
+ * @param string $link New URL of the post.
545
+ * @return string URL to be redirected.
546
  */
547
  function amp_redirect_old_slug_to_new_url( $link ) {
548
 
549
+ if ( is_amp_endpoint() && ! amp_is_canonical() ) {
550
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
551
+ $link = add_query_arg( amp_get_slug(), '', $link );
552
+ } else {
553
+ $link = trailingslashit( trailingslashit( $link ) . amp_get_slug() );
554
+ }
555
  }
556
 
557
  return $link;
assets/css/admin-bar.css ADDED
@@ -0,0 +1,1058 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ This is forked from core's admin-bar.css from WP 4.9.6
3
+ - Rules for IE<9 have been removed.
4
+ - References to .hover have been replaced with :focus-within (which is not supported in IE11).
5
+ - A universal selector properties have been removed which interferes with AMP shadow elements.
6
+ */
7
+
8
+
9
+ #wpadminbar * {
10
+ height: auto;
11
+ width: auto;
12
+ margin: 0;
13
+ padding: 0;
14
+ /* Removed because interferes with amp-img>img: position: static; */
15
+ text-shadow: none;
16
+ text-transform: none;
17
+ letter-spacing: normal;
18
+ font-size: 13px;
19
+ font-weight: 400;
20
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
21
+ line-height: 32px;
22
+ border-radius: 0;
23
+ box-sizing: content-box;
24
+ transition: none;
25
+ -webkit-font-smoothing: subpixel-antialiased; /* Prevent Safari from switching to standard antialiasing on hover */
26
+ -moz-osx-font-smoothing: auto; /* Prevent Firefox from inheriting from themes that use other values */
27
+ }
28
+
29
+ .rtl #wpadminbar * {
30
+ font-family: Tahoma, sans-serif;
31
+ }
32
+
33
+ html:lang(he-il) .rtl #wpadminbar * {
34
+ font-family: Arial, sans-serif;
35
+ }
36
+
37
+ #wpadminbar .ab-empty-item {
38
+ cursor: default;
39
+ }
40
+
41
+ #wpadminbar .ab-empty-item,
42
+ #wpadminbar a.ab-item,
43
+ #wpadminbar > #wp-toolbar span.ab-label,
44
+ #wpadminbar > #wp-toolbar span.noticon {
45
+ color: #eee;
46
+ }
47
+
48
+ #wpadminbar #wp-admin-bar-site-name a.ab-item,
49
+ #wpadminbar #wp-admin-bar-my-sites a.ab-item {
50
+ white-space: nowrap;
51
+ overflow: hidden;
52
+ text-overflow: ellipsis;
53
+ }
54
+
55
+ #wpadminbar ul li:before,
56
+ #wpadminbar ul li:after {
57
+ content: normal;
58
+ }
59
+
60
+ #wpadminbar a,
61
+ #wpadminbar a:hover,
62
+ #wpadminbar a img,
63
+ #wpadminbar a img:hover {
64
+ outline: none;
65
+ border: none;
66
+ text-decoration: none;
67
+ background: none;
68
+ }
69
+
70
+ #wpadminbar a:focus,
71
+ #wpadminbar a:active,
72
+ #wpadminbar input[type="text"],
73
+ #wpadminbar input[type="password"],
74
+ #wpadminbar input[type="number"],
75
+ #wpadminbar input[type="search"],
76
+ #wpadminbar input[type="email"],
77
+ #wpadminbar input[type="url"],
78
+ #wpadminbar select,
79
+ #wpadminbar textarea,
80
+ #wpadminbar div {
81
+ box-shadow: none;
82
+ outline: none;
83
+ }
84
+
85
+ #wpadminbar {
86
+ direction: ltr;
87
+ color: #ccc;
88
+ font-size: 13px;
89
+ font-weight: 400;
90
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
91
+ line-height: 32px;
92
+ height: 32px;
93
+ position: fixed;
94
+ top: 0;
95
+ left: 0;
96
+ width: 100%;
97
+ min-width: 600px; /* match the min-width of the body in wp-admin.css */
98
+ z-index: 99999;
99
+ background: #23282d;
100
+ }
101
+
102
+ #wpadminbar .ab-sub-wrapper,
103
+ #wpadminbar ul,
104
+ #wpadminbar ul li {
105
+ background: none;
106
+ clear: none;
107
+ list-style: none;
108
+ margin: 0;
109
+ padding: 0;
110
+ position: relative;
111
+ text-indent: 0;
112
+ z-index: 99999;
113
+ }
114
+
115
+ #wpadminbar ul#wp-admin-bar-root-default>li {
116
+ margin-right: 0;
117
+ }
118
+
119
+ #wpadminbar .quicklinks ul {
120
+ text-align: left;
121
+ }
122
+
123
+ #wpadminbar li {
124
+ float: left;
125
+ }
126
+
127
+ #wpadminbar .ab-empty-item {
128
+ outline: none;
129
+ }
130
+
131
+ #wpadminbar .quicklinks .ab-top-secondary > li {
132
+ float: right;
133
+ }
134
+
135
+ #wpadminbar .quicklinks a,
136
+ #wpadminbar .quicklinks .ab-empty-item,
137
+ #wpadminbar .shortlink-input {
138
+ height: 32px;
139
+ display: block;
140
+ padding: 0 10px;
141
+ margin: 0;
142
+ }
143
+
144
+ #wpadminbar .quicklinks > ul > li > a {
145
+ padding: 0 8px 0 7px;
146
+ }
147
+
148
+ #wpadminbar .menupop .ab-sub-wrapper,
149
+ #wpadminbar .shortlink-input {
150
+ margin: 0;
151
+ padding: 0;
152
+ box-shadow: 0 3px 5px rgba(0,0,0,0.2);
153
+ background: #32373c;
154
+ display: none;
155
+ position: absolute;
156
+ float: none;
157
+ }
158
+
159
+ #wpadminbar .ab-top-menu > .menupop > .ab-sub-wrapper {
160
+ min-width: 100%;
161
+ }
162
+
163
+ #wpadminbar .ab-top-secondary .menupop .ab-sub-wrapper {
164
+ right: 0;
165
+ left: auto;
166
+ }
167
+
168
+ #wpadminbar .ab-submenu {
169
+ padding: 6px 0;
170
+ }
171
+
172
+ #wpadminbar .selected .shortlink-input {
173
+ display: block;
174
+ }
175
+
176
+ #wpadminbar .quicklinks .menupop ul li {
177
+ float: none;
178
+ }
179
+
180
+ #wpadminbar .quicklinks .menupop ul li a strong {
181
+ font-weight: 600;
182
+ }
183
+
184
+ #wpadminbar .quicklinks .menupop ul li .ab-item,
185
+ #wpadminbar .quicklinks .menupop ul li a strong,
186
+ #wpadminbar .quicklinks .menupop:focus-within ul li .ab-item,
187
+ #wpadminbar.nojs .quicklinks .menupop:hover ul li .ab-item,
188
+ #wpadminbar .shortlink-input {
189
+ line-height: 26px;
190
+ height: 26px;
191
+ white-space: nowrap;
192
+ min-width: 140px;
193
+ }
194
+
195
+ #wpadminbar .shortlink-input {
196
+ width: 200px;
197
+ }
198
+
199
+ #wpadminbar.nojs li:hover > .ab-sub-wrapper,
200
+ #wpadminbar li:focus-within > .ab-sub-wrapper {
201
+ display: block;
202
+ }
203
+
204
+ #wpadminbar .menupop li:hover > .ab-sub-wrapper,
205
+ #wpadminbar .menupop li:focus-within > .ab-sub-wrapper {
206
+ margin-left: 100%;
207
+ margin-top: -32px;
208
+ }
209
+
210
+ #wpadminbar .ab-top-secondary .menupop li:hover > .ab-sub-wrapper,
211
+ #wpadminbar .ab-top-secondary .menupop li:focus-within > .ab-sub-wrapper {
212
+ margin-left: 0;
213
+ left: inherit;
214
+ right: 100%;
215
+ }
216
+
217
+ #wpadminbar:not(.mobile) .ab-top-menu > li > .ab-item:focus,
218
+ #wpadminbar.nojq .quicklinks .ab-top-menu > li > .ab-item:focus,
219
+ #wpadminbar:not(.mobile) .ab-top-menu > li:hover > .ab-item,
220
+ #wpadminbar .ab-top-menu > li:focus-within > .ab-item {
221
+ background: #32373c;
222
+ color: #00b9eb;
223
+ }
224
+ #wpadminbar .ab-top-menu > li:focus-within > .ab-item {
225
+ background: #32373c;
226
+ color: #00b9eb;
227
+ }
228
+
229
+ #wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label,
230
+ #wpadminbar > #wp-toolbar li:focus-within span.ab-label,
231
+ #wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label {
232
+ color: #00b9eb;
233
+ }
234
+
235
+ #wpadminbar > #wp-toolbar > #wp-admin-bar-root-default .ab-icon,
236
+ #wpadminbar .ab-icon,
237
+ #wpadminbar .ab-item:before {
238
+ position: relative;
239
+ float: left;
240
+ font: normal 20px/1 dashicons;
241
+ speak: none;
242
+ padding: 4px 0;
243
+ -webkit-font-smoothing: antialiased;
244
+ -moz-osx-font-smoothing: grayscale;
245
+ background-image: none !important;
246
+ margin-right: 6px;
247
+ }
248
+
249
+ #wpadminbar .ab-icon:before,
250
+ #wpadminbar .ab-item:before,
251
+ #wpadminbar #adminbarsearch:before {
252
+ color: #a0a5aa;
253
+ color: rgba(240,245,250,0.6);
254
+ }
255
+
256
+ #wpadminbar .ab-icon:before,
257
+ #wpadminbar .ab-item:before,
258
+ #wpadminbar #adminbarsearch:before {
259
+ position: relative;
260
+ transition: all .1s ease-in-out;
261
+ }
262
+
263
+ #wpadminbar .ab-label {
264
+ display: inline-block;
265
+ height: 32px;
266
+ }
267
+
268
+ #wpadminbar .ab-submenu .ab-item {
269
+ color: #b4b9be;
270
+ color: rgba(240,245,250,0.7);
271
+ }
272
+
273
+ #wpadminbar .quicklinks .menupop ul li a,
274
+ #wpadminbar .quicklinks .menupop ul li a strong,
275
+ #wpadminbar .quicklinks .menupop:focus-within ul li a,
276
+ #wpadminbar.nojs .quicklinks .menupop:hover ul li a {
277
+ color: #b4b9be;
278
+ color: rgba(240,245,250,0.7);
279
+ }
280
+
281
+ #wpadminbar .quicklinks .menupop ul li a:hover,
282
+ #wpadminbar .quicklinks .menupop ul li a:focus,
283
+ #wpadminbar .quicklinks .menupop ul li a:hover strong,
284
+ #wpadminbar .quicklinks .menupop ul li a:focus strong,
285
+ #wpadminbar .quicklinks .ab-sub-wrapper .menupop:focus-within > a,
286
+ #wpadminbar .quicklinks .menupop:focus-within ul li a:hover,
287
+ #wpadminbar .quicklinks .menupop:focus-within ul li a:focus,
288
+ #wpadminbar .quicklinks .menupop:focus-within ul li div[tabindex]:hover,
289
+ #wpadminbar .quicklinks .menupop:focus-within ul li div[tabindex]:focus,
290
+ #wpadminbar.nojs .quicklinks .menupop:hover ul li a:hover,
291
+ #wpadminbar.nojs .quicklinks .menupop:hover ul li a:focus,
292
+ #wpadminbar li:hover .ab-icon:before,
293
+ #wpadminbar li:hover .ab-item:before,
294
+ #wpadminbar li a:focus .ab-icon:before,
295
+ #wpadminbar li .ab-item:focus:before,
296
+ #wpadminbar li .ab-item:focus .ab-icon:before,
297
+ #wpadminbar li:focus-within .ab-icon:before,
298
+ #wpadminbar li:focus-within .ab-item:before,
299
+ #wpadminbar li:hover #adminbarsearch:before,
300
+ #wpadminbar li #adminbarsearch.adminbar-focused:before {
301
+ color: #00b9eb;
302
+ }
303
+
304
+ #wpadminbar.mobile .quicklinks .ab-icon:before,
305
+ #wpadminbar.mobile .quicklinks .ab-item:before {
306
+ color: #b4b9be;
307
+ }
308
+
309
+ #wpadminbar .menupop .menupop > .ab-item:before,
310
+ #wpadminbar .ab-top-secondary .menupop .menupop > .ab-item:before {
311
+ position: absolute;
312
+ font: normal 17px/1 dashicons;
313
+ speak: none;
314
+ -webkit-font-smoothing: antialiased;
315
+ -moz-osx-font-smoothing: grayscale;
316
+ }
317
+
318
+ #wpadminbar .menupop .menupop > .ab-item {
319
+ display: block;
320
+ padding-right: 2em;
321
+ }
322
+
323
+ #wpadminbar .menupop .menupop > .ab-item:before {
324
+ top: 1px;
325
+ right: 4px;
326
+ content: "\f139";
327
+ color: inherit;
328
+ }
329
+
330
+ #wpadminbar .ab-top-secondary .menupop .menupop > .ab-item {
331
+ padding-left: 2em;
332
+ padding-right: 1em;
333
+ }
334
+
335
+ #wpadminbar .ab-top-secondary .menupop .menupop > .ab-item:before {
336
+ top: 1px;
337
+ left: 6px;
338
+ content: "\f141";
339
+ }
340
+
341
+ #wpadminbar .quicklinks .menupop ul.ab-sub-secondary {
342
+ display: block;
343
+ position: relative;
344
+ right: auto;
345
+ margin: 0;
346
+ box-shadow: none;
347
+ }
348
+
349
+ #wpadminbar .quicklinks .menupop ul.ab-sub-secondary,
350
+ #wpadminbar .quicklinks .menupop ul.ab-sub-secondary .ab-submenu {
351
+ background: #464b50;
352
+ }
353
+
354
+ #wpadminbar .quicklinks .menupop .ab-sub-secondary > li > a:hover,
355
+ #wpadminbar .quicklinks .menupop .ab-sub-secondary > li .ab-item:focus a {
356
+ color: #00b9eb;
357
+ }
358
+
359
+ #wpadminbar .quicklinks a span#ab-updates {
360
+ background: #eee;
361
+ color: #32373c;
362
+ display: inline;
363
+ padding: 2px 5px;
364
+ font-size: 10px;
365
+ font-weight: 600;
366
+ border-radius: 10px;
367
+ }
368
+
369
+ #wpadminbar .quicklinks a:hover span#ab-updates {
370
+ background: #fff;
371
+ color: #000;
372
+ }
373
+
374
+ #wpadminbar .ab-top-secondary {
375
+ float: right;
376
+ }
377
+
378
+ #wpadminbar ul li:last-child,
379
+ #wpadminbar ul li:last-child .ab-item {
380
+ box-shadow: none;
381
+ }
382
+
383
+ /**
384
+ * My Account
385
+ */
386
+ #wp-admin-bar-my-account > ul {
387
+ min-width: 198px;
388
+ }
389
+
390
+ #wp-admin-bar-my-account > .ab-item:before {
391
+ content: "\f110";
392
+ top: 2px;
393
+ float: right;
394
+ margin-left: 6px;
395
+ margin-right: 0;
396
+ }
397
+
398
+ #wp-admin-bar-my-account.with-avatar > .ab-item:before {
399
+ display: none;
400
+ content: none;
401
+ }
402
+
403
+ #wp-admin-bar-my-account.with-avatar > ul {
404
+ min-width: 270px;
405
+ }
406
+
407
+ #wpadminbar #wp-admin-bar-user-actions > li {
408
+ margin-left: 16px;
409
+ margin-right: 16px;
410
+ }
411
+
412
+ #wpadminbar #wp-admin-bar-user-actions.ab-submenu {
413
+ padding: 6px 0 12px;
414
+ }
415
+
416
+ #wpadminbar #wp-admin-bar-my-account.with-avatar #wp-admin-bar-user-actions > li {
417
+ margin-left: 88px;
418
+ }
419
+
420
+ #wpadminbar #wp-admin-bar-user-info {
421
+ margin-top: 6px;
422
+ margin-bottom: 15px;
423
+ height: auto;
424
+ background: none;
425
+ }
426
+
427
+ #wp-admin-bar-user-info .avatar {
428
+ /* TODO: The amp-img>img does not get loaded since the container is initially hidden, and :hover does not trigger a re-calc. Resizing the window does, however. */
429
+ position: absolute;
430
+ left: -72px;
431
+ top: 4px;
432
+ width: 64px;
433
+ height: 64px;
434
+ }
435
+
436
+ #wpadminbar #wp-admin-bar-user-info a {
437
+ background: none;
438
+ height: auto;
439
+ }
440
+
441
+ #wpadminbar #wp-admin-bar-user-info span {
442
+ background: none;
443
+ padding: 0;
444
+ height: 18px;
445
+ }
446
+
447
+ #wpadminbar #wp-admin-bar-user-info .display-name,
448
+ #wpadminbar #wp-admin-bar-user-info .username {
449
+ display: block;
450
+ }
451
+
452
+ #wpadminbar #wp-admin-bar-user-info .username {
453
+ color: #a0a5aa;
454
+ font-size: 11px;
455
+ }
456
+
457
+ #wpadminbar #wp-admin-bar-my-account.with-avatar > .ab-empty-item img,
458
+ #wpadminbar #wp-admin-bar-my-account.with-avatar > a img {
459
+ width: 16px; /* Was auto. */
460
+ height: 16px;
461
+ padding: 0;
462
+ border: 1px solid #82878c;
463
+ background: #eee;
464
+ line-height: 24px;
465
+ vertical-align: middle;
466
+ margin: -4px 0 0 6px;
467
+ float: none;
468
+ display: inline-block; /* Was inline. */
469
+ }
470
+
471
+ /**
472
+ * WP Logo
473
+ */
474
+ #wpadminbar #wp-admin-bar-wp-logo > .ab-item .ab-icon {
475
+ width: 15px;
476
+ height: 20px;
477
+ margin-right: 0;
478
+ padding: 6px 0 5px;
479
+ }
480
+
481
+ #wpadminbar #wp-admin-bar-wp-logo > .ab-item {
482
+ padding: 0 7px;
483
+ }
484
+
485
+ #wpadminbar #wp-admin-bar-wp-logo > .ab-item .ab-icon:before {
486
+ content: "\f120";
487
+ top: 2px;
488
+ }
489
+
490
+ /*
491
+ * My Sites & Site Title
492
+ */
493
+ #wpadminbar .quicklinks li .blavatar {
494
+ float: left;
495
+ font: normal 16px/1 dashicons !important;
496
+ speak: none;
497
+ -webkit-font-smoothing: antialiased;
498
+ -moz-osx-font-smoothing: grayscale;
499
+ color: #eee;
500
+ }
501
+
502
+ #wpadminbar .quicklinks li a:hover .blavatar,
503
+ #wpadminbar .quicklinks li a:focus .blavatar,
504
+ #wpadminbar .quicklinks .ab-sub-wrapper .menupop:focus-within > a .blavatar {
505
+ color: #00b9eb;
506
+ }
507
+
508
+ #wpadminbar .quicklinks li .blavatar:before {
509
+ content: "\f120";
510
+ height: 16px;
511
+ width: 16px;
512
+ display: inline-block;
513
+ margin: 6px 8px 0 -2px;
514
+ }
515
+
516
+ #wpadminbar #wp-admin-bar-appearance {
517
+ margin-top: -12px;
518
+ }
519
+
520
+ #wpadminbar #wp-admin-bar-my-sites > .ab-item:before,
521
+ #wpadminbar #wp-admin-bar-site-name > .ab-item:before {
522
+ content: "\f541";
523
+ top: 2px;
524
+ }
525
+
526
+ #wpadminbar #wp-admin-bar-customize > .ab-item:before {
527
+ content: "\f540";
528
+ top: 2px;
529
+ }
530
+
531
+
532
+ #wpadminbar #wp-admin-bar-edit > .ab-item:before {
533
+ content: "\f464";
534
+ top: 2px;
535
+ }
536
+
537
+ #wpadminbar #wp-admin-bar-site-name > .ab-item:before {
538
+ content: "\f226";
539
+ }
540
+
541
+ .wp-admin #wpadminbar #wp-admin-bar-site-name > .ab-item:before {
542
+ content: "\f102";
543
+ }
544
+
545
+
546
+
547
+ /**
548
+ * Comments
549
+ */
550
+ #wpadminbar #wp-admin-bar-comments .ab-icon {
551
+ margin-right: 6px;
552
+ }
553
+
554
+ #wpadminbar #wp-admin-bar-comments .ab-icon:before {
555
+ content: "\f101";
556
+ top: 3px;
557
+ }
558
+
559
+ #wpadminbar #wp-admin-bar-comments .count-0 {
560
+ opacity: .5;
561
+ }
562
+
563
+ /**
564
+ * New Content
565
+ */
566
+ #wpadminbar #wp-admin-bar-new-content .ab-icon:before {
567
+ content: "\f132";
568
+ top: 4px;
569
+ }
570
+
571
+ /**
572
+ * Updates
573
+ */
574
+ #wpadminbar #wp-admin-bar-updates .ab-icon:before {
575
+ content: "\f463";
576
+ top: 2px;
577
+ }
578
+
579
+ /**
580
+ * Search
581
+ */
582
+ #wpadminbar #wp-admin-bar-search .ab-item {
583
+ padding: 0;
584
+ background: transparent;
585
+ }
586
+
587
+ #wpadminbar #adminbarsearch {
588
+ position: relative;
589
+ height: 32px;
590
+ padding: 0 2px;
591
+ z-index: 1;
592
+ }
593
+
594
+ #wpadminbar #adminbarsearch:before {
595
+ position: absolute;
596
+ top: 6px;
597
+ left: 5px;
598
+ z-index: 20;
599
+ font: normal 20px/1 dashicons !important;
600
+ content: "\f179";
601
+ speak: none;
602
+ -webkit-font-smoothing: antialiased;
603
+ -moz-osx-font-smoothing: grayscale;
604
+ }
605
+
606
+ /* The admin bar search field needs to reset many styles that might be inherited from the active Theme CSS. See ticket #40313. */
607
+ #wpadminbar > #wp-toolbar > #wp-admin-bar-top-secondary > #wp-admin-bar-search #adminbarsearch input.adminbar-input {
608
+ display: inline-block;
609
+ float: none;
610
+ position: relative;
611
+ z-index: 30;
612
+ font-size: 13px;
613
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
614
+ line-height: 24px;
615
+ text-indent: 0;
616
+ height: 24px;
617
+ width: 24px;
618
+ max-width: none;
619
+ padding: 0 3px 0 24px;
620
+ margin: 0;
621
+ color: #ccc;
622
+ background-color: rgba( 255, 255, 255, 0 );
623
+ border: none;
624
+ outline: none;
625
+ cursor: pointer;
626
+ box-shadow: none;
627
+ box-sizing: border-box;
628
+ transition-duration: 400ms;
629
+ transition-property: width, background;
630
+ transition-timing-function: ease;
631
+ }
632
+
633
+ #wpadminbar > #wp-toolbar > #wp-admin-bar-top-secondary > #wp-admin-bar-search #adminbarsearch input.adminbar-input:focus {
634
+ z-index: 10;
635
+ color: #000;
636
+ width: 200px;
637
+ background-color: rgba( 255, 255, 255, 0.9 );
638
+ cursor: text;
639
+ border: 0;
640
+ }
641
+
642
+ /* Removed IE hacks. */
643
+
644
+ #wpadminbar #adminbarsearch .adminbar-button {
645
+ display: none;
646
+ }
647
+
648
+ /**
649
+ * Customize support classes
650
+ */
651
+ .no-customize-support .hide-if-no-customize,
652
+ .customize-support .hide-if-customize,
653
+ .no-customize-support #wpadminbar .hide-if-no-customize,
654
+ .no-customize-support.wp-core-ui .hide-if-no-customize,
655
+ .no-customize-support .wp-core-ui .hide-if-no-customize,
656
+ .customize-support #wpadminbar .hide-if-customize,
657
+ .customize-support.wp-core-ui .hide-if-customize,
658
+ .customize-support .wp-core-ui .hide-if-customize {
659
+ display: none;
660
+ }
661
+
662
+ /* Skip link */
663
+ #wpadminbar .screen-reader-text,
664
+ #wpadminbar .screen-reader-text span {
665
+ border: 0;
666
+ clip: rect(1px, 1px, 1px, 1px);
667
+ -webkit-clip-path: inset(50%);
668
+ clip-path: inset(50%);
669
+ height: 1px;
670
+ margin: -1px;
671
+ overflow: hidden;
672
+ padding: 0;
673
+ position: absolute;
674
+ width: 1px;
675
+ word-wrap: normal !important;
676
+ }
677
+
678
+ #wpadminbar .screen-reader-shortcut {
679
+ position: absolute;
680
+ top: -1000em;
681
+ }
682
+
683
+ #wpadminbar .screen-reader-shortcut:focus {
684
+ left: 6px;
685
+ top: 7px;
686
+ height: auto;
687
+ width: auto;
688
+ display: block;
689
+ font-size: 14px;
690
+ font-weight: 600;
691
+ padding: 15px 23px 14px;
692
+ background: #f1f1f1;
693
+ color: #0073aa;
694
+ z-index: 100000;
695
+ line-height: normal;
696
+ text-decoration: none;
697
+ box-shadow: 0 0 2px 2px rgba(0,0,0,.6);
698
+ }
699
+
700
+ /**
701
+ * Removed IE 6-targeted rules
702
+ */
703
+
704
+ /* Removed No @font-face support */
705
+
706
+ @media screen and ( max-width: 782px ) {
707
+ /* Toolbar Touchification*/
708
+ html #wpadminbar {
709
+ height: 46px;
710
+ min-width: 300px;
711
+ }
712
+
713
+ #wpadminbar * {
714
+ font-size: 14px;
715
+ font-weight: 400;
716
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
717
+ line-height: 32px;
718
+ }
719
+
720
+ #wpadminbar .quicklinks > ul > li > a,
721
+ #wpadminbar .quicklinks .ab-empty-item {
722
+ padding: 0;
723
+ height: 46px;
724
+ line-height: 46px;
725
+ width: auto;
726
+ }
727
+
728
+ #wpadminbar .ab-icon {
729
+ font: 40px/1 dashicons !important;
730
+ margin: 0;
731
+ padding: 0;
732
+ width: 52px;
733
+ height: 46px;
734
+ text-align: center;
735
+ }
736
+
737
+ #wpadminbar .ab-icon:before {
738
+ text-align: center;
739
+ }
740
+
741
+ #wpadminbar .ab-submenu {
742
+ padding: 0;
743
+ }
744
+
745
+ #wpadminbar #wp-admin-bar-site-name a.ab-item,
746
+ #wpadminbar #wp-admin-bar-my-sites a.ab-item,
747
+ #wpadminbar #wp-admin-bar-my-account a.ab-item {
748
+ text-overflow: clip;
749
+ }
750
+
751
+ #wpadminbar .ab-label {
752
+ display: none;
753
+ }
754
+
755
+ #wpadminbar .menupop li:hover > .ab-sub-wrapper,
756
+ #wpadminbar .menupop li:focus-within > .ab-sub-wrapper {
757
+ margin-top: -46px;
758
+ }
759
+
760
+ #wpadminbar .ab-top-menu .menupop .ab-sub-wrapper .menupop > .ab-item {
761
+ padding-right: 30px;
762
+ }
763
+
764
+ #wpadminbar .menupop .menupop > .ab-item:before {
765
+ top: 10px;
766
+ right: 6px;
767
+ }
768
+
769
+ #wpadminbar .ab-top-menu > .menupop > .ab-sub-wrapper .ab-item {
770
+ font-size: 16px;
771
+ padding: 8px 16px;
772
+ }
773
+
774
+ #wpadminbar .ab-top-menu > .menupop > .ab-sub-wrapper a:empty {
775
+ display: none;
776
+ }
777
+
778
+ /* WP logo */
779
+ #wpadminbar #wp-admin-bar-wp-logo > .ab-item {
780
+ padding: 0;
781
+ }
782
+
783
+ #wpadminbar #wp-admin-bar-wp-logo > .ab-item .ab-icon {
784
+ padding: 0;
785
+ width: 52px;
786
+ height: 46px;
787
+ text-align: center;
788
+ vertical-align: top;
789
+ }
790
+
791
+ #wpadminbar #wp-admin-bar-wp-logo > .ab-item .ab-icon:before {
792
+ font: 28px/1 dashicons !important;
793
+ top: -3px;
794
+ }
795
+
796
+ #wpadminbar .ab-icon,
797
+ #wpadminbar .ab-item:before {
798
+ padding: 0;
799
+ }
800
+
801
+ /* My Sites and "Site Title" menu */
802
+ #wpadminbar #wp-admin-bar-my-sites > .ab-item,
803
+ #wpadminbar #wp-admin-bar-site-name > .ab-item,
804
+ #wpadminbar #wp-admin-bar-customize > .ab-item,
805
+ #wpadminbar #wp-admin-bar-edit > .ab-item,
806
+ #wpadminbar #wp-admin-bar-my-account > .ab-item {
807
+ text-indent: 100%;
808
+ white-space: nowrap;
809
+ overflow: hidden;
810
+ width: 52px;
811
+ padding: 0;
812
+ color: #a0a5aa; /* @todo not needed? this text is hidden */
813
+ position: relative;
814
+ }
815
+
816
+ #wpadminbar > #wp-toolbar > #wp-admin-bar-root-default .ab-icon,
817
+ #wpadminbar .ab-icon,
818
+ #wpadminbar .ab-item:before {
819
+ padding: 0;
820
+ margin-right: 0;
821
+ }
822
+
823
+ #wpadminbar #wp-admin-bar-edit > .ab-item:before,
824
+ #wpadminbar #wp-admin-bar-my-sites > .ab-item:before,
825
+ #wpadminbar #wp-admin-bar-site-name > .ab-item:before,
826
+ #wpadminbar #wp-admin-bar-customize > .ab-item:before,
827
+ #wpadminbar #wp-admin-bar-my-account > .ab-item:before {
828
+ display: block;
829
+ text-indent: 0;
830
+ font: normal 32px/1 dashicons;
831
+ speak: none;
832
+ top: 7px;
833
+ width: 52px;
834
+ text-align: center;
835
+ -webkit-font-smoothing: antialiased;
836
+ -moz-osx-font-smoothing: grayscale;
837
+ }
838
+
839
+ #wpadminbar #wp-admin-bar-appearance {
840
+ margin-top: 0;
841
+ }
842
+
843
+ #wpadminbar .quicklinks li .blavatar:before {
844
+ display: none;
845
+ }
846
+
847
+ /* Search */
848
+ #wpadminbar #wp-admin-bar-search {
849
+ display: none;
850
+ }
851
+
852
+ /* New Content */
853
+ #wpadminbar #wp-admin-bar-new-content .ab-icon:before {
854
+ top: 0;
855
+ line-height: 53px;
856
+ height: 46px !important;
857
+ text-align: center;
858
+ width: 52px;
859
+ display: block;
860
+ }
861
+
862
+ /* Updates */
863
+ #wpadminbar #wp-admin-bar-updates {
864
+ text-align: center;
865
+ }
866
+
867
+ #wpadminbar #wp-admin-bar-updates .ab-icon:before {
868
+ top: 3px;
869
+ }
870
+
871
+ /* Comments */
872
+ #wpadminbar #wp-admin-bar-comments .ab-icon {
873
+ margin: 0;
874
+ }
875
+
876
+ #wpadminbar #wp-admin-bar-comments .ab-icon:before {
877
+ display: block;
878
+ font-size: 34px;
879
+ height: 46px;
880
+ line-height: 47px;
881
+ top: 0;
882
+ }
883
+
884
+ /* My Account */
885
+ #wpadminbar #wp-admin-bar-my-account > a {
886
+ position: relative;
887
+ white-space: nowrap;
888
+ text-indent: 150%; /* More than 100% indention is needed since this element has padding */
889
+ width: 28px;
890
+ padding: 0 10px;
891
+ overflow: hidden; /* Prevent link text from forcing horizontal scrolling on mobile */
892
+ }
893
+
894
+ #wpadminbar .quicklinks li#wp-admin-bar-my-account.with-avatar > a img {
895
+ position: absolute;
896
+ top: 13px;
897
+ right: 10px;
898
+ width: 26px;
899
+ height: 26px;
900
+ }
901
+
902
+ #wpadminbar #wp-admin-bar-user-actions.ab-submenu {
903
+ padding: 0;
904
+ }
905
+
906
+ #wpadminbar #wp-admin-bar-user-actions.ab-submenu img.avatar {
907
+ display: none;
908
+ }
909
+
910
+ #wpadminbar #wp-admin-bar-my-account.with-avatar #wp-admin-bar-user-actions > li {
911
+ margin: 0;
912
+ }
913
+
914
+ #wpadminbar #wp-admin-bar-user-info .display-name {
915
+ height: auto;
916
+ font-size: 16px;
917
+ line-height: 24px;
918
+ color: #eee;
919
+ }
920
+
921
+ #wpadminbar #wp-admin-bar-user-info a {
922
+ padding-top: 4px;
923
+ }
924
+
925
+ #wpadminbar #wp-admin-bar-user-info .username {
926
+ line-height: 0.8 !important;
927
+ margin-bottom: -2px;
928
+ }
929
+
930
+ /* Show only default top level items */
931
+ #wp-toolbar > ul > li {
932
+ display: none;
933
+ }
934
+
935
+ #wpadminbar li#wp-admin-bar-menu-toggle,
936
+ #wpadminbar li#wp-admin-bar-wp-logo,
937
+ #wpadminbar li#wp-admin-bar-my-sites,
938
+ #wpadminbar li#wp-admin-bar-updates,
939
+ #wpadminbar li#wp-admin-bar-site-name,
940
+ #wpadminbar li#wp-admin-bar-customize,
941
+ #wpadminbar li#wp-admin-bar-new-content,
942
+ #wpadminbar li#wp-admin-bar-edit,
943
+ #wpadminbar li#wp-admin-bar-comments,
944
+ #wpadminbar li#wp-admin-bar-my-account {
945
+ display: block;
946
+ }
947
+
948
+ /* Allow dropdown list items to appear normally */
949
+ #wpadminbar li:hover ul li,
950
+ #wpadminbar li:focus-within ul li,
951
+ #wpadminbar li:hover ul li:hover ul li {
952
+ display: list-item;
953
+ }
954
+
955
+ /* Override default min-width so dropdown lists aren't stretched
956
+ to 100% viewport width at responsive sizes. */
957
+ #wpadminbar .ab-top-menu > .menupop > .ab-sub-wrapper {
958
+ min-width: -webkit-fit-content;
959
+ min-width: -moz-fit-content;
960
+ min-width: fit-content;
961
+ }
962
+
963
+ #wpadminbar ul#wp-admin-bar-root-default > li {
964
+ margin-right: 0;
965
+ }
966
+
967
+ /* Experimental fix for touch toolbar dropdown positioning */
968
+ #wpadminbar .ab-top-menu,
969
+ #wpadminbar .ab-top-secondary,
970
+ #wpadminbar #wp-admin-bar-wp-logo,
971
+ #wpadminbar #wp-admin-bar-my-sites,
972
+ #wpadminbar #wp-admin-bar-site-name,
973
+ #wpadminbar #wp-admin-bar-updates,
974
+ #wpadminbar #wp-admin-bar-comments,
975
+ #wpadminbar #wp-admin-bar-new-content,
976
+ #wpadminbar #wp-admin-bar-edit,
977
+ #wpadminbar #wp-admin-bar-my-account {
978
+ position: static;
979
+ }
980
+
981
+ #wpadminbar #wp-admin-bar-my-account {
982
+ float: right;
983
+ }
984
+
985
+ .network-admin #wpadminbar ul#wp-admin-bar-top-secondary > li#wp-admin-bar-my-account {
986
+ margin-right: 0;
987
+ }
988
+
989
+ /* Realign arrows on taller responsive submenus */
990
+
991
+ #wpadminbar .ab-top-secondary .menupop .menupop > .ab-item:before {
992
+ top: 10px;
993
+ left: 0;
994
+ }
995
+ }
996
+
997
+ /* Smartphone */
998
+ @media screen and (max-width: 600px) {
999
+ /* Removed: #wpadminbar { position: absolute; } */
1000
+
1001
+ #wp-responsive-overlay {
1002
+ position: fixed;
1003
+ top: 0;
1004
+ left: 0;
1005
+ width: 100%;
1006
+ height: 100%;
1007
+ z-index: 400;
1008
+ }
1009
+
1010
+ #wpadminbar .ab-top-menu > .menupop > .ab-sub-wrapper {
1011
+ width: 100%;
1012
+ left: 0;
1013
+ }
1014
+
1015
+ #wpadminbar .menupop .menupop > .ab-item:before {
1016
+ display: none;
1017
+ }
1018
+
1019
+ #wpadminbar #wp-admin-bar-wp-logo.menupop .ab-sub-wrapper {
1020
+ margin-left: 0;
1021
+ }
1022
+
1023
+ #wpadminbar .ab-top-menu > .menupop li > .ab-sub-wrapper {
1024
+ margin: 0;
1025
+ width: 100%;
1026
+ top: auto;
1027
+ left: auto;
1028
+ position: relative;
1029
+ }
1030
+
1031
+ #wpadminbar .ab-top-menu > .menupop li > .ab-sub-wrapper .ab-item {
1032
+ font-size: 16px;
1033
+ padding: 6px 15px 19px 30px;
1034
+ }
1035
+
1036
+ #wpadminbar li:hover ul li ul li {
1037
+ display: list-item;
1038
+ }
1039
+
1040
+ #wpadminbar li#wp-admin-bar-wp-logo,
1041
+ #wpadminbar li#wp-admin-bar-updates {
1042
+ display: none;
1043
+ }
1044
+
1045
+ /* Make submenus full-width at this size */
1046
+
1047
+ #wpadminbar .ab-top-menu > .menupop li > .ab-sub-wrapper {
1048
+ position: static;
1049
+ box-shadow: none;
1050
+ }
1051
+ }
1052
+
1053
+ /* Very narrow screens */
1054
+ @media screen and (max-width: 400px) {
1055
+ #wpadminbar li#wp-admin-bar-comments {
1056
+ display: none;
1057
+ }
1058
+ }
assets/css/admin-tables.css ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .column-error_status .dashicons-editor-help {
2
+ color: #767676;
3
+ }
4
+ .column-sources .dashicons,
5
+ .column-sources_with_invalid_output .dashicons {
6
+ margin-right: 5px;
7
+ }
8
+ .column-source .dashicons-admin-plugins,
9
+ .column-sources_with_invalid_output .dashicons-admin-plugins {
10
+ color: #64a2e9;
11
+ }
12
+ .column-source .dashicons-admin-appearance,
13
+ .column-sources_with_invalid_output .dashicons-admin-appearance {
14
+ color: #ebb04f;
15
+ }
16
+ .column-source, .dashicons-wordpress-alt,
17
+ .column-sources_with_invalid_output .dashicons-wordpress-alt {
18
+ color: #92b371;
19
+ }
20
+ .amp-logo-icon {
21
+ background-image: url( '../images/amp-logo-icon.svg' );
22
+ background-color: transparent;
23
+ background-size: 20px 20px;
24
+ height: 20px;
25
+ width: 20px;
26
+ display: inline-block;
27
+ }
28
+ .column-error_status .error-status {
29
+ line-height: 20px;
30
+ display: inline-block;
31
+ position: relative;
32
+ vertical-align: top;
33
+ margin-left: 10px;
34
+ }
35
+ td.column-found_elements_and_attributes {
36
+ color: #970010;
37
+ }
38
+ td.column-found_elements_and_attributes div {
39
+ margin-bottom: 0.6rem;
40
+ }
41
+ .column-error_status .dashicons-flag.new {
42
+ color: #d98501;
43
+ }
44
+ .column-error_status .dashicons-yes.new {
45
+ color: #ff0000;
46
+ }
47
+ .column-error_status .dashicons-warning.rejected {
48
+ color: #68c6ff;
49
+ }
50
+ .column-sources .source,
51
+ .column-sources_with_invalid_output .source {
52
+ margin-bottom: 10px;
53
+ display: block;
54
+ }
55
+ .wrap .wp-heading-inline + .page-title-action {
56
+ margin-left: 1rem;
57
+ }
assets/css/amp-default.css CHANGED
@@ -1,11 +1,25 @@
1
- .amp-wp-enforced-sizes {
2
- /** Our sizes fallback is 100vw, and we have a padding on the container; the max-width here prevents the element from overflowing. **/
3
- max-width: 100%;
4
- margin: 0 auto;
5
- }
6
-
7
- .amp-wp-unknown-size img {
8
  /** Worst case scenario when we can't figure out dimensions for an image. **/
9
  /** Force the image into a box of fixed dimensions and use object-fit to scale. **/
10
  object-fit: contain;
11
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .amp-wp-unknown-size [src] {
 
 
 
 
 
 
2
  /** Worst case scenario when we can't figure out dimensions for an image. **/
3
  /** Force the image into a box of fixed dimensions and use object-fit to scale. **/
4
  object-fit: contain;
5
  }
6
+
7
+ amp-fit-text blockquote,
8
+ amp-fit-text h1,
9
+ amp-fit-text h2,
10
+ amp-fit-text h3,
11
+ amp-fit-text h4,
12
+ amp-fit-text h5,
13
+ amp-fit-text h6 {
14
+ font-size: inherit;
15
+ }
16
+
17
+ /**
18
+ * Override a style rule in Twenty Sixteen and Twenty Seventeen.
19
+ * It set display:none for audio elements.
20
+ * This selector is the same, though it adds body and uses amp-audio instead of audio.
21
+ */
22
+ body amp-audio:not([controls]) {
23
+ display: inline-block;
24
+ height: auto;
25
+ }
assets/css/amp-editor-blocks.css ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ .is-amp-fit-text + .blocks-font-size > .components-font-size-picker__buttons,
2
+ .is-amp-fit-text + .blocks-font-size > .components-font-size-picker__custom-input {
3
+ display: none;
4
+ }
assets/css/amp-post-meta-box.css CHANGED
@@ -64,3 +64,16 @@
64
  line-height: 280%;
65
  }
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  line-height: 280%;
65
  }
66
  }
67
+
68
+ .amp-block-validation-errors {
69
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
70
+ font-size: 13px;
71
+ line-height: 1.5;
72
+ }
73
+ .amp-block-validation-errors .amp-block-validation-errors__summary {
74
+ margin: 0.5em 0;
75
+ padding: 2px;
76
+ }
77
+ .amp-block-validation-errors .amp-block-validation-errors__list {
78
+ padding-left: 2.5em;
79
+ }
assets/css/amp-validation-error-taxonomy.css ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #col-left {
2
+ display: none;
3
+ }
4
+ #col-right {
5
+ float: none;
6
+ width: auto;
7
+ }
8
+
9
+ /* Move the 'All dates' filter to the right of the new status and type filters */
10
+ #filter-by-date {
11
+ float: none;
12
+ }
13
+
14
+ /* Improve column widths */
15
+ td.column-details pre,
16
+ td.column-sources pre {
17
+ overflow: auto;
18
+ }
19
+
20
+ th.column-created_date_gmt,
21
+ th.column-error_type {
22
+ width: 15%;
23
+ }
24
+
25
+ td.column-error .error-code {
26
+ font-family: Consolas, Monaco, monospace;
27
+ }
28
+
29
+ th.column-status {
30
+ width: 15%;
31
+ }
32
+
33
+ .fixed th.column-posts {
34
+ width: 10%;
35
+ }
36
+
37
+ /* Details column */
38
+ .column-details .details-attributes__summary {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ }
43
+
44
+ details[open] .details-attributes__summary {
45
+ font-weight: 600;
46
+ margin-bottom: 15px;
47
+ }
48
+
49
+ .column-details .details-attributes__summary::-webkit-details-marker,
50
+ .column-details .notice details > summary::-webkit-details-marker {
51
+ display: none;
52
+ }
53
+ .details-attributes__summary::after,
54
+ .single-error-detail-summary::after {
55
+ order: 99;
56
+ width: 12px;
57
+ height: 12px;
58
+ background-image: url("../images/down-triangle.svg");
59
+ background-size: cover;
60
+ background-position: center;
61
+ content: "";
62
+ }
63
+
64
+ tr.expanded .details-attributes__summary::after,
65
+ details.single-error-detail[open] .single-error-detail-summary::after {
66
+ transform: rotate(180deg);
67
+ }
68
+
69
+ .notice .detailed {
70
+ padding-left: 15px;
71
+ }
72
+
73
+ .notice .detailed details {
74
+ padding-bottom: 16px;
75
+ }
76
+
77
+ .notice .detailed details .detailed {
78
+ padding-left: 32px;
79
+ font-family: Consolas, Monaco, monospace;
80
+ }
81
+
82
+ .details-attributes__title code,
83
+ .notice .detailed summary code {
84
+ display: inline-block;
85
+ min-width: 240px;
86
+ margin-left: 18px;
87
+ font-weight: 600;
88
+ }
89
+
90
+ .details-attributes__title code {
91
+ margin-left: 0;
92
+ }
93
+
94
+ .details-attributes__list {
95
+ margin-top: 0;
96
+ padding-left: 0;
97
+ list-style: none;
98
+ font-family: Consolas, Monaco, monospace;
99
+ }
100
+
101
+ .details-attributes__list li {
102
+ word-break: break-all;
103
+ }
104
+
105
+ .details-attributes__attr {
106
+ font-weight: 600;
107
+ }
108
+
109
+ .column-sources_with_invalid_output details[open] .details-attributes__summary {
110
+ margin-bottom: 5px;
111
+ }
112
+ .column-sources_with_invalid_output details > div {
113
+ padding-left: 25px;
114
+ }
115
+
116
+ /* Error details toggle button */
117
+ .manage-column.column-sources_with_invalid_output .error-details-toggle {
118
+ margin: 0;
119
+ }
120
+
121
+ .error-details-toggle {
122
+ float: right;
123
+ display: flex;
124
+ flex-direction: column;
125
+ height: 14px;
126
+ padding: 0;
127
+ margin-top: 4px;
128
+ background: none;
129
+ border: none;
130
+ cursor: pointer;
131
+ }
132
+
133
+ .error-details-toggle.is-open {
134
+ transform: rotate(180deg);
135
+ }
136
+
137
+ .column-details .error-details-toggle::before,
138
+ .column-details .error-details-toggle::after {
139
+ width: 12px;
140
+ height: 12px;
141
+ background-image: url("../images/down-triangle.svg");
142
+ background-size: cover;
143
+ background-position: center;
144
+ content: "";
145
+ }
146
+
147
+ /* Status text icons */
148
+ .status-text {
149
+ display: flex;
150
+ align-items: center;
151
+ padding-bottom: 0.6rem;
152
+ }
153
+
154
+ .status-text::before {
155
+ margin-right: 10px;
156
+ background-size: 20px 20px;
157
+ height: 20px;
158
+ width: 20px;
159
+ content: "";
160
+ min-width: 20px;
161
+ }
162
+
163
+ .status-text.sanitized::before {
164
+ background-image: url("../images/amp-logo-icon.svg");
165
+ }
166
+
167
+ .status-text.new::before {
168
+ background-image: url("../images/baseline-error.svg");
169
+ }
170
+ .status-text.new.new-accepted::before, .status-text.new.accepted::before{
171
+ background-image: url("../images/baseline-error-green.svg");
172
+ }
173
+ .status-text.new.new-rejected::before, .status-text.new.rejected::before {
174
+ background-image: url("../images/baseline-error-red.svg");
175
+ }
176
+ .status-text.accepted::before {
177
+ background-image: url("../images/baseline-check-circle-green.svg");
178
+ }
179
+
180
+ .status-text.rejected::before {
181
+ background-image: url("../images/error-rejected.svg");
182
+ }
183
+ .single-error-detail {
184
+ margin: 5px 0 5px 0;
185
+ }
186
+ .single-error-detail-summary:after {
187
+ display: inline-block;
188
+ }
189
+ .single-error-detail-summary strong {
190
+ margin-right: 10px;
191
+ font-size: 15px;
192
+ }
193
+ .single-error-detail ul.secondary-details-array .details-attributes__attr {
194
+ margin-left: 20px;
195
+ }
196
+ .single-error-detail ul.secondary-details-array .details-attributes__value {
197
+ margin-left: 30px;
198
+ }
199
+ .single-error-detail .details-attributes__value {
200
+ margin-left: 10px;
201
+ }
202
+
203
+ body.taxonomy-amp_validation_error .wp-list-table .new th,
204
+ body.taxonomy-amp_validation_error .wp-list-table .new td,
205
+ tr.expanded.new + tr > td:first-of-type,
206
+ body.post-type-amp_validated_url .wp-list-table .new th,
207
+ body.post-type-amp_validated_url .wp-list-table .new td {
208
+ background-color: #fef7f1;
209
+ }
210
+
211
+ body.taxonomy-amp_validation_error .wp-list-table .new th.check-column,
212
+ tr.expanded.new + tr > td:first-of-type,
213
+ body.post-type-amp_validated_url .wp-list-table .new th.check-column {
214
+ border-left: 4px solid #d54e21;
215
+ }
216
+
217
+ body.taxonomy-amp_validation_error .wp-list-table .new th.check-column input {
218
+ margin-left: 4px;
219
+ }
220
+
221
+ .row-actions .amp_validation_error_accept > a {
222
+ color: #006505;
223
+ }
224
+
225
+ .row-actions .amp_validation_error_reject > a {
226
+ color: #a00;
227
+ }
228
+
229
+ .notice.accept-reject-error {
230
+ display: flex;
231
+ margin-bottom: 0;
232
+ }
233
+
234
+ .notice.accept-reject-error > p {
235
+ display: inline-block;
236
+ font-size: 14px;
237
+ flex-grow: 10;
238
+ margin-right: 20px;
239
+ }
240
+ .notice.accept-reject-error > .button {
241
+ display: inline-block;
242
+ margin: 5px 5px 0 5px;
243
+ padding: 0 26px 2px;
244
+ flex-grow: 1;
245
+ text-align: center;
246
+ }
247
+ .notice.accept-reject-error > .button.accept {
248
+ /* @todo Add green colors */
249
+ }
250
+ .notice.accept-reject-error > .button.reject {
251
+ /* @todo Add red colors */
252
+ }
253
+
254
+ .notice.error-details {
255
+ margin-top: 1px;
256
+ }
257
+
258
+ .wp-heading-inline .status-text {
259
+ display: inline-flex;
260
+ margin-left: 10px;
261
+ vertical-align: middle;
262
+ padding-bottom: 0;
263
+ }
264
+
265
+ .wp-heading-inline code {
266
+ font-size: 1rem;
267
+ }
268
+
269
+ /** Details post action. */
270
+ .details button {
271
+ display: inline-block;
272
+ background: none;
273
+ border: none;
274
+ padding: 0;
275
+ text-align: left;
276
+ color: #0073aa;
277
+ cursor: pointer;
278
+ text-decoration: underline;
279
+ }
assets/css/amp-validation-single-error-url.css ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** Arrow icon on title in error column. */
2
+ .column-error > .single-url-detail-toggle {
3
+ position: relative;
4
+ width: 100%;
5
+ padding: 5px 36px 5px 0;
6
+ background: none;
7
+ border: none;
8
+ text-align: left;
9
+ line-height: 1.682;
10
+ color: #0073aa;
11
+ cursor: pointer;
12
+ }
13
+
14
+ .column-error > .single-url-detail-toggle::after {
15
+ position: absolute;
16
+ top: 0;
17
+ right: 0;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ width: 12px;
22
+ height: 18px;
23
+ margin-top: 5px;
24
+ background-image: url("../images/down-triangle.svg");
25
+ background-size: contain;
26
+ background-repeat: no-repeat;
27
+ background-position: center;
28
+ content: "";
29
+ }
30
+
31
+ tr.expanded .single-url-detail-toggle::after {
32
+ transform: rotate(180deg);
33
+ }
34
+
35
+ /** Striped table overrides. */
36
+ table.striped > tbody > tr.odd {
37
+ background: #f9f9f9;
38
+ }
39
+
40
+ table.striped > tbody > tr.even {
41
+ background: #fff;
42
+ }
43
+
44
+ /** Hide original details content. */
45
+ .details-attributes > .detailed {
46
+ display: none;
47
+ }
48
+
49
+ /** Details post action. */
50
+ .details button {
51
+ display: inline-block;
52
+ background: none;
53
+ border: none;
54
+ padding: 0;
55
+ text-align: left;
56
+ color: #0073aa;
57
+ cursor: pointer;
58
+ text-decoration: underline;
59
+ }
60
+
61
+ /** Details row styles. */
62
+ .details details.details-attributes:hover {
63
+ cursor: pointer;
64
+ }
65
+
66
+ .details ul.detailed {
67
+ padding: 0 32px;
68
+ margin-top: 0;
69
+ }
70
+
71
+ .details div.detailed {
72
+ padding-left: 30px;
73
+ margin-top: 10px;
74
+ font-family: Consolas, Monaco, monospace;
75
+ }
76
+
77
+ .details .detailed details {
78
+ padding-bottom: 16px;
79
+ }
80
+
81
+ .details .detailed summary code {
82
+ display: inline-block;
83
+ min-width: 240px;
84
+ margin-left: 12px;
85
+ font-weight: 600;
86
+ }
87
+
88
+ .column-status select {
89
+ vertical-align: top;
90
+ }
91
+
92
+ .column-status img {
93
+ width: 1.5rem;
94
+ margin-top: 0.2rem;
95
+ }
96
+
97
+ #number-errors {
98
+ text-align: center;
99
+ background-color: #d3d3d3b8;
100
+ color: #1e8cbecc;
101
+ }
102
+
103
+ #url-post-filter {
104
+ float: none;
105
+ display: inline;
106
+ }
107
+
108
+ .tablenav.top,
109
+ .tablenav.bottom {
110
+ display: none;
111
+ }
112
+
113
+ .amp-validated-url a {
114
+ text-decoration: none;
115
+ }
116
+
117
+ .curtime.misc-pub-section {
118
+ margin-top: 0.5rem;
119
+ }
120
+
121
+ /* Give enough width to prevent the widest column status, 'New Accepted,' from forcing the <select> below the icon. */
122
+ .wp-list-table th.column-status {
123
+ width: 150px;
124
+ }
125
+
126
+ .wp-list-table th.column-sources_with_invalid_output {
127
+ width: 20%;
128
+ }
129
+
130
+ .wp-list-table th.column-error {
131
+ width: 25%;
132
+ }
133
+
134
+ .wp-list-table th.column-details {
135
+ width: 20%;
136
+ }
137
+
138
+ /** Add space between list table and the filter and search box above it. */
139
+ #post-body-content button.action,
140
+ #post-body-content #url-post-filter,
141
+ #post-body-content .search-box {
142
+ margin-bottom: 4px;
143
+ }
144
+
145
+ #post-body-content button.reject {
146
+ margin-left: 4px;
147
+ }
148
+
149
+ #accept-reject-buttons:not(.hidden) {
150
+ display: inline-block;
151
+ }
152
+
153
+ #vertical-divider {
154
+ display: inline-block;
155
+ vertical-align: middle;
156
+ width: 15px;
157
+ margin-right: 15px;
158
+ height: 30px;
159
+ border-right: 1px solid #a0a5aa;
160
+ }
161
+ .amp-validation-error-status {
162
+ width: auto;
163
+ float: none;
164
+ }
assets/css/amp-validation-tooltips.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+
2
+ /* @todo This should be moved to admin-tables.css which is then enqueued on both screens. */
3
+ .tooltip-button {
4
+ margin: 0 6px;
5
+ cursor: pointer;
6
+ color: #767676;
7
+ }
assets/images/amp-logo-icon.svg ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
5
+ <g id="AMP-Logo-Icon" fill="#0075C2">
6
+ <path d="M13.3,9.1l-4,6.6H8.5l0.7-4.3l-2.2,0h0c-0.2,0-0.4-0.2-0.4-0.4c0-0.1,0.1-0.2,0.1-0.2l4-6.6l0.7,0l-0.7,4.3l2.2,0l0,0
7
+ c0.2,0,0.4,0.2,0.4,0.4C13.4,9,13.3,9.1,13.3,9.1L13.3,9.1z M10,0.5c-5.3,0-9.6,4.3-9.6,9.5c0,5.3,4.3,9.5,9.6,9.5
8
+ c5.3,0,9.6-4.3,9.6-9.5C19.6,4.7,15.3,0.5,10,0.5z"/></g>
9
+ </svg>
10
+
assets/images/amp-welcome-icon.svg ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="908.3604125976562 898.2000122070312 183.05340576171875 200.39996337890625" style="enable-background:new 0 0 2000 2000;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{opacity:0.8;fill:none;stroke:url(#SVGID_1_);stroke-width:9.6838;stroke-miterlimit:10;enable-background:new ;}
7
+ .st1{opacity:0.8;fill:none;stroke:url(#SVGID_2_);stroke-width:10.1244;stroke-miterlimit:10;enable-background:new ;}
8
+ .st2{fill:none;stroke:url(#SVGID_3_);stroke-width:2.4095;stroke-miterlimit:10;}
9
+ .st3{fill:url(#SVGID_4_);}
10
+ .st4{fill:#FFFFFF;stroke:url(#SVGID_5_);stroke-width:2.4095;stroke-miterlimit:10;}
11
+ .st5{fill:none;stroke:url(#SVGID_6_);stroke-width:2.4095;stroke-miterlimit:10;}
12
+ .st6{fill:#0DD7FF;fill-opacity:0.7;}
13
+ .st7{opacity:0.7;fill:#0DD7FF;enable-background:new ;}
14
+ .st8{fill:url(#SVGID_7_);fill-opacity:0.75;}
15
+ .st9{fill:none;stroke:url(#SVGID_8_);stroke-width:2.4095;stroke-linecap:round;stroke-miterlimit:10;}
16
+ .st10{fill:none;stroke:url(#SVGID_9_);stroke-width:2.4095;stroke-linecap:round;stroke-miterlimit:10;}
17
+ .st11{fill:#FFFFFF;fill-opacity:0.75;stroke:url(#SVGID_10_);stroke-width:2.4095;stroke-miterlimit:10;}
18
+ .st12{fill:url(#SVGID_11_);fill-opacity:0.75;}
19
+ .st13{fill:none;stroke:url(#SVGID_12_);stroke-width:2.4095;stroke-linecap:round;stroke-miterlimit:10;}
20
+ .st14{fill:none;stroke:url(#SVGID_13_);stroke-width:2.4095;stroke-linecap:round;stroke-miterlimit:10;}
21
+ .st15{fill:#FFFFFF;}
22
+ .st16{fill:#167DD2;}
23
+ .st17{opacity:0.3;fill:#0DD7FF;enable-background:new ;}
24
+ .st18{opacity:0.5;fill:none;stroke:#FFFFFF;stroke-width:2.4095;stroke-linecap:round;stroke-miterlimit:10;}
25
+ </style>
26
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-12002.9561" y1="893.2319" x2="-11948.1289" y2="893.2319" gradientTransform="matrix(0.3305 -0.9438 -0.9438 -0.3305 5754.7773 -10044.6777)">
27
+ <stop offset="0" style="stop-color:#0389FF"/>
28
+ <stop offset="0.5" style="stop-color:#0DD7FF"/>
29
+ <stop offset="1" style="stop-color:#FFFFFF"/>
30
+ </linearGradient>
31
+ <line class="st0" x1="954.3" y1="935.3" x2="953.8" y2="989.9"/>
32
+ <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-12020.958" y1="879.0432" x2="-11920.0166" y2="879.0432" gradientTransform="matrix(0.3305 -0.9438 -0.9438 -0.3305 5754.7773 -10044.6777)">
33
+ <stop offset="0" style="stop-color:#0389FF"/>
34
+ <stop offset="0.5" style="stop-color:#0DD7FF"/>
35
+ <stop offset="1" style="stop-color:#FFFFFF"/>
36
+ </linearGradient>
37
+ <line class="st1" x1="969.7" y1="910.9" x2="968.5" y2="1014"/>
38
+ <g>
39
+
40
+ <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="1008.2053" y1="1053.8057" x2="1008.2053" y2="1103.8019" gradientTransform="matrix(1 0 0 -1 0 2002)">
41
+ <stop offset="0" style="stop-color:#1C79C4"/>
42
+ <stop offset="0.51" style="stop-color:#0389FF"/>
43
+ <stop offset="1" style="stop-color:#0DD7FF"/>
44
+ </linearGradient>
45
+ <line class="st2" x1="1008.2" y1="948.2" x2="1008.2" y2="900.8"/>
46
+
47
+ <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="1008.2053" y1="1053.8057" x2="1008.2053" y2="1103.8019" gradientTransform="matrix(1 0 0 -1 0 2002)">
48
+ <stop offset="0" style="stop-color:#1C79C4"/>
49
+ <stop offset="0.51" style="stop-color:#0389FF"/>
50
+ <stop offset="1" style="stop-color:#0DD7FF"/>
51
+ </linearGradient>
52
+ <polygon class="st3" points="1018.1,908.8 1016.3,910.4 1008.2,901.7 1000.1,910.4 998.4,908.8 1008.2,898.2 "/>
53
+ </g>
54
+ <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="941.6317" y1="1014.0682" x2="1040.9495" y2="1014.0682">
55
+ <stop offset="0" style="stop-color:#187CCE"/>
56
+ <stop offset="1" style="stop-color:#0DD5FE"/>
57
+ </linearGradient>
58
+ <polygon class="st4" points="942.8,1098.6 1039.7,1045.5 1039.7,929.6 942.8,982.7 "/>
59
+ <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="943.079" y1="1027.4312" x2="1041.1453" y2="1027.4312" gradientTransform="matrix(1 0 0 -1 0 2002)">
60
+ <stop offset="0" style="stop-color:#1C79C4"/>
61
+ <stop offset="0.51" style="stop-color:#0389FF"/>
62
+ <stop offset="1" style="stop-color:#0DD7FF"/>
63
+ </linearGradient>
64
+ <line class="st5" x1="1040.6" y1="948" x2="943.7" y2="1001.1"/>
65
+ <ellipse transform="matrix(0.1104 -0.9939 0.9939 0.1104 -124.5549 1829.4717)" class="st6" cx="959.7" cy="984.3" rx="2.8" ry="2.8"/>
66
+ <ellipse transform="matrix(0.1094 -0.994 0.994 0.1094 -133.3753 1826.4724)" class="st6" cx="952.6" cy="987.7" rx="2.8" ry="2.8"/>
67
+ <ellipse transform="matrix(0.1094 -0.994 0.994 0.1094 -111.5842 1834.7726)" class="st6" cx="968.1" cy="979.7" rx="2.8" ry="2.8"/>
68
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 23.327 1872.063)" class="st7" cx="1064.6" cy="922.9" rx="3.4" ry="3.5"/>
69
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 -144.5006 1781.6594)" class="st7" cx="929.9" cy="972.1" rx="3.4" ry="3.5"/>
70
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 -261.0735 1874.2338)" class="st7" cx="923.7" cy="1084" rx="3.4" ry="3.5"/>
71
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 -116.1821 1770.166)" class="st7" cx="937.6" cy="950.4" rx="3.4" ry="3.5"/>
72
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 -177.6883 1787.7156)" class="st7" cx="916.7" cy="993.8" rx="3.4" ry="3.5"/>
73
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 2.8353 1930.9661)" class="st7" cx="1087.5" cy="963.9" rx="3.4" ry="3.5"/>
74
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 14.4208 1901.3986)" class="st7" cx="1076.7" cy="942.6" rx="3.4" ry="3.5"/>
75
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 -255.1249 1856.6938)" class="st7" cx="916.8" cy="1071.8" rx="3.4" ry="3.5"/>
76
+ <ellipse transform="matrix(0.1172 -0.9931 0.9931 0.1172 -141.707 1747.8772)" class="st7" cx="912.3" cy="953.6" rx="3.4" ry="3.5"/>
77
+ <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="988.4651" y1="1030.0048" x2="988.4651" y2="980.6591">
78
+ <stop offset="0" style="stop-color:#187CCE"/>
79
+ <stop offset="0" style="stop-color:#187FD0"/>
80
+ <stop offset="1" style="stop-color:#0DD5FE"/>
81
+ </linearGradient>
82
+ <polygon class="st8" points="972.5,1030 1004.4,1013.6 1004.4,980.7 972.5,997 "/>
83
+ <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="976.1646" y1="969.6558" x2="1000.2975" y2="969.6558" gradientTransform="matrix(1 0 0 -1 0 2002)">
84
+ <stop offset="0" style="stop-color:#1C79C4"/>
85
+ <stop offset="0.51" style="stop-color:#0389FF"/>
86
+ <stop offset="1" style="stop-color:#0DD7FF"/>
87
+ </linearGradient>
88
+ <line class="st9" x1="977.4" y1="1038.1" x2="999.1" y2="1026.6"/>
89
+ <linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="976.1646" y1="961.2227" x2="1000.2975" y2="961.2227" gradientTransform="matrix(1 0 0 -1 0 2002)">
90
+ <stop offset="0" style="stop-color:#1C79C4"/>
91
+ <stop offset="0.51" style="stop-color:#0389FF"/>
92
+ <stop offset="1" style="stop-color:#0DD7FF"/>
93
+ </linearGradient>
94
+ <line class="st10" x1="977.4" y1="1046.5" x2="999.1" y2="1035"/>
95
+ <g>
96
+ <linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="1016.4717" y1="1038.5374" x2="1063.432" y2="1038.5374">
97
+ <stop offset="0" style="stop-color:#187CCE"/>
98
+ <stop offset="1" style="stop-color:#0DD5FE"/>
99
+ </linearGradient>
100
+ <polygon class="st11" points="1062.2,981.4 1017.7,1004.3 1017.7,1095.7 1062.2,1072.8 "/>
101
+ <linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="1039.6508" y1="1046.2155" x2="1039.6508" y2="996.8698">
102
+ <stop offset="0" style="stop-color:#187CCE"/>
103
+ <stop offset="0" style="stop-color:#187FD0"/>
104
+ <stop offset="1" style="stop-color:#0DD5FE"/>
105
+ </linearGradient>
106
+ <polygon class="st12" points="1023.7,1046.2 1055.6,1029.9 1055.6,996.9 1023.7,1013.2 "/>
107
+
108
+ <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="1023.5676" y1="954.687" x2="1056.0122" y2="954.687" gradientTransform="matrix(1 0 0 -1 0 2002)">
109
+ <stop offset="0" style="stop-color:#1C79C4"/>
110
+ <stop offset="0.51" style="stop-color:#0389FF"/>
111
+ <stop offset="1" style="stop-color:#0DD7FF"/>
112
+ </linearGradient>
113
+ <line class="st13" x1="1024.8" y1="1055.3" x2="1054.8" y2="1039.4"/>
114
+
115
+ <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="1023.5676" y1="945.752" x2="1056.0122" y2="945.752" gradientTransform="matrix(1 0 0 -1 0 2002)">
116
+ <stop offset="0" style="stop-color:#1C79C4"/>
117
+ <stop offset="0.51" style="stop-color:#0389FF"/>
118
+ <stop offset="1" style="stop-color:#0DD7FF"/>
119
+ </linearGradient>
120
+ <line class="st14" x1="1024.8" y1="1064.2" x2="1054.8" y2="1048.3"/>
121
+ <path class="st15" d="M1043.8,1018.9l-5,11.6l-0.9,0.3l0.9-6.7l-2.8,0.9l0,0c-0.2,0.1-0.5-0.1-0.5-0.4c0-0.1,0.1-0.4,0.1-0.4
122
+ l5-11.6l0.9-0.3l-0.9,6.8l2.8-0.9l0,0c0.3-0.1,0.5,0.1,0.5,0.4C1043.8,1018.6,1043.8,1018.8,1043.8,1018.9L1043.8,1018.9z"/>
123
+ </g>
124
+ <polygon class="st16" points="968.6,1083 943.7,1097.1 943.7,1002.4 968.6,989 "/>
125
+ <polygon class="st17" points="1013.1,975.4 1032.9,964.4 1032.9,973.6 1013.6,984.7 "/>
126
+ <polygon class="st17" points="1013.1,988.5 1032.9,977.4 1032.9,982.8 1013.6,993.7 "/>
127
+ <path class="st15" d="M956.2,1000c-4.7,2.2-8.6,8.1-8.6,13.1c0,5,3.9,7.4,8.6,5.2c4.7-2.2,8.6-8.1,8.6-13.1
128
+ C964.8,1000.2,960.9,997.8,956.2,1000z M948.4,1012.7c0-1.2,0.2-2.4,0.7-3.7l3.7,9.1C950.2,1018,948.4,1016,948.4,1012.7z
129
+ M956.2,1017.4c-0.8,0.3-1.5,0.6-2.2,0.7l2.3-8.2l2.4,5.8c0,0,0,0.1,0.1,0.1C958,1016.4,957.1,1017,956.2,1017.4z M957.2,1004.8
130
+ c0.5-0.2,0.9-0.5,0.9-0.5c0.4-0.2,0.4-0.9-0.1-0.7c0,0-1.3,0.7-2.1,1.1c-0.8,0.3-2,0.8-2,0.8c-0.4,0.2-0.5,0.9,0,0.7
131
+ c0,0,0.4-0.1,0.8-0.3l1.2,3l-1.7,6.2l-2.8-7.6c0.5-0.2,0.9-0.5,0.9-0.5c0.4-0.2,0.4-0.9-0.1-0.7c0,0-1.3,0.7-2.1,1.1
132
+ c-0.1,0.1-0.3,0.1-0.5,0.2c1.4-2.9,3.8-5.4,6.5-6.7c2-0.9,3.9-1,5.2-0.2c0,0-0.1,0-0.1,0c-0.8,0.3-1.3,1.3-1.3,2.1
133
+ c0,0.7,0.4,1.1,0.8,1.6c0.3,0.4,0.6,1,0.6,2c0,0.7-0.3,1.6-0.6,2.9l-0.8,3.1L957.2,1004.8z M963,1002.1c0.6,0.9,1,2.1,1,3.5
134
+ c0,3-1.5,6.4-3.8,8.9l2.3-8.4c0.4-1.4,0.6-2.4,0.6-3.2C963,1002.6,963,1002.3,963,1002.1z"/>
135
+ <path class="st18" d="M970.6,1021.2"/>
136
+ <line class="st18" x1="948.6" y1="1028.4" x2="963.6" y2="1020.5"/>
137
+ <line class="st18" x1="948.6" y1="1036.3" x2="963.6" y2="1028.4"/>
138
+ <line class="st18" x1="948.6" y1="1044.3" x2="963.6" y2="1036.3"/>
139
+ </svg>
assets/images/baseline-check-circle-green.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
2
+ <path d="M0 0h24v24H0z" fill="none"/>
3
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#85b649" />
4
+ </svg>
assets/images/baseline-error-green.svg ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{fill:none;}
7
+ </style>
8
+ <path class="st0" d="M0,0h20v20H0V0z"/>
9
+ <g id="AMP-Warning" fill="#84b741">
10
+ <path d="M10,1c-5,0-9,4.1-9,9s4.1,9,9,9s9-4.1,9-9S15,1,10,1z M10.9,14.6H9.1v-1.8H11v1.8H10.9z M10.9,10.9H9.1V5.4H11v5.4H10.9z"/>
11
+ </g>
12
+ </svg>
assets/images/baseline-error-red.svg ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{fill:none;}
7
+ </style>
8
+ <path class="st0" d="M0,0h20v20H0V0z"/>
9
+ <g id="AMP-Warning" fill="#FF0000">
10
+ <path d="M10,1c-5,0-9,4.1-9,9s4.1,9,9,9s9-4.1,9-9S15,1,10,1z M10.9,14.6H9.1v-1.8H11v1.8H10.9z M10.9,10.9H9.1V5.4H11v5.4H10.9z"/>
11
+ </g>
12
+ </svg>
assets/images/baseline-error.svg ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{fill:none;}
7
+ </style>
8
+ <path class="st0" d="M0,0h20v20H0V0z"/>
9
+ <g id="AMP-Warning" fill="#E38000">
10
+ <path d="M10,1c-5,0-9,4.1-9,9s4.1,9,9,9s9-4.1,9-9S15,1,10,1z M10.9,14.6H9.1v-1.8H11v1.8H10.9z M10.9,10.9H9.1V5.4H11v5.4H10.9z"/>
11
+ </g>
12
+ </svg>
assets/images/down-triangle.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg height="1024" width="767.5" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0 384l383.75 383.75L767.5 384H0z" fill="#0073aa" />
3
+ </svg>
assets/images/editor-help.svg ADDED
@@ -0,0 +1 @@
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g fill="#FF0E57"><path d="M17 10c0-3.87-3.14-7-7-7-3.87 0-7 3.13-7 7s3.13 7 7 7c3.86 0 7-3.13 7-7zm-6.3 1.48H9.14v-.43c0-.38.08-.7.24-.98s.46-.57.88-.89c.41-.29.68-.53.81-.71.14-.18.2-.39.2-.62 0-.25-.09-.44-.28-.58-.19-.13-.45-.19-.79-.19-.58 0-1.25.19-2 .57l-.64-1.28c.87-.49 1.8-.74 2.77-.74.81 0 1.45.2 1.92.58.48.39.71.91.71 1.55 0 .43-.09.8-.29 1.11-.19.32-.57.67-1.11 1.06-.38.28-.61.49-.71.63-.1.15-.15.34-.15.57v.35zm-1.47 2.74c-.18-.17-.27-.42-.27-.73 0-.33.08-.58.26-.75s.43-.25.77-.25c.32 0 .57.09.75.26s.27.42.27.74c0 .3-.09.55-.27.72-.18.18-.43.27-.75.27-.33 0-.58-.09-.76-.26z"/></g></svg>
assets/images/error-rejected.svg ADDED
@@ -0,0 +1 @@
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g fill="#FF0000"><path d="M12.12 10l3.53 3.53-2.12 2.12L10 12.12l-3.54 3.54-2.12-2.12L7.88 10 4.34 6.46l2.12-2.12L10 7.88l3.54-3.53 2.12 2.12z"/></g></svg>
assets/js/amp-admin-pointer.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Adds an admin pointer that describes new features in 1.0.
3
+ */
4
+
5
+ /* exported ampAdminPointer */
6
+ /* global ajaxurl, jQuery */
7
+ var ampAdminPointer = ( function( $ ) { // eslint-disable-line no-unused-vars
8
+ 'use strict';
9
+
10
+ return {
11
+
12
+ /**
13
+ * Loads the pointer.
14
+ *
15
+ * @param {Object} data - Module data.
16
+ * @return {void}
17
+ */
18
+ load: function load( data ) {
19
+ var options = $.extend(
20
+ data.pointer.options,
21
+ {
22
+ /**
23
+ * Makes a POST request to store the pointer ID as dismissed for this user.
24
+ */
25
+ close: function() {
26
+ $.post( ajaxurl, {
27
+ pointer: data.pointer.pointer_id,
28
+ action: 'dismiss-wp-pointer'
29
+ } );
30
+ }
31
+ }
32
+ );
33
+
34
+ $( data.pointer.target ).pointer( options ).pointer( 'open' );
35
+ }
36
+ };
37
+ }( jQuery ) );
assets/js/amp-block-editor-toggle-compiled.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /******/ (function(modules) { // webpackBootstrap
2
+ /******/ // The module cache
3
+ /******/ var installedModules = {};
4
+ /******/
5
+ /******/ // The require function
6
+ /******/ function __webpack_require__(moduleId) {
7
+ /******/
8
+ /******/ // Check if module is in cache
9
+ /******/ if(installedModules[moduleId]) {
10
+ /******/ return installedModules[moduleId].exports;
11
+ /******/ }
12
+ /******/ // Create a new module (and put it into the cache)
13
+ /******/ var module = installedModules[moduleId] = {
14
+ /******/ i: moduleId,
15
+ /******/ l: false,
16
+ /******/ exports: {}
17
+ /******/ };
18
+ /******/
19
+ /******/ // Execute the module function
20
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21
+ /******/
22
+ /******/ // Flag the module as loaded
23
+ /******/ module.l = true;
24
+ /******/
25
+ /******/ // Return the exports of the module
26
+ /******/ return module.exports;
27
+ /******/ }
28
+ /******/
29
+ /******/
30
+ /******/ // expose the modules object (__webpack_modules__)
31
+ /******/ __webpack_require__.m = modules;
32
+ /******/
33
+ /******/ // expose the module cache
34
+ /******/ __webpack_require__.c = installedModules;
35
+ /******/
36
+ /******/ // define getter function for harmony exports
37
+ /******/ __webpack_require__.d = function(exports, name, getter) {
38
+ /******/ if(!__webpack_require__.o(exports, name)) {
39
+ /******/ Object.defineProperty(exports, name, {
40
+ /******/ configurable: false,
41
+ /******/ enumerable: true,
42
+ /******/ get: getter
43
+ /******/ });
44
+ /******/ }
45
+ /******/ };
46
+ /******/
47
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
48
+ /******/ __webpack_require__.n = function(module) {
49
+ /******/ var getter = module && module.__esModule ?
50
+ /******/ function getDefault() { return module['default']; } :
51
+ /******/ function getModuleExports() { return module; };
52
+ /******/ __webpack_require__.d(getter, 'a', getter);
53
+ /******/ return getter;
54
+ /******/ };
55
+ /******/
56
+ /******/ // Object.prototype.hasOwnProperty.call
57
+ /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58
+ /******/
59
+ /******/ // __webpack_public_path__
60
+ /******/ __webpack_require__.p = "";
61
+ /******/
62
+ /******/ // Load entry module and return exports
63
+ /******/ return __webpack_require__(__webpack_require__.s = 13);
64
+ /******/ })
65
+ /************************************************************************/
66
+ /******/ ({
67
+
68
+ /***/ 13:
69
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
70
+
71
+ "use strict";
72
+ eval("Object.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/**\n * WordPress dependencies\n */\nvar __ = wp.i18n.__;\nvar _wp$components = wp.components,\n FormToggle = _wp$components.FormToggle,\n Notice = _wp$components.Notice;\nvar _wp$element = wp.element,\n Fragment = _wp$element.Fragment,\n RawHTML = _wp$element.RawHTML;\nvar _wp$data = wp.data,\n withSelect = _wp$data.withSelect,\n withDispatch = _wp$data.withDispatch;\nvar PluginPostStatusInfo = wp.editPost.PluginPostStatusInfo;\nvar _wp$compose = wp.compose,\n compose = _wp$compose.compose,\n withInstanceId = _wp$compose.withInstanceId;\n\n/**\n * Exported via wp_localize_script().\n */\n\nvar _window$wpAmpEditor = window.wpAmpEditor,\n possibleStati = _window$wpAmpEditor.possibleStati,\n defaultStatus = _window$wpAmpEditor.defaultStatus,\n errorMessages = _window$wpAmpEditor.errorMessages;\n\n/**\n * Adds an 'Enable AMP' toggle to the block editor 'Status & Visibility' section.\n *\n * If there are error(s) that block AMP from being enabled or disabled,\n * this only displays a Notice with the error(s), not a toggle.\n * Error(s) are imported as errorMessages via wp_localize_script().\n *\n * @return {Object} AMPToggle component.\n */\n\nfunction AMPToggle(_ref) {\n\tvar enabledStatus = _ref.enabledStatus,\n\t onAmpChange = _ref.onAmpChange;\n\n\treturn wp.element.createElement(\n\t\tFragment,\n\t\tnull,\n\t\twp.element.createElement(\n\t\t\tPluginPostStatusInfo,\n\t\t\tnull,\n\t\t\t!errorMessages.length && wp.element.createElement(\n\t\t\t\t'label',\n\t\t\t\t{ htmlFor: 'amp-enabled' },\n\t\t\t\t__('Enable AMP', 'amp')\n\t\t\t),\n\t\t\t!errorMessages.length && wp.element.createElement(FormToggle, {\n\t\t\t\tchecked: 'enabled' === enabledStatus,\n\t\t\t\tonChange: function onChange() {\n\t\t\t\t\treturn onAmpChange(enabledStatus);\n\t\t\t\t},\n\t\t\t\tid: 'amp-enabled'\n\t\t\t}),\n\t\t\t!!errorMessages.length && wp.element.createElement(\n\t\t\t\tNotice,\n\t\t\t\t{\n\t\t\t\t\tstatus: 'warning',\n\t\t\t\t\tisDismissible: false\n\t\t\t\t},\n\t\t\t\terrorMessages.map(function (message, index) {\n\t\t\t\t\treturn wp.element.createElement(\n\t\t\t\t\t\tRawHTML,\n\t\t\t\t\t\t{ key: index },\n\t\t\t\t\t\tmessage\n\t\t\t\t\t);\n\t\t\t\t})\n\t\t\t)\n\t\t)\n\t);\n}\n\n/**\n * The AMP Toggle component, composed with the enabledStatus and a callback for when it's changed.\n *\n * @return {Object} The composed AMP toggle.\n */\nfunction ComposedAMPToggle() {\n\treturn compose([withSelect(function (select) {\n\t\t/**\n * Gets the AMP enabled status.\n *\n * Uses select from the enclosing function to get the meta value.\n * If it doesn't exist, it uses the default value.\n * This applies especially for a new post, where there probably won't be a meta value yet.\n *\n * @return {string} Enabled status, either 'enabled' or 'disabled'.\n */\n\t\tvar getEnabledStatus = function getEnabledStatus() {\n\t\t\tvar meta = select('core/editor').getEditedPostAttribute('meta');\n\t\t\tif (meta && meta.amp_status && possibleStati.includes(meta.amp_status)) {\n\t\t\t\treturn meta.amp_status;\n\t\t\t}\n\t\t\treturn defaultStatus;\n\t\t};\n\n\t\treturn { enabledStatus: getEnabledStatus() };\n\t}), withDispatch(function (dispatch) {\n\t\treturn {\n\t\t\tonAmpChange: function onAmpChange(enabledStatus) {\n\t\t\t\tvar newStatus = 'enabled' === enabledStatus ? 'disabled' : 'enabled';\n\t\t\t\tdispatch('core/editor').editPost({ meta: { amp_status: newStatus } });\n\t\t\t}\n\t\t};\n\t}), withInstanceId])(AMPToggle);\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (wp.plugins.registerPlugin('amp', {\n\ticon: 'hidden',\n\trender: ComposedAMPToggle()\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTMuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9hc3NldHMvc3JjL2FtcC1ibG9jay1lZGl0b3ItdG9nZ2xlLmpzPzVkMDkiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBXb3JkUHJlc3MgZGVwZW5kZW5jaWVzXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgX3dwJGNvbXBvbmVudHMgPSB3cC5jb21wb25lbnRzLFxuICAgIEZvcm1Ub2dnbGUgPSBfd3AkY29tcG9uZW50cy5Gb3JtVG9nZ2xlLFxuICAgIE5vdGljZSA9IF93cCRjb21wb25lbnRzLk5vdGljZTtcbnZhciBfd3AkZWxlbWVudCA9IHdwLmVsZW1lbnQsXG4gICAgRnJhZ21lbnQgPSBfd3AkZWxlbWVudC5GcmFnbWVudCxcbiAgICBSYXdIVE1MID0gX3dwJGVsZW1lbnQuUmF3SFRNTDtcbnZhciBfd3AkZGF0YSA9IHdwLmRhdGEsXG4gICAgd2l0aFNlbGVjdCA9IF93cCRkYXRhLndpdGhTZWxlY3QsXG4gICAgd2l0aERpc3BhdGNoID0gX3dwJGRhdGEud2l0aERpc3BhdGNoO1xudmFyIFBsdWdpblBvc3RTdGF0dXNJbmZvID0gd3AuZWRpdFBvc3QuUGx1Z2luUG9zdFN0YXR1c0luZm87XG52YXIgX3dwJGNvbXBvc2UgPSB3cC5jb21wb3NlLFxuICAgIGNvbXBvc2UgPSBfd3AkY29tcG9zZS5jb21wb3NlLFxuICAgIHdpdGhJbnN0YW5jZUlkID0gX3dwJGNvbXBvc2Uud2l0aEluc3RhbmNlSWQ7XG5cbi8qKlxuICogRXhwb3J0ZWQgdmlhIHdwX2xvY2FsaXplX3NjcmlwdCgpLlxuICovXG5cbnZhciBfd2luZG93JHdwQW1wRWRpdG9yID0gd2luZG93LndwQW1wRWRpdG9yLFxuICAgIHBvc3NpYmxlU3RhdGkgPSBfd2luZG93JHdwQW1wRWRpdG9yLnBvc3NpYmxlU3RhdGksXG4gICAgZGVmYXVsdFN0YXR1cyA9IF93aW5kb3ckd3BBbXBFZGl0b3IuZGVmYXVsdFN0YXR1cyxcbiAgICBlcnJvck1lc3NhZ2VzID0gX3dpbmRvdyR3cEFtcEVkaXRvci5lcnJvck1lc3NhZ2VzO1xuXG4vKipcbiAqIEFkZHMgYW4gJ0VuYWJsZSBBTVAnIHRvZ2dsZSB0byB0aGUgYmxvY2sgZWRpdG9yICdTdGF0dXMgJiBWaXNpYmlsaXR5JyBzZWN0aW9uLlxuICpcbiAqIElmIHRoZXJlIGFyZSBlcnJvcihzKSB0aGF0IGJsb2NrIEFNUCBmcm9tIGJlaW5nIGVuYWJsZWQgb3IgZGlzYWJsZWQsXG4gKiB0aGlzIG9ubHkgZGlzcGxheXMgYSBOb3RpY2Ugd2l0aCB0aGUgZXJyb3IocyksIG5vdCBhIHRvZ2dsZS5cbiAqIEVycm9yKHMpIGFyZSBpbXBvcnRlZCBhcyBlcnJvck1lc3NhZ2VzIHZpYSB3cF9sb2NhbGl6ZV9zY3JpcHQoKS5cbiAqXG4gKiBAcmV0dXJuIHtPYmplY3R9IEFNUFRvZ2dsZSBjb21wb25lbnQuXG4gKi9cblxuZnVuY3Rpb24gQU1QVG9nZ2xlKF9yZWYpIHtcblx0dmFyIGVuYWJsZWRTdGF0dXMgPSBfcmVmLmVuYWJsZWRTdGF0dXMsXG5cdCAgICBvbkFtcENoYW5nZSA9IF9yZWYub25BbXBDaGFuZ2U7XG5cblx0cmV0dXJuIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRGcmFnbWVudCxcblx0XHRudWxsLFxuXHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFBsdWdpblBvc3RTdGF0dXNJbmZvLFxuXHRcdFx0bnVsbCxcblx0XHRcdCFlcnJvck1lc3NhZ2VzLmxlbmd0aCAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdCdsYWJlbCcsXG5cdFx0XHRcdHsgaHRtbEZvcjogJ2FtcC1lbmFibGVkJyB9LFxuXHRcdFx0XHRfXygnRW5hYmxlIEFNUCcsICdhbXAnKVxuXHRcdFx0KSxcblx0XHRcdCFlcnJvck1lc3NhZ2VzLmxlbmd0aCAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoRm9ybVRvZ2dsZSwge1xuXHRcdFx0XHRjaGVja2VkOiAnZW5hYmxlZCcgPT09IGVuYWJsZWRTdGF0dXMsXG5cdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSgpIHtcblx0XHRcdFx0XHRyZXR1cm4gb25BbXBDaGFuZ2UoZW5hYmxlZFN0YXR1cyk7XG5cdFx0XHRcdH0sXG5cdFx0XHRcdGlkOiAnYW1wLWVuYWJsZWQnXG5cdFx0XHR9KSxcblx0XHRcdCEhZXJyb3JNZXNzYWdlcy5sZW5ndGggJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHROb3RpY2UsXG5cdFx0XHRcdHtcblx0XHRcdFx0XHRzdGF0dXM6ICd3YXJuaW5nJyxcblx0XHRcdFx0XHRpc0Rpc21pc3NpYmxlOiBmYWxzZVxuXHRcdFx0XHR9LFxuXHRcdFx0XHRlcnJvck1lc3NhZ2VzLm1hcChmdW5jdGlvbiAobWVzc2FnZSwgaW5kZXgpIHtcblx0XHRcdFx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdFx0UmF3SFRNTCxcblx0XHRcdFx0XHRcdHsga2V5OiBpbmRleCB9LFxuXHRcdFx0XHRcdFx0bWVzc2FnZVxuXHRcdFx0XHRcdCk7XG5cdFx0XHRcdH0pXG5cdFx0XHQpXG5cdFx0KVxuXHQpO1xufVxuXG4vKipcbiAqIFRoZSBBTVAgVG9nZ2xlIGNvbXBvbmVudCwgY29tcG9zZWQgd2l0aCB0aGUgZW5hYmxlZFN0YXR1cyBhbmQgYSBjYWxsYmFjayBmb3Igd2hlbiBpdCdzIGNoYW5nZWQuXG4gKlxuICogQHJldHVybiB7T2JqZWN0fSBUaGUgY29tcG9zZWQgQU1QIHRvZ2dsZS5cbiAqL1xuZnVuY3Rpb24gQ29tcG9zZWRBTVBUb2dnbGUoKSB7XG5cdHJldHVybiBjb21wb3NlKFt3aXRoU2VsZWN0KGZ1bmN0aW9uIChzZWxlY3QpIHtcblx0XHQvKipcbiAgICogR2V0cyB0aGUgQU1QIGVuYWJsZWQgc3RhdHVzLlxuICAgKlxuICAgKiBVc2VzIHNlbGVjdCBmcm9tIHRoZSBlbmNsb3NpbmcgZnVuY3Rpb24gdG8gZ2V0IHRoZSBtZXRhIHZhbHVlLlxuICAgKiBJZiBpdCBkb2Vzbid0IGV4aXN0LCBpdCB1c2VzIHRoZSBkZWZhdWx0IHZhbHVlLlxuICAgKiBUaGlzIGFwcGxpZXMgZXNwZWNpYWxseSBmb3IgYSBuZXcgcG9zdCwgd2hlcmUgdGhlcmUgcHJvYmFibHkgd29uJ3QgYmUgYSBtZXRhIHZhbHVlIHlldC5cbiAgICpcbiAgICogQHJldHVybiB7c3RyaW5nfSBFbmFibGVkIHN0YXR1cywgZWl0aGVyICdlbmFibGVkJyBvciAnZGlzYWJsZWQnLlxuICAgKi9cblx0XHR2YXIgZ2V0RW5hYmxlZFN0YXR1cyA9IGZ1bmN0aW9uIGdldEVuYWJsZWRTdGF0dXMoKSB7XG5cdFx0XHR2YXIgbWV0YSA9IHNlbGVjdCgnY29yZS9lZGl0b3InKS5nZXRFZGl0ZWRQb3N0QXR0cmlidXRlKCdtZXRhJyk7XG5cdFx0XHRpZiAobWV0YSAmJiBtZXRhLmFtcF9zdGF0dXMgJiYgcG9zc2libGVTdGF0aS5pbmNsdWRlcyhtZXRhLmFtcF9zdGF0dXMpKSB7XG5cdFx0XHRcdHJldHVybiBtZXRhLmFtcF9zdGF0dXM7XG5cdFx0XHR9XG5cdFx0XHRyZXR1cm4gZGVmYXVsdFN0YXR1cztcblx0XHR9O1xuXG5cdFx0cmV0dXJuIHsgZW5hYmxlZFN0YXR1czogZ2V0RW5hYmxlZFN0YXR1cygpIH07XG5cdH0pLCB3aXRoRGlzcGF0Y2goZnVuY3Rpb24gKGRpc3BhdGNoKSB7XG5cdFx0cmV0dXJuIHtcblx0XHRcdG9uQW1wQ2hhbmdlOiBmdW5jdGlvbiBvbkFtcENoYW5nZShlbmFibGVkU3RhdHVzKSB7XG5cdFx0XHRcdHZhciBuZXdTdGF0dXMgPSAnZW5hYmxlZCcgPT09IGVuYWJsZWRTdGF0dXMgPyAnZGlzYWJsZWQnIDogJ2VuYWJsZWQnO1xuXHRcdFx0XHRkaXNwYXRjaCgnY29yZS9lZGl0b3InKS5lZGl0UG9zdCh7IG1ldGE6IHsgYW1wX3N0YXR1czogbmV3U3RhdHVzIH0gfSk7XG5cdFx0XHR9XG5cdFx0fTtcblx0fSksIHdpdGhJbnN0YW5jZUlkXSkoQU1QVG9nZ2xlKTtcbn1cblxuZXhwb3J0IGRlZmF1bHQgd3AucGx1Z2lucy5yZWdpc3RlclBsdWdpbignYW1wJywge1xuXHRpY29uOiAnaGlkZGVuJyxcblx0cmVuZGVyOiBDb21wb3NlZEFNUFRvZ2dsZSgpXG59KTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL2Fzc2V0cy9zcmMvYW1wLWJsb2NrLWVkaXRvci10b2dnbGUuanNcbi8vIG1vZHVsZSBpZCA9IDEzXG4vLyBtb2R1bGUgY2h1bmtzID0gNCJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///13\n");
73
+
74
+ /***/ })
75
+
76
+ /******/ });
assets/js/amp-block-validation.js ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Validates blocks for AMP compatibility.
3
+ *
4
+ * This uses the REST API response from saving a page to find validation errors.
5
+ * If one exists for a block, it display it inline with a Notice component.
6
+ */
7
+
8
+ /* exported ampBlockValidation */
9
+ /* global wp, _ */
10
+ var ampBlockValidation = ( function() { // eslint-disable-line no-unused-vars
11
+ 'use strict';
12
+
13
+ var module = {
14
+
15
+ /**
16
+ * Data exported from server.
17
+ *
18
+ * @param {Object}
19
+ */
20
+ data: {
21
+ i18n: {},
22
+ ampValidityRestField: '',
23
+ isSanitizationAutoAccepted: false
24
+ },
25
+
26
+ /**
27
+ * Name of the store.
28
+ *
29
+ * @param {string}
30
+ */
31
+ storeName: 'amp/blockValidation',
32
+
33
+ /**
34
+ * Holds the last states which are used for comparisons.
35
+ *
36
+ * @param {Object}
37
+ */
38
+ lastStates: {
39
+ noticesAreReset: false,
40
+ validationErrors: [],
41
+ blockOrder: [],
42
+ blockValidationErrors: {}
43
+ },
44
+
45
+ /**
46
+ * Boot module.
47
+ *
48
+ * @param {Object} data - Module data.
49
+ * @return {void}
50
+ */
51
+ boot: function boot( data ) {
52
+ module.data = data;
53
+
54
+ wp.i18n.setLocaleData( module.data.i18n, 'amp' );
55
+
56
+ wp.hooks.addFilter(
57
+ 'editor.BlockEdit',
58
+ 'amp/add-notice',
59
+ module.conditionallyAddNotice,
60
+ 99 // eslint-disable-line
61
+ );
62
+
63
+ module.store = module.registerStore();
64
+
65
+ wp.data.subscribe( module.handleValidationErrorsStateChange );
66
+ },
67
+
68
+ /**
69
+ * Register store.
70
+ *
71
+ * @return {Object} Store.
72
+ */
73
+ registerStore: function registerStore() {
74
+ return wp.data.registerStore( module.storeName, {
75
+ reducer: function( _state, action ) {
76
+ var state = _state || {
77
+ blockValidationErrorsByClientId: {}
78
+ };
79
+
80
+ switch ( action.type ) {
81
+ case 'UPDATE_BLOCKS_VALIDATION_ERRORS':
82
+ return _.extend( {}, state, {
83
+ blockValidationErrorsByClientId: action.blockValidationErrorsByClientId
84
+ } );
85
+ default:
86
+ return state;
87
+ }
88
+ },
89
+ actions: {
90
+ updateBlocksValidationErrors: function( blockValidationErrorsByClientId ) {
91
+ return {
92
+ type: 'UPDATE_BLOCKS_VALIDATION_ERRORS',
93
+ blockValidationErrorsByClientId: blockValidationErrorsByClientId
94
+ };
95
+ }
96
+ },
97
+ selectors: {
98
+ getBlockValidationErrors: function( state, clientId ) {
99
+ return state.blockValidationErrorsByClientId[ clientId ] || [];
100
+ }
101
+ }
102
+ } );
103
+ },
104
+
105
+ /**
106
+ * Checks if AMP is enabled for this post.
107
+ *
108
+ * @return {boolean} Returns true when the AMP toggle is on; else, false is returned.
109
+ */
110
+ isAMPEnabled: function isAMPEnabled() {
111
+ var meta = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' );
112
+ if ( meta && meta.amp_status && window.wpAmpEditor.possibleStati.includes( meta.amp_status ) ) {
113
+ return 'enabled' === meta.amp_status;
114
+ }
115
+ return window.wpAmpEditor.defaultStatus;
116
+ },
117
+
118
+ /**
119
+ * Checks if the validate errors state change handler should wait before processing.
120
+ *
121
+ * @return {boolean} Whether should wait.
122
+ */
123
+ waitToHandleStateChange: function waitToHandleStateChange() {
124
+ var currentPost;
125
+
126
+ // @todo Gutenberg currently is not persisting isDirty state if changes are made during save request. Block order mismatch.
127
+ // We can only align block validation errors with blocks in editor when in saved state, since only here will the blocks be aligned with the validation errors.
128
+ if ( wp.data.select( 'core/editor' ).isEditedPostDirty() || ( ! wp.data.select( 'core/editor' ).isEditedPostDirty() && wp.data.select( 'core/editor' ).isEditedPostNew() ) ) {
129
+ return true;
130
+ }
131
+
132
+ // Wait for the current post to be set up.
133
+ currentPost = wp.data.select( 'core/editor' ).getCurrentPost();
134
+ if ( ! currentPost.hasOwnProperty( 'id' ) ) {
135
+ return true;
136
+ }
137
+
138
+ return false;
139
+ },
140
+
141
+ /**
142
+ * Handle state change regarding validation errors.
143
+ *
144
+ * This is essentially a JS implementation of \AMP_Validation_Manager::print_edit_form_validation_status() in PHP.
145
+ *
146
+ * @return {void}
147
+ */
148
+ handleValidationErrorsStateChange: function handleValidationErrorsStateChange() {
149
+ var currentPost, validationErrors, blockValidationErrors, noticeOptions, noticeMessage, blockErrorCount, ampValidity, rejectedErrors;
150
+
151
+ if ( ! module.isAMPEnabled() ) {
152
+ if ( ! module.lastStates.noticesAreReset ) {
153
+ module.lastStates.validationErrors = [];
154
+ module.lastStates.noticesAreReset = true;
155
+ module.resetWarningNotice();
156
+ module.resetBlockNotices();
157
+ }
158
+ return;
159
+ }
160
+
161
+ if ( module.waitToHandleStateChange() ) {
162
+ return;
163
+ }
164
+
165
+ currentPost = wp.data.select( 'core/editor' ).getCurrentPost();
166
+ ampValidity = currentPost[ module.data.ampValidityRestField ] || {};
167
+
168
+ // Show all validation errors which have not been explicitly acknowledged as accepted.
169
+ validationErrors = _.map(
170
+ _.filter( ampValidity.results, function( result ) {
171
+ // @todo Show VALIDATION_ERROR_ACK_REJECTED_STATUS differently since moderated?
172
+ return (
173
+ 0 /* \AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_REJECTED_STATUS */ === result.status ||
174
+ 1 /* \AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_ACCEPTED_STATUS */ === result.status ||
175
+ 2 /* \AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACK_REJECTED_STATUS */ === result.status // eslint-disable-line no-magic-numbers
176
+ );
177
+ } ),
178
+ function( result ) {
179
+ return result.error;
180
+ }
181
+ );
182
+
183
+ // Short-circuit if there was no change to the validation errors.
184
+ if ( ! module.didValidationErrorsChange( validationErrors ) ) {
185
+ if ( ! validationErrors.length && ! module.lastStates.noticesAreReset ) {
186
+ module.lastStates.noticesAreReset = true;
187
+ module.resetWarningNotice();
188
+ }
189
+ return;
190
+ }
191
+ module.lastStates.validationErrors = validationErrors;
192
+ module.lastStates.noticesAreReset = false;
193
+
194
+ // Remove any existing notice.
195
+ module.resetWarningNotice();
196
+
197
+ noticeMessage = wp.i18n.sprintf(
198
+ wp.i18n._n(
199
+ 'There is %s issue from AMP validation which needs review.',
200
+ 'There are %s issues from AMP validation which need review.',
201
+ validationErrors.length,
202
+ 'amp'
203
+ ),
204
+ validationErrors.length
205
+ );
206
+
207
+ try {
208
+ blockValidationErrors = module.getBlocksValidationErrors();
209
+ module.lastStates.blockValidationErrors = blockValidationErrors.byClientId;
210
+ wp.data.dispatch( module.storeName ).updateBlocksValidationErrors( blockValidationErrors.byClientId );
211
+
212
+ blockErrorCount = validationErrors.length - blockValidationErrors.other.length;
213
+ if ( blockErrorCount > 0 ) {
214
+ noticeMessage += ' ' + wp.i18n.sprintf(
215
+ wp.i18n._n(
216
+ 'And %s is directly due to content here.',
217
+ 'And %s are directly due to content here.',
218
+ blockErrorCount,
219
+ 'amp'
220
+ ),
221
+ blockErrorCount
222
+ );
223
+ } else {
224
+ noticeMessage += ' ' + wp.i18n.sprintf(
225
+ wp.i18n._n(
226
+ 'But it is not directly due to content here.',
227
+ 'But none are directly due to content here.',
228
+ validationErrors.length,
229
+ 'amp'
230
+ ),
231
+ validationErrors.length
232
+ );
233
+ }
234
+ } catch ( e ) {
235
+ // Clear out block validation errors in case the block sand errors cannot be aligned.
236
+ module.resetBlockNotices();
237
+
238
+ noticeMessage += ' ' + wp.i18n._n(
239
+ 'It may not be due to content here.',
240
+ 'Some may be due to content here.',
241
+ validationErrors.length,
242
+ 'amp'
243
+ );
244
+ }
245
+
246
+ rejectedErrors = _.filter( ampValidity.results, function( result ) {
247
+ return (
248
+ 0 /* \AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_REJECTED_STATUS */ === result.status ||
249
+ 2 /* \AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACK_REJECTED_STATUS */ === result.status // eslint-disable-line no-magic-numbers
250
+ );
251
+ } );
252
+
253
+ noticeMessage += ' ';
254
+ // Auto-acceptance is from either checking 'Automatically accept sanitization...' or from being in Native mode.
255
+ if ( module.data.isSanitizationAutoAccepted ) {
256
+ if ( 0 === rejectedErrors.length ) {
257
+ noticeMessage += wp.i18n.__( 'However, your site is configured to automatically accept sanitization of the offending markup.', 'amp' );
258
+ } else {
259
+ noticeMessage += wp.i18n._n(
260
+ 'Your site is configured to automatically accept sanitization errors, but this error could be from when auto-acceptance was not selected, or from manually rejecting an error.',
261
+ 'Your site is configured to automatically accept sanitization errors, but these errors could be from when auto-acceptance was not selected, or from manually rejecting an error.',
262
+ validationErrors.length,
263
+ 'amp'
264
+ );
265
+ }
266
+ } else {
267
+ noticeMessage += wp.i18n.__( 'Non-accepted validation errors prevent AMP from being served, and the user will be redirected to the non-AMP version.', 'amp' );
268
+ }
269
+
270
+ noticeOptions = {
271
+ id: 'amp-errors-notice'
272
+ };
273
+ if ( ampValidity.review_link ) {
274
+ noticeOptions.actions = [
275
+ {
276
+ label: wp.i18n.__( 'Review issues', 'amp' ),
277
+ url: ampValidity.review_link
278
+ }
279
+ ];
280
+ }
281
+
282
+ // Display notice if there were validation errors.
283
+ if ( validationErrors.length > 0 ) {
284
+ wp.data.dispatch( 'core/notices' ).createNotice( 'warning', noticeMessage, noticeOptions );
285
+ }
286
+
287
+ module.validationWarningNoticeId = noticeOptions.id;
288
+ },
289
+
290
+ /**
291
+ * Checks if the validation errors have changed.
292
+ *
293
+ * @param {Object[]} validationErrors A list of validation errors.
294
+ * @return {boolean|*} Returns true when the validation errors change.
295
+ */
296
+ didValidationErrorsChange: function didValidationErrorsChange( validationErrors ) {
297
+ if ( module.areBlocksOutOfSync() ) {
298
+ module.lastStates.validationErrors = [];
299
+ }
300
+
301
+ return (
302
+ module.lastStates.validationErrors.length !== validationErrors.length
303
+ ||
304
+ ( validationErrors && ! _.isEqual( module.lastStates.validationErrors, validationErrors ) )
305
+ );
306
+ },
307
+
308
+ /**
309
+ * Checks if the block order is out of sync.
310
+ *
311
+ * Block change on page load and can get out of sync during normal editing and saving processes. This method gives a check to determine if an "out of sync" condition occurred.
312
+ *
313
+ * @return {boolean} Whether out of sync.
314
+ */
315
+ areBlocksOutOfSync: function areBlocksOutOfSync() {
316
+ var blockOrder = wp.data.select( 'core/editor' ).getBlockOrder();
317
+ if ( module.lastStates.blockOrder.length !== blockOrder.length || ! _.isEqual( module.lastStates.blockOrder, blockOrder ) ) {
318
+ module.lastStates.blockOrder = blockOrder;
319
+ return true;
320
+ }
321
+
322
+ return false;
323
+ },
324
+
325
+ /**
326
+ * Resets the validation warning notice.
327
+ *
328
+ * @return {void}
329
+ */
330
+ resetWarningNotice: function resetWarningNotice() {
331
+ if ( module.validationWarningNoticeId ) {
332
+ wp.data.dispatch( 'core/notices' ).removeNotice( module.validationWarningNoticeId );
333
+ module.validationWarningNoticeId = null;
334
+ }
335
+ },
336
+
337
+ /**
338
+ * Resets the block level validation errors.
339
+ *
340
+ * @return {void}
341
+ */
342
+ resetBlockNotices: function resetBlockNotices() {
343
+ wp.data.dispatch( module.storeName ).updateBlocksValidationErrors( {} );
344
+ },
345
+
346
+ /**
347
+ * Get flattened block order.
348
+ *
349
+ * @param {Object[]} blocks - List of blocks which maty have nested blocks inside them.
350
+ * @return {string[]} Block IDs in flattened order.
351
+ */
352
+ getFlattenedBlockOrder: function getFlattenedBlockOrder( blocks ) {
353
+ var blockOrder = [];
354
+ _.each( blocks, function( block ) {
355
+ blockOrder.push( block.clientId );
356
+ if ( block.innerBlocks.length > 0 ) {
357
+ Array.prototype.push.apply( blockOrder, module.getFlattenedBlockOrder( block.innerBlocks ) );
358
+ }
359
+ } );
360
+ return blockOrder;
361
+ },
362
+
363
+ /**
364
+ * Update blocks' validation errors in the store.
365
+ *
366
+ * @return {Object} Validation errors grouped by block ID other ones.
367
+ */
368
+ getBlocksValidationErrors: function getBlocksValidationErrors() {
369
+ var acceptedStatus, blockValidationErrorsByClientId, editorSelect, currentPost, blockOrder, validationErrors, otherValidationErrors;
370
+ acceptedStatus = 3; // eslint-disable-line no-magic-numbers
371
+ editorSelect = wp.data.select( 'core/editor' );
372
+ currentPost = editorSelect.getCurrentPost();
373
+ validationErrors = _.map(
374
+ _.filter( currentPost[ module.data.ampValidityRestField ].results, function( result ) {
375
+ return result.term_status !== acceptedStatus; // If not accepted by the user.
376
+ } ),
377
+ function( result ) {
378
+ return result.error;
379
+ }
380
+ );
381
+ blockOrder = module.getFlattenedBlockOrder( editorSelect.getBlocks() );
382
+
383
+ otherValidationErrors = [];
384
+ blockValidationErrorsByClientId = {};
385
+ _.each( blockOrder, function( clientId ) {
386
+ blockValidationErrorsByClientId[ clientId ] = [];
387
+ } );
388
+
389
+ _.each( validationErrors, function( validationError ) {
390
+ var i, source, clientId, block, matched;
391
+ if ( ! validationError.sources ) {
392
+ otherValidationErrors.push( validationError );
393
+ return;
394
+ }
395
+
396
+ // Find the inner-most nested block source only; ignore any nested blocks.
397
+ matched = false;
398
+ for ( i = validationError.sources.length - 1; 0 <= i; i-- ) {
399
+ source = validationError.sources[ i ];
400
+
401
+ // Skip sources that are not for blocks.
402
+ if ( ! source.block_name || _.isUndefined( source.block_content_index ) || currentPost.id !== source.post_id ) {
403
+ continue;
404
+ }
405
+
406
+ // Look up the block ID by index, assuming the blocks of content in the editor are the same as blocks rendered on frontend.
407
+ clientId = blockOrder[ source.block_content_index ];
408
+ if ( _.isUndefined( clientId ) ) {
409
+ throw new Error( 'undefined_block_index' );
410
+ }
411
+
412
+ // Sanity check that block exists for clientId.
413
+ block = editorSelect.getBlock( clientId );
414
+ if ( ! block ) {
415
+ throw new Error( 'block_lookup_failure' );
416
+ }
417
+
418
+ // Check the block type in case a block is dynamically added/removed via the_content filter to cause alignment error.
419
+ if ( block.name !== source.block_name ) {
420
+ throw new Error( 'ordered_block_alignment_mismatch' );
421
+ }
422
+
423
+ blockValidationErrorsByClientId[ clientId ].push( validationError );
424
+ matched = true;
425
+
426
+ // Stop looking for sources, since we aren't looking for parent blocks.
427
+ break;
428
+ }
429
+
430
+ if ( ! matched ) {
431
+ otherValidationErrors.push( validationError );
432
+ }
433
+ } );
434
+
435
+ return {
436
+ byClientId: blockValidationErrorsByClientId,
437
+ other: otherValidationErrors
438
+ };
439
+ },
440
+
441
+ /**
442
+ * Get message for validation error.
443
+ *
444
+ * @param {Object} validationError - Validation error.
445
+ * @param {string} validationError.code - Validation error code.
446
+ * @param {string} [validationError.node_name] - Node name.
447
+ * @param {string} [validationError.message] - Validation error message.
448
+ * @return {wp.element.Component[]|string[]} Validation error message.
449
+ */
450
+ getValidationErrorMessage: function getValidationErrorMessage( validationError ) {
451
+ if ( validationError.message ) {
452
+ return validationError.message;
453
+ }
454
+ if ( 'invalid_element' === validationError.code && validationError.node_name ) {
455
+ return [
456
+ wp.i18n.__( 'Invalid element: ' ),
457
+ wp.element.createElement( 'code', { key: 'name' }, validationError.node_name )
458
+ ];
459
+ } else if ( 'invalid_attribute' === validationError.code && validationError.node_name ) {
460
+ return [
461
+ wp.i18n.__( 'Invalid attribute: ' ),
462
+ wp.element.createElement( 'code', { key: 'name' }, validationError.parent_name ? wp.i18n.sprintf( '%s[%s]', validationError.parent_name, validationError.node_name ) : validationError.node_name )
463
+ ];
464
+ }
465
+ return [
466
+ wp.i18n.__( 'Error code: ', 'amp' ),
467
+ wp.element.createElement( 'code', { key: 'name' }, validationError.code || wp.i18n.__( 'unknown' ) )
468
+ ];
469
+ },
470
+
471
+ /**
472
+ * Wraps the edit() method of a block, and conditionally adds a Notice.
473
+ *
474
+ * @param {Function} BlockEdit - The original edit() method of the block.
475
+ * @return {Function} The edit() method, conditionally wrapped in a notice for AMP validation error(s).
476
+ */
477
+ conditionallyAddNotice: function conditionallyAddNotice( BlockEdit ) {
478
+ return function( ownProps ) {
479
+ var validationErrors,
480
+ mergedProps;
481
+ function AmpNoticeBlockEdit( props ) {
482
+ var edit, details;
483
+ edit = wp.element.createElement(
484
+ BlockEdit,
485
+ props
486
+ );
487
+
488
+ if ( 0 === props.ampBlockValidationErrors.length ) {
489
+ return edit;
490
+ }
491
+
492
+ details = wp.element.createElement( 'details', { className: 'amp-block-validation-errors' }, [
493
+ wp.element.createElement( 'summary', { key: 'summary', className: 'amp-block-validation-errors__summary' }, wp.i18n.sprintf(
494
+ wp.i18n._n(
495
+ 'There is %s issue from AMP validation.',
496
+ 'There are %s issues from AMP validation.',
497
+ props.ampBlockValidationErrors.length,
498
+ 'amp'
499
+ ),
500
+ props.ampBlockValidationErrors.length
501
+ ) ),
502
+ wp.element.createElement(
503
+ 'ul',
504
+ { key: 'list', className: 'amp-block-validation-errors__list' },
505
+ _.map( props.ampBlockValidationErrors, function( error, key ) {
506
+ return wp.element.createElement( 'li', { key: key }, module.getValidationErrorMessage( error ) );
507
+ } )
508
+ )
509
+ ] );
510
+
511
+ return wp.element.createElement(
512
+ wp.element.Fragment, {},
513
+ wp.element.createElement(
514
+ wp.components.Notice,
515
+ {
516
+ status: 'warning',
517
+ isDismissible: false
518
+ },
519
+ details
520
+ ),
521
+ edit
522
+ );
523
+ }
524
+
525
+ if ( ! module.lastStates.blockValidationErrors[ ownProps.clientId ] ) {
526
+ validationErrors = wp.data.select( module.storeName ).getBlockValidationErrors( ownProps.clientId );
527
+ module.lastStates.blockValidationErrors[ ownProps.clientId ] = validationErrors;
528
+ }
529
+
530
+ mergedProps = _.extend( {}, ownProps, {
531
+ ampBlockValidationErrors: module.lastStates.blockValidationErrors[ ownProps.clientId ]
532
+ } );
533
+
534
+ return AmpNoticeBlockEdit( mergedProps );
535
+ };
536
+ }
537
+ };
538
+
539
+ return module;
540
+ }() );
assets/js/amp-blocks-compiled.js ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /******/ (function(modules) { // webpackBootstrap
2
+ /******/ // The module cache
3
+ /******/ var installedModules = {};
4
+ /******/
5
+ /******/ // The require function
6
+ /******/ function __webpack_require__(moduleId) {
7
+ /******/
8
+ /******/ // Check if module is in cache
9
+ /******/ if(installedModules[moduleId]) {
10
+ /******/ return installedModules[moduleId].exports;
11
+ /******/ }
12
+ /******/ // Create a new module (and put it into the cache)
13
+ /******/ var module = installedModules[moduleId] = {
14
+ /******/ i: moduleId,
15
+ /******/ l: false,
16
+ /******/ exports: {}
17
+ /******/ };
18
+ /******/
19
+ /******/ // Execute the module function
20
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21
+ /******/
22
+ /******/ // Flag the module as loaded
23
+ /******/ module.l = true;
24
+ /******/
25
+ /******/ // Return the exports of the module
26
+ /******/ return module.exports;
27
+ /******/ }
28
+ /******/
29
+ /******/
30
+ /******/ // expose the modules object (__webpack_modules__)
31
+ /******/ __webpack_require__.m = modules;
32
+ /******/
33
+ /******/ // expose the module cache
34
+ /******/ __webpack_require__.c = installedModules;
35
+ /******/
36
+ /******/ // define getter function for harmony exports
37
+ /******/ __webpack_require__.d = function(exports, name, getter) {
38
+ /******/ if(!__webpack_require__.o(exports, name)) {
39
+ /******/ Object.defineProperty(exports, name, {
40
+ /******/ configurable: false,
41
+ /******/ enumerable: true,
42
+ /******/ get: getter
43
+ /******/ });
44
+ /******/ }
45
+ /******/ };
46
+ /******/
47
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
48
+ /******/ __webpack_require__.n = function(module) {
49
+ /******/ var getter = module && module.__esModule ?
50
+ /******/ function getDefault() { return module['default']; } :
51
+ /******/ function getModuleExports() { return module; };
52
+ /******/ __webpack_require__.d(getter, 'a', getter);
53
+ /******/ return getter;
54
+ /******/ };
55
+ /******/
56
+ /******/ // Object.prototype.hasOwnProperty.call
57
+ /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58
+ /******/
59
+ /******/ // __webpack_public_path__
60
+ /******/ __webpack_require__.p = "";
61
+ /******/
62
+ /******/ // Load entry module and return exports
63
+ /******/ return __webpack_require__(__webpack_require__.s = 2);
64
+ /******/ })
65
+ /************************************************************************/
66
+ /******/ ([
67
+ /* 0 */
68
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
69
+
70
+ "use strict";
71
+ eval("/* harmony export (immutable) */ __webpack_exports__[\"b\"] = getMediaPlaceholder;\n/* harmony export (immutable) */ __webpack_exports__[\"a\"] = getLayoutControls;\nvar __ = wp.i18n.__;\nvar _wp$components = wp.components,\n TextControl = _wp$components.TextControl,\n SelectControl = _wp$components.SelectControl,\n Notice = _wp$components.Notice,\n Placeholder = _wp$components.Placeholder;\n\n/**\n * Display media placeholder.\n *\n * @param {string} name Block's name.\n * @param {string|boolean} url URL.\n * @return {XML} Placeholder.\n */\n\nfunction getMediaPlaceholder(name, url) {\n\treturn wp.element.createElement(\n\t\tPlaceholder,\n\t\t{ label: name },\n\t\twp.element.createElement(\n\t\t\t\"p\",\n\t\t\t{ className: \"components-placeholder__error\" },\n\t\t\turl\n\t\t),\n\t\twp.element.createElement(\n\t\t\t\"p\",\n\t\t\t{ className: \"components-placeholder__error\" },\n\t\t\t__('Previews for this are unavailable in the editor, sorry!', 'amp')\n\t\t)\n\t);\n}\n\n/**\n * Layout controls for AMP blocks' attributes: layout, width, height.\n *\n * @param {Object} props Props.\n * @param {Array} ampLayoutOptions Layout options.\n * @return {[XML,*,XML,*,XML]} Controls.\n */\nfunction getLayoutControls(props, ampLayoutOptions) {\n\t// @todo Move getting ampLayoutOptions to utils as well.\n\tvar attributes = props.attributes,\n\t setAttributes = props.setAttributes;\n\tvar ampLayout = attributes.ampLayout,\n\t height = attributes.height,\n\t width = attributes.width;\n\n\tvar showHeightNotice = !height && ('fixed' === ampLayout || 'fixed-height' === ampLayout);\n\tvar showWidthNotice = !width && 'fixed' === ampLayout;\n\n\treturn [wp.element.createElement(SelectControl, {\n\t\tkey: \"ampLayout\",\n\t\tlabel: __('Layout', 'amp'),\n\t\tvalue: ampLayout,\n\t\toptions: ampLayoutOptions,\n\t\tonChange: function onChange(value) {\n\t\t\treturn setAttributes({ ampLayout: value });\n\t\t}\n\t}), showWidthNotice && wp.element.createElement(\n\t\tNotice,\n\t\t{ key: \"showWidthNotice\", status: \"error\", isDismissible: false },\n\t\twp.i18n.sprintf(\n\t\t/* translators: %s is the layout name */\n\t\t__('Width is required for %s layout', 'amp'), ampLayout)\n\t), wp.element.createElement(TextControl, {\n\t\tkey: \"width\",\n\t\ttype: \"number\",\n\t\tlabel: __('Width (px)', 'amp'),\n\t\tvalue: width !== undefined ? width : '',\n\t\tonChange: function onChange(value) {\n\t\t\treturn setAttributes({ width: value });\n\t\t}\n\t}), showHeightNotice && wp.element.createElement(\n\t\tNotice,\n\t\t{ key: \"showHeightNotice\", status: \"error\", isDismissible: false },\n\t\twp.i18n.sprintf(\n\t\t/* translators: %s is the layout name */\n\t\t__('Height is required for %s layout', 'amp'), ampLayout)\n\t), wp.element.createElement(TextControl, {\n\t\tkey: \"height\",\n\t\ttype: \"number\",\n\t\tlabel: __('Height (px)', 'amp'),\n\t\tvalue: height,\n\t\tonChange: function onChange(value) {\n\t\t\treturn setAttributes({ height: value });\n\t\t}\n\t})];\n}//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy91dGlscy5qcz8wOGNiIl0sInNvdXJjZXNDb250ZW50IjpbInZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgX3dwJGNvbXBvbmVudHMgPSB3cC5jb21wb25lbnRzLFxuICAgIFRleHRDb250cm9sID0gX3dwJGNvbXBvbmVudHMuVGV4dENvbnRyb2wsXG4gICAgU2VsZWN0Q29udHJvbCA9IF93cCRjb21wb25lbnRzLlNlbGVjdENvbnRyb2wsXG4gICAgTm90aWNlID0gX3dwJGNvbXBvbmVudHMuTm90aWNlLFxuICAgIFBsYWNlaG9sZGVyID0gX3dwJGNvbXBvbmVudHMuUGxhY2Vob2xkZXI7XG5cbi8qKlxuICogRGlzcGxheSBtZWRpYSBwbGFjZWhvbGRlci5cbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gbmFtZSBCbG9jaydzIG5hbWUuXG4gKiBAcGFyYW0ge3N0cmluZ3xib29sZWFufSB1cmwgVVJMLlxuICogQHJldHVybiB7WE1MfSBQbGFjZWhvbGRlci5cbiAqL1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0TWVkaWFQbGFjZWhvbGRlcihuYW1lLCB1cmwpIHtcblx0cmV0dXJuIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRQbGFjZWhvbGRlcixcblx0XHR7IGxhYmVsOiBuYW1lIH0sXG5cdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XCJwXCIsXG5cdFx0XHR7IGNsYXNzTmFtZTogXCJjb21wb25lbnRzLXBsYWNlaG9sZGVyX19lcnJvclwiIH0sXG5cdFx0XHR1cmxcblx0XHQpLFxuXHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFwicFwiLFxuXHRcdFx0eyBjbGFzc05hbWU6IFwiY29tcG9uZW50cy1wbGFjZWhvbGRlcl9fZXJyb3JcIiB9LFxuXHRcdFx0X18oJ1ByZXZpZXdzIGZvciB0aGlzIGFyZSB1bmF2YWlsYWJsZSBpbiB0aGUgZWRpdG9yLCBzb3JyeSEnLCAnYW1wJylcblx0XHQpXG5cdCk7XG59XG5cbi8qKlxuICogTGF5b3V0IGNvbnRyb2xzIGZvciBBTVAgYmxvY2tzJyBhdHRyaWJ1dGVzOiBsYXlvdXQsIHdpZHRoLCBoZWlnaHQuXG4gKlxuICogQHBhcmFtIHtPYmplY3R9IHByb3BzIFByb3BzLlxuICogQHBhcmFtIHtBcnJheX0gYW1wTGF5b3V0T3B0aW9ucyBMYXlvdXQgb3B0aW9ucy5cbiAqIEByZXR1cm4ge1tYTUwsKixYTUwsKixYTUxdfSBDb250cm9scy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldExheW91dENvbnRyb2xzKHByb3BzLCBhbXBMYXlvdXRPcHRpb25zKSB7XG5cdC8vIEB0b2RvIE1vdmUgZ2V0dGluZyBhbXBMYXlvdXRPcHRpb25zIHRvIHV0aWxzIGFzIHdlbGwuXG5cdHZhciBhdHRyaWJ1dGVzID0gcHJvcHMuYXR0cmlidXRlcyxcblx0ICAgIHNldEF0dHJpYnV0ZXMgPSBwcm9wcy5zZXRBdHRyaWJ1dGVzO1xuXHR2YXIgYW1wTGF5b3V0ID0gYXR0cmlidXRlcy5hbXBMYXlvdXQsXG5cdCAgICBoZWlnaHQgPSBhdHRyaWJ1dGVzLmhlaWdodCxcblx0ICAgIHdpZHRoID0gYXR0cmlidXRlcy53aWR0aDtcblxuXHR2YXIgc2hvd0hlaWdodE5vdGljZSA9ICFoZWlnaHQgJiYgKCdmaXhlZCcgPT09IGFtcExheW91dCB8fCAnZml4ZWQtaGVpZ2h0JyA9PT0gYW1wTGF5b3V0KTtcblx0dmFyIHNob3dXaWR0aE5vdGljZSA9ICF3aWR0aCAmJiAnZml4ZWQnID09PSBhbXBMYXlvdXQ7XG5cblx0cmV0dXJuIFt3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoU2VsZWN0Q29udHJvbCwge1xuXHRcdGtleTogXCJhbXBMYXlvdXRcIixcblx0XHRsYWJlbDogX18oJ0xheW91dCcsICdhbXAnKSxcblx0XHR2YWx1ZTogYW1wTGF5b3V0LFxuXHRcdG9wdGlvbnM6IGFtcExheW91dE9wdGlvbnMsXG5cdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGFtcExheW91dDogdmFsdWUgfSk7XG5cdFx0fVxuXHR9KSwgc2hvd1dpZHRoTm90aWNlICYmIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHROb3RpY2UsXG5cdFx0eyBrZXk6IFwic2hvd1dpZHRoTm90aWNlXCIsIHN0YXR1czogXCJlcnJvclwiLCBpc0Rpc21pc3NpYmxlOiBmYWxzZSB9LFxuXHRcdHdwLmkxOG4uc3ByaW50Zihcblx0XHQvKiB0cmFuc2xhdG9yczogJXMgaXMgdGhlIGxheW91dCBuYW1lICovXG5cdFx0X18oJ1dpZHRoIGlzIHJlcXVpcmVkIGZvciAlcyBsYXlvdXQnLCAnYW1wJyksIGFtcExheW91dClcblx0KSwgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0a2V5OiBcIndpZHRoXCIsXG5cdFx0dHlwZTogXCJudW1iZXJcIixcblx0XHRsYWJlbDogX18oJ1dpZHRoIChweCknLCAnYW1wJyksXG5cdFx0dmFsdWU6IHdpZHRoICE9PSB1bmRlZmluZWQgPyB3aWR0aCA6ICcnLFxuXHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyB3aWR0aDogdmFsdWUgfSk7XG5cdFx0fVxuXHR9KSwgc2hvd0hlaWdodE5vdGljZSAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0Tm90aWNlLFxuXHRcdHsga2V5OiBcInNob3dIZWlnaHROb3RpY2VcIiwgc3RhdHVzOiBcImVycm9yXCIsIGlzRGlzbWlzc2libGU6IGZhbHNlIH0sXG5cdFx0d3AuaTE4bi5zcHJpbnRmKFxuXHRcdC8qIHRyYW5zbGF0b3JzOiAlcyBpcyB0aGUgbGF5b3V0IG5hbWUgKi9cblx0XHRfXygnSGVpZ2h0IGlzIHJlcXVpcmVkIGZvciAlcyBsYXlvdXQnLCAnYW1wJyksIGFtcExheW91dClcblx0KSwgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0a2V5OiBcImhlaWdodFwiLFxuXHRcdHR5cGU6IFwibnVtYmVyXCIsXG5cdFx0bGFiZWw6IF9fKCdIZWlnaHQgKHB4KScsICdhbXAnKSxcblx0XHR2YWx1ZTogaGVpZ2h0LFxuXHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBoZWlnaHQ6IHZhbHVlIH0pO1xuXHRcdH1cblx0fSldO1xufVxuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vYmxvY2tzL3V0aWxzLmpzXG4vLyBtb2R1bGUgaWQgPSAwXG4vLyBtb2R1bGUgY2h1bmtzID0gMCJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///0\n");
72
+
73
+ /***/ }),
74
+ /* 1 */,
75
+ /* 2 */
76
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
77
+
78
+ "use strict";
79
+ eval("Object.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__amp_mathml__ = __webpack_require__(3);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__amp_timeago__ = __webpack_require__(4);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__amp_o2_player__ = __webpack_require__(6);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__amp_ooyala_player__ = __webpack_require__(7);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__amp_reach_player__ = __webpack_require__(8);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__amp_springboard_player__ = __webpack_require__(9);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__amp_jwplayer__ = __webpack_require__(10);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__amp_brid_player__ = __webpack_require__(11);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_8__amp_ima_video__ = __webpack_require__(12);\n/**\n * Import blocks.\n */\n\n\n\n\n\n\n\n\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9pbmRleC5qcz84MTkzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogSW1wb3J0IGJsb2Nrcy5cbiAqL1xuaW1wb3J0ICcuL2FtcC1tYXRobWwnO1xuaW1wb3J0ICcuL2FtcC10aW1lYWdvJztcbmltcG9ydCAnLi9hbXAtbzItcGxheWVyJztcbmltcG9ydCAnLi9hbXAtb295YWxhLXBsYXllcic7XG5pbXBvcnQgJy4vYW1wLXJlYWNoLXBsYXllcic7XG5pbXBvcnQgJy4vYW1wLXNwcmluZ2JvYXJkLXBsYXllcic7XG5pbXBvcnQgJy4vYW1wLWp3cGxheWVyJztcbmltcG9ydCAnLi9hbXAtYnJpZC1wbGF5ZXInO1xuaW1wb3J0ICcuL2FtcC1pbWEtdmlkZW8nO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vYmxvY2tzL2luZGV4LmpzXG4vLyBtb2R1bGUgaWQgPSAyXG4vLyBtb2R1bGUgY2h1bmtzID0gMCJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOyIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///2\n");
80
+
81
+ /***/ }),
82
+ /* 3 */
83
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
84
+
85
+ "use strict";
86
+ eval("\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar PlainText = wp.editor.PlainText;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-mathml', {\n\ttitle: __('AMP MathML', 'amp'),\n\tcategory: 'common',\n\ticon: 'welcome-learn-more',\n\tkeywords: [__('Mathematical formula', 'amp'), __('Scientific content ', 'amp')],\n\n\tattributes: {\n\t\tdataFormula: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-mathml',\n\t\t\tattribute: 'data-formula'\n\t\t}\n\t},\n\n\tedit: function edit(_ref) {\n\t\tvar attributes = _ref.attributes,\n\t\t setAttributes = _ref.setAttributes;\n\t\tvar dataFormula = attributes.dataFormula;\n\n\n\t\treturn wp.element.createElement(PlainText, {\n\t\t\tkey: 'formula',\n\t\t\tvalue: dataFormula,\n\t\t\tplaceholder: __('Insert formula', 'amp'),\n\t\t\tonChange: function onChange(value) {\n\t\t\t\treturn setAttributes({ dataFormula: value });\n\t\t\t}\n\t\t});\n\t},\n\tsave: function save(_ref2) {\n\t\tvar attributes = _ref2.attributes;\n\n\t\tvar mathmlProps = {\n\t\t\t'data-formula': attributes.dataFormula,\n\t\t\tlayout: 'container'\n\t\t};\n\t\treturn wp.element.createElement('amp-mathml', mathmlProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9hbXAtbWF0aG1sL2luZGV4LmpzP2JhZjIiXSwic291cmNlc0NvbnRlbnQiOlsiXG4vKipcbiAqIEludGVybmFsIGJsb2NrIGxpYnJhcmllcy5cbiAqL1xudmFyIF9fID0gd3AuaTE4bi5fXztcbnZhciByZWdpc3RlckJsb2NrVHlwZSA9IHdwLmJsb2Nrcy5yZWdpc3RlckJsb2NrVHlwZTtcbnZhciBQbGFpblRleHQgPSB3cC5lZGl0b3IuUGxhaW5UZXh0O1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IHJlZ2lzdGVyQmxvY2tUeXBlKCdhbXAvYW1wLW1hdGhtbCcsIHtcblx0dGl0bGU6IF9fKCdBTVAgTWF0aE1MJywgJ2FtcCcpLFxuXHRjYXRlZ29yeTogJ2NvbW1vbicsXG5cdGljb246ICd3ZWxjb21lLWxlYXJuLW1vcmUnLFxuXHRrZXl3b3JkczogW19fKCdNYXRoZW1hdGljYWwgZm9ybXVsYScsICdhbXAnKSwgX18oJ1NjaWVudGlmaWMgY29udGVudCAnLCAnYW1wJyldLFxuXG5cdGF0dHJpYnV0ZXM6IHtcblx0XHRkYXRhRm9ybXVsYToge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLW1hdGhtbCcsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLWZvcm11bGEnXG5cdFx0fVxuXHR9LFxuXG5cdGVkaXQ6IGZ1bmN0aW9uIGVkaXQoX3JlZikge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gX3JlZi5hdHRyaWJ1dGVzLFxuXHRcdCAgICBzZXRBdHRyaWJ1dGVzID0gX3JlZi5zZXRBdHRyaWJ1dGVzO1xuXHRcdHZhciBkYXRhRm9ybXVsYSA9IGF0dHJpYnV0ZXMuZGF0YUZvcm11bGE7XG5cblxuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoUGxhaW5UZXh0LCB7XG5cdFx0XHRrZXk6ICdmb3JtdWxhJyxcblx0XHRcdHZhbHVlOiBkYXRhRm9ybXVsYSxcblx0XHRcdHBsYWNlaG9sZGVyOiBfXygnSW5zZXJ0IGZvcm11bGEnLCAnYW1wJyksXG5cdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhRm9ybXVsYTogdmFsdWUgfSk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH0sXG5cdHNhdmU6IGZ1bmN0aW9uIHNhdmUoX3JlZjIpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IF9yZWYyLmF0dHJpYnV0ZXM7XG5cblx0XHR2YXIgbWF0aG1sUHJvcHMgPSB7XG5cdFx0XHQnZGF0YS1mb3JtdWxhJzogYXR0cmlidXRlcy5kYXRhRm9ybXVsYSxcblx0XHRcdGxheW91dDogJ2NvbnRhaW5lcidcblx0XHR9O1xuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoJ2FtcC1tYXRobWwnLCBtYXRobWxQcm9wcyk7XG5cdH1cbn0pO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vYmxvY2tzL2FtcC1tYXRobWwvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDNcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///3\n");
87
+
88
+ /***/ }),
89
+ /* 4 */
90
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
91
+
92
+ "use strict";
93
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_timeago_js__ = __webpack_require__(5);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_timeago_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_timeago_js__);\n/* global moment */\n\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar _wp$editor = wp.editor,\n InspectorControls = _wp$editor.InspectorControls,\n BlockAlignmentToolbar = _wp$editor.BlockAlignmentToolbar,\n BlockControls = _wp$editor.BlockControls;\nvar _wp$components = wp.components,\n DateTimePicker = _wp$components.DateTimePicker,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl;\nvar Fragment = wp.element.Fragment;\n\n\n\n/**\n * Register block.\n */\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-timeago', {\n\ttitle: __('AMP Timeago'),\n\tcategory: 'common',\n\ticon: 'backup',\n\tkeywords: [__('Time difference'), __('Time ago'), __('Date')],\n\n\tattributes: {\n\t\talign: {\n\t\t\ttype: 'string'\n\t\t},\n\t\tcutoff: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-timeago',\n\t\t\tattribute: 'cutoff'\n\t\t},\n\t\tdateTime: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-timeago',\n\t\t\tattribute: 'datetime'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'fixed-height',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-timeago',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-timeago',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 20,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-timeago',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tgetEditWrapperProps: function getEditWrapperProps(attributes) {\n\t\tvar align = attributes.align;\n\n\t\tif ('left' === align || 'right' === align || 'center' === align) {\n\t\t\treturn { 'data-align': align };\n\t\t}\n\t},\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar align = attributes.align,\n\t\t cutoff = attributes.cutoff;\n\n\t\tvar timeAgo = void 0;\n\t\tif (attributes.dateTime) {\n\t\t\tif (attributes.cutoff && parseInt(attributes.cutoff) < Math.abs(moment(attributes.dateTime).diff(moment(), 'seconds'))) {\n\t\t\t\ttimeAgo = moment(attributes.dateTime).format('dddd D MMMM HH:mm');\n\t\t\t} else {\n\t\t\t\ttimeAgo = __WEBPACK_IMPORTED_MODULE_1_timeago_js___default()().format(attributes.dateTime);\n\t\t\t}\n\t\t} else {\n\t\t\ttimeAgo = __WEBPACK_IMPORTED_MODULE_1_timeago_js___default()().format(new Date());\n\t\t\tsetAttributes({ dateTime: moment(moment(), moment.ISO_8601, true).format() });\n\t\t}\n\n\t\tvar ampLayoutOptions = [{ value: '', label: __('Responsive', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fixed-height', label: __('Fixed height', 'amp') }];\n\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('AMP Timeago Settings') },\n\t\t\t\t\twp.element.createElement(DateTimePicker, {\n\t\t\t\t\t\tlocale: 'en',\n\t\t\t\t\t\tcurrentDate: attributes.dateTime || moment(),\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dateTime: moment(value, moment.ISO_8601, true).format() });\n\t\t\t\t\t\t} // eslint-disable-line\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tclassName: 'blocks-amp-timeout__cutoff',\n\t\t\t\t\t\tlabel: __('Cutoff (seconds)'),\n\t\t\t\t\t\tvalue: cutoff !== undefined ? cutoff : '',\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ cutoff: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t),\n\t\t\twp.element.createElement(\n\t\t\t\tBlockControls,\n\t\t\t\t{ key: 'controls' },\n\t\t\t\twp.element.createElement(BlockAlignmentToolbar, {\n\t\t\t\t\tvalue: align,\n\t\t\t\t\tonChange: function onChange(nextAlign) {\n\t\t\t\t\t\tsetAttributes({ align: nextAlign });\n\t\t\t\t\t},\n\t\t\t\t\tcontrols: ['left', 'center', 'right']\n\t\t\t\t})\n\t\t\t),\n\t\t\twp.element.createElement(\n\t\t\t\t'time',\n\t\t\t\t{ key: 'timeago', dateTime: attributes.dateTime },\n\t\t\t\ttimeAgo\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\n\t\tvar timeagoProps = {\n\t\t\tlayout: 'responsive',\n\t\t\tclassName: 'align' + (attributes.align || 'none'),\n\t\t\tdatetime: attributes.dateTime,\n\t\t\tlocale: 'en'\n\t\t};\n\t\tif (attributes.cutoff) {\n\t\t\ttimeagoProps.cutoff = attributes.cutoff;\n\t\t}\n\t\tif (attributes.ampLayout) {\n\t\t\tswitch (attributes.ampLayout) {\n\t\t\t\tcase 'fixed-height':\n\t\t\t\t\tif (attributes.height) {\n\t\t\t\t\t\ttimeagoProps.height = attributes.height;\n\t\t\t\t\t\ttimeagoProps.layout = attributes.ampLayout;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'fixed':\n\t\t\t\t\tif (attributes.height && attributes.width) {\n\t\t\t\t\t\ttimeagoProps.height = attributes.height;\n\t\t\t\t\t\ttimeagoProps.width = attributes.width;\n\t\t\t\t\t\ttimeagoProps.layout = attributes.ampLayout;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\t'amp-timeago',\n\t\t\ttimeagoProps,\n\t\t\tmoment(attributes.dateTime).format('dddd D MMMM HH:mm')\n\t\t);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9hbXAtdGltZWFnby9pbmRleC5qcz9iMThjIl0sInNvdXJjZXNDb250ZW50IjpbIi8qIGdsb2JhbCBtb21lbnQgKi9cblxuLyoqXG4gKiBIZWxwZXIgbWV0aG9kcyBmb3IgYmxvY2tzLlxuICovXG5pbXBvcnQgeyBnZXRMYXlvdXRDb250cm9scyB9IGZyb20gJy4uL3V0aWxzLmpzJztcblxuLyoqXG4gKiBJbnRlcm5hbCBibG9jayBsaWJyYXJpZXMuXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG52YXIgX3dwJGVkaXRvciA9IHdwLmVkaXRvcixcbiAgICBJbnNwZWN0b3JDb250cm9scyA9IF93cCRlZGl0b3IuSW5zcGVjdG9yQ29udHJvbHMsXG4gICAgQmxvY2tBbGlnbm1lbnRUb29sYmFyID0gX3dwJGVkaXRvci5CbG9ja0FsaWdubWVudFRvb2xiYXIsXG4gICAgQmxvY2tDb250cm9scyA9IF93cCRlZGl0b3IuQmxvY2tDb250cm9scztcbnZhciBfd3AkY29tcG9uZW50cyA9IHdwLmNvbXBvbmVudHMsXG4gICAgRGF0ZVRpbWVQaWNrZXIgPSBfd3AkY29tcG9uZW50cy5EYXRlVGltZVBpY2tlcixcbiAgICBQYW5lbEJvZHkgPSBfd3AkY29tcG9uZW50cy5QYW5lbEJvZHksXG4gICAgVGV4dENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5UZXh0Q29udHJvbDtcbnZhciBGcmFnbWVudCA9IHdwLmVsZW1lbnQuRnJhZ21lbnQ7XG5cbmltcG9ydCB0aW1lYWdvIGZyb20gJ3RpbWVhZ28uanMnO1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5leHBvcnQgZGVmYXVsdCByZWdpc3RlckJsb2NrVHlwZSgnYW1wL2FtcC10aW1lYWdvJywge1xuXHR0aXRsZTogX18oJ0FNUCBUaW1lYWdvJyksXG5cdGNhdGVnb3J5OiAnY29tbW9uJyxcblx0aWNvbjogJ2JhY2t1cCcsXG5cdGtleXdvcmRzOiBbX18oJ1RpbWUgZGlmZmVyZW5jZScpLCBfXygnVGltZSBhZ28nKSwgX18oJ0RhdGUnKV0sXG5cblx0YXR0cmlidXRlczoge1xuXHRcdGFsaWduOiB7XG5cdFx0XHR0eXBlOiAnc3RyaW5nJ1xuXHRcdH0sXG5cdFx0Y3V0b2ZmOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtdGltZWFnbycsXG5cdFx0XHRhdHRyaWJ1dGU6ICdjdXRvZmYnXG5cdFx0fSxcblx0XHRkYXRlVGltZToge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXRpbWVhZ28nLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0ZXRpbWUnXG5cdFx0fSxcblx0XHRhbXBMYXlvdXQ6IHtcblx0XHRcdGRlZmF1bHQ6ICdmaXhlZC1oZWlnaHQnLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXRpbWVhZ28nLFxuXHRcdFx0YXR0cmlidXRlOiAnbGF5b3V0J1xuXHRcdH0sXG5cdFx0d2lkdGg6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC10aW1lYWdvJyxcblx0XHRcdGF0dHJpYnV0ZTogJ3dpZHRoJ1xuXHRcdH0sXG5cdFx0aGVpZ2h0OiB7XG5cdFx0XHRkZWZhdWx0OiAyMCxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC10aW1lYWdvJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2hlaWdodCdcblx0XHR9XG5cdH0sXG5cblx0Z2V0RWRpdFdyYXBwZXJQcm9wczogZnVuY3Rpb24gZ2V0RWRpdFdyYXBwZXJQcm9wcyhhdHRyaWJ1dGVzKSB7XG5cdFx0dmFyIGFsaWduID0gYXR0cmlidXRlcy5hbGlnbjtcblxuXHRcdGlmICgnbGVmdCcgPT09IGFsaWduIHx8ICdyaWdodCcgPT09IGFsaWduIHx8ICdjZW50ZXInID09PSBhbGlnbikge1xuXHRcdFx0cmV0dXJuIHsgJ2RhdGEtYWxpZ24nOiBhbGlnbiB9O1xuXHRcdH1cblx0fSxcblx0ZWRpdDogZnVuY3Rpb24gZWRpdChwcm9wcykge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gcHJvcHMuYXR0cmlidXRlcyxcblx0XHQgICAgc2V0QXR0cmlidXRlcyA9IHByb3BzLnNldEF0dHJpYnV0ZXM7XG5cdFx0dmFyIGFsaWduID0gYXR0cmlidXRlcy5hbGlnbixcblx0XHQgICAgY3V0b2ZmID0gYXR0cmlidXRlcy5jdXRvZmY7XG5cblx0XHR2YXIgdGltZUFnbyA9IHZvaWQgMDtcblx0XHRpZiAoYXR0cmlidXRlcy5kYXRlVGltZSkge1xuXHRcdFx0aWYgKGF0dHJpYnV0ZXMuY3V0b2ZmICYmIHBhcnNlSW50KGF0dHJpYnV0ZXMuY3V0b2ZmKSA8IE1hdGguYWJzKG1vbWVudChhdHRyaWJ1dGVzLmRhdGVUaW1lKS5kaWZmKG1vbWVudCgpLCAnc2Vjb25kcycpKSkge1xuXHRcdFx0XHR0aW1lQWdvID0gbW9tZW50KGF0dHJpYnV0ZXMuZGF0ZVRpbWUpLmZvcm1hdCgnZGRkZCBEIE1NTU0gSEg6bW0nKTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdHRpbWVBZ28gPSB0aW1lYWdvKCkuZm9ybWF0KGF0dHJpYnV0ZXMuZGF0ZVRpbWUpO1xuXHRcdFx0fVxuXHRcdH0gZWxzZSB7XG5cdFx0XHR0aW1lQWdvID0gdGltZWFnbygpLmZvcm1hdChuZXcgRGF0ZSgpKTtcblx0XHRcdHNldEF0dHJpYnV0ZXMoeyBkYXRlVGltZTogbW9tZW50KG1vbWVudCgpLCBtb21lbnQuSVNPXzg2MDEsIHRydWUpLmZvcm1hdCgpIH0pO1xuXHRcdH1cblxuXHRcdHZhciBhbXBMYXlvdXRPcHRpb25zID0gW3sgdmFsdWU6ICcnLCBsYWJlbDogX18oJ1Jlc3BvbnNpdmUnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkJywgbGFiZWw6IF9fKCdGaXhlZCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZml4ZWQtaGVpZ2h0JywgbGFiZWw6IF9fKCdGaXhlZCBoZWlnaHQnLCAnYW1wJykgfV07XG5cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0RnJhZ21lbnQsXG5cdFx0XHRudWxsLFxuXHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRJbnNwZWN0b3JDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdpbnNwZWN0b3InIH0sXG5cdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRQYW5lbEJvZHksXG5cdFx0XHRcdFx0eyB0aXRsZTogX18oJ0FNUCBUaW1lYWdvIFNldHRpbmdzJykgfSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoRGF0ZVRpbWVQaWNrZXIsIHtcblx0XHRcdFx0XHRcdGxvY2FsZTogJ2VuJyxcblx0XHRcdFx0XHRcdGN1cnJlbnREYXRlOiBhdHRyaWJ1dGVzLmRhdGVUaW1lIHx8IG1vbWVudCgpLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0ZVRpbWU6IG1vbWVudCh2YWx1ZSwgbW9tZW50LklTT184NjAxLCB0cnVlKS5mb3JtYXQoKSB9KTtcblx0XHRcdFx0XHRcdH0gLy8gZXNsaW50LWRpc2FibGUtbGluZVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdGdldExheW91dENvbnRyb2xzKHByb3BzLCBhbXBMYXlvdXRPcHRpb25zKSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdHR5cGU6ICdudW1iZXInLFxuXHRcdFx0XHRcdFx0Y2xhc3NOYW1lOiAnYmxvY2tzLWFtcC10aW1lb3V0X19jdXRvZmYnLFxuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdDdXRvZmYgKHNlY29uZHMpJyksXG5cdFx0XHRcdFx0XHR2YWx1ZTogY3V0b2ZmICE9PSB1bmRlZmluZWQgPyBjdXRvZmYgOiAnJyxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGN1dG9mZjogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSlcblx0XHRcdFx0KVxuXHRcdFx0KSxcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0QmxvY2tDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdjb250cm9scycgfSxcblx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KEJsb2NrQWxpZ25tZW50VG9vbGJhciwge1xuXHRcdFx0XHRcdHZhbHVlOiBhbGlnbixcblx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UobmV4dEFsaWduKSB7XG5cdFx0XHRcdFx0XHRzZXRBdHRyaWJ1dGVzKHsgYWxpZ246IG5leHRBbGlnbiB9KTtcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdGNvbnRyb2xzOiBbJ2xlZnQnLCAnY2VudGVyJywgJ3JpZ2h0J11cblx0XHRcdFx0fSlcblx0XHRcdCksXG5cdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdCd0aW1lJyxcblx0XHRcdFx0eyBrZXk6ICd0aW1lYWdvJywgZGF0ZVRpbWU6IGF0dHJpYnV0ZXMuZGF0ZVRpbWUgfSxcblx0XHRcdFx0dGltZUFnb1xuXHRcdFx0KVxuXHRcdCk7XG5cdH0sXG5cdHNhdmU6IGZ1bmN0aW9uIHNhdmUoX3JlZikge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gX3JlZi5hdHRyaWJ1dGVzO1xuXG5cdFx0dmFyIHRpbWVhZ29Qcm9wcyA9IHtcblx0XHRcdGxheW91dDogJ3Jlc3BvbnNpdmUnLFxuXHRcdFx0Y2xhc3NOYW1lOiAnYWxpZ24nICsgKGF0dHJpYnV0ZXMuYWxpZ24gfHwgJ25vbmUnKSxcblx0XHRcdGRhdGV0aW1lOiBhdHRyaWJ1dGVzLmRhdGVUaW1lLFxuXHRcdFx0bG9jYWxlOiAnZW4nXG5cdFx0fTtcblx0XHRpZiAoYXR0cmlidXRlcy5jdXRvZmYpIHtcblx0XHRcdHRpbWVhZ29Qcm9wcy5jdXRvZmYgPSBhdHRyaWJ1dGVzLmN1dG9mZjtcblx0XHR9XG5cdFx0aWYgKGF0dHJpYnV0ZXMuYW1wTGF5b3V0KSB7XG5cdFx0XHRzd2l0Y2ggKGF0dHJpYnV0ZXMuYW1wTGF5b3V0KSB7XG5cdFx0XHRcdGNhc2UgJ2ZpeGVkLWhlaWdodCc6XG5cdFx0XHRcdFx0aWYgKGF0dHJpYnV0ZXMuaGVpZ2h0KSB7XG5cdFx0XHRcdFx0XHR0aW1lYWdvUHJvcHMuaGVpZ2h0ID0gYXR0cmlidXRlcy5oZWlnaHQ7XG5cdFx0XHRcdFx0XHR0aW1lYWdvUHJvcHMubGF5b3V0ID0gYXR0cmlidXRlcy5hbXBMYXlvdXQ7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0XHRjYXNlICdmaXhlZCc6XG5cdFx0XHRcdFx0aWYgKGF0dHJpYnV0ZXMuaGVpZ2h0ICYmIGF0dHJpYnV0ZXMud2lkdGgpIHtcblx0XHRcdFx0XHRcdHRpbWVhZ29Qcm9wcy5oZWlnaHQgPSBhdHRyaWJ1dGVzLmhlaWdodDtcblx0XHRcdFx0XHRcdHRpbWVhZ29Qcm9wcy53aWR0aCA9IGF0dHJpYnV0ZXMud2lkdGg7XG5cdFx0XHRcdFx0XHR0aW1lYWdvUHJvcHMubGF5b3V0ID0gYXR0cmlidXRlcy5hbXBMYXlvdXQ7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0J2FtcC10aW1lYWdvJyxcblx0XHRcdHRpbWVhZ29Qcm9wcyxcblx0XHRcdG1vbWVudChhdHRyaWJ1dGVzLmRhdGVUaW1lKS5mb3JtYXQoJ2RkZGQgRCBNTU1NIEhIOm1tJylcblx0XHQpO1xuXHR9XG59KTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL2Jsb2Nrcy9hbXAtdGltZWFnby9pbmRleC5qc1xuLy8gbW9kdWxlIGlkID0gNFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///4\n");
94
+
95
+ /***/ }),
96
+ /* 5 */
97
+ /***/ (function(module, exports) {
98
+
99
+ eval("!function(t,e){\"object\"==typeof module&&module.exports?(module.exports=e(),module.exports.default=module.exports):t.timeago=e()}(\"undefined\"!=typeof window?window:this,function(){function t(t){return t instanceof Date?t:isNaN(t)?/^\\d+$/.test(t)?new Date(e(t)):(t=(t||\"\").trim().replace(/\\.\\d+/,\"\").replace(/-/,\"/\").replace(/-/,\"/\").replace(/(\\d)T(\\d)/,\"$1 $2\").replace(/Z/,\" UTC\").replace(/([\\+\\-]\\d\\d)\\:?(\\d\\d)/,\" $1$2\"),new Date(t)):new Date(e(t))}function e(t){return parseInt(t)}function n(t,n,r){n=l[n]?n:l[r]?r:\"en\";for(var o=0,i=t<0?1:0,a=t=Math.abs(t);t>=p[o]&&o<h;o++)t/=p[o];return t=e(t),o*=2,t>(0===o?9:1)&&(o+=1),l[n](t,o,a)[i].replace(\"%s\",t)}function r(e,n){return((n=n?t(n):new Date)-t(e))/1e3}function o(t){for(var e=1,n=0,r=Math.abs(t);t>=p[n]&&n<h;n++)t/=p[n],e*=p[n];return r%=e,r=r?e-r:e,Math.ceil(r)}function i(t){return a(t,\"data-timeago\")||a(t,\"datetime\")}function a(t,e){return t.getAttribute?t.getAttribute(e):t.attr?t.attr(e):void 0}function u(t,e){return t.setAttribute?t.setAttribute(m,e):t.attr?t.attr(m,e):void 0}function c(t,e){this.nowDate=t,this.defaultLocale=e||\"en\"}function d(t,e){return new c(t,e)}var f=\"second_minute_hour_day_week_month_year\".split(\"_\"),s=\"秒_分钟_小时_天_周_月_年\".split(\"_\"),l={en:function(t,e){if(0===e)return[\"just now\",\"right now\"];var n=f[parseInt(e/2)];return t>1&&(n+=\"s\"),[t+\" \"+n+\" ago\",\"in \"+t+\" \"+n]},zh_CN:function(t,e){if(0===e)return[\"刚刚\",\"片刻后\"];var n=s[parseInt(e/2)];return[t+n+\"前\",t+n+\"后\"]}},p=[60,60,24,7,365/7/12,12],h=6,m=\"data-tid\",w={};return c.prototype.doRender=function(t,e,i){var a,c=r(e,this.nowDate),d=this;t.innerHTML=n(c,i,this.defaultLocale),w[a=setTimeout(function(){d.doRender(t,e,i),delete w[a]},Math.min(1e3*o(c),2147483647))]=0,u(t,a)},c.prototype.format=function(t,e){return n(r(t,this.nowDate),e,this.defaultLocale)},c.prototype.render=function(t,e){void 0===t.length&&(t=[t]);for(var n=0,r=t.length;n<r;n++)this.doRender(t[n],i(t[n]),e)},c.prototype.setLocale=function(t){this.defaultLocale=t},d.register=function(t,e){l[t]=e},d.cancel=function(t){var e;if(t)(e=a(t,m))&&(clearTimeout(e),delete w[e]);else{for(e in w)clearTimeout(e);w={}}},d});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL25vZGVfbW9kdWxlcy90aW1lYWdvLmpzL2Rpc3QvdGltZWFnby5taW4uanM/ZjAwZCJdLCJzb3VyY2VzQ29udGVudCI6WyIhZnVuY3Rpb24odCxlKXtcIm9iamVjdFwiPT10eXBlb2YgbW9kdWxlJiZtb2R1bGUuZXhwb3J0cz8obW9kdWxlLmV4cG9ydHM9ZSgpLG1vZHVsZS5leHBvcnRzLmRlZmF1bHQ9bW9kdWxlLmV4cG9ydHMpOnQudGltZWFnbz1lKCl9KFwidW5kZWZpbmVkXCIhPXR5cGVvZiB3aW5kb3c/d2luZG93OnRoaXMsZnVuY3Rpb24oKXtmdW5jdGlvbiB0KHQpe3JldHVybiB0IGluc3RhbmNlb2YgRGF0ZT90OmlzTmFOKHQpPy9eXFxkKyQvLnRlc3QodCk/bmV3IERhdGUoZSh0KSk6KHQ9KHR8fFwiXCIpLnRyaW0oKS5yZXBsYWNlKC9cXC5cXGQrLyxcIlwiKS5yZXBsYWNlKC8tLyxcIi9cIikucmVwbGFjZSgvLS8sXCIvXCIpLnJlcGxhY2UoLyhcXGQpVChcXGQpLyxcIiQxICQyXCIpLnJlcGxhY2UoL1ovLFwiIFVUQ1wiKS5yZXBsYWNlKC8oW1xcK1xcLV1cXGRcXGQpXFw6PyhcXGRcXGQpLyxcIiAkMSQyXCIpLG5ldyBEYXRlKHQpKTpuZXcgRGF0ZShlKHQpKX1mdW5jdGlvbiBlKHQpe3JldHVybiBwYXJzZUludCh0KX1mdW5jdGlvbiBuKHQsbixyKXtuPWxbbl0/bjpsW3JdP3I6XCJlblwiO2Zvcih2YXIgbz0wLGk9dDwwPzE6MCxhPXQ9TWF0aC5hYnModCk7dD49cFtvXSYmbzxoO28rKyl0Lz1wW29dO3JldHVybiB0PWUodCksbyo9Mix0PigwPT09bz85OjEpJiYobys9MSksbFtuXSh0LG8sYSlbaV0ucmVwbGFjZShcIiVzXCIsdCl9ZnVuY3Rpb24gcihlLG4pe3JldHVybigobj1uP3Qobik6bmV3IERhdGUpLXQoZSkpLzFlM31mdW5jdGlvbiBvKHQpe2Zvcih2YXIgZT0xLG49MCxyPU1hdGguYWJzKHQpO3Q+PXBbbl0mJm48aDtuKyspdC89cFtuXSxlKj1wW25dO3JldHVybiByJT1lLHI9cj9lLXI6ZSxNYXRoLmNlaWwocil9ZnVuY3Rpb24gaSh0KXtyZXR1cm4gYSh0LFwiZGF0YS10aW1lYWdvXCIpfHxhKHQsXCJkYXRldGltZVwiKX1mdW5jdGlvbiBhKHQsZSl7cmV0dXJuIHQuZ2V0QXR0cmlidXRlP3QuZ2V0QXR0cmlidXRlKGUpOnQuYXR0cj90LmF0dHIoZSk6dm9pZCAwfWZ1bmN0aW9uIHUodCxlKXtyZXR1cm4gdC5zZXRBdHRyaWJ1dGU/dC5zZXRBdHRyaWJ1dGUobSxlKTp0LmF0dHI/dC5hdHRyKG0sZSk6dm9pZCAwfWZ1bmN0aW9uIGModCxlKXt0aGlzLm5vd0RhdGU9dCx0aGlzLmRlZmF1bHRMb2NhbGU9ZXx8XCJlblwifWZ1bmN0aW9uIGQodCxlKXtyZXR1cm4gbmV3IGModCxlKX12YXIgZj1cInNlY29uZF9taW51dGVfaG91cl9kYXlfd2Vla19tb250aF95ZWFyXCIuc3BsaXQoXCJfXCIpLHM9XCLnp5Jf5YiG6ZKfX+Wwj+aXtl/lpKlf5ZGoX+aciF/lubRcIi5zcGxpdChcIl9cIiksbD17ZW46ZnVuY3Rpb24odCxlKXtpZigwPT09ZSlyZXR1cm5bXCJqdXN0IG5vd1wiLFwicmlnaHQgbm93XCJdO3ZhciBuPWZbcGFyc2VJbnQoZS8yKV07cmV0dXJuIHQ+MSYmKG4rPVwic1wiKSxbdCtcIiBcIituK1wiIGFnb1wiLFwiaW4gXCIrdCtcIiBcIituXX0semhfQ046ZnVuY3Rpb24odCxlKXtpZigwPT09ZSlyZXR1cm5bXCLliJrliJpcIixcIueJh+WIu+WQjlwiXTt2YXIgbj1zW3BhcnNlSW50KGUvMildO3JldHVyblt0K24rXCLliY1cIix0K24rXCLlkI5cIl19fSxwPVs2MCw2MCwyNCw3LDM2NS83LzEyLDEyXSxoPTYsbT1cImRhdGEtdGlkXCIsdz17fTtyZXR1cm4gYy5wcm90b3R5cGUuZG9SZW5kZXI9ZnVuY3Rpb24odCxlLGkpe3ZhciBhLGM9cihlLHRoaXMubm93RGF0ZSksZD10aGlzO3QuaW5uZXJIVE1MPW4oYyxpLHRoaXMuZGVmYXVsdExvY2FsZSksd1thPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtkLmRvUmVuZGVyKHQsZSxpKSxkZWxldGUgd1thXX0sTWF0aC5taW4oMWUzKm8oYyksMjE0NzQ4MzY0NykpXT0wLHUodCxhKX0sYy5wcm90b3R5cGUuZm9ybWF0PWZ1bmN0aW9uKHQsZSl7cmV0dXJuIG4ocih0LHRoaXMubm93RGF0ZSksZSx0aGlzLmRlZmF1bHRMb2NhbGUpfSxjLnByb3RvdHlwZS5yZW5kZXI9ZnVuY3Rpb24odCxlKXt2b2lkIDA9PT10Lmxlbmd0aCYmKHQ9W3RdKTtmb3IodmFyIG49MCxyPXQubGVuZ3RoO248cjtuKyspdGhpcy5kb1JlbmRlcih0W25dLGkodFtuXSksZSl9LGMucHJvdG90eXBlLnNldExvY2FsZT1mdW5jdGlvbih0KXt0aGlzLmRlZmF1bHRMb2NhbGU9dH0sZC5yZWdpc3Rlcj1mdW5jdGlvbih0LGUpe2xbdF09ZX0sZC5jYW5jZWw9ZnVuY3Rpb24odCl7dmFyIGU7aWYodCkoZT1hKHQsbSkpJiYoY2xlYXJUaW1lb3V0KGUpLGRlbGV0ZSB3W2VdKTtlbHNle2ZvcihlIGluIHcpY2xlYXJUaW1lb3V0KGUpO3c9e319fSxkfSk7XG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ub2RlX21vZHVsZXMvdGltZWFnby5qcy9kaXN0L3RpbWVhZ28ubWluLmpzXG4vLyBtb2R1bGUgaWQgPSA1XG4vLyBtb2R1bGUgY2h1bmtzID0gMCJdLCJtYXBwaW5ncyI6IkFBQUEiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///5\n");
100
+
101
+ /***/ }),
102
+ /* 6 */
103
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
104
+
105
+ "use strict";
106
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n Placeholder = _wp$components.Placeholder,\n ToggleControl = _wp$components.ToggleControl;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-o2-player', {\n\ttitle: __('AMP O2 Player', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp'), __('AOL O2Player', 'amp')],\n\n\t// @todo Add other useful macro toggles, e.g. showing relevant content.\n\tattributes: {\n\t\tdataPid: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'data-pid'\n\t\t},\n\t\tdataVid: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'data-vid'\n\t\t},\n\t\tdataBcid: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'data-bcid'\n\t\t},\n\t\tdataBid: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'data-bid'\n\t\t},\n\t\tautoPlay: {\n\t\t\tdefault: false\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'responsive',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tdefault: 600,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-o2-player',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar autoPlay = attributes.autoPlay,\n\t\t dataPid = attributes.dataPid,\n\t\t dataVid = attributes.dataVid,\n\t\t dataBcid = attributes.dataBcid,\n\t\t dataBid = attributes.dataBid;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed-height', label: __('Fixed height', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fill', label: __('Fill', 'amp') }, { value: 'flex-item', label: __('Flex-item', 'amp') }, { value: 'nodisplay', label: __('No Display', 'amp') }];\n\t\tvar url = false;\n\t\tif (dataPid && (dataBcid || dataVid)) {\n\t\t\turl = 'https://delivery.vidible.tv/htmlembed/pid=' + dataPid + '/';\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('O2 Player Settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Player ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPid,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPid: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Buyer Company ID (either buyer or video ID is required)', 'amp'),\n\t\t\t\t\t\tvalue: dataBcid,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataBcid: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Video ID (either buyer or video ID is required)', 'amp'),\n\t\t\t\t\t\tvalue: dataVid,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataVid: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Playlist ID', 'amp'),\n\t\t\t\t\t\tvalue: dataBid,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataBid: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(ToggleControl, {\n\t\t\t\t\t\tlabel: __('Autoplay', 'amp'),\n\t\t\t\t\t\tchecked: autoPlay,\n\t\t\t\t\t\tonChange: function onChange() {\n\t\t\t\t\t\t\treturn setAttributes({ autoPlay: !autoPlay });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\turl && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('O2 Player', 'amp'), url),\n\t\t\t!url && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('O2 Player', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add required data to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\n\t\tvar o2Props = {\n\t\t\tlayout: attributes.ampLayout,\n\t\t\theight: attributes.height,\n\t\t\t'data-pid': attributes.dataPid\n\t\t};\n\t\tif ('fixed-height' !== attributes.ampLayout && attributes.width) {\n\t\t\to2Props.width = attributes.width;\n\t\t}\n\t\tif (!attributes.autoPlay) {\n\t\t\to2Props['data-macros'] = 'm.playback=click';\n\t\t}\n\t\tif (attributes.dataVid) {\n\t\t\to2Props['data-vid'] = attributes.dataVid;\n\t\t} else if (attributes.dataBcid) {\n\t\t\to2Props['data-bcid'] = attributes.dataBcid;\n\t\t}\n\t\tif (attributes.dataBid) {\n\t\t\to2Props['data-bid'] = attributes.dataBid;\n\t\t}\n\t\treturn wp.element.createElement('amp-o2-player', o2Props);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9hbXAtbzItcGxheWVyL2luZGV4LmpzPzIwMGYiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBIZWxwZXIgbWV0aG9kcyBmb3IgYmxvY2tzLlxuICovXG5pbXBvcnQgeyBnZXRMYXlvdXRDb250cm9scywgZ2V0TWVkaWFQbGFjZWhvbGRlciB9IGZyb20gJy4uL3V0aWxzLmpzJztcblxuLyoqXG4gKiBJbnRlcm5hbCBibG9jayBsaWJyYXJpZXMuXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG52YXIgSW5zcGVjdG9yQ29udHJvbHMgPSB3cC5lZGl0b3IuSW5zcGVjdG9yQ29udHJvbHM7XG52YXIgRnJhZ21lbnQgPSB3cC5lbGVtZW50LkZyYWdtZW50O1xudmFyIF93cCRjb21wb25lbnRzID0gd3AuY29tcG9uZW50cyxcbiAgICBQYW5lbEJvZHkgPSBfd3AkY29tcG9uZW50cy5QYW5lbEJvZHksXG4gICAgVGV4dENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5UZXh0Q29udHJvbCxcbiAgICBQbGFjZWhvbGRlciA9IF93cCRjb21wb25lbnRzLlBsYWNlaG9sZGVyLFxuICAgIFRvZ2dsZUNvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5Ub2dnbGVDb250cm9sO1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IHJlZ2lzdGVyQmxvY2tUeXBlKCdhbXAvYW1wLW8yLXBsYXllcicsIHtcblx0dGl0bGU6IF9fKCdBTVAgTzIgUGxheWVyJywgJ2FtcCcpLFxuXHRjYXRlZ29yeTogJ2VtYmVkJyxcblx0aWNvbjogJ2VtYmVkLWdlbmVyaWMnLFxuXHRrZXl3b3JkczogW19fKCdFbWJlZCcsICdhbXAnKSwgX18oJ0FPTCBPMlBsYXllcicsICdhbXAnKV0sXG5cblx0Ly8gQHRvZG8gQWRkIG90aGVyIHVzZWZ1bCBtYWNybyB0b2dnbGVzLCBlLmcuIHNob3dpbmcgcmVsZXZhbnQgY29udGVudC5cblx0YXR0cmlidXRlczoge1xuXHRcdGRhdGFQaWQ6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1vMi1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1waWQnXG5cdFx0fSxcblx0XHRkYXRhVmlkOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtbzItcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtdmlkJ1xuXHRcdH0sXG5cdFx0ZGF0YUJjaWQ6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1vMi1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1iY2lkJ1xuXHRcdH0sXG5cdFx0ZGF0YUJpZDoge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLW8yLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLWJpZCdcblx0XHR9LFxuXHRcdGF1dG9QbGF5OiB7XG5cdFx0XHRkZWZhdWx0OiBmYWxzZVxuXHRcdH0sXG5cdFx0YW1wTGF5b3V0OiB7XG5cdFx0XHRkZWZhdWx0OiAncmVzcG9uc2l2ZScsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtbzItcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2xheW91dCdcblx0XHR9LFxuXHRcdHdpZHRoOiB7XG5cdFx0XHRkZWZhdWx0OiA2MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtbzItcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ3dpZHRoJ1xuXHRcdH0sXG5cdFx0aGVpZ2h0OiB7XG5cdFx0XHRkZWZhdWx0OiA0MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtbzItcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2hlaWdodCdcblx0XHR9XG5cdH0sXG5cblx0ZWRpdDogZnVuY3Rpb24gZWRpdChwcm9wcykge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gcHJvcHMuYXR0cmlidXRlcyxcblx0XHQgICAgc2V0QXR0cmlidXRlcyA9IHByb3BzLnNldEF0dHJpYnV0ZXM7XG5cdFx0dmFyIGF1dG9QbGF5ID0gYXR0cmlidXRlcy5hdXRvUGxheSxcblx0XHQgICAgZGF0YVBpZCA9IGF0dHJpYnV0ZXMuZGF0YVBpZCxcblx0XHQgICAgZGF0YVZpZCA9IGF0dHJpYnV0ZXMuZGF0YVZpZCxcblx0XHQgICAgZGF0YUJjaWQgPSBhdHRyaWJ1dGVzLmRhdGFCY2lkLFxuXHRcdCAgICBkYXRhQmlkID0gYXR0cmlidXRlcy5kYXRhQmlkO1xuXG5cdFx0dmFyIGFtcExheW91dE9wdGlvbnMgPSBbeyB2YWx1ZTogJ3Jlc3BvbnNpdmUnLCBsYWJlbDogX18oJ1Jlc3BvbnNpdmUnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkLWhlaWdodCcsIGxhYmVsOiBfXygnRml4ZWQgaGVpZ2h0JywgJ2FtcCcpIH0sIHsgdmFsdWU6ICdmaXhlZCcsIGxhYmVsOiBfXygnRml4ZWQnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpbGwnLCBsYWJlbDogX18oJ0ZpbGwnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZsZXgtaXRlbScsIGxhYmVsOiBfXygnRmxleC1pdGVtJywgJ2FtcCcpIH0sIHsgdmFsdWU6ICdub2Rpc3BsYXknLCBsYWJlbDogX18oJ05vIERpc3BsYXknLCAnYW1wJykgfV07XG5cdFx0dmFyIHVybCA9IGZhbHNlO1xuXHRcdGlmIChkYXRhUGlkICYmIChkYXRhQmNpZCB8fCBkYXRhVmlkKSkge1xuXHRcdFx0dXJsID0gJ2h0dHBzOi8vZGVsaXZlcnkudmlkaWJsZS50di9odG1sZW1iZWQvcGlkPScgKyBkYXRhUGlkICsgJy8nO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0RnJhZ21lbnQsXG5cdFx0XHRudWxsLFxuXHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRJbnNwZWN0b3JDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdpbnNwZWN0b3InIH0sXG5cdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRQYW5lbEJvZHksXG5cdFx0XHRcdFx0eyB0aXRsZTogX18oJ08yIFBsYXllciBTZXR0aW5ncycsICdhbXAnKSB9LFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUZXh0Q29udHJvbCwge1xuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdQbGF5ZXIgSUQgKHJlcXVpcmVkKScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhUGlkLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YVBpZDogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ0J1eWVyIENvbXBhbnkgSUQgKGVpdGhlciBidXllciBvciB2aWRlbyBJRCBpcyByZXF1aXJlZCknLCAnYW1wJyksXG5cdFx0XHRcdFx0XHR2YWx1ZTogZGF0YUJjaWQsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhQmNpZDogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ1ZpZGVvIElEIChlaXRoZXIgYnV5ZXIgb3IgdmlkZW8gSUQgaXMgcmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFWaWQsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhVmlkOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnUGxheWxpc3QgSUQnLCAnYW1wJyksXG5cdFx0XHRcdFx0XHR2YWx1ZTogZGF0YUJpZCxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGRhdGFCaWQ6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUb2dnbGVDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ0F1dG9wbGF5JywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0Y2hlY2tlZDogYXV0b1BsYXksXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UoKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgYXV0b1BsYXk6ICFhdXRvUGxheSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHRnZXRMYXlvdXRDb250cm9scyhwcm9wcywgYW1wTGF5b3V0T3B0aW9ucylcblx0XHRcdFx0KVxuXHRcdFx0KSxcblx0XHRcdHVybCAmJiBnZXRNZWRpYVBsYWNlaG9sZGVyKF9fKCdPMiBQbGF5ZXInLCAnYW1wJyksIHVybCksXG5cdFx0XHQhdXJsICYmIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0UGxhY2Vob2xkZXIsXG5cdFx0XHRcdHsgbGFiZWw6IF9fKCdPMiBQbGF5ZXInLCAnYW1wJykgfSxcblx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdCdwJyxcblx0XHRcdFx0XHRudWxsLFxuXHRcdFx0XHRcdF9fKCdBZGQgcmVxdWlyZWQgZGF0YSB0byB1c2UgdGhlIGJsb2NrLicsICdhbXAnKVxuXHRcdFx0XHQpXG5cdFx0XHQpXG5cdFx0KTtcblx0fSxcblx0c2F2ZTogZnVuY3Rpb24gc2F2ZShfcmVmKSB7XG5cdFx0dmFyIGF0dHJpYnV0ZXMgPSBfcmVmLmF0dHJpYnV0ZXM7XG5cblx0XHR2YXIgbzJQcm9wcyA9IHtcblx0XHRcdGxheW91dDogYXR0cmlidXRlcy5hbXBMYXlvdXQsXG5cdFx0XHRoZWlnaHQ6IGF0dHJpYnV0ZXMuaGVpZ2h0LFxuXHRcdFx0J2RhdGEtcGlkJzogYXR0cmlidXRlcy5kYXRhUGlkXG5cdFx0fTtcblx0XHRpZiAoJ2ZpeGVkLWhlaWdodCcgIT09IGF0dHJpYnV0ZXMuYW1wTGF5b3V0ICYmIGF0dHJpYnV0ZXMud2lkdGgpIHtcblx0XHRcdG8yUHJvcHMud2lkdGggPSBhdHRyaWJ1dGVzLndpZHRoO1xuXHRcdH1cblx0XHRpZiAoIWF0dHJpYnV0ZXMuYXV0b1BsYXkpIHtcblx0XHRcdG8yUHJvcHNbJ2RhdGEtbWFjcm9zJ10gPSAnbS5wbGF5YmFjaz1jbGljayc7XG5cdFx0fVxuXHRcdGlmIChhdHRyaWJ1dGVzLmRhdGFWaWQpIHtcblx0XHRcdG8yUHJvcHNbJ2RhdGEtdmlkJ10gPSBhdHRyaWJ1dGVzLmRhdGFWaWQ7XG5cdFx0fSBlbHNlIGlmIChhdHRyaWJ1dGVzLmRhdGFCY2lkKSB7XG5cdFx0XHRvMlByb3BzWydkYXRhLWJjaWQnXSA9IGF0dHJpYnV0ZXMuZGF0YUJjaWQ7XG5cdFx0fVxuXHRcdGlmIChhdHRyaWJ1dGVzLmRhdGFCaWQpIHtcblx0XHRcdG8yUHJvcHNbJ2RhdGEtYmlkJ10gPSBhdHRyaWJ1dGVzLmRhdGFCaWQ7XG5cdFx0fVxuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoJ2FtcC1vMi1wbGF5ZXInLCBvMlByb3BzKTtcblx0fVxufSk7XG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ibG9ja3MvYW1wLW8yLXBsYXllci9pbmRleC5qc1xuLy8gbW9kdWxlIGlkID0gNlxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///6\n");
107
+
108
+ /***/ }),
109
+ /* 7 */
110
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
111
+
112
+ "use strict";
113
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n SelectControl = _wp$components.SelectControl,\n Placeholder = _wp$components.Placeholder;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-ooyala-player', {\n\ttitle: __('AMP Ooyala Player', 'amp'),\n\tdescription: __('Displays an Ooyala video.', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp'), __('Ooyala video', 'amp')],\n\n\t// @todo Add data-config attribute?\n\tattributes: {\n\t\tdataEmbedCode: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'data-embedcode'\n\t\t},\n\t\tdataPlayerId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'data-playerid'\n\t\t},\n\t\tdataPcode: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'data-pcode'\n\t\t},\n\t\tdataPlayerVersion: {\n\t\t\tdefault: 'v3',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'data-playerversion'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'responsive',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tdefault: 600,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ooyala-player',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar dataEmbedCode = attributes.dataEmbedCode,\n\t\t dataPlayerId = attributes.dataPlayerId,\n\t\t dataPcode = attributes.dataPcode,\n\t\t dataPlayerVersion = attributes.dataPlayerVersion;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fill', label: __('Fill', 'amp') }, { value: 'flex-item', label: __('Flex-item', 'amp') }];\n\t\tvar url = false;\n\t\tif (dataEmbedCode && dataPlayerId && dataPcode) {\n\t\t\turl = 'http://cf.c.ooyala.com/' + dataEmbedCode;\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('Ooyala settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Video embed code (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataEmbedCode,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataEmbedCode: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Player ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPlayerId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlayerId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Provider code for the account (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPcode,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPcode: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(SelectControl, {\n\t\t\t\t\t\tlabel: __('Player version', 'amp'),\n\t\t\t\t\t\tvalue: dataPlayerVersion,\n\t\t\t\t\t\toptions: [{ value: 'v3', label: __('V3', 'amp') }, { value: 'v4', label: __('V4', 'amp') }],\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlayerVersion: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\turl && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('Ooyala Player', 'amp'), url),\n\t\t\t!url && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('Ooyala Player', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add required data to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\t\tvar dataEmbedCode = attributes.dataEmbedCode,\n\t\t dataPlayerId = attributes.dataPlayerId,\n\t\t dataPcode = attributes.dataPcode,\n\t\t dataPlayerVersion = attributes.dataPlayerVersion,\n\t\t ampLayout = attributes.ampLayout,\n\t\t height = attributes.height,\n\t\t width = attributes.width;\n\n\n\t\tvar ooyalaProps = {\n\t\t\tlayout: ampLayout,\n\t\t\theight: height,\n\t\t\t'data-embedcode': dataEmbedCode,\n\t\t\t'data-playerid': dataPlayerId,\n\t\t\t'data-pcode': dataPcode,\n\t\t\t'data-playerversion': dataPlayerVersion\n\t\t};\n\t\tif ('fixed-height' !== ampLayout && width) {\n\t\t\tooyalaProps.width = width;\n\t\t}\n\t\treturn wp.element.createElement('amp-ooyala-player', ooyalaProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9hbXAtb295YWxhLXBsYXllci9pbmRleC5qcz81NmRlIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogSGVscGVyIG1ldGhvZHMgZm9yIGJsb2Nrcy5cbiAqL1xuaW1wb3J0IHsgZ2V0TGF5b3V0Q29udHJvbHMsIGdldE1lZGlhUGxhY2Vob2xkZXIgfSBmcm9tICcuLi91dGlscy5qcyc7XG5cbi8qKlxuICogSW50ZXJuYWwgYmxvY2sgbGlicmFyaWVzLlxuICovXG52YXIgX18gPSB3cC5pMThuLl9fO1xudmFyIHJlZ2lzdGVyQmxvY2tUeXBlID0gd3AuYmxvY2tzLnJlZ2lzdGVyQmxvY2tUeXBlO1xudmFyIEluc3BlY3RvckNvbnRyb2xzID0gd3AuZWRpdG9yLkluc3BlY3RvckNvbnRyb2xzO1xudmFyIEZyYWdtZW50ID0gd3AuZWxlbWVudC5GcmFnbWVudDtcbnZhciBfd3AkY29tcG9uZW50cyA9IHdwLmNvbXBvbmVudHMsXG4gICAgUGFuZWxCb2R5ID0gX3dwJGNvbXBvbmVudHMuUGFuZWxCb2R5LFxuICAgIFRleHRDb250cm9sID0gX3dwJGNvbXBvbmVudHMuVGV4dENvbnRyb2wsXG4gICAgU2VsZWN0Q29udHJvbCA9IF93cCRjb21wb25lbnRzLlNlbGVjdENvbnRyb2wsXG4gICAgUGxhY2Vob2xkZXIgPSBfd3AkY29tcG9uZW50cy5QbGFjZWhvbGRlcjtcblxuLyoqXG4gKiBSZWdpc3RlciBibG9jay5cbiAqL1xuXG5leHBvcnQgZGVmYXVsdCByZWdpc3RlckJsb2NrVHlwZSgnYW1wL2FtcC1vb3lhbGEtcGxheWVyJywge1xuXHR0aXRsZTogX18oJ0FNUCBPb3lhbGEgUGxheWVyJywgJ2FtcCcpLFxuXHRkZXNjcmlwdGlvbjogX18oJ0Rpc3BsYXlzIGFuIE9veWFsYSB2aWRlby4nLCAnYW1wJyksXG5cdGNhdGVnb3J5OiAnZW1iZWQnLFxuXHRpY29uOiAnZW1iZWQtZ2VuZXJpYycsXG5cdGtleXdvcmRzOiBbX18oJ0VtYmVkJywgJ2FtcCcpLCBfXygnT295YWxhIHZpZGVvJywgJ2FtcCcpXSxcblxuXHQvLyBAdG9kbyBBZGQgZGF0YS1jb25maWcgYXR0cmlidXRlP1xuXHRhdHRyaWJ1dGVzOiB7XG5cdFx0ZGF0YUVtYmVkQ29kZToge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLW9veWFsYS1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1lbWJlZGNvZGUnXG5cdFx0fSxcblx0XHRkYXRhUGxheWVySWQ6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1vb3lhbGEtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtcGxheWVyaWQnXG5cdFx0fSxcblx0XHRkYXRhUGNvZGU6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1vb3lhbGEtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtcGNvZGUnXG5cdFx0fSxcblx0XHRkYXRhUGxheWVyVmVyc2lvbjoge1xuXHRcdFx0ZGVmYXVsdDogJ3YzJyxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1vb3lhbGEtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtcGxheWVydmVyc2lvbidcblx0XHR9LFxuXHRcdGFtcExheW91dDoge1xuXHRcdFx0ZGVmYXVsdDogJ3Jlc3BvbnNpdmUnLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLW9veWFsYS1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnbGF5b3V0J1xuXHRcdH0sXG5cdFx0d2lkdGg6IHtcblx0XHRcdGRlZmF1bHQ6IDYwMCxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1vb3lhbGEtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ3dpZHRoJ1xuXHRcdH0sXG5cdFx0aGVpZ2h0OiB7XG5cdFx0XHRkZWZhdWx0OiA0MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtb295YWxhLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdoZWlnaHQnXG5cdFx0fVxuXHR9LFxuXG5cdGVkaXQ6IGZ1bmN0aW9uIGVkaXQocHJvcHMpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IHByb3BzLmF0dHJpYnV0ZXMsXG5cdFx0ICAgIHNldEF0dHJpYnV0ZXMgPSBwcm9wcy5zZXRBdHRyaWJ1dGVzO1xuXHRcdHZhciBkYXRhRW1iZWRDb2RlID0gYXR0cmlidXRlcy5kYXRhRW1iZWRDb2RlLFxuXHRcdCAgICBkYXRhUGxheWVySWQgPSBhdHRyaWJ1dGVzLmRhdGFQbGF5ZXJJZCxcblx0XHQgICAgZGF0YVBjb2RlID0gYXR0cmlidXRlcy5kYXRhUGNvZGUsXG5cdFx0ICAgIGRhdGFQbGF5ZXJWZXJzaW9uID0gYXR0cmlidXRlcy5kYXRhUGxheWVyVmVyc2lvbjtcblxuXHRcdHZhciBhbXBMYXlvdXRPcHRpb25zID0gW3sgdmFsdWU6ICdyZXNwb25zaXZlJywgbGFiZWw6IF9fKCdSZXNwb25zaXZlJywgJ2FtcCcpIH0sIHsgdmFsdWU6ICdmaXhlZCcsIGxhYmVsOiBfXygnRml4ZWQnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpbGwnLCBsYWJlbDogX18oJ0ZpbGwnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZsZXgtaXRlbScsIGxhYmVsOiBfXygnRmxleC1pdGVtJywgJ2FtcCcpIH1dO1xuXHRcdHZhciB1cmwgPSBmYWxzZTtcblx0XHRpZiAoZGF0YUVtYmVkQ29kZSAmJiBkYXRhUGxheWVySWQgJiYgZGF0YVBjb2RlKSB7XG5cdFx0XHR1cmwgPSAnaHR0cDovL2NmLmMub295YWxhLmNvbS8nICsgZGF0YUVtYmVkQ29kZTtcblx0XHR9XG5cdFx0cmV0dXJuIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdEZyYWdtZW50LFxuXHRcdFx0bnVsbCxcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0SW5zcGVjdG9yQ29udHJvbHMsXG5cdFx0XHRcdHsga2V5OiAnaW5zcGVjdG9yJyB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0UGFuZWxCb2R5LFxuXHRcdFx0XHRcdHsgdGl0bGU6IF9fKCdPb3lhbGEgc2V0dGluZ3MnLCAnYW1wJykgfSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnVmlkZW8gZW1iZWQgY29kZSAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFFbWJlZENvZGUsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhRW1iZWRDb2RlOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnUGxheWVyIElEIChyZXF1aXJlZCknLCAnYW1wJyksXG5cdFx0XHRcdFx0XHR2YWx1ZTogZGF0YVBsYXllcklkLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YVBsYXllcklkOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnUHJvdmlkZXIgY29kZSBmb3IgdGhlIGFjY291bnQgKHJlcXVpcmVkKScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhUGNvZGUsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhUGNvZGU6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChTZWxlY3RDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ1BsYXllciB2ZXJzaW9uJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFQbGF5ZXJWZXJzaW9uLFxuXHRcdFx0XHRcdFx0b3B0aW9uczogW3sgdmFsdWU6ICd2MycsIGxhYmVsOiBfXygnVjMnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ3Y0JywgbGFiZWw6IF9fKCdWNCcsICdhbXAnKSB9XSxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGRhdGFQbGF5ZXJWZXJzaW9uOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHRnZXRMYXlvdXRDb250cm9scyhwcm9wcywgYW1wTGF5b3V0T3B0aW9ucylcblx0XHRcdFx0KVxuXHRcdFx0KSxcblx0XHRcdHVybCAmJiBnZXRNZWRpYVBsYWNlaG9sZGVyKF9fKCdPb3lhbGEgUGxheWVyJywgJ2FtcCcpLCB1cmwpLFxuXHRcdFx0IXVybCAmJiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFBsYWNlaG9sZGVyLFxuXHRcdFx0XHR7IGxhYmVsOiBfXygnT295YWxhIFBsYXllcicsICdhbXAnKSB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J3AnLFxuXHRcdFx0XHRcdG51bGwsXG5cdFx0XHRcdFx0X18oJ0FkZCByZXF1aXJlZCBkYXRhIHRvIHVzZSB0aGUgYmxvY2suJywgJ2FtcCcpXG5cdFx0XHRcdClcblx0XHRcdClcblx0XHQpO1xuXHR9LFxuXHRzYXZlOiBmdW5jdGlvbiBzYXZlKF9yZWYpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IF9yZWYuYXR0cmlidXRlcztcblx0XHR2YXIgZGF0YUVtYmVkQ29kZSA9IGF0dHJpYnV0ZXMuZGF0YUVtYmVkQ29kZSxcblx0XHQgICAgZGF0YVBsYXllcklkID0gYXR0cmlidXRlcy5kYXRhUGxheWVySWQsXG5cdFx0ICAgIGRhdGFQY29kZSA9IGF0dHJpYnV0ZXMuZGF0YVBjb2RlLFxuXHRcdCAgICBkYXRhUGxheWVyVmVyc2lvbiA9IGF0dHJpYnV0ZXMuZGF0YVBsYXllclZlcnNpb24sXG5cdFx0ICAgIGFtcExheW91dCA9IGF0dHJpYnV0ZXMuYW1wTGF5b3V0LFxuXHRcdCAgICBoZWlnaHQgPSBhdHRyaWJ1dGVzLmhlaWdodCxcblx0XHQgICAgd2lkdGggPSBhdHRyaWJ1dGVzLndpZHRoO1xuXG5cblx0XHR2YXIgb295YWxhUHJvcHMgPSB7XG5cdFx0XHRsYXlvdXQ6IGFtcExheW91dCxcblx0XHRcdGhlaWdodDogaGVpZ2h0LFxuXHRcdFx0J2RhdGEtZW1iZWRjb2RlJzogZGF0YUVtYmVkQ29kZSxcblx0XHRcdCdkYXRhLXBsYXllcmlkJzogZGF0YVBsYXllcklkLFxuXHRcdFx0J2RhdGEtcGNvZGUnOiBkYXRhUGNvZGUsXG5cdFx0XHQnZGF0YS1wbGF5ZXJ2ZXJzaW9uJzogZGF0YVBsYXllclZlcnNpb25cblx0XHR9O1xuXHRcdGlmICgnZml4ZWQtaGVpZ2h0JyAhPT0gYW1wTGF5b3V0ICYmIHdpZHRoKSB7XG5cdFx0XHRvb3lhbGFQcm9wcy53aWR0aCA9IHdpZHRoO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KCdhbXAtb295YWxhLXBsYXllcicsIG9veWFsYVByb3BzKTtcblx0fVxufSk7XG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ibG9ja3MvYW1wLW9veWFsYS1wbGF5ZXIvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDdcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///7\n");
114
+
115
+ /***/ }),
116
+ /* 8 */
117
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
118
+
119
+ "use strict";
120
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n Placeholder = _wp$components.Placeholder;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-reach-player', {\n\ttitle: __('AMP Reach Player', 'amp'),\n\tdescription: __('Displays the Reach Player configured in the Beachfront Reach platform.', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp'), __('Beachfront Reach video', 'amp')],\n\n\tattributes: {\n\t\tdataEmbedId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-reach-player',\n\t\t\tattribute: 'data-embed-id'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'fixed-height',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-reach-player',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tdefault: 600,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-reach-player',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-reach-player',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar dataEmbedId = attributes.dataEmbedId;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed-height', label: __('Fixed Height', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fill', label: __('Fill', 'amp') }, { value: 'flex-item', label: __('Flex-item', 'amp') }];\n\t\tvar url = false;\n\t\tif (dataEmbedId) {\n\t\t\turl = 'https://media-cdn.beachfrontreach.com/acct_1/video/';\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('Reach settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('The Reach player embed id (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataEmbedId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataEmbedId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\turl && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('Reach Player', 'amp'), url),\n\t\t\t!url && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('Reach Player', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add Reach player embed ID to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\t\tvar dataEmbedId = attributes.dataEmbedId,\n\t\t ampLayout = attributes.ampLayout,\n\t\t height = attributes.height,\n\t\t width = attributes.width;\n\n\n\t\tvar reachProps = {\n\t\t\tlayout: ampLayout,\n\t\t\theight: height,\n\t\t\t'data-embed-id': dataEmbedId\n\t\t};\n\t\tif ('fixed-height' !== ampLayout && width) {\n\t\t\treachProps.width = width;\n\t\t}\n\t\treturn wp.element.createElement('amp-reach-player', reachProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiOC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9hbXAtcmVhY2gtcGxheWVyL2luZGV4LmpzPzIwNmIiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBIZWxwZXIgbWV0aG9kcyBmb3IgYmxvY2tzLlxuICovXG5pbXBvcnQgeyBnZXRMYXlvdXRDb250cm9scywgZ2V0TWVkaWFQbGFjZWhvbGRlciB9IGZyb20gJy4uL3V0aWxzLmpzJztcblxuLyoqXG4gKiBJbnRlcm5hbCBibG9jayBsaWJyYXJpZXMuXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG52YXIgSW5zcGVjdG9yQ29udHJvbHMgPSB3cC5lZGl0b3IuSW5zcGVjdG9yQ29udHJvbHM7XG52YXIgRnJhZ21lbnQgPSB3cC5lbGVtZW50LkZyYWdtZW50O1xudmFyIF93cCRjb21wb25lbnRzID0gd3AuY29tcG9uZW50cyxcbiAgICBQYW5lbEJvZHkgPSBfd3AkY29tcG9uZW50cy5QYW5lbEJvZHksXG4gICAgVGV4dENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5UZXh0Q29udHJvbCxcbiAgICBQbGFjZWhvbGRlciA9IF93cCRjb21wb25lbnRzLlBsYWNlaG9sZGVyO1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IHJlZ2lzdGVyQmxvY2tUeXBlKCdhbXAvYW1wLXJlYWNoLXBsYXllcicsIHtcblx0dGl0bGU6IF9fKCdBTVAgUmVhY2ggUGxheWVyJywgJ2FtcCcpLFxuXHRkZXNjcmlwdGlvbjogX18oJ0Rpc3BsYXlzIHRoZSBSZWFjaCBQbGF5ZXIgY29uZmlndXJlZCBpbiB0aGUgQmVhY2hmcm9udCBSZWFjaCBwbGF0Zm9ybS4nLCAnYW1wJyksXG5cdGNhdGVnb3J5OiAnZW1iZWQnLFxuXHRpY29uOiAnZW1iZWQtZ2VuZXJpYycsXG5cdGtleXdvcmRzOiBbX18oJ0VtYmVkJywgJ2FtcCcpLCBfXygnQmVhY2hmcm9udCBSZWFjaCB2aWRlbycsICdhbXAnKV0sXG5cblx0YXR0cmlidXRlczoge1xuXHRcdGRhdGFFbWJlZElkOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtcmVhY2gtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtZW1iZWQtaWQnXG5cdFx0fSxcblx0XHRhbXBMYXlvdXQ6IHtcblx0XHRcdGRlZmF1bHQ6ICdmaXhlZC1oZWlnaHQnLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXJlYWNoLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdsYXlvdXQnXG5cdFx0fSxcblx0XHR3aWR0aDoge1xuXHRcdFx0ZGVmYXVsdDogNjAwLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXJlYWNoLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICd3aWR0aCdcblx0XHR9LFxuXHRcdGhlaWdodDoge1xuXHRcdFx0ZGVmYXVsdDogNDAwLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXJlYWNoLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdoZWlnaHQnXG5cdFx0fVxuXHR9LFxuXG5cdGVkaXQ6IGZ1bmN0aW9uIGVkaXQocHJvcHMpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IHByb3BzLmF0dHJpYnV0ZXMsXG5cdFx0ICAgIHNldEF0dHJpYnV0ZXMgPSBwcm9wcy5zZXRBdHRyaWJ1dGVzO1xuXHRcdHZhciBkYXRhRW1iZWRJZCA9IGF0dHJpYnV0ZXMuZGF0YUVtYmVkSWQ7XG5cblx0XHR2YXIgYW1wTGF5b3V0T3B0aW9ucyA9IFt7IHZhbHVlOiAncmVzcG9uc2l2ZScsIGxhYmVsOiBfXygnUmVzcG9uc2l2ZScsICdhbXAnKSB9LCB7IHZhbHVlOiAnZml4ZWQtaGVpZ2h0JywgbGFiZWw6IF9fKCdGaXhlZCBIZWlnaHQnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkJywgbGFiZWw6IF9fKCdGaXhlZCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZmlsbCcsIGxhYmVsOiBfXygnRmlsbCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZmxleC1pdGVtJywgbGFiZWw6IF9fKCdGbGV4LWl0ZW0nLCAnYW1wJykgfV07XG5cdFx0dmFyIHVybCA9IGZhbHNlO1xuXHRcdGlmIChkYXRhRW1iZWRJZCkge1xuXHRcdFx0dXJsID0gJ2h0dHBzOi8vbWVkaWEtY2RuLmJlYWNoZnJvbnRyZWFjaC5jb20vYWNjdF8xL3ZpZGVvLyc7XG5cdFx0fVxuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRGcmFnbWVudCxcblx0XHRcdG51bGwsXG5cdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdEluc3BlY3RvckNvbnRyb2xzLFxuXHRcdFx0XHR7IGtleTogJ2luc3BlY3RvcicgfSxcblx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdFBhbmVsQm9keSxcblx0XHRcdFx0XHR7IHRpdGxlOiBfXygnUmVhY2ggc2V0dGluZ3MnLCAnYW1wJykgfSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnVGhlIFJlYWNoIHBsYXllciBlbWJlZCBpZCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFFbWJlZElkLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YUVtYmVkSWQ6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdGdldExheW91dENvbnRyb2xzKHByb3BzLCBhbXBMYXlvdXRPcHRpb25zKVxuXHRcdFx0XHQpXG5cdFx0XHQpLFxuXHRcdFx0dXJsICYmIGdldE1lZGlhUGxhY2Vob2xkZXIoX18oJ1JlYWNoIFBsYXllcicsICdhbXAnKSwgdXJsKSxcblx0XHRcdCF1cmwgJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRQbGFjZWhvbGRlcixcblx0XHRcdFx0eyBsYWJlbDogX18oJ1JlYWNoIFBsYXllcicsICdhbXAnKSB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J3AnLFxuXHRcdFx0XHRcdG51bGwsXG5cdFx0XHRcdFx0X18oJ0FkZCBSZWFjaCBwbGF5ZXIgZW1iZWQgSUQgdG8gdXNlIHRoZSBibG9jay4nLCAnYW1wJylcblx0XHRcdFx0KVxuXHRcdFx0KVxuXHRcdCk7XG5cdH0sXG5cdHNhdmU6IGZ1bmN0aW9uIHNhdmUoX3JlZikge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gX3JlZi5hdHRyaWJ1dGVzO1xuXHRcdHZhciBkYXRhRW1iZWRJZCA9IGF0dHJpYnV0ZXMuZGF0YUVtYmVkSWQsXG5cdFx0ICAgIGFtcExheW91dCA9IGF0dHJpYnV0ZXMuYW1wTGF5b3V0LFxuXHRcdCAgICBoZWlnaHQgPSBhdHRyaWJ1dGVzLmhlaWdodCxcblx0XHQgICAgd2lkdGggPSBhdHRyaWJ1dGVzLndpZHRoO1xuXG5cblx0XHR2YXIgcmVhY2hQcm9wcyA9IHtcblx0XHRcdGxheW91dDogYW1wTGF5b3V0LFxuXHRcdFx0aGVpZ2h0OiBoZWlnaHQsXG5cdFx0XHQnZGF0YS1lbWJlZC1pZCc6IGRhdGFFbWJlZElkXG5cdFx0fTtcblx0XHRpZiAoJ2ZpeGVkLWhlaWdodCcgIT09IGFtcExheW91dCAmJiB3aWR0aCkge1xuXHRcdFx0cmVhY2hQcm9wcy53aWR0aCA9IHdpZHRoO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KCdhbXAtcmVhY2gtcGxheWVyJywgcmVhY2hQcm9wcyk7XG5cdH1cbn0pO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vYmxvY2tzL2FtcC1yZWFjaC1wbGF5ZXIvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDhcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///8\n");
121
+
122
+ /***/ }),
123
+ /* 9 */
124
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
125
+
126
+ "use strict";
127
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n SelectControl = _wp$components.SelectControl,\n Placeholder = _wp$components.Placeholder;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-springboard-player', {\n\ttitle: __('AMP Springboard Player', 'amp'),\n\tdescription: __('Displays the Springboard Player used in the Springboard Video Platform', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp')],\n\n\tattributes: {\n\t\tdataSiteId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'data-site-id'\n\t\t},\n\t\tdataContentId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'data-content-id'\n\t\t},\n\t\tdataPlayerId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'data-player-id'\n\t\t},\n\t\tdataDomain: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'data-domain'\n\t\t},\n\t\tdataMode: {\n\t\t\tdefault: 'video',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'data-mode'\n\t\t},\n\t\tdataItems: {\n\t\t\tdefault: 1,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'data-items'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'responsive',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tdefault: 600,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-springboard-player',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar dataSiteId = attributes.dataSiteId,\n\t\t dataPlayerId = attributes.dataPlayerId,\n\t\t dataContentId = attributes.dataContentId,\n\t\t dataDomain = attributes.dataDomain,\n\t\t dataMode = attributes.dataMode,\n\t\t dataItems = attributes.dataItems;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fill', label: __('Fill', 'amp') }, { value: 'flex-item', label: __('Flex-item', 'amp') }];\n\t\tvar url = false;\n\t\tif (dataSiteId && dataContentId && dataDomain && dataMode && dataItems) {\n\t\t\turl = 'https://cms.springboardplatform.com/embed_iframe/';\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('Springboard Player Settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('SprintBoard site ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataSiteId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataSiteId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Player content ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataContentId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataContentId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Player ID', 'amp'),\n\t\t\t\t\t\tvalue: dataPlayerId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlayerId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Springboard partner domain', 'amp'),\n\t\t\t\t\t\tvalue: dataDomain,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataDomain: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(SelectControl, {\n\t\t\t\t\t\tlabel: __('Mode (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataMode,\n\t\t\t\t\t\toptions: [{ value: 'video', label: __('Video', 'amp') }, { value: 'playlist', label: __('Playlist', 'amp') }],\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataMode: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tlabel: __('Number of video is playlist (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataItems,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataItems: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\turl && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('Springboard Player', 'amp'), url),\n\t\t\t!url && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('Springboard Player', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add required data to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\t\tvar dataSiteId = attributes.dataSiteId,\n\t\t dataPlayerId = attributes.dataPlayerId,\n\t\t dataContentId = attributes.dataContentId,\n\t\t dataDomain = attributes.dataDomain,\n\t\t dataMode = attributes.dataMode,\n\t\t dataItems = attributes.dataItems,\n\t\t ampLayout = attributes.ampLayout,\n\t\t height = attributes.height,\n\t\t width = attributes.width;\n\n\t\tvar springboardProps = {\n\t\t\tlayout: ampLayout,\n\t\t\theight: height,\n\t\t\t'data-site-id': dataSiteId,\n\t\t\t'data-mode': dataMode,\n\t\t\t'data-content-id': dataContentId,\n\t\t\t'data-player-id': dataPlayerId,\n\t\t\t'data-domain': dataDomain,\n\t\t\t'data-items': dataItems\n\t\t};\n\t\tif ('fixed-height' !== ampLayout && width) {\n\t\t\tspringboardProps.width = attributes.width;\n\t\t}\n\t\treturn wp.element.createElement('amp-springboard-player', springboardProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiOS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL2Jsb2Nrcy9hbXAtc3ByaW5nYm9hcmQtcGxheWVyL2luZGV4LmpzPzcyYjgiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBIZWxwZXIgbWV0aG9kcyBmb3IgYmxvY2tzLlxuICovXG5pbXBvcnQgeyBnZXRMYXlvdXRDb250cm9scywgZ2V0TWVkaWFQbGFjZWhvbGRlciB9IGZyb20gJy4uL3V0aWxzLmpzJztcblxuLyoqXG4gKiBJbnRlcm5hbCBibG9jayBsaWJyYXJpZXMuXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG52YXIgSW5zcGVjdG9yQ29udHJvbHMgPSB3cC5lZGl0b3IuSW5zcGVjdG9yQ29udHJvbHM7XG52YXIgRnJhZ21lbnQgPSB3cC5lbGVtZW50LkZyYWdtZW50O1xudmFyIF93cCRjb21wb25lbnRzID0gd3AuY29tcG9uZW50cyxcbiAgICBQYW5lbEJvZHkgPSBfd3AkY29tcG9uZW50cy5QYW5lbEJvZHksXG4gICAgVGV4dENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5UZXh0Q29udHJvbCxcbiAgICBTZWxlY3RDb250cm9sID0gX3dwJGNvbXBvbmVudHMuU2VsZWN0Q29udHJvbCxcbiAgICBQbGFjZWhvbGRlciA9IF93cCRjb21wb25lbnRzLlBsYWNlaG9sZGVyO1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IHJlZ2lzdGVyQmxvY2tUeXBlKCdhbXAvYW1wLXNwcmluZ2JvYXJkLXBsYXllcicsIHtcblx0dGl0bGU6IF9fKCdBTVAgU3ByaW5nYm9hcmQgUGxheWVyJywgJ2FtcCcpLFxuXHRkZXNjcmlwdGlvbjogX18oJ0Rpc3BsYXlzIHRoZSBTcHJpbmdib2FyZCBQbGF5ZXIgdXNlZCBpbiB0aGUgU3ByaW5nYm9hcmQgVmlkZW8gUGxhdGZvcm0nLCAnYW1wJyksXG5cdGNhdGVnb3J5OiAnZW1iZWQnLFxuXHRpY29uOiAnZW1iZWQtZ2VuZXJpYycsXG5cdGtleXdvcmRzOiBbX18oJ0VtYmVkJywgJ2FtcCcpXSxcblxuXHRhdHRyaWJ1dGVzOiB7XG5cdFx0ZGF0YVNpdGVJZDoge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXNwcmluZ2JvYXJkLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLXNpdGUtaWQnXG5cdFx0fSxcblx0XHRkYXRhQ29udGVudElkOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtc3ByaW5nYm9hcmQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtY29udGVudC1pZCdcblx0XHR9LFxuXHRcdGRhdGFQbGF5ZXJJZDoge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXNwcmluZ2JvYXJkLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLXBsYXllci1pZCdcblx0XHR9LFxuXHRcdGRhdGFEb21haW46IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1zcHJpbmdib2FyZC1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1kb21haW4nXG5cdFx0fSxcblx0XHRkYXRhTW9kZToge1xuXHRcdFx0ZGVmYXVsdDogJ3ZpZGVvJyxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1zcHJpbmdib2FyZC1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1tb2RlJ1xuXHRcdH0sXG5cdFx0ZGF0YUl0ZW1zOiB7XG5cdFx0XHRkZWZhdWx0OiAxLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLXNwcmluZ2JvYXJkLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLWl0ZW1zJ1xuXHRcdH0sXG5cdFx0YW1wTGF5b3V0OiB7XG5cdFx0XHRkZWZhdWx0OiAncmVzcG9uc2l2ZScsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtc3ByaW5nYm9hcmQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2xheW91dCdcblx0XHR9LFxuXHRcdHdpZHRoOiB7XG5cdFx0XHRkZWZhdWx0OiA2MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtc3ByaW5nYm9hcmQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ3dpZHRoJ1xuXHRcdH0sXG5cdFx0aGVpZ2h0OiB7XG5cdFx0XHRkZWZhdWx0OiA0MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtc3ByaW5nYm9hcmQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2hlaWdodCdcblx0XHR9XG5cdH0sXG5cblx0ZWRpdDogZnVuY3Rpb24gZWRpdChwcm9wcykge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gcHJvcHMuYXR0cmlidXRlcyxcblx0XHQgICAgc2V0QXR0cmlidXRlcyA9IHByb3BzLnNldEF0dHJpYnV0ZXM7XG5cdFx0dmFyIGRhdGFTaXRlSWQgPSBhdHRyaWJ1dGVzLmRhdGFTaXRlSWQsXG5cdFx0ICAgIGRhdGFQbGF5ZXJJZCA9IGF0dHJpYnV0ZXMuZGF0YVBsYXllcklkLFxuXHRcdCAgICBkYXRhQ29udGVudElkID0gYXR0cmlidXRlcy5kYXRhQ29udGVudElkLFxuXHRcdCAgICBkYXRhRG9tYWluID0gYXR0cmlidXRlcy5kYXRhRG9tYWluLFxuXHRcdCAgICBkYXRhTW9kZSA9IGF0dHJpYnV0ZXMuZGF0YU1vZGUsXG5cdFx0ICAgIGRhdGFJdGVtcyA9IGF0dHJpYnV0ZXMuZGF0YUl0ZW1zO1xuXG5cdFx0dmFyIGFtcExheW91dE9wdGlvbnMgPSBbeyB2YWx1ZTogJ3Jlc3BvbnNpdmUnLCBsYWJlbDogX18oJ1Jlc3BvbnNpdmUnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkJywgbGFiZWw6IF9fKCdGaXhlZCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZmlsbCcsIGxhYmVsOiBfXygnRmlsbCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZmxleC1pdGVtJywgbGFiZWw6IF9fKCdGbGV4LWl0ZW0nLCAnYW1wJykgfV07XG5cdFx0dmFyIHVybCA9IGZhbHNlO1xuXHRcdGlmIChkYXRhU2l0ZUlkICYmIGRhdGFDb250ZW50SWQgJiYgZGF0YURvbWFpbiAmJiBkYXRhTW9kZSAmJiBkYXRhSXRlbXMpIHtcblx0XHRcdHVybCA9ICdodHRwczovL2Ntcy5zcHJpbmdib2FyZHBsYXRmb3JtLmNvbS9lbWJlZF9pZnJhbWUvJztcblx0XHR9XG5cdFx0cmV0dXJuIHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdEZyYWdtZW50LFxuXHRcdFx0bnVsbCxcblx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0SW5zcGVjdG9yQ29udHJvbHMsXG5cdFx0XHRcdHsga2V5OiAnaW5zcGVjdG9yJyB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0UGFuZWxCb2R5LFxuXHRcdFx0XHRcdHsgdGl0bGU6IF9fKCdTcHJpbmdib2FyZCBQbGF5ZXIgU2V0dGluZ3MnLCAnYW1wJykgfSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnU3ByaW50Qm9hcmQgc2l0ZSBJRCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFTaXRlSWQsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhU2l0ZUlkOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnUGxheWVyIGNvbnRlbnQgSUQgKHJlcXVpcmVkKScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhQ29udGVudElkLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YUNvbnRlbnRJZDogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ1BsYXllciBJRCcsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhUGxheWVySWQsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhUGxheWVySWQ6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUZXh0Q29udHJvbCwge1xuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdTcHJpbmdib2FyZCBwYXJ0bmVyIGRvbWFpbicsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhRG9tYWluLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YURvbWFpbjogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFNlbGVjdENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnTW9kZSAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFNb2RlLFxuXHRcdFx0XHRcdFx0b3B0aW9uczogW3sgdmFsdWU6ICd2aWRlbycsIGxhYmVsOiBfXygnVmlkZW8nLCAnYW1wJykgfSwgeyB2YWx1ZTogJ3BsYXlsaXN0JywgbGFiZWw6IF9fKCdQbGF5bGlzdCcsICdhbXAnKSB9XSxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGRhdGFNb2RlOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdHR5cGU6ICdudW1iZXInLFxuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdOdW1iZXIgb2YgdmlkZW8gaXMgcGxheWxpc3QgKHJlcXVpcmVkKScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhSXRlbXMsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhSXRlbXM6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdGdldExheW91dENvbnRyb2xzKHByb3BzLCBhbXBMYXlvdXRPcHRpb25zKVxuXHRcdFx0XHQpXG5cdFx0XHQpLFxuXHRcdFx0dXJsICYmIGdldE1lZGlhUGxhY2Vob2xkZXIoX18oJ1NwcmluZ2JvYXJkIFBsYXllcicsICdhbXAnKSwgdXJsKSxcblx0XHRcdCF1cmwgJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRQbGFjZWhvbGRlcixcblx0XHRcdFx0eyBsYWJlbDogX18oJ1NwcmluZ2JvYXJkIFBsYXllcicsICdhbXAnKSB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J3AnLFxuXHRcdFx0XHRcdG51bGwsXG5cdFx0XHRcdFx0X18oJ0FkZCByZXF1aXJlZCBkYXRhIHRvIHVzZSB0aGUgYmxvY2suJywgJ2FtcCcpXG5cdFx0XHRcdClcblx0XHRcdClcblx0XHQpO1xuXHR9LFxuXHRzYXZlOiBmdW5jdGlvbiBzYXZlKF9yZWYpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IF9yZWYuYXR0cmlidXRlcztcblx0XHR2YXIgZGF0YVNpdGVJZCA9IGF0dHJpYnV0ZXMuZGF0YVNpdGVJZCxcblx0XHQgICAgZGF0YVBsYXllcklkID0gYXR0cmlidXRlcy5kYXRhUGxheWVySWQsXG5cdFx0ICAgIGRhdGFDb250ZW50SWQgPSBhdHRyaWJ1dGVzLmRhdGFDb250ZW50SWQsXG5cdFx0ICAgIGRhdGFEb21haW4gPSBhdHRyaWJ1dGVzLmRhdGFEb21haW4sXG5cdFx0ICAgIGRhdGFNb2RlID0gYXR0cmlidXRlcy5kYXRhTW9kZSxcblx0XHQgICAgZGF0YUl0ZW1zID0gYXR0cmlidXRlcy5kYXRhSXRlbXMsXG5cdFx0ICAgIGFtcExheW91dCA9IGF0dHJpYnV0ZXMuYW1wTGF5b3V0LFxuXHRcdCAgICBoZWlnaHQgPSBhdHRyaWJ1dGVzLmhlaWdodCxcblx0XHQgICAgd2lkdGggPSBhdHRyaWJ1dGVzLndpZHRoO1xuXG5cdFx0dmFyIHNwcmluZ2JvYXJkUHJvcHMgPSB7XG5cdFx0XHRsYXlvdXQ6IGFtcExheW91dCxcblx0XHRcdGhlaWdodDogaGVpZ2h0LFxuXHRcdFx0J2RhdGEtc2l0ZS1pZCc6IGRhdGFTaXRlSWQsXG5cdFx0XHQnZGF0YS1tb2RlJzogZGF0YU1vZGUsXG5cdFx0XHQnZGF0YS1jb250ZW50LWlkJzogZGF0YUNvbnRlbnRJZCxcblx0XHRcdCdkYXRhLXBsYXllci1pZCc6IGRhdGFQbGF5ZXJJZCxcblx0XHRcdCdkYXRhLWRvbWFpbic6IGRhdGFEb21haW4sXG5cdFx0XHQnZGF0YS1pdGVtcyc6IGRhdGFJdGVtc1xuXHRcdH07XG5cdFx0aWYgKCdmaXhlZC1oZWlnaHQnICE9PSBhbXBMYXlvdXQgJiYgd2lkdGgpIHtcblx0XHRcdHNwcmluZ2JvYXJkUHJvcHMud2lkdGggPSBhdHRyaWJ1dGVzLndpZHRoO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KCdhbXAtc3ByaW5nYm9hcmQtcGxheWVyJywgc3ByaW5nYm9hcmRQcm9wcyk7XG5cdH1cbn0pO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vYmxvY2tzL2FtcC1zcHJpbmdib2FyZC1wbGF5ZXIvaW5kZXguanNcbi8vIG1vZHVsZSBpZCA9IDlcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///9\n");
128
+
129
+ /***/ }),
130
+ /* 10 */
131
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
132
+
133
+ "use strict";
134
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n Placeholder = _wp$components.Placeholder;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-jwplayer', {\n\ttitle: __('AMP JW Player', 'amp'),\n\tdescription: __('Displays a cloud-hosted JW Player.', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp')],\n\n\tattributes: {\n\t\tdataPlayerId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-jwplayer',\n\t\t\tattribute: 'data-player-id'\n\t\t},\n\t\tdataMediaId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-jwplayer',\n\t\t\tattribute: 'data-media-id'\n\t\t},\n\t\tdataPlaylistId: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-jwplayer',\n\t\t\tattribute: 'data-playlist-id'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'responsive',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-jwplayer',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tdefault: 600,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-jwplayer',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-jwplayer',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar dataPlayerId = attributes.dataPlayerId,\n\t\t dataMediaId = attributes.dataMediaId,\n\t\t dataPlaylistId = attributes.dataPlaylistId;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed-height', label: __('Fixed height', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fill', label: __('Fill', 'amp') }, { value: 'flex-item', label: __('Flex-item', 'amp') }, { value: 'nodisplay', label: __('No Display', 'amp') }];\n\t\tvar url = false;\n\t\tif (dataPlayerId && (dataMediaId || dataPlaylistId)) {\n\t\t\tif (dataPlaylistId) {\n\t\t\t\turl = 'https://content.jwplatform.com/players/' + dataPlaylistId + '-' + dataPlayerId;\n\t\t\t} else {\n\t\t\t\turl = 'https://content.jwplatform.com/players/' + dataMediaId + '-' + dataPlayerId;\n\t\t\t}\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('JW Player Settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Player ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPlayerId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlayerId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Media ID (required if playlist ID not set)', 'amp'),\n\t\t\t\t\t\tvalue: dataMediaId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataMediaId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Playlist ID (required if media ID not set)', 'amp'),\n\t\t\t\t\t\tvalue: dataPlaylistId,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlaylistId: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\turl && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('JW Player', 'amp'), url),\n\t\t\t!url && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('JW Player', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add required data to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\n\t\tvar jwProps = {\n\t\t\tlayout: attributes.ampLayout,\n\t\t\theight: attributes.height,\n\t\t\t'data-player-id': attributes.dataPlayerId\n\t\t};\n\t\tif ('fixed-height' !== attributes.ampLayout && attributes.width) {\n\t\t\tjwProps.width = attributes.width;\n\t\t}\n\t\tif (attributes.dataPlaylistId) {\n\t\t\tjwProps['data-playlist-id'] = attributes.dataPlaylistId;\n\t\t}\n\t\tif (attributes.dataMediaId) {\n\t\t\tjwProps['data-media-id'] = attributes.dataMediaId;\n\t\t}\n\t\treturn wp.element.createElement('amp-jwplayer', jwProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTAuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9ibG9ja3MvYW1wLWp3cGxheWVyL2luZGV4LmpzP2E1ZDAiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBIZWxwZXIgbWV0aG9kcyBmb3IgYmxvY2tzLlxuICovXG5pbXBvcnQgeyBnZXRMYXlvdXRDb250cm9scywgZ2V0TWVkaWFQbGFjZWhvbGRlciB9IGZyb20gJy4uL3V0aWxzLmpzJztcblxuLyoqXG4gKiBJbnRlcm5hbCBibG9jayBsaWJyYXJpZXMuXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG52YXIgSW5zcGVjdG9yQ29udHJvbHMgPSB3cC5lZGl0b3IuSW5zcGVjdG9yQ29udHJvbHM7XG52YXIgRnJhZ21lbnQgPSB3cC5lbGVtZW50LkZyYWdtZW50O1xudmFyIF93cCRjb21wb25lbnRzID0gd3AuY29tcG9uZW50cyxcbiAgICBQYW5lbEJvZHkgPSBfd3AkY29tcG9uZW50cy5QYW5lbEJvZHksXG4gICAgVGV4dENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5UZXh0Q29udHJvbCxcbiAgICBQbGFjZWhvbGRlciA9IF93cCRjb21wb25lbnRzLlBsYWNlaG9sZGVyO1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IHJlZ2lzdGVyQmxvY2tUeXBlKCdhbXAvYW1wLWp3cGxheWVyJywge1xuXHR0aXRsZTogX18oJ0FNUCBKVyBQbGF5ZXInLCAnYW1wJyksXG5cdGRlc2NyaXB0aW9uOiBfXygnRGlzcGxheXMgYSBjbG91ZC1ob3N0ZWQgSlcgUGxheWVyLicsICdhbXAnKSxcblx0Y2F0ZWdvcnk6ICdlbWJlZCcsXG5cdGljb246ICdlbWJlZC1nZW5lcmljJyxcblx0a2V5d29yZHM6IFtfXygnRW1iZWQnLCAnYW1wJyldLFxuXG5cdGF0dHJpYnV0ZXM6IHtcblx0XHRkYXRhUGxheWVySWQ6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1qd3BsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLXBsYXllci1pZCdcblx0XHR9LFxuXHRcdGRhdGFNZWRpYUlkOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtandwbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1tZWRpYS1pZCdcblx0XHR9LFxuXHRcdGRhdGFQbGF5bGlzdElkOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtandwbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1wbGF5bGlzdC1pZCdcblx0XHR9LFxuXHRcdGFtcExheW91dDoge1xuXHRcdFx0ZGVmYXVsdDogJ3Jlc3BvbnNpdmUnLFxuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLWp3cGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2xheW91dCdcblx0XHR9LFxuXHRcdHdpZHRoOiB7XG5cdFx0XHRkZWZhdWx0OiA2MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtandwbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnd2lkdGgnXG5cdFx0fSxcblx0XHRoZWlnaHQ6IHtcblx0XHRcdGRlZmF1bHQ6IDQwMCxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1qd3BsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdoZWlnaHQnXG5cdFx0fVxuXHR9LFxuXG5cdGVkaXQ6IGZ1bmN0aW9uIGVkaXQocHJvcHMpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IHByb3BzLmF0dHJpYnV0ZXMsXG5cdFx0ICAgIHNldEF0dHJpYnV0ZXMgPSBwcm9wcy5zZXRBdHRyaWJ1dGVzO1xuXHRcdHZhciBkYXRhUGxheWVySWQgPSBhdHRyaWJ1dGVzLmRhdGFQbGF5ZXJJZCxcblx0XHQgICAgZGF0YU1lZGlhSWQgPSBhdHRyaWJ1dGVzLmRhdGFNZWRpYUlkLFxuXHRcdCAgICBkYXRhUGxheWxpc3RJZCA9IGF0dHJpYnV0ZXMuZGF0YVBsYXlsaXN0SWQ7XG5cblx0XHR2YXIgYW1wTGF5b3V0T3B0aW9ucyA9IFt7IHZhbHVlOiAncmVzcG9uc2l2ZScsIGxhYmVsOiBfXygnUmVzcG9uc2l2ZScsICdhbXAnKSB9LCB7IHZhbHVlOiAnZml4ZWQtaGVpZ2h0JywgbGFiZWw6IF9fKCdGaXhlZCBoZWlnaHQnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkJywgbGFiZWw6IF9fKCdGaXhlZCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZmlsbCcsIGxhYmVsOiBfXygnRmlsbCcsICdhbXAnKSB9LCB7IHZhbHVlOiAnZmxleC1pdGVtJywgbGFiZWw6IF9fKCdGbGV4LWl0ZW0nLCAnYW1wJykgfSwgeyB2YWx1ZTogJ25vZGlzcGxheScsIGxhYmVsOiBfXygnTm8gRGlzcGxheScsICdhbXAnKSB9XTtcblx0XHR2YXIgdXJsID0gZmFsc2U7XG5cdFx0aWYgKGRhdGFQbGF5ZXJJZCAmJiAoZGF0YU1lZGlhSWQgfHwgZGF0YVBsYXlsaXN0SWQpKSB7XG5cdFx0XHRpZiAoZGF0YVBsYXlsaXN0SWQpIHtcblx0XHRcdFx0dXJsID0gJ2h0dHBzOi8vY29udGVudC5qd3BsYXRmb3JtLmNvbS9wbGF5ZXJzLycgKyBkYXRhUGxheWxpc3RJZCArICctJyArIGRhdGFQbGF5ZXJJZDtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdHVybCA9ICdodHRwczovL2NvbnRlbnQuandwbGF0Zm9ybS5jb20vcGxheWVycy8nICsgZGF0YU1lZGlhSWQgKyAnLScgKyBkYXRhUGxheWVySWQ7XG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRGcmFnbWVudCxcblx0XHRcdG51bGwsXG5cdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdEluc3BlY3RvckNvbnRyb2xzLFxuXHRcdFx0XHR7IGtleTogJ2luc3BlY3RvcicgfSxcblx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRcdFBhbmVsQm9keSxcblx0XHRcdFx0XHR7IHRpdGxlOiBfXygnSlcgUGxheWVyIFNldHRpbmdzJywgJ2FtcCcpIH0sXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ1BsYXllciBJRCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFQbGF5ZXJJZCxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGRhdGFQbGF5ZXJJZDogdmFsdWUgfSk7XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fSksXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ01lZGlhIElEIChyZXF1aXJlZCBpZiBwbGF5bGlzdCBJRCBub3Qgc2V0KScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhTWVkaWFJZCxcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGRhdGFNZWRpYUlkOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnUGxheWxpc3QgSUQgKHJlcXVpcmVkIGlmIG1lZGlhIElEIG5vdCBzZXQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFQbGF5bGlzdElkLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YVBsYXlsaXN0SWQ6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdGdldExheW91dENvbnRyb2xzKHByb3BzLCBhbXBMYXlvdXRPcHRpb25zKVxuXHRcdFx0XHQpXG5cdFx0XHQpLFxuXHRcdFx0dXJsICYmIGdldE1lZGlhUGxhY2Vob2xkZXIoX18oJ0pXIFBsYXllcicsICdhbXAnKSwgdXJsKSxcblx0XHRcdCF1cmwgJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRQbGFjZWhvbGRlcixcblx0XHRcdFx0eyBsYWJlbDogX18oJ0pXIFBsYXllcicsICdhbXAnKSB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J3AnLFxuXHRcdFx0XHRcdG51bGwsXG5cdFx0XHRcdFx0X18oJ0FkZCByZXF1aXJlZCBkYXRhIHRvIHVzZSB0aGUgYmxvY2suJywgJ2FtcCcpXG5cdFx0XHRcdClcblx0XHRcdClcblx0XHQpO1xuXHR9LFxuXHRzYXZlOiBmdW5jdGlvbiBzYXZlKF9yZWYpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IF9yZWYuYXR0cmlidXRlcztcblxuXHRcdHZhciBqd1Byb3BzID0ge1xuXHRcdFx0bGF5b3V0OiBhdHRyaWJ1dGVzLmFtcExheW91dCxcblx0XHRcdGhlaWdodDogYXR0cmlidXRlcy5oZWlnaHQsXG5cdFx0XHQnZGF0YS1wbGF5ZXItaWQnOiBhdHRyaWJ1dGVzLmRhdGFQbGF5ZXJJZFxuXHRcdH07XG5cdFx0aWYgKCdmaXhlZC1oZWlnaHQnICE9PSBhdHRyaWJ1dGVzLmFtcExheW91dCAmJiBhdHRyaWJ1dGVzLndpZHRoKSB7XG5cdFx0XHRqd1Byb3BzLndpZHRoID0gYXR0cmlidXRlcy53aWR0aDtcblx0XHR9XG5cdFx0aWYgKGF0dHJpYnV0ZXMuZGF0YVBsYXlsaXN0SWQpIHtcblx0XHRcdGp3UHJvcHNbJ2RhdGEtcGxheWxpc3QtaWQnXSA9IGF0dHJpYnV0ZXMuZGF0YVBsYXlsaXN0SWQ7XG5cdFx0fVxuXHRcdGlmIChhdHRyaWJ1dGVzLmRhdGFNZWRpYUlkKSB7XG5cdFx0XHRqd1Byb3BzWydkYXRhLW1lZGlhLWlkJ10gPSBhdHRyaWJ1dGVzLmRhdGFNZWRpYUlkO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KCdhbXAtandwbGF5ZXInLCBqd1Byb3BzKTtcblx0fVxufSk7XG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ibG9ja3MvYW1wLWp3cGxheWVyL2luZGV4LmpzXG4vLyBtb2R1bGUgaWQgPSAxMFxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///10\n");
135
+
136
+ /***/ }),
137
+ /* 11 */
138
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
139
+
140
+ "use strict";
141
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n Placeholder = _wp$components.Placeholder,\n ToggleControl = _wp$components.ToggleControl;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-brid-player', {\n\ttitle: __('AMP Brid Player', 'amp'),\n\tdescription: __('Displays the Brid Player used in Brid.tv Video Platform.', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp')],\n\n\tattributes: {\n\t\tautoPlay: {\n\t\t\ttype: 'boolean'\n\t\t},\n\t\tdataPartner: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'data-partner'\n\t\t},\n\t\tdataPlayer: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'data-player'\n\t\t},\n\t\tdataVideo: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'data-video'\n\t\t},\n\t\tdataPlaylist: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'data-playlist'\n\t\t},\n\t\tdataOutstream: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'data-outstream'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'responsive',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\ttype: 'number',\n\t\t\tdefault: 600\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-brid-player',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar autoPlay = attributes.autoPlay,\n\t\t dataPartner = attributes.dataPartner,\n\t\t dataPlayer = attributes.dataPlayer,\n\t\t dataVideo = attributes.dataVideo,\n\t\t dataPlaylist = attributes.dataPlaylist,\n\t\t dataOutstream = attributes.dataOutstream;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed-height', label: __('Fixed height', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }, { value: 'fill', label: __('Fill', 'amp') }, { value: 'flex-item', label: __('Flex-item', 'amp') }, { value: 'nodisplay', label: __('No Display', 'amp') }];\n\t\tvar url = false;\n\t\tif (dataPartner && dataPlayer && (dataVideo || dataPlaylist || dataOutstream)) {\n\t\t\turl = 'http://cdn.brid.tv/live/partners/' + dataPartner;\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('Brid Player Settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Brid.tv partner ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPartner,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPartner: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Brid.tv player ID (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPlayer,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlayer: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Video ID (one of video / playlist / outstream ID is required)', 'amp'),\n\t\t\t\t\t\tvalue: dataVideo,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataVideo: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Outstream unit ID (one of video / playlist / outstream ID is required)', 'amp'),\n\t\t\t\t\t\tvalue: dataOutstream,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataOutstream: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Playlist ID (one of video / playlist / outstream ID is required)', 'amp'),\n\t\t\t\t\t\tvalue: dataPlaylist,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPlaylist: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(ToggleControl, {\n\t\t\t\t\t\tlabel: __('Autoplay', 'amp'),\n\t\t\t\t\t\tchecked: autoPlay,\n\t\t\t\t\t\tonChange: function onChange() {\n\t\t\t\t\t\t\treturn setAttributes({ autoPlay: !autoPlay });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\turl && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('Brid Player', 'amp'), url),\n\t\t\t!url && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('Brid Player', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add required data to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\n\t\tvar bridProps = {\n\t\t\tlayout: attributes.ampLayout,\n\t\t\theight: attributes.height,\n\t\t\t'data-player': attributes.dataPlayer,\n\t\t\t'data-partner': attributes.dataPartner\n\t\t};\n\t\tif ('fixed-height' !== attributes.ampLayout && attributes.width) {\n\t\t\tbridProps.width = attributes.width;\n\t\t}\n\t\tif (attributes.dataPlaylist) {\n\t\t\tbridProps['data-playlist'] = attributes.dataPlaylist;\n\t\t}\n\t\tif (attributes.dataVideo) {\n\t\t\tbridProps['data-video'] = attributes.dataVideo;\n\t\t}\n\t\tif (attributes.dataOutstream) {\n\t\t\tbridProps['data-outstream'] = attributes.dataOutstream;\n\t\t}\n\t\tif (attributes.autoPlay) {\n\t\t\tbridProps.autoplay = attributes.autoPlay;\n\t\t}\n\t\treturn wp.element.createElement('amp-brid-player', bridProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTEuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9ibG9ja3MvYW1wLWJyaWQtcGxheWVyL2luZGV4LmpzPzEyYjkiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBIZWxwZXIgbWV0aG9kcyBmb3IgYmxvY2tzLlxuICovXG5pbXBvcnQgeyBnZXRMYXlvdXRDb250cm9scywgZ2V0TWVkaWFQbGFjZWhvbGRlciB9IGZyb20gJy4uL3V0aWxzLmpzJztcblxuLyoqXG4gKiBJbnRlcm5hbCBibG9jayBsaWJyYXJpZXMuXG4gKi9cbnZhciBfXyA9IHdwLmkxOG4uX187XG52YXIgcmVnaXN0ZXJCbG9ja1R5cGUgPSB3cC5ibG9ja3MucmVnaXN0ZXJCbG9ja1R5cGU7XG52YXIgSW5zcGVjdG9yQ29udHJvbHMgPSB3cC5lZGl0b3IuSW5zcGVjdG9yQ29udHJvbHM7XG52YXIgRnJhZ21lbnQgPSB3cC5lbGVtZW50LkZyYWdtZW50O1xudmFyIF93cCRjb21wb25lbnRzID0gd3AuY29tcG9uZW50cyxcbiAgICBQYW5lbEJvZHkgPSBfd3AkY29tcG9uZW50cy5QYW5lbEJvZHksXG4gICAgVGV4dENvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5UZXh0Q29udHJvbCxcbiAgICBQbGFjZWhvbGRlciA9IF93cCRjb21wb25lbnRzLlBsYWNlaG9sZGVyLFxuICAgIFRvZ2dsZUNvbnRyb2wgPSBfd3AkY29tcG9uZW50cy5Ub2dnbGVDb250cm9sO1xuXG4vKipcbiAqIFJlZ2lzdGVyIGJsb2NrLlxuICovXG5cbmV4cG9ydCBkZWZhdWx0IHJlZ2lzdGVyQmxvY2tUeXBlKCdhbXAvYW1wLWJyaWQtcGxheWVyJywge1xuXHR0aXRsZTogX18oJ0FNUCBCcmlkIFBsYXllcicsICdhbXAnKSxcblx0ZGVzY3JpcHRpb246IF9fKCdEaXNwbGF5cyB0aGUgQnJpZCBQbGF5ZXIgdXNlZCBpbiBCcmlkLnR2IFZpZGVvIFBsYXRmb3JtLicsICdhbXAnKSxcblx0Y2F0ZWdvcnk6ICdlbWJlZCcsXG5cdGljb246ICdlbWJlZC1nZW5lcmljJyxcblx0a2V5d29yZHM6IFtfXygnRW1iZWQnLCAnYW1wJyldLFxuXG5cdGF0dHJpYnV0ZXM6IHtcblx0XHRhdXRvUGxheToge1xuXHRcdFx0dHlwZTogJ2Jvb2xlYW4nXG5cdFx0fSxcblx0XHRkYXRhUGFydG5lcjoge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLWJyaWQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtcGFydG5lcidcblx0XHR9LFxuXHRcdGRhdGFQbGF5ZXI6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1icmlkLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLXBsYXllcidcblx0XHR9LFxuXHRcdGRhdGFWaWRlbzoge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLWJyaWQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtdmlkZW8nXG5cdFx0fSxcblx0XHRkYXRhUGxheWxpc3Q6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1icmlkLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLXBsYXlsaXN0J1xuXHRcdH0sXG5cdFx0ZGF0YU91dHN0cmVhbToge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLWJyaWQtcGxheWVyJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtb3V0c3RyZWFtJ1xuXHRcdH0sXG5cdFx0YW1wTGF5b3V0OiB7XG5cdFx0XHRkZWZhdWx0OiAncmVzcG9uc2l2ZScsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtYnJpZC1wbGF5ZXInLFxuXHRcdFx0YXR0cmlidXRlOiAnbGF5b3V0J1xuXHRcdH0sXG5cdFx0d2lkdGg6IHtcblx0XHRcdHR5cGU6ICdudW1iZXInLFxuXHRcdFx0ZGVmYXVsdDogNjAwXG5cdFx0fSxcblx0XHRoZWlnaHQ6IHtcblx0XHRcdGRlZmF1bHQ6IDQwMCxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1icmlkLXBsYXllcicsXG5cdFx0XHRhdHRyaWJ1dGU6ICdoZWlnaHQnXG5cdFx0fVxuXHR9LFxuXG5cdGVkaXQ6IGZ1bmN0aW9uIGVkaXQocHJvcHMpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IHByb3BzLmF0dHJpYnV0ZXMsXG5cdFx0ICAgIHNldEF0dHJpYnV0ZXMgPSBwcm9wcy5zZXRBdHRyaWJ1dGVzO1xuXHRcdHZhciBhdXRvUGxheSA9IGF0dHJpYnV0ZXMuYXV0b1BsYXksXG5cdFx0ICAgIGRhdGFQYXJ0bmVyID0gYXR0cmlidXRlcy5kYXRhUGFydG5lcixcblx0XHQgICAgZGF0YVBsYXllciA9IGF0dHJpYnV0ZXMuZGF0YVBsYXllcixcblx0XHQgICAgZGF0YVZpZGVvID0gYXR0cmlidXRlcy5kYXRhVmlkZW8sXG5cdFx0ICAgIGRhdGFQbGF5bGlzdCA9IGF0dHJpYnV0ZXMuZGF0YVBsYXlsaXN0LFxuXHRcdCAgICBkYXRhT3V0c3RyZWFtID0gYXR0cmlidXRlcy5kYXRhT3V0c3RyZWFtO1xuXG5cdFx0dmFyIGFtcExheW91dE9wdGlvbnMgPSBbeyB2YWx1ZTogJ3Jlc3BvbnNpdmUnLCBsYWJlbDogX18oJ1Jlc3BvbnNpdmUnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkLWhlaWdodCcsIGxhYmVsOiBfXygnRml4ZWQgaGVpZ2h0JywgJ2FtcCcpIH0sIHsgdmFsdWU6ICdmaXhlZCcsIGxhYmVsOiBfXygnRml4ZWQnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpbGwnLCBsYWJlbDogX18oJ0ZpbGwnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZsZXgtaXRlbScsIGxhYmVsOiBfXygnRmxleC1pdGVtJywgJ2FtcCcpIH0sIHsgdmFsdWU6ICdub2Rpc3BsYXknLCBsYWJlbDogX18oJ05vIERpc3BsYXknLCAnYW1wJykgfV07XG5cdFx0dmFyIHVybCA9IGZhbHNlO1xuXHRcdGlmIChkYXRhUGFydG5lciAmJiBkYXRhUGxheWVyICYmIChkYXRhVmlkZW8gfHwgZGF0YVBsYXlsaXN0IHx8IGRhdGFPdXRzdHJlYW0pKSB7XG5cdFx0XHR1cmwgPSAnaHR0cDovL2Nkbi5icmlkLnR2L2xpdmUvcGFydG5lcnMvJyArIGRhdGFQYXJ0bmVyO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0RnJhZ21lbnQsXG5cdFx0XHRudWxsLFxuXHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRJbnNwZWN0b3JDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdpbnNwZWN0b3InIH0sXG5cdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRQYW5lbEJvZHksXG5cdFx0XHRcdFx0eyB0aXRsZTogX18oJ0JyaWQgUGxheWVyIFNldHRpbmdzJywgJ2FtcCcpIH0sXG5cdFx0XHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFRleHRDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ0JyaWQudHYgcGFydG5lciBJRCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFQYXJ0bmVyLFxuXHRcdFx0XHRcdFx0b25DaGFuZ2U6IGZ1bmN0aW9uIG9uQ2hhbmdlKHZhbHVlKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YVBhcnRuZXI6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUZXh0Q29udHJvbCwge1xuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdCcmlkLnR2IHBsYXllciBJRCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFQbGF5ZXIsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhUGxheWVyOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnVmlkZW8gSUQgKG9uZSBvZiB2aWRlbyAvIHBsYXlsaXN0IC8gb3V0c3RyZWFtIElEIGlzIHJlcXVpcmVkKScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhVmlkZW8sXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhVmlkZW86IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUZXh0Q29udHJvbCwge1xuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdPdXRzdHJlYW0gdW5pdCBJRCAob25lIG9mIHZpZGVvIC8gcGxheWxpc3QgLyBvdXRzdHJlYW0gSUQgaXMgcmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFPdXRzdHJlYW0sXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhT3V0c3RyZWFtOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnUGxheWxpc3QgSUQgKG9uZSBvZiB2aWRlbyAvIHBsYXlsaXN0IC8gb3V0c3RyZWFtIElEIGlzIHJlcXVpcmVkKScsICdhbXAnKSxcblx0XHRcdFx0XHRcdHZhbHVlOiBkYXRhUGxheWxpc3QsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhUGxheWxpc3Q6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUb2dnbGVDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ0F1dG9wbGF5JywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0Y2hlY2tlZDogYXV0b1BsYXksXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UoKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgYXV0b1BsYXk6ICFhdXRvUGxheSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHRnZXRMYXlvdXRDb250cm9scyhwcm9wcywgYW1wTGF5b3V0T3B0aW9ucylcblx0XHRcdFx0KVxuXHRcdFx0KSxcblx0XHRcdHVybCAmJiBnZXRNZWRpYVBsYWNlaG9sZGVyKF9fKCdCcmlkIFBsYXllcicsICdhbXAnKSwgdXJsKSxcblx0XHRcdCF1cmwgJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRQbGFjZWhvbGRlcixcblx0XHRcdFx0eyBsYWJlbDogX18oJ0JyaWQgUGxheWVyJywgJ2FtcCcpIH0sXG5cdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHQncCcsXG5cdFx0XHRcdFx0bnVsbCxcblx0XHRcdFx0XHRfXygnQWRkIHJlcXVpcmVkIGRhdGEgdG8gdXNlIHRoZSBibG9jay4nLCAnYW1wJylcblx0XHRcdFx0KVxuXHRcdFx0KVxuXHRcdCk7XG5cdH0sXG5cdHNhdmU6IGZ1bmN0aW9uIHNhdmUoX3JlZikge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gX3JlZi5hdHRyaWJ1dGVzO1xuXG5cdFx0dmFyIGJyaWRQcm9wcyA9IHtcblx0XHRcdGxheW91dDogYXR0cmlidXRlcy5hbXBMYXlvdXQsXG5cdFx0XHRoZWlnaHQ6IGF0dHJpYnV0ZXMuaGVpZ2h0LFxuXHRcdFx0J2RhdGEtcGxheWVyJzogYXR0cmlidXRlcy5kYXRhUGxheWVyLFxuXHRcdFx0J2RhdGEtcGFydG5lcic6IGF0dHJpYnV0ZXMuZGF0YVBhcnRuZXJcblx0XHR9O1xuXHRcdGlmICgnZml4ZWQtaGVpZ2h0JyAhPT0gYXR0cmlidXRlcy5hbXBMYXlvdXQgJiYgYXR0cmlidXRlcy53aWR0aCkge1xuXHRcdFx0YnJpZFByb3BzLndpZHRoID0gYXR0cmlidXRlcy53aWR0aDtcblx0XHR9XG5cdFx0aWYgKGF0dHJpYnV0ZXMuZGF0YVBsYXlsaXN0KSB7XG5cdFx0XHRicmlkUHJvcHNbJ2RhdGEtcGxheWxpc3QnXSA9IGF0dHJpYnV0ZXMuZGF0YVBsYXlsaXN0O1xuXHRcdH1cblx0XHRpZiAoYXR0cmlidXRlcy5kYXRhVmlkZW8pIHtcblx0XHRcdGJyaWRQcm9wc1snZGF0YS12aWRlbyddID0gYXR0cmlidXRlcy5kYXRhVmlkZW87XG5cdFx0fVxuXHRcdGlmIChhdHRyaWJ1dGVzLmRhdGFPdXRzdHJlYW0pIHtcblx0XHRcdGJyaWRQcm9wc1snZGF0YS1vdXRzdHJlYW0nXSA9IGF0dHJpYnV0ZXMuZGF0YU91dHN0cmVhbTtcblx0XHR9XG5cdFx0aWYgKGF0dHJpYnV0ZXMuYXV0b1BsYXkpIHtcblx0XHRcdGJyaWRQcm9wcy5hdXRvcGxheSA9IGF0dHJpYnV0ZXMuYXV0b1BsYXk7XG5cdFx0fVxuXHRcdHJldHVybiB3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoJ2FtcC1icmlkLXBsYXllcicsIGJyaWRQcm9wcyk7XG5cdH1cbn0pO1xuXG5cbi8vLy8vLy8vLy8vLy8vLy8vL1xuLy8gV0VCUEFDSyBGT09URVJcbi8vIC4vYmxvY2tzL2FtcC1icmlkLXBsYXllci9pbmRleC5qc1xuLy8gbW9kdWxlIGlkID0gMTFcbi8vIG1vZHVsZSBjaHVua3MgPSAwIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///11\n");
142
+
143
+ /***/ }),
144
+ /* 12 */
145
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
146
+
147
+ "use strict";
148
+ eval("/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__utils_js__ = __webpack_require__(0);\n/**\n * Helper methods for blocks.\n */\n\n\n/**\n * Internal block libraries.\n */\nvar __ = wp.i18n.__;\nvar registerBlockType = wp.blocks.registerBlockType;\nvar InspectorControls = wp.editor.InspectorControls;\nvar Fragment = wp.element.Fragment;\nvar _wp$components = wp.components,\n PanelBody = _wp$components.PanelBody,\n TextControl = _wp$components.TextControl,\n Placeholder = _wp$components.Placeholder,\n ToggleControl = _wp$components.ToggleControl;\n\n/**\n * Register block.\n */\n\n/* unused harmony default export */ var _unused_webpack_default_export = (registerBlockType('amp/amp-ima-video', {\n\ttitle: __('AMP IMA Video', 'amp'),\n\tdescription: __('Embeds a video player for instream video ads that are integrated with the IMA SDK', 'amp'),\n\tcategory: 'embed',\n\ticon: 'embed-generic',\n\tkeywords: [__('Embed', 'amp')],\n\n\t// @todo Perhaps later add subtitles option and additional source options?\n\tattributes: {\n\t\tdataDelayAdRequest: {\n\t\t\tdefault: false,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'data-delay-ad-request'\n\t\t},\n\t\tdataTag: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'data-tag'\n\t\t},\n\t\tdataSrc: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'data-src'\n\t\t},\n\t\tdataPoster: {\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'data-poster'\n\t\t},\n\t\tampLayout: {\n\t\t\tdefault: 'responsive',\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'layout'\n\t\t},\n\t\twidth: {\n\t\t\tdefault: 600,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'width'\n\t\t},\n\t\theight: {\n\t\t\tdefault: 400,\n\t\t\tsource: 'attribute',\n\t\t\tselector: 'amp-ima-video',\n\t\t\tattribute: 'height'\n\t\t}\n\t},\n\n\tedit: function edit(props) {\n\t\tvar attributes = props.attributes,\n\t\t setAttributes = props.setAttributes;\n\t\tvar dataDelayAdRequest = attributes.dataDelayAdRequest,\n\t\t dataTag = attributes.dataTag,\n\t\t dataSrc = attributes.dataSrc,\n\t\t dataPoster = attributes.dataPoster;\n\n\t\tvar ampLayoutOptions = [{ value: 'responsive', label: __('Responsive', 'amp') }, { value: 'fixed', label: __('Fixed', 'amp') }];\n\t\tvar dataSet = false;\n\t\tif (dataTag && dataSrc) {\n\t\t\tdataSet = true;\n\t\t}\n\t\treturn wp.element.createElement(\n\t\t\tFragment,\n\t\t\tnull,\n\t\t\twp.element.createElement(\n\t\t\t\tInspectorControls,\n\t\t\t\t{ key: 'inspector' },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\tPanelBody,\n\t\t\t\t\t{ title: __('IMA Video Settings', 'amp') },\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Https URL for your VAST ad document (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataTag,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataTag: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Https URL of your video content (required)', 'amp'),\n\t\t\t\t\t\tvalue: dataSrc,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataSrc: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(TextControl, {\n\t\t\t\t\t\tlabel: __('Https URL to preview image', 'amp'),\n\t\t\t\t\t\tvalue: dataPoster,\n\t\t\t\t\t\tonChange: function onChange(value) {\n\t\t\t\t\t\t\treturn setAttributes({ dataPoster: value });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\twp.element.createElement(ToggleControl, {\n\t\t\t\t\t\tlabel: __('Delay Ad Request', 'amp'),\n\t\t\t\t\t\tchecked: dataDelayAdRequest,\n\t\t\t\t\t\tonChange: function onChange() {\n\t\t\t\t\t\t\treturn setAttributes({ dataDelayAdRequest: !dataDelayAdRequest });\n\t\t\t\t\t\t}\n\t\t\t\t\t}),\n\t\t\t\t\tObject(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"a\" /* getLayoutControls */])(props, ampLayoutOptions)\n\t\t\t\t)\n\t\t\t),\n\t\t\tdataSet && Object(__WEBPACK_IMPORTED_MODULE_0__utils_js__[\"b\" /* getMediaPlaceholder */])(__('IMA Video', 'amp'), dataSrc),\n\t\t\t!dataSet && wp.element.createElement(\n\t\t\t\tPlaceholder,\n\t\t\t\t{ label: __('IMA Video', 'amp') },\n\t\t\t\twp.element.createElement(\n\t\t\t\t\t'p',\n\t\t\t\t\tnull,\n\t\t\t\t\t__('Add required data to use the block.', 'amp')\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\t},\n\tsave: function save(_ref) {\n\t\tvar attributes = _ref.attributes;\n\n\t\tvar imaProps = {\n\t\t\tlayout: attributes.ampLayout,\n\t\t\theight: attributes.height,\n\t\t\twidth: attributes.width,\n\t\t\t'data-tag': attributes.dataTag,\n\t\t\t'data-src': attributes.dataSrc\n\t\t};\n\t\tif (attributes.dataPoster) {\n\t\t\timaProps['data-poster'] = attributes.dataPoster;\n\t\t}\n\t\tif (attributes.dataDelayAdRequest) {\n\t\t\timaProps['data-delay-ad-request'] = attributes.dataDelayAdRequest;\n\t\t}\n\t\treturn wp.element.createElement('amp-ima-video', imaProps);\n\t}\n}));//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTIuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9ibG9ja3MvYW1wLWltYS12aWRlby9pbmRleC5qcz9kYzU1Il0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogSGVscGVyIG1ldGhvZHMgZm9yIGJsb2Nrcy5cbiAqL1xuaW1wb3J0IHsgZ2V0TGF5b3V0Q29udHJvbHMsIGdldE1lZGlhUGxhY2Vob2xkZXIgfSBmcm9tICcuLi91dGlscy5qcyc7XG5cbi8qKlxuICogSW50ZXJuYWwgYmxvY2sgbGlicmFyaWVzLlxuICovXG52YXIgX18gPSB3cC5pMThuLl9fO1xudmFyIHJlZ2lzdGVyQmxvY2tUeXBlID0gd3AuYmxvY2tzLnJlZ2lzdGVyQmxvY2tUeXBlO1xudmFyIEluc3BlY3RvckNvbnRyb2xzID0gd3AuZWRpdG9yLkluc3BlY3RvckNvbnRyb2xzO1xudmFyIEZyYWdtZW50ID0gd3AuZWxlbWVudC5GcmFnbWVudDtcbnZhciBfd3AkY29tcG9uZW50cyA9IHdwLmNvbXBvbmVudHMsXG4gICAgUGFuZWxCb2R5ID0gX3dwJGNvbXBvbmVudHMuUGFuZWxCb2R5LFxuICAgIFRleHRDb250cm9sID0gX3dwJGNvbXBvbmVudHMuVGV4dENvbnRyb2wsXG4gICAgUGxhY2Vob2xkZXIgPSBfd3AkY29tcG9uZW50cy5QbGFjZWhvbGRlcixcbiAgICBUb2dnbGVDb250cm9sID0gX3dwJGNvbXBvbmVudHMuVG9nZ2xlQ29udHJvbDtcblxuLyoqXG4gKiBSZWdpc3RlciBibG9jay5cbiAqL1xuXG5leHBvcnQgZGVmYXVsdCByZWdpc3RlckJsb2NrVHlwZSgnYW1wL2FtcC1pbWEtdmlkZW8nLCB7XG5cdHRpdGxlOiBfXygnQU1QIElNQSBWaWRlbycsICdhbXAnKSxcblx0ZGVzY3JpcHRpb246IF9fKCdFbWJlZHMgYSB2aWRlbyBwbGF5ZXIgZm9yIGluc3RyZWFtIHZpZGVvIGFkcyB0aGF0IGFyZSBpbnRlZ3JhdGVkIHdpdGggdGhlIElNQSBTREsnLCAnYW1wJyksXG5cdGNhdGVnb3J5OiAnZW1iZWQnLFxuXHRpY29uOiAnZW1iZWQtZ2VuZXJpYycsXG5cdGtleXdvcmRzOiBbX18oJ0VtYmVkJywgJ2FtcCcpXSxcblxuXHQvLyBAdG9kbyBQZXJoYXBzIGxhdGVyIGFkZCBzdWJ0aXRsZXMgb3B0aW9uIGFuZCBhZGRpdGlvbmFsIHNvdXJjZSBvcHRpb25zP1xuXHRhdHRyaWJ1dGVzOiB7XG5cdFx0ZGF0YURlbGF5QWRSZXF1ZXN0OiB7XG5cdFx0XHRkZWZhdWx0OiBmYWxzZSxcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1pbWEtdmlkZW8nLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1kZWxheS1hZC1yZXF1ZXN0J1xuXHRcdH0sXG5cdFx0ZGF0YVRhZzoge1xuXHRcdFx0c291cmNlOiAnYXR0cmlidXRlJyxcblx0XHRcdHNlbGVjdG9yOiAnYW1wLWltYS12aWRlbycsXG5cdFx0XHRhdHRyaWJ1dGU6ICdkYXRhLXRhZydcblx0XHR9LFxuXHRcdGRhdGFTcmM6IHtcblx0XHRcdHNvdXJjZTogJ2F0dHJpYnV0ZScsXG5cdFx0XHRzZWxlY3RvcjogJ2FtcC1pbWEtdmlkZW8nLFxuXHRcdFx0YXR0cmlidXRlOiAnZGF0YS1zcmMnXG5cdFx0fSxcblx0XHRkYXRhUG9zdGVyOiB7XG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtaW1hLXZpZGVvJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2RhdGEtcG9zdGVyJ1xuXHRcdH0sXG5cdFx0YW1wTGF5b3V0OiB7XG5cdFx0XHRkZWZhdWx0OiAncmVzcG9uc2l2ZScsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtaW1hLXZpZGVvJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2xheW91dCdcblx0XHR9LFxuXHRcdHdpZHRoOiB7XG5cdFx0XHRkZWZhdWx0OiA2MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtaW1hLXZpZGVvJyxcblx0XHRcdGF0dHJpYnV0ZTogJ3dpZHRoJ1xuXHRcdH0sXG5cdFx0aGVpZ2h0OiB7XG5cdFx0XHRkZWZhdWx0OiA0MDAsXG5cdFx0XHRzb3VyY2U6ICdhdHRyaWJ1dGUnLFxuXHRcdFx0c2VsZWN0b3I6ICdhbXAtaW1hLXZpZGVvJyxcblx0XHRcdGF0dHJpYnV0ZTogJ2hlaWdodCdcblx0XHR9XG5cdH0sXG5cblx0ZWRpdDogZnVuY3Rpb24gZWRpdChwcm9wcykge1xuXHRcdHZhciBhdHRyaWJ1dGVzID0gcHJvcHMuYXR0cmlidXRlcyxcblx0XHQgICAgc2V0QXR0cmlidXRlcyA9IHByb3BzLnNldEF0dHJpYnV0ZXM7XG5cdFx0dmFyIGRhdGFEZWxheUFkUmVxdWVzdCA9IGF0dHJpYnV0ZXMuZGF0YURlbGF5QWRSZXF1ZXN0LFxuXHRcdCAgICBkYXRhVGFnID0gYXR0cmlidXRlcy5kYXRhVGFnLFxuXHRcdCAgICBkYXRhU3JjID0gYXR0cmlidXRlcy5kYXRhU3JjLFxuXHRcdCAgICBkYXRhUG9zdGVyID0gYXR0cmlidXRlcy5kYXRhUG9zdGVyO1xuXG5cdFx0dmFyIGFtcExheW91dE9wdGlvbnMgPSBbeyB2YWx1ZTogJ3Jlc3BvbnNpdmUnLCBsYWJlbDogX18oJ1Jlc3BvbnNpdmUnLCAnYW1wJykgfSwgeyB2YWx1ZTogJ2ZpeGVkJywgbGFiZWw6IF9fKCdGaXhlZCcsICdhbXAnKSB9XTtcblx0XHR2YXIgZGF0YVNldCA9IGZhbHNlO1xuXHRcdGlmIChkYXRhVGFnICYmIGRhdGFTcmMpIHtcblx0XHRcdGRhdGFTZXQgPSB0cnVlO1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0RnJhZ21lbnQsXG5cdFx0XHRudWxsLFxuXHRcdFx0d3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRJbnNwZWN0b3JDb250cm9scyxcblx0XHRcdFx0eyBrZXk6ICdpbnNwZWN0b3InIH0sXG5cdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChcblx0XHRcdFx0XHRQYW5lbEJvZHksXG5cdFx0XHRcdFx0eyB0aXRsZTogX18oJ0lNQSBWaWRlbyBTZXR0aW5ncycsICdhbXAnKSB9LFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUZXh0Q29udHJvbCwge1xuXHRcdFx0XHRcdFx0bGFiZWw6IF9fKCdIdHRwcyBVUkwgZm9yIHlvdXIgVkFTVCBhZCBkb2N1bWVudCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFUYWcsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhVGFnOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnSHR0cHMgVVJMIG9mIHlvdXIgdmlkZW8gY29udGVudCAocmVxdWlyZWQpJywgJ2FtcCcpLFxuXHRcdFx0XHRcdFx0dmFsdWU6IGRhdGFTcmMsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UodmFsdWUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIHNldEF0dHJpYnV0ZXMoeyBkYXRhU3JjOiB2YWx1ZSB9KTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSxcblx0XHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoVGV4dENvbnRyb2wsIHtcblx0XHRcdFx0XHRcdGxhYmVsOiBfXygnSHR0cHMgVVJMIHRvIHByZXZpZXcgaW1hZ2UnLCAnYW1wJyksXG5cdFx0XHRcdFx0XHR2YWx1ZTogZGF0YVBvc3Rlcixcblx0XHRcdFx0XHRcdG9uQ2hhbmdlOiBmdW5jdGlvbiBvbkNoYW5nZSh2YWx1ZSkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm4gc2V0QXR0cmlidXRlcyh7IGRhdGFQb3N0ZXI6IHZhbHVlIH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdHdwLmVsZW1lbnQuY3JlYXRlRWxlbWVudChUb2dnbGVDb250cm9sLCB7XG5cdFx0XHRcdFx0XHRsYWJlbDogX18oJ0RlbGF5IEFkIFJlcXVlc3QnLCAnYW1wJyksXG5cdFx0XHRcdFx0XHRjaGVja2VkOiBkYXRhRGVsYXlBZFJlcXVlc3QsXG5cdFx0XHRcdFx0XHRvbkNoYW5nZTogZnVuY3Rpb24gb25DaGFuZ2UoKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBzZXRBdHRyaWJ1dGVzKHsgZGF0YURlbGF5QWRSZXF1ZXN0OiAhZGF0YURlbGF5QWRSZXF1ZXN0IH0pO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH0pLFxuXHRcdFx0XHRcdGdldExheW91dENvbnRyb2xzKHByb3BzLCBhbXBMYXlvdXRPcHRpb25zKVxuXHRcdFx0XHQpXG5cdFx0XHQpLFxuXHRcdFx0ZGF0YVNldCAmJiBnZXRNZWRpYVBsYWNlaG9sZGVyKF9fKCdJTUEgVmlkZW8nLCAnYW1wJyksIGRhdGFTcmMpLFxuXHRcdFx0IWRhdGFTZXQgJiYgd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KFxuXHRcdFx0XHRQbGFjZWhvbGRlcixcblx0XHRcdFx0eyBsYWJlbDogX18oJ0lNQSBWaWRlbycsICdhbXAnKSB9LFxuXHRcdFx0XHR3cC5lbGVtZW50LmNyZWF0ZUVsZW1lbnQoXG5cdFx0XHRcdFx0J3AnLFxuXHRcdFx0XHRcdG51bGwsXG5cdFx0XHRcdFx0X18oJ0FkZCByZXF1aXJlZCBkYXRhIHRvIHVzZSB0aGUgYmxvY2suJywgJ2FtcCcpXG5cdFx0XHRcdClcblx0XHRcdClcblx0XHQpO1xuXHR9LFxuXHRzYXZlOiBmdW5jdGlvbiBzYXZlKF9yZWYpIHtcblx0XHR2YXIgYXR0cmlidXRlcyA9IF9yZWYuYXR0cmlidXRlcztcblxuXHRcdHZhciBpbWFQcm9wcyA9IHtcblx0XHRcdGxheW91dDogYXR0cmlidXRlcy5hbXBMYXlvdXQsXG5cdFx0XHRoZWlnaHQ6IGF0dHJpYnV0ZXMuaGVpZ2h0LFxuXHRcdFx0d2lkdGg6IGF0dHJpYnV0ZXMud2lkdGgsXG5cdFx0XHQnZGF0YS10YWcnOiBhdHRyaWJ1dGVzLmRhdGFUYWcsXG5cdFx0XHQnZGF0YS1zcmMnOiBhdHRyaWJ1dGVzLmRhdGFTcmNcblx0XHR9O1xuXHRcdGlmIChhdHRyaWJ1dGVzLmRhdGFQb3N0ZXIpIHtcblx0XHRcdGltYVByb3BzWydkYXRhLXBvc3RlciddID0gYXR0cmlidXRlcy5kYXRhUG9zdGVyO1xuXHRcdH1cblx0XHRpZiAoYXR0cmlidXRlcy5kYXRhRGVsYXlBZFJlcXVlc3QpIHtcblx0XHRcdGltYVByb3BzWydkYXRhLWRlbGF5LWFkLXJlcXVlc3QnXSA9IGF0dHJpYnV0ZXMuZGF0YURlbGF5QWRSZXF1ZXN0O1xuXHRcdH1cblx0XHRyZXR1cm4gd3AuZWxlbWVudC5jcmVhdGVFbGVtZW50KCdhbXAtaW1hLXZpZGVvJywgaW1hUHJvcHMpO1xuXHR9XG59KTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL2Jsb2Nrcy9hbXAtaW1hLXZpZGVvL2luZGV4LmpzXG4vLyBtb2R1bGUgaWQgPSAxMlxuLy8gbW9kdWxlIGNodW5rcyA9IDAiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///12\n");
149
+
150
+ /***/ })
151
+ /******/ ]);
assets/js/amp-customize-controls.js CHANGED
@@ -1,7 +1,7 @@
1
  /* exported ampCustomizeControls */
2
  /* eslint no-magic-numbers: [ "error", { "ignore": [ 0, 1, 250] } ] */
3
 
4
- var ampCustomizeControls = ( function( api, $ ) {
5
  'use strict';
6
 
7
  var component = {
@@ -46,7 +46,7 @@ var ampCustomizeControls = ( function( api, $ ) {
46
  /**
47
  * Add state for AMP.
48
  *
49
- * @returns {void}
50
  */
51
  component.addState = function addState() {
52
  api.state.add( 'ampEnabled', new api.Value( false ) );
@@ -84,7 +84,7 @@ var ampCustomizeControls = ( function( api, $ ) {
84
  urlParser.href = url;
85
  urlParser.pathname = urlParser.pathname.replace( regexEndpoint, '' );
86
 
87
- if ( urlParser.search.length > 1 ) {
88
  params = wp.customize.utils.parseQueryString( urlParser.search.substr( 1 ) );
89
  delete params[ component.data.queryVar ];
90
  urlParser.search = $.param( params );
@@ -112,7 +112,7 @@ var ampCustomizeControls = ( function( api, $ ) {
112
  /**
113
  * Try to close the tooltip after a given timeout.
114
  *
115
- * @returns {void}
116
  */
117
  component.tryToCloseTooltip = function tryToCloseTooltip() {
118
  clearTimeout( component.tooltipTimeoutId );
@@ -120,7 +120,7 @@ var ampCustomizeControls = ( function( api, $ ) {
120
  if ( ! component.tooltipVisible.get() ) {
121
  return;
122
  }
123
- if ( component.tooltipFocused.get() > 0 ) {
124
  component.tryToCloseTooltip();
125
  } else {
126
  component.tooltipVisible.set( false );
@@ -157,7 +157,7 @@ var ampCustomizeControls = ( function( api, $ ) {
157
  * Enable AMP and navigate to the given URL.
158
  *
159
  * @param {string} url - URL.
160
- * @returns {void}
161
  */
162
  component.enableAndNavigateToUrl = function enableAndNavigateToUrl( url ) {
163
  api.state( 'ampEnabled' ).set( true );
@@ -167,10 +167,11 @@ var ampCustomizeControls = ( function( api, $ ) {
167
  /**
168
  * Update panel notifications.
169
  *
170
- * @returns {void}
171
  */
172
  component.updatePanelNotifications = function updatePanelNotifications() {
173
- var panel = api.panel( component.data.panelId ), containers;
 
174
  containers = panel.sections().concat( [ panel ] );
175
  if ( api.state( 'ampAvailable' ).get() ) {
176
  _.each( containers, function( container ) {
@@ -223,7 +224,6 @@ var ampCustomizeControls = ( function( api, $ ) {
223
  if ( api.state( 'ampAvailable' ).get() ) {
224
  api.state( 'ampEnabled' ).set( panel.expanded.get() );
225
  } else if ( ! panel.notifications ) {
226
-
227
  /*
228
  * This is only done if panel notifications aren't supported.
229
  * If they are (as of 4.9) then a notification will be shown
@@ -270,7 +270,7 @@ var ampCustomizeControls = ( function( api, $ ) {
270
  }
271
  return val;
272
  };
273
- } )( api.previewer.previewUrl.validate );
274
 
275
  // Listen for ampEnabled state changes.
276
  api.state( 'ampEnabled' ).bind( function( enabled ) {
@@ -302,7 +302,7 @@ var ampCustomizeControls = ( function( api, $ ) {
302
  tooltip.attr( 'aria-hidden', visible ? 'false' : 'true' );
303
  if ( visible ) {
304
  $( document ).on( 'click.amp-toggle-outside', function( event ) {
305
- if ( ! $.contains( ampToggleContainer[0], event.target ) ) {
306
  component.tooltipVisible.set( false );
307
  }
308
  } );
@@ -347,5 +347,4 @@ var ampCustomizeControls = ( function( api, $ ) {
347
  };
348
 
349
  return component;
350
-
351
- } )( wp.customize, jQuery );
1
  /* exported ampCustomizeControls */
2
  /* eslint no-magic-numbers: [ "error", { "ignore": [ 0, 1, 250] } ] */
3
 
4
+ var ampCustomizeControls = ( function( api, $ ) { // eslint-disable-line no-unused-vars
5
  'use strict';
6
 
7
  var component = {
46
  /**
47
  * Add state for AMP.
48
  *
49
+ * @return {void}
50
  */
51
  component.addState = function addState() {
52
  api.state.add( 'ampEnabled', new api.Value( false ) );
84
  urlParser.href = url;
85
  urlParser.pathname = urlParser.pathname.replace( regexEndpoint, '' );
86
 
87
+ if ( 1 < urlParser.search.length ) {
88
  params = wp.customize.utils.parseQueryString( urlParser.search.substr( 1 ) );
89
  delete params[ component.data.queryVar ];
90
  urlParser.search = $.param( params );
112
  /**
113
  * Try to close the tooltip after a given timeout.
114
  *
115
+ * @return {void}
116
  */
117
  component.tryToCloseTooltip = function tryToCloseTooltip() {
118
  clearTimeout( component.tooltipTimeoutId );
120
  if ( ! component.tooltipVisible.get() ) {
121
  return;
122
  }
123
+ if ( 0 < component.tooltipFocused.get() ) {
124
  component.tryToCloseTooltip();
125
  } else {
126
  component.tooltipVisible.set( false );
157
  * Enable AMP and navigate to the given URL.
158
  *
159
  * @param {string} url - URL.
160
+ * @return {void}
161
  */
162
  component.enableAndNavigateToUrl = function enableAndNavigateToUrl( url ) {
163
  api.state( 'ampEnabled' ).set( true );
167
  /**
168
  * Update panel notifications.
169
  *
170
+ * @return {void}
171
  */
172
  component.updatePanelNotifications = function updatePanelNotifications() {
173
+ var panel = api.panel( component.data.panelId ),
174
+ containers;
175
  containers = panel.sections().concat( [ panel ] );
176
  if ( api.state( 'ampAvailable' ).get() ) {
177
  _.each( containers, function( container ) {
224
  if ( api.state( 'ampAvailable' ).get() ) {
225
  api.state( 'ampEnabled' ).set( panel.expanded.get() );
226
  } else if ( ! panel.notifications ) {
 
227
  /*
228
  * This is only done if panel notifications aren't supported.
229
  * If they are (as of 4.9) then a notification will be shown
270
  }
271
  return val;
272
  };
273
+ }( api.previewer.previewUrl.validate ) );
274
 
275
  // Listen for ampEnabled state changes.
276
  api.state( 'ampEnabled' ).bind( function( enabled ) {
302
  tooltip.attr( 'aria-hidden', visible ? 'false' : 'true' );
303
  if ( visible ) {
304
  $( document ).on( 'click.amp-toggle-outside', function( event ) {
305
+ if ( ! $.contains( ampToggleContainer[ 0 ], event.target ) ) {
306
  component.tooltipVisible.set( false );
307
  }
308
  } );
347
  };
348
 
349
  return component;
350
+ }( wp.customize, jQuery ) );
 
assets/js/amp-customize-preview.js CHANGED
@@ -1,6 +1,6 @@
1
  /* exported ampCustomizePreview */
2
 
3
- var ampCustomizePreview = ( function( api ) {
4
  'use strict';
5
 
6
  var component = {};
@@ -22,5 +22,4 @@ var ampCustomizePreview = ( function( api ) {
22
  };
23
 
24
  return component;
25
-
26
- } )( wp.customize );
1
  /* exported ampCustomizePreview */
2
 
3
+ var ampCustomizePreview = ( function( api ) { // eslint-disable-line no-unused-vars
4
  'use strict';
5
 
6
  var component = {};
22
  };
23
 
24
  return component;
25
+ }( wp.customize ) );
 
assets/js/amp-customizer-design-preview.js CHANGED
@@ -24,10 +24,10 @@
24
  // AMP background color scheme.
25
  wp.customize( 'amp_customizer[color_scheme]', function( value ) {
26
  value.bind( function( to ) {
27
- var colors = amp_customizer_design.color_schemes[ to ];
28
 
29
  if ( ! colors ) {
30
- console.error( 'Selected color scheme "%s" not registered.', to );
31
  return;
32
  }
33
 
@@ -45,5 +45,4 @@
45
  $( '.amp-wp-header .amp-site-title, .amp-wp-footer h2' ).text( title );
46
  } );
47
  } );
48
-
49
- } )( jQuery );
24
  // AMP background color scheme.
25
  wp.customize( 'amp_customizer[color_scheme]', function( value ) {
26
  value.bind( function( to ) {
27
+ var colors = amp_customizer_design.color_schemes[ to ]; // eslint-disable-line
28
 
29
  if ( ! colors ) {
30
+ console.error( 'Selected color scheme "%s" not registered.', to ); // eslint-disable-line
31
  return;
32
  }
33
 
45
  $( '.amp-wp-header .amp-site-title, .amp-wp-footer h2' ).text( title );
46
  } );
47
  } );
48
+ }( jQuery ) );
 
assets/js/amp-editor-blocks.js ADDED
@@ -0,0 +1,852 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* exported ampEditorBlocks */
2
+ /* eslint no-magic-numbers: [ "error", { "ignore": [ 1, -1, 0, 4 ] } ] */
3
+
4
+ var ampEditorBlocks = ( function() { // eslint-disable-line no-unused-vars
5
+ var component, __;
6
+
7
+ __ = wp.i18n.__;
8
+
9
+ component = {
10
+
11
+ /**
12
+ * Holds data.
13
+ */
14
+ data: {
15
+ ampLayoutOptions: [
16
+ {
17
+ value: 'nodisplay',
18
+ label: __( 'No Display', 'amp' ),
19
+ notAvailable: [
20
+ 'core-embed/vimeo',
21
+ 'core-embed/dailymotion',
22
+ 'core-embed/hulu',
23
+ 'core-embed/reddit',
24
+ 'core-embed/soundcloud'
25
+ ]
26
+ },
27
+ {
28
+ // Not supported by amp-audio and amp-pixel.
29
+ value: 'fixed',
30
+ label: __( 'Fixed', 'amp' ),
31
+ notAvailable: [
32
+ 'core-embed/soundcloud'
33
+ ]
34
+ },
35
+ {
36
+ // To ensure your AMP element displays, you must specify a width and height for the containing element.
37
+ value: 'responsive',
38
+ label: __( 'Responsive', 'amp' ),
39
+ notAvailable: [
40
+ 'core/audio',
41
+ 'core-embed/soundcloud'
42
+ ]
43
+ },
44
+ {
45
+ value: 'fixed-height',
46
+ label: __( 'Fixed height', 'amp' ),
47
+ notAvailable: []
48
+ },
49
+ {
50
+ value: 'fill',
51
+ label: __( 'Fill', 'amp' ),
52
+ notAvailable: [
53
+ 'core/audio',
54
+ 'core-embed/soundcloud'
55
+ ]
56
+ },
57
+ {
58
+ value: 'flex-item',
59
+ label: __( 'Flex Item', 'amp' ),
60
+ notAvailable: [
61
+ 'core/audio',
62
+ 'core-embed/soundcloud'
63
+ ]
64
+ },
65
+ {
66
+ // Not supported by video.
67
+ value: 'intrinsic',
68
+ label: __( 'Intrinsic', 'amp' ),
69
+ notAvailable: [
70
+ 'core/audio',
71
+ 'core-embed/youtube',
72
+ 'core-embed/facebook',
73
+ 'core-embed/instagram',
74
+ 'core-embed/vimeo',
75
+ 'core-embed/dailymotion',
76
+ 'core-embed/hulu',
77
+ 'core-embed/reddit',
78
+ 'core-embed/soundcloud'
79
+ ]
80
+ }
81
+ ],
82
+ defaultWidth: 608, // Max-width in the editor.
83
+ defaultHeight: 400,
84
+ mediaBlocks: [
85
+ 'core/image',
86
+ 'core/video',
87
+ 'core/audio'
88
+ ],
89
+ textBlocks: [
90
+ 'core/paragraph',
91
+ 'core/heading',
92
+ 'core/code',
93
+ 'core/quote',
94
+ 'core/subhead'
95
+ ],
96
+ ampSettingsLabel: __( 'AMP Settings' ),
97
+ fontSizes: {
98
+ small: 14,
99
+ larger: 48
100
+ },
101
+ ampPanelLabel: __( 'AMP Settings' )
102
+ },
103
+ hasThemeSupport: true
104
+ };
105
+
106
+ /**
107
+ * Add filters.
108
+ *
109
+ * @param {Object} data Data.
110
+ */
111
+ component.boot = function boot( data ) {
112
+ if ( data ) {
113
+ _.extend( component.data, data );
114
+ }
115
+
116
+ wp.hooks.addFilter( 'blocks.registerBlockType', 'ampEditorBlocks/addAttributes', component.addAMPAttributes );
117
+ wp.hooks.addFilter( 'blocks.getSaveElement', 'ampEditorBlocks/filterSave', component.filterBlocksSave );
118
+ wp.hooks.addFilter( 'editor.BlockEdit', 'ampEditorBlocks/filterEdit', component.filterBlocksEdit );
119
+ wp.hooks.addFilter( 'blocks.getSaveContent.extraProps', 'ampEditorBlocks/addExtraAttributes', component.addAMPExtraProps );
120
+ };
121
+
122
+ /**
123
+ * Check if layout is available for the block.
124
+ *
125
+ * @param {string} blockName Block name.
126
+ * @param {Object} option Layout option object.
127
+ * @return {boolean} If is available.
128
+ */
129
+ component.isLayoutAvailable = function isLayoutAvailable( blockName, option ) {
130
+ return -1 === option.notAvailable.indexOf( blockName );
131
+ };
132
+
133
+ /**
134
+ * Get layout options depending on the block.
135
+ *
136
+ * @param {string} blockName Block name.
137
+ * @return {[*]} Options.
138
+ */
139
+ component.getLayoutOptions = function getLayoutOptions( blockName ) {
140
+ var layoutOptions = [
141
+ {
142
+ value: '',
143
+ label: __( 'Default', 'amp' )
144
+ }
145
+ ];
146
+
147
+ _.each( component.data.ampLayoutOptions, function( option ) {
148
+ if ( component.isLayoutAvailable( blockName, option ) ) {
149
+ layoutOptions.push( {
150
+ value: option.value,
151
+ label: option.label
152
+ } );
153
+ }
154
+ } );
155
+
156
+ return layoutOptions;
157
+ };
158
+
159
+ /**
160
+ * Add extra data-amp-layout attribute to save to DB.
161
+ *
162
+ * @param {Object} props Properties.
163
+ * @param {Object} blockType Block type.
164
+ * @param {Object} attributes Attributes.
165
+ * @return {Object} Props.
166
+ */
167
+ component.addAMPExtraProps = function addAMPExtraProps( props, blockType, attributes ) {
168
+ var ampAttributes = {};
169
+
170
+ // Shortcode props are handled differently.
171
+ if ( 'core/shortcode' === blockType.name ) {
172
+ return props;
173
+ }
174
+
175
+ // AMP blocks handle layout and other props on their own.
176
+ if ( 'amp/' === blockType.name.substr( 0, 4 ) ) {
177
+ return props;
178
+ }
179
+
180
+ if ( attributes.ampLayout ) {
181
+ ampAttributes[ 'data-amp-layout' ] = attributes.ampLayout;
182
+ }
183
+ if ( attributes.ampNoLoading ) {
184
+ ampAttributes[ 'data-amp-noloading' ] = attributes.ampNoLoading;
185
+ }
186
+ if ( attributes.ampLightbox ) {
187
+ ampAttributes[ 'data-amp-lightbox' ] = attributes.ampLightbox;
188
+ }
189
+ if ( attributes.ampCarousel ) {
190
+ ampAttributes[ 'data-amp-carousel' ] = attributes.ampCarousel;
191
+ }
192
+
193
+ return _.extend( ampAttributes, props );
194
+ };
195
+
196
+ /**
197
+ * Add AMP attributes (in this test case just ampLayout) to every core block.
198
+ *
199
+ * @param {Object} settings Settings.
200
+ * @param {string} name Block name.
201
+ * @return {Object} Settings.
202
+ */
203
+ component.addAMPAttributes = function addAMPAttributes( settings, name ) {
204
+ // AMP Carousel settings.
205
+ if ( 'core/shortcode' === name || 'core/gallery' === name ) {
206
+ if ( ! settings.attributes ) {
207
+ settings.attributes = {};
208
+ }
209
+ settings.attributes.ampCarousel = {
210
+ type: 'boolean'
211
+ };
212
+ settings.attributes.ampLightbox = {
213
+ type: 'boolean'
214
+ };
215
+ }
216
+
217
+ // Add AMP Lightbox settings.
218
+ if ( 'core/image' === name ) {
219
+ if ( ! settings.attributes ) {
220
+ settings.attributes = {};
221
+ }
222
+ settings.attributes.ampLightbox = {
223
+ type: 'boolean'
224
+ };
225
+ }
226
+
227
+ // Fit-text for text blocks.
228
+ if ( -1 !== component.data.textBlocks.indexOf( name ) ) {
229
+ if ( ! settings.attributes ) {
230
+ settings.attributes = {};
231
+ }
232
+ settings.attributes.ampFitText = {
233
+ default: false
234
+ };
235
+ settings.attributes.minFont = {
236
+ default: component.data.fontSizes.small,
237
+ source: 'attribute',
238
+ selector: 'amp-fit-text',
239
+ attribute: 'min-font-size'
240
+ };
241
+ settings.attributes.maxFont = {
242
+ default: component.data.fontSizes.larger,
243
+ source: 'attribute',
244
+ selector: 'amp-fit-text',
245
+ attribute: 'max-font-size'
246
+ };
247
+ settings.attributes.height = {
248
+ default: 50,
249
+ source: 'attribute',
250
+ selector: 'amp-fit-text',
251
+ attribute: 'height'
252
+ };
253
+ }
254
+
255
+ // Layout settings for embeds and media blocks.
256
+ if ( 0 === name.indexOf( 'core-embed' ) || -1 !== component.data.mediaBlocks.indexOf( name ) ) {
257
+ if ( ! settings.attributes ) {
258
+ settings.attributes = {};
259
+ }
260
+ settings.attributes.ampLayout = {
261
+ type: 'string'
262
+ };
263
+ settings.attributes.ampNoLoading = {
264
+ type: 'boolean'
265
+ };
266
+ }
267
+ return settings;
268
+ };
269
+
270
+ /**
271
+ * Filters blocks edit function of all blocks.
272
+ *
273
+ * @param {Function} BlockEdit Edit function.
274
+ * @return {Function} Edit function.
275
+ */
276
+ component.filterBlocksEdit = function filterBlocksEdit( BlockEdit ) {
277
+ var el = wp.element.createElement;
278
+
279
+ return function( props ) {
280
+ var attributes = props.attributes,
281
+ name = props.name,
282
+ ampLayout,
283
+ inspectorControls;
284
+
285
+ ampLayout = attributes.ampLayout;
286
+
287
+ if ( 'core/shortcode' === name ) {
288
+ // Lets remove amp-carousel from edit view.
289
+ if ( component.hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) {
290
+ props.setAttributes( { text: component.removeAmpCarouselFromShortcodeAtts( attributes.text ) } );
291
+ }
292
+ // Lets remove amp-lightbox from edit view.
293
+ if ( component.hasGalleryShortcodeLightboxAttribute( attributes.text || '' ) ) {
294
+ props.setAttributes( { text: component.removeAmpLightboxFromShortcodeAtts( attributes.text ) } );
295
+ }
296
+
297
+ inspectorControls = component.setUpShortcodeInspectorControls( props );
298
+ if ( '' === inspectorControls ) {
299
+ // Return original.
300
+ return [
301
+ el( BlockEdit, _.extend( {
302
+ key: 'original'
303
+ }, props ) )
304
+ ];
305
+ }
306
+ } else if ( 'core/gallery' === name ) {
307
+ inspectorControls = component.setUpGalleryInpsectorControls( props );
308
+ } else if ( 'core/image' === name ) {
309
+ inspectorControls = component.setUpImageInpsectorControls( props );
310
+ } else if ( -1 !== component.data.mediaBlocks.indexOf( name ) || 0 === name.indexOf( 'core-embed/' ) ) {
311
+ inspectorControls = component.setUpInspectorControls( props );
312
+ } else if ( -1 !== component.data.textBlocks.indexOf( name ) ) {
313
+ inspectorControls = component.setUpTextBlocksInspectorControls( props );
314
+ }
315
+
316
+ // Return just inspector controls in case of 'nodisplay'.
317
+ if ( ampLayout && 'nodisplay' === ampLayout ) {
318
+ return [
319
+ inspectorControls
320
+ ];
321
+ }
322
+
323
+ return [
324
+ el( BlockEdit, _.extend( {
325
+ key: 'original'
326
+ }, props ) ),
327
+ inspectorControls
328
+ ];
329
+ };
330
+ };
331
+
332
+ /**
333
+ * Set width and height in case of image block.
334
+ *
335
+ * @param {Object} props Props.
336
+ * @param {string} layout Layout.
337
+ */
338
+ component.setImageBlockLayoutAttributes = function setImageBlockLayoutAttributes( props, layout ) {
339
+ var attributes = props.attributes;
340
+ switch ( layout ) {
341
+ case 'fixed-height':
342
+ if ( ! attributes.height ) {
343
+ props.setAttributes( { height: component.data.defaultHeight } );
344
+ }
345
+ // Lightbox doesn't work with fixed height, so unset it.
346
+ if ( attributes.ampLightbox ) {
347
+ props.setAttributes( { ampLightbox: false } );
348
+ }
349
+ break;
350
+
351
+ case 'fixed':
352
+ if ( ! attributes.height ) {
353
+ props.setAttributes( { height: component.data.defaultHeight } );
354
+ }
355
+ if ( ! attributes.width ) {
356
+ props.setAttributes( { width: component.data.defaultWidth } );
357
+ }
358
+ break;
359
+ }
360
+ };
361
+
362
+ /**
363
+ * Default setup for inspector controls.
364
+ *
365
+ * @param {Object} props Props.
366
+ * @return {Object|Element|*|{$$typeof, type, key, ref, props, _owner}} Inspector Controls.
367
+ */
368
+ component.setUpInspectorControls = function setUpInspectorControls( props ) {
369
+ var isSelected = props.isSelected,
370
+ el = wp.element.createElement,
371
+ InspectorControls = wp.editor.InspectorControls,
372
+ PanelBody = wp.components.PanelBody;
373
+
374
+ return isSelected && (
375
+ el( InspectorControls, { key: 'inspector' },
376
+ el( PanelBody, { title: component.data.ampPanelLabel },
377
+ component.getAmpLayoutControl( props ),
378
+ component.getAmpNoloadingToggle( props )
379
+ )
380
+ )
381
+ );
382
+ };
383
+
384
+ /**
385
+ * Get AMP Layout select control.
386
+ *
387
+ * @param {Object} props Props.
388
+ * @return {Object} Element.
389
+ */
390
+ component.getAmpLayoutControl = function getAmpLayoutControl( props ) {
391
+ var ampLayout = props.attributes.ampLayout,
392
+ el = wp.element.createElement,
393
+ SelectControl = wp.components.SelectControl,
394
+ name = props.name,
395
+ label = __( 'AMP Layout' );
396
+
397
+ if ( 'core/image' === name ) {
398
+ label = __( 'AMP Layout (modifies width/height)' );
399
+ }
400
+
401
+ return el( SelectControl, {
402
+ label: label,
403
+ value: ampLayout,
404
+ options: component.getLayoutOptions( name ),
405
+ onChange: function( value ) {
406
+ props.setAttributes( { ampLayout: value } );
407
+ if ( 'core/image' === props.name ) {
408
+ component.setImageBlockLayoutAttributes( props, value );
409
+ }
410
+ }
411
+ } );
412
+ };
413
+
414
+ /**
415
+ * Get AMP Noloading toggle control.
416
+ *
417
+ * @param {Object} props Props.
418
+ * @return {Object} Element.
419
+ */
420
+ component.getAmpNoloadingToggle = function getAmpNoloadingToggle( props ) {
421
+ var ampNoLoading = props.attributes.ampNoLoading,
422
+ el = wp.element.createElement,
423
+ ToggleControl = wp.components.ToggleControl,
424
+ label = __( 'AMP Noloading' );
425
+
426
+ return el( ToggleControl, {
427
+ label: label,
428
+ checked: ampNoLoading,
429
+ onChange: function() {
430
+ props.setAttributes( { ampNoLoading: ! ampNoLoading } );
431
+ }
432
+ } );
433
+ };
434
+
435
+ /**
436
+ * Setup inspector controls for text blocks.
437
+ *
438
+ * @todo Consider wrapping the render function to delete the original font size in text settings when ampFitText.
439
+ *
440
+ * @param {Object} props Props.
441
+ * @return {Object|Element|*|{$$typeof, type, key, ref, props, _owner}} Inspector Controls.
442
+ */
443
+ component.setUpTextBlocksInspectorControls = function setUpInspectorControls( props ) {
444
+ var inspectorPanelBodyArgs,
445
+ ampFitText = props.attributes.ampFitText,
446
+ minFont = props.attributes.minFont,
447
+ maxFont = props.attributes.maxFont,
448
+ height = props.attributes.height,
449
+ isSelected = props.isSelected,
450
+ el = wp.element.createElement,
451
+ InspectorControls = wp.editor.InspectorControls,
452
+ TextControl = wp.components.TextControl,
453
+ FontSizePicker = wp.components.FontSizePicker,
454
+ ToggleControl = wp.components.ToggleControl,
455
+ PanelBody = wp.components.PanelBody,
456
+ label = __( 'Use AMP Fit Text' ),
457
+ FONT_SIZES = [
458
+ {
459
+ name: 'small',
460
+ shortName: __( 'S' ),
461
+ size: 14
462
+ },
463
+ {
464
+ name: 'regular',
465
+ shortName: __( 'M' ),
466
+ size: 16
467
+ },
468
+ {
469
+ name: 'large',
470
+ shortName: __( 'L' ),
471
+ size: 36
472
+ },
473
+ {
474
+ name: 'larger',
475
+ shortName: __( 'XL' ),
476
+ size: 48
477
+ }
478
+ ];
479
+
480
+ if ( ! isSelected ) {
481
+ return null;
482
+ }
483
+
484
+ inspectorPanelBodyArgs = [
485
+ PanelBody,
486
+ { title: component.data.ampSettingsLabel, className: ampFitText ? 'is-amp-fit-text' : '' },
487
+ el( ToggleControl, {
488
+ label: label,
489
+ checked: ampFitText,
490
+ onChange: function() {
491
+ props.setAttributes( { ampFitText: ! ampFitText } );
492
+ }
493
+ } )
494
+ ];
495
+
496
+ if ( ampFitText ) {
497
+ inspectorPanelBodyArgs.push.apply( inspectorPanelBodyArgs, [
498
+ el( TextControl, {
499
+ label: __( 'Height' ),
500
+ value: height,
501
+ min: 1,
502
+ onChange: function( nextHeight ) {
503
+ props.setAttributes( { height: nextHeight } );
504
+ }
505
+ } ),
506
+ parseInt( maxFont ) > parseInt( height ) && el(
507
+ wp.components.Notice,
508
+ {
509
+ status: 'error',
510
+ isDismissible: false
511
+ },
512
+ __( 'The height must be greater than the max font size.' )
513
+ ),
514
+ el( PanelBody, { title: __( 'Minimum font size' ) },
515
+ el( FontSizePicker, {
516
+ fallbackFontSize: 14,
517
+ value: minFont,
518
+ fontSizes: FONT_SIZES,
519
+ onChange: function( nextMinFont ) {
520
+ if ( ! nextMinFont ) {
521
+ nextMinFont = component.data.fontSizes.small; // @todo Supplying fallbackFontSize should be done automatically by the component?
522
+ }
523
+ if ( parseInt( nextMinFont ) <= parseInt( maxFont ) ) {
524
+ props.setAttributes( { minFont: nextMinFont } );
525
+ }
526
+ }
527
+ } )
528
+ ),
529
+ parseInt( minFont ) > parseInt( maxFont ) && el(
530
+ wp.components.Notice,
531
+ {
532
+ status: 'error',
533
+ isDismissible: false
534
+ },
535
+ __( 'The min font size must less than the max font size.' )
536
+ ),
537
+ el( PanelBody, { title: __( 'Maximum font size' ) },
538
+ el( FontSizePicker, {
539
+ value: maxFont,
540
+ fallbackFontSize: 48,
541
+ fontSizes: FONT_SIZES,
542
+ onChange: function( nextMaxFont ) {
543
+ if ( ! nextMaxFont ) {
544
+ nextMaxFont = component.data.fontSizes.larger; // @todo Supplying fallbackFontSize should be done automatically by the component?
545
+ }
546
+ props.setAttributes( {
547
+ maxFont: nextMaxFont,
548
+ height: Math.max( nextMaxFont, height )
549
+ } );
550
+ }
551
+ } )
552
+ )
553
+ ] );
554
+ }
555
+
556
+ return (
557
+ el( InspectorControls, { key: 'inspector' },
558
+ el.apply( null, inspectorPanelBodyArgs )
559
+ )
560
+ );
561
+ };
562
+
563
+ /**
564
+ * Set up inspector controls for shortcode block.
565
+ * Adds ampCarousel attribute in case of gallery shortcode.
566
+ *
567
+ * @param {Object} props Props.
568
+ * @return {Object} Inspector controls.
569
+ */
570
+ component.setUpShortcodeInspectorControls = function setUpShortcodeInspectorControls( props ) {
571
+ var isSelected = props.isSelected,
572
+ el = wp.element.createElement,
573
+ InspectorControls = wp.editor.InspectorControls,
574
+ PanelBody = wp.components.PanelBody;
575
+
576
+ if ( component.isGalleryShortcode( props.attributes ) ) {
577
+ return isSelected && (
578
+ el( InspectorControls, { key: 'inspector' },
579
+ el( PanelBody, { title: component.data.ampPanelLabel },
580
+ component.data.hasThemeSupport && component.getAmpCarouselToggle( props ),
581
+ component.getAmpLightboxToggle( props )
582
+ )
583
+ )
584
+ );
585
+ }
586
+
587
+ return '';
588
+ };
589
+
590
+ /**
591
+ * Get AMP Lightbox toggle control.
592
+ *
593
+ * @param {Object} props Props.
594
+ * @return {Object} Element.
595
+ */
596
+ component.getAmpLightboxToggle = function getAmpLightboxToggle( props ) {
597
+ var ampLightbox = props.attributes.ampLightbox,
598
+ el = wp.element.createElement,
599
+ ToggleControl = wp.components.ToggleControl,
600
+ label = __( 'Add lightbox effect' );
601
+
602
+ return el( ToggleControl, {
603
+ label: label,
604
+ checked: ampLightbox,
605
+ onChange: function( nextValue ) {
606
+ props.setAttributes( { ampLightbox: ! ampLightbox } );
607
+ if ( nextValue ) {
608
+ // Lightbox doesn't work with fixed height, so change.
609
+ if ( 'fixed-height' === props.attributes.ampLayout ) {
610
+ props.setAttributes( { ampLayout: 'fixed' } );
611
+ }
612
+ // In case of lightbox set linking images to 'none'.
613
+ if ( props.attributes.linkTo && 'none' !== props.attributes.linkTo ) {
614
+ props.setAttributes( { linkTo: 'none' } );
615
+ }
616
+ }
617
+ }
618
+ } );
619
+ };
620
+
621
+ /**
622
+ * Get AMP Carousel toggle control.
623
+ *
624
+ * @param {Object} props Props.
625
+ * @return {Object} Element.
626
+ */
627
+ component.getAmpCarouselToggle = function getAmpCarouselToggle( props ) {
628
+ var ampCarousel = props.attributes.ampCarousel,
629
+ el = wp.element.createElement,
630
+ ToggleControl = wp.components.ToggleControl,
631
+ label = __( 'Display as AMP carousel' );
632
+
633
+ return el( ToggleControl, {
634
+ label: label,
635
+ checked: ampCarousel,
636
+ onChange: function() {
637
+ props.setAttributes( { ampCarousel: ! ampCarousel } );
638
+ }
639
+ } );
640
+ };
641
+
642
+ /**
643
+ * Set up inspector controls for Image block.
644
+ *
645
+ * @param {Object} props Props.
646
+ * @return {Object} Inspector Controls.
647
+ */
648
+ component.setUpImageInpsectorControls = function setUpImageInpsectorControls( props ) {
649
+ var isSelected = props.isSelected,
650
+ el = wp.element.createElement,
651
+ InspectorControls = wp.editor.InspectorControls,
652
+ PanelBody = wp.components.PanelBody;
653
+
654
+ return isSelected && (
655
+ el( InspectorControls, { key: 'inspector' },
656
+ el( PanelBody, { title: component.data.ampPanelLabel },
657
+ component.getAmpLayoutControl( props ),
658
+ component.getAmpNoloadingToggle( props ),
659
+ component.getAmpLightboxToggle( props )
660
+ )
661
+ )
662
+ );
663
+ };
664
+
665
+ /**
666
+ * Set up inspector controls for Gallery block.
667
+ * Adds ampCarousel attribute for displaying the output as amp-carousel.
668
+ *
669
+ * @param {Object} props Props.
670
+ * @return {Object} Inspector controls.
671
+ */
672
+ component.setUpGalleryInpsectorControls = function setUpGalleryInpsectorControls( props ) {
673
+ var isSelected = props.isSelected,
674
+ el = wp.element.createElement,
675
+ InspectorControls = wp.editor.InspectorControls,
676
+ PanelBody = wp.components.PanelBody;
677
+
678
+ return isSelected && (
679
+ el( InspectorControls, { key: 'inspector' },
680
+ el( PanelBody, { title: component.data.ampPanelLabel },
681
+ component.data.hasThemeSupport && component.getAmpCarouselToggle( props ),
682
+ component.getAmpLightboxToggle( props )
683
+ )
684
+ )
685
+ );
686
+ };
687
+
688
+ /**
689
+ * Filters blocks' save function.
690
+ *
691
+ * @param {Object} element Element.
692
+ * @param {string} blockType Block type.
693
+ * @param {Object} attributes Attributes.
694
+ * @return {Object} Output element.
695
+ */
696
+ component.filterBlocksSave = function filterBlocksSave( element, blockType, attributes ) {
697
+ var text = attributes.text || '',
698
+ fitTextProps = {
699
+ layout: 'fixed-height',
700
+ children: element
701
+ };
702
+
703
+ if ( 'core/shortcode' === blockType.name && component.isGalleryShortcode( attributes ) ) {
704
+ if ( ! attributes.ampLightbox ) {
705
+ if ( component.hasGalleryShortcodeLightboxAttribute( attributes.text || '' ) ) {
706
+ text = component.removeAmpLightboxFromShortcodeAtts( attributes.text );
707
+ }
708
+ }
709
+ if ( attributes.ampCarousel ) {
710
+ // If the text contains amp-carousel or amp-lightbox, lets remove it.
711
+ if ( component.hasGalleryShortcodeCarouselAttribute( text ) ) {
712
+ text = component.removeAmpCarouselFromShortcodeAtts( text );
713
+ }
714
+
715
+ // If lightbox is not set, we can return here.
716
+ if ( ! attributes.ampLightbox ) {
717
+ if ( attributes.text !== text ) {
718
+ return wp.element.createElement(
719
+ wp.element.RawHTML,
720
+ {},
721
+ text
722
+ );
723
+ }
724
+
725
+ // Else lets return original.
726
+ return element;
727
+ }
728
+ } else if ( ! component.hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) {
729
+ // Add amp-carousel=false attribute to the shortcode.
730
+ text = attributes.text.replace( '[gallery', '[gallery amp-carousel=false' );
731
+ } else {
732
+ text = attributes.text;
733
+ }
734
+
735
+ if ( attributes.ampLightbox && ! component.hasGalleryShortcodeLightboxAttribute( text ) ) {
736
+ text = text.replace( '[gallery', '[gallery amp-lightbox=true' );
737
+ }
738
+
739
+ if ( attributes.text !== text ) {
740
+ return wp.element.createElement(
741
+ wp.element.RawHTML,
742
+ {},
743
+ text
744
+ );
745
+ }
746
+ } else if ( -1 !== component.data.textBlocks.indexOf( blockType.name ) && attributes.ampFitText ) {
747
+ if ( attributes.minFont ) {
748
+ fitTextProps[ 'min-font-size' ] = attributes.minFont;
749
+ }
750
+ if ( attributes.maxFont ) {
751
+ fitTextProps[ 'max-font-size' ] = attributes.maxFont;
752
+ }
753
+ if ( attributes.height ) {
754
+ fitTextProps.height = attributes.height;
755
+ }
756
+ return wp.element.createElement( 'amp-fit-text', fitTextProps );
757
+ }
758
+ return element;
759
+ };
760
+
761
+ /**
762
+ * Check if AMP Lightbox is set.
763
+ *
764
+ * @param {Object} attributes Attributes.
765
+ * @return {boolean} If is set.
766
+ */
767
+ component.hasAmpLightboxSet = function hasAmpLightboxSet( attributes ) {
768
+ return attributes.ampLightbox && false !== attributes.ampLightbox;
769
+ };
770
+
771
+ /**
772
+ * Check if AMP Carousel is set.
773
+ *
774
+ * @param {Object} attributes Attributes.
775
+ * @return {boolean} If is set.
776
+ */
777
+ component.hasAmpCarouselSet = function hasAmpCarouselSet( attributes ) {
778
+ return attributes.ampCarousel && false !== attributes.ampCarousel;
779
+ };
780
+
781
+ /**
782
+ * Check if AMP NoLoading is set.
783
+ *
784
+ * @param {Object} attributes Attributes.
785
+ * @return {boolean} If is set.
786
+ */
787
+ component.hasAmpNoLoadingSet = function hasAmpNoLoadingSet( attributes ) {
788
+ return attributes.ampNoLoading && false !== attributes.ampNoLoading;
789
+ };
790
+
791
+ /**
792
+ * Check if AMP Layout is set.
793
+ *
794
+ * @param {Object} attributes Attributes.
795
+ * @return {boolean} If AMP Layout is set.
796
+ */
797
+ component.hasAmpLayoutSet = function hasAmpLayoutSet( attributes ) {
798
+ return attributes.ampLayout && attributes.ampLayout.length;
799
+ };
800
+
801
+ /**
802
+ * Removes amp-carousel=false from attributes.
803
+ *
804
+ * @param {string} shortcode Shortcode text.
805
+ * @return {string} Modified shortcode.
806
+ */
807
+ component.removeAmpCarouselFromShortcodeAtts = function removeAmpCarouselFromShortcodeAtts( shortcode ) {
808
+ return shortcode.replace( ' amp-carousel=false', '' );
809
+ };
810
+
811
+ /**
812
+ * Removes amp-lightbox=true from attributes.
813
+ *
814
+ * @param {string} shortcode Shortcode text.
815
+ * @return {string} Modified shortcode.
816
+ */
817
+ component.removeAmpLightboxFromShortcodeAtts = function removeAmpLightboxFromShortcodeAtts( shortcode ) {
818
+ return shortcode.replace( ' amp-lightbox=true', '' );
819
+ };
820
+
821
+ /**
822
+ * Check if shortcode includes amp-carousel attribute.
823
+ *
824
+ * @param {string} text Shortcode.
825
+ * @return {boolean} If has amp-carousel.
826
+ */
827
+ component.hasGalleryShortcodeCarouselAttribute = function hasGalleryShortcodeCarouselAttribute( text ) {
828
+ return -1 !== text.indexOf( 'amp-carousel=false' );
829
+ };
830
+
831
+ /**
832
+ * Check if shortcode includes amp-lightbox attribute.
833
+ *
834
+ * @param {string} text Shortcode.
835
+ * @return {boolean} If has amp-lightbox.
836
+ */
837
+ component.hasGalleryShortcodeLightboxAttribute = function hasGalleryShortcodeLightboxAttribute( text ) {
838
+ return -1 !== text.indexOf( 'amp-lightbox=true' );
839
+ };
840
+
841
+ /**
842
+ * Check if shortcode is gallery shortcode.
843
+ *
844
+ * @param {Object} attributes Attributes.
845
+ * @return {boolean} If is gallery shortcode.
846
+ */
847
+ component.isGalleryShortcode = function isGalleryShortcode( attributes ) {
848
+ return attributes.text && -1 !== attributes.text.indexOf( 'gallery' );
849
+ };
850
+
851
+ return component;
852
+ }() );
assets/js/amp-post-meta-box.js CHANGED
@@ -7,7 +7,7 @@
7
  *
8
  * @since 0.6
9
  */
10
- var ampPostMetaBox = ( function( $ ) {
11
  'use strict';
12
 
13
  var component = {
@@ -104,8 +104,8 @@ var ampPostMetaBox = ( function( $ ) {
104
  .clone()
105
  .insertAfter( previewBtn )
106
  .prop( {
107
- 'href': component.data.previewLink,
108
- 'id': component.ampPreviewBtnSelector.replace( '#', '' )
109
  } )
110
  .text( component.data.l10n.ampPreviewBtnLabel )
111
  .parent()
@@ -126,9 +126,9 @@ var ampPostMetaBox = ( function( $ ) {
126
  // Flag the AMP preview referer.
127
  $input = $( '<input>' )
128
  .prop( {
129
- 'type': 'hidden',
130
- 'name': 'amp-preview',
131
- 'value': 'do-preview'
132
  } )
133
  .insertAfter( component.ampPreviewBtnSelector );
134
 
@@ -176,4 +176,4 @@ var ampPostMetaBox = ( function( $ ) {
176
  };
177
 
178
  return component;
179
- })( window.jQuery );
7
  *
8
  * @since 0.6
9
  */
10
+ var ampPostMetaBox = ( function( $ ) { // eslint-disable-line no-unused-vars
11
  'use strict';
12
 
13
  var component = {
104
  .clone()
105
  .insertAfter( previewBtn )
106
  .prop( {
107
+ href: component.data.previewLink,
108
+ id: component.ampPreviewBtnSelector.replace( '#', '' )
109
  } )
110
  .text( component.data.l10n.ampPreviewBtnLabel )
111
  .parent()
126
  // Flag the AMP preview referer.
127
  $input = $( '<input>' )
128
  .prop( {
129
+ type: 'hidden',
130
+ name: 'amp-preview',
131
+ value: 'do-preview'
132
  } )
133
  .insertAfter( component.ampPreviewBtnSelector );
134
 
176
  };
177
 
178
  return component;
179
+ }( window.jQuery ) );
assets/js/amp-validated-url-post-edit-screen.js ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* exported ampValidatedUrlPostEditScreen */
2
+
3
+ const ampValidatedUrlPostEditScreen = ( function() { // eslint-disable-line no-unused-vars
4
+ let component = {
5
+ data: {
6
+ l10n: {
7
+ unsaved_changes: '',
8
+ showing_number_errors: '',
9
+ page_heading: '',
10
+ show_all: '',
11
+ amp_enabled: false
12
+ }
13
+ }
14
+ };
15
+
16
+ /**
17
+ * The id for the 'Showing x of y errors' notice.
18
+ *
19
+ * @var {string}
20
+ */
21
+ component.idNumberErrors = 'number-errors';
22
+
23
+ /**
24
+ * The id for the 'Show all' button.
25
+ *
26
+ * @var {string}
27
+ */
28
+ component.showAllId = 'show-all-errors';
29
+
30
+ /**
31
+ * Boot.
32
+ *
33
+ * @param {Object} data Data.
34
+ * @param {Object} data.l10n Translations.
35
+ */
36
+ component.boot = function boot( data ) {
37
+ Object.assign( component.data, data );
38
+ component.handleShowAll();
39
+ component.handleFiltering();
40
+ component.handleSearching();
41
+ component.handleStatusChange();
42
+ component.handleBulkActions();
43
+ component.changeHeading();
44
+ component.watchForUnsavedChanges();
45
+ component.showAMPIconIfEnabled();
46
+ };
47
+
48
+ /**
49
+ * Add prompt when leaving page due to unsaved changes.
50
+ */
51
+ component.addBeforeUnloadPrompt = function addBeforeUnloadPrompt() {
52
+ if ( component.beforeUnloadPromptAdded ) {
53
+ return;
54
+ }
55
+ window.addEventListener( 'beforeunload', component.onBeforeUnload );
56
+
57
+ // Remove prompt when clicking trash or update.
58
+ document.querySelector( '#major-publishing-actions' ).addEventListener( 'click', function() {
59
+ window.removeEventListener( 'beforeunload', component.onBeforeUnload );
60
+ } );
61
+
62
+ component.beforeUnloadPromptAdded = true;
63
+ };
64
+
65
+ /**
66
+ * Watch for unsaved changes.
67
+ *
68
+ * Add an beforeunload warning when attempting to leave the page when there are unsaved changes,
69
+ * unless the user is pressing the trash link or update button.
70
+ */
71
+ component.watchForUnsavedChanges = function watchForUnsavedChanges() {
72
+ const onChange = function( event ) {
73
+ if ( event.target.matches( 'select' ) ) {
74
+ document.getElementById( 'post' ).removeEventListener( 'change', onChange );
75
+ component.addBeforeUnloadPrompt();
76
+ }
77
+ };
78
+ document.getElementById( 'post' ).addEventListener( 'change', onChange );
79
+ };
80
+
81
+ /**
82
+ * Show message at beforeunload.
83
+ *
84
+ * @param {Event} event - The beforeunload event.
85
+ * @return {string} Message.
86
+ */
87
+ component.onBeforeUnload = function onBeforeUnload( event ) {
88
+ event.preventDefault();
89
+ event.returnValue = component.data.l10n.unsaved_changes;
90
+ return component.data.l10n.unsaved_changes;
91
+ };
92
+
93
+ /**
94
+ * Updates the <tr> with 'Showing x of y validation errors' at the top of the list table with the current count.
95
+ * If this does not exist yet, it creates the element.
96
+ *
97
+ * @param {number} numberErrorsDisplaying - The number of errors displaying.
98
+ * @param {number} totalErrors - The total number of errors, displaying or not.
99
+ */
100
+ component.updateShowingErrorsRow = function updateShowingErrorsRow( numberErrorsDisplaying, totalErrors ) {
101
+ const showAllButton = document.getElementById( component.showAllId );
102
+ let thead, th,
103
+ tr = document.getElementById( component.idNumberErrors );
104
+ const theadQuery = document.getElementsByTagName( 'thead' );
105
+
106
+ // Only create the <tr> if it does not exist yet.
107
+ if ( theadQuery[ 0 ] && ! tr ) {
108
+ thead = theadQuery[ 0 ];
109
+ tr = document.createElement( 'tr' );
110
+ th = document.createElement( 'th' );
111
+ th.setAttribute( 'id', component.idNumberErrors );
112
+ th.setAttribute( 'colspan', '6' );
113
+ tr.appendChild( th );
114
+ thead.appendChild( tr );
115
+ }
116
+
117
+ // If all of the errors are displaying, hide the 'Show all' button and the count notice.
118
+ if ( showAllButton && numberErrorsDisplaying === totalErrors ) {
119
+ showAllButton.classList.add( 'hidden' );
120
+ tr.classList.add( 'hidden' );
121
+ } else if ( null !== numberErrorsDisplaying ) {
122
+ // Update the number of errors displaying and create a 'Show all' button if it does not exist yet.
123
+ document.getElementById( component.idNumberErrors ).innerText = component.data.l10n.showing_number_errors.replace( '%', numberErrorsDisplaying );
124
+ document.getElementById( component.idNumberErrors ).classList.remove( 'hidden' );
125
+ component.conditionallyCreateShowAllButton();
126
+ if ( document.getElementById( component.showAllId ) ) {
127
+ document.getElementById( component.showAllId ).classList.remove( 'hidden' );
128
+ }
129
+ }
130
+ };
131
+
132
+ /**
133
+ * Conditionally creates and appends a 'Show all' button.
134
+ */
135
+ component.conditionallyCreateShowAllButton = function conditionallyCreateShowAllButton() {
136
+ const buttonContainer = document.getElementById( 'url-post-filter' );
137
+ let showAllButton = document.getElementById( component.showAllId );
138
+
139
+ // There is no 'Show all' <button> yet, but there is a container element for it, create the <button>
140
+ if ( ! showAllButton && buttonContainer ) {
141
+ showAllButton = document.createElement( 'button' );
142
+ showAllButton.id = component.showAllId;
143
+ showAllButton.classList.add( 'button' );
144
+ showAllButton.innerText = component.data.l10n.show_all;
145
+ buttonContainer.appendChild( showAllButton );
146
+ }
147
+ };
148
+
149
+ /**
150
+ * On clicking the 'Show all' <button>, this displays all of the validation errors.
151
+ * Then, it hides this 'Show all' <button> and the notice for the number of errors showing.
152
+ */
153
+ component.handleShowAll = function handleShowAll() {
154
+ const onClick = function( event ) {
155
+ const validationErrors = document.querySelectorAll( '[data-error-type]' );
156
+ if ( ! event.target.matches( '#' + component.showAllId ) ) {
157
+ return;
158
+ }
159
+ event.preventDefault();
160
+
161
+ // Iterate through all of the errors, and remove the 'hidden' class.
162
+ validationErrors.forEach( function( element ) {
163
+ element.parentElement.parentElement.classList.remove( 'hidden' );
164
+ } );
165
+
166
+ /*
167
+ * Update the notice to indicate that all of the errors are displaying.
168
+ * Like 'Showing 5 of 5 validation errors'.
169
+ */
170
+ component.updateShowingErrorsRow( validationErrors.length, validationErrors.length );
171
+
172
+ // Hide this 'Show all' button.
173
+ event.target.classList.add( 'hidden' );
174
+
175
+ // Change the value of the error type <select> element to 'All Error Types'.
176
+ document.getElementById( 'amp_validation_error_type' ).selectedIndex = 0;
177
+ };
178
+
179
+ document.getElementById( 'url-post-filter' ).addEventListener( 'click', onClick );
180
+ };
181
+
182
+ /**
183
+ * Handles filtering by error type, triggered by clicking 'Apply Filter'.
184
+ *
185
+ * Gets the value of the error type <select> element.
186
+ * And hides all <tr> elements that do not have the same type of this value.
187
+ * If 'All Error Types' is selected, this displays all errors.
188
+ */
189
+ component.handleFiltering = function handleFiltering() {
190
+ const onChange = function( event ) {
191
+ const showAllButton = document.getElementById( component.showAllId );
192
+ if ( ! event.target.matches( 'select' ) ) {
193
+ return;
194
+ }
195
+
196
+ event.preventDefault();
197
+
198
+ const isAllErrorTypesSelected = ( '-1' === event.target.value );
199
+ const errorTypeQuery = document.querySelectorAll( '[data-error-type]' );
200
+
201
+ // If the user has chosen 'All Error Types' from the <select>, hide the 'Show all' button.
202
+ if ( isAllErrorTypesSelected && showAllButton ) {
203
+ showAllButton.classList.add( 'hidden' );
204
+ }
205
+
206
+ /*
207
+ * Iterate through all of the <tr> elements in the list table.
208
+ * If the error type does not match the value (selected error type), hide them.
209
+ */
210
+ let numberErrorsDisplaying = 0;
211
+ errorTypeQuery.forEach( function( element ) {
212
+ const errorType = element.getAttribute( 'data-error-type' );
213
+
214
+ // If 'All Error Types' was selected, this should display all errors.
215
+ if ( isAllErrorTypesSelected || ! event.target.value || event.target.value === errorType ) {
216
+ element.parentElement.parentElement.classList.remove( 'hidden' );
217
+ numberErrorsDisplaying++;
218
+ } else {
219
+ element.parentElement.parentElement.classList.add( 'hidden' );
220
+ }
221
+ } );
222
+
223
+ component.updateShowingErrorsRow( numberErrorsDisplaying, errorTypeQuery.length );
224
+ };
225
+
226
+ document.getElementById( 'amp_validation_error_type' ).addEventListener( 'change', onChange );
227
+ };
228
+
229
+ /**
230
+ * Handles searching for errors via the <input> and the 'Search Errors' <button>.
231
+ */
232
+ component.handleSearching = function handleSearching() {
233
+ const onClick = function( event ) {
234
+ event.preventDefault();
235
+ if ( ! event.target.matches( 'input' ) ) {
236
+ return;
237
+ }
238
+
239
+ const searchQuery = document.getElementById( 'invalid-url-search-search-input' ).value;
240
+ const detailsQuery = document.querySelectorAll( 'tbody .column-details' );
241
+
242
+ /*
243
+ * Iterate through the 'Details' column of each row.
244
+ * If the search query is not present, hide the row.
245
+ */
246
+ let numberErrorsDisplaying = 0;
247
+ detailsQuery.forEach( function( element ) {
248
+ let isSearchQueryPresent = false;
249
+
250
+ element.querySelectorAll( '.detailed' ).forEach( function( detailed ) {
251
+ if ( -1 !== detailed.innerText.indexOf( searchQuery ) ) {
252
+ isSearchQueryPresent = true;
253
+ }
254
+ } );
255
+
256
+ if ( isSearchQueryPresent ) {
257
+ element.parentElement.classList.remove( 'hidden' );
258
+ numberErrorsDisplaying++;
259
+ } else {
260
+ element.parentElement.classList.add( 'hidden' );
261
+ }
262
+ } );
263
+
264
+ component.updateShowingErrorsRow( numberErrorsDisplaying, detailsQuery.length );
265
+ };
266
+
267
+ document.getElementById( 'search-submit' ).addEventListener( 'click', onClick );
268
+ };
269
+
270
+ /**
271
+ * Update icon for select element.
272
+ *
273
+ * @param {HTMLSelectElement} select Select element.
274
+ */
275
+ component.updateSelectIcon = function updateSelectIcon( select ) {
276
+ const newOption = select.options[ select.selectedIndex ];
277
+ if ( newOption ) {
278
+ const iconSrc = newOption.getAttribute( 'data-status-icon' );
279
+ select.parentNode.querySelector( 'img' ).setAttribute( 'src', iconSrc );
280
+ }
281
+ };
282
+
283
+ /**
284
+ * Handles a change in the error status, like from 'New' to 'Accepted'.
285
+ *
286
+ * Gets the data-status-icon value from the newly-selected <option>.
287
+ * And sets this as the src of the status icon <img>.
288
+ */
289
+ component.handleStatusChange = function handleStatusChange() {
290
+ const setRowStatusClass = function( { row, select } ) {
291
+ const acceptedValue = 3;
292
+ const rejectedValue = 2;
293
+ const status = parseInt( select.options[ select.selectedIndex ].value );
294
+
295
+ row.classList.toggle( 'new', isNaN( status ) );
296
+ row.classList.toggle( 'accepted', acceptedValue === status );
297
+ row.classList.toggle( 'rejected', rejectedValue === status );
298
+ };
299
+
300
+ const onChange = function( { event, row, select } ) {
301
+ if ( event.target.matches( 'select' ) ) {
302
+ component.updateSelectIcon( event.target );
303
+ setRowStatusClass( { row, select } );
304
+ }
305
+ };
306
+
307
+ document.querySelectorAll( 'tr[id^="tag-"]' ).forEach( function( row ) {
308
+ const select = row.querySelector( '.amp-validation-error-status' );
309
+
310
+ if ( select ) {
311
+ setRowStatusClass( { row, select } );
312
+ select.addEventListener( 'change', function( event ) {
313
+ onChange( { event, row, select } );
314
+ } );
315
+ }
316
+ } );
317
+ };
318
+
319
+ /**
320
+ * On checking a bulk action checkbox, this ensures that the 'Accept' and 'Reject' buttons are present. Handle clicking on buttons.
321
+ *
322
+ * They're hidden until one of these boxes is checked.
323
+ * Also, on unchecking the last checked box, this hides these buttons.
324
+ */
325
+ component.handleBulkActions = function handleBulkActions() {
326
+ const acceptButton = document.querySelector( 'button.action.accept' );
327
+ const rejectButton = document.querySelector( 'button.action.reject' );
328
+ const acceptAndRejectContainer = document.getElementById( 'accept-reject-buttons' );
329
+
330
+ const onChange = function( event ) {
331
+ let areThereCheckedBoxes;
332
+
333
+ if ( ! event.target.matches( '[type=checkbox]' ) ) {
334
+ return;
335
+ }
336
+
337
+ if ( event.target.checked ) {
338
+ // This checkbox was checked, so ensure the buttons display.
339
+ acceptAndRejectContainer.classList.remove( 'hidden' );
340
+ } else {
341
+ /*
342
+ * This checkbox was unchecked.
343
+ * So find if there are any other checkboxes that are checked.
344
+ * If not, hide the 'Accept' and 'Reject' buttons.
345
+ */
346
+ areThereCheckedBoxes = false;
347
+ document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( function( element ) {
348
+ if ( element.checked ) {
349
+ areThereCheckedBoxes = true;
350
+ }
351
+ } );
352
+ if ( ! areThereCheckedBoxes ) {
353
+ acceptAndRejectContainer.classList.add( 'hidden' );
354
+ }
355
+ }
356
+ };
357
+
358
+ document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( function( element ) {
359
+ element.addEventListener( 'change', onChange );
360
+ } );
361
+
362
+ // Handle click on accept button.
363
+ acceptButton.addEventListener( 'click', function() {
364
+ Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), function( select ) {
365
+ if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
366
+ select.value = '3';
367
+ component.updateSelectIcon( select );
368
+ component.addBeforeUnloadPrompt();
369
+ }
370
+ } );
371
+ } );
372
+
373
+ // Handle click on reject button.
374
+ rejectButton.addEventListener( 'click', function() {
375
+ Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), function( select ) {
376
+ if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
377
+ select.value = '2';
378
+ component.updateSelectIcon( select );
379
+ component.addBeforeUnloadPrompt();
380
+ }
381
+ } );
382
+ } );
383
+ };
384
+
385
+ /**
386
+ * Changes the page heading and document title, as this doesn't look to be possible with a PHP filter.
387
+ */
388
+ component.changeHeading = function changeHeading() {
389
+ const headingQuery = document.getElementsByClassName( 'wp-heading-inline' );
390
+ if ( headingQuery[ 0 ] && component.data.l10n.page_heading ) {
391
+ headingQuery[ 0 ].innerText = component.data.l10n.page_heading;
392
+ document.title = component.data.l10n.page_heading + document.title;
393
+ }
394
+ };
395
+
396
+ /**
397
+ * Adds the AMP icon to the page heading if AMP is enabled on this URL.
398
+ */
399
+ component.showAMPIconIfEnabled = function() {
400
+ const heading = document.querySelector( 'h1.wp-heading-inline' );
401
+ if ( heading && true === component.data.l10n.amp_enabled ) {
402
+ const ampIcon = document.createElement( 'span' );
403
+ ampIcon.classList.add( 'status-text', 'sanitized' );
404
+ heading.appendChild( ampIcon );
405
+ }
406
+ };
407
+
408
+ return component;
409
+ }() );
assets/js/amp-validated-urls-index.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* exported ampValidatedUrlsIndex */
2
+
3
+ const ampValidatedUrlsIndex = ( function() { // eslint-disable-line no-unused-vars
4
+ let component = {
5
+ classes: {}
6
+ };
7
+
8
+ /**
9
+ * The class for the new status
10
+ *
11
+ * @type {string}
12
+ */
13
+ component.classes.new = 'new';
14
+
15
+ /**
16
+ * Boot.
17
+ */
18
+ component.boot = function boot() {
19
+ component.highlightRowsWithNewStatus();
20
+ };
21
+
22
+ /**
23
+ * Highlight rows with new status.
24
+ */
25
+ component.highlightRowsWithNewStatus = function highlightRowsWithNewStatus() {
26
+ document.querySelectorAll( 'tr[id^="post-"]' ).forEach( function( row ) {
27
+ if ( row.querySelector( 'span.status-text.' + component.classes.new ) ) {
28
+ row.classList.add( 'new' );
29
+ }
30
+ } );
31
+ };
32
+
33
+ return component;
34
+ }() );
assets/js/amp-validation-detail-toggle-compiled.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /******/ (function(modules) { // webpackBootstrap
2
+ /******/ // The module cache
3
+ /******/ var installedModules = {};
4
+ /******/
5
+ /******/ // The require function
6
+ /******/ function __webpack_require__(moduleId) {
7
+ /******/
8
+ /******/ // Check if module is in cache
9
+ /******/ if(installedModules[moduleId]) {
10
+ /******/ return installedModules[moduleId].exports;
11
+ /******/ }
12
+ /******/ // Create a new module (and put it into the cache)
13
+ /******/ var module = installedModules[moduleId] = {
14
+ /******/ i: moduleId,
15
+ /******/ l: false,
16
+ /******/ exports: {}
17
+ /******/ };
18
+ /******/
19
+ /******/ // Execute the module function
20
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21
+ /******/
22
+ /******/ // Flag the module as loaded
23
+ /******/ module.l = true;
24
+ /******/
25
+ /******/ // Return the exports of the module
26
+ /******/ return module.exports;
27
+ /******/ }
28
+ /******/
29
+ /******/
30
+ /******/ // expose the modules object (__webpack_modules__)
31
+ /******/ __webpack_require__.m = modules;
32
+ /******/
33
+ /******/ // expose the module cache
34
+ /******/ __webpack_require__.c = installedModules;
35
+ /******/
36
+ /******/ // define getter function for harmony exports
37
+ /******/ __webpack_require__.d = function(exports, name, getter) {
38
+ /******/ if(!__webpack_require__.o(exports, name)) {
39
+ /******/ Object.defineProperty(exports, name, {
40
+ /******/ configurable: false,
41
+ /******/ enumerable: true,
42
+ /******/ get: getter
43
+ /******/ });
44
+ /******/ }
45
+ /******/ };
46
+ /******/
47
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
48
+ /******/ __webpack_require__.n = function(module) {
49
+ /******/ var getter = module && module.__esModule ?
50
+ /******/ function getDefault() { return module['default']; } :
51
+ /******/ function getModuleExports() { return module; };
52
+ /******/ __webpack_require__.d(getter, 'a', getter);
53
+ /******/ return getter;
54
+ /******/ };
55
+ /******/
56
+ /******/ // Object.prototype.hasOwnProperty.call
57
+ /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58
+ /******/
59
+ /******/ // __webpack_public_path__
60
+ /******/ __webpack_require__.p = "";
61
+ /******/
62
+ /******/ // Load entry module and return exports
63
+ /******/ return __webpack_require__(__webpack_require__.s = 14);
64
+ /******/ })
65
+ /************************************************************************/
66
+ /******/ ({
67
+
68
+ /***/ 1:
69
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
70
+
71
+ "use strict";
72
+ eval("/**\n * Specify a function to execute when the DOM is fully loaded.\n *\n * @param {Function} callback A function to execute after the DOM is ready.\n *\n * @return {void}\n */\nvar domReady = function domReady(callback) {\n if (document.readyState === 'complete' || // DOMContentLoaded + Images/Styles/etc loaded, so we call directly.\n document.readyState === 'interactive' // DOMContentLoaded fires at this point, so we call directly.\n ) {\n return callback();\n } // DOMContentLoaded has not fired yet, delay callback until then.\n\n\n document.addEventListener('DOMContentLoaded', callback);\n};\n\n/* harmony default export */ __webpack_exports__[\"a\"] = (domReady);\n//# sourceMappingURL=index.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL25vZGVfbW9kdWxlcy9Ad29yZHByZXNzL2RvbS1yZWFkeS9idWlsZC1tb2R1bGUvaW5kZXguanM/YTc2MyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNwZWNpZnkgYSBmdW5jdGlvbiB0byBleGVjdXRlIHdoZW4gdGhlIERPTSBpcyBmdWxseSBsb2FkZWQuXG4gKlxuICogQHBhcmFtIHtGdW5jdGlvbn0gY2FsbGJhY2sgQSBmdW5jdGlvbiB0byBleGVjdXRlIGFmdGVyIHRoZSBET00gaXMgcmVhZHkuXG4gKlxuICogQHJldHVybiB7dm9pZH1cbiAqL1xudmFyIGRvbVJlYWR5ID0gZnVuY3Rpb24gZG9tUmVhZHkoY2FsbGJhY2spIHtcbiAgaWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdjb21wbGV0ZScgfHwgLy8gRE9NQ29udGVudExvYWRlZCArIEltYWdlcy9TdHlsZXMvZXRjIGxvYWRlZCwgc28gd2UgY2FsbCBkaXJlY3RseS5cbiAgZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2ludGVyYWN0aXZlJyAvLyBET01Db250ZW50TG9hZGVkIGZpcmVzIGF0IHRoaXMgcG9pbnQsIHNvIHdlIGNhbGwgZGlyZWN0bHkuXG4gICkge1xuICAgICAgcmV0dXJuIGNhbGxiYWNrKCk7XG4gICAgfSAvLyBET01Db250ZW50TG9hZGVkIGhhcyBub3QgZmlyZWQgeWV0LCBkZWxheSBjYWxsYmFjayB1bnRpbCB0aGVuLlxuXG5cbiAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGNhbGxiYWNrKTtcbn07XG5cbmV4cG9ydCBkZWZhdWx0IGRvbVJlYWR5O1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9aW5kZXguanMubWFwXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ub2RlX21vZHVsZXMvQHdvcmRwcmVzcy9kb20tcmVhZHkvYnVpbGQtbW9kdWxlL2luZGV4LmpzXG4vLyBtb2R1bGUgaWQgPSAxXG4vLyBtb2R1bGUgY2h1bmtzID0gMSAyIDMiXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///1\n");
73
+
74
+ /***/ }),
75
+
76
+ /***/ 14:
77
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
78
+
79
+ "use strict";
80
+ eval("Object.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__wordpress_dom_ready__ = __webpack_require__(1);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_amp_validation_i18n__ = __webpack_require__(15);\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_amp_validation_i18n___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_amp_validation_i18n__);\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\n/**\n * WordPress dependencies\n */\n\n\n/**\n * Localized data\n */\n\n\nvar OPEN_CLASS = 'is-open';\n\n/**\n * Adds detail toggle buttons to the header and footer rows of the validation error \"details\" column.\n * The buttons are added via JS because there's no easy way to append them to the heading of a sortable\n * table column via backend code.\n *\n * @param {string} containerSelector Selector for elements that will have the button added.\n * @param {string} ariaLabel Screen reader label for the button.\n * @return {Array} Array of added buttons.\n */\nfunction addToggleButtons(containerSelector, ariaLabel) {\n\tvar addButton = function addButton(container) {\n\t\tvar button = document.createElement('button');\n\t\tbutton.setAttribute('aria-label', ariaLabel);\n\t\tbutton.setAttribute('type', 'button');\n\t\tbutton.setAttribute('class', 'error-details-toggle');\n\t\tcontainer.appendChild(button);\n\n\t\treturn button;\n\t};\n\n\treturn [].concat(_toConsumableArray(document.querySelectorAll(containerSelector))).map(function (container) {\n\t\treturn addButton(container);\n\t});\n}\n\nfunction addToggleAllListener(_ref) {\n\tvar btn = _ref.btn,\n\t _ref$toggleAllButtonS = _ref.toggleAllButtonSelector,\n\t toggleAllButtonSelector = _ref$toggleAllButtonS === undefined ? null : _ref$toggleAllButtonS,\n\t targetDetailsSelector = _ref.targetDetailsSelector;\n\n\tvar open = false;\n\n\tvar targetDetails = [].concat(_toConsumableArray(document.querySelectorAll(targetDetailsSelector)));\n\n\tvar toggleAllButtons = [];\n\tif (toggleAllButtonSelector) {\n\t\ttoggleAllButtons = [].concat(_toConsumableArray(document.querySelectorAll(toggleAllButtonSelector)));\n\t}\n\n\tvar onButtonClick = function onButtonClick() {\n\t\topen = !open;\n\t\ttoggleAllButtons.forEach(function (toggleAllButton) {\n\t\t\ttoggleAllButton.classList.toggle(OPEN_CLASS);\n\t\t});\n\n\t\ttargetDetails.forEach(function (detail) {\n\t\t\tif (open) {\n\t\t\t\tdetail.setAttribute('open', true);\n\t\t\t} else {\n\t\t\t\tdetail.removeAttribute('open');\n\t\t\t}\n\t\t});\n\t};\n\n\tbtn.addEventListener('click', onButtonClick);\n}\n\n/**\n * Adds classes to the rows for the amp_validation_error term list table.\n *\n * This is needed because \\WP_Terms_List_Table::single_row() does not allow for additional\n * attributes to be added to the <tr> element.\n */\nfunction addTermListTableRowClasses() {\n\tvar rows = [].concat(_toConsumableArray(document.querySelectorAll('#the-list tr')));\n\trows.forEach(function (row) {\n\t\tvar statusText = row.querySelector('.column-status > .status-text');\n\t\tif (statusText) {\n\t\t\trow.classList.toggle('new', statusText.classList.contains('new'));\n\t\t\trow.classList.toggle('accepted', statusText.classList.contains('accepted'));\n\t\t\trow.classList.toggle('rejected', statusText.classList.contains('rejected'));\n\t\t}\n\t});\n}\n\nObject(__WEBPACK_IMPORTED_MODULE_0__wordpress_dom_ready__[\"a\" /* default */])(function () {\n\taddToggleButtons('th.column-details.manage-column', __WEBPACK_IMPORTED_MODULE_1_amp_validation_i18n__[\"detailToggleBtnAriaLabel\"]).forEach(function (btn) {\n\t\taddToggleAllListener({\n\t\t\tbtn: btn,\n\t\t\ttoggleAllButtonSelector: '.column-details button.error-details-toggle',\n\t\t\ttargetDetailsSelector: '.column-details details'\n\t\t});\n\t});\n\n\taddToggleButtons('th.manage-column.column-sources_with_invalid_output', __WEBPACK_IMPORTED_MODULE_1_amp_validation_i18n__[\"sourcesToggleBtnAriaLabel\"]).forEach(function (btn) {\n\t\taddToggleAllListener({\n\t\t\tbtn: btn,\n\t\t\ttoggleAllButtonSelector: '.column-sources_with_invalid_output button.error-details-toggle',\n\t\t\ttargetDetailsSelector: 'details.source'\n\t\t});\n\t});\n\n\taddTermListTableRowClasses();\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTQuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9hc3NldHMvc3JjL2FtcC12YWxpZGF0aW9uLWRldGFpbC10b2dnbGUuanM/YmUxMiJdLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvbiBfdG9Db25zdW1hYmxlQXJyYXkoYXJyKSB7IGlmIChBcnJheS5pc0FycmF5KGFycikpIHsgZm9yICh2YXIgaSA9IDAsIGFycjIgPSBBcnJheShhcnIubGVuZ3RoKTsgaSA8IGFyci5sZW5ndGg7IGkrKykgeyBhcnIyW2ldID0gYXJyW2ldOyB9IHJldHVybiBhcnIyOyB9IGVsc2UgeyByZXR1cm4gQXJyYXkuZnJvbShhcnIpOyB9IH1cblxuLyoqXG4gKiBXb3JkUHJlc3MgZGVwZW5kZW5jaWVzXG4gKi9cbmltcG9ydCBkb21SZWFkeSBmcm9tICdAd29yZHByZXNzL2RvbS1yZWFkeSc7XG5cbi8qKlxuICogTG9jYWxpemVkIGRhdGFcbiAqL1xuaW1wb3J0IHsgZGV0YWlsVG9nZ2xlQnRuQXJpYUxhYmVsLCBzb3VyY2VzVG9nZ2xlQnRuQXJpYUxhYmVsIH0gZnJvbSAnYW1wLXZhbGlkYXRpb24taTE4bic7XG5cbnZhciBPUEVOX0NMQVNTID0gJ2lzLW9wZW4nO1xuXG4vKipcbiAqIEFkZHMgZGV0YWlsIHRvZ2dsZSBidXR0b25zIHRvIHRoZSBoZWFkZXIgYW5kIGZvb3RlciByb3dzIG9mIHRoZSB2YWxpZGF0aW9uIGVycm9yIFwiZGV0YWlsc1wiIGNvbHVtbi5cbiAqIFRoZSBidXR0b25zIGFyZSBhZGRlZCB2aWEgSlMgYmVjYXVzZSB0aGVyZSdzIG5vIGVhc3kgd2F5IHRvIGFwcGVuZCB0aGVtIHRvIHRoZSBoZWFkaW5nIG9mIGEgc29ydGFibGVcbiAqIHRhYmxlIGNvbHVtbiB2aWEgYmFja2VuZCBjb2RlLlxuICpcbiAqIEBwYXJhbSB7c3RyaW5nfSBjb250YWluZXJTZWxlY3RvciBTZWxlY3RvciBmb3IgZWxlbWVudHMgdGhhdCB3aWxsIGhhdmUgdGhlIGJ1dHRvbiBhZGRlZC5cbiAqIEBwYXJhbSB7c3RyaW5nfSBhcmlhTGFiZWwgU2NyZWVuIHJlYWRlciBsYWJlbCBmb3IgdGhlIGJ1dHRvbi5cbiAqIEByZXR1cm4ge0FycmF5fSBBcnJheSBvZiBhZGRlZCBidXR0b25zLlxuICovXG5mdW5jdGlvbiBhZGRUb2dnbGVCdXR0b25zKGNvbnRhaW5lclNlbGVjdG9yLCBhcmlhTGFiZWwpIHtcblx0dmFyIGFkZEJ1dHRvbiA9IGZ1bmN0aW9uIGFkZEJ1dHRvbihjb250YWluZXIpIHtcblx0XHR2YXIgYnV0dG9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYnV0dG9uJyk7XG5cdFx0YnV0dG9uLnNldEF0dHJpYnV0ZSgnYXJpYS1sYWJlbCcsIGFyaWFMYWJlbCk7XG5cdFx0YnV0dG9uLnNldEF0dHJpYnV0ZSgndHlwZScsICdidXR0b24nKTtcblx0XHRidXR0b24uc2V0QXR0cmlidXRlKCdjbGFzcycsICdlcnJvci1kZXRhaWxzLXRvZ2dsZScpO1xuXHRcdGNvbnRhaW5lci5hcHBlbmRDaGlsZChidXR0b24pO1xuXG5cdFx0cmV0dXJuIGJ1dHRvbjtcblx0fTtcblxuXHRyZXR1cm4gW10uY29uY2F0KF90b0NvbnN1bWFibGVBcnJheShkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKGNvbnRhaW5lclNlbGVjdG9yKSkpLm1hcChmdW5jdGlvbiAoY29udGFpbmVyKSB7XG5cdFx0cmV0dXJuIGFkZEJ1dHRvbihjb250YWluZXIpO1xuXHR9KTtcbn1cblxuZnVuY3Rpb24gYWRkVG9nZ2xlQWxsTGlzdGVuZXIoX3JlZikge1xuXHR2YXIgYnRuID0gX3JlZi5idG4sXG5cdCAgICBfcmVmJHRvZ2dsZUFsbEJ1dHRvblMgPSBfcmVmLnRvZ2dsZUFsbEJ1dHRvblNlbGVjdG9yLFxuXHQgICAgdG9nZ2xlQWxsQnV0dG9uU2VsZWN0b3IgPSBfcmVmJHRvZ2dsZUFsbEJ1dHRvblMgPT09IHVuZGVmaW5lZCA/IG51bGwgOiBfcmVmJHRvZ2dsZUFsbEJ1dHRvblMsXG5cdCAgICB0YXJnZXREZXRhaWxzU2VsZWN0b3IgPSBfcmVmLnRhcmdldERldGFpbHNTZWxlY3RvcjtcblxuXHR2YXIgb3BlbiA9IGZhbHNlO1xuXG5cdHZhciB0YXJnZXREZXRhaWxzID0gW10uY29uY2F0KF90b0NvbnN1bWFibGVBcnJheShkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHRhcmdldERldGFpbHNTZWxlY3RvcikpKTtcblxuXHR2YXIgdG9nZ2xlQWxsQnV0dG9ucyA9IFtdO1xuXHRpZiAodG9nZ2xlQWxsQnV0dG9uU2VsZWN0b3IpIHtcblx0XHR0b2dnbGVBbGxCdXR0b25zID0gW10uY29uY2F0KF90b0NvbnN1bWFibGVBcnJheShkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKHRvZ2dsZUFsbEJ1dHRvblNlbGVjdG9yKSkpO1xuXHR9XG5cblx0dmFyIG9uQnV0dG9uQ2xpY2sgPSBmdW5jdGlvbiBvbkJ1dHRvbkNsaWNrKCkge1xuXHRcdG9wZW4gPSAhb3Blbjtcblx0XHR0b2dnbGVBbGxCdXR0b25zLmZvckVhY2goZnVuY3Rpb24gKHRvZ2dsZUFsbEJ1dHRvbikge1xuXHRcdFx0dG9nZ2xlQWxsQnV0dG9uLmNsYXNzTGlzdC50b2dnbGUoT1BFTl9DTEFTUyk7XG5cdFx0fSk7XG5cblx0XHR0YXJnZXREZXRhaWxzLmZvckVhY2goZnVuY3Rpb24gKGRldGFpbCkge1xuXHRcdFx0aWYgKG9wZW4pIHtcblx0XHRcdFx0ZGV0YWlsLnNldEF0dHJpYnV0ZSgnb3BlbicsIHRydWUpO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0ZGV0YWlsLnJlbW92ZUF0dHJpYnV0ZSgnb3BlbicpO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9O1xuXG5cdGJ0bi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIG9uQnV0dG9uQ2xpY2spO1xufVxuXG4vKipcbiAqIEFkZHMgY2xhc3NlcyB0byB0aGUgcm93cyBmb3IgdGhlIGFtcF92YWxpZGF0aW9uX2Vycm9yIHRlcm0gbGlzdCB0YWJsZS5cbiAqXG4gKiBUaGlzIGlzIG5lZWRlZCBiZWNhdXNlIFxcV1BfVGVybXNfTGlzdF9UYWJsZTo6c2luZ2xlX3JvdygpIGRvZXMgbm90IGFsbG93IGZvciBhZGRpdGlvbmFsXG4gKiBhdHRyaWJ1dGVzIHRvIGJlIGFkZGVkIHRvIHRoZSA8dHI+IGVsZW1lbnQuXG4gKi9cbmZ1bmN0aW9uIGFkZFRlcm1MaXN0VGFibGVSb3dDbGFzc2VzKCkge1xuXHR2YXIgcm93cyA9IFtdLmNvbmNhdChfdG9Db25zdW1hYmxlQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnI3RoZS1saXN0IHRyJykpKTtcblx0cm93cy5mb3JFYWNoKGZ1bmN0aW9uIChyb3cpIHtcblx0XHR2YXIgc3RhdHVzVGV4dCA9IHJvdy5xdWVyeVNlbGVjdG9yKCcuY29sdW1uLXN0YXR1cyA+IC5zdGF0dXMtdGV4dCcpO1xuXHRcdGlmIChzdGF0dXNUZXh0KSB7XG5cdFx0XHRyb3cuY2xhc3NMaXN0LnRvZ2dsZSgnbmV3Jywgc3RhdHVzVGV4dC5jbGFzc0xpc3QuY29udGFpbnMoJ25ldycpKTtcblx0XHRcdHJvdy5jbGFzc0xpc3QudG9nZ2xlKCdhY2NlcHRlZCcsIHN0YXR1c1RleHQuY2xhc3NMaXN0LmNvbnRhaW5zKCdhY2NlcHRlZCcpKTtcblx0XHRcdHJvdy5jbGFzc0xpc3QudG9nZ2xlKCdyZWplY3RlZCcsIHN0YXR1c1RleHQuY2xhc3NMaXN0LmNvbnRhaW5zKCdyZWplY3RlZCcpKTtcblx0XHR9XG5cdH0pO1xufVxuXG5kb21SZWFkeShmdW5jdGlvbiAoKSB7XG5cdGFkZFRvZ2dsZUJ1dHRvbnMoJ3RoLmNvbHVtbi1kZXRhaWxzLm1hbmFnZS1jb2x1bW4nLCBkZXRhaWxUb2dnbGVCdG5BcmlhTGFiZWwpLmZvckVhY2goZnVuY3Rpb24gKGJ0bikge1xuXHRcdGFkZFRvZ2dsZUFsbExpc3RlbmVyKHtcblx0XHRcdGJ0bjogYnRuLFxuXHRcdFx0dG9nZ2xlQWxsQnV0dG9uU2VsZWN0b3I6ICcuY29sdW1uLWRldGFpbHMgYnV0dG9uLmVycm9yLWRldGFpbHMtdG9nZ2xlJyxcblx0XHRcdHRhcmdldERldGFpbHNTZWxlY3RvcjogJy5jb2x1bW4tZGV0YWlscyBkZXRhaWxzJ1xuXHRcdH0pO1xuXHR9KTtcblxuXHRhZGRUb2dnbGVCdXR0b25zKCd0aC5tYW5hZ2UtY29sdW1uLmNvbHVtbi1zb3VyY2VzX3dpdGhfaW52YWxpZF9vdXRwdXQnLCBzb3VyY2VzVG9nZ2xlQnRuQXJpYUxhYmVsKS5mb3JFYWNoKGZ1bmN0aW9uIChidG4pIHtcblx0XHRhZGRUb2dnbGVBbGxMaXN0ZW5lcih7XG5cdFx0XHRidG46IGJ0bixcblx0XHRcdHRvZ2dsZUFsbEJ1dHRvblNlbGVjdG9yOiAnLmNvbHVtbi1zb3VyY2VzX3dpdGhfaW52YWxpZF9vdXRwdXQgYnV0dG9uLmVycm9yLWRldGFpbHMtdG9nZ2xlJyxcblx0XHRcdHRhcmdldERldGFpbHNTZWxlY3RvcjogJ2RldGFpbHMuc291cmNlJ1xuXHRcdH0pO1xuXHR9KTtcblxuXHRhZGRUZXJtTGlzdFRhYmxlUm93Q2xhc3NlcygpO1xufSk7XG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9hc3NldHMvc3JjL2FtcC12YWxpZGF0aW9uLWRldGFpbC10b2dnbGUuanNcbi8vIG1vZHVsZSBpZCA9IDE0XG4vLyBtb2R1bGUgY2h1bmtzID0gMSJdLCJtYXBwaW5ncyI6IkFBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///14\n");
81
+
82
+ /***/ }),
83
+
84
+ /***/ 15:
85
+ /***/ (function(module, exports) {
86
+
87
+ module.exports = ampValidationI18n;
88
+
89
+ /***/ })
90
+
91
+ /******/ });
assets/js/amp-validation-single-error-url-details-compiled.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /******/ (function(modules) { // webpackBootstrap
2
+ /******/ // The module cache
3
+ /******/ var installedModules = {};
4
+ /******/
5
+ /******/ // The require function
6
+ /******/ function __webpack_require__(moduleId) {
7
+ /******/
8
+ /******/ // Check if module is in cache
9
+ /******/ if(installedModules[moduleId]) {
10
+ /******/ return installedModules[moduleId].exports;
11
+ /******/ }
12
+ /******/ // Create a new module (and put it into the cache)
13
+ /******/ var module = installedModules[moduleId] = {
14
+ /******/ i: moduleId,
15
+ /******/ l: false,
16
+ /******/ exports: {}
17
+ /******/ };
18
+ /******/
19
+ /******/ // Execute the module function
20
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21
+ /******/
22
+ /******/ // Flag the module as loaded
23
+ /******/ module.l = true;
24
+ /******/
25
+ /******/ // Return the exports of the module
26
+ /******/ return module.exports;
27
+ /******/ }
28
+ /******/
29
+ /******/
30
+ /******/ // expose the modules object (__webpack_modules__)
31
+ /******/ __webpack_require__.m = modules;
32
+ /******/
33
+ /******/ // expose the module cache
34
+ /******/ __webpack_require__.c = installedModules;
35
+ /******/
36
+ /******/ // define getter function for harmony exports
37
+ /******/ __webpack_require__.d = function(exports, name, getter) {
38
+ /******/ if(!__webpack_require__.o(exports, name)) {
39
+ /******/ Object.defineProperty(exports, name, {
40
+ /******/ configurable: false,
41
+ /******/ enumerable: true,
42
+ /******/ get: getter
43
+ /******/ });
44
+ /******/ }
45
+ /******/ };
46
+ /******/
47
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
48
+ /******/ __webpack_require__.n = function(module) {
49
+ /******/ var getter = module && module.__esModule ?
50
+ /******/ function getDefault() { return module['default']; } :
51
+ /******/ function getModuleExports() { return module; };
52
+ /******/ __webpack_require__.d(getter, 'a', getter);
53
+ /******/ return getter;
54
+ /******/ };
55
+ /******/
56
+ /******/ // Object.prototype.hasOwnProperty.call
57
+ /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58
+ /******/
59
+ /******/ // __webpack_public_path__
60
+ /******/ __webpack_require__.p = "";
61
+ /******/
62
+ /******/ // Load entry module and return exports
63
+ /******/ return __webpack_require__(__webpack_require__.s = 17);
64
+ /******/ })
65
+ /************************************************************************/
66
+ /******/ ({
67
+
68
+ /***/ 1:
69
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
70
+
71
+ "use strict";
72
+ eval("/**\n * Specify a function to execute when the DOM is fully loaded.\n *\n * @param {Function} callback A function to execute after the DOM is ready.\n *\n * @return {void}\n */\nvar domReady = function domReady(callback) {\n if (document.readyState === 'complete' || // DOMContentLoaded + Images/Styles/etc loaded, so we call directly.\n document.readyState === 'interactive' // DOMContentLoaded fires at this point, so we call directly.\n ) {\n return callback();\n } // DOMContentLoaded has not fired yet, delay callback until then.\n\n\n document.addEventListener('DOMContentLoaded', callback);\n};\n\n/* harmony default export */ __webpack_exports__[\"a\"] = (domReady);\n//# sourceMappingURL=index.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL25vZGVfbW9kdWxlcy9Ad29yZHByZXNzL2RvbS1yZWFkeS9idWlsZC1tb2R1bGUvaW5kZXguanM/YTc2MyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNwZWNpZnkgYSBmdW5jdGlvbiB0byBleGVjdXRlIHdoZW4gdGhlIERPTSBpcyBmdWxseSBsb2FkZWQuXG4gKlxuICogQHBhcmFtIHtGdW5jdGlvbn0gY2FsbGJhY2sgQSBmdW5jdGlvbiB0byBleGVjdXRlIGFmdGVyIHRoZSBET00gaXMgcmVhZHkuXG4gKlxuICogQHJldHVybiB7dm9pZH1cbiAqL1xudmFyIGRvbVJlYWR5ID0gZnVuY3Rpb24gZG9tUmVhZHkoY2FsbGJhY2spIHtcbiAgaWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdjb21wbGV0ZScgfHwgLy8gRE9NQ29udGVudExvYWRlZCArIEltYWdlcy9TdHlsZXMvZXRjIGxvYWRlZCwgc28gd2UgY2FsbCBkaXJlY3RseS5cbiAgZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2ludGVyYWN0aXZlJyAvLyBET01Db250ZW50TG9hZGVkIGZpcmVzIGF0IHRoaXMgcG9pbnQsIHNvIHdlIGNhbGwgZGlyZWN0bHkuXG4gICkge1xuICAgICAgcmV0dXJuIGNhbGxiYWNrKCk7XG4gICAgfSAvLyBET01Db250ZW50TG9hZGVkIGhhcyBub3QgZmlyZWQgeWV0LCBkZWxheSBjYWxsYmFjayB1bnRpbCB0aGVuLlxuXG5cbiAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGNhbGxiYWNrKTtcbn07XG5cbmV4cG9ydCBkZWZhdWx0IGRvbVJlYWR5O1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9aW5kZXguanMubWFwXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ub2RlX21vZHVsZXMvQHdvcmRwcmVzcy9kb20tcmVhZHkvYnVpbGQtbW9kdWxlL2luZGV4LmpzXG4vLyBtb2R1bGUgaWQgPSAxXG4vLyBtb2R1bGUgY2h1bmtzID0gMSAyIDMiXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///1\n");
73
+
74
+ /***/ }),
75
+
76
+ /***/ 17:
77
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
78
+
79
+ "use strict";
80
+ eval("Object.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__wordpress_dom_ready__ = __webpack_require__(1);\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\n/**\n * WordPress dependencies\n */\n\n\n/**\n * Toggles the contents of a details element as an additional table tr.\n */\n\nvar RowToggler = function () {\n\tfunction RowToggler(tr, index) {\n\t\t_classCallCheck(this, RowToggler);\n\n\t\tthis.tr = tr;\n\t\tthis.index = index;\n\n\t\t// Since we're adding additional rows, we need to override default .striped tables styles.\n\t\tthis.tr.classList.add(this.index % 2 ? 'odd' : 'even'); // eslint-disable-line no-magic-numbers\n\n\t\tthis.toggle = this.toggle.bind(this);\n\t}\n\n\t/**\n * Sets up the new tr and adds an event listener to toggle details.\n */\n\n\n\t_createClass(RowToggler, [{\n\t\tkey: 'init',\n\t\tvalue: function init() {\n\t\t\tvar _this = this;\n\n\t\t\tthis.details = this.tr.querySelector('.column-details details');\n\t\t\tif (this.details) {\n\t\t\t\tthis.createNewTr();\n\t\t\t\tvar togglers = [].concat(_toConsumableArray(this.tr.querySelectorAll('.single-url-detail-toggle')), [this.details.querySelector('summary')]);\n\n\t\t\t\ttogglers.forEach(function (el) {\n\t\t\t\t\tel.addEventListener('click', function () {\n\t\t\t\t\t\t_this.toggle(el);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t/**\n * Creates the details table row from the original row's <details> element content, minus the summary.\n */\n\n\t}, {\n\t\tkey: 'createNewTr',\n\t\tvalue: function createNewTr() {\n\t\t\tthis.newTr = document.createElement('tr');\n\t\t\tthis.newTr.classList.add('details');\n\t\t\tthis.newTr.classList.add(this.index % 2 ? 'odd' : 'even'); // eslint-disable-line no-magic-numbers\n\n\t\t\tvar newCell = document.createElement('td');\n\t\t\tnewCell.setAttribute('colspan', this.getRowColspan());\n\n\t\t\tvar _iteratorNormalCompletion = true;\n\t\t\tvar _didIteratorError = false;\n\t\t\tvar _iteratorError = undefined;\n\n\t\t\ttry {\n\t\t\t\tfor (var _iterator = this.details.childNodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n\t\t\t\t\tvar childNode = _step.value;\n\n\t\t\t\t\tif ('SUMMARY' !== childNode.tagName) {\n\t\t\t\t\t\tnewCell.appendChild(childNode.cloneNode(true));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\t_didIteratorError = true;\n\t\t\t\t_iteratorError = err;\n\t\t\t} finally {\n\t\t\t\ttry {\n\t\t\t\t\tif (!_iteratorNormalCompletion && _iterator.return) {\n\t\t\t\t\t\t_iterator.return();\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tif (_didIteratorError) {\n\t\t\t\t\t\tthrow _iteratorError;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.newTr.appendChild(newCell);\n\t\t}\n\n\t\t/**\n * Gets the number of cells within the original row.\n *\n * @return {number} The number of cells.\n */\n\n\t}, {\n\t\tkey: 'getRowColspan',\n\t\tvalue: function getRowColspan() {\n\t\t\treturn [].concat(_toConsumableArray(this.tr.childNodes)).filter(function (childNode) {\n\t\t\t\treturn ['TD', 'TH'].includes(childNode.tagName);\n\t\t\t}).length;\n\t\t}\n\n\t\t/**\n * Toggles the additional row.\n *\n * @param {Object} target The click event target.\n */\n\n\t}, {\n\t\tkey: 'toggle',\n\t\tvalue: function toggle(target) {\n\t\t\tif (this.tr.classList.contains('expanded')) {\n\t\t\t\tthis.onClose(target);\n\t\t\t} else {\n\t\t\t\tthis.onOpen(target);\n\t\t\t}\n\t\t}\n\n\t\t/**\n * Adds the additional row.\n *\n * @param {Object} target The click event target.\n */\n\n\t}, {\n\t\tkey: 'onOpen',\n\t\tvalue: function onOpen(target) {\n\t\t\tthis.tr.parentNode.insertBefore(this.newTr, this.tr.nextSibling);\n\t\t\tthis.tr.classList.add('expanded');\n\n\t\t\tif ('SUMMARY' !== target.tagName) {\n\t\t\t\t// This browser will do this if the summary was clicked.\n\t\t\t\tthis.details.setAttribute('open', true);\n\t\t\t}\n\t\t}\n\n\t\t/**\n * Removes the additional row.\n *\n * @param {Object} target The click event target.\n */\n\n\t}, {\n\t\tkey: 'onClose',\n\t\tvalue: function onClose(target) {\n\t\t\tthis.tr.parentNode.removeChild(this.newTr);\n\t\t\tthis.tr.classList.remove('expanded');\n\n\t\t\tif ('SUMMARY' !== target.tagName) {\n\t\t\t\tthis.details.removeAttribute('open');\n\t\t\t}\n\t\t}\n\t}]);\n\n\treturn RowToggler;\n}();\n\n/**\n * Sets up expandable details for errors when viewing a single URL error list.\n */\n\n\nvar ErrorRows = function () {\n\tfunction ErrorRows() {\n\t\t_classCallCheck(this, ErrorRows);\n\n\t\tthis.rows = [].concat(_toConsumableArray(document.querySelectorAll('.wp-list-table tr[id^=\"tag-\"]'))).map(function (tr, index) {\n\t\t\tvar rowHandler = new RowToggler(tr, index);\n\t\t\trowHandler.init();\n\t\t\treturn rowHandler;\n\t\t}).filter(function (row) {\n\t\t\treturn row.details;\n\t\t});\n\t}\n\n\t_createClass(ErrorRows, [{\n\t\tkey: 'init',\n\t\tvalue: function init() {\n\t\t\tthis.addToggleAllListener();\n\t\t}\n\n\t\t/**\n * Handle 'toggle all' buttons on the page.\n */\n\n\t}, {\n\t\tkey: 'addToggleAllListener',\n\t\tvalue: function addToggleAllListener() {\n\t\t\tvar _this2 = this;\n\n\t\t\tvar open = false;\n\t\t\tvar toggleButtons = [].concat(_toConsumableArray(document.querySelectorAll('.column-details button.error-details-toggle')));\n\n\t\t\tvar onButtonClick = function onButtonClick(target) {\n\t\t\t\topen = !open;\n\t\t\t\t_this2.rows.forEach(function (row) {\n\t\t\t\t\tif (open) {\n\t\t\t\t\t\trow.onOpen(target);\n\t\t\t\t\t} else {\n\t\t\t\t\t\trow.onClose(target);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t};\n\n\t\t\twindow.addEventListener('click', function (event) {\n\t\t\t\tif (toggleButtons.includes(event.target)) {\n\t\t\t\t\tonButtonClick(event.target);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}]);\n\n\treturn ErrorRows;\n}();\n\nObject(__WEBPACK_IMPORTED_MODULE_0__wordpress_dom_ready__[\"a\" /* default */])(function () {\n\tnew ErrorRows().init();\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTcuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9hc3NldHMvc3JjL2FtcC12YWxpZGF0aW9uLXNpbmdsZS1lcnJvci11cmwtZGV0YWlscy5qcz80OTI0Il0sInNvdXJjZXNDb250ZW50IjpbInZhciBfY3JlYXRlQ2xhc3MgPSBmdW5jdGlvbiAoKSB7IGZ1bmN0aW9uIGRlZmluZVByb3BlcnRpZXModGFyZ2V0LCBwcm9wcykgeyBmb3IgKHZhciBpID0gMDsgaSA8IHByb3BzLmxlbmd0aDsgaSsrKSB7IHZhciBkZXNjcmlwdG9yID0gcHJvcHNbaV07IGRlc2NyaXB0b3IuZW51bWVyYWJsZSA9IGRlc2NyaXB0b3IuZW51bWVyYWJsZSB8fCBmYWxzZTsgZGVzY3JpcHRvci5jb25maWd1cmFibGUgPSB0cnVlOyBpZiAoXCJ2YWx1ZVwiIGluIGRlc2NyaXB0b3IpIGRlc2NyaXB0b3Iud3JpdGFibGUgPSB0cnVlOyBPYmplY3QuZGVmaW5lUHJvcGVydHkodGFyZ2V0LCBkZXNjcmlwdG9yLmtleSwgZGVzY3JpcHRvcik7IH0gfSByZXR1cm4gZnVuY3Rpb24gKENvbnN0cnVjdG9yLCBwcm90b1Byb3BzLCBzdGF0aWNQcm9wcykgeyBpZiAocHJvdG9Qcm9wcykgZGVmaW5lUHJvcGVydGllcyhDb25zdHJ1Y3Rvci5wcm90b3R5cGUsIHByb3RvUHJvcHMpOyBpZiAoc3RhdGljUHJvcHMpIGRlZmluZVByb3BlcnRpZXMoQ29uc3RydWN0b3IsIHN0YXRpY1Byb3BzKTsgcmV0dXJuIENvbnN0cnVjdG9yOyB9OyB9KCk7XG5cbmZ1bmN0aW9uIF90b0NvbnN1bWFibGVBcnJheShhcnIpIHsgaWYgKEFycmF5LmlzQXJyYXkoYXJyKSkgeyBmb3IgKHZhciBpID0gMCwgYXJyMiA9IEFycmF5KGFyci5sZW5ndGgpOyBpIDwgYXJyLmxlbmd0aDsgaSsrKSB7IGFycjJbaV0gPSBhcnJbaV07IH0gcmV0dXJuIGFycjI7IH0gZWxzZSB7IHJldHVybiBBcnJheS5mcm9tKGFycik7IH0gfVxuXG5mdW5jdGlvbiBfY2xhc3NDYWxsQ2hlY2soaW5zdGFuY2UsIENvbnN0cnVjdG9yKSB7IGlmICghKGluc3RhbmNlIGluc3RhbmNlb2YgQ29uc3RydWN0b3IpKSB7IHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb25cIik7IH0gfVxuXG4vKipcbiAqIFdvcmRQcmVzcyBkZXBlbmRlbmNpZXNcbiAqL1xuaW1wb3J0IGRvbVJlYWR5IGZyb20gJ0B3b3JkcHJlc3MvZG9tLXJlYWR5JztcblxuLyoqXG4gKiBUb2dnbGVzIHRoZSBjb250ZW50cyBvZiBhIGRldGFpbHMgZWxlbWVudCBhcyBhbiBhZGRpdGlvbmFsIHRhYmxlIHRyLlxuICovXG5cbnZhciBSb3dUb2dnbGVyID0gZnVuY3Rpb24gKCkge1xuXHRmdW5jdGlvbiBSb3dUb2dnbGVyKHRyLCBpbmRleCkge1xuXHRcdF9jbGFzc0NhbGxDaGVjayh0aGlzLCBSb3dUb2dnbGVyKTtcblxuXHRcdHRoaXMudHIgPSB0cjtcblx0XHR0aGlzLmluZGV4ID0gaW5kZXg7XG5cblx0XHQvLyBTaW5jZSB3ZSdyZSBhZGRpbmcgYWRkaXRpb25hbCByb3dzLCB3ZSBuZWVkIHRvIG92ZXJyaWRlIGRlZmF1bHQgLnN0cmlwZWQgdGFibGVzIHN0eWxlcy5cblx0XHR0aGlzLnRyLmNsYXNzTGlzdC5hZGQodGhpcy5pbmRleCAlIDIgPyAnb2RkJyA6ICdldmVuJyk7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tbWFnaWMtbnVtYmVyc1xuXG5cdFx0dGhpcy50b2dnbGUgPSB0aGlzLnRvZ2dsZS5iaW5kKHRoaXMpO1xuXHR9XG5cblx0LyoqXG4gICogU2V0cyB1cCB0aGUgbmV3IHRyIGFuZCBhZGRzIGFuIGV2ZW50IGxpc3RlbmVyIHRvIHRvZ2dsZSBkZXRhaWxzLlxuICAqL1xuXG5cblx0X2NyZWF0ZUNsYXNzKFJvd1RvZ2dsZXIsIFt7XG5cdFx0a2V5OiAnaW5pdCcsXG5cdFx0dmFsdWU6IGZ1bmN0aW9uIGluaXQoKSB7XG5cdFx0XHR2YXIgX3RoaXMgPSB0aGlzO1xuXG5cdFx0XHR0aGlzLmRldGFpbHMgPSB0aGlzLnRyLnF1ZXJ5U2VsZWN0b3IoJy5jb2x1bW4tZGV0YWlscyBkZXRhaWxzJyk7XG5cdFx0XHRpZiAodGhpcy5kZXRhaWxzKSB7XG5cdFx0XHRcdHRoaXMuY3JlYXRlTmV3VHIoKTtcblx0XHRcdFx0dmFyIHRvZ2dsZXJzID0gW10uY29uY2F0KF90b0NvbnN1bWFibGVBcnJheSh0aGlzLnRyLnF1ZXJ5U2VsZWN0b3JBbGwoJy5zaW5nbGUtdXJsLWRldGFpbC10b2dnbGUnKSksIFt0aGlzLmRldGFpbHMucXVlcnlTZWxlY3Rvcignc3VtbWFyeScpXSk7XG5cblx0XHRcdFx0dG9nZ2xlcnMuZm9yRWFjaChmdW5jdGlvbiAoZWwpIHtcblx0XHRcdFx0XHRlbC5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGZ1bmN0aW9uICgpIHtcblx0XHRcdFx0XHRcdF90aGlzLnRvZ2dsZShlbCk7XG5cdFx0XHRcdFx0fSk7XG5cdFx0XHRcdH0pO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdC8qKlxuICAgKiBDcmVhdGVzIHRoZSBkZXRhaWxzIHRhYmxlIHJvdyBmcm9tIHRoZSBvcmlnaW5hbCByb3cncyA8ZGV0YWlscz4gZWxlbWVudCBjb250ZW50LCBtaW51cyB0aGUgc3VtbWFyeS5cbiAgICovXG5cblx0fSwge1xuXHRcdGtleTogJ2NyZWF0ZU5ld1RyJyxcblx0XHR2YWx1ZTogZnVuY3Rpb24gY3JlYXRlTmV3VHIoKSB7XG5cdFx0XHR0aGlzLm5ld1RyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndHInKTtcblx0XHRcdHRoaXMubmV3VHIuY2xhc3NMaXN0LmFkZCgnZGV0YWlscycpO1xuXHRcdFx0dGhpcy5uZXdUci5jbGFzc0xpc3QuYWRkKHRoaXMuaW5kZXggJSAyID8gJ29kZCcgOiAnZXZlbicpOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLW1hZ2ljLW51bWJlcnNcblxuXHRcdFx0dmFyIG5ld0NlbGwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCd0ZCcpO1xuXHRcdFx0bmV3Q2VsbC5zZXRBdHRyaWJ1dGUoJ2NvbHNwYW4nLCB0aGlzLmdldFJvd0NvbHNwYW4oKSk7XG5cblx0XHRcdHZhciBfaXRlcmF0b3JOb3JtYWxDb21wbGV0aW9uID0gdHJ1ZTtcblx0XHRcdHZhciBfZGlkSXRlcmF0b3JFcnJvciA9IGZhbHNlO1xuXHRcdFx0dmFyIF9pdGVyYXRvckVycm9yID0gdW5kZWZpbmVkO1xuXG5cdFx0XHR0cnkge1xuXHRcdFx0XHRmb3IgKHZhciBfaXRlcmF0b3IgPSB0aGlzLmRldGFpbHMuY2hpbGROb2Rlc1tTeW1ib2wuaXRlcmF0b3JdKCksIF9zdGVwOyAhKF9pdGVyYXRvck5vcm1hbENvbXBsZXRpb24gPSAoX3N0ZXAgPSBfaXRlcmF0b3IubmV4dCgpKS5kb25lKTsgX2l0ZXJhdG9yTm9ybWFsQ29tcGxldGlvbiA9IHRydWUpIHtcblx0XHRcdFx0XHR2YXIgY2hpbGROb2RlID0gX3N0ZXAudmFsdWU7XG5cblx0XHRcdFx0XHRpZiAoJ1NVTU1BUlknICE9PSBjaGlsZE5vZGUudGFnTmFtZSkge1xuXHRcdFx0XHRcdFx0bmV3Q2VsbC5hcHBlbmRDaGlsZChjaGlsZE5vZGUuY2xvbmVOb2RlKHRydWUpKTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH0gY2F0Y2ggKGVycikge1xuXHRcdFx0XHRfZGlkSXRlcmF0b3JFcnJvciA9IHRydWU7XG5cdFx0XHRcdF9pdGVyYXRvckVycm9yID0gZXJyO1xuXHRcdFx0fSBmaW5hbGx5IHtcblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRpZiAoIV9pdGVyYXRvck5vcm1hbENvbXBsZXRpb24gJiYgX2l0ZXJhdG9yLnJldHVybikge1xuXHRcdFx0XHRcdFx0X2l0ZXJhdG9yLnJldHVybigpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBmaW5hbGx5IHtcblx0XHRcdFx0XHRpZiAoX2RpZEl0ZXJhdG9yRXJyb3IpIHtcblx0XHRcdFx0XHRcdHRocm93IF9pdGVyYXRvckVycm9yO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHR0aGlzLm5ld1RyLmFwcGVuZENoaWxkKG5ld0NlbGwpO1xuXHRcdH1cblxuXHRcdC8qKlxuICAgKiBHZXRzIHRoZSBudW1iZXIgb2YgY2VsbHMgd2l0aGluIHRoZSBvcmlnaW5hbCByb3cuXG4gICAqXG4gICAqIEByZXR1cm4ge251bWJlcn0gVGhlIG51bWJlciBvZiBjZWxscy5cbiAgICovXG5cblx0fSwge1xuXHRcdGtleTogJ2dldFJvd0NvbHNwYW4nLFxuXHRcdHZhbHVlOiBmdW5jdGlvbiBnZXRSb3dDb2xzcGFuKCkge1xuXHRcdFx0cmV0dXJuIFtdLmNvbmNhdChfdG9Db25zdW1hYmxlQXJyYXkodGhpcy50ci5jaGlsZE5vZGVzKSkuZmlsdGVyKGZ1bmN0aW9uIChjaGlsZE5vZGUpIHtcblx0XHRcdFx0cmV0dXJuIFsnVEQnLCAnVEgnXS5pbmNsdWRlcyhjaGlsZE5vZGUudGFnTmFtZSk7XG5cdFx0XHR9KS5sZW5ndGg7XG5cdFx0fVxuXG5cdFx0LyoqXG4gICAqIFRvZ2dsZXMgdGhlIGFkZGl0aW9uYWwgcm93LlxuICAgKlxuICAgKiBAcGFyYW0ge09iamVjdH0gdGFyZ2V0IFRoZSBjbGljayBldmVudCB0YXJnZXQuXG4gICAqL1xuXG5cdH0sIHtcblx0XHRrZXk6ICd0b2dnbGUnLFxuXHRcdHZhbHVlOiBmdW5jdGlvbiB0b2dnbGUodGFyZ2V0KSB7XG5cdFx0XHRpZiAodGhpcy50ci5jbGFzc0xpc3QuY29udGFpbnMoJ2V4cGFuZGVkJykpIHtcblx0XHRcdFx0dGhpcy5vbkNsb3NlKHRhcmdldCk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHR0aGlzLm9uT3Blbih0YXJnZXQpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdC8qKlxuICAgKiBBZGRzIHRoZSBhZGRpdGlvbmFsIHJvdy5cbiAgICpcbiAgICogQHBhcmFtIHtPYmplY3R9IHRhcmdldCBUaGUgY2xpY2sgZXZlbnQgdGFyZ2V0LlxuICAgKi9cblxuXHR9LCB7XG5cdFx0a2V5OiAnb25PcGVuJyxcblx0XHR2YWx1ZTogZnVuY3Rpb24gb25PcGVuKHRhcmdldCkge1xuXHRcdFx0dGhpcy50ci5wYXJlbnROb2RlLmluc2VydEJlZm9yZSh0aGlzLm5ld1RyLCB0aGlzLnRyLm5leHRTaWJsaW5nKTtcblx0XHRcdHRoaXMudHIuY2xhc3NMaXN0LmFkZCgnZXhwYW5kZWQnKTtcblxuXHRcdFx0aWYgKCdTVU1NQVJZJyAhPT0gdGFyZ2V0LnRhZ05hbWUpIHtcblx0XHRcdFx0Ly8gVGhpcyBicm93c2VyIHdpbGwgZG8gdGhpcyBpZiB0aGUgc3VtbWFyeSB3YXMgY2xpY2tlZC5cblx0XHRcdFx0dGhpcy5kZXRhaWxzLnNldEF0dHJpYnV0ZSgnb3BlbicsIHRydWUpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdC8qKlxuICAgKiBSZW1vdmVzIHRoZSBhZGRpdGlvbmFsIHJvdy5cbiAgICpcbiAgICogQHBhcmFtIHtPYmplY3R9IHRhcmdldCBUaGUgY2xpY2sgZXZlbnQgdGFyZ2V0LlxuICAgKi9cblxuXHR9LCB7XG5cdFx0a2V5OiAnb25DbG9zZScsXG5cdFx0dmFsdWU6IGZ1bmN0aW9uIG9uQ2xvc2UodGFyZ2V0KSB7XG5cdFx0XHR0aGlzLnRyLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQodGhpcy5uZXdUcik7XG5cdFx0XHR0aGlzLnRyLmNsYXNzTGlzdC5yZW1vdmUoJ2V4cGFuZGVkJyk7XG5cblx0XHRcdGlmICgnU1VNTUFSWScgIT09IHRhcmdldC50YWdOYW1lKSB7XG5cdFx0XHRcdHRoaXMuZGV0YWlscy5yZW1vdmVBdHRyaWJ1dGUoJ29wZW4nKTtcblx0XHRcdH1cblx0XHR9XG5cdH1dKTtcblxuXHRyZXR1cm4gUm93VG9nZ2xlcjtcbn0oKTtcblxuLyoqXG4gKiBTZXRzIHVwIGV4cGFuZGFibGUgZGV0YWlscyBmb3IgZXJyb3JzIHdoZW4gdmlld2luZyBhIHNpbmdsZSBVUkwgZXJyb3IgbGlzdC5cbiAqL1xuXG5cbnZhciBFcnJvclJvd3MgPSBmdW5jdGlvbiAoKSB7XG5cdGZ1bmN0aW9uIEVycm9yUm93cygpIHtcblx0XHRfY2xhc3NDYWxsQ2hlY2sodGhpcywgRXJyb3JSb3dzKTtcblxuXHRcdHRoaXMucm93cyA9IFtdLmNvbmNhdChfdG9Db25zdW1hYmxlQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLndwLWxpc3QtdGFibGUgdHJbaWRePVwidGFnLVwiXScpKSkubWFwKGZ1bmN0aW9uICh0ciwgaW5kZXgpIHtcblx0XHRcdHZhciByb3dIYW5kbGVyID0gbmV3IFJvd1RvZ2dsZXIodHIsIGluZGV4KTtcblx0XHRcdHJvd0hhbmRsZXIuaW5pdCgpO1xuXHRcdFx0cmV0dXJuIHJvd0hhbmRsZXI7XG5cdFx0fSkuZmlsdGVyKGZ1bmN0aW9uIChyb3cpIHtcblx0XHRcdHJldHVybiByb3cuZGV0YWlscztcblx0XHR9KTtcblx0fVxuXG5cdF9jcmVhdGVDbGFzcyhFcnJvclJvd3MsIFt7XG5cdFx0a2V5OiAnaW5pdCcsXG5cdFx0dmFsdWU6IGZ1bmN0aW9uIGluaXQoKSB7XG5cdFx0XHR0aGlzLmFkZFRvZ2dsZUFsbExpc3RlbmVyKCk7XG5cdFx0fVxuXG5cdFx0LyoqXG4gICAqIEhhbmRsZSAndG9nZ2xlIGFsbCcgYnV0dG9ucyBvbiB0aGUgcGFnZS5cbiAgICovXG5cblx0fSwge1xuXHRcdGtleTogJ2FkZFRvZ2dsZUFsbExpc3RlbmVyJyxcblx0XHR2YWx1ZTogZnVuY3Rpb24gYWRkVG9nZ2xlQWxsTGlzdGVuZXIoKSB7XG5cdFx0XHR2YXIgX3RoaXMyID0gdGhpcztcblxuXHRcdFx0dmFyIG9wZW4gPSBmYWxzZTtcblx0XHRcdHZhciB0b2dnbGVCdXR0b25zID0gW10uY29uY2F0KF90b0NvbnN1bWFibGVBcnJheShkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcuY29sdW1uLWRldGFpbHMgYnV0dG9uLmVycm9yLWRldGFpbHMtdG9nZ2xlJykpKTtcblxuXHRcdFx0dmFyIG9uQnV0dG9uQ2xpY2sgPSBmdW5jdGlvbiBvbkJ1dHRvbkNsaWNrKHRhcmdldCkge1xuXHRcdFx0XHRvcGVuID0gIW9wZW47XG5cdFx0XHRcdF90aGlzMi5yb3dzLmZvckVhY2goZnVuY3Rpb24gKHJvdykge1xuXHRcdFx0XHRcdGlmIChvcGVuKSB7XG5cdFx0XHRcdFx0XHRyb3cub25PcGVuKHRhcmdldCk7XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdHJvdy5vbkNsb3NlKHRhcmdldCk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9KTtcblx0XHRcdH07XG5cblx0XHRcdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGZ1bmN0aW9uIChldmVudCkge1xuXHRcdFx0XHRpZiAodG9nZ2xlQnV0dG9ucy5pbmNsdWRlcyhldmVudC50YXJnZXQpKSB7XG5cdFx0XHRcdFx0b25CdXR0b25DbGljayhldmVudC50YXJnZXQpO1xuXHRcdFx0XHR9XG5cdFx0XHR9KTtcblx0XHR9XG5cdH1dKTtcblxuXHRyZXR1cm4gRXJyb3JSb3dzO1xufSgpO1xuXG5kb21SZWFkeShmdW5jdGlvbiAoKSB7XG5cdG5ldyBFcnJvclJvd3MoKS5pbml0KCk7XG59KTtcblxuXG4vLy8vLy8vLy8vLy8vLy8vLy9cbi8vIFdFQlBBQ0sgRk9PVEVSXG4vLyAuL2Fzc2V0cy9zcmMvYW1wLXZhbGlkYXRpb24tc2luZ2xlLWVycm9yLXVybC1kZXRhaWxzLmpzXG4vLyBtb2R1bGUgaWQgPSAxN1xuLy8gbW9kdWxlIGNodW5rcyA9IDMiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///17\n");
81
+
82
+ /***/ })
83
+
84
+ /******/ });
assets/js/amp-validation-tooltips-compiled.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /******/ (function(modules) { // webpackBootstrap
2
+ /******/ // The module cache
3
+ /******/ var installedModules = {};
4
+ /******/
5
+ /******/ // The require function
6
+ /******/ function __webpack_require__(moduleId) {
7
+ /******/
8
+ /******/ // Check if module is in cache
9
+ /******/ if(installedModules[moduleId]) {
10
+ /******/ return installedModules[moduleId].exports;
11
+ /******/ }
12
+ /******/ // Create a new module (and put it into the cache)
13
+ /******/ var module = installedModules[moduleId] = {
14
+ /******/ i: moduleId,
15
+ /******/ l: false,
16
+ /******/ exports: {}
17
+ /******/ };
18
+ /******/
19
+ /******/ // Execute the module function
20
+ /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21
+ /******/
22
+ /******/ // Flag the module as loaded
23
+ /******/ module.l = true;
24
+ /******/
25
+ /******/ // Return the exports of the module
26
+ /******/ return module.exports;
27
+ /******/ }
28
+ /******/
29
+ /******/
30
+ /******/ // expose the modules object (__webpack_modules__)
31
+ /******/ __webpack_require__.m = modules;
32
+ /******/
33
+ /******/ // expose the module cache
34
+ /******/ __webpack_require__.c = installedModules;
35
+ /******/
36
+ /******/ // define getter function for harmony exports
37
+ /******/ __webpack_require__.d = function(exports, name, getter) {
38
+ /******/ if(!__webpack_require__.o(exports, name)) {
39
+ /******/ Object.defineProperty(exports, name, {
40
+ /******/ configurable: false,
41
+ /******/ enumerable: true,
42
+ /******/ get: getter
43
+ /******/ });
44
+ /******/ }
45
+ /******/ };
46
+ /******/
47
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
48
+ /******/ __webpack_require__.n = function(module) {
49
+ /******/ var getter = module && module.__esModule ?
50
+ /******/ function getDefault() { return module['default']; } :
51
+ /******/ function getModuleExports() { return module; };
52
+ /******/ __webpack_require__.d(getter, 'a', getter);
53
+ /******/ return getter;
54
+ /******/ };
55
+ /******/
56
+ /******/ // Object.prototype.hasOwnProperty.call
57
+ /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58
+ /******/
59
+ /******/ // __webpack_public_path__
60
+ /******/ __webpack_require__.p = "";
61
+ /******/
62
+ /******/ // Load entry module and return exports
63
+ /******/ return __webpack_require__(__webpack_require__.s = 16);
64
+ /******/ })
65
+ /************************************************************************/
66
+ /******/ ({
67
+
68
+ /***/ 1:
69
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
70
+
71
+ "use strict";
72
+ eval("/**\n * Specify a function to execute when the DOM is fully loaded.\n *\n * @param {Function} callback A function to execute after the DOM is ready.\n *\n * @return {void}\n */\nvar domReady = function domReady(callback) {\n if (document.readyState === 'complete' || // DOMContentLoaded + Images/Styles/etc loaded, so we call directly.\n document.readyState === 'interactive' // DOMContentLoaded fires at this point, so we call directly.\n ) {\n return callback();\n } // DOMContentLoaded has not fired yet, delay callback until then.\n\n\n document.addEventListener('DOMContentLoaded', callback);\n};\n\n/* harmony default export */ __webpack_exports__[\"a\"] = (domReady);\n//# sourceMappingURL=index.js.map//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL25vZGVfbW9kdWxlcy9Ad29yZHByZXNzL2RvbS1yZWFkeS9idWlsZC1tb2R1bGUvaW5kZXguanM/YTc2MyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNwZWNpZnkgYSBmdW5jdGlvbiB0byBleGVjdXRlIHdoZW4gdGhlIERPTSBpcyBmdWxseSBsb2FkZWQuXG4gKlxuICogQHBhcmFtIHtGdW5jdGlvbn0gY2FsbGJhY2sgQSBmdW5jdGlvbiB0byBleGVjdXRlIGFmdGVyIHRoZSBET00gaXMgcmVhZHkuXG4gKlxuICogQHJldHVybiB7dm9pZH1cbiAqL1xudmFyIGRvbVJlYWR5ID0gZnVuY3Rpb24gZG9tUmVhZHkoY2FsbGJhY2spIHtcbiAgaWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdjb21wbGV0ZScgfHwgLy8gRE9NQ29udGVudExvYWRlZCArIEltYWdlcy9TdHlsZXMvZXRjIGxvYWRlZCwgc28gd2UgY2FsbCBkaXJlY3RseS5cbiAgZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ2ludGVyYWN0aXZlJyAvLyBET01Db250ZW50TG9hZGVkIGZpcmVzIGF0IHRoaXMgcG9pbnQsIHNvIHdlIGNhbGwgZGlyZWN0bHkuXG4gICkge1xuICAgICAgcmV0dXJuIGNhbGxiYWNrKCk7XG4gICAgfSAvLyBET01Db250ZW50TG9hZGVkIGhhcyBub3QgZmlyZWQgeWV0LCBkZWxheSBjYWxsYmFjayB1bnRpbCB0aGVuLlxuXG5cbiAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGNhbGxiYWNrKTtcbn07XG5cbmV4cG9ydCBkZWZhdWx0IGRvbVJlYWR5O1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9aW5kZXguanMubWFwXG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9ub2RlX21vZHVsZXMvQHdvcmRwcmVzcy9kb20tcmVhZHkvYnVpbGQtbW9kdWxlL2luZGV4LmpzXG4vLyBtb2R1bGUgaWQgPSAxXG4vLyBtb2R1bGUgY2h1bmtzID0gMSAyIDMiXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///1\n");
73
+
74
+ /***/ }),
75
+
76
+ /***/ 16:
77
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
78
+
79
+ "use strict";
80
+ eval("Object.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__wordpress_dom_ready__ = __webpack_require__(1);\n/**\n * WordPress dependencies\n */\n\n\n// WIP Pointer function\nfunction sourcesPointer() {\n\tjQuery(document).on('click', '.tooltip-button', function () {\n\t\tjQuery(this).pointer({\n\t\t\tcontent: jQuery(this).next('.tooltip').attr('data-content'),\n\t\t\tposition: {\n\t\t\t\tedge: 'left',\n\t\t\t\talign: 'center'\n\t\t\t},\n\t\t\tpointerClass: 'wp-pointer wp-pointer--tooltip'\n\t\t}).pointer('open');\n\t});\n}\n\nObject(__WEBPACK_IMPORTED_MODULE_0__wordpress_dom_ready__[\"a\" /* default */])(function () {\n\tsourcesPointer();\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTYuanMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9hc3NldHMvc3JjL2FtcC12YWxpZGF0aW9uLXRvb2x0aXBzLmpzPzAxYTMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBXb3JkUHJlc3MgZGVwZW5kZW5jaWVzXG4gKi9cbmltcG9ydCBkb21SZWFkeSBmcm9tICdAd29yZHByZXNzL2RvbS1yZWFkeSc7XG5cbi8vIFdJUCBQb2ludGVyIGZ1bmN0aW9uXG5mdW5jdGlvbiBzb3VyY2VzUG9pbnRlcigpIHtcblx0alF1ZXJ5KGRvY3VtZW50KS5vbignY2xpY2snLCAnLnRvb2x0aXAtYnV0dG9uJywgZnVuY3Rpb24gKCkge1xuXHRcdGpRdWVyeSh0aGlzKS5wb2ludGVyKHtcblx0XHRcdGNvbnRlbnQ6IGpRdWVyeSh0aGlzKS5uZXh0KCcudG9vbHRpcCcpLmF0dHIoJ2RhdGEtY29udGVudCcpLFxuXHRcdFx0cG9zaXRpb246IHtcblx0XHRcdFx0ZWRnZTogJ2xlZnQnLFxuXHRcdFx0XHRhbGlnbjogJ2NlbnRlcidcblx0XHRcdH0sXG5cdFx0XHRwb2ludGVyQ2xhc3M6ICd3cC1wb2ludGVyIHdwLXBvaW50ZXItLXRvb2x0aXAnXG5cdFx0fSkucG9pbnRlcignb3BlbicpO1xuXHR9KTtcbn1cblxuZG9tUmVhZHkoZnVuY3Rpb24gKCkge1xuXHRzb3VyY2VzUG9pbnRlcigpO1xufSk7XG5cblxuLy8vLy8vLy8vLy8vLy8vLy8vXG4vLyBXRUJQQUNLIEZPT1RFUlxuLy8gLi9hc3NldHMvc3JjL2FtcC12YWxpZGF0aW9uLXRvb2x0aXBzLmpzXG4vLyBtb2R1bGUgaWQgPSAxNlxuLy8gbW9kdWxlIGNodW5rcyA9IDIiXSwibWFwcGluZ3MiOiJBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///16\n");
81
+
82
+ /***/ })
83
+
84
+ /******/ });
assets/src/amp-block-editor-toggle.js ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ const { __ } = wp.i18n;
5
+ const { FormToggle, Notice } = wp.components;
6
+ const { Fragment, RawHTML } = wp.element;
7
+ const { withSelect, withDispatch } = wp.data;
8
+ const { PluginPostStatusInfo } = wp.editPost;
9
+ const { compose, withInstanceId } = wp.compose;
10
+
11
+ /**
12
+ * Exported via wp_localize_script().
13
+ */
14
+ const { possibleStati, defaultStatus, errorMessages } = window.wpAmpEditor;
15
+
16
+ /**
17
+ * Adds an 'Enable AMP' toggle to the block editor 'Status & Visibility' section.
18
+ *
19
+ * If there are error(s) that block AMP from being enabled or disabled,
20
+ * this only displays a Notice with the error(s), not a toggle.
21
+ * Error(s) are imported as errorMessages via wp_localize_script().
22
+ *
23
+ * @return {Object} AMPToggle component.
24
+ */
25
+ function AMPToggle( { enabledStatus, onAmpChange } ) {
26
+ return (
27
+ <Fragment>
28
+ <PluginPostStatusInfo>
29
+ { ! errorMessages.length && <label htmlFor='amp-enabled'>{ __( 'Enable AMP', 'amp' ) }</label> }
30
+ {
31
+ ! errorMessages.length &&
32
+ (
33
+ <FormToggle
34
+ checked={ 'enabled' === enabledStatus }
35
+ onChange={ () => onAmpChange( enabledStatus ) }
36
+ id='amp-enabled'
37
+ />
38
+ )
39
+ }
40
+ {
41
+ !! errorMessages.length &&
42
+ (
43
+ <Notice
44
+ status='warning'
45
+ isDismissible={ false }
46
+ >
47
+ {
48
+ errorMessages.map(
49
+ ( message, index ) => <RawHTML key={ index }>{ message }</RawHTML>
50
+ )
51
+ }
52
+ </Notice>
53
+ )
54
+ }
55
+ </PluginPostStatusInfo>
56
+ </Fragment>
57
+ );
58
+ }
59
+
60
+ /**
61
+ * The AMP Toggle component, composed with the enabledStatus and a callback for when it's changed.
62
+ *
63
+ * @return {Object} The composed AMP toggle.
64
+ */
65
+ function ComposedAMPToggle() {
66
+ return compose( [
67
+ withSelect( ( select ) => {
68
+ /**
69
+ * Gets the AMP enabled status.
70
+ *
71
+ * Uses select from the enclosing function to get the meta value.
72
+ * If it doesn't exist, it uses the default value.
73
+ * This applies especially for a new post, where there probably won't be a meta value yet.
74
+ *
75
+ * @return {string} Enabled status, either 'enabled' or 'disabled'.
76
+ */
77
+ const getEnabledStatus = () => {
78
+ const meta = select( 'core/editor' ).getEditedPostAttribute( 'meta' );
79
+ if ( meta && meta.amp_status && possibleStati.includes( meta.amp_status ) ) {
80
+ return meta.amp_status;
81
+ }
82
+ return defaultStatus;
83
+ };
84
+
85
+ return { enabledStatus: getEnabledStatus() };
86
+ } ),
87
+ withDispatch( ( dispatch ) => ( {
88
+ onAmpChange: ( enabledStatus ) => {
89
+ const newStatus = 'enabled' === enabledStatus ? 'disabled' : 'enabled';
90
+ dispatch( 'core/editor' ).editPost( { meta: { amp_status: newStatus } } );
91
+ }
92
+ } ) ),
93
+ withInstanceId
94
+ ] )( AMPToggle );
95
+ }
96
+
97
+ export default wp.plugins.registerPlugin( 'amp', {
98
+ icon: 'hidden',
99
+ render: ComposedAMPToggle()
100
+ } );
assets/src/amp-validation-detail-toggle.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import domReady from '@wordpress/dom-ready';
5
+
6
+ /**
7
+ * Localized data
8
+ */
9
+ import { detailToggleBtnAriaLabel, sourcesToggleBtnAriaLabel } from 'amp-validation-i18n';
10
+
11
+ const OPEN_CLASS = 'is-open';
12
+
13
+ /**
14
+ * Adds detail toggle buttons to the header and footer rows of the validation error "details" column.
15
+ * The buttons are added via JS because there's no easy way to append them to the heading of a sortable
16
+ * table column via backend code.
17
+ *
18
+ * @param {string} containerSelector Selector for elements that will have the button added.
19
+ * @param {string} ariaLabel Screen reader label for the button.
20
+ * @return {Array} Array of added buttons.
21
+ */
22
+ function addToggleButtons( containerSelector, ariaLabel ) {
23
+ const addButton = ( container ) => {
24
+ const button = document.createElement( 'button' );
25
+ button.setAttribute( 'aria-label', ariaLabel );
26
+ button.setAttribute( 'type', 'button' );
27
+ button.setAttribute( 'class', 'error-details-toggle' );
28
+ container.appendChild( button );
29
+
30
+ return button;
31
+ };
32
+
33
+ return [ ...document.querySelectorAll( containerSelector ) ].map( container => addButton( container ) );
34
+ }
35
+
36
+ function addToggleAllListener( { btn, toggleAllButtonSelector = null, targetDetailsSelector } ) {
37
+ let open = false;
38
+
39
+ const targetDetails = [ ...document.querySelectorAll( targetDetailsSelector ) ];
40
+
41
+ let toggleAllButtons = [];
42
+ if ( toggleAllButtonSelector ) {
43
+ toggleAllButtons = [ ...document.querySelectorAll( toggleAllButtonSelector ) ];
44
+ }
45
+
46
+ const onButtonClick = () => {
47
+ open = ! open;
48
+ toggleAllButtons.forEach( toggleAllButton => {
49
+ toggleAllButton.classList.toggle( OPEN_CLASS );
50
+ } );
51
+
52
+ targetDetails.forEach( detail => {
53
+ if ( open ) {
54
+ detail.setAttribute( 'open', true );
55
+ } else {
56
+ detail.removeAttribute( 'open' );
57
+ }
58
+ } );
59
+ };
60
+
61
+ btn.addEventListener( 'click', onButtonClick );
62
+ }
63
+
64
+ /**
65
+ * Adds classes to the rows for the amp_validation_error term list table.
66
+ *
67
+ * This is needed because \WP_Terms_List_Table::single_row() does not allow for additional
68
+ * attributes to be added to the <tr> element.
69
+ */
70
+ function addTermListTableRowClasses() {
71
+ const rows = [ ...document.querySelectorAll( '#the-list tr' ) ];
72
+ rows.forEach( ( row ) => {
73
+ const statusText = row.querySelector( '.column-status > .status-text' );
74
+ if ( statusText ) {
75
+ row.classList.toggle( 'new', statusText.classList.contains( 'new' ) );
76
+ row.classList.toggle( 'accepted', statusText.classList.contains( 'accepted' ) );
77
+ row.classList.toggle( 'rejected', statusText.classList.contains( 'rejected' ) );
78
+ }
79
+ } );
80
+ }
81
+
82
+ domReady( () => {
83
+ addToggleButtons( 'th.column-details.manage-column', detailToggleBtnAriaLabel )
84
+ .forEach( ( btn ) => {
85
+ addToggleAllListener( {
86
+ btn,
87
+ toggleAllButtonSelector: '.column-details button.error-details-toggle',
88
+ targetDetailsSelector: '.column-details details'
89
+ } );
90
+ } );
91
+
92
+ addToggleButtons( 'th.manage-column.column-sources_with_invalid_output', sourcesToggleBtnAriaLabel )
93
+ .forEach( ( btn ) => {
94
+ addToggleAllListener( {
95
+ btn,
96
+ toggleAllButtonSelector: '.column-sources_with_invalid_output button.error-details-toggle',
97
+ targetDetailsSelector: 'details.source'
98
+ } );
99
+ } );
100
+
101
+ addTermListTableRowClasses();
102
+ } );
assets/src/amp-validation-single-error-url-details.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import domReady from '@wordpress/dom-ready';
5
+
6
+ /**
7
+ * Toggles the contents of a details element as an additional table tr.
8
+ */
9
+ class RowToggler {
10
+ constructor( tr, index ) {
11
+ this.tr = tr;
12
+ this.index = index;
13
+
14
+ // Since we're adding additional rows, we need to override default .striped tables styles.
15
+ this.tr.classList.add( this.index % 2 ? 'odd' : 'even' ); // eslint-disable-line no-magic-numbers
16
+
17
+ this.toggle = this.toggle.bind( this );
18
+ }
19
+
20
+ /**
21
+ * Sets up the new tr and adds an event listener to toggle details.
22
+ */
23
+ init() {
24
+ this.details = this.tr.querySelector( '.column-details details' );
25
+ if ( this.details ) {
26
+ this.createNewTr();
27
+ const togglers = [
28
+ ...this.tr.querySelectorAll( '.single-url-detail-toggle' ),
29
+ this.details.querySelector( 'summary' )
30
+ ];
31
+
32
+ togglers.forEach( el => {
33
+ el.addEventListener( 'click', () => {
34
+ this.toggle( el );
35
+ } );
36
+ } );
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Creates the details table row from the original row's <details> element content, minus the summary.
42
+ */
43
+ createNewTr() {
44
+ this.newTr = document.createElement( 'tr' );
45
+ this.newTr.classList.add( 'details' );
46
+ this.newTr.classList.add( this.index % 2 ? 'odd' : 'even' ); // eslint-disable-line no-magic-numbers
47
+
48
+ const newCell = document.createElement( 'td' );
49
+ newCell.setAttribute( 'colspan', this.getRowColspan() );
50
+
51
+ for ( const childNode of this.details.childNodes ) {
52
+ if ( 'SUMMARY' !== childNode.tagName ) {
53
+ newCell.appendChild( childNode.cloneNode( true ) );
54
+ }
55
+ }
56
+
57
+ this.newTr.appendChild( newCell );
58
+ }
59
+
60
+ /**
61
+ * Gets the number of cells within the original row.
62
+ *
63
+ * @return {number} The number of cells.
64
+ */
65
+ getRowColspan() {
66
+ return [ ...this.tr.childNodes ]
67
+ .filter( childNode => [ 'TD', 'TH' ].includes( childNode.tagName ) )
68
+ .length;
69
+ }
70
+
71
+ /**
72
+ * Toggles the additional row.
73
+ *
74
+ * @param {Object} target The click event target.
75
+ */
76
+ toggle( target ) {
77
+ if ( this.tr.classList.contains( 'expanded' ) ) {
78
+ this.onClose( target );
79
+ } else {
80
+ this.onOpen( target );
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Adds the additional row.
86
+ *
87
+ * @param {Object} target The click event target.
88
+ */
89
+ onOpen( target ) {
90
+ this.tr.parentNode.insertBefore( this.newTr, this.tr.nextSibling );
91
+ this.tr.classList.add( 'expanded' );
92
+
93
+ if ( 'SUMMARY' !== target.tagName ) { // This browser will do this if the summary was clicked.
94
+ this.details.setAttribute( 'open', true );
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Removes the additional row.
100
+ *
101
+ * @param {Object} target The click event target.
102
+ */
103
+ onClose( target ) {
104
+ this.tr.parentNode.removeChild( this.newTr );
105
+ this.tr.classList.remove( 'expanded' );
106
+
107
+ if ( 'SUMMARY' !== target.tagName ) {
108
+ this.details.removeAttribute( 'open' );
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Sets up expandable details for errors when viewing a single URL error list.
115
+ */
116
+ class ErrorRows {
117
+ constructor() {
118
+ this.rows = [ ...document.querySelectorAll( '.wp-list-table tr[id^="tag-"]' ) ]
119
+ .map( ( tr, index ) => {
120
+ const rowHandler = new RowToggler( tr, index );
121
+ rowHandler.init();
122
+ return rowHandler;
123
+ } )
124
+ .filter( row => row.details );
125
+ }
126
+
127
+ init() {
128
+ this.addToggleAllListener();
129
+ }
130
+
131
+ /**
132
+ * Handle 'toggle all' buttons on the page.
133
+ */
134
+ addToggleAllListener() {
135
+ let open = false;
136
+ const toggleButtons = [ ...document.querySelectorAll( '.column-details button.error-details-toggle' ) ];
137
+
138
+ const onButtonClick = ( target ) => {
139
+ open = ! open;
140
+ this.rows.forEach( row => {
141
+ if ( open ) {
142
+ row.onOpen( target );
143
+ } else {
144
+ row.onClose( target );
145
+ }
146
+ } );
147
+ };
148
+
149
+ window.addEventListener( 'click', event => {
150
+ if ( toggleButtons.includes( event.target ) ) {
151
+ onButtonClick( event.target );
152
+ }
153
+ } );
154
+ }
155
+ }
156
+
157
+ domReady( () => {
158
+ new ErrorRows().init();
159
+ } );
assets/src/amp-validation-tooltips.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import domReady from '@wordpress/dom-ready';
5
+
6
+ // WIP Pointer function
7
+ function sourcesPointer() {
8
+ jQuery( document ).on( 'click', '.tooltip-button', function() {
9
+ jQuery( this ).pointer( {
10
+ content: jQuery( this ).next( '.tooltip' ).attr( 'data-content' ),
11
+ position: {
12
+ edge: 'left',
13
+ align: 'center'
14
+ },
15
+ pointerClass: 'wp-pointer wp-pointer--tooltip'
16
+ } ).pointer( 'open' );
17
+ } );
18
+ }
19
+
20
+ domReady( () => {
21
+ sourcesPointer();
22
+ } );
back-compat/templates-v0-3/meta-time.php CHANGED
@@ -3,7 +3,8 @@
3
  <?php
4
  echo esc_html(
5
  sprintf(
6
- _x( '%s ago', '%s = human-readable time difference', 'amp' ),
 
7
  human_time_diff( $this->get( 'post_publish_timestamp' ), current_time( 'timestamp' ) )
8
  )
9
  );
3
  <?php
4
  echo esc_html(
5
  sprintf(
6
+ /* translators: %s: the human-readable time difference. */
7
+ __( '%s ago', 'amp' ),
8
  human_time_diff( $this->get( 'post_publish_timestamp' ), current_time( 'timestamp' ) )
9
  )
10
  );
includes/admin/class-amp-admin-pointer.php ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Admin pointer class.
4
+ *
5
+ * @package AMP
6
+ * @since 1.0
7
+ */
8
+
9
+ /**
10
+ * AMP_Admin_Pointer class.
11
+ *
12
+ * Outputs an admin pointer to show the new features of v1.0.
13
+ * Based on https://code.tutsplus.com/articles/integrating-with-wordpress-ui-admin-pointers--wp-26853
14
+ *
15
+ * @since 1.0
16
+ */
17
+ class AMP_Admin_Pointer {
18
+
19
+ /**
20
+ * The ID of the template mode admin pointer.
21
+ *
22
+ * @var string
23
+ */
24
+ const TEMPLATE_POINTER_ID = 'amp_template_mode_pointer_10';
25
+
26
+ /**
27
+ * The slug of the script.
28
+ *
29
+ * @var string
30
+ */
31
+ const SCRIPT_SLUG = 'amp-admin-pointer';
32
+
33
+ /**
34
+ * The slug of the tooltip script.
35
+ *
36
+ * @var string
37
+ */
38
+ const TOOLTIP_SLUG = 'amp-validation-tooltips';
39
+
40
+ /**
41
+ * Initializes the class.
42
+ *
43
+ * @since 1.0
44
+ */
45
+ public function init() {
46
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_pointer' ) );
47
+ add_action( 'admin_enqueue_scripts', array( $this, 'register_tooltips' ) );
48
+ }
49
+
50
+ /**
51
+ * Enqueues the pointer assets.
52
+ *
53
+ * If the pointer has not been dismissed, enqueues the style and script.
54
+ * And outputs the pointer data for the script.
55
+ *
56
+ * @since 1.0
57
+ */
58
+ public function enqueue_pointer() {
59
+ if ( $this->is_pointer_dismissed() ) {
60
+ return;
61
+ }
62
+
63
+ wp_enqueue_style( 'wp-pointer' );
64
+
65
+ wp_enqueue_script(
66
+ self::SCRIPT_SLUG,
67
+ amp_get_asset_url( 'js/' . self::SCRIPT_SLUG . '.js' ),
68
+ array( 'jquery', 'wp-pointer' ),
69
+ AMP__VERSION,
70
+ true
71
+ );
72
+
73
+ wp_add_inline_script(
74
+ self::SCRIPT_SLUG,
75
+ sprintf( 'ampAdminPointer.load( %s );', wp_json_encode( $this->get_pointer_data() ) )
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Registers style and script for tooltips.
81
+ *
82
+ * @since 1.0
83
+ */
84
+ public function register_tooltips() {
85
+ wp_register_style(
86
+ self::TOOLTIP_SLUG,
87
+ amp_get_asset_url( 'css/' . self::TOOLTIP_SLUG . '.css' ),
88
+ array( 'wp-pointer' ),
89
+ AMP__VERSION
90
+ );
91
+
92
+ wp_register_script(
93
+ self::TOOLTIP_SLUG,
94
+ amp_get_asset_url( 'js/' . self::TOOLTIP_SLUG . '-compiled.js' ),
95
+ array( 'jquery', 'wp-pointer' ),
96
+ AMP__VERSION,
97
+ true
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Whether the AMP admin pointer has been dismissed.
103
+ *
104
+ * @since 1.0
105
+ * @return boolean Is dismissed.
106
+ */
107
+ protected function is_pointer_dismissed() {
108
+ $dismissed = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true );
109
+ if ( empty( $dismissed ) ) {
110
+ return false;
111
+ }
112
+ $dismissed = explode( ',', strval( $dismissed ) );
113
+
114
+ return in_array( self::TEMPLATE_POINTER_ID, $dismissed, true );
115
+ }
116
+
117
+ /**
118
+ * Gets the pointer data to pass to the script.
119
+ *
120
+ * @since 1.0
121
+ * @return array Pointer data.
122
+ */
123
+ public function get_pointer_data() {
124
+ return array(
125
+ 'pointer' => array(
126
+ 'pointer_id' => self::TEMPLATE_POINTER_ID,
127
+ 'target' => '#toplevel_page_amp-options',
128
+ 'options' => array(
129
+ 'content' => sprintf(
130
+ '<h3>%s</h3><p><strong>%s</strong></p><p>%s</p>',
131
+ __( 'AMP', 'amp' ),
132
+ __( 'New AMP Template Modes', 'amp' ),
133
+ __( 'You can now reuse your theme\'s templates and styles in AMP responses, in both &#8220;Paired&#8221; and &#8220;Native&#8221; modes.', 'amp' )
134
+ ),
135
+ 'position' => array(
136
+ 'edge' => 'left',
137
+ 'align' => 'middle',
138
+ ),
139
+ ),
140
+ ),
141
+ );
142
+ }
143
+ }
includes/admin/class-amp-customizer.php CHANGED
@@ -124,30 +124,32 @@ class AMP_Template_Customizer {
124
  * @since 0.6
125
  */
126
  public function add_customizer_scripts() {
127
- wp_enqueue_script(
128
- 'amp-customize-controls',
129
- amp_get_asset_url( 'js/amp-customize-controls.js' ),
130
- array( 'jquery', 'customize-controls' ),
131
- AMP__VERSION,
132
- true
133
- );
 
134
 
135
- wp_add_inline_script( 'amp-customize-controls', sprintf( 'ampCustomizeControls.boot( %s );',
136
- wp_json_encode( array(
137
- 'queryVar' => amp_get_slug(),
138
- 'panelId' => self::PANEL_ID,
139
- 'ampUrl' => amp_admin_get_preview_permalink(),
140
- 'l10n' => array(
141
- 'unavailableMessage' => __( 'AMP is not available for the page currently being previewed.', 'amp' ),
142
- 'unavailableLinkText' => __( 'Navigate to an AMP compatible page', 'amp' ),
143
- ),
144
- ) )
145
- ) );
146
 
147
- wp_enqueue_style(
148
- 'amp-customizer',
149
- amp_get_asset_url( 'css/amp-customizer.css' )
150
- );
 
151
 
152
  /**
153
  * Fires when plugins should register settings for AMP.
@@ -164,8 +166,10 @@ class AMP_Template_Customizer {
164
  * Enqueues scripts used in both the AMP and non-AMP Customizer preview.
165
  *
166
  * @since 0.6
 
167
  */
168
  public function enqueue_preview_scripts() {
 
169
 
170
  // Bail if user can't customize anyway.
171
  if ( ! current_user_can( 'customize' ) ) {
@@ -180,9 +184,24 @@ class AMP_Template_Customizer {
180
  true
181
  );
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  wp_add_inline_script( 'amp-customize-preview', sprintf( 'ampCustomizePreview.boot( %s );',
184
  wp_json_encode( array(
185
- 'available' => (bool) is_singular() && post_supports_amp( get_queried_object() ),
186
  'enabled' => is_amp_endpoint(),
187
  ) )
188
  ) );
124
  * @since 0.6
125
  */
126
  public function add_customizer_scripts() {
127
+ if ( ! amp_is_canonical() ) {
128
+ wp_enqueue_script(
129
+ 'amp-customize-controls',
130
+ amp_get_asset_url( 'js/amp-customize-controls.js' ),
131
+ array( 'jquery', 'customize-controls' ),
132
+ AMP__VERSION,
133
+ true
134
+ );
135
 
136
+ wp_add_inline_script( 'amp-customize-controls', sprintf( 'ampCustomizeControls.boot( %s );',
137
+ wp_json_encode( array(
138
+ 'queryVar' => amp_get_slug(),
139
+ 'panelId' => self::PANEL_ID,
140
+ 'ampUrl' => amp_admin_get_preview_permalink(),
141
+ 'l10n' => array(
142
+ 'unavailableMessage' => __( 'AMP is not available for the page currently being previewed.', 'amp' ),
143
+ 'unavailableLinkText' => __( 'Navigate to an AMP compatible page', 'amp' ),
144
+ ),
145
+ ) )
146
+ ) );
147
 
148
+ wp_enqueue_style(
149
+ 'amp-customizer',
150
+ amp_get_asset_url( 'css/amp-customizer.css' )
151
+ );
152
+ }
153
 
154
  /**
155
  * Fires when plugins should register settings for AMP.
166
  * Enqueues scripts used in both the AMP and non-AMP Customizer preview.
167
  *
168
  * @since 0.6
169
+ * @global WP_Query $wp_query
170
  */
171
  public function enqueue_preview_scripts() {
172
+ global $wp_query;
173
 
174
  // Bail if user can't customize anyway.
175
  if ( ! current_user_can( 'customize' ) ) {
184
  true
185
  );
186
 
187
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
188
+ $availability = AMP_Theme_Support::get_template_availability();
189
+ $available = $availability['supported'];
190
+ } elseif ( is_singular() || $wp_query->is_posts_page ) {
191
+ /**
192
+ * Queried object.
193
+ *
194
+ * @var WP_Post $queried_object
195
+ */
196
+ $queried_object = get_queried_object();
197
+ $available = post_supports_amp( $queried_object );
198
+ } else {
199
+ $available = false;
200
+ }
201
+
202
  wp_add_inline_script( 'amp-customize-preview', sprintf( 'ampCustomizePreview.boot( %s );',
203
  wp_json_encode( array(
204
+ 'available' => $available,
205
  'enabled' => is_amp_endpoint(),
206
  ) )
207
  ) );
includes/admin/class-amp-editor-blocks.php ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * AMP Editor Blocks extending.
4
+ *
5
+ * @package AMP
6
+ * @since 1.0
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Editor_Blocks
11
+ */
12
+ class AMP_Editor_Blocks {
13
+
14
+ /**
15
+ * List of AMP scripts that need to be printed when AMP components are used in non-AMP document context ("dirty AMP").
16
+ *
17
+ * @var array
18
+ */
19
+ public $content_required_amp_scripts = array();
20
+
21
+ /**
22
+ * AMP components that have blocks.
23
+ *
24
+ * @var array
25
+ */
26
+ public $amp_blocks = array(
27
+ 'amp-mathml',
28
+ 'amp-timeago',
29
+ 'amp-o2-player',
30
+ 'amp-ooyala-player',
31
+ 'amp-reach-player',
32
+ 'amp-springboard-player',
33
+ 'amp-jwplayer',
34
+ 'amp-brid-player',
35
+ 'amp-ima-video',
36
+ 'amp-fit-text',
37
+ );
38
+
39
+ /**
40
+ * Init.
41
+ */
42
+ public function init() {
43
+ if ( function_exists( 'register_block_type' ) ) {
44
+ add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
45
+ add_filter( 'wp_kses_allowed_html', array( $this, 'whitelist_block_atts_in_wp_kses_allowed_html' ), 10, 2 );
46
+
47
+ /*
48
+ * Dirty AMP is required when a site is in native mode but not all templates are being served
49
+ * as AMP. In particular, if a single post is using AMP-specific Gutenberg Blocks which make
50
+ * use of AMP components, and the singular template is served as AMP but the blog page is not,
51
+ * then the non-AMP blog page need to load the AMP runtime scripts so that the AMP components
52
+ * in the posts displayed there will be rendered properly. This is only relevant on native AMP
53
+ * sites because the AMP Gutenberg blocks are only made available in that mode; they are not
54
+ * presented in the Gutenberg inserter in paired mode. In general, using AMP components in
55
+ * non-AMP documents is still not officially supported, so it's occurrence is being minimized
56
+ * as much as possible. For more, see <https://github.com/ampproject/amp-wp/issues/1192>.
57
+ */
58
+ if ( amp_is_canonical() ) {
59
+ add_filter( 'the_content', array( $this, 'tally_content_requiring_amp_scripts' ) );
60
+ add_action( 'wp_print_footer_scripts', array( $this, 'print_dirty_amp_scripts' ) );
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Whitelist elements and attributes used for AMP.
67
+ *
68
+ * This prevents AMP markup from being deleted in
69
+ *
70
+ * @param array $tags Array of allowed post tags.
71
+ * @param string $context Context.
72
+ * @return mixed Modified array.
73
+ */
74
+ public function whitelist_block_atts_in_wp_kses_allowed_html( $tags, $context ) {
75
+ if ( 'post' !== $context ) {
76
+ return $tags;
77
+ }
78
+
79
+ foreach ( $tags as &$tag ) {
80
+ $tag['data-amp-layout'] = true;
81
+ $tag['data-amp-noloading'] = true;
82
+ $tag['data-amp-lightbox'] = true;
83
+ $tag['data-close-button-aria-label'] = true;
84
+ }
85
+
86
+ foreach ( $this->amp_blocks as $amp_block ) {
87
+ if ( ! isset( $tags[ $amp_block ] ) ) {
88
+ $tags[ $amp_block ] = array();
89
+ }
90
+
91
+ // @todo The global attributes included here should be matched up with what is actually used by each block.
92
+ $tags[ $amp_block ] = array_merge(
93
+ array_fill_keys(
94
+ array(
95
+ 'layout',
96
+ 'width',
97
+ 'height',
98
+ 'class',
99
+ ),
100
+ true
101
+ ),
102
+ $tags[ $amp_block ]
103
+ );
104
+
105
+ $amp_tag_specs = AMP_Allowed_Tags_Generated::get_allowed_tag( $amp_block );
106
+ foreach ( $amp_tag_specs as $amp_tag_spec ) {
107
+ if ( ! isset( $amp_tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] ) ) {
108
+ continue;
109
+ }
110
+ $tags[ $amp_block ] = array_merge(
111
+ $tags[ $amp_block ],
112
+ array_fill_keys( array_keys( $amp_tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] ), true )
113
+ );
114
+ }
115
+ }
116
+
117
+ return $tags;
118
+ }
119
+
120
+ /**
121
+ * Enqueue filters for extending core blocks attributes.
122
+ * Has to be loaded before registering the blocks in registerCoreBlocks.
123
+ */
124
+ public function enqueue_block_editor_assets() {
125
+
126
+ // Enqueue script and style for AMP-specific blocks.
127
+ if ( amp_is_canonical() ) {
128
+ wp_enqueue_style(
129
+ 'amp-editor-blocks-style',
130
+ amp_get_asset_url( 'css/amp-editor-blocks.css' ),
131
+ array(),
132
+ AMP__VERSION
133
+ );
134
+
135
+ wp_enqueue_script(
136
+ 'amp-editor-blocks-build',
137
+ amp_get_asset_url( 'js/amp-blocks-compiled.js' ),
138
+ array( 'wp-editor', 'wp-blocks', 'lodash', 'wp-i18n', 'wp-element', 'wp-components' ),
139
+ AMP__VERSION
140
+ );
141
+
142
+ if ( function_exists( 'wp_set_script_translations' ) ) {
143
+ wp_set_script_translations( 'amp-editor-blocks-build', 'amp' );
144
+ }
145
+ }
146
+
147
+ wp_enqueue_script(
148
+ 'amp-editor-blocks',
149
+ amp_get_asset_url( 'js/amp-editor-blocks.js' ),
150
+ array( 'underscore', 'wp-hooks', 'wp-i18n', 'wp-components' ),
151
+ AMP__VERSION,
152
+ true
153
+ );
154
+
155
+ wp_add_inline_script(
156
+ 'amp-editor-blocks',
157
+ sprintf( 'ampEditorBlocks.boot( %s );', wp_json_encode( array(
158
+ 'hasThemeSupport' => current_theme_supports( AMP_Theme_Support::SLUG ),
159
+ ) ) )
160
+ );
161
+
162
+ if ( function_exists( 'wp_set_script_translations' ) ) {
163
+ wp_set_script_translations( 'amp-editor-blocks', 'amp' );
164
+ } elseif ( function_exists( 'wp_get_jed_locale_data' ) || function_exists( 'gutenberg_get_jed_locale_data' ) ) {
165
+ $locale_data = function_exists( 'wp_get_jed_locale_data' ) ? wp_get_jed_locale_data( 'amp' ) : gutenberg_get_jed_locale_data( 'amp' );
166
+ wp_add_inline_script(
167
+ 'wp-i18n',
168
+ 'wp.i18n.setLocaleData( ' . wp_json_encode( $locale_data ) . ', "amp" );',
169
+ 'after'
170
+ );
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Tally the AMP component scripts that are needed in a dirty AMP document.
176
+ *
177
+ * @param string $content Content.
178
+ * @return string Content (unmodified).
179
+ */
180
+ public function tally_content_requiring_amp_scripts( $content ) {
181
+ if ( ! is_amp_endpoint() ) {
182
+ $pattern = sprintf( '/<(%s)\b.*?>/s', join( '|', $this->amp_blocks ) );
183
+ if ( preg_match_all( $pattern, $content, $matches ) ) {
184
+ $this->content_required_amp_scripts = array_merge(
185
+ $this->content_required_amp_scripts,
186
+ $matches[1]
187
+ );
188
+ }
189
+ }
190
+ return $content;
191
+ }
192
+
193
+ /**
194
+ * Print AMP scripts required for AMP components used in a non-AMP document (dirty AMP).
195
+ */
196
+ public function print_dirty_amp_scripts() {
197
+ if ( ! is_amp_endpoint() && ! empty( $this->content_required_amp_scripts ) ) {
198
+ wp_scripts()->do_items( $this->content_required_amp_scripts );
199
+ }
200
+ }
201
+ }
includes/admin/class-amp-post-meta-box.php CHANGED
@@ -21,6 +21,14 @@ class AMP_Post_Meta_Box {
21
  */
22
  const ASSETS_HANDLE = 'amp-post-meta-box';
23
 
 
 
 
 
 
 
 
 
24
  /**
25
  * The enabled status post meta value.
26
  *
@@ -84,6 +92,7 @@ class AMP_Post_Meta_Box {
84
  ) );
85
 
86
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
 
87
  add_action( 'post_submitbox_misc_actions', array( $this, 'render_status' ) );
88
  add_action( 'save_post', array( $this, 'save_amp_status' ) );
89
  add_filter( 'preview_post_link', array( $this, 'preview_post_link' ) );
@@ -143,12 +152,20 @@ class AMP_Post_Meta_Box {
143
  array( 'jquery' ),
144
  AMP__VERSION
145
  );
 
 
 
 
 
 
 
 
146
  wp_add_inline_script( self::ASSETS_HANDLE, sprintf( 'ampPostMetaBox.boot( %s );',
147
  wp_json_encode( array(
148
  'previewLink' => esc_url_raw( add_query_arg( amp_get_slug(), '', get_preview_post_link( $post ) ) ),
149
  'canonical' => amp_is_canonical(),
150
- 'enabled' => post_supports_amp( $post ),
151
- 'canSupport' => count( AMP_Post_Type_Support::get_support_errors( $post ) ) === 0,
152
  'statusInputName' => self::STATUS_INPUT_NAME,
153
  'l10n' => array(
154
  'ampPreviewBtnLabel' => __( 'Preview changes in AMP (opens in new window)', 'amp' ),
@@ -157,6 +174,49 @@ class AMP_Post_Meta_Box {
157
  ) );
158
  }
159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  /**
161
  * Render AMP status.
162
  *
@@ -170,23 +230,102 @@ class AMP_Post_Meta_Box {
170
  is_post_type_viewable( $post->post_type )
171
  &&
172
  current_user_can( 'edit_post', $post->ID )
173
- &&
174
- ! amp_is_canonical()
175
  );
176
 
177
  if ( true !== $verify ) {
178
  return;
179
  }
180
 
181
- $errors = AMP_Post_Type_Support::get_support_errors( $post );
182
- $status = post_supports_amp( $post ) ? self::ENABLED_STATUS : self::DISABLED_STATUS;
 
 
 
183
  $labels = array(
184
  'enabled' => __( 'Enabled', 'amp' ),
185
  'disabled' => __( 'Disabled', 'amp' ),
186
  );
187
 
188
  // The preceding variables are used inside the following amp-status.php template.
189
- include_once AMP__DIR__ . '/templates/admin/amp-status.php';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
 
192
  /**
21
  */
22
  const ASSETS_HANDLE = 'amp-post-meta-box';
23
 
24
+ /**
25
+ * Block asset handle.
26
+ *
27
+ * @since 1.0
28
+ * @var string
29
+ */
30
+ const BLOCK_ASSET_HANDLE = 'amp-block-editor-toggle-compiled';
31
+
32
  /**
33
  * The enabled status post meta value.
34
  *
92
  ) );
93
 
94
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
95
+ add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_assets' ) );
96
  add_action( 'post_submitbox_misc_actions', array( $this, 'render_status' ) );
97
  add_action( 'save_post', array( $this, 'save_amp_status' ) );
98
  add_filter( 'preview_post_link', array( $this, 'preview_post_link' ) );
152
  array( 'jquery' ),
153
  AMP__VERSION
154
  );
155
+
156
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
157
+ $availability = AMP_Theme_Support::get_template_availability( $post );
158
+ $support_errors = $availability['errors'];
159
+ } else {
160
+ $support_errors = AMP_Post_Type_Support::get_support_errors( $post );
161
+ }
162
+
163
  wp_add_inline_script( self::ASSETS_HANDLE, sprintf( 'ampPostMetaBox.boot( %s );',
164
  wp_json_encode( array(
165
  'previewLink' => esc_url_raw( add_query_arg( amp_get_slug(), '', get_preview_post_link( $post ) ) ),
166
  'canonical' => amp_is_canonical(),
167
+ 'enabled' => empty( $support_errors ),
168
+ 'canSupport' => 0 === count( array_diff( $support_errors, array( 'post-status-disabled' ) ) ),
169
  'statusInputName' => self::STATUS_INPUT_NAME,
170
  'l10n' => array(
171
  'ampPreviewBtnLabel' => __( 'Preview changes in AMP (opens in new window)', 'amp' ),
174
  ) );
175
  }
176
 
177
+ /**
178
+ * Enqueues block assets.
179
+ *
180
+ * @since 1.0
181
+ */
182
+ public function enqueue_block_assets() {
183
+ $post = get_post();
184
+ if ( ! is_post_type_viewable( $post->post_type ) ) {
185
+ return;
186
+ }
187
+
188
+ wp_enqueue_script(
189
+ self::BLOCK_ASSET_HANDLE,
190
+ amp_get_asset_url( 'js/' . self::BLOCK_ASSET_HANDLE . '.js' ),
191
+ array( 'wp-hooks', 'wp-i18n', 'wp-components' ),
192
+ AMP__VERSION,
193
+ true
194
+ );
195
+
196
+ $status_and_errors = $this->get_status_and_errors( $post );
197
+ $enabled_status = $status_and_errors['status'];
198
+ $error_messages = $this->get_error_messages( $status_and_errors['status'], $status_and_errors['errors'] );
199
+ $script_data = array(
200
+ 'possibleStati' => array( self::ENABLED_STATUS, self::DISABLED_STATUS ),
201
+ 'defaultStatus' => $enabled_status,
202
+ 'errorMessages' => $error_messages,
203
+ );
204
+
205
+ if ( function_exists( 'wp_set_script_translations' ) ) {
206
+ wp_set_script_translations( self::BLOCK_ASSET_HANDLE, 'amp' );
207
+ } elseif ( function_exists( 'wp_get_jed_locale_data' ) ) {
208
+ $script_data['i18n'] = wp_get_jed_locale_data( 'amp' );
209
+ } elseif ( function_exists( 'gutenberg_get_jed_locale_data' ) ) {
210
+ $script_data['i18n'] = gutenberg_get_jed_locale_data( 'amp' );
211
+ }
212
+
213
+ wp_add_inline_script(
214
+ self::BLOCK_ASSET_HANDLE,
215
+ sprintf( 'var wpAmpEditor = %s;', wp_json_encode( $script_data ) ),
216
+ 'before'
217
+ );
218
+ }
219
+
220
  /**
221
  * Render AMP status.
222
  *
230
  is_post_type_viewable( $post->post_type )
231
  &&
232
  current_user_can( 'edit_post', $post->ID )
 
 
233
  );
234
 
235
  if ( true !== $verify ) {
236
  return;
237
  }
238
 
239
+ $status_and_errors = $this->get_status_and_errors( $post );
240
+ $status = $status_and_errors['status'];
241
+ $errors = $status_and_errors['errors'];
242
+ $error_messages = $this->get_error_messages( $status, $errors );
243
+
244
  $labels = array(
245
  'enabled' => __( 'Enabled', 'amp' ),
246
  'disabled' => __( 'Disabled', 'amp' ),
247
  );
248
 
249
  // The preceding variables are used inside the following amp-status.php template.
250
+ include AMP__DIR__ . '/templates/admin/amp-status.php';
251
+ }
252
+
253
+ /**
254
+ * Gets the AMP enabled status and errors.
255
+ *
256
+ * @since 1.0
257
+ * @param WP_Post $post The post to check.
258
+ * @return array {
259
+ * The status and errors.
260
+ *
261
+ * @type string $status The AMP enabled status.
262
+ * @type string[] $errors AMP errors.
263
+ * }
264
+ */
265
+ public function get_status_and_errors( $post ) {
266
+ /*
267
+ * When theme support is present then theme templates can be served in AMP and we check first if the template is available.
268
+ * Checking for template availability will include a check for get_support_errors. Otherwise, if theme support is not present
269
+ * then we just check get_support_errors.
270
+ */
271
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
272
+ $availability = AMP_Theme_Support::get_template_availability( $post );
273
+ $status = $availability['supported'] ? self::ENABLED_STATUS : self::DISABLED_STATUS;
274
+ $errors = array_diff( $availability['errors'], array( 'post-status-disabled' ) ); // Subtract the status which the metabox will allow to be toggled.
275
+ if ( true === $availability['immutable'] ) {
276
+ $errors[] = 'status_immutable';
277
+ }
278
+ } else {
279
+ $errors = AMP_Post_Type_Support::get_support_errors( $post );
280
+ $status = empty( $errors ) ? self::ENABLED_STATUS : self::DISABLED_STATUS;
281
+ $errors = array_diff( $errors, array( 'post-status-disabled' ) ); // Subtract the status which the metabox will allow to be toggled.
282
+ }
283
+
284
+ return compact( 'status', 'errors' );
285
+ }
286
+
287
+ /**
288
+ * Gets the AMP enabled error message(s).
289
+ *
290
+ * @since 1.0
291
+ * @param string $status The AMP enabled status.
292
+ * @param array $errors The AMP enabled errors.
293
+ * @return array $error_messages The error messages, as an array of strings.
294
+ */
295
+ public function get_error_messages( $status, $errors ) {
296
+ $error_messages = array();
297
+ if ( in_array( 'status_immutable', $errors, true ) ) {
298
+ if ( self::ENABLED_STATUS === $status ) {
299
+ $error_messages[] = __( 'Your site does not allow AMP to be disabled.', 'amp' );
300
+ } else {
301
+ $error_messages[] = __( 'Your site does not allow AMP to be enabled.', 'amp' );
302
+ }
303
+ }
304
+ if ( in_array( 'template_unsupported', $errors, true ) || in_array( 'no_matching_template', $errors, true ) ) {
305
+ $error_messages[] = sprintf(
306
+ /* translators: %s is a link to the AMP settings screen */
307
+ __( 'There are no <a href="%s">supported templates</a> to display this in AMP.', 'amp' ),
308
+ esc_url( admin_url( 'admin.php?page=' . AMP_Options_Manager::OPTION_NAME ) )
309
+ );
310
+ }
311
+ if ( in_array( 'password-protected', $errors, true ) ) {
312
+ $error_messages[] = __( 'AMP cannot be enabled on password protected posts.', 'amp' );
313
+ }
314
+ if ( in_array( 'post-type-support', $errors, true ) ) {
315
+ $error_messages[] = sprintf(
316
+ /* translators: %s is a link to the AMP settings screen */
317
+ __( 'AMP cannot be enabled because this <a href="%s">post type does not support it</a>.', 'amp' ),
318
+ esc_url( admin_url( 'admin.php?page=' . AMP_Options_Manager::OPTION_NAME ) )
319
+ );
320
+ }
321
+ if ( in_array( 'skip-post', $errors, true ) ) {
322
+ $error_messages[] = __( 'A plugin or theme has disabled AMP support.', 'amp' );
323
+ }
324
+ if ( count( array_diff( $errors, array( 'status_immutable', 'page-on-front', 'page-for-posts', 'password-protected', 'post-type-support', 'skip-post', 'template_unsupported', 'no_matching_template' ) ) ) > 0 ) {
325
+ $error_messages[] = __( 'Unavailable for an unknown reason.', 'amp' );
326
+ }
327
+
328
+ return $error_messages;
329
  }
330
 
331
  /**
includes/admin/functions.php CHANGED
@@ -38,7 +38,7 @@ function amp_init_customizer() {
38
  /**
39
  * Get permalink for the first AMP-eligible post.
40
  *
41
- * @return string|null
42
  */
43
  function amp_admin_get_preview_permalink() {
44
  /**
@@ -48,16 +48,34 @@ function amp_admin_get_preview_permalink() {
48
  */
49
  $post_type = (string) apply_filters( 'amp_customizer_post_type', 'post' );
50
 
51
- if ( ! post_type_supports( $post_type, amp_get_slug() ) ) {
 
 
 
 
 
 
 
52
  return null;
53
  }
54
 
 
 
 
 
 
 
 
 
55
  $post_ids = get_posts( array(
56
- 'post_status' => 'publish',
57
- 'post_password' => '',
58
- 'post_type' => $post_type,
59
- 'posts_per_page' => 1,
60
- 'fields' => 'ids',
 
 
 
61
  ) );
62
 
63
  if ( empty( $post_ids ) ) {
@@ -73,6 +91,11 @@ function amp_admin_get_preview_permalink() {
73
  * Registers a submenu page to access the AMP template editor panel in the Customizer.
74
  */
75
  function amp_add_customizer_link() {
 
 
 
 
 
76
  $menu_slug = add_query_arg( array(
77
  'autofocus[panel]' => AMP_Template_Customizer::PANEL_ID,
78
  'url' => rawurlencode( amp_admin_get_preview_permalink() ),
@@ -153,3 +176,21 @@ function amp_post_meta_box() {
153
  $post_meta_box = new AMP_Post_Meta_Box();
154
  $post_meta_box->init();
155
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Get permalink for the first AMP-eligible post.
40
  *
41
+ * @return string|null URL on success, null if none found.
42
  */
43
  function amp_admin_get_preview_permalink() {
44
  /**
48
  */
49
  $post_type = (string) apply_filters( 'amp_customizer_post_type', 'post' );
50
 
51
+ // Make sure the desired post type is actually supported, and if so, prefer it.
52
+ $supported_post_types = get_post_types_by_support( AMP_Post_Type_Support::SLUG );
53
+ if ( in_array( $post_type, $supported_post_types, true ) ) {
54
+ $supported_post_types = array_unique( array_merge( array( $post_type ), $supported_post_types ) );
55
+ }
56
+
57
+ // Bail if there are no supported post types.
58
+ if ( empty( $supported_post_types ) ) {
59
  return null;
60
  }
61
 
62
+ // If theme support is present, then bail if the singular template is not supported.
63
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
64
+ $supported_templates = AMP_Theme_Support::get_supportable_templates();
65
+ if ( empty( $supported_templates['is_singular']['supported'] ) ) {
66
+ return null;
67
+ }
68
+ }
69
+
70
  $post_ids = get_posts( array(
71
+ 'no_found_rows' => true,
72
+ 'suppress_filters' => false,
73
+ 'post_status' => 'publish',
74
+ 'post_password' => '',
75
+ 'post_type' => $supported_post_types,
76
+ 'posts_per_page' => 1,
77
+ 'fields' => 'ids',
78
+ // @todo This should eventually do a meta_query to make sure there are none that have AMP_Post_Meta_Box::STATUS_POST_META_KEY = DISABLED_STATUS.
79
  ) );
80
 
81
  if ( empty( $post_ids ) ) {
91
  * Registers a submenu page to access the AMP template editor panel in the Customizer.
92
  */
93
  function amp_add_customizer_link() {
94
+ /** This filter is documented in includes/settings/class-amp-customizer-design-settings.php */
95
+ if ( ! apply_filters( 'amp_customizer_is_enabled', true ) || current_theme_supports( AMP_Theme_Support::SLUG ) ) {
96
+ return;
97
+ }
98
+
99
  $menu_slug = add_query_arg( array(
100
  'autofocus[panel]' => AMP_Template_Customizer::PANEL_ID,
101
  'url' => rawurlencode( amp_admin_get_preview_permalink() ),
176
  $post_meta_box = new AMP_Post_Meta_Box();
177
  $post_meta_box->init();
178
  }
179
+
180
+ /**
181
+ * Bootstrap AMP Editor core blocks.
182
+ */
183
+ function amp_editor_core_blocks() {
184
+ $editor_blocks = new AMP_Editor_Blocks();
185
+ $editor_blocks->init();
186
+ }
187
+
188
+ /**
189
+ * Bootstrap the AMP admin pointer class.
190
+ *
191
+ * @since 1.0
192
+ */
193
+ function amp_admin_pointer() {
194
+ $admin_pointer = new AMP_Admin_Pointer();
195
+ $admin_pointer->init();
196
+ }
includes/amp-frontend-actions.php CHANGED
@@ -2,39 +2,22 @@
2
  /**
3
  * Callbacks for adding AMP-related things to the main theme.
4
  *
 
5
  * @package AMP
6
  */
7
 
8
- add_action( 'wp_head', 'amp_frontend_add_canonical' );
9
 
10
  /**
11
  * Add amphtml link to frontend.
12
  *
13
- * @todo This function's name is incorrect. It's not about adding a canonical link but adding the amphtml link.
14
  *
15
  * @since 0.2
 
 
16
  */
17
  function amp_frontend_add_canonical() {
18
-
19
- /**
20
- * Filters whether to show the amphtml link on the frontend.
21
- *
22
- * @todo This filter's name is incorrect. It's not about adding a canonical link but adding the amphtml link.
23
- * @since 0.2
24
- */
25
- if ( false === apply_filters( 'amp_frontend_show_canonical', true ) ) {
26
- return;
27
- }
28
-
29
- $amp_url = null;
30
- if ( is_singular() ) {
31
- $amp_url = amp_get_permalink( get_queried_object_id() );
32
- } elseif ( isset( $_SERVER['REQUEST_URI'] ) ) {
33
- $host_url = preg_replace( '#(^https?://[^/]+)/.*#', '$1', home_url( '/' ) );
34
- $self_url = esc_url_raw( $host_url . wp_unslash( $_SERVER['REQUEST_URI'] ) );
35
- $amp_url = add_query_arg( amp_get_slug(), '', $self_url );
36
- }
37
- if ( $amp_url ) {
38
- printf( '<link rel="amphtml" href="%s">', esc_url( $amp_url ) );
39
- }
40
  }
2
  /**
3
  * Callbacks for adding AMP-related things to the main theme.
4
  *
5
+ * @deprecated Function in this file has been moved to amp-helper-functions.php.
6
  * @package AMP
7
  */
8
 
9
+ _deprecated_file( __FILE__, '1.0', null, esc_html__( 'Use amp_add_amphtml_link() function which is already included from amp-helper-functions.php', 'amp' ) );
10
 
11
  /**
12
  * Add amphtml link to frontend.
13
  *
14
+ * @deprecated
15
  *
16
  * @since 0.2
17
+ * @since 1.0 Deprecated
18
+ * @see amp_add_amphtml_link()
19
  */
20
  function amp_frontend_add_canonical() {
21
+ _deprecated_function( __FUNCTION__, '1.0', 'amp_add_amphtml_link' );
22
+ amp_add_amphtml_link();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
includes/amp-helper-functions.php CHANGED
@@ -13,6 +13,7 @@
13
  * may be deprecated in the future. Normally the slug should be just 'amp'.
14
  *
15
  * @since 0.7
 
16
  * @return string Slug used for query var, endpoint, and post type support.
17
  */
18
  function amp_get_slug() {
@@ -26,6 +27,7 @@ function amp_get_slug() {
26
  * Warning: This filter may become deprecated.
27
  *
28
  * @since 0.3.2
 
29
  * @param string $query_var The AMP query variable.
30
  */
31
  $query_var = apply_filters( 'amp_query_var', 'amp' );
@@ -35,23 +37,52 @@ function amp_get_slug() {
35
  return $query_var;
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Retrieves the full AMP-specific permalink for the given post ID.
40
  *
41
  * @since 0.1
42
  *
43
  * @param int $post_id Post ID.
44
- *
45
  * @return string AMP permalink.
46
  */
47
  function amp_get_permalink( $post_id ) {
48
 
 
 
 
 
 
 
 
 
 
49
  /**
50
  * Filters the AMP permalink to short-circuit normal generation.
51
  *
52
  * Returning a non-false value in this filter will cause the `get_permalink()` to get called and the `amp_get_permalink` filter to not apply.
53
  *
54
  * @since 0.4
 
55
  *
56
  * @param false $url Short-circuited URL.
57
  * @param int $post_id Post ID.
@@ -62,15 +93,34 @@ function amp_get_permalink( $post_id ) {
62
  return $pre_url;
63
  }
64
 
 
 
65
  if ( amp_is_canonical() ) {
66
- $amp_url = get_permalink( $post_id );
67
  } else {
68
- $parsed_url = wp_parse_url( get_permalink( $post_id ) );
69
- $structure = get_option( 'permalink_structure' );
70
- if ( empty( $structure ) || ! empty( $parsed_url['query'] ) || is_post_type_hierarchical( get_post_type( $post_id ) ) ) {
71
- $amp_url = add_query_arg( amp_get_slug(), '', get_permalink( $post_id ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  } else {
73
- $amp_url = trailingslashit( get_permalink( $post_id ) ) . user_trailingslashit( amp_get_slug(), 'single_amp' );
 
 
 
 
74
  }
75
  }
76
 
@@ -78,6 +128,7 @@ function amp_get_permalink( $post_id ) {
78
  * Filters AMP permalink.
79
  *
80
  * @since 0.2
 
81
  *
82
  * @param false $amp_url AMP URL.
83
  * @param int $post_id Post ID.
@@ -105,85 +156,135 @@ function amp_remove_endpoint( $url ) {
105
  }
106
 
107
  /**
108
- * Determine whether a given post supports AMP.
109
- *
110
- * @since 0.1
111
- * @since 0.6 Returns false when post has meta to disable AMP.
112
- * @see AMP_Post_Type_Support::get_support_errors()
113
  *
114
- * @param WP_Post $post Post.
115
  *
116
- * @return bool Whether the post supports AMP.
117
  */
118
- function post_supports_amp( $post ) {
119
- if ( amp_is_canonical() ) {
120
- return true;
 
 
 
 
 
 
 
121
  }
122
 
123
- $errors = AMP_Post_Type_Support::get_support_errors( $post );
124
 
125
- // Return false if an error is found.
126
- if ( ! empty( $errors ) ) {
127
- return false;
 
 
 
 
 
 
 
 
128
  }
129
 
130
- switch ( get_post_meta( $post->ID, AMP_Post_Meta_Box::STATUS_POST_META_KEY, true ) ) {
131
- case AMP_Post_Meta_Box::ENABLED_STATUS:
132
- return true;
133
-
134
- case AMP_Post_Meta_Box::DISABLED_STATUS:
135
- return false;
136
-
137
- // Disabled by default for custom page templates, page on front and page for posts.
138
- default:
139
- $enabled = (
140
- ! (bool) get_page_template_slug( $post )
141
- &&
142
- ! (
143
- 'page' === $post->post_type
144
- &&
145
- 'page' === get_option( 'show_on_front' )
146
- &&
147
- in_array( (int) $post->ID, array(
148
- (int) get_option( 'page_on_front' ),
149
- (int) get_option( 'page_for_posts' ),
150
- ), true )
151
- )
152
- );
 
 
153
 
154
- /**
155
- * Filters whether default AMP status should be enabled or not.
156
- *
157
- * @since 0.6
158
- *
159
- * @param string $status Status.
160
- * @param WP_Post $post Post.
161
- */
162
- return apply_filters( 'amp_post_status_default_enabled', $enabled, $post );
163
  }
164
  }
165
 
166
  /**
167
- * Are we currently on an AMP URL?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  *
169
- * Note: will always return `false` if called before the `parse_query` hook.
 
 
 
 
170
  *
171
  * @return bool Whether it is the AMP endpoint.
172
  */
173
  function is_amp_endpoint() {
174
- if ( is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
 
 
175
  return false;
176
  }
177
 
178
- if ( amp_is_canonical() ) {
179
- return true;
180
- }
181
 
182
- if ( 0 === did_action( 'parse_query' ) ) {
183
  _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( "is_amp_endpoint() was called before the 'parse_query' hook was called. This function will always return 'false' before the 'parse_query' hook is called.", 'amp' ) ), '0.4.2' );
184
  }
185
 
186
- return false !== get_query_var( amp_get_slug(), false );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
  /**
@@ -209,6 +310,23 @@ function amp_get_boilerplate_code() {
209
  . '<noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>';
210
  }
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  /**
213
  * Register default scripts for AMP components.
214
  *
@@ -242,23 +360,19 @@ function amp_register_default_scripts( $wp_scripts ) {
242
 
243
  // Get all AMP components as defined in the spec.
244
  $extensions = array();
245
- foreach ( AMP_Allowed_Tags_Generated::get_allowed_tags() as $allowed_tag ) {
246
- foreach ( $allowed_tag as $rule_spec ) {
247
- if ( ! empty( $rule_spec[ AMP_Rule_Spec::TAG_SPEC ]['requires_extension'] ) ) {
248
- $extensions = array_merge(
249
- $extensions,
250
- $rule_spec[ AMP_Rule_Spec::TAG_SPEC ]['requires_extension']
251
- );
252
- }
253
  }
254
  }
255
- $extensions = array_unique( $extensions );
256
 
257
- foreach ( $extensions as $extension ) {
258
  $src = sprintf(
259
  'https://cdn.ampproject.org/v0/%s-%s.js',
260
  $extension,
261
- 'latest'
262
  );
263
 
264
  $wp_scripts->add(
@@ -377,6 +491,49 @@ function amp_filter_script_loader_tag( $tag, $handle ) {
377
  return $tag;
378
  }
379
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  /**
381
  * Retrieve analytics data added in backend.
382
  *
@@ -435,7 +592,7 @@ function amp_print_analytics( $analytics ) {
435
  // Can enter multiple configs within backend.
436
  foreach ( $analytics_entries as $id => $analytics_entry ) {
437
  if ( ! isset( $analytics_entry['type'], $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) {
438
- /* translators: %1$s is analytics entry ID, %2$s is actual entry keys. */
439
  _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'Analytics entry for %1$s is missing one of the following keys: `type`, `attributes`, or `config_data` (array keys: %2$s)', 'amp' ), esc_html( $id ), esc_html( implode( ', ', array_keys( $analytics_entry ) ) ) ), '0.3.2' );
440
  continue;
441
  }
@@ -462,7 +619,7 @@ function amp_print_analytics( $analytics ) {
462
  * @return array Embed handlers.
463
  */
464
  function amp_get_content_embed_handlers( $post = null ) {
465
- if ( current_theme_supports( 'amp' ) && $post ) {
466
  _deprecated_argument( __FUNCTION__, '0.7', esc_html__( 'The $post argument is deprecated when theme supports AMP.', 'amp' ) );
467
  $post = null;
468
  }
@@ -478,6 +635,7 @@ function amp_get_content_embed_handlers( $post = null ) {
478
  */
479
  return apply_filters( 'amp_content_embed_handlers',
480
  array(
 
481
  'AMP_Twitter_Embed_Handler' => array(),
482
  'AMP_YouTube_Embed_Handler' => array(),
483
  'AMP_DailyMotion_Embed_Handler' => array(),
@@ -493,6 +651,9 @@ function amp_get_content_embed_handlers( $post = null ) {
493
  'AMP_Reddit_Embed_Handler' => array(),
494
  'AMP_Tumblr_Embed_Handler' => array(),
495
  'AMP_Gallery_Embed_Handler' => array(),
 
 
 
496
  'WPCOM_AMP_Polldaddy_Embed' => array(),
497
  ),
498
  $post
@@ -509,7 +670,7 @@ function amp_get_content_embed_handlers( $post = null ) {
509
  * @return array Embed handlers.
510
  */
511
  function amp_get_content_sanitizers( $post = null ) {
512
- if ( current_theme_supports( 'amp' ) && $post ) {
513
  _deprecated_argument( __FUNCTION__, '0.7', esc_html__( 'The $post argument is deprecated when theme supports AMP.', 'amp' ) );
514
  $post = null;
515
  }
@@ -525,15 +686,26 @@ function amp_get_content_sanitizers( $post = null ) {
525
  */
526
  $sanitizers = apply_filters( 'amp_content_sanitizers',
527
  array(
 
 
 
 
528
  'AMP_Img_Sanitizer' => array(),
529
  'AMP_Form_Sanitizer' => array(),
530
  'AMP_Comments_Sanitizer' => array(),
531
  'AMP_Video_Sanitizer' => array(),
 
532
  'AMP_Audio_Sanitizer' => array(),
533
  'AMP_Playbuzz_Sanitizer' => array(),
 
534
  'AMP_Iframe_Sanitizer' => array(
535
  'add_placeholder' => true,
536
  ),
 
 
 
 
 
537
  'AMP_Style_Sanitizer' => array(),
538
  'AMP_Tag_And_Attribute_Sanitizer' => array(), // Note: This whitelist sanitizer must come at the end to clean up any remaining issues the other sanitizers didn't catch.
539
  ),
@@ -627,23 +799,66 @@ function amp_get_schemaorg_metadata() {
627
  ),
628
  );
629
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  /**
631
- * Filters the site icon used in AMP responses.
632
  *
633
- * In general the `get_site_icon_url` filter should be used instead.
 
634
  *
635
  * @since 0.3
636
- * @todo Why is the size set to 32px?
637
  *
638
- * @param string $site_icon_url
639
  */
640
- $site_icon_url = apply_filters( 'amp_site_icon_url', get_site_icon_url( AMP_Post_Template::SITE_ICON_SIZE ) );
641
- if ( $site_icon_url ) {
642
- $metadata['publisher']['logo'] = array(
643
- '@type' => 'ImageObject',
644
- 'url' => $site_icon_url,
645
- 'height' => AMP_Post_Template::SITE_ICON_SIZE,
646
- 'width' => AMP_Post_Template::SITE_ICON_SIZE,
 
 
 
 
 
647
  );
648
  }
649
 
@@ -715,3 +930,17 @@ function amp_print_schemaorg_metadata() {
715
  <script type="application/ld+json"><?php echo wp_json_encode( $metadata ); ?></script>
716
  <?php
717
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  * may be deprecated in the future. Normally the slug should be just 'amp'.
14
  *
15
  * @since 0.7
16
+ *
17
  * @return string Slug used for query var, endpoint, and post type support.
18
  */
19
  function amp_get_slug() {
27
  * Warning: This filter may become deprecated.
28
  *
29
  * @since 0.3.2
30
+ *
31
  * @param string $query_var The AMP query variable.
32
  */
33
  $query_var = apply_filters( 'amp_query_var', 'amp' );
37
  return $query_var;
38
  }
39
 
40
+ /**
41
+ * Get the URL for the current request.
42
+ *
43
+ * This is essentially the REQUEST_URI prefixed by the scheme and host for the home URL.
44
+ * This is needed in particular due to subdirectory installs.
45
+ *
46
+ * @since 1.0
47
+ *
48
+ * @return string Current URL.
49
+ */
50
+ function amp_get_current_url() {
51
+ $url = preg_replace( '#(^https?://[^/]+)/.*#', '$1', home_url( '/' ) );
52
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
53
+ $url = esc_url_raw( $url . wp_unslash( $_SERVER['REQUEST_URI'] ) );
54
+ } else {
55
+ $url .= '/';
56
+ }
57
+ return $url;
58
+ }
59
+
60
  /**
61
  * Retrieves the full AMP-specific permalink for the given post ID.
62
  *
63
  * @since 0.1
64
  *
65
  * @param int $post_id Post ID.
 
66
  * @return string AMP permalink.
67
  */
68
  function amp_get_permalink( $post_id ) {
69
 
70
+ // When theme support is present, the plain query var should always be used.
71
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
72
+ $permalink = get_permalink( $post_id );
73
+ if ( ! amp_is_canonical() ) {
74
+ $permalink = add_query_arg( amp_get_slug(), '', $permalink );
75
+ }
76
+ return $permalink;
77
+ }
78
+
79
  /**
80
  * Filters the AMP permalink to short-circuit normal generation.
81
  *
82
  * Returning a non-false value in this filter will cause the `get_permalink()` to get called and the `amp_get_permalink` filter to not apply.
83
  *
84
  * @since 0.4
85
+ * @since 1.0 This filter does not apply when 'amp' theme support is present.
86
  *
87
  * @param false $url Short-circuited URL.
88
  * @param int $post_id Post ID.
93
  return $pre_url;
94
  }
95
 
96
+ $permalink = get_permalink( $post_id );
97
+
98
  if ( amp_is_canonical() ) {
99
+ $amp_url = $permalink;
100
  } else {
101
+ $parsed_url = wp_parse_url( get_permalink( $post_id ) );
102
+ $structure = get_option( 'permalink_structure' );
103
+ $use_query_var = (
104
+ // If pretty permalinks aren't available, then query var must be used.
105
+ empty( $structure )
106
+ ||
107
+ // If there are existing query vars, then always use the amp query var as well.
108
+ ! empty( $parsed_url['query'] )
109
+ ||
110
+ // If the post type is hierarchical then the /amp/ endpoint isn't available.
111
+ is_post_type_hierarchical( get_post_type( $post_id ) )
112
+ ||
113
+ // Attachment pages don't accept the /amp/ endpoint.
114
+ 'attachment' === get_post_type( $post_id )
115
+ );
116
+ if ( $use_query_var ) {
117
+ $amp_url = add_query_arg( amp_get_slug(), '', $permalink );
118
  } else {
119
+ $amp_url = preg_replace( '/#.*/', '', $permalink );
120
+ $amp_url = trailingslashit( $amp_url ) . user_trailingslashit( amp_get_slug(), 'single_amp' );
121
+ if ( ! empty( $parsed_url['fragment'] ) ) {
122
+ $amp_url .= '#' . $parsed_url['fragment'];
123
+ }
124
  }
125
  }
126
 
128
  * Filters AMP permalink.
129
  *
130
  * @since 0.2
131
+ * @since 1.0 This filter does not apply when 'amp' theme support is present.
132
  *
133
  * @param false $amp_url AMP URL.
134
  * @param int $post_id Post ID.
156
  }
157
 
158
  /**
159
+ * Add amphtml link.
 
 
 
 
160
  *
161
+ * If there are known validation errors for the current URL then do not output anything.
162
  *
163
+ * @since 1.0
164
  */
165
+ function amp_add_amphtml_link() {
166
+
167
+ /**
168
+ * Filters whether to show the amphtml link on the frontend.
169
+ *
170
+ * @todo This filter's name is incorrect. It's not about adding a canonical link but adding the amphtml link.
171
+ * @since 0.2
172
+ */
173
+ if ( false === apply_filters( 'amp_frontend_show_canonical', true ) ) {
174
+ return;
175
  }
176
 
177
+ $current_url = amp_get_current_url();
178
 
179
+ $amp_url = null;
180
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
181
+ if ( AMP_Theme_Support::is_paired_available() ) {
182
+ $amp_url = add_query_arg( amp_get_slug(), '', $current_url );
183
+ }
184
+ } else {
185
+ if ( is_singular() ) {
186
+ $amp_url = amp_get_permalink( get_queried_object_id() );
187
+ } else {
188
+ $amp_url = add_query_arg( amp_get_slug(), '', $current_url );
189
+ }
190
  }
191
 
192
+ if ( ! $amp_url ) {
193
+ printf( '<!-- %s -->', esc_html__( 'There is no amphtml version available for this URL.', 'amp' ) );
194
+ return;
195
+ }
196
+
197
+ // Check to see if there are known unaccepted validation errors for this URL.
198
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
199
+ $validation_errors = AMP_Validated_URL_Post_Type::get_invalid_url_validation_errors( $current_url, array( 'ignore_accepted' => true ) );
200
+ $error_count = count( $validation_errors );
201
+ if ( $error_count > 0 ) {
202
+ echo "<!--\n";
203
+ echo esc_html( sprintf(
204
+ /* translators: %s is error count */
205
+ _n(
206
+ 'There is %s validation error that is blocking the amphtml version from being available.',
207
+ 'There are %s validation errors that are blocking the amphtml version from being available.',
208
+ $error_count,
209
+ 'amp'
210
+ ),
211
+ number_format_i18n( $error_count )
212
+ ) );
213
+ echo "\n-->";
214
+ return;
215
+ }
216
+ }
217
 
218
+ if ( $amp_url ) {
219
+ printf( '<link rel="amphtml" href="%s">', esc_url( $amp_url ) );
 
 
 
 
 
 
 
220
  }
221
  }
222
 
223
  /**
224
+ * Determine whether a given post supports AMP.
225
+ *
226
+ * @since 0.1
227
+ * @since 0.6 Returns false when post has meta to disable AMP.
228
+ * @see AMP_Post_Type_Support::get_support_errors()
229
+ *
230
+ * @param WP_Post $post Post.
231
+ * @return bool Whether the post supports AMP.
232
+ */
233
+ function post_supports_amp( $post ) {
234
+ return 0 === count( AMP_Post_Type_Support::get_support_errors( $post ) );
235
+ }
236
+
237
+ /**
238
+ * Determine whether the current response being served as AMP.
239
  *
240
+ * This function cannot be called before the parse_query action because it needs to be able
241
+ * to determine the queried object is able to be served as AMP. If 'amp' theme support is not
242
+ * present, this function returns true just if the query var is present. If theme support is
243
+ * present, then it returns true in paired mode if an AMP template is available and the query
244
+ * var is present, or else in native mode if just the template is available.
245
  *
246
  * @return bool Whether it is the AMP endpoint.
247
  */
248
  function is_amp_endpoint() {
249
+ global $pagenow;
250
+
251
+ if ( is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || in_array( $pagenow, array( 'wp-login.php', 'wp-signup.php', 'wp-activate.php' ), true ) ) {
252
  return false;
253
  }
254
 
255
+ $did_parse_query = did_action( 'parse_query' );
 
 
256
 
257
+ if ( ! $did_parse_query ) {
258
  _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( "is_amp_endpoint() was called before the 'parse_query' hook was called. This function will always return 'false' before the 'parse_query' hook is called.", 'amp' ) ), '0.4.2' );
259
  }
260
 
261
+ $has_amp_query_var = (
262
+ isset( $_GET[ amp_get_slug() ] ) // WPCS: CSRF OK.
263
+ ||
264
+ false !== get_query_var( amp_get_slug(), false )
265
+ );
266
+
267
+ if ( ! current_theme_supports( AMP_Theme_Support::SLUG ) ) {
268
+ return $has_amp_query_var;
269
+ }
270
+
271
+ // When there is no query var and AMP is not canonical/native, then this is definitely not an AMP endpoint.
272
+ if ( ! $has_amp_query_var && ! amp_is_canonical() ) {
273
+ return false;
274
+ }
275
+
276
+ /*
277
+ * If this is a URL for validation, and validation is forced for all URLs, return true.
278
+ * Normally, this would be false if the user has deselected a template,
279
+ * like by unchecking 'Categories' in 'AMP Settings' > 'Supported Templates'.
280
+ * But there's a flag for the WP-CLI command that sets this query var to validate all URLs.
281
+ */
282
+ if ( AMP_Validation_Manager::is_theme_support_forced() ) {
283
+ return true;
284
+ }
285
+
286
+ $availability = AMP_Theme_Support::get_template_availability();
287
+ return amp_is_canonical() ? $availability['supported'] : ( $has_amp_query_var && $availability['supported'] );
288
  }
289
 
290
  /**
310
  . '<noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>';
311
  }
312
 
313
+ /**
314
+ * Add generator metadata.
315
+ *
316
+ * @since 6.0
317
+ * @since 1.0 Add template mode.
318
+ */
319
+ function amp_add_generator_metadata() {
320
+ if ( amp_is_canonical() ) {
321
+ $mode = 'native';
322
+ } elseif ( current_theme_supports( AMP_Theme_Support::SLUG ) ) {
323
+ $mode = 'paired';
324
+ } else {
325
+ $mode = 'classic';
326
+ }
327
+ printf( '<meta name="generator" content="%s">', esc_attr( sprintf( 'AMP Plugin v%s; mode=%s', AMP__VERSION, $mode ) ) );
328
+ }
329
+
330
  /**
331
  * Register default scripts for AMP components.
332
  *
360
 
361
  // Get all AMP components as defined in the spec.
362
  $extensions = array();
363
+ foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'script' ) as $script_spec ) {
364
+ if ( isset( $script_spec[ AMP_Rule_Spec::TAG_SPEC ]['extension_spec']['name'], $script_spec[ AMP_Rule_Spec::TAG_SPEC ]['extension_spec']['version'] ) ) {
365
+ $versions = $script_spec[ AMP_Rule_Spec::TAG_SPEC ]['extension_spec']['version'];
366
+ array_pop( $versions );
367
+ $extensions[ $script_spec[ AMP_Rule_Spec::TAG_SPEC ]['extension_spec']['name'] ] = array_pop( $versions );
 
 
 
368
  }
369
  }
 
370
 
371
+ foreach ( $extensions as $extension => $version ) {
372
  $src = sprintf(
373
  'https://cdn.ampproject.org/v0/%s-%s.js',
374
  $extension,
375
+ $version
376
  );
377
 
378
  $wp_scripts->add(
491
  return $tag;
492
  }
493
 
494
+ /**
495
+ * Explicitly opt-in to CORS mode by adding the crossorigin attribute to font stylesheet links.
496
+ *
497
+ * This explicitly triggers a CORS request, and gets back a non-opaque response, ensuring that a service
498
+ * worker caching the external stylesheet will not inflate the storage quota. This must be done in AMP
499
+ * and non-AMP alike because in paired mode the service worker could cache the font stylesheets in a
500
+ * non-AMP document without CORS (crossorigin="anonymous") in which case the service worker could then
501
+ * fail to serve the cached font resources in an AMP document with the warning:
502
+ *
503
+ * > The FetchEvent resulted in a network error response: an "opaque" response was used for a request whose type is not no-cors
504
+ *
505
+ * @since 1.0
506
+ * @link https://developers.google.com/web/tools/workbox/guides/storage-quota#beware_of_opaque_responses
507
+ * @link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests#cross-origin_requests_and_opaque_responses
508
+ * @todo This should be proposed for WordPress core.
509
+ *
510
+ * @param string $tag Link tag HTML.
511
+ * @param string $handle Dependency handle.
512
+ * @param string $href Link URL.
513
+ * @return string Link tag HTML.
514
+ */
515
+ function amp_filter_font_style_loader_tag_with_crossorigin_anonymous( $tag, $handle, $href ) {
516
+ static $allowed_font_src_regex = null;
517
+ unset( $handle );
518
+ if ( ! $allowed_font_src_regex ) {
519
+ $spec_name = 'link rel=stylesheet for fonts'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
520
+ foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'link' ) as $spec_rule ) {
521
+ if ( isset( $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) && $spec_name === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) {
522
+ $allowed_font_src_regex = '@^(' . $spec_rule[ AMP_Rule_Spec::ATTR_SPEC_LIST ]['href']['value_regex'] . ')$@';
523
+ break;
524
+ }
525
+ }
526
+ }
527
+
528
+ $href = preg_replace( '#^(http:)?(?=//)#', 'https:', $href );
529
+
530
+ if ( preg_match( $allowed_font_src_regex, $href ) && false === strpos( $tag, 'crossorigin=' ) ) {
531
+ $tag = preg_replace( '/(?<=<link\s)/', 'crossorigin="anonymous" ', $tag );
532
+ }
533
+
534
+ return $tag;
535
+ }
536
+
537
  /**
538
  * Retrieve analytics data added in backend.
539
  *
592
  // Can enter multiple configs within backend.
593
  foreach ( $analytics_entries as $id => $analytics_entry ) {
594
  if ( ! isset( $analytics_entry['type'], $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) {
595
+ /* translators: 1: the analytics entry ID, 2: comma-separated list of the actual entry keys. */
596
  _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'Analytics entry for %1$s is missing one of the following keys: `type`, `attributes`, or `config_data` (array keys: %2$s)', 'amp' ), esc_html( $id ), esc_html( implode( ', ', array_keys( $analytics_entry ) ) ) ), '0.3.2' );
597
  continue;
598
  }
619
  * @return array Embed handlers.
620
  */
621
  function amp_get_content_embed_handlers( $post = null ) {
622
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) && $post ) {
623
  _deprecated_argument( __FUNCTION__, '0.7', esc_html__( 'The $post argument is deprecated when theme supports AMP.', 'amp' ) );
624
  $post = null;
625
  }
635
  */
636
  return apply_filters( 'amp_content_embed_handlers',
637
  array(
638
+ 'AMP_Core_Block_Handler' => array(),
639
  'AMP_Twitter_Embed_Handler' => array(),
640
  'AMP_YouTube_Embed_Handler' => array(),
641
  'AMP_DailyMotion_Embed_Handler' => array(),
651
  'AMP_Reddit_Embed_Handler' => array(),
652
  'AMP_Tumblr_Embed_Handler' => array(),
653
  'AMP_Gallery_Embed_Handler' => array(),
654
+ 'AMP_Gfycat_Embed_Handler' => array(),
655
+ 'AMP_Hulu_Embed_Handler' => array(),
656
+ 'AMP_Imgur_Embed_Handler' => array(),
657
  'WPCOM_AMP_Polldaddy_Embed' => array(),
658
  ),
659
  $post
670
  * @return array Embed handlers.
671
  */
672
  function amp_get_content_sanitizers( $post = null ) {
673
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) && $post ) {
674
  _deprecated_argument( __FUNCTION__, '0.7', esc_html__( 'The $post argument is deprecated when theme supports AMP.', 'amp' ) );
675
  $post = null;
676
  }
686
  */
687
  $sanitizers = apply_filters( 'amp_content_sanitizers',
688
  array(
689
+ 'AMP_Core_Theme_Sanitizer' => array(
690
+ 'template' => get_template(),
691
+ 'stylesheet' => get_stylesheet(),
692
+ ),
693
  'AMP_Img_Sanitizer' => array(),
694
  'AMP_Form_Sanitizer' => array(),
695
  'AMP_Comments_Sanitizer' => array(),
696
  'AMP_Video_Sanitizer' => array(),
697
+ 'AMP_O2_Player_Sanitizer' => array(),
698
  'AMP_Audio_Sanitizer' => array(),
699
  'AMP_Playbuzz_Sanitizer' => array(),
700
+ 'AMP_Embed_Sanitizer' => array(),
701
  'AMP_Iframe_Sanitizer' => array(
702
  'add_placeholder' => true,
703
  ),
704
+ 'AMP_Gallery_Block_Sanitizer' => array( // Note: Gallery block sanitizer must come after image sanitizers since itś logic is using the already sanitized images.
705
+ 'carousel_required' => ! current_theme_supports( AMP_Theme_Support::SLUG ), // For back-compat.
706
+ ),
707
+ 'AMP_Block_Sanitizer' => array(), // Note: Block sanitizer must come after embed / media sanitizers since it's logic is using the already sanitized content.
708
+ 'AMP_Script_Sanitizer' => array(),
709
  'AMP_Style_Sanitizer' => array(),
710
  'AMP_Tag_And_Attribute_Sanitizer' => array(), // Note: This whitelist sanitizer must come at the end to clean up any remaining issues the other sanitizers didn't catch.
711
  ),
799
  ),
800
  );
801
 
802
+ /*
803
+ * "The logo should be a rectangle, not a square. The logo should fit in a 60x600px rectangle.,
804
+ * and either be exactly 60px high (preferred), or exactly 600px wide. For example, 450x45px
805
+ * would not be acceptable, even though it fits in the 600x60px rectangle."
806
+ * See <https://developers.google.com/search/docs/data-types/article#logo-guidelines>.
807
+ */
808
+ $max_logo_width = 600;
809
+ $max_logo_height = 60;
810
+ $custom_logo_id = get_theme_mod( 'custom_logo' );
811
+ $schema_img = array();
812
+
813
+ if ( has_custom_logo() && $custom_logo_id ) {
814
+ $custom_logo_img = wp_get_attachment_image_src( $custom_logo_id, array( $max_logo_width, $max_logo_height ), false );
815
+ if ( $custom_logo_img ) {
816
+ // @todo Warning: The width/height returned may not actually be physically the $max_logo_width and $max_logo_height for the image returned.
817
+ $schema_img = array(
818
+ 'url' => $custom_logo_img[0],
819
+ 'width' => $custom_logo_img[1],
820
+ 'height' => $custom_logo_img[2],
821
+ );
822
+ }
823
+ }
824
+
825
+ // Try Site Icon, though it is not ideal because "The logo should be a rectangle, not a square." per <https://developers.google.com/search/docs/data-types/article#logo-guidelines>.
826
+ if ( empty( $schema_img['url'] ) ) {
827
+ /*
828
+ * Note that AMP_Post_Template::SITE_ICON_SIZE is used and not $max_logo_height because 32px is the largest
829
+ * size that is defined in \WP_Site_Icon::$site_icon_sizes which is less than 60px. It may be a good idea
830
+ * to add a site_icon_image_sizes filter which appends 60 to the list of sizes, but this will only help
831
+ * when adding a new site icon and it would be irrelevant when a custom logo is present, per above.
832
+ */
833
+ $schema_img = array(
834
+ 'url' => get_site_icon_url( AMP_Post_Template::SITE_ICON_SIZE ),
835
+ 'width' => AMP_Post_Template::SITE_ICON_SIZE,
836
+ 'height' => AMP_Post_Template::SITE_ICON_SIZE,
837
+ );
838
+ }
839
+
840
  /**
841
+ * Filters the publisher logo URL in the schema.org data.
842
  *
843
+ * Previously, this only filtered the Site Icon, as that was the only possible schema.org publisher logo.
844
+ * But the Custom Logo is now the preferred publisher logo, if it exists and its dimensions aren't too big.
845
  *
846
  * @since 0.3
 
847
  *
848
+ * @param string $schema_img_url URL of the publisher logo, either the Custom Logo or the Site Icon.
849
  */
850
+ $filtered_schema_img_url = apply_filters( 'amp_site_icon_url', $schema_img['url'] );
851
+ if ( $filtered_schema_img_url !== $schema_img['url'] ) {
852
+ $schema_img['url'] = $filtered_schema_img_url;
853
+ unset( $schema_img['width'], $schema_img['height'] ); // Clear width/height since now unknown, and not required.
854
+ }
855
+
856
+ if ( ! empty( $schema_img['url'] ) ) {
857
+ $metadata['publisher']['logo'] = array_merge(
858
+ array(
859
+ '@type' => 'ImageObject',
860
+ ),
861
+ $schema_img
862
  );
863
  }
864
 
930
  <script type="application/ld+json"><?php echo wp_json_encode( $metadata ); ?></script>
931
  <?php
932
  }
933
+
934
+ /**
935
+ * Filters content and keeps only allowable HTML elements by amp-mustache.
936
+ *
937
+ * @see wp_kses()
938
+ * @since 1.0
939
+ *
940
+ * @param string $markup Markup to sanitize.
941
+ * @return string HTML markup with tags allowed by amp-mustache.
942
+ */
943
+ function amp_wp_kses_mustache( $markup ) {
944
+ $amp_mustache_allowed_html_tags = array( 'strong', 'b', 'em', 'i', 'u', 's', 'small', 'mark', 'del', 'ins', 'sup', 'sub' );
945
+ return wp_kses( $markup, array_fill_keys( $amp_mustache_allowed_html_tags, array() ) );
946
+ }
includes/amp-post-template-actions.php CHANGED
@@ -2,6 +2,7 @@
2
  /**
3
  * Callbacks for adding content to an AMP template.
4
  *
 
5
  * @package AMP
6
  */
7
 
@@ -98,6 +99,12 @@ function amp_post_template_add_schemaorg_metadata() {
98
  * @param AMP_Post_Template $amp_template Template.
99
  */
100
  function amp_post_template_add_styles( $amp_template ) {
 
 
 
 
 
 
101
  $styles = $amp_template->get( 'post_amp_styles' );
102
  if ( ! empty( $styles ) ) {
103
  echo '/* Inline styles */' . PHP_EOL; // WPCS: XSS OK.
@@ -130,12 +137,3 @@ function amp_post_template_add_analytics_data() {
130
  $analytics = amp_add_custom_analytics();
131
  amp_print_analytics( $analytics );
132
  }
133
-
134
- /**
135
- * Add generator metadata.
136
- *
137
- * @since 6.0
138
- */
139
- function amp_add_generator_metadata() {
140
- printf( '<meta name="generator" content="%s" />', esc_attr( 'AMP Plugin v' . AMP__VERSION ) );
141
- }
2
  /**
3
  * Callbacks for adding content to an AMP template.
4
  *
5
+ * @todo Rename this file from amp-post-template-actions.php to amp-post-template-functions.php.
6
  * @package AMP
7
  */
8
 
99
  * @param AMP_Post_Template $amp_template Template.
100
  */
101
  function amp_post_template_add_styles( $amp_template ) {
102
+ $stylesheets = $amp_template->get( 'post_amp_stylesheets' );
103
+ if ( ! empty( $stylesheets ) ) {
104
+ echo '/* Inline stylesheets */' . PHP_EOL; // WPCS: XSS OK.
105
+ echo implode( '', $stylesheets ); // WPCS: XSS OK.
106
+ }
107
+
108
  $styles = $amp_template->get( 'post_amp_styles' );
109
  if ( ! empty( $styles ) ) {
110
  echo '/* Inline styles */' . PHP_EOL; // WPCS: XSS OK.
137
  $analytics = amp_add_custom_analytics();
138
  amp_print_analytics( $analytics );
139
  }
 
 
 
 
 
 
 
 
 
includes/class-amp-autoloader.php CHANGED
@@ -29,15 +29,22 @@ class AMP_Autoloader {
29
  * @var string[]
30
  */
31
  private static $_classmap = array(
 
32
  'AMP_Theme_Support' => 'includes/class-amp-theme-support',
 
33
  'AMP_Comment_Walker' => 'includes/class-amp-comment-walker',
34
  'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer',
35
  'AMP_Post_Meta_Box' => 'includes/admin/class-amp-post-meta-box',
 
36
  'AMP_Post_Type_Support' => 'includes/class-amp-post-type-support',
37
  'AMP_Base_Embed_Handler' => 'includes/embeds/class-amp-base-embed-handler',
38
  'AMP_DailyMotion_Embed_Handler' => 'includes/embeds/class-amp-dailymotion-embed',
39
  'AMP_Facebook_Embed_Handler' => 'includes/embeds/class-amp-facebook-embed',
40
  'AMP_Gallery_Embed_Handler' => 'includes/embeds/class-amp-gallery-embed',
 
 
 
 
41
  'AMP_Instagram_Embed_Handler' => 'includes/embeds/class-amp-instagram-embed',
42
  'AMP_Issuu_Embed_Handler' => 'includes/embeds/class-amp-issuu-embed-handler',
43
  'AMP_Meetup_Embed_Handler' => 'includes/embeds/class-amp-meetup-embed-handler',
@@ -50,14 +57,14 @@ class AMP_Autoloader {
50
  'AMP_Vimeo_Embed_Handler' => 'includes/embeds/class-amp-vimeo-embed',
51
  'AMP_Vine_Embed_Handler' => 'includes/embeds/class-amp-vine-embed',
52
  'AMP_YouTube_Embed_Handler' => 'includes/embeds/class-amp-youtube-embed',
53
- 'FastImage' => 'includes/lib/fastimage/class-fastimage',
54
- 'WillWashburn\Stream\Exception\StreamBufferTooSmallException' => 'includes/lib/fasterimage/Stream/Exception/StreamBufferTooSmallException',
55
- 'WillWashburn\Stream\StreamableInterface' => 'includes/lib/fasterimage/Stream/StreamableInterface',
56
- 'WillWashburn\Stream\Stream' => 'includes/lib/fasterimage/Stream/Stream',
57
- 'FasterImage\Exception\InvalidImageException' => 'includes/lib/fasterimage/Exception/InvalidImageException',
58
- 'FasterImage\ExifParser' => 'includes/lib/fasterimage/ExifParser',
59
- 'FasterImage\ImageParser' => 'includes/lib/fasterimage/ImageParser',
60
- 'FasterImage\FasterImage' => 'includes/lib/fasterimage/FasterImage',
61
  'AMP_Analytics_Options_Submenu' => 'includes/options/class-amp-analytics-options-submenu',
62
  'AMP_Options_Menu' => 'includes/options/class-amp-options-menu',
63
  'AMP_Options_Manager' => 'includes/options/class-amp-options-manager',
@@ -68,14 +75,20 @@ class AMP_Autoloader {
68
  'AMP_Audio_Sanitizer' => 'includes/sanitizers/class-amp-audio-sanitizer',
69
  'AMP_Base_Sanitizer' => 'includes/sanitizers/class-amp-base-sanitizer',
70
  'AMP_Blacklist_Sanitizer' => 'includes/sanitizers/class-amp-blacklist-sanitizer',
 
 
71
  'AMP_Iframe_Sanitizer' => 'includes/sanitizers/class-amp-iframe-sanitizer',
72
  'AMP_Img_Sanitizer' => 'includes/sanitizers/class-amp-img-sanitizer',
73
  'AMP_Comments_Sanitizer' => 'includes/sanitizers/class-amp-comments-sanitizer',
74
  'AMP_Form_Sanitizer' => 'includes/sanitizers/class-amp-form-sanitizer',
 
75
  'AMP_Playbuzz_Sanitizer' => 'includes/sanitizers/class-amp-playbuzz-sanitizer',
76
  'AMP_Style_Sanitizer' => 'includes/sanitizers/class-amp-style-sanitizer',
 
 
77
  'AMP_Tag_And_Attribute_Sanitizer' => 'includes/sanitizers/class-amp-tag-and-attribute-sanitizer',
78
  'AMP_Video_Sanitizer' => 'includes/sanitizers/class-amp-video-sanitizer',
 
79
  'AMP_Customizer_Design_Settings' => 'includes/settings/class-amp-customizer-design-settings',
80
  'AMP_Customizer_Settings' => 'includes/settings/class-amp-customizer-settings',
81
  'AMP_Content' => 'includes/templates/class-amp-content',
@@ -84,13 +97,14 @@ class AMP_Autoloader {
84
  'AMP_DOM_Utils' => 'includes/utils/class-amp-dom-utils',
85
  'AMP_HTML_Utils' => 'includes/utils/class-amp-html-utils',
86
  'AMP_Image_Dimension_Extractor' => 'includes/utils/class-amp-image-dimension-extractor',
87
- 'AMP_Validation_Utils' => 'includes/utils/class-amp-validation-utils',
 
 
 
88
  'AMP_String_Utils' => 'includes/utils/class-amp-string-utils',
89
  'AMP_WP_Utils' => 'includes/utils/class-amp-wp-utils',
90
  'AMP_Widget_Archives' => 'includes/widgets/class-amp-widget-archives',
91
  'AMP_Widget_Categories' => 'includes/widgets/class-amp-widget-categories',
92
- 'AMP_Widget_Media_Video' => 'includes/widgets/class-amp-widget-media-video',
93
- 'AMP_Widget_Recent_Comments' => 'includes/widgets/class-amp-widget-recent-comments',
94
  'AMP_Widget_Text' => 'includes/widgets/class-amp-widget-text',
95
  'WPCOM_AMP_Polldaddy_Embed' => 'wpcom/class-amp-polldaddy-embed',
96
  'AMP_Test_Stub_Sanitizer' => 'tests/stubs',
@@ -129,6 +143,10 @@ class AMP_Autoloader {
129
  * Called at the end of this file; calling a second time has no effect.
130
  */
131
  public static function register() {
 
 
 
 
132
  if ( ! self::$is_registered ) {
133
  spl_autoload_register( array( __CLASS__, 'autoload' ) );
134
  self::$is_registered = true;
29
  * @var string[]
30
  */
31
  private static $_classmap = array(
32
+ 'AMP_Editor_Blocks' => 'includes/admin/class-amp-editor-blocks',
33
  'AMP_Theme_Support' => 'includes/class-amp-theme-support',
34
+ 'AMP_HTTP' => 'includes/class-amp-http',
35
  'AMP_Comment_Walker' => 'includes/class-amp-comment-walker',
36
  'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer',
37
  'AMP_Post_Meta_Box' => 'includes/admin/class-amp-post-meta-box',
38
+ 'AMP_Admin_Pointer' => 'includes/admin/class-amp-admin-pointer',
39
  'AMP_Post_Type_Support' => 'includes/class-amp-post-type-support',
40
  'AMP_Base_Embed_Handler' => 'includes/embeds/class-amp-base-embed-handler',
41
  'AMP_DailyMotion_Embed_Handler' => 'includes/embeds/class-amp-dailymotion-embed',
42
  'AMP_Facebook_Embed_Handler' => 'includes/embeds/class-amp-facebook-embed',
43
  'AMP_Gallery_Embed_Handler' => 'includes/embeds/class-amp-gallery-embed',
44
+ 'AMP_Gfycat_Embed_Handler' => 'includes/embeds/class-amp-gfycat-embed-handler',
45
+ 'AMP_Hulu_Embed_Handler' => 'includes/embeds/class-amp-hulu-embed-handler',
46
+ 'AMP_Imgur_Embed_Handler' => 'includes/embeds/class-amp-imgur-embed-handler',
47
+ 'AMP_Core_Block_Handler' => 'includes/embeds/class-amp-core-block-handler',
48
  'AMP_Instagram_Embed_Handler' => 'includes/embeds/class-amp-instagram-embed',
49
  'AMP_Issuu_Embed_Handler' => 'includes/embeds/class-amp-issuu-embed-handler',
50
  'AMP_Meetup_Embed_Handler' => 'includes/embeds/class-amp-meetup-embed-handler',
57
  'AMP_Vimeo_Embed_Handler' => 'includes/embeds/class-amp-vimeo-embed',
58
  'AMP_Vine_Embed_Handler' => 'includes/embeds/class-amp-vine-embed',
59
  'AMP_YouTube_Embed_Handler' => 'includes/embeds/class-amp-youtube-embed',
60
+ 'FastImage' => 'third_party/fastimage/class-fastimage',
61
+ 'WillWashburn\Stream\Exception\StreamBufferTooSmallException' => 'third_party/fasterimage/Stream/Exception/StreamBufferTooSmallException',
62
+ 'WillWashburn\Stream\StreamableInterface' => 'third_party/fasterimage/Stream/StreamableInterface',
63
+ 'WillWashburn\Stream\Stream' => 'third_party/fasterimage/Stream/Stream',
64
+ 'FasterImage\Exception\InvalidImageException' => 'third_party/fasterimage/Exception/InvalidImageException',
65
+ 'FasterImage\ExifParser' => 'third_party/fasterimage/ExifParser',
66
+ 'FasterImage\ImageParser' => 'third_party/fasterimage/ImageParser',
67
+ 'FasterImage\FasterImage' => 'third_party/fasterimage/FasterImage',
68
  'AMP_Analytics_Options_Submenu' => 'includes/options/class-amp-analytics-options-submenu',
69
  'AMP_Options_Menu' => 'includes/options/class-amp-options-menu',
70
  'AMP_Options_Manager' => 'includes/options/class-amp-options-manager',
75
  'AMP_Audio_Sanitizer' => 'includes/sanitizers/class-amp-audio-sanitizer',
76
  'AMP_Base_Sanitizer' => 'includes/sanitizers/class-amp-base-sanitizer',
77
  'AMP_Blacklist_Sanitizer' => 'includes/sanitizers/class-amp-blacklist-sanitizer',
78
+ 'AMP_Block_Sanitizer' => 'includes/sanitizers/class-amp-block-sanitizer',
79
+ 'AMP_Gallery_Block_Sanitizer' => 'includes/sanitizers/class-amp-gallery-block-sanitizer',
80
  'AMP_Iframe_Sanitizer' => 'includes/sanitizers/class-amp-iframe-sanitizer',
81
  'AMP_Img_Sanitizer' => 'includes/sanitizers/class-amp-img-sanitizer',
82
  'AMP_Comments_Sanitizer' => 'includes/sanitizers/class-amp-comments-sanitizer',
83
  'AMP_Form_Sanitizer' => 'includes/sanitizers/class-amp-form-sanitizer',
84
+ 'AMP_O2_Player_Sanitizer' => 'includes/sanitizers/class-amp-o2-player-sanitizer',
85
  'AMP_Playbuzz_Sanitizer' => 'includes/sanitizers/class-amp-playbuzz-sanitizer',
86
  'AMP_Style_Sanitizer' => 'includes/sanitizers/class-amp-style-sanitizer',
87
+ 'AMP_Script_Sanitizer' => 'includes/sanitizers/class-amp-script-sanitizer',
88
+ 'AMP_Embed_Sanitizer' => 'includes/sanitizers/class-amp-embed-sanitizer',
89
  'AMP_Tag_And_Attribute_Sanitizer' => 'includes/sanitizers/class-amp-tag-and-attribute-sanitizer',
90
  'AMP_Video_Sanitizer' => 'includes/sanitizers/class-amp-video-sanitizer',
91
+ 'AMP_Core_Theme_Sanitizer' => 'includes/sanitizers/class-amp-core-theme-sanitizer',
92
  'AMP_Customizer_Design_Settings' => 'includes/settings/class-amp-customizer-design-settings',
93
  'AMP_Customizer_Settings' => 'includes/settings/class-amp-customizer-settings',
94
  'AMP_Content' => 'includes/templates/class-amp-content',
97
  'AMP_DOM_Utils' => 'includes/utils/class-amp-dom-utils',
98
  'AMP_HTML_Utils' => 'includes/utils/class-amp-html-utils',
99
  'AMP_Image_Dimension_Extractor' => 'includes/utils/class-amp-image-dimension-extractor',
100
+ 'AMP_Validation_Manager' => 'includes/validation/class-amp-validation-manager',
101
+ 'AMP_Validated_URL_Post_Type' => 'includes/validation/class-amp-validated-url-post-type',
102
+ 'AMP_Validation_Error_Taxonomy' => 'includes/validation/class-amp-validation-error-taxonomy',
103
+ 'AMP_CLI' => 'includes/class-amp-cli',
104
  'AMP_String_Utils' => 'includes/utils/class-amp-string-utils',
105
  'AMP_WP_Utils' => 'includes/utils/class-amp-wp-utils',
106
  'AMP_Widget_Archives' => 'includes/widgets/class-amp-widget-archives',
107
  'AMP_Widget_Categories' => 'includes/widgets/class-amp-widget-categories',
 
 
108
  'AMP_Widget_Text' => 'includes/widgets/class-amp-widget-text',
109
  'WPCOM_AMP_Polldaddy_Embed' => 'wpcom/class-amp-polldaddy-embed',
110
  'AMP_Test_Stub_Sanitizer' => 'tests/stubs',
143
  * Called at the end of this file; calling a second time has no effect.
144
  */
145
  public static function register() {
146
+ if ( file_exists( AMP__DIR__ . '/vendor/autoload.php' ) ) {
147
+ require_once AMP__DIR__ . '/vendor/autoload.php';
148
+ }
149
+
150
  if ( ! self::$is_registered ) {
151
  spl_autoload_register( array( __CLASS__, 'autoload' ) );
152
  self::$is_registered = true;
includes/class-amp-cli.php ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_CLI
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_CLI
10
+ *
11
+ * Registers and implements a WP-CLI command to crawl the entire site for AMP validity.
12
+ * To use this, run: wp amp validate-site.
13
+ *
14
+ * @since 1.0
15
+ */
16
+ class AMP_CLI {
17
+
18
+ /**
19
+ * The WP-CLI flag to force validation.
20
+ *
21
+ * By default, the WP-CLI command does not validate templates that the user has opted-out of.
22
+ * For example, by unchecking 'Categories' in 'AMP Settings' > 'Supported Templates'.
23
+ * But with this flag, validation will ignore these options.
24
+ *
25
+ * @var string
26
+ */
27
+ const FLAG_NAME_FORCE_VALIDATION = 'force';
28
+
29
+ /**
30
+ * The WP-CLI argument to validate based on certain conditionals
31
+ *
32
+ * For example, --include=is_tag,is_author
33
+ * Normally, this script will validate all of the templates that don't have AMP disabled.
34
+ * But this allows validating only specific templates.
35
+ *
36
+ * @var string
37
+ */
38
+ const INCLUDE_ARGUMENT = 'include';
39
+
40
+ /**
41
+ * The WP-CLI argument for the maximum URLs to validate for each type.
42
+ *
43
+ * If this is passed in the command,
44
+ * it overrides the value of self::$maximum_urls_to_validate_for_each_type.
45
+ *
46
+ * @var string
47
+ */
48
+ const LIMIT_URLS_ARGUMENT = 'limit';
49
+
50
+ /**
51
+ * The WP CLI progress bar.
52
+ *
53
+ * @see https://make.wordpress.org/cli/handbook/internal-api/wp-cli-utils-make-progress-bar/
54
+ * @var \cli\progress\Bar|\WP_CLI\NoOp
55
+ */
56
+ public static $wp_cli_progress;
57
+
58
+ /**
59
+ * The total number of validation errors, regardless of whether they were accepted.
60
+ *
61
+ * @var int
62
+ */
63
+ public static $total_errors = 0;
64
+
65
+ /**
66
+ * The total number of unaccepted validation errors.
67
+ *
68
+ * If an error has been accepted in the /wp-admin validation UI,
69
+ * it won't count toward this.
70
+ *
71
+ * @var int
72
+ */
73
+ public static $unaccepted_errors = 0;
74
+
75
+ /**
76
+ * The number of URLs crawled, regardless of whether they have validation errors.
77
+ *
78
+ * @var int
79
+ */
80
+ public static $number_crawled = 0;
81
+
82
+ /**
83
+ * Whether to force crawling of URLs.
84
+ *
85
+ * By default, this script only crawls URLs that support AMP,
86
+ * where the user has not opted-out of AMP for the URL.
87
+ * For example, by un-checking 'Posts' in 'AMP Settings' > 'Supported Templates'.
88
+ * Or un-checking 'Enable AMP' in the post's editor.
89
+ *
90
+ * @var int
91
+ */
92
+ public static $force_crawl_urls = false;
93
+
94
+ /**
95
+ * A whitelist of conditionals to use for validation.
96
+ *
97
+ * Usually, this script will validate all of the templates that don't have AMP disabled.
98
+ * But this allows validating based on only these conditionals.
99
+ * This is set if the WP-CLI command has an --include argument.
100
+ *
101
+ * @var array
102
+ */
103
+ public static $include_conditionals = array();
104
+
105
+ /**
106
+ * The maximum number of URLs to validate for each type.
107
+ *
108
+ * Templates are each a separate type, like those for is_category() and is_tag().
109
+ * Also, each post type is a separate type.
110
+ * This value is overridden if the WP-CLI command has an --limit argument, like --limit=10.
111
+ *
112
+ * @var int
113
+ */
114
+ public static $limit_type_validate_count;
115
+
116
+ /**
117
+ * The validation counts by type, like template or post type.
118
+ *
119
+ * @var array[][] {
120
+ * Validity by type.
121
+ *
122
+ * @type int $valid The number of valid URLs for this type.
123
+ * @type int $total The total number of URLs for this type, valid or invalid.
124
+ * }
125
+ */
126
+ public static $validity_by_type = array();
127
+
128
+ /**
129
+ * Crawl the entire site to get AMP validation results.
130
+ *
131
+ * ## OPTIONS
132
+ *
133
+ * [--limit=<count>]
134
+ * : The maximum number of URLs to validate for each template and content type.
135
+ * ---
136
+ * default: 100
137
+ * ---
138
+ *
139
+ * [--include=<templates>]
140
+ * : Only validates a URL if one of the conditionals is true.
141
+ *
142
+ * [--force]
143
+ * : Force validation of URLs even if their associated templates or object types do not have AMP enabled.
144
+ *
145
+ * ## EXAMPLES
146
+ *
147
+ * wp amp validate-site --include=is_author,is_tag
148
+ *
149
+ * @subcommand validate-site
150
+ * @param array $args Positional args.
151
+ * @param array $assoc_args Associative args.
152
+ * @throws Exception If an error happens.
153
+ */
154
+ public function validate_site( $args, $assoc_args ) {
155
+ unset( $args );
156
+ self::$include_conditionals = array();
157
+ self::$force_crawl_urls = false;
158
+ self::$limit_type_validate_count = (int) $assoc_args[ self::LIMIT_URLS_ARGUMENT ];
159
+
160
+ /*
161
+ * Handle the argument and flag passed to the command: --include and --force.
162
+ * If the self::INCLUDE_ARGUMENT is present, force crawling or URLs.
163
+ * The WP-CLI command should indicate which templates are crawled, not the /wp-admin options.
164
+ */
165
+ if ( ! empty( $assoc_args[ self::INCLUDE_ARGUMENT ] ) ) {
166
+ self::$include_conditionals = explode( ',', $assoc_args[ self::INCLUDE_ARGUMENT ] );
167
+ self::$force_crawl_urls = true;
168
+ } elseif ( isset( $assoc_args[ self::FLAG_NAME_FORCE_VALIDATION ] ) ) {
169
+ self::$force_crawl_urls = true;
170
+ }
171
+
172
+ if ( ! current_theme_supports( AMP_Theme_Support::SLUG ) ) {
173
+ if ( self::$force_crawl_urls ) {
174
+ /*
175
+ * There is no theme support added programmatically or via options.
176
+ * So make sure that theme support is present so that AMP_Validation_Manager::validate_url()
177
+ * will use a canonical URL as the basis for obtaining validation results.
178
+ */
179
+ add_theme_support( AMP_Theme_Support::SLUG );
180
+ } else {
181
+ WP_CLI::error(
182
+ sprintf(
183
+ /* translators: %s is the flag to force validation */
184
+ __( 'The current template mode is Classic, which does not allow crawling the site. Please pass the --%s flag in order to force crawling for validation.', 'amp' ),
185
+ self::FLAG_NAME_FORCE_VALIDATION
186
+ )
187
+ );
188
+ }
189
+ }
190
+
191
+ $number_urls_to_crawl = self::count_urls_to_validate();
192
+ if ( ! $number_urls_to_crawl ) {
193
+ if ( ! empty( self::$include_conditionals ) ) {
194
+ WP_CLI::error(
195
+ sprintf(
196
+ /* translators: %s is the command line argument to include certain templates */
197
+ __( 'The templates passed via the --%s argument did not match any URLs. You might try passing different templates to it.', 'amp' ),
198
+ self::INCLUDE_ARGUMENT
199
+ )
200
+ );
201
+ } else {
202
+ WP_CLI::error(
203
+ sprintf(
204
+ /* translators: %s is the command line argument to force validation */
205
+ __( 'All of your templates might be unchecked in AMP Settings > Supported Templates. You might pass --%s to this command.', 'amp' ),
206
+ self::FLAG_NAME_FORCE_VALIDATION
207
+ )
208
+ );
209
+ }
210
+ }
211
+
212
+ WP_CLI::log( __( 'Crawling the site for AMP validity.', 'amp' ) );
213
+
214
+ self::$wp_cli_progress = WP_CLI\Utils\make_progress_bar(
215
+ /* translators: %d is the number of URLs */
216
+ sprintf( __( 'Validating %d URLs...', 'amp' ), $number_urls_to_crawl ),
217
+ $number_urls_to_crawl
218
+ );
219
+ self::crawl_site();
220
+ self::$wp_cli_progress->finish();
221
+
222
+ $key_template_type = __( 'Template or content type', 'amp' );
223
+ $key_url_count = __( 'URL Count', 'amp' );
224
+ $key_validity_rate = __( 'Validity Rate', 'amp' );
225
+
226
+ $table_validation_by_type = array();
227
+ foreach ( self::$validity_by_type as $type_name => $validity ) {
228
+ $table_validation_by_type[] = array(
229
+ $key_template_type => $type_name,
230
+ $key_url_count => $validity['total'],
231
+ $key_validity_rate => sprintf( '%d%%', 100.0 * ( $validity['valid'] / $validity['total'] ) ),
232
+ );
233
+ }
234
+
235
+ if ( empty( $table_validation_by_type ) ) {
236
+ WP_CLI::error( __( 'No validation results were obtained from the URLs.', 'amp' ) );
237
+ return;
238
+ }
239
+
240
+ WP_CLI::success(
241
+ sprintf(
242
+ /* translators: $1%d is the number of URls crawled, $2%d is the number of validation issues, $3%d is the number of unaccepted issues, $4%s is the list of validation by type, $5%s is the link for more details */
243
+ __( '%3$d crawled URLs have unaccepted issue(s) out of %2$d total with AMP validation issue(s); %1$d URLs were crawled.', 'amp' ),
244
+ self::$number_crawled,
245
+ self::$total_errors,
246
+ self::$unaccepted_errors
247
+ )
248
+ );
249
+
250
+ // Output a table of validity by template/content type.
251
+ WP_CLI\Utils\format_items(
252
+ 'table',
253
+ $table_validation_by_type,
254
+ array( $key_template_type, $key_url_count, $key_validity_rate )
255
+ );
256
+
257
+ $url_more_details = add_query_arg(
258
+ 'post_type',
259
+ AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
260
+ admin_url( 'edit.php' )
261
+ );
262
+ /* translators: %s is the URL to the admin */
263
+ WP_CLI::line( sprintf( __( 'For more details, please see: %s', 'amp' ), $url_more_details ) );
264
+ }
265
+
266
+ /**
267
+ * Reset all validation data on a site.
268
+ *
269
+ * This deletes all amp_validated_url posts and all amp_validation_error terms.
270
+ *
271
+ * ## OPTIONS
272
+ *
273
+ * [--yes]
274
+ * : Proceed to empty the site validation data without a confirmation prompt.
275
+ *
276
+ * ## EXAMPLES
277
+ *
278
+ * wp amp reset-site-validation --yes
279
+ *
280
+ * @subcommand reset-site-validation
281
+ * @param array $args Positional args. Unused.
282
+ * @param array $assoc_args Associative args.
283
+ * @throws Exception If an error happens.
284
+ */
285
+ public function reset_site_validation( $args, $assoc_args ) {
286
+ unset( $args );
287
+ global $wpdb;
288
+ WP_CLI::confirm( 'Are you sure you want to empty all amp_validated_url posts and amp_validation_error taxonomy terms?', $assoc_args );
289
+
290
+ // Delete all posts.
291
+ $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = %s", AMP_Validated_URL_Post_Type::POST_TYPE_SLUG ) );
292
+ $query = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = %s", AMP_Validated_URL_Post_Type::POST_TYPE_SLUG );
293
+ $posts = new WP_CLI\Iterators\Query( $query, 10000 );
294
+
295
+ $progress = WP_CLI\Utils\make_progress_bar(
296
+ /* translators: %d is the number of posts */
297
+ sprintf( __( 'Deleting %d amp_validated_url posts...', 'amp' ), $count ),
298
+ $count
299
+ );
300
+ while ( $posts->valid() ) {
301
+ $post_id = $posts->current()->ID;
302
+ $posts->next();
303
+ wp_delete_post( $post_id, true );
304
+ $progress->tick();
305
+ }
306
+ $progress->finish();
307
+
308
+ // Delete all terms. Note that many terms should get deleted when their post counts go to zero above.
309
+ $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM $wpdb->term_taxonomy WHERE taxonomy = %s", AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG ) );
310
+ $query = $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s", AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG );
311
+ $terms = new WP_CLI\Iterators\Query( $query, 10000 );
312
+
313
+ $progress = WP_CLI\Utils\make_progress_bar(
314
+ /* translators: %d is the number of terms */
315
+ sprintf( __( 'Deleting %d amp_taxonomy_error terms...', 'amp' ), $count ),
316
+ $count
317
+ );
318
+ while ( $terms->valid() ) {
319
+ $term_id = $terms->current()->term_id;
320
+ $terms->next();
321
+ wp_delete_term( $term_id, AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG );
322
+ $progress->tick();
323
+ }
324
+ $progress->finish();
325
+
326
+ WP_CLI::success( 'All AMP validation data has been removed.' );
327
+ }
328
+
329
+ /**
330
+ * Gets the total number of URLs to validate.
331
+ *
332
+ * By default, this only counts AMP-enabled posts and terms.
333
+ * But if $force_crawl_urls is true, it counts all of them, regardless of their AMP status.
334
+ * It also uses self::$maximum_urls_to_validate_for_each_type,
335
+ * which can be overridden with a command line argument.
336
+ *
337
+ * @return int The number of URLs to validate.
338
+ */
339
+ public static function count_urls_to_validate() {
340
+ /*
341
+ * If the homepage is set to 'Your latest posts,' start the $total_count at 1.
342
+ * Otherwise, it will probably be counted in the query for pages below.
343
+ */
344
+ $total_count = 'posts' === get_option( 'show_on_front' ) && self::is_template_supported( 'is_home' ) ? 1 : 0;
345
+
346
+ $amp_enabled_taxonomies = array_filter(
347
+ get_taxonomies( array( 'public' => true ) ),
348
+ array( 'AMP_CLI', 'does_taxonomy_support_amp' )
349
+ );
350
+
351
+ // Count all public taxonomy terms.
352
+ foreach ( $amp_enabled_taxonomies as $taxonomy ) {
353
+ $term_query = new WP_Term_Query( array(
354
+ 'taxonomy' => $taxonomy,
355
+ 'fields' => 'ids',
356
+ 'number' => self::$limit_type_validate_count,
357
+ ) );
358
+
359
+ // If $term_query->terms is an empty array, passing it to count() will throw an error.
360
+ $total_count += ! empty( $term_query->terms ) ? count( $term_query->terms ) : 0;
361
+ }
362
+
363
+ // Count posts by type, like post, page, attachment, etc.
364
+ $public_post_types = get_post_types( array( 'public' => true ), 'names' );
365
+ foreach ( $public_post_types as $post_type ) {
366
+ $posts = self::get_posts_that_support_amp( self::get_posts_by_type( $post_type ) );
367
+ $total_count += ! empty( $posts ) ? count( $posts ) : 0;
368
+ }
369
+
370
+ // Count author pages, like https://example.com/author/admin/.
371
+ $total_count += count( self::get_author_page_urls() );
372
+
373
+ // Count a single example date page, like https://example.com/?year=2019.
374
+ if ( self::get_date_page() ) {
375
+ $total_count++;
376
+ }
377
+
378
+ // Count a single example search page, like https://example.com/?s=example.
379
+ if ( self::get_search_page() ) {
380
+ $total_count++;
381
+ }
382
+
383
+ return $total_count;
384
+ }
385
+
386
+ /**
387
+ * Gets the posts IDs that support AMP.
388
+ *
389
+ * By default, this only gets the post IDs if they support AMP.
390
+ * This means that 'Posts' isn't deselected in 'AMP Settings' > 'Supported Templates'.
391
+ * And 'Enable AMP' isn't unchecked in the post's editor.
392
+ * But if $force_crawl_urls is true, this simply returns all of the IDs.
393
+ *
394
+ * @param array $ids THe post IDs to check for AMP support.
395
+ * @return array The post IDs that support AMP, or an empty array.
396
+ */
397
+ public static function get_posts_that_support_amp( $ids ) {
398
+ if ( ! self::is_template_supported( 'is_singular' ) ) {
399
+ return array();
400
+ }
401
+
402
+ if ( self::$force_crawl_urls ) {
403
+ return $ids;
404
+ }
405
+
406
+ return array_filter(
407
+ $ids,
408
+ function( $id ) {
409
+ return post_supports_amp( $id );
410
+ }
411
+ );
412
+ }
413
+
414
+ /**
415
+ * Gets whether the taxonomy supports AMP.
416
+ *
417
+ * This only gets the term IDs if they support AMP.
418
+ * If their taxonomy is unchecked in 'AMP Settings' > 'Supported Templates,' this does not return them.
419
+ * For example, if 'Categories' is unchecked.
420
+ * This can be overridden by passing the self::FLAG_NAME_FORCE_VALIDATION argument to the WP-CLI command.
421
+ *
422
+ * @param string $taxonomy The taxonomy.
423
+ * @return boolean Whether the taxonomy supports AMP.
424
+ */
425
+ public static function does_taxonomy_support_amp( $taxonomy ) {
426
+ if ( 'post_tag' === $taxonomy ) {
427
+ $taxonomy = 'tag';
428
+ }
429
+ $taxonomy_key = 'is_' . $taxonomy;
430
+ $custom_taxonomy_key = sprintf( 'is_tax[%s]', $taxonomy );
431
+ return self::is_template_supported( $taxonomy_key ) || self::is_template_supported( $custom_taxonomy_key );
432
+ }
433
+
434
+ /**
435
+ * Gets whether the template is supported.
436
+ *
437
+ * If the user has passed an include argument to the WP-CLI command, use that to find if this template supports AMP.
438
+ * For example, wp amp validate-site --include=is_tag,is_category
439
+ * would return true only if is_tag() or is_category().
440
+ * But passing the self::FLAG_NAME_FORCE_VALIDATION argument to the WP-CLI command overrides this.
441
+ *
442
+ * @param string $template The template to check.
443
+ * @return bool Whether the template is supported.
444
+ */
445
+ public static function is_template_supported( $template ) {
446
+ // If the --include argument is present in the WP-CLI command, this template conditional must be present in it.
447
+ if ( ! empty( self::$include_conditionals ) ) {
448
+ return in_array( $template, self::$include_conditionals, true );
449
+ }
450
+ if ( self::$force_crawl_urls ) {
451
+ return true;
452
+ }
453
+
454
+ $supportable_templates = AMP_Theme_Support::get_supportable_templates();
455
+
456
+ // Check whether this taxonomy's template is supported, including in the 'AMP Settings' > 'Supported Templates' UI.
457
+ return ! empty( $supportable_templates[ $template ]['supported'] );
458
+ }
459
+
460
+ /**
461
+ * Gets the IDs of public, published posts.
462
+ *
463
+ * @param string $post_type The post type.
464
+ * @param int|null $offset The offset of the query (optional).
465
+ * @param int|null $number The number of posts to query for (optional).
466
+ * @return int[] $post_ids The post IDs in an array.
467
+ */
468
+ public static function get_posts_by_type( $post_type, $offset = null, $number = null ) {
469
+ $args = array(
470
+ 'post_type' => $post_type,
471
+ 'posts_per_page' => is_int( $number ) ? $number : self::$limit_type_validate_count,
472
+ 'post_status' => 'publish',
473
+ 'orderby' => 'ID',
474
+ 'order' => 'DESC',
475
+ 'fields' => 'ids',
476
+ );
477
+ if ( is_int( $offset ) ) {
478
+ $args['offset'] = $offset;
479
+ }
480
+
481
+ // Attachment posts usually have the post_status of 'inherit,' so they can use the status of the post they're attached to.
482
+ if ( 'attachment' === $post_type ) {
483
+ $args['post_status'] = 'inherit';
484
+ }
485
+ $query = new WP_Query( $args );
486
+
487
+ return $query->posts;
488
+ }
489
+
490
+ /**
491
+ * Gets the front-end links for taxonomy terms.
492
+ * For example, https://example.org/?cat=2
493
+ *
494
+ * @param string $taxonomy The name of the taxonomy, like 'category' or 'post_tag'.
495
+ * @param int|string $offset The number at which to offset the query (optional).
496
+ * @param int $number The maximum amount of links to get (optional).
497
+ * @return string[] The term links, as an array of strings.
498
+ */
499
+ public static function get_taxonomy_links( $taxonomy, $offset = '', $number = 1 ) {
500
+ return array_map(
501
+ 'get_term_link',
502
+ get_terms(
503
+ array_merge(
504
+ compact( 'taxonomy', 'offset', 'number' ),
505
+ array(
506
+ 'orderby' => 'id',
507
+ )
508
+ )
509
+ )
510
+ );
511
+ }
512
+
513
+ /**
514
+ * Gets the author page URLs, like https://example.com/author/admin/.
515
+ *
516
+ * Accepts an $offset parameter, for the query of authors.
517
+ * 0 is the first author in the query, and 1 is the second.
518
+ *
519
+ * @param int|string $offset The offset for the URL to query for, should be an int if passing an argument.
520
+ * @param int|string $number The total number to query for, should be an int if passing an argument.
521
+ * @return array The author page URLs, or an empty array.
522
+ */
523
+ public static function get_author_page_urls( $offset = '', $number = '' ) {
524
+ $author_page_urls = array();
525
+ if ( ! self::is_template_supported( 'is_author' ) ) {
526
+ return $author_page_urls;
527
+ }
528
+
529
+ $number = ! empty( $number ) ? $number : self::$limit_type_validate_count;
530
+ foreach ( get_users( compact( 'offset', 'number' ) ) as $author ) {
531
+ $author_page_urls[] = get_author_posts_url( $author->ID, $author->user_nicename );
532
+ }
533
+
534
+ return $author_page_urls;
535
+ }
536
+
537
+ /**
538
+ * Gets a single search page URL, like https://example.com/?s=example.
539
+ *
540
+ * @return string|null An example search page, or null.
541
+ */
542
+ public static function get_search_page() {
543
+ if ( ! self::is_template_supported( 'is_search' ) ) {
544
+ return null;
545
+ }
546
+
547
+ return add_query_arg( 's', 'example', home_url( '/' ) );
548
+ }
549
+
550
+ /**
551
+ * Gets a single date page URL, like https://example.com/?year=2018.
552
+ *
553
+ * @return string|null An example search page, or null.
554
+ */
555
+ public static function get_date_page() {
556
+ if ( ! self::is_template_supported( 'is_date' ) ) {
557
+ return null;
558
+ }
559
+
560
+ return add_query_arg( 'year', date( 'Y' ), home_url( '/' ) );
561
+ }
562
+
563
+ /**
564
+ * Validates the URLs of the entire site.
565
+ *
566
+ * Includes the URLs of public, published posts, public taxonomies, and other templates.
567
+ * This validates one of each type at a time,
568
+ * and iterates until it reaches the maximum number of URLs for each type.
569
+ */
570
+ public static function crawl_site() {
571
+ /*
572
+ * If 'Your homepage displays' is set to 'Your latest posts', validate the homepage.
573
+ * It will not be part of the page validation below.
574
+ */
575
+ if ( 'posts' === get_option( 'show_on_front' ) && self::is_template_supported( 'is_home' ) ) {
576
+ self::validate_and_store_url( home_url( '/' ), 'home' );
577
+ }
578
+
579
+ $amp_enabled_taxonomies = array_filter(
580
+ get_taxonomies( array( 'public' => true ) ),
581
+ array( 'AMP_CLI', 'does_taxonomy_support_amp' )
582
+ );
583
+ $public_post_types = get_post_types( array( 'public' => true ), 'names' );
584
+
585
+ // Validate one URL of each template/content type, then another URL of each type on the next iteration.
586
+ for ( $i = 0; $i < self::$limit_type_validate_count; $i++ ) {
587
+ // Validate all public, published posts.
588
+ foreach ( $public_post_types as $post_type ) {
589
+ $post_ids = self::get_posts_that_support_amp( self::get_posts_by_type( $post_type, $i, 1 ) );
590
+ if ( ! empty( $post_ids[0] ) ) {
591
+ self::validate_and_store_url( get_permalink( $post_ids[0] ), $post_type );
592
+ }
593
+ }
594
+
595
+ foreach ( $amp_enabled_taxonomies as $taxonomy ) {
596
+ $taxonomy_links = self::get_taxonomy_links( $taxonomy, $i, 1 );
597
+ $link = reset( $taxonomy_links );
598
+ if ( ! empty( $link ) ) {
599
+ self::validate_and_store_url( $link, $taxonomy );
600
+ }
601
+ }
602
+
603
+ $author_page_urls = self::get_author_page_urls( $i, 1 );
604
+ if ( ! empty( $author_page_urls[0] ) ) {
605
+ self::validate_and_store_url( $author_page_urls[0], 'author' );
606
+ }
607
+ }
608
+
609
+ // Only validate 1 date and 1 search page.
610
+ $url = self::get_date_page();
611
+ if ( $url ) {
612
+ self::validate_and_store_url( $url, 'date' );
613
+ }
614
+ $url = self::get_search_page();
615
+ if ( $url ) {
616
+ self::validate_and_store_url( $url, 'search' );
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Validates the URL, stores the results, and increments the counts.
622
+ *
623
+ * @param string $url The URL to validate.
624
+ * @param string $type The type of template, post, or taxonomy.
625
+ */
626
+ public static function validate_and_store_url( $url, $type ) {
627
+ $validity = AMP_Validation_Manager::validate_url( $url );
628
+
629
+ /*
630
+ * If the request to validate this returns a WP_Error, return.
631
+ * One cause of an error is if the validation request results in a 404 response code.
632
+ */
633
+ if ( is_wp_error( $validity ) ) {
634
+ /* translators: %1$s is error code, %2$s is error message, and %3$s is the actual URL. */
635
+ WP_CLI::warning( sprintf( __( 'Validate URL error (%1$s): %2$s URL: %3$s', 'amp' ), $validity->get_error_code(), $validity->get_error_message(), $url ) );
636
+ return;
637
+ }
638
+ if ( self::$wp_cli_progress ) {
639
+ self::$wp_cli_progress->tick();
640
+ }
641
+
642
+ $validation_errors = wp_list_pluck( $validity['results'], 'error' );
643
+ AMP_Validated_URL_Post_Type::store_validation_errors(
644
+ $validation_errors,
645
+ $validity['url'],
646
+ wp_array_slice_assoc( $validity, array( 'queried_object' ) )
647
+ );
648
+ $unaccepted_error_count = count( array_filter(
649
+ $validation_errors,
650
+ function( $error ) {
651
+ $validation_status = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( $error );
652
+ return (
653
+ AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACK_ACCEPTED_STATUS !== $validation_status['term_status']
654
+ &&
655
+ AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_ACCEPTED_STATUS !== $validation_status['term_status']
656
+ );
657
+ }
658
+ ) );
659
+
660
+ if ( count( $validation_errors ) > 0 ) {
661
+ self::$total_errors++;
662
+ }
663
+ if ( $unaccepted_error_count > 0 ) {
664
+ self::$unaccepted_errors++;
665
+ }
666
+
667
+ self::$number_crawled++;
668
+
669
+ if ( ! isset( self::$validity_by_type[ $type ] ) ) {
670
+ self::$validity_by_type[ $type ] = array(
671
+ 'valid' => 0,
672
+ 'total' => 0,
673
+ );
674
+ }
675
+ self::$validity_by_type[ $type ]['total']++;
676
+ if ( 0 === $unaccepted_error_count ) {
677
+ self::$validity_by_type[ $type ]['valid']++;
678
+ }
679
+ }
680
+ }
includes/class-amp-http.php ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_HTTP
4
+ *
5
+ * @since 1.0
6
+ * @package AMP
7
+ */
8
+
9
+ /**
10
+ * Class AMP_HTTP
11
+ */
12
+ class AMP_HTTP {
13
+
14
+ /**
15
+ * Headers sent (or attempted to be sent).
16
+ *
17
+ * @since 1.0
18
+ * @see AMP_HTTP::send_header()
19
+ * @var array[]
20
+ */
21
+ public static $headers_sent = array();
22
+
23
+ /**
24
+ * AMP-specific query vars that were purged.
25
+ *
26
+ * @since 0.7
27
+ * @since 1.0 Moved to AMP_HTTP class.
28
+ * @see AMP_HTTP::purge_amp_query_vars()
29
+ * @var string[]
30
+ */
31
+ public static $purged_amp_query_vars = array();
32
+
33
+ /**
34
+ * Send an HTTP response header.
35
+ *
36
+ * This largely exists to facilitate unit testing but it also provides a better interface for sending headers.
37
+ *
38
+ * @since 0.7.0
39
+ * @since 1.0 Moved to AMP_HTTP class.
40
+ *
41
+ * @param string $name Header name.
42
+ * @param string $value Header value.
43
+ * @param array $args {
44
+ * Args to header().
45
+ *
46
+ * @type bool $replace Whether to replace a header previously sent. Default true.
47
+ * @type int $status_code Status code to send with the sent header.
48
+ * }
49
+ * @return bool Whether the header was sent.
50
+ */
51
+ public static function send_header( $name, $value, $args = array() ) {
52
+ $args = array_merge(
53
+ array(
54
+ 'replace' => true,
55
+ 'status_code' => null,
56
+ ),
57
+ $args
58
+ );
59
+
60
+ self::$headers_sent[] = array_merge( compact( 'name', 'value' ), $args );
61
+ if ( headers_sent() ) {
62
+ return false;
63
+ }
64
+
65
+ header(
66
+ sprintf( '%s: %s', $name, $value ),
67
+ $args['replace'],
68
+ $args['status_code']
69
+ );
70
+ return true;
71
+ }
72
+
73
+ /**
74
+ * Send Server-Timing header.
75
+ *
76
+ * If WP_DEBUG is not enabled and an admin user (who can manage_options) is not logged-in, the Server-Header will not be sent.
77
+ *
78
+ * @since 1.0
79
+ *
80
+ * @param string $name Name.
81
+ * @param float $duration Duration. If negative, will be added to microtime( true ). Optional.
82
+ * @param string $description Description. Optional.
83
+ * @return bool Return value of send_header call. If WP_DEBUG is not enabled or admin user (who can manage_options) is not logged-in, this will always return false.
84
+ */
85
+ public static function send_server_timing( $name, $duration = null, $description = null ) {
86
+ if ( ! WP_DEBUG && ! current_user_can( 'manage_options' ) ) {
87
+ return false;
88
+ }
89
+ $value = $name;
90
+ if ( isset( $description ) ) {
91
+ $value .= sprintf( ';desc="%s"', str_replace( array( '\\', '"' ), '', substr( $description, 0, 100 ) ) );
92
+ }
93
+ if ( isset( $duration ) ) {
94
+ if ( $duration < 0 ) {
95
+ $duration = microtime( true ) + $duration;
96
+ }
97
+ $value .= sprintf( ';dur=%f', $duration * 1000 );
98
+ }
99
+ return self::send_header( 'Server-Timing', $value, array( 'replace' => false ) );
100
+ }
101
+
102
+ /**
103
+ * Remove query vars that come in requests such as for amp-live-list.
104
+ *
105
+ * WordPress should generally not respond differently to requests when these parameters
106
+ * are present. In some cases, when a query param such as __amp_source_origin is present
107
+ * then it would normally get included into pagination links generated by get_pagenum_link().
108
+ * The whitelist sanitizer empties out links that contain this string as it matches the
109
+ * blacklisted_value_regex. So by preemptively scrubbing any reference to these query vars
110
+ * we can ensure that WordPress won't end up referencing them in any way.
111
+ *
112
+ * @since 0.7
113
+ * @since 1.0 Moved to AMP_HTTP class.
114
+ */
115
+ public static function purge_amp_query_vars() {
116
+ $query_vars = array(
117
+ '__amp_source_origin',
118
+ '_wp_amp_action_xhr_converted',
119
+ 'amp_latest_update_time',
120
+ 'amp_last_check_time',
121
+ );
122
+
123
+ // Scrub input vars.
124
+ foreach ( $query_vars as $query_var ) {
125
+ if ( ! isset( $_GET[ $query_var ] ) ) { // phpcs:ignore
126
+ continue;
127
+ }
128
+ self::$purged_amp_query_vars[ $query_var ] = wp_unslash( $_GET[ $query_var ] ); // phpcs:ignore
129
+ unset( $_REQUEST[ $query_var ], $_GET[ $query_var ] );
130
+ $scrubbed = true;
131
+ }
132
+
133
+ if ( isset( $scrubbed ) ) {
134
+ $build_query = function ( $query ) use ( $query_vars ) {
135
+ $pattern = '/^(' . join( '|', $query_vars ) . ')(?==|$)/';
136
+ $pairs = array();
137
+ foreach ( explode( '&', $query ) as $pair ) {
138
+ if ( ! preg_match( $pattern, $pair ) ) {
139
+ $pairs[] = $pair;
140
+ }
141
+ }
142
+
143
+ return join( '&', $pairs );
144
+ };
145
+
146
+ // Scrub QUERY_STRING.
147
+ if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
148
+ $_SERVER['QUERY_STRING'] = $build_query( $_SERVER['QUERY_STRING'] );
149
+ }
150
+
151
+ // Scrub REQUEST_URI.
152
+ if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
153
+ list( $path, $query ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
154
+
155
+ $pairs = $build_query( $query );
156
+ $_SERVER['REQUEST_URI'] = $path;
157
+ if ( ! empty( $pairs ) ) {
158
+ $_SERVER['REQUEST_URI'] .= "?{$pairs}";
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Filter the allowed redirect hosts to include AMP caches.
166
+ *
167
+ * @since 1.0
168
+ *
169
+ * @param array $allowed_hosts Allowed hosts.
170
+ * @return array Allowed redirect hosts.
171
+ */
172
+ public static function filter_allowed_redirect_hosts( $allowed_hosts ) {
173
+ return array_merge( $allowed_hosts, self::get_amp_cache_hosts() );
174
+ }
175
+
176
+ /**
177
+ * Get list of AMP cache hosts (that is, CORS origins).
178
+ *
179
+ * @since 1.0
180
+ * @link https://www.ampproject.org/docs/fundamentals/amp-cors-requests#1)-allow-requests-for-specific-cors-origins
181
+ *
182
+ * @return array AMP cache hosts.
183
+ */
184
+ public static function get_amp_cache_hosts() {
185
+ $hosts = array();
186
+
187
+ // Google AMP Cache (legacy).
188
+ $hosts[] = 'cdn.ampproject.org';
189
+
190
+ // From the publisher’s own origins.
191
+ $domains = array_unique( array(
192
+ wp_parse_url( site_url(), PHP_URL_HOST ),
193
+ wp_parse_url( home_url(), PHP_URL_HOST ),
194
+ ) );
195
+
196
+ /*
197
+ * From AMP docs:
198
+ * "When possible, the Google AMP Cache will create a subdomain for each AMP document's domain by first converting it
199
+ * from IDN (punycode) to UTF-8. The caches replaces every - (dash) with -- (2 dashes) and replace every . (dot) with
200
+ * - (dash). For example, pub.com will map to pub-com.cdn.ampproject.org."
201
+ */
202
+ foreach ( $domains as $domain ) {
203
+ if ( function_exists( 'idn_to_utf8' ) ) {
204
+ if ( version_compare( PHP_VERSION, '5.4', '>=' ) && defined( 'INTL_IDNA_VARIANT_UTS46' ) ) {
205
+ $domain = idn_to_utf8( $domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46 ); // phpcs:ignore PHPCompatibility.PHP.NewFunctionParameters.idn_to_utf8_variantFound, PHPCompatibility.PHP.NewConstants.intl_idna_variant_uts46Found
206
+ } else {
207
+ $domain = idn_to_utf8( $domain );
208
+ }
209
+ }
210
+ $subdomain = str_replace( '-', '--', $domain );
211
+ $subdomain = str_replace( '.', '-', $subdomain );
212
+
213
+ // Google AMP Cache subdomain.
214
+ $hosts[] = sprintf( '%s.cdn.ampproject.org', $subdomain );
215
+
216
+ // Cloudflare AMP Cache.
217
+ $hosts[] = sprintf( '%s.amp.cloudflare.com', $subdomain );
218
+
219
+ // Bing AMP Cache.
220
+ $hosts[] = sprintf( '%s.bing-amp.com', $subdomain );
221
+ }
222
+
223
+ return $hosts;
224
+ }
225
+
226
+ /**
227
+ * Send cors headers.
228
+ *
229
+ * From the AMP docs:
230
+ * Restrict requests to source origins
231
+ * In all fetch requests, the AMP Runtime passes the "__amp_source_origin" query parameter, which contains
232
+ * the value of the source origin (for example, "https://publisher1.com").
233
+ *
234
+ * To restrict requests to only source origins, check that the value of the "__amp_source_origin" parameter
235
+ * is within a set of the Publisher's own origins.
236
+ *
237
+ * Access-Control-Allow-Origin: <origin>
238
+ * This header is a W3 CORS Spec requirement, where origin refers to the requesting origin that was allowed
239
+ * via the CORS Origin request header (for example, "https://<publisher's subdomain>.cdn.ampproject.org").
240
+ *
241
+ * Although the W3 CORS spec allows the value of * to be returned in the response, for improved security, you should:
242
+ *
243
+ * - If the Origin header is present, validate and echo the value of the Origin header.
244
+ * - If the Origin header isn't present, validate and echo the value of the "__amp_source_origin".
245
+ *
246
+ * (Otherwise, no Access-Control-Allow-Origin header is sent.)
247
+ *
248
+ * AMP-Access-Control-Allow-Source-Origin: <source-origin>
249
+ * This header allows the specified source-origin to read the authorization response. The source-origin is
250
+ * the value specified and verified in the "__amp_source_origin" URL parameter (for example, "https://publisher1.com").
251
+ *
252
+ * Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin
253
+ * This header simply allows the CORS response to contain the AMP-Access-Control-Allow-Source-Origin header.
254
+ *
255
+ * @link https://www.ampproject.org/docs/fundamentals/amp-cors-requests
256
+ * @since 1.0
257
+ */
258
+ public static function send_cors_headers() {
259
+ $origin = null;
260
+ $source_origin = null;
261
+ if ( isset( $_SERVER['HTTP_ORIGIN'] ) ) {
262
+ $origin = wp_validate_redirect( wp_sanitize_redirect( esc_url_raw( wp_unslash( $_SERVER['HTTP_ORIGIN'] ) ) ) );
263
+ }
264
+ if ( isset( self::$purged_amp_query_vars['__amp_source_origin'] ) ) {
265
+ $source_origin = wp_validate_redirect( wp_sanitize_redirect( esc_url_raw( self::$purged_amp_query_vars['__amp_source_origin'] ) ) );
266
+ }
267
+ if ( ! $origin ) {
268
+ $origin = $source_origin;
269
+ }
270
+
271
+ if ( $origin ) {
272
+ self::send_header( 'Access-Control-Allow-Origin', $origin, array( 'replace' => false ) );
273
+ self::send_header( 'Access-Control-Allow-Credentials', 'true' );
274
+ self::send_header( 'Vary', 'Origin', array( 'replace' => false ) );
275
+ }
276
+ if ( $source_origin ) {
277
+ self::send_header( 'AMP-Access-Control-Allow-Source-Origin', $source_origin );
278
+ self::send_header( 'Access-Control-Expose-Headers', 'AMP-Access-Control-Allow-Source-Origin', array( 'replace' => false ) );
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Hook into a POST form submissions, such as the comment form or some other form submission.
284
+ *
285
+ * @since 0.7.0
286
+ * @since 1.0 Moved to AMP_HTTP class. Extracted some logic to send_cors_headers method.
287
+ */
288
+ public static function handle_xhr_request() {
289
+ $is_amp_xhr = (
290
+ ! empty( self::$purged_amp_query_vars['_wp_amp_action_xhr_converted'] )
291
+ &&
292
+ ( ! empty( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] )
293
+ );
294
+ if ( ! $is_amp_xhr ) {
295
+ return;
296
+ }
297
+
298
+ // Intercept POST requests which redirect.
299
+ add_filter( 'wp_redirect', array( __CLASS__, 'intercept_post_request_redirect' ), PHP_INT_MAX );
300
+
301
+ // Add special handling for redirecting after comment submission.
302
+ add_filter( 'comment_post_redirect', array( __CLASS__, 'filter_comment_post_redirect' ), PHP_INT_MAX, 2 );
303
+
304
+ // Add die handler for AMP error display, most likely due to problem with comment.
305
+ add_filter( 'wp_die_handler', function () {
306
+ return array( __CLASS__, 'handle_wp_die' );
307
+ } );
308
+ }
309
+
310
+ /**
311
+ * Intercept the response to a POST request.
312
+ *
313
+ * @since 0.7.0
314
+ * @since 1.0 Moved to AMP_HTTP class.
315
+ * @see wp_redirect()
316
+ *
317
+ * @param string $location The location to redirect to.
318
+ */
319
+ public static function intercept_post_request_redirect( $location ) {
320
+
321
+ // Make sure relative redirects get made absolute.
322
+ $parsed_location = array_merge(
323
+ array(
324
+ 'scheme' => 'https',
325
+ 'host' => wp_parse_url( home_url(), PHP_URL_HOST ),
326
+ 'path' => isset( $_SERVER['REQUEST_URI'] ) ? strtok( wp_unslash( $_SERVER['REQUEST_URI'] ), '?' ) : '/',
327
+ ),
328
+ wp_parse_url( $location )
329
+ );
330
+
331
+ $absolute_location = '';
332
+ if ( 'https' === $parsed_location['scheme'] ) {
333
+ $absolute_location .= $parsed_location['scheme'] . ':';
334
+ }
335
+ $absolute_location .= '//' . $parsed_location['host'];
336
+ if ( isset( $parsed_location['port'] ) ) {
337
+ $absolute_location .= ':' . $parsed_location['port'];
338
+ }
339
+ $absolute_location .= $parsed_location['path'];
340
+ if ( isset( $parsed_location['query'] ) ) {
341
+ $absolute_location .= '?' . $parsed_location['query'];
342
+ }
343
+ if ( isset( $parsed_location['fragment'] ) ) {
344
+ $absolute_location .= '#' . $parsed_location['fragment'];
345
+ }
346
+
347
+ self::send_header( 'AMP-Redirect-To', $absolute_location );
348
+ self::send_header( 'Access-Control-Expose-Headers', 'AMP-Redirect-To', array( 'replace' => false ) );
349
+
350
+ wp_send_json_success();
351
+ }
352
+
353
+ /**
354
+ * New error handler for AMP form submission.
355
+ *
356
+ * @since 0.7.0
357
+ * @since 1.0 Moved to AMP_HTTP class.
358
+ * @see wp_die()
359
+ *
360
+ * @param WP_Error|string $error The error to handle.
361
+ * @param string|int $title Optional. Error title. If `$message` is a `WP_Error` object,
362
+ * error data with the key 'title' may be used to specify the title.
363
+ * If `$title` is an integer, then it is treated as the response
364
+ * code. Default empty.
365
+ * @param string|array|int $args {
366
+ * Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
367
+ * as the response code. Default empty array.
368
+ *
369
+ * @type int $response The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
370
+ * }
371
+ */
372
+ public static function handle_wp_die( $error, $title = '', $args = array() ) {
373
+ if ( is_int( $title ) ) {
374
+ $status_code = $title;
375
+ } elseif ( is_int( $args ) ) {
376
+ $status_code = $args;
377
+ } elseif ( is_array( $args ) && isset( $args['response'] ) ) {
378
+ $status_code = $args['response'];
379
+ } else {
380
+ $status_code = 500;
381
+ }
382
+ status_header( $status_code );
383
+
384
+ if ( is_wp_error( $error ) ) {
385
+ $error = $error->get_error_message();
386
+ }
387
+
388
+ // Message will be shown in template defined by AMP_Theme_Support::amend_comment_form().
389
+ wp_send_json( array(
390
+ 'error' => amp_wp_kses_mustache( $error ),
391
+ ) );
392
+ }
393
+
394
+ /**
395
+ * Handle comment_post_redirect to ensure page reload is done when comments_live_list is not supported, while sending back a success message when it is.
396
+ *
397
+ * @since 0.7.0
398
+ * @since 1.0 Moved to AMP_HTTP class.
399
+ *
400
+ * @param string $url Comment permalink to redirect to.
401
+ * @param WP_Comment $comment Posted comment.
402
+ *
403
+ * @return string|null URL if redirect to be done; otherwise function will exist.
404
+ */
405
+ public static function filter_comment_post_redirect( $url, $comment ) {
406
+ $theme_support = AMP_Theme_Support::get_theme_support_args();
407
+
408
+ // Cause a page refresh if amp-live-list is not implemented for comments via add_theme_support( AMP_Theme_Support::SLUG, array( 'comments_live_list' => true ) ).
409
+ if ( empty( $theme_support['comments_live_list'] ) ) {
410
+ /*
411
+ * Add the comment ID to the URL to force AMP to refresh the page.
412
+ * This is ideally a temporary workaround to deal with https://github.com/ampproject/amphtml/issues/14170
413
+ */
414
+ $url = add_query_arg( 'comment', $comment->comment_ID, $url );
415
+
416
+ // Pass URL along to wp_redirect().
417
+ return $url;
418
+ }
419
+
420
+ // Create a success message to display to the user.
421
+ if ( '1' === (string) $comment->comment_approved ) {
422
+ $message = __( 'Your comment has been posted.', 'amp' );
423
+ } else {
424
+ $message = __( 'Your comment is awaiting moderation.', 'default' ); // Note core string re-use.
425
+ }
426
+
427
+ /**
428
+ * Filters the message when comment submitted success message when
429
+ *
430
+ * @since 0.7
431
+ */
432
+ $message = apply_filters( 'amp_comment_posted_message', $message, $comment );
433
+
434
+ // Message will be shown in template defined by AMP_Theme_Support::amend_comment_form().
435
+ wp_send_json( array(
436
+ 'message' => amp_wp_kses_mustache( $message ),
437
+ ) );
438
+
439
+ return null;
440
+ }
441
+ }
includes/class-amp-post-type-support.php CHANGED
@@ -11,12 +11,21 @@
11
  */
12
  class AMP_Post_Type_Support {
13
 
 
 
 
 
 
 
 
14
  /**
15
  * Get post types that plugin supports out of the box (which cannot be disabled).
16
  *
 
17
  * @return string[] Post types.
18
  */
19
  public static function get_builtin_supported_post_types() {
 
20
  return array_filter( array( 'post' ), 'post_type_exists' );
21
  }
22
 
@@ -27,17 +36,12 @@ class AMP_Post_Type_Support {
27
  * @return string[] Post types eligible for AMP.
28
  */
29
  public static function get_eligible_post_types() {
30
- return array_merge(
31
- self::get_builtin_supported_post_types(),
32
- array( 'page' ),
33
- array_values( get_post_types(
34
- array(
35
- 'public' => true,
36
- '_builtin' => false,
37
- ),
38
- 'names'
39
- ) )
40
- );
41
  }
42
 
43
  /**
@@ -49,12 +53,13 @@ class AMP_Post_Type_Support {
49
  * @since 0.6
50
  */
51
  public static function add_post_type_support() {
52
- $post_types = array_merge(
53
- self::get_builtin_supported_post_types(),
54
- AMP_Options_Manager::get_option( 'supported_post_types', array() )
55
- );
 
56
  foreach ( $post_types as $post_type ) {
57
- add_post_type_support( $post_type, amp_get_slug() );
58
  }
59
  }
60
 
@@ -72,8 +77,7 @@ class AMP_Post_Type_Support {
72
  }
73
  $errors = array();
74
 
75
- // Because `add_rewrite_endpoint` doesn't let us target specific post_types.
76
- if ( isset( $post->post_type ) && ! post_type_supports( $post->post_type, amp_get_slug() ) ) {
77
  $errors[] = 'post-type-support';
78
  }
79
 
@@ -94,6 +98,48 @@ class AMP_Post_Type_Support {
94
  $errors[] = 'skip-post';
95
  }
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  return $errors;
98
  }
99
  }
11
  */
12
  class AMP_Post_Type_Support {
13
 
14
+ /**
15
+ * Post type support slug.
16
+ *
17
+ * @var string
18
+ */
19
+ const SLUG = 'amp';
20
+
21
  /**
22
  * Get post types that plugin supports out of the box (which cannot be disabled).
23
  *
24
+ * @deprecated
25
  * @return string[] Post types.
26
  */
27
  public static function get_builtin_supported_post_types() {
28
+ _deprecated_function( __METHOD__, '1.0' );
29
  return array_filter( array( 'post' ), 'post_type_exists' );
30
  }
31
 
36
  * @return string[] Post types eligible for AMP.
37
  */
38
  public static function get_eligible_post_types() {
39
+ return array_values( get_post_types(
40
+ array(
41
+ 'public' => true,
42
+ ),
43
+ 'names'
44
+ ) );
 
 
 
 
 
45
  }
46
 
47
  /**
53
  * @since 0.6
54
  */
55
  public static function add_post_type_support() {
56
+ if ( current_theme_supports( AMP_Theme_Support::SLUG ) && AMP_Options_Manager::get_option( 'all_templates_supported' ) ) {
57
+ $post_types = self::get_eligible_post_types();
58
+ } else {
59
+ $post_types = AMP_Options_Manager::get_option( 'supported_post_types', array() );
60
+ }
61
  foreach ( $post_types as $post_type ) {
62
+ add_post_type_support( $post_type, self::SLUG );
63
  }
64
  }
65
 
77
  }
78
  $errors = array();
79
 
80
+ if ( ! post_type_supports( $post->post_type, self::SLUG ) ) {
 
81
  $errors[] = 'post-type-support';
82
  }
83
 
98
  $errors[] = 'skip-post';
99
  }
100
 
101
+ $status = get_post_meta( $post->ID, AMP_Post_Meta_Box::STATUS_POST_META_KEY, true );
102
+ if ( $status ) {
103
+ if ( AMP_Post_Meta_Box::DISABLED_STATUS === $status ) {
104
+ $errors[] = 'post-status-disabled';
105
+ }
106
+ } else {
107
+ /*
108
+ * Disabled by default for custom page templates, page on front and page for posts, unless 'amp' theme
109
+ * support is present (in which case AMP_Theme_Support::get_template_availability() determines availability).
110
+ */
111
+ $enabled = (
112
+ current_theme_supports( AMP_Theme_Support::SLUG )
113
+ ||
114
+ (
115
+ ! (bool) get_page_template_slug( $post )
116
+ &&
117
+ ! (
118
+ 'page' === $post->post_type
119
+ &&
120
+ 'page' === get_option( 'show_on_front' )
121
+ &&
122
+ in_array( (int) $post->ID, array(
123
+ (int) get_option( 'page_on_front' ),
124
+ (int) get_option( 'page_for_posts' ),
125
+ ), true )
126
+ )
127
+ )
128
+ );
129
+
130
+ /**
131
+ * Filters whether default AMP status should be enabled or not.
132
+ *
133
+ * @since 0.6
134
+ *
135
+ * @param string $status Status.
136
+ * @param WP_Post $post Post.
137
+ */
138
+ $enabled = apply_filters( 'amp_post_status_default_enabled', $enabled, $post );
139
+ if ( ! $enabled ) {
140
+ $errors[] = 'post-status-disabled';
141
+ }
142
+ }
143
  return $errors;
144
  }
145
  }
includes/class-amp-theme-support.php CHANGED
@@ -13,11 +13,46 @@
13
  class AMP_Theme_Support {
14
 
15
  /**
16
- * Replaced with the necessary scripts depending on components used in output.
17
  *
18
  * @var string
19
  */
20
- const SCRIPTS_PLACEHOLDER = '<!-- AMP:SCRIPTS_PLACEHOLDER -->';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  /**
23
  * Sanitizer classes.
@@ -59,57 +94,44 @@ class AMP_Theme_Support {
59
  );
60
 
61
  /**
62
- * AMP-specific query vars that were purged.
63
  *
64
- * @since 0.7
65
- * @see AMP_Theme_Support::purge_amp_query_vars()
66
- * @var string[]
67
  */
68
- public static $purged_amp_query_vars = array();
69
 
70
  /**
71
- * Headers sent (or attempted to be sent).
72
  *
73
  * @since 0.7
74
- * @see AMP_Theme_Support::send_header()
75
- * @var array[]
76
  */
77
- public static $headers_sent = array();
78
 
79
  /**
80
- * Whether output buffering has started.
81
  *
82
- * @since 0.7
83
  * @var bool
84
  */
85
- protected static $is_output_buffering = false;
86
 
87
  /**
88
  * Initialize.
 
 
89
  */
90
  public static function init() {
91
- if ( ! current_theme_supports( 'amp' ) ) {
 
92
  return;
93
  }
94
 
95
- AMP_Validation_Utils::init();
96
-
97
- self::purge_amp_query_vars();
98
- self::handle_xhr_request();
99
 
100
  require_once AMP__DIR__ . '/includes/amp-post-template-actions.php';
101
 
102
- // Validate theme support usage.
103
- $support = get_theme_support( 'amp' );
104
- if ( WP_DEBUG && is_array( $support ) ) {
105
- $args = array_shift( $support );
106
- if ( ! is_array( $args ) ) {
107
- trigger_error( esc_html__( 'Expected AMP theme support arg to be array.', 'amp' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
108
- } elseif ( count( array_diff( array_keys( $args ), array( 'template_dir', 'available_callback', 'comments_live_list' ) ) ) !== 0 ) {
109
- trigger_error( esc_html__( 'Expected AMP theme support to only have template_dir and/or available_callback.', 'amp' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
110
- }
111
- }
112
-
113
  add_action( 'widgets_init', array( __CLASS__, 'register_widgets' ) );
114
 
115
  /*
@@ -120,6 +142,82 @@ class AMP_Theme_Support {
120
  add_action( 'wp', array( __CLASS__, 'finish_init' ), PHP_INT_MAX );
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  /**
124
  * Finish initialization once query vars are set.
125
  *
@@ -127,71 +225,133 @@ class AMP_Theme_Support {
127
  */
128
  public static function finish_init() {
129
  if ( ! is_amp_endpoint() ) {
130
- // Add amphtml link when paired mode is available.
131
- if ( self::is_paired_available() ) {
132
- amp_add_frontend_actions(); // @todo This function is poor in how it requires a file that then does add_action().
133
- if ( ! has_action( 'wp_head', 'amp_frontend_add_canonical' ) ) {
134
- add_action( 'wp_head', 'amp_frontend_add_canonical' );
135
- }
136
  }
 
 
137
  return;
138
  }
139
 
140
- if ( amp_is_canonical() ) {
141
- self::redirect_canonical_amp();
142
- } else {
143
- self::register_paired_hooks();
 
144
  }
145
 
146
  self::add_hooks();
147
  self::$sanitizer_classes = amp_get_content_sanitizers();
 
148
  self::$embed_handlers = self::register_content_embed_handlers();
 
 
 
 
 
 
 
149
  }
150
 
151
  /**
152
- * Redirect to canonical URL if the AMP URL was loaded, since canonical is now AMP.
153
  *
154
- * @since 0.7
 
 
 
155
  */
156
- public static function redirect_canonical_amp() {
157
- if ( false !== get_query_var( amp_get_slug(), false ) ) { // Because is_amp_endpoint() now returns true if amp_is_canonical().
158
- $url = preg_replace( '#^(https?://.+?)(/.*)$#', '$1', home_url( '/' ) );
159
- if ( isset( $_SERVER['REQUEST_URI'] ) ) {
160
- $url .= wp_unslash( $_SERVER['REQUEST_URI'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  }
 
 
 
162
 
163
- $url = amp_remove_endpoint( $url );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
- wp_safe_redirect( $url, 302 ); // Temporary redirect because canonical may change in future.
 
 
 
 
 
 
166
  exit;
167
  }
 
 
168
  }
169
 
170
  /**
171
  * Determines whether paired mode is available.
172
  *
173
  * When 'amp' theme support has not been added or canonical mode is enabled, then this returns false.
174
- * Returns true when there is a template_dir defined in theme support, and if a defined available_callback
175
- * returns true.
176
  *
 
 
 
177
  * @return bool Whether available.
178
  */
179
  public static function is_paired_available() {
180
- $support = get_theme_support( 'amp' );
181
- if ( empty( $support ) || amp_is_canonical() ) {
182
  return false;
183
  }
184
 
185
- if ( is_singular() && ! post_supports_amp( get_queried_object() ) ) {
186
  return false;
187
  }
188
 
189
- $args = array_shift( $support );
190
-
191
- if ( isset( $args['available_callback'] ) && is_callable( $args['available_callback'] ) ) {
192
- return call_user_func( $args['available_callback'] );
193
- }
194
- return true;
195
  }
196
 
197
  /**
@@ -207,354 +367,476 @@ class AMP_Theme_Support {
207
  }
208
 
209
  /**
210
- * Register hooks for paired mode.
211
  */
212
- public static function register_paired_hooks() {
213
  foreach ( self::$template_types as $template_type ) {
214
- add_filter( "{$template_type}_template_hierarchy", array( __CLASS__, 'filter_paired_template_hierarchy' ) );
215
  }
216
- add_filter( 'template_include', array( __CLASS__, 'filter_paired_template_include' ), 100 );
217
  }
218
 
219
  /**
220
- * Register hooks.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  */
222
- public static function add_hooks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
- // Remove core actions which are invalid AMP.
225
- remove_action( 'wp_head', 'wp_post_preview_js', 1 );
226
- remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
227
- remove_action( 'wp_print_styles', 'print_emoji_styles' );
228
- remove_action( 'wp_head', 'wp_oembed_add_host_js' );
 
229
 
230
- // Prevent MediaElement.js scripts/styles from being enqueued.
231
- add_filter( 'wp_video_shortcode_library', function() {
232
- return 'amp';
233
- } );
234
- add_filter( 'wp_audio_shortcode_library', function() {
235
- return 'amp';
236
- } );
237
 
238
- /*
239
- * Add additional markup required by AMP <https://www.ampproject.org/docs/reference/spec#required-markup>.
240
- * Note that the meta[name=viewport] is not added here because a theme may want to define one with additional
241
- * properties than included in the default configuration. If a theme doesn't include one, then the meta viewport
242
- * will be added when output buffering is finished. Note that meta charset _is_ output here because the output
243
- * buffer will need it to parse the document properly, and it must be exactly as is to be valid AMP. Nevertheless,
244
- * in this case too we should defer to the theme as well to output the meta charset because it is possible the
245
- * install is not on utf-8 and we may need to do a encoding conversion.
246
- */
247
- add_action( 'wp_print_styles', array( __CLASS__, 'print_amp_styles' ), 0 ); // Print boilerplate before theme and plugin stylesheets.
248
- add_action( 'wp_head', 'amp_add_generator_metadata', 20 );
249
 
250
- add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
251
- add_action( 'wp_enqueue_scripts', array( __CLASS__, 'dequeue_customize_preview_scripts' ), 1000 );
252
- add_filter( 'customize_partial_render', array( __CLASS__, 'filter_customize_partial_render' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
- add_action( 'wp_footer', 'amp_print_analytics' );
 
 
 
 
 
 
 
 
 
 
 
255
 
256
- /*
257
- * Disable admin bar because admin-bar.css (28K) and Dashicons (48K) alone
258
- * combine to surpass the 50K limit imposed for the amp-custom style.
259
- */
260
- add_filter( 'show_admin_bar', '__return_false', 100 );
 
 
261
 
262
- /*
263
- * Start output buffering at very low priority for sake of plugins and themes that use template_redirect
264
- * instead of template_include.
265
- */
266
- $priority = defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX; // phpcs:ignore PHPCompatibility.PHP.NewConstants.php_int_minFound
267
- add_action( 'template_redirect', array( __CLASS__, 'start_output_buffering' ), $priority );
268
 
269
- // Add validation hooks *after* output buffering has started for the response.
270
- if ( AMP_Validation_Utils::should_validate_response() ) {
271
- AMP_Validation_Utils::add_validation_hooks();
272
- }
 
 
 
 
273
 
274
- // Commenting hooks.
275
- add_filter( 'wp_list_comments_args', array( __CLASS__, 'set_comments_walker' ), PHP_INT_MAX );
276
- add_filter( 'comment_form_defaults', array( __CLASS__, 'filter_comment_form_defaults' ) );
277
- add_filter( 'comment_reply_link', array( __CLASS__, 'filter_comment_reply_link' ), 10, 4 );
278
- add_filter( 'cancel_comment_reply_link', array( __CLASS__, 'filter_cancel_comment_reply_link' ), 10, 3 );
279
- add_action( 'comment_form', array( __CLASS__, 'amend_comment_form' ), 100 );
280
- remove_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' );
281
- add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'whitelist_layout_in_wp_kses_allowed_html' ), 10 );
 
 
282
 
283
- // @todo Add character conversion.
284
- }
 
 
 
 
 
 
285
 
286
- /**
287
- * Remove query vars that come in requests such as for amp-live-list.
288
- *
289
- * WordPress should generally not respond differently to requests when these parameters
290
- * are present. In some cases, when a query param such as __amp_source_origin is present
291
- * then it would normally get included into pagination links generated by get_pagenum_link().
292
- * The whitelist sanitizer empties out links that contain this string as it matches the
293
- * blacklisted_value_regex. So by preemptively scrubbing any reference to these query vars
294
- * we can ensure that WordPress won't end up referencing them in any way.
295
- *
296
- * @since 0.7
297
- */
298
- public static function purge_amp_query_vars() {
299
- $query_vars = array(
300
- '__amp_source_origin',
301
- '_wp_amp_action_xhr_converted',
302
- 'amp_latest_update_time',
303
- 'amp_last_check_time',
304
- );
305
 
306
- // Scrub input vars.
307
- foreach ( $query_vars as $query_var ) {
308
- if ( ! isset( $_GET[ $query_var ] ) ) { // phpcs:ignore
309
- continue;
 
 
 
 
 
 
 
 
 
 
 
310
  }
311
- self::$purged_amp_query_vars[ $query_var ] = wp_unslash( $_GET[ $query_var ] ); // phpcs:ignore
312
- unset( $_REQUEST[ $query_var ], $_GET[ $query_var ] );
313
- $scrubbed = true;
314
- }
315
-
316
- if ( isset( $scrubbed ) ) {
317
- $build_query = function( $query ) use ( $query_vars ) {
318
- $pattern = '/^(' . join( '|', $query_vars ) . ')(?==|$)/';
319
- $pairs = array();
320
- foreach ( explode( '&', $query ) as $pair ) {
321
- if ( ! preg_match( $pattern, $pair ) ) {
322
- $pairs[] = $pair;
323
- }
324
  }
325
- return join( '&', $pairs );
326
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- // Scrub QUERY_STRING.
329
- if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
330
- $_SERVER['QUERY_STRING'] = $build_query( $_SERVER['QUERY_STRING'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  }
 
 
332
 
333
- // Scrub REQUEST_URI.
334
- if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
335
- list( $path, $query ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
 
336
 
337
- $pairs = $build_query( $query );
338
- $_SERVER['REQUEST_URI'] = $path;
339
- if ( ! empty( $pairs ) ) {
340
- $_SERVER['REQUEST_URI'] .= "?{$pairs}";
341
- }
 
 
 
 
 
 
 
342
  }
343
  }
 
 
344
  }
345
 
346
  /**
347
- * Send an HTTP response header.
348
- *
349
- * This largely exists to facilitate unit testing but it also provides a better interface for sending headers.
350
- *
351
- * @since 0.7.0
352
- *
353
- * @param string $name Header name.
354
- * @param string $value Header value.
355
- * @param array $args {
356
- * Args to header().
357
  *
358
- * @type bool $replace Whether to replace a header previously sent. Default true.
359
- * @type int $status_code Status code to send with the sent header.
360
- * }
361
- * @return bool Whether the header was sent.
362
  */
363
- public static function send_header( $name, $value, $args = array() ) {
364
- $args = array_merge(
365
- array(
366
- 'replace' => true,
367
- 'status_code' => null,
368
  ),
369
- $args
370
  );
 
 
 
 
 
 
 
 
 
371
 
372
- self::$headers_sent[] = array_merge( compact( 'name', 'value' ), $args );
373
- if ( headers_sent() ) {
374
- return false;
 
 
 
 
 
 
 
 
 
375
  }
376
 
377
- header(
378
- sprintf( '%s: %s', $name, $value ),
379
- $args['replace'],
380
- $args['status_code']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  );
382
- return true;
383
- }
384
 
385
- /**
386
- * Hook into a POST form submissions, such as the comment form or some other form submission.
387
- *
388
- * @since 0.7.0
389
- */
390
- public static function handle_xhr_request() {
391
- $is_amp_xhr = (
392
- ! empty( self::$purged_amp_query_vars['_wp_amp_action_xhr_converted'] )
393
- &&
394
- ! empty( self::$purged_amp_query_vars['__amp_source_origin'] )
395
- &&
396
- ( ! empty( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] )
 
 
 
 
397
  );
398
- if ( ! $is_amp_xhr ) {
399
- return;
 
 
 
 
 
 
400
  }
401
 
402
- // Send AMP response header.
403
- $origin = wp_validate_redirect( wp_sanitize_redirect( esc_url_raw( self::$purged_amp_query_vars['__amp_source_origin'] ) ) );
404
- if ( $origin ) {
405
- self::send_header( 'AMP-Access-Control-Allow-Source-Origin', $origin, array( 'replace' => true ) );
 
 
 
 
 
 
 
 
406
  }
407
 
408
- // Intercept POST requests which redirect.
409
- add_filter( 'wp_redirect', array( __CLASS__, 'intercept_post_request_redirect' ), PHP_INT_MAX );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
- // Add special handling for redirecting after comment submission.
412
- add_filter( 'comment_post_redirect', array( __CLASS__, 'filter_comment_post_redirect' ), PHP_INT_MAX, 2 );
 
 
 
413
 
414
- // Add die handler for AMP error display, most likely due to problem with comment.
415
- add_filter( 'wp_die_handler', function() {
416
- return array( __CLASS__, 'handle_wp_die' );
417
- } );
418
 
419
- }
 
420
 
421
- /**
422
- * Strip tags that are not allowed in amp-mustache.
423
- *
424
- * @since 0.7.0
425
- *
426
- * @param string $text Text to sanitize.
427
- * @return string Sanitized text.
428
- */
429
- protected static function wp_kses_amp_mustache( $text ) {
430
- $amp_mustache_allowed_html_tags = array( 'strong', 'b', 'em', 'i', 'u', 's', 'small', 'mark', 'del', 'ins', 'sup', 'sub' );
431
- return wp_kses( $text, array_fill_keys( $amp_mustache_allowed_html_tags, array() ) );
 
 
 
 
 
 
 
 
432
  }
433
 
434
  /**
435
- * Handle comment_post_redirect to ensure page reload is done when comments_live_list is not supported, while sending back a success message when it is.
436
- *
437
- * @since 0.7.0
438
- *
439
- * @param string $url Comment permalink to redirect to.
440
- * @param WP_Comment $comment Posted comment.
441
- * @return string URL.
442
  */
443
- public static function filter_comment_post_redirect( $url, $comment ) {
444
- $theme_support = get_theme_support( 'amp' );
445
 
446
- // Cause a page refresh if amp-live-list is not implemented for comments via add_theme_support( 'amp', array( 'comments_live_list' => true ) ).
447
- if ( empty( $theme_support[0]['comments_live_list'] ) ) {
448
- /*
449
- * Add the comment ID to the URL to force AMP to refresh the page.
450
- * This is ideally a temporary workaround to deal with https://github.com/ampproject/amphtml/issues/14170
451
- */
452
- $url = add_query_arg( 'comment', $comment->comment_ID, $url );
453
 
454
- // Pass URL along to wp_redirect().
455
- return $url;
456
- }
 
 
457
 
458
- // Create a success message to display to the user.
459
- if ( '1' === (string) $comment->comment_approved ) {
460
- $message = __( 'Your comment has been posted.', 'amp' );
461
- } else {
462
- $message = __( 'Your comment is awaiting moderation.', 'default' ); // Note core string re-use.
463
- }
 
 
464
 
465
- /**
466
- * Filters the message when comment submitted success message when
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  *
468
- * @since 0.7
469
  */
470
- $message = apply_filters( 'amp_comment_posted_message', $message, $comment );
 
 
 
 
 
471
 
472
- // Message will be shown in template defined by AMP_Theme_Support::amend_comment_form().
473
- wp_send_json( array(
474
- 'message' => self::wp_kses_amp_mustache( $message ),
475
- ) );
476
- }
477
 
478
- /**
479
- * New error handler for AMP form submission.
480
- *
481
- * @since 0.7.0
482
- * @see wp_die()
483
- *
484
- * @param WP_Error|string $error The error to handle.
485
- * @param string|int $title Optional. Error title. If `$message` is a `WP_Error` object,
486
- * error data with the key 'title' may be used to specify the title.
487
- * If `$title` is an integer, then it is treated as the response
488
- * code. Default empty.
489
- * @param string|array|int $args {
490
- * Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
491
- * as the response code. Default empty array.
492
- *
493
- * @type int $response The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
494
- * }
495
- */
496
- public static function handle_wp_die( $error, $title = '', $args = array() ) {
497
- if ( is_int( $title ) ) {
498
- $status_code = $title;
499
- } elseif ( is_int( $args ) ) {
500
- $status_code = $args;
501
- } elseif ( is_array( $args ) && isset( $args['response'] ) ) {
502
- $status_code = $args['response'];
503
- } else {
504
- $status_code = 500;
505
- }
506
- status_header( $status_code );
507
 
508
- if ( is_wp_error( $error ) ) {
509
- $error = $error->get_error_message();
 
 
 
 
 
 
510
  }
511
 
512
- // Message will be shown in template defined by AMP_Theme_Support::amend_comment_form().
513
- wp_send_json( array(
514
- 'error' => self::wp_kses_amp_mustache( $error ),
515
- ) );
516
- }
517
-
518
- /**
519
- * Intercept the response to a POST request.
520
- *
521
- * @since 0.7.0
522
- * @see wp_redirect()
523
- *
524
- * @param string $location The location to redirect to.
525
- */
526
- public static function intercept_post_request_redirect( $location ) {
527
-
528
- // Make sure relative redirects get made absolute.
529
- $parsed_location = array_merge(
530
- array(
531
- 'scheme' => 'https',
532
- 'host' => wp_parse_url( home_url(), PHP_URL_HOST ),
533
- 'path' => isset( $_SERVER['REQUEST_URI'] ) ? strtok( wp_unslash( $_SERVER['REQUEST_URI'] ), '?' ) : '/',
534
- ),
535
- wp_parse_url( $location )
536
- );
537
-
538
- $absolute_location = '';
539
- if ( 'https' === $parsed_location['scheme'] ) {
540
- $absolute_location .= $parsed_location['scheme'] . ':';
541
- }
542
- $absolute_location .= '//' . $parsed_location['host'];
543
- if ( isset( $parsed_location['port'] ) ) {
544
- $absolute_location .= ':' . $parsed_location['port'];
545
- }
546
- $absolute_location .= $parsed_location['path'];
547
- if ( isset( $parsed_location['query'] ) ) {
548
- $absolute_location .= '?' . $parsed_location['query'];
549
- }
550
- if ( isset( $parsed_location['fragment'] ) ) {
551
- $absolute_location .= '#' . $parsed_location['fragment'];
552
- }
553
 
554
- self::send_header( 'AMP-Redirect-To', $absolute_location );
555
- self::send_header( 'Access-Control-Expose-Headers', 'AMP-Redirect-To' );
 
 
 
 
 
 
 
 
 
 
 
 
 
556
 
557
- wp_send_json_success();
558
  }
559
 
560
  /**
@@ -658,41 +940,22 @@ class AMP_Theme_Support {
658
  /**
659
  * Prepends template hierarchy with template_dir for AMP paired mode templates.
660
  *
661
- * @see get_query_template()
662
- *
663
  * @param array $templates Template hierarchy.
664
- * @returns array Templates.
665
  */
666
- public static function filter_paired_template_hierarchy( $templates ) {
667
- $support = get_theme_support( 'amp' );
668
- $args = array_shift( $support );
669
  if ( isset( $args['template_dir'] ) ) {
670
  $amp_templates = array();
671
  foreach ( $templates as $template ) {
672
- $amp_templates[] = $args['template_dir'] . '/' . $template;
 
673
  }
674
  $templates = $amp_templates;
675
  }
676
  return $templates;
677
  }
678
 
679
- /**
680
- * Redirect to the non-canonical URL when the template to include is empty.
681
- *
682
- * This is a failsafe in case an index.php is not located in the AMP template_dir,
683
- * and the available_callback fails to omit a given request from being available in AMP.
684
- *
685
- * @param string $template Template to include.
686
- * @return string Template to include.
687
- */
688
- public static function filter_paired_template_include( $template ) {
689
- if ( empty( $template ) || ! self::is_paired_available() ) {
690
- wp_safe_redirect( self::get_current_canonical_url(), 302 ); // Temporary redirect because support may come later.
691
- exit;
692
- }
693
- return $template;
694
- }
695
-
696
  /**
697
  * Get canonical URL for current request.
698
  *
@@ -858,43 +1121,117 @@ class AMP_Theme_Support {
858
  }
859
 
860
  /**
861
- * Print AMP boilerplate and custom styles.
 
 
862
  */
863
- public static function print_amp_styles() {
864
- echo amp_get_boilerplate_code() . "\n"; // WPCS: XSS OK.
865
- echo "<style amp-custom></style>\n"; // This will by populated by AMP_Style_Sanitizer.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
866
  }
867
 
868
  /**
869
- * Ensure markup required by AMP <https://www.ampproject.org/docs/reference/spec#required-markup>.
870
  *
871
- * Ensure meta[charset], meta[name=viewport], and link[rel=canonical]; a the whitelist sanitizer
872
- * may have removed an illegal meta[http-equiv] or meta[name=viewport]. Core only outputs a
873
- * canonical URL by default if a singular post.
874
  *
875
  * @since 0.7
 
 
876
  * @todo All of this might be better placed inside of a sanitizer.
877
  *
878
- * @param DOMDocument $dom Doc.
 
879
  */
880
- public static function ensure_required_markup( DOMDocument $dom ) {
 
 
 
 
 
 
 
 
 
 
 
881
  $head = $dom->getElementsByTagName( 'head' )->item( 0 );
882
  if ( ! $head ) {
883
  $head = $dom->createElement( 'head' );
884
  $dom->documentElement->insertBefore( $head, $dom->documentElement->firstChild );
885
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886
  $meta_charset = null;
887
  $meta_viewport = null;
 
888
  foreach ( $head->getElementsByTagName( 'meta' ) as $meta ) {
889
- /**
890
- * Meta.
891
- *
892
- * @var DOMElement $meta
893
- */
894
- if ( $meta->hasAttribute( 'charset' ) && 'utf-8' === strtolower( $meta->getAttribute( 'charset' ) ) ) { // @todo Also look for meta[http-equiv="Content-Type"]?
895
  $meta_charset = $meta;
896
  } elseif ( 'viewport' === $meta->getAttribute( 'name' ) ) {
897
  $meta_viewport = $meta;
 
 
898
  }
899
  }
900
  if ( ! $meta_charset ) {
@@ -902,43 +1239,166 @@ class AMP_Theme_Support {
902
  $meta_charset = AMP_DOM_Utils::create_node( $dom, 'meta', array(
903
  'charset' => 'utf-8',
904
  ) );
905
- $head->insertBefore( $meta_charset, $head->firstChild );
 
906
  }
 
 
907
  if ( ! $meta_viewport ) {
908
  $meta_viewport = AMP_DOM_Utils::create_node( $dom, 'meta', array(
909
  'name' => 'viewport',
910
- 'content' => 'width=device-width,minimum-scale=1',
911
  ) );
912
- $head->insertBefore( $meta_viewport, $meta_charset->nextSibling );
913
- }
914
- // Prevent schema.org duplicates.
915
- $has_schema_org_metadata = false;
916
- foreach ( $head->getElementsByTagName( 'script' ) as $script ) {
917
- if ( 'application/ld+json' === $script->getAttribute( 'type' ) && false !== strpos( $script->nodeValue, 'schema.org' ) ) {
918
- $has_schema_org_metadata = true;
919
- break;
920
- }
921
  }
922
- if ( ! $has_schema_org_metadata ) {
923
- $script = $dom->createElement( 'script', wp_json_encode( amp_get_schemaorg_metadata() ) );
924
- $script->setAttribute( 'type', 'application/ld+json' );
925
- $head->appendChild( $script );
 
 
 
926
  }
927
- // Ensure rel=canonical link.
928
- $rel_canonical = null;
929
- foreach ( $head->getElementsByTagName( 'link' ) as $link ) {
930
- if ( 'canonical' === $link->getAttribute( 'rel' ) ) {
931
- $rel_canonical = $link;
932
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
933
  }
 
934
  }
935
- if ( ! $rel_canonical ) {
936
- $rel_canonical = AMP_DOM_Utils::create_node( $dom, 'link', array(
937
- 'rel' => 'canonical',
938
- 'href' => self::get_current_canonical_url(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
939
  ) );
940
- $head->appendChild( $rel_canonical );
941
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
942
  }
943
 
944
  /**
@@ -971,7 +1431,7 @@ class AMP_Theme_Support {
971
  public static function start_output_buffering() {
972
  /*
973
  * Disable the New Relic Browser agent on AMP responses.
974
- * This prevents th New Relic from causing invalid AMP responses due the NREUM script it injects after the meta charset:
975
  * https://docs.newrelic.com/docs/browser/new-relic-browser/troubleshooting/google-amp-validator-fails-due-3rd-party-script
976
  * Sites with New Relic will need to specially configure New Relic for AMP:
977
  * https://docs.newrelic.com/docs/browser/new-relic-browser/installation/monitor-amp-pages-new-relic-browser
@@ -1042,16 +1502,18 @@ class AMP_Theme_Support {
1042
  * @since 0.7
1043
  *
1044
  * @param string $response HTML document response. By default it expects a complete document.
1045
- * @param array $args {
1046
- * Args to send to the preprocessor/sanitizer.
1047
- *
1048
- * @type callable $remove_invalid_callback Function to call whenever a node is removed due to being invalid.
1049
- * }
1050
  * @return string AMP document response.
1051
  * @global int $content_width
1052
  */
1053
  public static function prepare_response( $response, $args = array() ) {
1054
  global $content_width;
 
 
 
 
 
 
1055
 
1056
  /*
1057
  * Check if the response starts with HTML markup.
@@ -1062,7 +1524,11 @@ class AMP_Theme_Support {
1062
  return $response;
1063
  }
1064
 
1065
- $is_validation_debug_mode = ! empty( $_REQUEST[ AMP_Validation_Utils::DEBUG_QUERY_VAR ] ); // WPCS: csrf ok.
 
 
 
 
1066
 
1067
  $args = array_merge(
1068
  array(
@@ -1070,11 +1536,129 @@ class AMP_Theme_Support {
1070
  'use_document_element' => true,
1071
  'allow_dirty_styles' => self::is_customize_preview_iframe(), // Dirty styles only needed when editing (e.g. for edit shortcodes).
1072
  'allow_dirty_scripts' => is_customize_preview(), // Scripts are always needed to inject changeset UUID.
1073
- 'disable_invalid_removal' => $is_validation_debug_mode,
 
 
 
 
 
 
 
 
1074
  ),
1075
  $args
1076
  );
1077
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1078
  /*
1079
  * Make sure that <meta charset> is present in output prior to parsing.
1080
  * Note that the meta charset is supposed to appear within the first 1024 bytes.
@@ -1088,65 +1672,246 @@ class AMP_Theme_Support {
1088
  1
1089
  );
1090
  }
1091
- $dom = AMP_DOM_Utils::get_dom( $response );
1092
-
1093
- $xpath = new DOMXPath( $dom );
1094
 
 
1095
  $head = $dom->getElementsByTagName( 'head' )->item( 0 );
1096
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
  if ( isset( $head ) ) {
1098
- // Make sure scripts from the body get moved to the head.
1099
  foreach ( $xpath->query( '//body//script[ @custom-element or @custom-template ]' ) as $script ) {
1100
- $head->appendChild( $script );
1101
  }
1102
  }
1103
 
1104
- // Ensure the mandatory amp attribute is present on the html element, as otherwise it will be stripped entirely.
1105
  if ( ! $dom->documentElement->hasAttribute( 'amp' ) && ! $dom->documentElement->hasAttribute( '⚡️' ) ) {
1106
  $dom->documentElement->setAttribute( 'amp', '' );
1107
  }
1108
 
 
 
1109
  $assets = AMP_Content_Sanitizer::sanitize_document( $dom, self::$sanitizer_classes, $args );
1110
 
1111
- self::ensure_required_markup( $dom );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1112
 
1113
  // @todo If 'utf-8' is not the blog charset, then we'll need to do some character encoding conversation or "entityification".
1114
  if ( 'utf-8' !== strtolower( get_bloginfo( 'charset' ) ) ) {
1115
- /* translators: %s is the charset of the current site */
1116
  trigger_error( esc_html( sprintf( __( 'The database has the %s encoding when it needs to be utf-8 to work with AMP.', 'amp' ), get_bloginfo( 'charset' ) ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
1117
  }
1118
 
1119
- if ( AMP_Validation_Utils::should_validate_response() ) {
1120
- AMP_Validation_Utils::finalize_validation( $dom, array(
1121
- 'remove_source_comments' => ! $is_validation_debug_mode,
1122
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1123
  }
1124
 
1125
  $response = "<!DOCTYPE html>\n";
1126
  $response .= AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement );
1127
 
1128
- $amp_scripts = $assets['scripts'];
1129
- foreach ( self::$embed_handlers as $embed_handler ) {
1130
- $amp_scripts = array_merge(
1131
- $amp_scripts,
1132
- $embed_handler->get_scripts()
1133
- );
 
 
 
 
 
 
 
 
1134
  }
1135
 
1136
- // Inject additional AMP component scripts which have been discovered by the sanitizers into the head.
1137
- $script_tags = amp_render_scripts( $amp_scripts );
1138
- if ( ! empty( $script_tags ) ) {
1139
- $response = preg_replace(
1140
- '#(?=</head>)#',
1141
- $script_tags,
1142
- $response,
1143
- 1
1144
- );
1145
  }
1146
 
1147
  return $response;
1148
  }
1149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1150
  /**
1151
  * Adds 'data-amp-layout' to the allowed <img> attributes for wp_kses().
1152
  *
@@ -1176,4 +1941,74 @@ class AMP_Theme_Support {
1176
  // Enqueue default styles expected by sanitizer.
1177
  wp_enqueue_style( 'amp-default', amp_get_asset_url( 'css/amp-default.css' ), array(), AMP__VERSION );
1178
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1179
  }
13
  class AMP_Theme_Support {
14
 
15
  /**
16
+ * Theme support slug.
17
  *
18
  * @var string
19
  */
20
+ const SLUG = 'amp';
21
+
22
+ /**
23
+ * Response cache group name.
24
+ *
25
+ * @var string
26
+ */
27
+ const RESPONSE_CACHE_GROUP = 'amp-response';
28
+
29
+ /**
30
+ * Post-processor cache effectiveness group name.
31
+ *
32
+ * @var string
33
+ */
34
+ const POST_PROCESSOR_CACHE_EFFECTIVENESS_GROUP = 'post_processor_cache_effectiveness_group';
35
+
36
+ /**
37
+ * Post-processor cache effectiveness key name.
38
+ *
39
+ * @var string
40
+ */
41
+ const POST_PROCESSOR_CACHE_EFFECTIVENESS_KEY = 'post_processor_cache_effectiveness';
42
+
43
+ /**
44
+ * Cache miss threshold for determining when to disable post-processor cache.
45
+ *
46
+ * @var int
47
+ */
48
+ const CACHE_MISS_THRESHOLD = 20;
49
+
50
+ /**
51
+ * Cache miss URL option name.
52
+ *
53
+ * @var string
54
+ */
55
+ const CACHE_MISS_URL_OPTION = 'amp_cache_miss_url';
56
 
57
  /**
58
  * Sanitizer classes.
94
  );
95
 
96
  /**
97
+ * Start time when init was called.
98
  *
99
+ * @since 1.0
100
+ * @var float
 
101
  */
102
+ public static $init_start_time;
103
 
104
  /**
105
+ * Whether output buffering has started.
106
  *
107
  * @since 0.7
108
+ * @var bool
 
109
  */
110
+ protected static $is_output_buffering = false;
111
 
112
  /**
113
+ * Theme support options that were added via option.
114
  *
115
+ * @since 1.0
116
  * @var bool
117
  */
118
+ protected static $support_added_via_option = false;
119
 
120
  /**
121
  * Initialize.
122
+ *
123
+ * @since 0.7
124
  */
125
  public static function init() {
126
+ self::read_theme_support();
127
+ if ( ! current_theme_supports( self::SLUG ) ) {
128
  return;
129
  }
130
 
131
+ self::$init_start_time = microtime( true );
 
 
 
132
 
133
  require_once AMP__DIR__ . '/includes/amp-post-template-actions.php';
134
 
 
 
 
 
 
 
 
 
 
 
 
135
  add_action( 'widgets_init', array( __CLASS__, 'register_widgets' ) );
136
 
137
  /*
142
  add_action( 'wp', array( __CLASS__, 'finish_init' ), PHP_INT_MAX );
143
  }
144
 
145
+ /**
146
+ * Determine whether theme support was added via admin option.
147
+ *
148
+ * @since 1.0
149
+ * @see AMP_Theme_Support::read_theme_support()
150
+ *
151
+ * @return bool Support added via option.
152
+ */
153
+ public static function is_support_added_via_option() {
154
+ return self::$support_added_via_option;
155
+ }
156
+
157
+ /**
158
+ * Check theme support args or add theme support if option is set in the admin.
159
+ *
160
+ * The DB option is only considered if the theme does not already explicitly support AMP.
161
+ *
162
+ * @see AMP_Theme_Support::is_support_added_via_option()
163
+ * @see AMP_Post_Type_Support::add_post_type_support() For where post type support is added, since it is irrespective of theme support.
164
+ */
165
+ public static function read_theme_support() {
166
+ $theme_support_option = AMP_Options_Manager::get_option( 'theme_support' );
167
+ if ( current_theme_supports( self::SLUG ) ) {
168
+ $args = self::get_theme_support_args();
169
+
170
+ // Validate theme support usage.
171
+ $keys = array( 'template_dir', 'comments_live_list', 'paired', 'templates_supported', 'available_callback' );
172
+
173
+ if ( count( array_diff( array_keys( $args ), $keys ) ) !== 0 ) {
174
+ _doing_it_wrong( 'add_theme_support', esc_html( sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
175
+ /* translators: 1: comma-separated list of expected keys, 2: comma-separated list of actual keys */
176
+ __( 'Expected AMP theme support to keys (%1$s) but saw (%2$s)', 'amp' ),
177
+ join( ', ', $keys ),
178
+ join( ', ', array_keys( $args ) )
179
+ ) ), '1.0' );
180
+ }
181
+
182
+ if ( isset( $args['available_callback'] ) ) {
183
+ _doing_it_wrong( 'add_theme_support', esc_html__( 'The available_callback is deprecated when adding amp theme support in favor of declaratively setting the supported_templates.', 'amp' ), '1.0' );
184
+ }
185
+ self::$support_added_via_option = false;
186
+ } elseif ( 'disabled' !== $theme_support_option ) {
187
+ add_theme_support( self::SLUG, array(
188
+ 'paired' => ( 'paired' === $theme_support_option ),
189
+ ) );
190
+ self::$support_added_via_option = true;
191
+ } elseif ( AMP_Validation_Manager::is_theme_support_forced() ) {
192
+ add_theme_support( self::SLUG );
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Get the theme support args.
198
+ *
199
+ * This avoids having to repeatedly call `get_theme_support()`, check the args, shift an item off the array, and so on.
200
+ *
201
+ * @since 1.0
202
+ *
203
+ * @return array|false Theme support args, or false if theme support is not present.
204
+ */
205
+ public static function get_theme_support_args() {
206
+ if ( ! current_theme_supports( self::SLUG ) ) {
207
+ return false;
208
+ }
209
+ $support = get_theme_support( self::SLUG );
210
+ if ( true === $support ) {
211
+ return array(
212
+ 'paired' => false,
213
+ );
214
+ }
215
+ if ( ! isset( $support[0] ) || ! is_array( $support[0] ) ) {
216
+ return array();
217
+ }
218
+ return $support[0];
219
+ }
220
+
221
  /**
222
  * Finish initialization once query vars are set.
223
  *
225
  */
226
  public static function finish_init() {
227
  if ( ! is_amp_endpoint() ) {
228
+
229
+ // Redirect to AMP-less variable if AMP is not available for this URL and yet the query var is present.
230
+ if ( isset( $_GET[ amp_get_slug() ] ) ) { // WPCS: csrf ok.
231
+ self::redirect_ampless_url();
 
 
232
  }
233
+
234
+ amp_add_frontend_actions();
235
  return;
236
  }
237
 
238
+ self::ensure_proper_amp_location();
239
+
240
+ $theme_support = self::get_theme_support_args();
241
+ if ( ! empty( $theme_support['template_dir'] ) ) {
242
+ self::add_amp_template_filters();
243
  }
244
 
245
  self::add_hooks();
246
  self::$sanitizer_classes = amp_get_content_sanitizers();
247
+ self::$sanitizer_classes = AMP_Validation_Manager::filter_sanitizer_args( self::$sanitizer_classes );
248
  self::$embed_handlers = self::register_content_embed_handlers();
249
+ self::$sanitizer_classes['AMP_Embed_Sanitizer']['embed_handlers'] = self::$embed_handlers;
250
+
251
+ foreach ( self::$sanitizer_classes as $sanitizer_class => $args ) {
252
+ if ( method_exists( $sanitizer_class, 'add_buffering_hooks' ) ) {
253
+ call_user_func( array( $sanitizer_class, 'add_buffering_hooks' ), $args );
254
+ }
255
+ }
256
  }
257
 
258
  /**
259
+ * Ensure that the current AMP location is correct.
260
  *
261
+ * @since 1.0
262
+ *
263
+ * @param bool $exit Whether to exit after redirecting.
264
+ * @return bool Whether redirection was done. Naturally this is irrelevant if $exit is true.
265
  */
266
+ public static function ensure_proper_amp_location( $exit = true ) {
267
+ $has_query_var = false !== get_query_var( amp_get_slug(), false ); // May come from URL param or endpoint slug.
268
+ $has_url_param = isset( $_GET[ amp_get_slug() ] ); // WPCS: CSRF OK.
269
+
270
+ if ( amp_is_canonical() ) {
271
+ /*
272
+ * When AMP native/canonical, then when there is an /amp/ endpoint or ?amp URL param,
273
+ * then a redirect needs to be done to the URL without any AMP indicator in the URL.
274
+ */
275
+ if ( $has_query_var || $has_url_param ) {
276
+ return self::redirect_ampless_url( $exit );
277
+ }
278
+ } else {
279
+ /*
280
+ * When in AMP paired mode *with* theme support, then the proper AMP URL has the 'amp' URL param
281
+ * and not the /amp/ endpoint. The URL param is now the exclusive way to mark AMP in paired mode
282
+ * when amp theme support present. This is important for plugins to be able to reliably call
283
+ * is_amp_endpoint() before the parse_query action.
284
+ */
285
+ if ( $has_query_var && ! $has_url_param ) {
286
+ $old_url = amp_get_current_url();
287
+ $new_url = add_query_arg( amp_get_slug(), '', amp_remove_endpoint( $old_url ) );
288
+ if ( $old_url !== $new_url ) {
289
+ wp_safe_redirect( $new_url, 302 );
290
+ // @codeCoverageIgnoreStart
291
+ if ( $exit ) {
292
+ exit;
293
+ }
294
+ return true;
295
+ // @codeCoverageIgnoreEnd
296
+ }
297
  }
298
+ }
299
+ return false;
300
+ }
301
 
302
+ /**
303
+ * Redirect to non-AMP version of the current URL, such as because AMP is canonical or there are unaccepted validation errors.
304
+ *
305
+ * If the current URL is already AMP-less then do nothing.
306
+ *
307
+ * @since 0.7
308
+ * @since 1.0 Added $exit param.
309
+ * @since 1.0 Renamed from redirect_canonical_amp().
310
+ *
311
+ * @param bool $exit Whether to exit after redirecting.
312
+ * @return bool Whether redirection was done. Naturally this is irrelevant if $exit is true.
313
+ */
314
+ public static function redirect_ampless_url( $exit = true ) {
315
+ $current_url = amp_get_current_url();
316
+ $ampless_url = amp_remove_endpoint( $current_url );
317
+ if ( $ampless_url === $current_url ) {
318
+ return false;
319
+ }
320
 
321
+ /*
322
+ * Temporary redirect because AMP URL may return when blocking validation errors
323
+ * occur or when a non-canonical AMP theme is used.
324
+ */
325
+ wp_safe_redirect( $ampless_url, 302 );
326
+ // @codeCoverageIgnoreStart
327
+ if ( $exit ) {
328
  exit;
329
  }
330
+ return true;
331
+ // @codeCoverageIgnoreEnd
332
  }
333
 
334
  /**
335
  * Determines whether paired mode is available.
336
  *
337
  * When 'amp' theme support has not been added or canonical mode is enabled, then this returns false.
 
 
338
  *
339
+ * @since 0.7
340
+ *
341
+ * @see amp_is_canonical()
342
  * @return bool Whether available.
343
  */
344
  public static function is_paired_available() {
345
+ if ( ! current_theme_supports( self::SLUG ) ) {
 
346
  return false;
347
  }
348
 
349
+ if ( amp_is_canonical() ) {
350
  return false;
351
  }
352
 
353
+ $availability = self::get_template_availability();
354
+ return $availability['supported'];
 
 
 
 
355
  }
356
 
357
  /**
367
  }
368
 
369
  /**
370
+ * Register filters for loading AMP-specific templates.
371
  */
372
+ public static function add_amp_template_filters() {
373
  foreach ( self::$template_types as $template_type ) {
374
+ add_filter( "{$template_type}_template_hierarchy", array( __CLASS__, 'filter_amp_template_hierarchy' ) );
375
  }
 
376
  }
377
 
378
  /**
379
+ * Determine template availability of AMP for the given query.
380
+ *
381
+ * This is not intended to return whether AMP is available for a _specific_ post. For that, use `post_supports_amp()`.
382
+ *
383
+ * @since 1.0
384
+ * @global WP_Query $wp_query
385
+ * @see post_supports_amp()
386
+ *
387
+ * @param WP_Query|WP_Post|null $query Query or queried post. If null then the global query will be used.
388
+ * @return array {
389
+ * Template availability.
390
+ *
391
+ * @type bool $supported Whether the template is supported in AMP.
392
+ * @type bool|null $immutable Whether the supported status is known to be unchangeable.
393
+ * @type string|null $template The ID of the matched template (conditional), such as 'is_singular', or null if nothing was matched.
394
+ * @type string[] $errors List of the errors or reasons for why the template is not available.
395
+ * }
396
  */
397
+ public static function get_template_availability( $query = null ) {
398
+ global $wp_query;
399
+ if ( ! $query ) {
400
+ $query = $wp_query;
401
+ } elseif ( $query instanceof WP_Post ) {
402
+ $post = $query;
403
+ $query = new WP_Query();
404
+ if ( 'page' === $post->post_type ) {
405
+ $query->set( 'page_id', $post->ID );
406
+ } else {
407
+ $query->set( 'p', $post->ID );
408
+ }
409
+ $query->queried_object = $post;
410
+ $query->queried_object_id = $post->ID;
411
+ $query->parse_query_vars();
412
+ }
413
 
414
+ $default_response = array(
415
+ 'errors' => array(),
416
+ 'supported' => false,
417
+ 'immutable' => null,
418
+ 'template' => null,
419
+ );
420
 
421
+ if ( ! ( $query instanceof WP_Query ) ) {
422
+ _doing_it_wrong( __METHOD__, esc_html__( 'No WP_Query available.', 'amp' ), '1.0' );
423
+ return array_merge(
424
+ $default_response,
425
+ array( 'errors' => array( 'no_query_available' ) )
426
+ );
427
+ }
428
 
429
+ $theme_support_args = self::get_theme_support_args();
430
+ if ( false === $theme_support_args ) {
431
+ return array_merge(
432
+ $default_response,
433
+ array( 'errors' => array( 'no_theme_support' ) )
434
+ );
435
+ }
 
 
 
 
436
 
437
+ // Support available_callback from 0.7, though it is deprecated.
438
+ if ( isset( $theme_support_args['available_callback'] ) && is_callable( $theme_support_args['available_callback'] ) ) {
439
+ /**
440
+ * Queried object.
441
+ *
442
+ * @var WP_Post $queried_object
443
+ */
444
+ $queried_object = $query->get_queried_object();
445
+ if ( ( is_singular() || $query->is_posts_page ) && ! post_supports_amp( $queried_object ) ) {
446
+ return array_merge(
447
+ $default_response,
448
+ array(
449
+ 'errors' => array( 'no-post-support' ),
450
+ 'supported' => false,
451
+ 'immutable' => true,
452
+ )
453
+ );
454
+ }
455
 
456
+ $response = array_merge(
457
+ $default_response,
458
+ array(
459
+ 'supported' => call_user_func( $theme_support_args['available_callback'] ),
460
+ 'immutable' => true,
461
+ )
462
+ );
463
+ if ( ! $response['supported'] ) {
464
+ $response['errors'][] = 'available_callback';
465
+ }
466
+ return $response;
467
+ }
468
 
469
+ $all_templates_supported_by_theme_support = false;
470
+ if ( isset( $theme_support_args['templates_supported'] ) ) {
471
+ $all_templates_supported_by_theme_support = 'all' === $theme_support_args['templates_supported'];
472
+ }
473
+ $all_templates_supported = (
474
+ $all_templates_supported_by_theme_support || AMP_Options_Manager::get_option( 'all_templates_supported' )
475
+ );
476
 
477
+ // Make sure global $wp_query is set in case of conditionals that unfortunately look at global scope.
478
+ $prev_query = $wp_query;
479
+ $wp_query = $query; // WPCS: override ok.
 
 
 
480
 
481
+ $matching_templates = array();
482
+ $supportable_templates = self::get_supportable_templates();
483
+ foreach ( $supportable_templates as $id => $supportable_template ) {
484
+ if ( empty( $supportable_template['callback'] ) ) {
485
+ $callback = $id;
486
+ } else {
487
+ $callback = $supportable_template['callback'];
488
+ }
489
 
490
+ // If the callback is a method on the query, then call the method on the query itself.
491
+ if ( is_string( $callback ) && 'is_' === substr( $callback, 0, 3 ) && method_exists( $query, $callback ) ) {
492
+ $is_match = call_user_func( array( $query, $callback ) );
493
+ } elseif ( is_callable( $callback ) ) {
494
+ $is_match = call_user_func( $callback, $query );
495
+ } else {
496
+ /* translators: %s: the supportable template ID. */
497
+ _doing_it_wrong( __FUNCTION__, esc_html( sprintf( __( 'Supportable template "%s" does not have a callable callback.', 'amp' ), $id ) ), '1.0' );
498
+ $is_match = false;
499
+ }
500
 
501
+ if ( $is_match ) {
502
+ $matching_templates[ $id ] = array(
503
+ 'template' => $id,
504
+ 'supported' => ! empty( $supportable_template['supported'] ),
505
+ 'immutable' => ! empty( $supportable_template['immutable'] ),
506
+ );
507
+ }
508
+ }
509
 
510
+ // Restore previous $wp_query (if any).
511
+ $wp_query = $prev_query; // WPCS: override ok.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
+ // Make sure children override their parents.
514
+ $matching_template_ids = array_keys( $matching_templates );
515
+ foreach ( array_diff( array_keys( $supportable_templates ), $matching_template_ids ) as $template_id ) {
516
+ unset( $supportable_templates[ $template_id ] );
517
+ }
518
+ foreach ( $matching_template_ids as $id ) {
519
+ $has_children = false;
520
+ foreach ( $supportable_templates as $other_id => $supportable_template ) {
521
+ if ( $other_id === $id ) {
522
+ continue;
523
+ }
524
+ if ( isset( $supportable_template['parent'] ) && $id === $supportable_template['parent'] ) {
525
+ $has_children = true;
526
+ break;
527
+ }
528
  }
529
+
530
+ // Delete all matching parent templates since the child will override them.
531
+ if ( ! $has_children ) {
532
+ $supportable_template = $supportable_templates[ $id ];
533
+ while ( ! empty( $supportable_template['parent'] ) ) {
534
+ $parent = $supportable_template['parent'];
535
+ $supportable_template = $supportable_templates[ $parent ];
536
+
537
+ // Let the child supported status override the parent's supported status.
538
+ unset( $matching_templates[ $parent ] );
 
 
 
539
  }
540
+ }
541
+ }
542
+
543
+ // The is_home() condition is the default so discard it if there are other matching templates.
544
+ if ( count( $matching_templates ) > 1 && isset( $matching_templates['is_home'] ) ) {
545
+ unset( $matching_templates['is_home'] );
546
+ }
547
+
548
+ /*
549
+ * If there are more than one matching templates, then something is probably not right.
550
+ * Template conditions need to be set up properly to prevent this from happening.
551
+ */
552
+ if ( count( $matching_templates ) > 1 ) {
553
+ _doing_it_wrong( __METHOD__, esc_html__( 'Did not expect there to be more than one matching template. Did you filter amp_supportable_templates to not honor the template hierarchy?', 'amp' ), '1.0' );
554
+ }
555
 
556
+ $matching_template = array_shift( $matching_templates );
557
+
558
+ // If there aren't any matching templates left that are supported, then we consider it to not be available.
559
+ if ( ! $matching_template ) {
560
+ if ( $all_templates_supported ) {
561
+ return array_merge(
562
+ $default_response,
563
+ array(
564
+ 'supported' => true,
565
+ )
566
+ );
567
+ } else {
568
+ return array_merge(
569
+ $default_response,
570
+ array( 'errors' => array( 'no_matching_template' ) )
571
+ );
572
  }
573
+ }
574
+ $matching_template = array_merge( $default_response, $matching_template );
575
 
576
+ // If there aren't any matching templates left that are supported, then we consider it to not be available.
577
+ if ( empty( $matching_template['supported'] ) ) {
578
+ $matching_template['errors'][] = 'template_unsupported';
579
+ }
580
 
581
+ // For singular queries, post_supports_amp() is given the final say.
582
+ if ( $query->is_singular() || $query->is_posts_page ) {
583
+ /**
584
+ * Queried object.
585
+ *
586
+ * @var WP_Post $queried_object
587
+ */
588
+ $queried_object = $query->get_queried_object();
589
+ $support_errors = AMP_Post_Type_Support::get_support_errors( $queried_object );
590
+ if ( ! empty( $support_errors ) ) {
591
+ $matching_template['errors'] = array_merge( $matching_template['errors'], $support_errors );
592
+ $matching_template['supported'] = false;
593
  }
594
  }
595
+
596
+ return $matching_template;
597
  }
598
 
599
  /**
600
+ * Get the templates which can be supported.
 
 
 
 
 
 
 
 
 
601
  *
602
+ * @return array Supportable templates.
 
 
 
603
  */
604
+ public static function get_supportable_templates() {
605
+ $templates = array(
606
+ 'is_singular' => array(
607
+ 'label' => __( 'Singular', 'amp' ),
608
+ 'description' => __( 'Required for the above content types.', 'amp' ),
609
  ),
 
610
  );
611
+ if ( 'page' === get_option( 'show_on_front' ) ) {
612
+ $templates['is_front_page'] = array(
613
+ 'label' => __( 'Homepage', 'amp' ),
614
+ 'parent' => 'is_singular',
615
+ );
616
+ if ( AMP_Post_Meta_Box::DISABLED_STATUS === get_post_meta( get_option( 'page_on_front' ), AMP_Post_Meta_Box::STATUS_POST_META_KEY, true ) ) {
617
+ /* translators: %s: the URL to the edit post screen. */
618
+ $templates['is_front_page']['description'] = sprintf( __( 'Currently disabled at the <a href="%s" target="_blank">page level</a>.', 'amp' ), esc_url( get_edit_post_link( get_option( 'page_on_front' ) ) ) );
619
+ }
620
 
621
+ // In other words, same as is_posts_page, *but* it not is_singular.
622
+ $templates['is_home'] = array(
623
+ 'label' => __( 'Blog', 'amp' ),
624
+ );
625
+ if ( AMP_Post_Meta_Box::DISABLED_STATUS === get_post_meta( get_option( 'page_for_posts' ), AMP_Post_Meta_Box::STATUS_POST_META_KEY, true ) ) {
626
+ /* translators: %s: the URL to the edit post screen. */
627
+ $templates['is_home']['description'] = sprintf( __( 'Currently disabled at the <a href="%s" target="_blank">page level</a>.', 'amp' ), esc_url( get_edit_post_link( get_option( 'page_for_posts' ) ) ) );
628
+ }
629
+ } else {
630
+ $templates['is_home'] = array(
631
+ 'label' => __( 'Homepage', 'amp' ),
632
+ );
633
  }
634
 
635
+ $templates = array_merge(
636
+ $templates,
637
+ array(
638
+ 'is_archive' => array(
639
+ 'label' => __( 'Archives', 'amp' ),
640
+ ),
641
+ 'is_author' => array(
642
+ 'label' => __( 'Author', 'amp' ),
643
+ 'parent' => 'is_archive',
644
+ ),
645
+ 'is_date' => array(
646
+ 'label' => __( 'Date', 'amp' ),
647
+ 'parent' => 'is_archive',
648
+ ),
649
+ 'is_search' => array(
650
+ 'label' => __( 'Search', 'amp' ),
651
+ ),
652
+ 'is_404' => array(
653
+ 'label' => __( 'Not Found (404)', 'amp' ),
654
+ ),
655
+ )
656
  );
 
 
657
 
658
+ if ( taxonomy_exists( 'category' ) ) {
659
+ $templates['is_category'] = array(
660
+ 'label' => get_taxonomy( 'category' )->labels->name,
661
+ 'parent' => 'is_archive',
662
+ );
663
+ }
664
+ if ( taxonomy_exists( 'post_tag' ) ) {
665
+ $templates['is_tag'] = array(
666
+ 'label' => get_taxonomy( 'post_tag' )->labels->name,
667
+ 'parent' => 'is_archive',
668
+ );
669
+ }
670
+
671
+ $taxonomy_args = array(
672
+ '_builtin' => false,
673
+ 'publicly_queryable' => true,
674
  );
675
+ foreach ( get_taxonomies( $taxonomy_args, 'objects' ) as $taxonomy ) {
676
+ $templates[ sprintf( 'is_tax[%s]', $taxonomy->name ) ] = array(
677
+ 'label' => $taxonomy->labels->name,
678
+ 'parent' => 'is_archive',
679
+ 'callback' => function ( WP_Query $query ) use ( $taxonomy ) {
680
+ return $query->is_tax( $taxonomy->name );
681
+ },
682
+ );
683
  }
684
 
685
+ $post_type_args = array(
686
+ 'has_archive' => true,
687
+ 'publicly_queryable' => true,
688
+ );
689
+ foreach ( get_post_types( $post_type_args, 'objects' ) as $post_type ) {
690
+ $templates[ sprintf( 'is_post_type_archive[%s]', $post_type->name ) ] = array(
691
+ 'label' => $post_type->labels->archives,
692
+ 'parent' => 'is_archive',
693
+ 'callback' => function ( WP_Query $query ) use ( $post_type ) {
694
+ return $query->is_post_type_archive( $post_type->name );
695
+ },
696
+ );
697
  }
698
 
699
+ /**
700
+ * Filters list of supportable templates.
701
+ *
702
+ * A theme or plugin can force a given template to be supported or not by preemptively
703
+ * setting the 'supported' flag for a given template. Otherwise, if the flag is undefined
704
+ * then the user will be able to toggle it themselves in the admin. Each array item should
705
+ * have a key that corresponds to a template conditional function. If the key is such a
706
+ * function, then the key is used to evaluate whether the given template entry is a match.
707
+ * Otherwise, a supportable template item can include a callback value which is used instead.
708
+ * Each item needs a 'label' value. Additionally, if the supportable template is a subset of
709
+ * another condition (e.g. is_singular > is_single) then this relationship needs to be
710
+ * indicated via the 'parent' value.
711
+ *
712
+ * @since 1.0
713
+ *
714
+ * @param array $templates Supportable templates.
715
+ */
716
+ $templates = apply_filters( 'amp_supportable_templates', $templates );
717
 
718
+ $theme_support_args = self::get_theme_support_args();
719
+ $theme_supported_templates = array();
720
+ if ( isset( $theme_support_args['templates_supported'] ) ) {
721
+ $theme_supported_templates = $theme_support_args['templates_supported'];
722
+ }
723
 
724
+ $supported_templates = AMP_Options_Manager::get_option( 'supported_templates' );
725
+ foreach ( $templates as $id => &$template ) {
 
 
726
 
727
+ // Capture user-elected support from options. This allows us to preserve the original user selection through programmatic overrides.
728
+ $template['user_supported'] = in_array( $id, $supported_templates, true );
729
 
730
+ // Consider supported templates from theme support args.
731
+ if ( ! isset( $template['supported'] ) ) {
732
+ if ( 'all' === $theme_supported_templates ) {
733
+ $template['supported'] = true;
734
+ } elseif ( is_array( $theme_supported_templates ) && isset( $theme_supported_templates[ $id ] ) ) {
735
+ $template['supported'] = $theme_supported_templates[ $id ];
736
+ }
737
+ }
738
+
739
+ // Make supported state immutable if it was programmatically set.
740
+ $template['immutable'] = isset( $template['supported'] );
741
+
742
+ // Set supported state from user preference.
743
+ if ( ! $template['immutable'] ) {
744
+ $template['supported'] = AMP_Options_Manager::get_option( 'all_templates_supported' ) || $template['user_supported'];
745
+ }
746
+ }
747
+
748
+ return $templates;
749
  }
750
 
751
  /**
752
+ * Register hooks.
 
 
 
 
 
 
753
  */
754
+ public static function add_hooks() {
 
755
 
756
+ // Let the AMP plugin manage service worker streaming in the PWA plugin.
757
+ remove_action( 'template_redirect', 'WP_Service_Worker_Navigation_Routing_Component::start_output_buffering_stream_fragment', PHP_INT_MAX );
 
 
 
 
 
758
 
759
+ // Remove core actions which are invalid AMP.
760
+ remove_action( 'wp_head', 'wp_post_preview_js', 1 );
761
+ remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
762
+ remove_action( 'wp_print_styles', 'print_emoji_styles' );
763
+ remove_action( 'wp_head', 'wp_oembed_add_host_js' );
764
 
765
+ // @todo The wp_mediaelement_fallback() should still run to be injected inside of the audio/video generated by wp_audio_shortcode()/wp_video_shortcode() respectively.
766
+ // Prevent MediaElement.js scripts/styles from being enqueued.
767
+ add_filter( 'wp_video_shortcode_library', function() {
768
+ return 'amp';
769
+ } );
770
+ add_filter( 'wp_audio_shortcode_library', function() {
771
+ return 'amp';
772
+ } );
773
 
774
+ // Don't show loading indicator on custom logo since it makes most sense for larger images.
775
+ add_filter( 'get_custom_logo', function( $html ) {
776
+ return preg_replace( '/(?<=<img\s)/', ' noloading ', $html );
777
+ }, 1 );
778
+
779
+ /*
780
+ * "AMP HTML documents MUST contain the AMP boilerplate code (head > style[amp-boilerplate] and noscript > style[amp-boilerplate])
781
+ * in their head tag." {@link https://www.ampproject.org/docs/fundamentals/spec#required-markup AMP Required markup}
782
+ *
783
+ * After "Specify the <link> tag for your favicon.", then
784
+ * "Specify any custom styles by using the <style amp-custom> tag."
785
+ *
786
+ * Note that the boilerplate is added at the very end because:
787
+ * "Finally, specify the AMP boilerplate code. By putting the boilerplate code last, it prevents custom styles from accidentally
788
+ * overriding the boilerplate css rules." {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit AMP Hosting Guide}
789
  *
790
+ * Other required markup is added in the ensure_required_markup method, including meta charset, meta viewport, and rel=canonical link.
791
  */
792
+ add_action( 'wp_head', function() {
793
+ echo '<style amp-custom></style>';
794
+ }, 0 );
795
+ add_action( 'wp_head', function() {
796
+ echo amp_get_boilerplate_code(); // WPCS: xss ok.
797
+ }, PHP_INT_MAX );
798
 
799
+ add_action( 'wp_head', 'amp_add_generator_metadata', 20 );
800
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
801
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'dequeue_customize_preview_scripts' ), 1000 );
802
+ add_filter( 'customize_partial_render', array( __CLASS__, 'filter_customize_partial_render' ) );
 
803
 
804
+ add_action( 'wp_footer', 'amp_print_analytics' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
 
806
+ /*
807
+ * Disable admin bar because admin-bar.css (28K) and Dashicons (48K) alone
808
+ * combine to surpass the 50K limit imposed for the amp-custom style.
809
+ */
810
+ if ( AMP_Options_Manager::get_option( 'disable_admin_bar' ) ) {
811
+ add_filter( 'show_admin_bar', '__return_false', 100 );
812
+ } else {
813
+ add_action( 'admin_bar_init', array( __CLASS__, 'init_admin_bar' ) );
814
  }
815
 
816
+ /*
817
+ * Start output buffering at very low priority for sake of plugins and themes that use template_redirect
818
+ * instead of template_include.
819
+ */
820
+ $priority = defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX; // phpcs:ignore PHPCompatibility.PHP.NewConstants.php_int_minFound
821
+ add_action( 'template_redirect', array( __CLASS__, 'start_output_buffering' ), $priority );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822
 
823
+ // Commenting hooks.
824
+ add_filter( 'wp_list_comments_args', array( __CLASS__, 'set_comments_walker' ), PHP_INT_MAX );
825
+ add_filter( 'comment_form_defaults', array( __CLASS__, 'filter_comment_form_defaults' ) );
826
+ add_filter( 'comment_reply_link', array( __CLASS__, 'filter_comment_reply_link' ), 10, 4 );
827
+ add_filter( 'cancel_comment_reply_link', array( __CLASS__, 'filter_cancel_comment_reply_link' ), 10, 3 );
828
+ add_action( 'comment_form', array( __CLASS__, 'amend_comment_form' ), 100 );
829
+ remove_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' );
830
+ add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'whitelist_layout_in_wp_kses_allowed_html' ), 10 );
831
+ add_filter( 'get_header_image_tag', array( __CLASS__, 'amend_header_image_with_video_header' ), PHP_INT_MAX );
832
+ add_action( 'wp_print_footer_scripts', function() {
833
+ wp_dequeue_script( 'wp-custom-header' );
834
+ }, 0 );
835
+ add_action( 'wp_enqueue_scripts', function() {
836
+ wp_dequeue_script( 'comment-reply' ); // Handled largely by AMP_Comments_Sanitizer and *reply* methods in this class.
837
+ } );
838
 
839
+ // @todo Add character conversion.
840
  }
841
 
842
  /**
940
  /**
941
  * Prepends template hierarchy with template_dir for AMP paired mode templates.
942
  *
 
 
943
  * @param array $templates Template hierarchy.
944
+ * @return array Templates.
945
  */
946
+ public static function filter_amp_template_hierarchy( $templates ) {
947
+ $args = self::get_theme_support_args();
 
948
  if ( isset( $args['template_dir'] ) ) {
949
  $amp_templates = array();
950
  foreach ( $templates as $template ) {
951
+ $amp_templates[] = $args['template_dir'] . '/' . $template; // Let template_dir have precedence.
952
+ $amp_templates[] = $template;
953
  }
954
  $templates = $amp_templates;
955
  }
956
  return $templates;
957
  }
958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
  /**
960
  * Get canonical URL for current request.
961
  *
1121
  }
1122
 
1123
  /**
1124
+ * Configure the admin bar for AMP.
1125
+ *
1126
+ * @since 1.0
1127
  */
1128
+ public static function init_admin_bar() {
1129
+
1130
+ // Replace admin-bar.css in core with forked version which makes use of :focus-within among other change for AMP-compat.
1131
+ wp_styles()->registered['admin-bar']->src = amp_get_asset_url( 'css/admin-bar.css' );
1132
+ wp_styles()->registered['admin-bar']->ver = AMP__VERSION;
1133
+
1134
+ // Remove script which is almost entirely made obsolete by :focus-inside in the forked admin-bar.css.
1135
+ wp_dequeue_script( 'admin-bar' );
1136
+
1137
+ // Remove customize support script since not valid AMP.
1138
+ add_action( 'admin_bar_menu', function() {
1139
+ remove_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
1140
+ }, 41 );
1141
+
1142
+ // Emulate customize support script in PHP, to assume Customizer.
1143
+ add_filter( 'body_class', function( $body_classes ) {
1144
+ return array_merge(
1145
+ array_diff(
1146
+ $body_classes,
1147
+ array( 'no-customize-support' )
1148
+ ),
1149
+ array( 'customize-support' )
1150
+ );
1151
+ } );
1152
  }
1153
 
1154
  /**
1155
+ * Ensure the markup exists as required by AMP and elements are in the optimal loading order.
1156
  *
1157
+ * Ensure meta[charset], meta[name=viewport], and link[rel=canonical] exist, as the whitelist sanitizer
1158
+ * may have removed an illegal meta[http-equiv] or meta[name=viewport]. For a singular post, core only outputs a
1159
+ * canonical URL by default. Adds the preload links.
1160
  *
1161
  * @since 0.7
1162
+ * @link https://www.ampproject.org/docs/reference/spec#required-markup
1163
+ * @link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit#heading=h.2ha259c3ffos
1164
  * @todo All of this might be better placed inside of a sanitizer.
1165
  *
1166
+ * @param DOMDocument $dom Document.
1167
+ * @param string[] $script_handles AMP script handles for components identified during output buffering.
1168
  */
1169
+ public static function ensure_required_markup( DOMDocument $dom, $script_handles = array() ) {
1170
+ /**
1171
+ * Elements.
1172
+ *
1173
+ * @var DOMElement $meta
1174
+ * @var DOMElement $script
1175
+ * @var DOMElement $link
1176
+ */
1177
+
1178
+ $xpath = new DOMXPath( $dom );
1179
+
1180
+ // Make sure the HEAD element is in the doc.
1181
  $head = $dom->getElementsByTagName( 'head' )->item( 0 );
1182
  if ( ! $head ) {
1183
  $head = $dom->createElement( 'head' );
1184
  $dom->documentElement->insertBefore( $head, $dom->documentElement->firstChild );
1185
  }
1186
+
1187
+ // Ensure there is a schema.org script in the document.
1188
+ // @todo Consider applying the amp_schemaorg_metadata filter on the contents when a script is already present.
1189
+ $schema_org_meta_script = $xpath->query( '//script[ @type = "application/ld+json" ][ contains( ./text(), "schema.org" ) ]' )->item( 0 );
1190
+ if ( ! $schema_org_meta_script ) {
1191
+ $script = $dom->createElement( 'script' );
1192
+ $script->setAttribute( 'type', 'application/ld+json' );
1193
+ $script->appendChild( $dom->createTextNode( wp_json_encode( amp_get_schemaorg_metadata() ) ) );
1194
+ $head->appendChild( $script );
1195
+ }
1196
+
1197
+ // Ensure rel=canonical link.
1198
+ $links = array();
1199
+ $link_elements = $head->getElementsByTagName( 'link' );
1200
+ $rel_canonical = null;
1201
+ foreach ( $link_elements as $link ) {
1202
+ if ( $link->hasAttribute( 'rel' ) ) {
1203
+ $links[ $link->getAttribute( 'rel' ) ][] = $link;
1204
+ }
1205
+ }
1206
+ if ( empty( $links['canonical'] ) ) {
1207
+ $rel_canonical = AMP_DOM_Utils::create_node( $dom, 'link', array(
1208
+ 'rel' => 'canonical',
1209
+ 'href' => self::get_current_canonical_url(),
1210
+ ) );
1211
+ $head->appendChild( $rel_canonical );
1212
+ }
1213
+
1214
+ /*
1215
+ * Ensure meta charset and meta viewport are present.
1216
+ *
1217
+ * "AMP is already quite restrictive about which markup is allowed in the <head> section. However,
1218
+ * there are a few basic optimizations that you can apply. The key is to structure the <head> section
1219
+ * in a way so that all render-blocking scripts and custom fonts load as fast as possible."
1220
+ *
1221
+ * "The first tag should be the meta charset tag, followed by any remaining meta tags."
1222
+ *
1223
+ * {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit#heading=h.2ha259c3ffos Optimize the AMP Runtime loading}
1224
+ */
1225
  $meta_charset = null;
1226
  $meta_viewport = null;
1227
+ $meta_elements = array();
1228
  foreach ( $head->getElementsByTagName( 'meta' ) as $meta ) {
1229
+ if ( $meta->hasAttribute( 'charset' ) ) { // There will not be a meta[http-equiv] because the sanitizer removed it.
 
 
 
 
 
1230
  $meta_charset = $meta;
1231
  } elseif ( 'viewport' === $meta->getAttribute( 'name' ) ) {
1232
  $meta_viewport = $meta;
1233
+ } else {
1234
+ $meta_elements[] = $meta;
1235
  }
1236
  }
1237
  if ( ! $meta_charset ) {
1239
  $meta_charset = AMP_DOM_Utils::create_node( $dom, 'meta', array(
1240
  'charset' => 'utf-8',
1241
  ) );
1242
+ } else {
1243
+ $head->removeChild( $meta_charset ); // So we can move it.
1244
  }
1245
+ $head->insertBefore( $meta_charset, $head->firstChild );
1246
+
1247
  if ( ! $meta_viewport ) {
1248
  $meta_viewport = AMP_DOM_Utils::create_node( $dom, 'meta', array(
1249
  'name' => 'viewport',
1250
+ 'content' => 'width=device-width',
1251
  ) );
1252
+ } else {
1253
+ $head->removeChild( $meta_viewport ); // So we can move it.
 
 
 
 
 
 
 
1254
  }
1255
+ $head->insertBefore( $meta_viewport, $meta_charset->nextSibling );
1256
+
1257
+ $previous_node = $meta_viewport;
1258
+ foreach ( $meta_elements as $meta_element ) {
1259
+ $meta_element->parentNode->removeChild( $meta_element );
1260
+ $head->insertBefore( $meta_element, $previous_node->nextSibling );
1261
+ $previous_node = $meta_element;
1262
  }
1263
+
1264
+ $title = $head->getElementsByTagName( 'title' )->item( 0 );
1265
+ if ( $title ) {
1266
+ $title->parentNode->removeChild( $title ); // So we can move it.
1267
+ $head->insertBefore( $title, $previous_node->nextSibling );
1268
+ $previous_node = $title;
1269
+ }
1270
+
1271
+ // @see https://github.com/ampproject/amphtml/blob/2fd30ca984bceac05905bd5b17f9e0010629d719/src/render-delaying-services.js#L39-L43 AMPHTML Render Delaying Services SERVICES definition.
1272
+ $render_delaying_extensions = array(
1273
+ 'amp-experiment',
1274
+ 'amp-dynamic-css-classes',
1275
+ 'amp-story',
1276
+ );
1277
+
1278
+ // Obtain the existing AMP scripts.
1279
+ $amp_scripts = array();
1280
+ $ordered_scripts = array();
1281
+ $head_scripts = array();
1282
+ $runtime_src = wp_scripts()->registered['amp-runtime']->src;
1283
+ foreach ( $head->getElementsByTagName( 'script' ) as $script ) { // Note that prepare_response() already moved body scripts to head.
1284
+ $head_scripts[] = $script;
1285
+ }
1286
+ foreach ( $head_scripts as $script ) {
1287
+ $src = $script->getAttribute( 'src' );
1288
+ if ( ! $src || 'https://cdn.ampproject.org/' !== substr( $src, 0, 27 ) ) {
1289
+ continue;
1290
+ }
1291
+ if ( $runtime_src === $src ) {
1292
+ $amp_scripts['amp-runtime'] = $script;
1293
+ } elseif ( $script->hasAttribute( 'custom-element' ) ) {
1294
+ $amp_scripts[ $script->getAttribute( 'custom-element' ) ] = $script;
1295
+ } elseif ( $script->hasAttribute( 'custom-template' ) ) {
1296
+ $amp_scripts[ $script->getAttribute( 'custom-template' ) ] = $script;
1297
+ } else {
1298
+ continue;
1299
  }
1300
+ $script->parentNode->removeChild( $script ); // So we can move it.
1301
  }
1302
+
1303
+ // Create scripts for any components discovered from output buffering.
1304
+ foreach ( array_diff( $script_handles, array_keys( $amp_scripts ) ) as $missing_script_handle ) {
1305
+ if ( ! wp_script_is( $missing_script_handle, 'registered' ) ) {
1306
+ continue;
1307
+ }
1308
+ $attrs = array(
1309
+ 'src' => wp_scripts()->registered[ $missing_script_handle ]->src,
1310
+ 'async' => '',
1311
+ );
1312
+ if ( 'amp-mustache' === $missing_script_handle ) {
1313
+ $attrs['custom-template'] = $missing_script_handle;
1314
+ } else {
1315
+ $attrs['custom-element'] = $missing_script_handle;
1316
+ }
1317
+
1318
+ $amp_scripts[ $missing_script_handle ] = AMP_DOM_Utils::create_node( $dom, 'script', $attrs );
1319
+ }
1320
+
1321
+ /* phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1322
+ *
1323
+ * "Next, preload the AMP runtime v0.js <script> tag with <link as=script href=https://cdn.ampproject.org/v0.js rel=preload>.
1324
+ * The AMP runtime should start downloading as soon as possible because the AMP boilerplate hides the document via body { visibility:hidden }
1325
+ * until the AMP runtime has loaded. Preloading the AMP runtime tells the browser to download the script with a higher priority."
1326
+ * {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit#heading=h.2ha259c3ffos Optimize the AMP Runtime loading}
1327
+ */
1328
+ $prioritized_preloads = array();
1329
+ if ( ! isset( $links['preload'] ) ) {
1330
+ $links['preload'] = array();
1331
+ }
1332
+
1333
+ $prioritized_preloads[] = AMP_DOM_Utils::create_node( $dom, 'link', array(
1334
+ 'rel' => 'preload',
1335
+ 'as' => 'script',
1336
+ 'href' => $runtime_src,
1337
+ ) );
1338
+
1339
+ $amp_script_handles = array_keys( $amp_scripts );
1340
+ foreach ( array_intersect( $render_delaying_extensions, $amp_script_handles ) as $script_handle ) {
1341
+ if ( ! in_array( $script_handle, $render_delaying_extensions, true ) ) {
1342
+ continue;
1343
+ }
1344
+ $prioritized_preloads[] = AMP_DOM_Utils::create_node( $dom, 'link', array(
1345
+ 'rel' => 'preload',
1346
+ 'as' => 'script',
1347
+ 'href' => $amp_scripts[ $script_handle ]->getAttribute( 'src' ),
1348
  ) );
 
1349
  }
1350
+ $links['preload'] = array_merge( $prioritized_preloads, $links['preload'] );
1351
+
1352
+ $link_relations = array( 'preconnect', 'dns-prefetch', 'preload', 'prerender', 'prefetch' );
1353
+ foreach ( $link_relations as $rel ) {
1354
+ if ( ! isset( $links[ $rel ] ) ) {
1355
+ continue;
1356
+ }
1357
+ foreach ( $links[ $rel ] as $link ) {
1358
+ if ( $link->parentNode ) {
1359
+ $link->parentNode->removeChild( $link ); // So we can move it.
1360
+ }
1361
+ $head->insertBefore( $link, $previous_node->nextSibling );
1362
+ $previous_node = $link;
1363
+ }
1364
+ }
1365
+
1366
+ /*
1367
+ * "Specify the <script> tags for render-delaying extensions (e.g., amp-experiment, amp-dynamic-css-classes, and amp-story)."
1368
+ * "Specify the <script> tags for remaining extensions (e.g., amp-bind, ...). These extensions are not render-delaying and therefore
1369
+ * should not be preloaded because they might take away important bandwidth for the initial render."
1370
+ * {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit AMP Hosting Guide}
1371
+ */
1372
+ if ( isset( $amp_scripts['amp-runtime'] ) ) {
1373
+ $ordered_scripts['amp-runtime'] = $amp_scripts['amp-runtime'];
1374
+ }
1375
+ foreach ( $render_delaying_extensions as $extension ) {
1376
+ if ( isset( $amp_scripts[ $extension ] ) ) {
1377
+ $ordered_scripts[ $extension ] = $amp_scripts[ $extension ];
1378
+ unset( $amp_scripts[ $extension ] );
1379
+ }
1380
+ }
1381
+
1382
+ $ordered_scripts = array_merge( $ordered_scripts, $amp_scripts );
1383
+ foreach ( $ordered_scripts as $ordered_script ) {
1384
+ $head->insertBefore( $ordered_script, $previous_node->nextSibling );
1385
+ $previous_node = $ordered_script;
1386
+ }
1387
+
1388
+ /*
1389
+ * "Specify the <link> tag for your favicon."
1390
+ * {@link https://docs.google.com/document/d/169XUxtSSEJb16NfkrCr9y5lqhUR7vxXEAsNxBzg07fM/edit AMP Hosting Guide}
1391
+ */
1392
+ if ( isset( $links['icon'] ) ) {
1393
+ foreach ( $links['icon'] as $link ) {
1394
+ $link->parentNode->removeChild( $link ); // So we can move it.
1395
+ $head->insertBefore( $link, $previous_node->nextSibling );
1396
+ $previous_node = $link;
1397
+ }
1398
+ }
1399
+
1400
+ // Note the style[amp-custom] and style[amp-boilerplate] are output in the add_hooks() method.
1401
+ unset( $previous_node );
1402
  }
1403
 
1404
  /**
1431
  public static function start_output_buffering() {
1432
  /*
1433
  * Disable the New Relic Browser agent on AMP responses.
1434
+ * This prevents the New Relic from causing invalid AMP responses due the NREUM script it injects after the meta charset:
1435
  * https://docs.newrelic.com/docs/browser/new-relic-browser/troubleshooting/google-amp-validator-fails-due-3rd-party-script
1436
  * Sites with New Relic will need to specially configure New Relic for AMP:
1437
  * https://docs.newrelic.com/docs/browser/new-relic-browser/installation/monitor-amp-pages-new-relic-browser
1502
  * @since 0.7
1503
  *
1504
  * @param string $response HTML document response. By default it expects a complete document.
1505
+ * @param array $args Args to send to the preprocessor/sanitizer.
 
 
 
 
1506
  * @return string AMP document response.
1507
  * @global int $content_width
1508
  */
1509
  public static function prepare_response( $response, $args = array() ) {
1510
  global $content_width;
1511
+ $prepare_response_start = microtime( true );
1512
+
1513
+ if ( isset( $args['validation_error_callback'] ) ) {
1514
+ _doing_it_wrong( __METHOD__, 'Do not supply validation_error_callback arg.', '1.0' );
1515
+ unset( $args['validation_error_callback'] );
1516
+ }
1517
 
1518
  /*
1519
  * Check if the response starts with HTML markup.
1524
  return $response;
1525
  }
1526
 
1527
+ // Dependencies on the PWA plugin for service worker streaming.
1528
+ $stream_fragment = null;
1529
+ if ( class_exists( 'WP_Service_Worker_Navigation_Routing_Component' ) && current_theme_supports( WP_Service_Worker_Navigation_Routing_Component::STREAM_THEME_SUPPORT ) ) {
1530
+ $stream_fragment = WP_Service_Worker_Navigation_Routing_Component::get_stream_fragment_query_var();
1531
+ }
1532
 
1533
  $args = array_merge(
1534
  array(
1536
  'use_document_element' => true,
1537
  'allow_dirty_styles' => self::is_customize_preview_iframe(), // Dirty styles only needed when editing (e.g. for edit shortcodes).
1538
  'allow_dirty_scripts' => is_customize_preview(), // Scripts are always needed to inject changeset UUID.
1539
+ 'enable_response_caching' => (
1540
+ ! ( defined( 'WP_DEBUG' ) && WP_DEBUG )
1541
+ &&
1542
+ ! AMP_Validation_Manager::should_validate_response()
1543
+ &&
1544
+ ! is_customize_preview()
1545
+ ),
1546
+ 'user_can_validate' => AMP_Validation_Manager::has_cap(),
1547
+ 'stream_fragment' => $stream_fragment,
1548
  ),
1549
  $args
1550
  );
1551
 
1552
+ $current_url = amp_get_current_url();
1553
+ $ampless_url = amp_remove_endpoint( $current_url );
1554
+
1555
+ // When response caching is enabled, determine if it should be turned off for cache misses.
1556
+ $caches_for_url = null;
1557
+ if ( true === $args['enable_response_caching'] ) {
1558
+ list( $disable_response_caching, $caches_for_url ) = self::check_for_cache_misses();
1559
+ $args['enable_response_caching'] = ! $disable_response_caching;
1560
+ }
1561
+
1562
+ /*
1563
+ * Set response cache hash, the data values dictates whether a new hash key should be generated or not.
1564
+ * This is also used as the ETag.
1565
+ */
1566
+ $response_cache_key = md5( wp_json_encode( array(
1567
+ $args,
1568
+ $response,
1569
+ self::$sanitizer_classes,
1570
+ self::$embed_handlers,
1571
+ AMP__VERSION,
1572
+ ) ) );
1573
+
1574
+ /*
1575
+ * Per rfc7232:
1576
+ * "The server generating a 304 response MUST generate any of the
1577
+ * following header fields that would have been sent in a 200 (OK)
1578
+ * response to the same request: Cache-Control, Content-Location, Date,
1579
+ * ETag, Expires, and Vary." The only one of these headers which would
1580
+ * not have been set yet during the WordPress template generation is
1581
+ * the ETag. The AMP plugin sends a Vary header at amp_init.
1582
+ */
1583
+ AMP_HTTP::send_header( 'ETag', $response_cache_key );
1584
+
1585
+ // Handle responses that are cached by the browser.
1586
+ if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) && $_SERVER['HTTP_IF_NONE_MATCH'] === $response_cache_key ) {
1587
+ status_header( 304 );
1588
+ return '';
1589
+ }
1590
+
1591
+ // Return cache if enabled and found.
1592
+ $cache_response = null;
1593
+ if ( true === $args['enable_response_caching'] ) {
1594
+ $response_cache = wp_cache_get( $response_cache_key, self::RESPONSE_CACHE_GROUP );
1595
+
1596
+ // Make sure that all of the validation errors should be sanitized in the same way; if not, then the cached body should be discarded.
1597
+ $blocking_error_count = 0;
1598
+ if ( isset( $response_cache['validation_results'] ) ) {
1599
+ foreach ( $response_cache['validation_results'] as $validation_result ) {
1600
+ if ( ! $validation_result['sanitized'] ) {
1601
+ $blocking_error_count++;
1602
+ }
1603
+ $should_sanitize = AMP_Validation_Error_Taxonomy::is_validation_error_sanitized( $validation_result['error'] );
1604
+ if ( $should_sanitize !== $validation_result['sanitized'] ) {
1605
+ unset( $response_cache['body'] );
1606
+ break;
1607
+ }
1608
+ }
1609
+ }
1610
+
1611
+ // Short-circuit response with cached body.
1612
+ if ( isset( $response_cache['body'] ) ) {
1613
+
1614
+ // Re-send the headers that were sent before when the response was first cached.
1615
+ if ( isset( $response_cache['headers'] ) ) {
1616
+ foreach ( $response_cache['headers'] as $header ) {
1617
+ if ( in_array( $header, AMP_HTTP::$headers_sent, true ) ) {
1618
+ continue; // Skip sending headers that were already sent prior to post-processing.
1619
+ }
1620
+ AMP_HTTP::send_header( $header['name'], $header['value'], wp_array_slice_assoc( $header, array( 'replace', 'status_code' ) ) );
1621
+ }
1622
+ }
1623
+
1624
+ AMP_HTTP::send_server_timing( 'amp_processor_cache_hit', -$prepare_response_start );
1625
+
1626
+ // Redirect to non-AMP version.
1627
+ if ( ! amp_is_canonical() && $blocking_error_count > 0 ) {
1628
+ if ( AMP_Validation_Manager::has_cap() ) {
1629
+ $ampless_url = add_query_arg( AMP_Validation_Manager::VALIDATION_ERRORS_QUERY_VAR, $blocking_error_count, $ampless_url );
1630
+ }
1631
+ wp_safe_redirect( $ampless_url );
1632
+ }
1633
+ return $response_cache['body'];
1634
+ }
1635
+
1636
+ $cache_response = function( $body, $validation_results ) use ( $response_cache_key, $caches_for_url ) {
1637
+ $caches_for_url[] = $response_cache_key;
1638
+ wp_cache_set(
1639
+ AMP_Theme_Support::POST_PROCESSOR_CACHE_EFFECTIVENESS_KEY,
1640
+ $caches_for_url,
1641
+ AMP_Theme_Support::POST_PROCESSOR_CACHE_EFFECTIVENESS_GROUP,
1642
+ 600 // 10 minute cache.
1643
+ );
1644
+
1645
+ return wp_cache_set(
1646
+ $response_cache_key,
1647
+ array(
1648
+ 'headers' => AMP_HTTP::$headers_sent,
1649
+ 'body' => $body,
1650
+ 'validation_results' => $validation_results,
1651
+ ),
1652
+ AMP_Theme_Support::RESPONSE_CACHE_GROUP,
1653
+ MONTH_IN_SECONDS
1654
+ );
1655
+ };
1656
+ }
1657
+
1658
+ AMP_HTTP::send_server_timing( 'amp_output_buffer', -self::$init_start_time, 'AMP Output Buffer' );
1659
+
1660
+ $dom_parse_start = microtime( true );
1661
+
1662
  /*
1663
  * Make sure that <meta charset> is present in output prior to parsing.
1664
  * Note that the meta charset is supposed to appear within the first 1024 bytes.
1672
  1
1673
  );
1674
  }
 
 
 
1675
 
1676
+ $dom = AMP_DOM_Utils::get_dom( $response );
1677
  $head = $dom->getElementsByTagName( 'head' )->item( 0 );
1678
 
1679
+ // Remove scripts that are being added for PWA service worker streaming for restoration later.
1680
+ $stream_combine_script_define_element = null;
1681
+ $stream_combine_script_define_placeholder = null;
1682
+ $stream_combine_script_invoke_element = null;
1683
+ $stream_combine_script_invoke_placeholder = null;
1684
+ if ( 'header' === $stream_fragment ) {
1685
+ $stream_combine_script_define_element = $dom->getElementById( WP_Service_Worker_Navigation_Routing_Component::STREAM_COMBINE_DEFINE_SCRIPT_ID );
1686
+ if ( $stream_combine_script_define_element ) {
1687
+ $stream_combine_script_define_placeholder = $dom->createComment( WP_Service_Worker_Navigation_Routing_Component::STREAM_COMBINE_DEFINE_SCRIPT_ID );
1688
+ $stream_combine_script_define_element->parentNode->replaceChild( $stream_combine_script_define_placeholder, $stream_combine_script_define_element );
1689
+ }
1690
+ } elseif ( 'body' === $stream_fragment ) {
1691
+ $stream_combine_script_invoke_placeholder = $dom->getElementById( WP_Service_Worker_Navigation_Routing_Component::STREAM_FRAGMENT_BOUNDARY_ELEMENT_ID );
1692
+ }
1693
+
1694
+ // Move anything after </html>, such as Query Monitor output added at shutdown, to be moved before </body>.
1695
+ $body = $dom->getElementsByTagName( 'body' )->item( 0 );
1696
+ if ( $body ) {
1697
+ while ( $dom->documentElement->nextSibling ) {
1698
+ // Trailing elements after </html> will get wrapped in additional <html> elements.
1699
+ if ( 'html' === $dom->documentElement->nextSibling->nodeName ) {
1700
+ while ( $dom->documentElement->nextSibling->firstChild ) {
1701
+ $body->appendChild( $dom->documentElement->nextSibling->firstChild );
1702
+ }
1703
+ $dom->removeChild( $dom->documentElement->nextSibling );
1704
+ } else {
1705
+ $body->appendChild( $dom->documentElement->nextSibling );
1706
+ }
1707
+ }
1708
+ }
1709
+
1710
+ // Make sure scripts from the body get moved to the head.
1711
  if ( isset( $head ) ) {
1712
+ $xpath = new DOMXPath( $dom );
1713
  foreach ( $xpath->query( '//body//script[ @custom-element or @custom-template ]' ) as $script ) {
1714
+ $head->appendChild( $script->parentNode->removeChild( $script ) );
1715
  }
1716
  }
1717
 
1718
+ // Ensure the mandatory amp attribute is present on the html element.
1719
  if ( ! $dom->documentElement->hasAttribute( 'amp' ) && ! $dom->documentElement->hasAttribute( '⚡️' ) ) {
1720
  $dom->documentElement->setAttribute( 'amp', '' );
1721
  }
1722
 
1723
+ AMP_HTTP::send_server_timing( 'amp_dom_parse', -$dom_parse_start, 'AMP DOM Parse' );
1724
+
1725
  $assets = AMP_Content_Sanitizer::sanitize_document( $dom, self::$sanitizer_classes, $args );
1726
 
1727
+ // Determine what the validation errors are.
1728
+ $blocking_error_count = 0;
1729
+ $validation_results = array();
1730
+ foreach ( AMP_Validation_Manager::$validation_results as $validation_result ) {
1731
+ if ( ! $validation_result['sanitized'] ) {
1732
+ $blocking_error_count++;
1733
+ }
1734
+ unset( $validation_result['error']['sources'] );
1735
+ $validation_results[] = $validation_result;
1736
+ }
1737
+
1738
+ $dom_serialize_start = microtime( true );
1739
+
1740
+ // Gather all component scripts that are used in the document and then render any not already printed.
1741
+ $amp_scripts = $assets['scripts'];
1742
+ foreach ( self::$embed_handlers as $embed_handler ) {
1743
+ $amp_scripts = array_merge(
1744
+ $amp_scripts,
1745
+ $embed_handler->get_scripts()
1746
+ );
1747
+ }
1748
+ foreach ( $amp_scripts as $handle => $src ) {
1749
+ /*
1750
+ * Make sure the src is up-to-date. This allows for embed handlers to override the
1751
+ * default extension version by defining a different URL.
1752
+ */
1753
+ if ( is_string( $src ) && wp_script_is( $handle, 'registered' ) ) {
1754
+ wp_scripts()->registered[ $handle ]->src = $src;
1755
+ }
1756
+ }
1757
+
1758
+ self::ensure_required_markup( $dom, array_keys( $amp_scripts ) );
1759
+
1760
+ if ( $blocking_error_count > 0 && ! AMP_Validation_Manager::should_validate_response() ) {
1761
+ /*
1762
+ * In native AMP, strip html@amp attribute to prevent GSC from complaining about a validation error
1763
+ * already surfaced inside of WordPress. This is intended to not serve dirty AMP, but rather a
1764
+ * non-AMP document (intentionally not valid AMP) that contains the AMP runtime and AMP components.
1765
+ */
1766
+ if ( amp_is_canonical() ) {
1767
+ $dom->documentElement->removeAttribute( 'amp' );
1768
+ $dom->documentElement->removeAttribute( '⚡️' );
1769
+
1770
+ /*
1771
+ * Make sure that document.write() is disabled to prevent dynamically-added content (such as added
1772
+ * via amp-live-list) from wiping out the page by introducing any scripts that call this function.
1773
+ */
1774
+ if ( $head ) {
1775
+ $script = $dom->createElement( 'script' );
1776
+ $script->appendChild( $dom->createTextNode( 'document.addEventListener( "DOMContentLoaded", function() { document.write = function( text ) { throw new Error( "[AMP-WP] Prevented document.write() call with: " + text ); }; } );' ) );
1777
+ $head->appendChild( $script );
1778
+ }
1779
+ } elseif ( ! self::is_customize_preview_iframe() ) {
1780
+ $response = esc_html__( 'Redirecting to non-AMP version.', 'amp' );
1781
+
1782
+ if ( $cache_response ) {
1783
+ $cache_response( $response, $validation_results );
1784
+ }
1785
+
1786
+ /*
1787
+ * Temporary redirect because AMP URL may return when blocking validation errors
1788
+ * occur or when a non-canonical AMP theme is used.
1789
+ */
1790
+ if ( AMP_Validation_Manager::has_cap() ) {
1791
+ $ampless_url = add_query_arg( AMP_Validation_Manager::VALIDATION_ERRORS_QUERY_VAR, $blocking_error_count, $ampless_url );
1792
+ }
1793
+ wp_safe_redirect( $ampless_url, 302 );
1794
+ return $response;
1795
+ }
1796
+ }
1797
 
1798
  // @todo If 'utf-8' is not the blog charset, then we'll need to do some character encoding conversation or "entityification".
1799
  if ( 'utf-8' !== strtolower( get_bloginfo( 'charset' ) ) ) {
1800
+ /* translators: %s: the charset of the current site. */
1801
  trigger_error( esc_html( sprintf( __( 'The database has the %s encoding when it needs to be utf-8 to work with AMP.', 'amp' ), get_bloginfo( 'charset' ) ) ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
1802
  }
1803
 
1804
+ AMP_Validation_Manager::finalize_validation( $dom, array(
1805
+ 'remove_source_comments' => ! isset( $_GET['amp_preserve_source_comments'] ), // WPCS: CSRF.
1806
+ ) );
1807
+
1808
+ // For service worker streaming, restore the script that was removed above and obtain the script that should be added to the body fragment.
1809
+ $truncate_after_comment = null;
1810
+ $truncate_before_comment = null;
1811
+ if ( $stream_fragment ) {
1812
+ if ( $stream_combine_script_define_placeholder && $stream_combine_script_define_element ) {
1813
+ $stream_combine_script_define_placeholder->parentNode->replaceChild( $stream_combine_script_define_element, $stream_combine_script_define_placeholder );
1814
+ $truncate_after_comment = $dom->createComment( 'AMP_TRUNCATE_RESPONSE_FOR_STREAM_HEADER' );
1815
+ $stream_combine_script_define_element->parentNode->insertBefore( $truncate_after_comment, $stream_combine_script_define_element->nextSibling );
1816
+ }
1817
+ if ( $stream_combine_script_invoke_placeholder ) {
1818
+ $stream_combine_script_invoke_element = WP_Service_Worker_Navigation_Routing_Component::get_header_combine_invoke_script( $dom, false );
1819
+ $stream_combine_script_invoke_placeholder->parentNode->replaceChild( $stream_combine_script_invoke_element, $stream_combine_script_invoke_placeholder );
1820
+ $truncate_before_comment = $dom->createComment( 'AMP_TRUNCATE_RESPONSE_FOR_STREAM_BODY' );
1821
+ $stream_combine_script_invoke_element->parentNode->insertBefore( $truncate_before_comment, $stream_combine_script_invoke_element );
1822
+ }
1823
  }
1824
 
1825
  $response = "<!DOCTYPE html>\n";
1826
  $response .= AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement );
1827
 
1828
+ // For service worker streaming, make sure that the header response doesn't contain closing tags, and that the body fragment starts with the required script tag.
1829
+ if ( $truncate_after_comment ) {
1830
+ $search = sprintf( '<!--%s-->', $truncate_after_comment->nodeValue );
1831
+ $position = strpos( $response, $search );
1832
+ if ( false !== $position ) {
1833
+ $response = substr( $response, 0, $position );
1834
+ }
1835
+ }
1836
+ if ( $truncate_before_comment ) {
1837
+ $search = sprintf( '<!--%s-->', $truncate_before_comment->nodeValue );
1838
+ $position = strpos( $response, $search );
1839
+ if ( false !== $position ) {
1840
+ $response = substr( $response, $position + strlen( $search ) );
1841
+ }
1842
  }
1843
 
1844
+ AMP_HTTP::send_server_timing( 'amp_dom_serialize', -$dom_serialize_start, 'AMP DOM Serialize' );
1845
+
1846
+ // Cache response if enabled.
1847
+ if ( $cache_response ) {
1848
+ $cache_response( $response, $validation_results );
 
 
 
 
1849
  }
1850
 
1851
  return $response;
1852
  }
1853
 
1854
+ /**
1855
+ * Check for cache misses. When found, store in an option to retain the URL.
1856
+ *
1857
+ * @since 1.0
1858
+ *
1859
+ * @return array {
1860
+ * State.
1861
+ *
1862
+ * @type bool Flag indicating if the threshold has been exceeded.
1863
+ * @type string[] Collection of URLs.
1864
+ * }
1865
+ */
1866
+ private static function check_for_cache_misses() {
1867
+ // If the cache miss threshold is exceeded, return true.
1868
+ if ( self::exceeded_cache_miss_threshold() ) {
1869
+ return array( true, null );
1870
+ }
1871
+
1872
+ // Get the cache miss URLs.
1873
+ $cache_miss_urls = wp_cache_get( self::POST_PROCESSOR_CACHE_EFFECTIVENESS_KEY, self::POST_PROCESSOR_CACHE_EFFECTIVENESS_GROUP );
1874
+ $cache_miss_urls = is_array( $cache_miss_urls ) ? $cache_miss_urls : array();
1875
+
1876
+ $exceeded_threshold = (
1877
+ ! empty( $cache_miss_urls )
1878
+ &&
1879
+ count( $cache_miss_urls ) >= self::CACHE_MISS_THRESHOLD
1880
+ );
1881
+
1882
+ if ( ! $exceeded_threshold ) {
1883
+ return array( $exceeded_threshold, $cache_miss_urls );
1884
+ }
1885
+
1886
+ // When the threshold is exceeded, store the URL for cache miss and turn off response caching.
1887
+ update_option( self::CACHE_MISS_URL_OPTION, amp_get_current_url() );
1888
+ AMP_Options_Manager::update_option( 'enable_response_caching', false );
1889
+ return array( true, null );
1890
+ }
1891
+
1892
+ /**
1893
+ * Reset the cache miss URL option.
1894
+ *
1895
+ * @since 1.0
1896
+ */
1897
+ public static function reset_cache_miss_url_option() {
1898
+ if ( get_option( self::CACHE_MISS_URL_OPTION ) ) {
1899
+ delete_option( self::CACHE_MISS_URL_OPTION );
1900
+ }
1901
+ }
1902
+
1903
+ /**
1904
+ * Checks if cache miss threshold has been exceeded.
1905
+ *
1906
+ * @since 1.0
1907
+ *
1908
+ * @return bool
1909
+ */
1910
+ public static function exceeded_cache_miss_threshold() {
1911
+ $url = get_option( self::CACHE_MISS_URL_OPTION, false );
1912
+ return ! empty( $url );
1913
+ }
1914
+
1915
  /**
1916
  * Adds 'data-amp-layout' to the allowed <img> attributes for wp_kses().
1917
  *
1941
  // Enqueue default styles expected by sanitizer.
1942
  wp_enqueue_style( 'amp-default', amp_get_asset_url( 'css/amp-default.css' ), array(), AMP__VERSION );
1943
  }
1944
+
1945
+ /**
1946
+ * Conditionally replace the header image markup with a header video or image.
1947
+ *
1948
+ * This is JS-driven in Core themes like Twenty Sixteen and Twenty Seventeen.
1949
+ * So in order for the header video to display, this replaces the markup of the header image.
1950
+ *
1951
+ * @since 1.0
1952
+ * @link https://github.com/WordPress/wordpress-develop/blob/d002fde80e5e3a083e5f950313163f566561517f/src/wp-includes/js/wp-custom-header.js#L54
1953
+ * @link https://github.com/WordPress/wordpress-develop/blob/d002fde80e5e3a083e5f950313163f566561517f/src/wp-includes/js/wp-custom-header.js#L78
1954
+ *
1955
+ * @param string $image_markup The image markup to filter.
1956
+ * @return string $html Filtered markup.
1957
+ */
1958
+ public static function amend_header_image_with_video_header( $image_markup ) {
1959
+
1960
+ // If there is no video, just pass the image through.
1961
+ if ( ! has_header_video() || ! is_header_video_active() ) {
1962
+ return $image_markup;
1963
+ };
1964
+
1965
+ $video_settings = get_header_video_settings();
1966
+ $parsed_url = wp_parse_url( $video_settings['videoUrl'] );
1967
+ $query = isset( $parsed_url['query'] ) ? wp_parse_args( $parsed_url['query'] ) : array();
1968
+ $video_attributes = array(
1969
+ 'media' => '(min-width: ' . $video_settings['minWidth'] . 'px)',
1970
+ 'width' => $video_settings['width'],
1971
+ 'height' => $video_settings['height'],
1972
+ 'layout' => 'responsive',
1973
+ 'autoplay' => '',
1974
+ 'id' => 'wp-custom-header-video',
1975
+ );
1976
+
1977
+ $youtube_id = null;
1978
+ if ( isset( $parsed_url['host'] ) && preg_match( '/(^|\.)(youtube\.com|youtu\.be)$/', $parsed_url['host'] ) ) {
1979
+ if ( 'youtu.be' === $parsed_url['host'] && ! empty( $parsed_url['path'] ) ) {
1980
+ $youtube_id = trim( $parsed_url['path'], '/' );
1981
+ } elseif ( isset( $query['v'] ) ) {
1982
+ $youtube_id = $query['v'];
1983
+ }
1984
+ }
1985
+
1986
+ // If the video URL is for YouTube, return an <amp-youtube> element.
1987
+ if ( ! empty( $youtube_id ) ) {
1988
+ $video_markup = AMP_HTML_Utils::build_tag(
1989
+ 'amp-youtube',
1990
+ array_merge(
1991
+ $video_attributes,
1992
+ array(
1993
+ 'data-videoid' => $youtube_id,
1994
+ 'data-param-rel' => '0', // Don't show related videos.
1995
+ 'data-param-showinfo' => '0', // Don't show video title at the top.
1996
+ 'data-param-controls' => '0', // Don't show video controls.
1997
+ )
1998
+ )
1999
+ );
2000
+ } else {
2001
+ $video_markup = AMP_HTML_Utils::build_tag(
2002
+ 'amp-video',
2003
+ array_merge(
2004
+ $video_attributes,
2005
+ array(
2006
+ 'src' => $video_settings['videoUrl'],
2007
+ )
2008
+ )
2009
+ );
2010
+ }
2011
+
2012
+ return $image_markup . $video_markup;
2013
+ }
2014
  }
includes/{lib/fasterimage/amp-fasterimage.php → deprecated.php} RENAMED
@@ -1,6 +1,6 @@
1
  <?php
2
  /**
3
- * Functions for FasterImage.
4
  *
5
  * @package AMP
6
  */
@@ -8,9 +8,7 @@
8
  /**
9
  * Load classes for FasterImage.
10
  *
11
- * This is obsolete now that there is an autoloader.
12
- *
13
- * @deprecated
14
  */
15
  function amp_load_fasterimage_classes() {
16
  _deprecated_function( __FUNCTION__, '0.6' );
@@ -19,9 +17,12 @@ function amp_load_fasterimage_classes() {
19
  /**
20
  * Get FasterImage client for user agent.
21
  *
 
 
22
  * @param string $user_agent User Agent.
23
  * @return \FasterImage\FasterImage Instance.
24
  */
25
  function amp_get_fasterimage_client( $user_agent ) {
 
26
  return new FasterImage\FasterImage( $user_agent );
27
  }
1
  <?php
2
  /**
3
+ * Deprecated functions.
4
  *
5
  * @package AMP
6
  */
8
  /**
9
  * Load classes for FasterImage.
10
  *
11
+ * @deprecated This is obsolete now that there is an autoloader.
 
 
12
  */
13
  function amp_load_fasterimage_classes() {
14
  _deprecated_function( __FUNCTION__, '0.6' );
17
  /**
18
  * Get FasterImage client for user agent.
19
  *
20
+ * @deprecated This function is no longer used in favor of just instantiating the class.
21
+ *
22
  * @param string $user_agent User Agent.
23
  * @return \FasterImage\FasterImage Instance.
24
  */
25
  function amp_get_fasterimage_client( $user_agent ) {
26
+ _deprecated_function( __FUNCTION__, '1.0' );
27
  return new FasterImage\FasterImage( $user_agent );
28
  }
includes/embeds/class-amp-base-embed-handler.php CHANGED
@@ -11,18 +11,52 @@
11
  * Class AMP_Base_Embed_Handler
12
  */
13
  abstract class AMP_Base_Embed_Handler {
 
 
 
 
 
14
  protected $DEFAULT_WIDTH = 600;
 
 
 
 
 
 
15
  protected $DEFAULT_HEIGHT = 480;
16
 
 
 
 
 
 
17
  protected $args = array();
 
 
 
 
 
 
18
  protected $did_convert_elements = false;
19
 
20
- abstract function register_embed();
21
- abstract function unregister_embed();
 
 
22
 
23
- function __construct( $args = array() ) {
 
 
 
 
 
 
 
 
 
 
24
  $this->args = wp_parse_args( $args, array(
25
- 'width' => $this->DEFAULT_WIDTH,
26
  'height' => $this->DEFAULT_HEIGHT,
27
  ) );
28
  }
11
  * Class AMP_Base_Embed_Handler
12
  */
13
  abstract class AMP_Base_Embed_Handler {
14
+ /**
15
+ * Default width.
16
+ *
17
+ * @var int
18
+ */
19
  protected $DEFAULT_WIDTH = 600;
20
+
21
+ /**
22
+ * Default height.
23
+ *
24
+ * @var int
25
+ */
26
  protected $DEFAULT_HEIGHT = 480;
27
 
28
+ /**
29
+ * Default arguments.
30
+ *
31
+ * @var array
32
+ */
33
  protected $args = array();
34
+
35
+ /**
36
+ * Whether or not conversion was completed.
37
+ *
38
+ * @var boolean
39
+ */
40
  protected $did_convert_elements = false;
41
 
42
+ /**
43
+ * Register embed.
44
+ */
45
+ abstract public function register_embed();
46
 
47
+ /**
48
+ * Unregister embed.
49
+ */
50
+ abstract public function unregister_embed();
51
+
52
+ /**
53
+ * AMP_Base_Embed_Handler constructor.
54
+ *
55
+ * @param array $args Height and width for embed.
56
+ */
57
+ public function __construct( $args = array() ) {
58
  $this->args = wp_parse_args( $args, array(
59
+ 'width' => $this->DEFAULT_WIDTH,
60
  'height' => $this->DEFAULT_HEIGHT,
61
  ) );
62
  }
includes/embeds/class-amp-core-block-handler.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Core_Block_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Core_Block_Handler
10
+ *
11
+ * @since 1.0
12
+ */
13
+ class AMP_Core_Block_Handler extends AMP_Base_Embed_Handler {
14
+
15
+ /**
16
+ * Original block callback.
17
+ *
18
+ * @var array
19
+ */
20
+ public $original_categories_callback;
21
+
22
+ /**
23
+ * Block name.
24
+ *
25
+ * @var string
26
+ */
27
+ public $block_name = 'core/categories';
28
+
29
+ /**
30
+ * Register embed.
31
+ */
32
+ public function register_embed() {
33
+ if ( class_exists( 'WP_Block_Type_Registry' ) ) {
34
+ $registry = WP_Block_Type_Registry::get_instance();
35
+ $block = $registry->get_registered( $this->block_name );
36
+
37
+ if ( $block ) {
38
+ $this->original_categories_callback = $block->render_callback;
39
+ $block->render_callback = array( $this, 'render' );
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Unregister embed.
46
+ */
47
+ public function unregister_embed() {
48
+ if ( class_exists( 'WP_Block_Type_Registry' ) ) {
49
+ $registry = WP_Block_Type_Registry::get_instance();
50
+ $block = $registry->get_registered( $this->block_name );
51
+
52
+ if ( $block && ! empty( $this->original_categories_callback ) ) {
53
+ $block->render_callback = $this->original_categories_callback;
54
+ $this->original_categories_callback = null;
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Render Gutenberg block. This is essentially the same method as the original.
61
+ * Difference is excluding the disallowed JS script, adding <form> tags, and using on:change for <select>.
62
+ *
63
+ * @param array $attributes Attributes.
64
+ * @return string Rendered.
65
+ */
66
+ public function render( $attributes ) {
67
+ static $block_id = 0;
68
+ $block_id++;
69
+
70
+ $align = 'center';
71
+ if ( isset( $attributes['align'] ) && in_array( $attributes['align'], array( 'left', 'right', 'full' ), true ) ) {
72
+ $align = $attributes['align'];
73
+ }
74
+
75
+ $args = array(
76
+ 'echo' => false,
77
+ 'hierarchical' => ! empty( $attributes['showHierarchy'] ),
78
+ 'orderby' => 'name',
79
+ 'show_count' => ! empty( $attributes['showPostCounts'] ),
80
+ 'title_li' => '',
81
+ );
82
+
83
+ if ( ! empty( $attributes['displayAsDropdown'] ) ) {
84
+ $id = 'wp-block-categories-dropdown-' . $block_id;
85
+ $form_id = $id . '-form';
86
+ $args['id'] = $id;
87
+ $args['show_option_none'] = __( 'Select Category', 'amp' );
88
+ $wrapper_markup = '<div class="%1$s">%2$s</div>';
89
+ $items_markup = wp_dropdown_categories( $args );
90
+ $type = 'dropdown';
91
+
92
+ $items_markup = preg_replace(
93
+ '/(?<=<select\b)/',
94
+ sprintf( ' on="change:%s.submit"', esc_attr( $form_id ) ),
95
+ $items_markup,
96
+ 1
97
+ );
98
+ } else {
99
+ $wrapper_markup = '<div class="%1$s"><ul>%2$s</ul></div>';
100
+ $items_markup = wp_list_categories( $args );
101
+ $type = 'list';
102
+ }
103
+
104
+ $class = "wp-block-categories wp-block-categories-{$type} align{$align}";
105
+
106
+ $block_content = sprintf(
107
+ $wrapper_markup,
108
+ esc_attr( $class ),
109
+ $items_markup
110
+ );
111
+
112
+ if ( ! empty( $attributes['displayAsDropdown'] ) ) {
113
+ $block_content = sprintf( '<form action="%s" method="get" target="_top" id="%s">%s</form>', esc_url( home_url() ), esc_attr( $form_id ), $block_content );
114
+ }
115
+ return $block_content;
116
+ }
117
+ }
includes/embeds/class-amp-dailymotion-embed.php CHANGED
@@ -13,31 +13,59 @@
13
  class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
14
 
15
  const URL_PATTERN = '#https?:\/\/(www\.)?dailymotion\.com\/video\/.*#i';
16
- const RATIO = 0.5625;
17
 
 
 
 
 
 
18
  protected $DEFAULT_WIDTH = 600;
 
 
 
 
 
 
19
  protected $DEFAULT_HEIGHT = 338;
20
 
21
- function __construct( $args = array() ) {
 
 
 
 
 
22
  parent::__construct( $args );
23
 
24
  if ( isset( $this->args['content_max_width'] ) ) {
25
- $max_width = $this->args['content_max_width'];
26
- $this->args['width'] = $max_width;
27
  $this->args['height'] = round( $max_width * self::RATIO );
28
  }
29
  }
30
 
31
- function register_embed() {
 
 
 
32
  wp_embed_register_handler( 'amp-dailymotion', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
33
  add_shortcode( 'dailymotion', array( $this, 'shortcode' ) );
34
  }
35
 
 
 
 
36
  public function unregister_embed() {
37
  wp_embed_unregister_handler( 'amp-dailymotion', -1 );
38
  remove_shortcode( 'dailymotion' );
39
  }
40
 
 
 
 
 
 
 
41
  public function shortcode( $attr ) {
42
  $video_id = false;
43
 
@@ -53,25 +81,53 @@ class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
53
  return '';
54
  }
55
 
56
- return $this->render( array(
57
- 'video_id' => $video_id,
58
- ) );
 
 
59
  }
60
 
 
 
 
 
 
 
 
 
 
 
 
61
  public function oembed( $matches, $attr, $url, $rawattr ) {
62
  $video_id = $this->get_video_id_from_url( $url );
63
- return $this->render( array(
64
- 'video_id' => $video_id,
65
- ) );
 
 
66
  }
67
 
 
 
 
 
 
 
68
  public function render( $args ) {
69
- $args = wp_parse_args( $args, array(
70
- 'video_id' => false,
71
- ) );
 
 
72
 
73
  if ( empty( $args['video_id'] ) ) {
74
- return AMP_HTML_Utils::build_tag( 'a', array( 'href' => esc_url( $args['url'] ), 'class' => 'amp-wp-embed-fallback' ), esc_html( $args['url'] ) );
 
 
 
 
 
75
  }
76
 
77
  $this->did_convert_elements = true;
@@ -80,18 +136,24 @@ class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
80
  'amp-dailymotion',
81
  array(
82
  'data-videoid' => $args['video_id'],
83
- 'layout' => 'responsive',
84
- 'width' => $this->args['width'],
85
- 'height' => $this->args['height'],
86
  )
87
  );
88
  }
89
 
 
 
 
 
 
 
90
  private function get_video_id_from_url( $url ) {
91
- $parsed_url = AMP_WP_Utils::parse_url( $url );
92
  parse_str( $parsed_url['path'], $path );
93
- $tok = explode( '/', $parsed_url['path'] );
94
- $tok = explode( '_', $tok[2] );
95
  $video_id = $tok[0];
96
 
97
  return $video_id;
13
  class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
14
 
15
  const URL_PATTERN = '#https?:\/\/(www\.)?dailymotion\.com\/video\/.*#i';
16
+ const RATIO = 0.5625;
17
 
18
+ /**
19
+ * Default width.
20
+ *
21
+ * @var int
22
+ */
23
  protected $DEFAULT_WIDTH = 600;
24
+
25
+ /**
26
+ * Default height.
27
+ *
28
+ * @var int
29
+ */
30
  protected $DEFAULT_HEIGHT = 338;
31
 
32
+ /**
33
+ * AMP_DailyMotion_Embed_Handler constructor.
34
+ *
35
+ * @param array $args Height, width and maximum width for embed.
36
+ */
37
+ public function __construct( $args = array() ) {
38
  parent::__construct( $args );
39
 
40
  if ( isset( $this->args['content_max_width'] ) ) {
41
+ $max_width = $this->args['content_max_width'];
42
+ $this->args['width'] = $max_width;
43
  $this->args['height'] = round( $max_width * self::RATIO );
44
  }
45
  }
46
 
47
+ /**
48
+ * Register embed.
49
+ */
50
+ public function register_embed() {
51
  wp_embed_register_handler( 'amp-dailymotion', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
52
  add_shortcode( 'dailymotion', array( $this, 'shortcode' ) );
53
  }
54
 
55
+ /**
56
+ * Unregister embed.
57
+ */
58
  public function unregister_embed() {
59
  wp_embed_unregister_handler( 'amp-dailymotion', -1 );
60
  remove_shortcode( 'dailymotion' );
61
  }
62
 
63
+ /**
64
+ * Gets AMP-compliant markup for the Dailymotion shortcode.
65
+ *
66
+ * @param array $attr The Dailymotion attributes.
67
+ * @return string Dailymotion shortcode markup.
68
+ */
69
  public function shortcode( $attr ) {
70
  $video_id = false;
71
 
81
  return '';
82
  }
83
 
84
+ return $this->render(
85
+ array(
86
+ 'video_id' => $video_id,
87
+ )
88
+ );
89
  }
90
 
91
+ /**
92
+ * Render oEmbed.
93
+ *
94
+ * @see \WP_Embed::shortcode()
95
+ *
96
+ * @param array $matches URL pattern matches.
97
+ * @param array $attr Shortcode attribues.
98
+ * @param string $url URL.
99
+ * @param string $rawattr Unmodified shortcode attributes.
100
+ * @return string Rendered oEmbed.
101
+ */
102
  public function oembed( $matches, $attr, $url, $rawattr ) {
103
  $video_id = $this->get_video_id_from_url( $url );
104
+ return $this->render(
105
+ array(
106
+ 'video_id' => $video_id,
107
+ )
108
+ );
109
  }
110
 
111
+ /**
112
+ * Render.
113
+ *
114
+ * @param array $args Args.
115
+ * @return string Rendered.
116
+ */
117
  public function render( $args ) {
118
+ $args = wp_parse_args(
119
+ $args, array(
120
+ 'video_id' => false,
121
+ )
122
+ );
123
 
124
  if ( empty( $args['video_id'] ) ) {
125
+ return AMP_HTML_Utils::build_tag(
126
+ 'a', array(
127
+ 'href' => esc_url( $args['url'] ),
128
+ 'class' => 'amp-wp-embed-fallback',
129
+ ), esc_html( $args['url'] )
130
+ );
131
  }
132
 
133
  $this->did_convert_elements = true;
136
  'amp-dailymotion',
137
  array(
138
  'data-videoid' => $args['video_id'],
139
+ 'layout' => 'responsive',
140
+ 'width' => $this->args['width'],
141
+ 'height' => $this->args['height'],
142
  )
143
  );
144
  }
145
 
146
+ /**
147
+ * Determine the video ID from the URL.
148
+ *
149
+ * @param string $url URL.
150
+ * @return integer Video ID.
151
+ */
152
  private function get_video_id_from_url( $url ) {
153
+ $parsed_url = wp_parse_url( $url );
154
  parse_str( $parsed_url['path'], $path );
155
+ $tok = explode( '/', $parsed_url['path'] );
156
+ $tok = explode( '_', $tok[2] );
157
  $video_id = $tok[0];
158
 
159
  return $video_id;
includes/embeds/class-amp-facebook-embed.php CHANGED
@@ -14,12 +14,26 @@ class AMP_Facebook_Embed_Handler extends AMP_Base_Embed_Handler {
14
  protected $DEFAULT_WIDTH = 600;
15
  protected $DEFAULT_HEIGHT = 400;
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  public function register_embed() {
18
- wp_embed_register_handler( 'amp-facebook', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
19
  }
20
 
21
  public function unregister_embed() {
22
- wp_embed_unregister_handler( 'amp-facebook', -1 );
23
  }
24
 
25
  public function oembed( $matches, $attr, $url, $rawattr ) {
@@ -38,13 +52,86 @@ class AMP_Facebook_Embed_Handler extends AMP_Base_Embed_Handler {
38
  $this->did_convert_elements = true;
39
 
40
  return AMP_HTML_Utils::build_tag(
41
- 'amp-facebook',
42
  array(
43
  'data-href' => $args['url'],
44
- 'layout' => 'responsive',
45
- 'width' => $this->args['width'],
46
- 'height' => $this->args['height'],
47
  )
48
  );
49
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
14
  protected $DEFAULT_WIDTH = 600;
15
  protected $DEFAULT_HEIGHT = 400;
16
 
17
+ /**
18
+ * Tag.
19
+ *
20
+ * @var string embed HTML blockquote tag to identify and replace with AMP version.
21
+ */
22
+ protected $sanitize_tag = 'div';
23
+
24
+ /**
25
+ * Tag.
26
+ *
27
+ * @var string AMP amp-facebook tag
28
+ */
29
+ private $amp_tag = 'amp-facebook';
30
+
31
  public function register_embed() {
32
+ wp_embed_register_handler( $this->amp_tag, self::URL_PATTERN, array( $this, 'oembed' ), -1 );
33
  }
34
 
35
  public function unregister_embed() {
36
+ wp_embed_unregister_handler( $this->amp_tag, -1 );
37
  }
38
 
39
  public function oembed( $matches, $attr, $url, $rawattr ) {
52
  $this->did_convert_elements = true;
53
 
54
  return AMP_HTML_Utils::build_tag(
55
+ $this->amp_tag,
56
  array(
57
  'data-href' => $args['url'],
58
+ 'layout' => 'responsive',
59
+ 'width' => $this->args['width'],
60
+ 'height' => $this->args['height'],
61
  )
62
  );
63
  }
64
+
65
+ /**
66
+ * Sanitized <div class="fb-video" data-href=> tags to <amp-facebook>.
67
+ *
68
+ * @param DOMDocument $dom DOM.
69
+ */
70
+ public function sanitize_raw_embeds( $dom ) {
71
+ /**
72
+ * Node list.
73
+ *
74
+ * @var DOMNodeList $node
75
+ */
76
+ $nodes = $dom->getElementsByTagName( $this->sanitize_tag );
77
+ $num_nodes = $nodes->length;
78
+
79
+ if ( 0 === $num_nodes ) {
80
+ return;
81
+ }
82
+
83
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
84
+ $node = $nodes->item( $i );
85
+ if ( ! $node instanceof DOMElement ) {
86
+ continue;
87
+ }
88
+
89
+ $embed_type = $this->get_embed_type( $node );
90
+
91
+ if ( null !== $embed_type ) {
92
+ $this->create_amp_facebook_and_replace_node( $dom, $node, $embed_type );
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Get embed type.
99
+ *
100
+ * @param DOMElement $node The DOMNode to adjust and replace.
101
+ * @return string|null Embed type or null if not detected.
102
+ */
103
+ private function get_embed_type( $node ) {
104
+ $class_attr = $node->getAttribute( 'class' );
105
+ if ( null !== $class_attr && $node->hasAttribute( 'data-href' ) ) {
106
+ if ( false !== strpos( $class_attr, 'fb-post' ) ) {
107
+ return 'post';
108
+ } elseif ( false !== strpos( $class_attr, 'fb-video' ) ) {
109
+ return 'video';
110
+ }
111
+ return false !== strpos( $class_attr, 'fb-video' ) ? 'video' : 'post';
112
+ }
113
+
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Create amp-facebook and replace node.
119
+ *
120
+ * @param DOMDocument $dom The HTML Document.
121
+ * @param DOMElement $node The DOMNode to adjust and replace.
122
+ * @param string $embed_type Embed type.
123
+ */
124
+ private function create_amp_facebook_and_replace_node( $dom, $node, $embed_type ) {
125
+ $amp_facebook_node = AMP_DOM_Utils::create_node( $dom, $this->amp_tag, array(
126
+ 'data-href' => $node->getAttribute( 'data-href' ),
127
+ 'data-embed-as' => $embed_type,
128
+ 'layout' => 'responsive',
129
+ 'width' => $this->DEFAULT_WIDTH,
130
+ 'height' => $this->DEFAULT_HEIGHT,
131
+ ) );
132
+
133
+ $node->parentNode->replaceChild( $amp_facebook_node, $node );
134
+
135
+ $this->did_convert_elements = true;
136
+ }
137
  }
includes/embeds/class-amp-gallery-embed.php CHANGED
@@ -16,7 +16,8 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
16
  * Register embed.
17
  */
18
  public function register_embed() {
19
- add_filter( 'post_gallery', array( $this, 'override_gallery' ), 10, 2 );
 
20
  }
21
 
22
  /**
@@ -51,6 +52,10 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
51
  'link' => 'none',
52
  ), $attr, 'gallery' );
53
 
 
 
 
 
54
  $id = intval( $atts['id'] );
55
 
56
  if ( ! empty( $atts['include'] ) ) {
@@ -99,10 +104,12 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
99
  }
100
 
101
  $href = null;
102
- if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) {
103
- $href = $url;
104
- } elseif ( ! empty( $atts['link'] ) && 'post' === $atts['link'] ) {
105
- $href = get_attachment_link( $attachment_id );
 
 
106
  }
107
 
108
  $urls[] = array(
@@ -113,13 +120,28 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
113
  );
114
  }
115
 
116
- return $this->render( array(
117
  'images' => $urls,
118
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
120
 
121
  /**
122
- * Override the output of gallery_shortcode().
123
  *
124
  * The 'Gallery' widget also uses this function.
125
  * This ensures that it outputs an <amp-carousel>.
@@ -128,7 +150,27 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
128
  * @param array $attributes Shortcode attributes.
129
  * @return string $html Markup for the gallery.
130
  */
131
- public function override_gallery( $html, $attributes ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  return $this->shortcode( $attributes );
133
  }
134
 
@@ -151,14 +193,21 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
151
 
152
  $images = array();
153
  foreach ( $args['images'] as $props ) {
 
 
 
 
 
 
 
 
 
 
 
 
154
  $image = AMP_HTML_Utils::build_tag(
155
  'amp-img',
156
- array(
157
- 'src' => $props['url'],
158
- 'width' => $props['width'],
159
- 'height' => $props['height'],
160
- 'layout' => 'responsive',
161
- )
162
  );
163
 
164
  if ( ! empty( $props['href'] ) ) {
@@ -185,4 +234,25 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
185
  implode( PHP_EOL, $images )
186
  );
187
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  }
16
  * Register embed.
17
  */
18
  public function register_embed() {
19
+ add_filter( 'post_gallery', array( $this, 'maybe_override_gallery' ), 10, 2 );
20
+ add_action( 'wp_print_styles', array( $this, 'print_styles' ) );
21
  }
22
 
23
  /**
52
  'link' => 'none',
53
  ), $attr, 'gallery' );
54
 
55
+ if ( ! empty( $attr['amp-lightbox'] ) ) {
56
+ $atts['lightbox'] = filter_var( $attr['amp-lightbox'], FILTER_VALIDATE_BOOLEAN );
57
+ }
58
+
59
  $id = intval( $atts['id'] );
60
 
61
  if ( ! empty( $atts['include'] ) ) {
104
  }
105
 
106
  $href = null;
107
+ if ( empty( $atts['lightbox'] ) ) {
108
+ if ( ! empty( $atts['link'] ) && 'file' === $atts['link'] ) {
109
+ $href = $url;
110
+ } elseif ( ! empty( $atts['link'] ) && 'post' === $atts['link'] ) {
111
+ $href = get_attachment_link( $attachment_id );
112
+ }
113
  }
114
 
115
  $urls[] = array(
120
  );
121
  }
122
 
123
+ $args = array(
124
  'images' => $urls,
125
+ );
126
+ if ( ! empty( $atts['lightbox'] ) ) {
127
+ $args['lightbox'] = true;
128
+ $lightbox_tag = AMP_HTML_Utils::build_tag(
129
+ 'amp-image-lightbox',
130
+ array(
131
+ 'id' => AMP_Base_Sanitizer::AMP_IMAGE_LIGHTBOX_ID,
132
+ 'layout' => 'nodisplay',
133
+ 'data-close-button-aria-label' => __( 'Close', 'amp' ),
134
+ )
135
+ );
136
+ /* We need to add lightbox tag, too. @todo Could there be a better alternative for this? */
137
+ return $this->render( $args ) . $lightbox_tag;
138
+ }
139
+
140
+ return $this->render( $args );
141
  }
142
 
143
  /**
144
+ * Override the output of gallery_shortcode() if amp-carousel is not false.
145
  *
146
  * The 'Gallery' widget also uses this function.
147
  * This ensures that it outputs an <amp-carousel>.
150
  * @param array $attributes Shortcode attributes.
151
  * @return string $html Markup for the gallery.
152
  */
153
+ public function maybe_override_gallery( $html, $attributes ) {
154
+ $is_lightbox = isset( $attributes['amp-lightbox'] ) && true === filter_var( $attributes['amp-lightbox'], FILTER_VALIDATE_BOOLEAN );
155
+ if ( isset( $attributes['amp-carousel'] ) && false === filter_var( $attributes['amp-carousel'], FILTER_VALIDATE_BOOLEAN ) ) {
156
+ if ( true === $is_lightbox ) {
157
+ remove_filter( 'post_gallery', array( $this, 'maybe_override_gallery' ), 10 );
158
+ $attributes['link'] = 'none';
159
+ $html = '<ul class="amp-lightbox">' . gallery_shortcode( $attributes ) . '</ul>';
160
+ add_filter( 'post_gallery', array( $this, 'maybe_override_gallery' ), 10, 2 );
161
+ }
162
+
163
+ return $html;
164
+ } elseif ( isset( $attributes['size'] ) && 'thumbnail' === $attributes['size'] ) {
165
+ /*
166
+ * If the 'gallery' shortcode has a 'size' attribute of 'thumbnail', prevent outputting an <amp-carousel>.
167
+ * That will often get thumbnail images around 150 x 150,
168
+ * while the <amp-carousel> usually has a width of 600 and a height of 480.
169
+ * That often means very low-resolution images.
170
+ * So fall back to the normal 'gallery' shortcode callback, gallery_shortcode().
171
+ */
172
+ return '';
173
+ }
174
  return $this->shortcode( $attributes );
175
  }
176
 
193
 
194
  $images = array();
195
  foreach ( $args['images'] as $props ) {
196
+ $image_atts = array(
197
+ 'src' => $props['url'],
198
+ 'width' => $props['width'],
199
+ 'height' => $props['height'],
200
+ 'layout' => 'responsive',
201
+ );
202
+ if ( ! empty( $args['lightbox'] ) ) {
203
+ $image_atts['lightbox'] = '';
204
+ $image_atts['on'] = 'tap:' . AMP_Img_Sanitizer::AMP_IMAGE_LIGHTBOX_ID;
205
+ $image_atts['role'] = 'button';
206
+ $image_atts['tabindex'] = 0;
207
+ }
208
  $image = AMP_HTML_Utils::build_tag(
209
  'amp-img',
210
+ $image_atts
 
 
 
 
 
211
  );
212
 
213
  if ( ! empty( $props['href'] ) ) {
234
  implode( PHP_EOL, $images )
235
  );
236
  }
237
+
238
+ /**
239
+ * Prints the Gallery block styling.
240
+ *
241
+ * It would be better to print this in AMP_Gallery_Block_Sanitizer,
242
+ * but by the time that runs, it's too late.
243
+ * This rule is copied exactly from block-library/style.css, but the selector here has amp-img >.
244
+ * The image sanitizer normally converts the <img> from that original stylesheet <amp-img>,
245
+ * but that doesn't have the same effect as applying it to the <img>.
246
+ *
247
+ * @return void
248
+ */
249
+ public function print_styles() {
250
+ ?>
251
+ <style>
252
+ .wp-block-gallery.is-cropped .blocks-gallery-item amp-img > img {
253
+ object-fit: cover;
254
+ }
255
+ </style>
256
+ <?php
257
+ }
258
  }
includes/embeds/class-amp-gfycat-embed-handler.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Gfycat_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 1.0
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Gfycat_Embed_Handler
11
+ */
12
+ class AMP_Gfycat_Embed_Handler extends AMP_Base_Embed_Handler {
13
+ /**
14
+ * Regex matched to produce output amp-gfycat.
15
+ *
16
+ * @var string
17
+ */
18
+ const URL_PATTERN = '#https?://(www\.)?gfycat\.com/.*#i';
19
+
20
+ /**
21
+ * Register embed.
22
+ */
23
+ public function register_embed() {
24
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 3 );
25
+ }
26
+
27
+ /**
28
+ * Unregister embed.
29
+ */
30
+ public function unregister_embed() {
31
+ remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
32
+ }
33
+
34
+ /**
35
+ * Filter oEmbed HTML for Gfycat to prepare it for AMP.
36
+ *
37
+ * @param mixed $return The shortcode callback function to call.
38
+ * @param string $url The attempted embed URL.
39
+ * @param array $attr An array of shortcode attributes.
40
+ * @return string Embed.
41
+ */
42
+ public function filter_embed_oembed_html( $return, $url, $attr ) {
43
+ $parsed_url = wp_parse_url( $url );
44
+ if ( false !== strpos( $parsed_url['host'], 'gfycat.com' ) ) {
45
+ if ( preg_match( '/width=["\']?(\d+)/', $return, $matches ) ) {
46
+ $attr['width'] = $matches[1];
47
+ }
48
+ if ( preg_match( '/height=["\']?(\d+)/', $return, $matches ) ) {
49
+ $attr['height'] = $matches[1];
50
+ }
51
+
52
+ if ( empty( $attr['height'] ) ) {
53
+ return $return;
54
+ }
55
+
56
+ $attributes = wp_array_slice_assoc( $attr, array( 'width', 'height' ) );
57
+
58
+ if ( empty( $attr['width'] ) ) {
59
+ $attributes['layout'] = 'fixed-height';
60
+ $attributes['width'] = 'auto';
61
+ }
62
+
63
+ $pieces = explode( '/detail/', $parsed_url['path'] );
64
+ if ( ! isset( $pieces[1] ) ) {
65
+ if ( ! preg_match( '/\/([A-Za-z0-9]+)/', $parsed_url['path'], $matches ) ) {
66
+ return $return;
67
+ }
68
+ $attributes['data-gfyid'] = $matches[1];
69
+ } else {
70
+ $attributes['data-gfyid'] = $pieces[1];
71
+ }
72
+
73
+ $return = AMP_HTML_Utils::build_tag(
74
+ 'amp-gfycat',
75
+ $attributes
76
+ );
77
+ }
78
+ return $return;
79
+ }
80
+ }
81
+
includes/embeds/class-amp-hulu-embed-handler.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Hulu_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 1.0
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Hulu_Embed_Handler
11
+ */
12
+ class AMP_Hulu_Embed_Handler extends AMP_Base_Embed_Handler {
13
+ /**
14
+ * Regex matched to produce output amp-hulu.
15
+ *
16
+ * @var string
17
+ */
18
+ const URL_PATTERN = '#https?://(www\.)?hulu\.com/.*#i';
19
+
20
+ /**
21
+ * Default height.
22
+ *
23
+ * @var int
24
+ */
25
+ protected $DEFAULT_HEIGHT = 600;
26
+
27
+ /**
28
+ * Register embed.
29
+ */
30
+ public function register_embed() {
31
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 3 );
32
+ }
33
+
34
+ /**
35
+ * Unregister embed.
36
+ */
37
+ public function unregister_embed() {
38
+ remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
39
+ }
40
+
41
+ /**
42
+ * Filter oEmbed HTML for Hulu to prepare it for AMP.
43
+ *
44
+ * @param mixed $return The shortcode callback function to call.
45
+ * @param string $url The attempted embed URL.
46
+ * @param array $attr An array of shortcode attributes.
47
+ * @return string Embed.
48
+ */
49
+ public function filter_embed_oembed_html( $return, $url, $attr ) {
50
+ $parsed_url = wp_parse_url( $url );
51
+ if ( false !== strpos( $parsed_url['host'], 'hulu.com' ) ) {
52
+ if ( preg_match( '/width=["\']?(\d+)/', $return, $matches ) ) {
53
+ $attr['width'] = $matches[1];
54
+ }
55
+ if ( preg_match( '/height=["\']?(\d+)/', $return, $matches ) ) {
56
+ $attr['height'] = $matches[1];
57
+ }
58
+
59
+ if ( empty( $attr['height'] ) ) {
60
+ return $return;
61
+ }
62
+
63
+ $attributes = wp_array_slice_assoc( $attr, array( 'width', 'height' ) );
64
+
65
+ if ( empty( $attr['width'] ) ) {
66
+ $attributes['layout'] = 'fixed-height';
67
+ $attributes['width'] = 'auto';
68
+ }
69
+
70
+ $pieces = explode( '/watch/', $parsed_url['path'] );
71
+ if ( ! isset( $pieces[1] ) ) {
72
+ if ( ! preg_match( '/\/([A-Za-z0-9]+)/', $parsed_url['path'], $matches ) ) {
73
+ return $return;
74
+ }
75
+ $attributes['data-eid'] = $matches[1];
76
+ } else {
77
+ $attributes['data-eid'] = $pieces[1];
78
+ }
79
+
80
+ $return = AMP_HTML_Utils::build_tag(
81
+ 'amp-hulu',
82
+ $attributes
83
+ );
84
+ }
85
+ return $return;
86
+ }
87
+ }
includes/embeds/class-amp-imgur-embed-handler.php ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Imgur_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 1.0
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Imgur_Embed_Handler
11
+ */
12
+ class AMP_Imgur_Embed_Handler extends AMP_Base_Embed_Handler {
13
+ /**
14
+ * Regex matched to produce output amp-imgur.
15
+ *
16
+ * @var string
17
+ */
18
+ const URL_PATTERN = '#https?://(www\.)?imgur\.com/.*#i';
19
+
20
+ /**
21
+ * Register embed.
22
+ */
23
+ public function register_embed() {
24
+ if ( version_compare( strtok( get_bloginfo( 'version' ), '-' ), '4.9', '<' ) ) {
25
+
26
+ // Before 4.9 the embedding Imgur is not working properly, register embed for that case.
27
+ wp_embed_register_handler( 'amp-imgur', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
28
+ } else {
29
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 3 );
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Unregister embed.
35
+ */
36
+ public function unregister_embed() {
37
+ if ( version_compare( strtok( get_bloginfo( 'version' ), '-' ), '4.9', '<' ) ) {
38
+ wp_embed_unregister_handler( 'amp-imgur', -1 );
39
+ } else {
40
+ remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Oembed.
46
+ *
47
+ * @param array $matches Matches.
48
+ * @param array $attr Attributes.
49
+ * @param string $url URL.
50
+ * @param array $rawattr Raw attributes.
51
+ * @return string Embed.
52
+ */
53
+ public function oembed( $matches, $attr, $url, $rawattr ) {
54
+ return $this->render( array( 'url' => $url ) );
55
+ }
56
+
57
+ /**
58
+ * Render embed.
59
+ *
60
+ * @param array $args Args.
61
+ * @return string Embed.
62
+ */
63
+ public function render( $args ) {
64
+ $args = wp_parse_args( $args, array(
65
+ 'url' => false,
66
+ ) );
67
+
68
+ if ( empty( $args['url'] ) ) {
69
+ return '';
70
+ }
71
+
72
+ $this->did_convert_elements = true;
73
+
74
+ $id = $this->get_imgur_id_from_url( $args['url'] );
75
+ if ( false === $id ) {
76
+ return '';
77
+ }
78
+ return AMP_HTML_Utils::build_tag(
79
+ 'amp-imgur',
80
+ array(
81
+ 'width' => $this->args['width'],
82
+ 'height' => $this->args['height'],
83
+ 'data-imgur-id' => $id,
84
+ )
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Filter oEmbed HTML for Imgur to prepare it for AMP.
90
+ *
91
+ * @param mixed $return The shortcode callback function to call.
92
+ * @param string $url The attempted embed URL.
93
+ * @param array $attr An array of shortcode attributes.
94
+ * @return string Embed.
95
+ */
96
+ public function filter_embed_oembed_html( $return, $url, $attr ) {
97
+ $parsed_url = wp_parse_url( $url );
98
+ if ( false !== strpos( $parsed_url['host'], 'imgur.com' ) ) {
99
+ if ( preg_match( '/width=["\']?(\d+)/', $return, $matches ) ) {
100
+ $attr['width'] = $matches[1];
101
+ }
102
+ if ( preg_match( '/height=["\']?(\d+)/', $return, $matches ) ) {
103
+ $attr['height'] = $matches[1];
104
+ }
105
+
106
+ if ( empty( $attr['height'] ) ) {
107
+ return $return;
108
+ }
109
+
110
+ $attributes = wp_array_slice_assoc( $attr, array( 'width', 'height' ) );
111
+
112
+ if ( empty( $attr['width'] ) ) {
113
+ $attributes['layout'] = 'fixed-height';
114
+ $attributes['width'] = 'auto';
115
+ }
116
+
117
+ $attributes['data-imgur-id'] = $this->get_imgur_id_from_url( $url );
118
+ if ( false === $attributes['data-imgur-id'] ) {
119
+ return $return;
120
+ }
121
+
122
+ $return = AMP_HTML_Utils::build_tag(
123
+ 'amp-imgur',
124
+ $attributes
125
+ );
126
+ }
127
+ return $return;
128
+ }
129
+
130
+ /**
131
+ * Get Imgur ID from URL.
132
+ *
133
+ * @param string $url URL.
134
+ * @return bool|string ID / false.
135
+ */
136
+ protected function get_imgur_id_from_url( $url ) {
137
+ $parsed_url = wp_parse_url( $url );
138
+ $pieces = explode( '/gallery/', $parsed_url['path'] );
139
+ if ( ! isset( $pieces[1] ) ) {
140
+ if ( ! preg_match( '/\/([A-Za-z0-9]+)/', $parsed_url['path'], $matches ) ) {
141
+ return false;
142
+ }
143
+ return $matches[1];
144
+ } else {
145
+ return $pieces[1];
146
+ }
147
+ }
148
+ }
includes/embeds/class-amp-instagram-embed.php CHANGED
@@ -17,13 +17,27 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
17
  protected $DEFAULT_WIDTH = 600;
18
  protected $DEFAULT_HEIGHT = 600;
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  public function register_embed() {
21
- wp_embed_register_handler( 'amp-instagram', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
22
  add_shortcode( 'instagram', array( $this, 'shortcode' ) );
23
  }
24
 
25
  public function unregister_embed() {
26
- wp_embed_unregister_handler( 'amp-instagram', -1 );
27
  remove_shortcode( 'instagram' );
28
  }
29
 
@@ -53,7 +67,7 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
53
 
54
  public function render( $args ) {
55
  $args = wp_parse_args( $args, array(
56
- 'url' => false,
57
  'instagram_id' => false,
58
  ) );
59
 
@@ -64,16 +78,23 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
64
  $this->did_convert_elements = true;
65
 
66
  return AMP_HTML_Utils::build_tag(
67
- 'amp-instagram',
68
  array(
69
  'data-shortcode' => $args['instagram_id'],
70
- 'layout' => 'responsive',
71
- 'width' => $this->args['width'],
72
- 'height' => $this->args['height'],
 
73
  )
74
  );
75
  }
76
 
 
 
 
 
 
 
77
  private function get_instagram_id_from_url( $url ) {
78
  $found = preg_match( self::URL_PATTERN, $url, $matches );
79
 
@@ -83,4 +104,98 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
83
 
84
  return end( $matches );
85
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
17
  protected $DEFAULT_WIDTH = 600;
18
  protected $DEFAULT_HEIGHT = 600;
19
 
20
+ /**
21
+ * Tag.
22
+ *
23
+ * @var string embed HTML blockquote tag to identify and replace with AMP version.
24
+ */
25
+ protected $sanitize_tag = 'blockquote';
26
+
27
+ /**
28
+ * Tag.
29
+ *
30
+ * @var string AMP amp-facebook tag
31
+ */
32
+ private $amp_tag = 'amp-instagram';
33
+
34
  public function register_embed() {
35
+ wp_embed_register_handler( $this->amp_tag, self::URL_PATTERN, array( $this, 'oembed' ), -1 );
36
  add_shortcode( 'instagram', array( $this, 'shortcode' ) );
37
  }
38
 
39
  public function unregister_embed() {
40
+ wp_embed_unregister_handler( $this->amp_tag, -1 );
41
  remove_shortcode( 'instagram' );
42
  }
43
 
67
 
68
  public function render( $args ) {
69
  $args = wp_parse_args( $args, array(
70
+ 'url' => false,
71
  'instagram_id' => false,
72
  ) );
73
 
78
  $this->did_convert_elements = true;
79
 
80
  return AMP_HTML_Utils::build_tag(
81
+ $this->amp_tag,
82
  array(
83
  'data-shortcode' => $args['instagram_id'],
84
+ 'data-captioned' => '',
85
+ 'layout' => 'responsive',
86
+ 'width' => $this->args['width'],
87
+ 'height' => $this->args['height'],
88
  )
89
  );
90
  }
91
 
92
+ /**
93
+ * Get Instagram ID from URL.
94
+ *
95
+ * @param string $url URL.
96
+ * @return string|false The ID parsed from the URL or false if not found.
97
+ */
98
  private function get_instagram_id_from_url( $url ) {
99
  $found = preg_match( self::URL_PATTERN, $url, $matches );
100
 
104
 
105
  return end( $matches );
106
  }
107
+
108
+ /**
109
+ * Sanitized <blockquote class="instagram-media"> tags to <amp-instagram>
110
+ *
111
+ * @param DOMDocument $dom DOM.
112
+ */
113
+ public function sanitize_raw_embeds( $dom ) {
114
+ /**
115
+ * Node list.
116
+ *
117
+ * @var DOMNodeList $node
118
+ */
119
+ $nodes = $dom->getElementsByTagName( $this->sanitize_tag );
120
+ $num_nodes = $nodes->length;
121
+
122
+ if ( 0 === $num_nodes ) {
123
+ return;
124
+ }
125
+
126
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
127
+ $node = $nodes->item( $i );
128
+ if ( ! $node instanceof DOMElement ) {
129
+ continue;
130
+ }
131
+
132
+ if ( $node->hasAttribute( 'data-instgrm-permalink' ) ) {
133
+ $this->create_amp_instagram_and_replace_node( $dom, $node );
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Make final modifications to DOMNode
140
+ *
141
+ * @param DOMDocument $dom The HTML Document.
142
+ * @param DOMElement $node The DOMNode to adjust and replace.
143
+ */
144
+ private function create_amp_instagram_and_replace_node( $dom, $node ) {
145
+ $instagram_id = $this->get_instagram_id_from_url( $node->getAttribute( 'data-instgrm-permalink' ) );
146
+
147
+ $node_args = array(
148
+ 'data-shortcode' => $instagram_id,
149
+ 'layout' => 'responsive',
150
+ 'width' => $this->DEFAULT_WIDTH,
151
+ 'height' => $this->DEFAULT_HEIGHT,
152
+ );
153
+
154
+ if ( true === $node->hasAttribute( 'data-instgrm-captioned' ) ) {
155
+ $node_args['data-captioned'] = '';
156
+ }
157
+
158
+ $new_node = AMP_DOM_Utils::create_node( $dom, $this->amp_tag, $node_args );
159
+
160
+ $this->sanitize_embed_script( $node );
161
+
162
+ $node->parentNode->replaceChild( $new_node, $node );
163
+
164
+ $this->did_convert_elements = true;
165
+ }
166
+
167
+ /**
168
+ * Removes Instagram's embed <script> tag.
169
+ *
170
+ * @param DOMElement $node The DOMNode to whose sibling is the instagram script.
171
+ */
172
+ private function sanitize_embed_script( $node ) {
173
+ $next_element_sibling = $node->nextSibling;
174
+ while ( $next_element_sibling && ! ( $next_element_sibling instanceof DOMElement ) ) {
175
+ $next_element_sibling = $next_element_sibling->nextSibling;
176
+ }
177
+
178
+ $script_src = 'instagram.com/embed.js';
179
+
180
+ // Handle case where script is wrapped in paragraph by wpautop.
181
+ if ( $next_element_sibling instanceof DOMElement && 'p' === $next_element_sibling->nodeName ) {
182
+ $children = $next_element_sibling->getElementsByTagName( '*' );
183
+ if ( 1 === $children->length && 'script' === $children->item( 0 )->nodeName && false !== strpos( $children->item( 0 )->getAttribute( 'src' ), $script_src ) ) {
184
+ $next_element_sibling->parentNode->removeChild( $next_element_sibling );
185
+ return;
186
+ }
187
+ }
188
+
189
+ // Handle case where script is immediately following.
190
+ $is_embed_script = (
191
+ $next_element_sibling
192
+ &&
193
+ 'script' === strtolower( $next_element_sibling->nodeName )
194
+ &&
195
+ false !== strpos( $next_element_sibling->getAttribute( 'src' ), $script_src )
196
+ );
197
+ if ( $is_embed_script ) {
198
+ $next_element_sibling->parentNode->removeChild( $next_element_sibling );
199
+ }
200
+ }
201
  }
includes/embeds/class-amp-soundcloud-embed.php CHANGED
@@ -178,7 +178,7 @@ class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
178
  * @return string Track ID or empty string if none matched.
179
  */
180
  private function get_track_id_from_url( $url ) {
181
- $parsed_url = AMP_WP_Utils::parse_url( $url );
182
  if ( ! preg_match( '#tracks/(?P<track_id>[^/]+)#', $parsed_url['path'], $matches ) ) {
183
  return '';
184
  }
178
  * @return string Track ID or empty string if none matched.
179
  */
180
  private function get_track_id_from_url( $url ) {
181
+ $parsed_url = wp_parse_url( $url );
182
  if ( ! preg_match( '#tracks/(?P<track_id>[^/]+)#', $parsed_url['path'], $matches ) ) {
183
  return '';
184
  }
includes/embeds/class-amp-twitter-embed.php CHANGED
@@ -11,19 +11,64 @@
11
  * Much of this class is borrowed from Jetpack embeds
12
  */
13
  class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
14
- const URL_PATTERN = '#http(s|):\/\/twitter\.com(\/\#\!\/|\/)([a-zA-Z0-9_]{1,20})\/status(es)*\/(\d+)#i';
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  public function register_embed() {
17
- add_shortcode( 'tweet', array( $this, 'shortcode' ) );
18
  wp_embed_register_handler( 'amp-twitter', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
 
19
  }
20
 
 
 
 
21
  public function unregister_embed() {
22
- remove_shortcode( 'tweet' );
23
  wp_embed_unregister_handler( 'amp-twitter', -1 );
 
24
  }
25
 
26
- function shortcode( $attr ) {
 
 
 
 
 
 
 
 
27
  $attr = wp_parse_args( $attr, array(
28
  'tweet' => false,
29
  ) );
@@ -37,8 +82,8 @@ class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
37
  $id = $attr['tweet'];
38
  } else {
39
  preg_match( self::URL_PATTERN, $attr['tweet'], $matches );
40
- if ( isset( $matches[5] ) && is_numeric( $matches[5] ) ) {
41
- $id = $matches[5];
42
  }
43
 
44
  if ( empty( $id ) ) {
@@ -49,21 +94,29 @@ class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
49
  $this->did_convert_elements = true;
50
 
51
  return AMP_HTML_Utils::build_tag(
52
- 'amp-twitter',
53
  array(
54
  'data-tweetid' => $id,
55
- 'layout' => 'responsive',
56
- 'width' => $this->args['width'],
57
- 'height' => $this->args['height'],
58
  )
59
  );
60
  }
61
 
62
- function oembed( $matches, $attr, $url, $rawattr ) {
 
 
 
 
 
 
 
 
63
  $id = false;
64
 
65
- if ( isset( $matches[5] ) && is_numeric( $matches[5] ) ) {
66
- $id = $matches[5];
67
  }
68
 
69
  if ( ! $id ) {
@@ -72,4 +125,182 @@ class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
72
 
73
  return $this->shortcode( array( 'tweet' => $id ) );
74
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
11
  * Much of this class is borrowed from Jetpack embeds
12
  */
13
  class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
 
14
 
15
+ /**
16
+ * URL pattern for a Tweet URL.
17
+ *
18
+ * @since 0.2
19
+ * @var string
20
+ */
21
+ const URL_PATTERN = '#https?:\/\/twitter\.com(?:\/\#\!\/|\/)(?P<username>[a-zA-Z0-9_]{1,20})\/status(?:es)?\/(?P<tweet>\d+)#i';
22
+
23
+ /**
24
+ * URL pattern for a Twitter timeline.
25
+ *
26
+ * @since 1.0
27
+ * @var string
28
+ */
29
+ const URL_PATTERN_TIMELINE = '#https?:\/\/twitter\.com(?:\/\#\!\/|\/)(?P<username>[a-zA-Z0-9_]{1,20})(?:$|\/(?P<type>likes|lists)(\/(?P<id>[a-zA-Z0-9_-]+))?)#i';
30
+
31
+ /**
32
+ * Tag.
33
+ *
34
+ * @var string embed HTML blockquote tag to identify and replace with AMP version.
35
+ */
36
+ protected $sanitize_tag = 'blockquote';
37
+
38
+ /**
39
+ * Tag.
40
+ *
41
+ * @var string AMP amp-facebook tag
42
+ */
43
+ private $amp_tag = 'amp-twitter';
44
+
45
+ /**
46
+ * Register embed.
47
+ */
48
  public function register_embed() {
49
+ add_shortcode( 'tweet', array( $this, 'shortcode' ) ); // Note: This is a Jetpack shortcode.
50
  wp_embed_register_handler( 'amp-twitter', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
51
+ wp_embed_register_handler( 'amp-twitter-timeline', self::URL_PATTERN_TIMELINE, array( $this, 'oembed_timeline' ), -1 );
52
  }
53
 
54
+ /**
55
+ * Unregister embed.
56
+ */
57
  public function unregister_embed() {
58
+ remove_shortcode( 'tweet' ); // Note: This is a Jetpack shortcode.
59
  wp_embed_unregister_handler( 'amp-twitter', -1 );
60
+ wp_embed_unregister_handler( 'amp-twitter-timeline', -1 );
61
  }
62
 
63
+ /**
64
+ * Gets AMP-compliant markup for the Twitter shortcode.
65
+ *
66
+ * Note that this shortcode is is defined in Jetpack.
67
+ *
68
+ * @param array $attr The Twitter attributes.
69
+ * @return string Twitter shortcode markup.
70
+ */
71
+ public function shortcode( $attr ) {
72
  $attr = wp_parse_args( $attr, array(
73
  'tweet' => false,
74
  ) );
82
  $id = $attr['tweet'];
83
  } else {
84
  preg_match( self::URL_PATTERN, $attr['tweet'], $matches );
85
+ if ( isset( $matches['tweet'] ) && is_numeric( $matches['tweet'] ) ) {
86
+ $id = $matches['tweet'];
87
  }
88
 
89
  if ( empty( $id ) ) {
94
  $this->did_convert_elements = true;
95
 
96
  return AMP_HTML_Utils::build_tag(
97
+ $this->amp_tag,
98
  array(
99
  'data-tweetid' => $id,
100
+ 'layout' => 'responsive',
101
+ 'width' => $this->args['width'],
102
+ 'height' => $this->args['height'],
103
  )
104
  );
105
  }
106
 
107
+ /**
108
+ * Render oEmbed.
109
+ *
110
+ * @see \WP_Embed::shortcode()
111
+ *
112
+ * @param array $matches URL pattern matches.
113
+ * @return string Rendered oEmbed.
114
+ */
115
+ public function oembed( $matches ) {
116
  $id = false;
117
 
118
+ if ( isset( $matches['tweet'] ) && is_numeric( $matches['tweet'] ) ) {
119
+ $id = $matches['tweet'];
120
  }
121
 
122
  if ( ! $id ) {
125
 
126
  return $this->shortcode( array( 'tweet' => $id ) );
127
  }
128
+
129
+ /**
130
+ * Render oEmbed for a timeline.
131
+ *
132
+ * @since 1.0
133
+ *
134
+ * @param array $matches URL pattern matches.
135
+ * @return string Rendered oEmbed.
136
+ */
137
+ public function oembed_timeline( $matches ) {
138
+ if ( ! isset( $matches['username'] ) ) {
139
+ return '';
140
+ }
141
+
142
+ $attributes = array(
143
+ 'data-timeline-source-type' => 'profile',
144
+ 'data-timeline-screen-name' => $matches['username'],
145
+ );
146
+
147
+ if ( isset( $matches['type'] ) ) {
148
+ switch ( $matches['type'] ) {
149
+ case 'likes':
150
+ $attributes['data-timeline-source-type'] = 'likes';
151
+ break;
152
+ case 'lists':
153
+ if ( ! isset( $matches['id'] ) ) {
154
+ return '';
155
+ }
156
+ $attributes['data-timeline-source-type'] = 'list';
157
+ $attributes['data-timeline-slug'] = $matches['id'];
158
+ $attributes['data-timeline-owner-screen-name'] = $attributes['data-timeline-screen-name'];
159
+ unset( $attributes['data-timeline-screen-name'] );
160
+ break;
161
+ default:
162
+ return '';
163
+ }
164
+ }
165
+
166
+ $attributes['layout'] = 'responsive';
167
+ $attributes['width'] = $this->args['width'];
168
+ $attributes['height'] = $this->args['height'];
169
+
170
+ $this->did_convert_elements = true;
171
+
172
+ return AMP_HTML_Utils::build_tag( $this->amp_tag, $attributes );
173
+ }
174
+
175
+ /**
176
+ * Sanitized <blockquote class="twitter-tweet"> tags to <amp-twitter>.
177
+ *
178
+ * @param DOMDocument $dom DOM.
179
+ */
180
+ public function sanitize_raw_embeds( $dom ) {
181
+ /**
182
+ * Node list.
183
+ *
184
+ * @var DOMNodeList $node
185
+ */
186
+ $nodes = $dom->getElementsByTagName( $this->sanitize_tag );
187
+ $num_nodes = $nodes->length;
188
+
189
+ if ( 0 === $num_nodes ) {
190
+ return;
191
+ }
192
+
193
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
194
+ $node = $nodes->item( $i );
195
+ if ( ! $node instanceof DOMElement ) {
196
+ continue;
197
+ }
198
+
199
+ if ( $this->is_tweet_raw_embed( $node ) ) {
200
+ $this->create_amp_twitter_and_replace_node( $dom, $node );
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Checks whether it's a twitter blockquote or not
207
+ *
208
+ * @param DOMElement $node The DOMNode to adjust and replace.
209
+ * @return bool Whether node is for raw embed.
210
+ */
211
+ private function is_tweet_raw_embed( $node ) {
212
+ $class_attr = $node->getAttribute( 'class' );
213
+
214
+ return null !== $class_attr && false !== strpos( $class_attr, 'twitter-tweet' );
215
+ }
216
+
217
+ /**
218
+ * Make final modifications to DOMNode
219
+ *
220
+ * @param DOMDocument $dom The HTML Document.
221
+ * @param DOMElement $node The DOMNode to adjust and replace.
222
+ */
223
+ private function create_amp_twitter_and_replace_node( $dom, $node ) {
224
+ $tweet_id = $this->get_tweet_id( $node );
225
+ if ( ! $tweet_id ) {
226
+ return;
227
+ }
228
+
229
+ $new_node = AMP_DOM_Utils::create_node( $dom, $this->amp_tag, array(
230
+ 'width' => $this->DEFAULT_WIDTH,
231
+ 'height' => $this->DEFAULT_HEIGHT,
232
+ 'layout' => 'responsive',
233
+ 'data-tweetid' => $tweet_id,
234
+ ) );
235
+
236
+ $this->sanitize_embed_script( $node );
237
+
238
+ $node->parentNode->replaceChild( $new_node, $node );
239
+
240
+ $this->did_convert_elements = true;
241
+ }
242
+
243
+ /**
244
+ * Extracts Tweet id.
245
+ *
246
+ * @param DOMElement $node The DOMNode to adjust and replace.
247
+ * @return string Tweet ID.
248
+ */
249
+ private function get_tweet_id( $node ) {
250
+ /**
251
+ * DOMNode
252
+ *
253
+ * @var DOMNodeList $anchors
254
+ */
255
+ $anchors = $node->getElementsByTagName( 'a' );
256
+
257
+ /**
258
+ * Anchor.
259
+ *
260
+ * @type DOMElement $anchor
261
+ */
262
+ foreach ( $anchors as $anchor ) {
263
+ $found = preg_match( self::URL_PATTERN, $anchor->getAttribute( 'href' ), $matches );
264
+ if ( $found ) {
265
+ return $matches['tweet'];
266
+ }
267
+ }
268
+
269
+ return null;
270
+ }
271
+
272
+ /**
273
+ * Removes Twitter's embed <script> tag.
274
+ *
275
+ * @param DOMElement $node The DOMNode to whose sibling is the instagram script.
276
+ */
277
+ private function sanitize_embed_script( $node ) {
278
+ $next_element_sibling = $node->nextSibling;
279
+ while ( $next_element_sibling && ! ( $next_element_sibling instanceof DOMElement ) ) {
280
+ $next_element_sibling = $next_element_sibling->nextSibling;
281
+ }
282
+
283
+ $script_src = 'platform.twitter.com/widgets.js';
284
+
285
+ // Handle case where script is wrapped in paragraph by wpautop.
286
+ if ( $next_element_sibling instanceof DOMElement && 'p' === $next_element_sibling->nodeName ) {
287
+ $children = $next_element_sibling->getElementsByTagName( '*' );
288
+ if ( 1 === $children->length && 'script' === $children->item( 0 )->nodeName && false !== strpos( $children->item( 0 )->getAttribute( 'src' ), $script_src ) ) {
289
+ $next_element_sibling->parentNode->removeChild( $next_element_sibling );
290
+ return;
291
+ }
292
+ }
293
+
294
+ // Handle case where script is immediately following.
295
+ $is_embed_script = (
296
+ $next_element_sibling
297
+ &&
298
+ 'script' === strtolower( $next_element_sibling->nodeName )
299
+ &&
300
+ false !== strpos( $next_element_sibling->getAttribute( 'src' ), $script_src )
301
+ );
302
+ if ( $is_embed_script ) {
303
+ $next_element_sibling->parentNode->removeChild( $next_element_sibling );
304
+ }
305
+ }
306
  }
includes/embeds/class-amp-vimeo-embed.php CHANGED
@@ -16,39 +16,67 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
16
 
17
  const RATIO = 0.5625;
18
 
 
 
 
 
 
19
  protected $DEFAULT_WIDTH = 600;
 
 
 
 
 
 
20
  protected $DEFAULT_HEIGHT = 338;
21
 
22
- function __construct( $args = array() ) {
 
 
 
 
 
23
  parent::__construct( $args );
24
 
25
  if ( isset( $this->args['content_max_width'] ) ) {
26
- $max_width = $this->args['content_max_width'];
27
- $this->args['width'] = $max_width;
28
  $this->args['height'] = round( $max_width * self::RATIO );
29
  }
30
  }
31
 
32
- function register_embed() {
 
 
 
33
  wp_embed_register_handler( 'amp-vimeo', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
34
  add_shortcode( 'vimeo', array( $this, 'shortcode' ) );
35
  add_filter( 'wp_video_shortcode_override', array( $this, 'video_override' ), 10, 2 );
36
  }
37
 
 
 
 
38
  public function unregister_embed() {
39
  wp_embed_unregister_handler( 'amp-vimeo', -1 );
40
  remove_shortcode( 'vimeo' );
41
  }
42
 
 
 
 
 
 
 
43
  public function shortcode( $attr ) {
44
  $video_id = false;
45
 
46
  if ( isset( $attr['id'] ) ) {
47
  $video_id = $attr['id'];
48
  } elseif ( isset( $attr['url'] ) ) {
49
- $video_id = $this->get_video_id_from_url($attr['url']);
50
- }elseif ( isset( $attr[0] ) ) {
51
- $video_id = $this->get_video_id_from_url($attr[0]);
52
  } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
53
  $video_id = shortcode_new_to_old_params( $attr );
54
  }
@@ -57,27 +85,55 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
57
  return '';
58
  }
59
 
60
- return $this->render( array(
61
- 'video_id' => $video_id,
62
- ) );
 
 
63
  }
64
 
 
 
 
 
 
 
 
 
 
 
 
65
  public function oembed( $matches, $attr, $url, $rawattr ) {
66
  $video_id = $this->get_video_id_from_url( $url );
67
 
68
- return $this->render( array(
69
- 'url' => $url,
70
- 'video_id' => $video_id,
71
- ) );
 
 
72
  }
73
 
 
 
 
 
 
 
74
  public function render( $args ) {
75
- $args = wp_parse_args( $args, array(
76
- 'video_id' => false,
77
- ) );
 
 
78
 
79
  if ( empty( $args['video_id'] ) ) {
80
- return AMP_HTML_Utils::build_tag( 'a', array( 'href' => esc_url( $args['url'] ), 'class' => 'amp-wp-embed-fallback' ), esc_html( $args['url'] ) );
 
 
 
 
 
81
  }
82
 
83
  $this->did_convert_elements = true;
@@ -86,24 +142,27 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
86
  'amp-vimeo',
87
  array(
88
  'data-videoid' => $args['video_id'],
89
- 'layout' => 'responsive',
90
- 'width' => $this->args['width'],
91
- 'height' => $this->args['height'],
92
  )
93
  );
94
  }
95
 
96
- // get_video_id_from_url()
97
- // Takes the last component of a Vimeo URL
98
- // and returns it as the associated video id
 
 
 
99
  private function get_video_id_from_url( $url ) {
100
- $parsed_url = parse_url( $url );
101
  parse_str( $parsed_url['path'], $path );
102
 
103
- $video_id = "";
104
  if ( $path ) {
105
- $tok = explode( '/', $parsed_url['path'] );
106
- $video_id = end($tok);
107
  }
108
 
109
  return $video_id;
16
 
17
  const RATIO = 0.5625;
18
 
19
+ /**
20
+ * Default width.
21
+ *
22
+ * @var int
23
+ */
24
  protected $DEFAULT_WIDTH = 600;
25
+
26
+ /**
27
+ * Default height.
28
+ *
29
+ * @var int
30
+ */
31
  protected $DEFAULT_HEIGHT = 338;
32
 
33
+ /**
34
+ * AMP_Vimeo_Embed_Handler constructor.
35
+ *
36
+ * @param array $args Height, width and maximum width for embed.
37
+ */
38
+ public function __construct( $args = array() ) {
39
  parent::__construct( $args );
40
 
41
  if ( isset( $this->args['content_max_width'] ) ) {
42
+ $max_width = $this->args['content_max_width'];
43
+ $this->args['width'] = $max_width;
44
  $this->args['height'] = round( $max_width * self::RATIO );
45
  }
46
  }
47
 
48
+ /**
49
+ * Register embed.
50
+ */
51
+ public function register_embed() {
52
  wp_embed_register_handler( 'amp-vimeo', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
53
  add_shortcode( 'vimeo', array( $this, 'shortcode' ) );
54
  add_filter( 'wp_video_shortcode_override', array( $this, 'video_override' ), 10, 2 );
55
  }
56
 
57
+ /**
58
+ * Unregister embed.
59
+ */
60
  public function unregister_embed() {
61
  wp_embed_unregister_handler( 'amp-vimeo', -1 );
62
  remove_shortcode( 'vimeo' );
63
  }
64
 
65
+ /**
66
+ * Gets AMP-compliant markup for the Vimeo shortcode.
67
+ *
68
+ * @param array $attr The Vimeo attributes.
69
+ * @return string Vimeo shortcode markup.
70
+ */
71
  public function shortcode( $attr ) {
72
  $video_id = false;
73
 
74
  if ( isset( $attr['id'] ) ) {
75
  $video_id = $attr['id'];
76
  } elseif ( isset( $attr['url'] ) ) {
77
+ $video_id = $this->get_video_id_from_url( $attr['url'] );
78
+ } elseif ( isset( $attr[0] ) ) {
79
+ $video_id = $this->get_video_id_from_url( $attr[0] );
80
  } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
81
  $video_id = shortcode_new_to_old_params( $attr );
82
  }
85
  return '';
86
  }
87
 
88
+ return $this->render(
89
+ array(
90
+ 'video_id' => $video_id,
91
+ )
92
+ );
93
  }
94
 
95
+ /**
96
+ * Render oEmbed.
97
+ *
98
+ * @see \WP_Embed::shortcode()
99
+ *
100
+ * @param array $matches URL pattern matches.
101
+ * @param array $attr Shortcode attribues.
102
+ * @param string $url URL.
103
+ * @param string $rawattr Unmodified shortcode attributes.
104
+ * @return string Rendered oEmbed.
105
+ */
106
  public function oembed( $matches, $attr, $url, $rawattr ) {
107
  $video_id = $this->get_video_id_from_url( $url );
108
 
109
+ return $this->render(
110
+ array(
111
+ 'url' => $url,
112
+ 'video_id' => $video_id,
113
+ )
114
+ );
115
  }
116
 
117
+ /**
118
+ * Render.
119
+ *
120
+ * @param array $args Args.
121
+ * @return string Rendered.
122
+ */
123
  public function render( $args ) {
124
+ $args = wp_parse_args(
125
+ $args, array(
126
+ 'video_id' => false,
127
+ )
128
+ );
129
 
130
  if ( empty( $args['video_id'] ) ) {
131
+ return AMP_HTML_Utils::build_tag(
132
+ 'a', array(
133
+ 'href' => esc_url( $args['url'] ),
134
+ 'class' => 'amp-wp-embed-fallback',
135
+ ), esc_html( $args['url'] )
136
+ );
137
  }
138
 
139
  $this->did_convert_elements = true;
142
  'amp-vimeo',
143
  array(
144
  'data-videoid' => $args['video_id'],
145
+ 'layout' => 'responsive',
146
+ 'width' => $this->args['width'],
147
+ 'height' => $this->args['height'],
148
  )
149
  );
150
  }
151
 
152
+ /**
153
+ * Determine the video ID from the URL.
154
+ *
155
+ * @param string $url URL.
156
+ * @return integer Video ID.
157
+ */
158
  private function get_video_id_from_url( $url ) {
159
+ $parsed_url = wp_parse_url( $url );
160
  parse_str( $parsed_url['path'], $path );
161
 
162
+ $video_id = '';
163
  if ( $path ) {
164
+ $tok = explode( '/', $parsed_url['path'] );
165
+ $video_id = end( $tok );
166
  }
167
 
168
  return $video_id;
includes/embeds/class-amp-youtube-embed.php CHANGED
@@ -14,37 +14,65 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
14
  const SHORT_URL_HOST = 'youtu.be';
15
  // Only handling single videos. Playlists are handled elsewhere.
16
  const URL_PATTERN = '#https?://(?:www\.)?(?:youtube.com/(?:v/|e/|embed/|watch[/\#?])|youtu\.be/).*#i';
17
- const RATIO = 0.5625;
18
 
 
 
 
 
 
19
  protected $DEFAULT_WIDTH = 600;
 
 
 
 
 
 
20
  protected $DEFAULT_HEIGHT = 338;
21
 
22
- function __construct( $args = array() ) {
 
 
 
 
 
23
  parent::__construct( $args );
24
 
25
  if ( isset( $this->args['content_max_width'] ) ) {
26
- $max_width = $this->args['content_max_width'];
27
  $this->args['width'] = $max_width;
28
  $this->args['height'] = round( $max_width * self::RATIO );
29
  }
30
  }
31
 
32
- function register_embed() {
 
 
 
33
  wp_embed_register_handler( 'amp-youtube', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
34
  add_shortcode( 'youtube', array( $this, 'shortcode' ) );
35
  add_filter( 'wp_video_shortcode_override', array( $this, 'video_override' ), 10, 2 );
36
  }
37
 
 
 
 
38
  public function unregister_embed() {
39
  wp_embed_unregister_handler( 'amp-youtube', -1 );
40
  remove_shortcode( 'youtube' );
41
  }
42
 
 
 
 
 
 
 
43
  public function shortcode( $attr ) {
44
- $url = false;
45
  $video_id = false;
46
  if ( isset( $attr[0] ) ) {
47
- $url = ltrim( $attr[0] , '=' );
48
  } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
49
  $url = shortcode_new_to_old_params( $attr );
50
  }
@@ -55,23 +83,49 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
55
 
56
  $video_id = $this->get_video_id_from_url( $url );
57
 
58
- return $this->render( array(
59
- 'url' => $url,
60
- 'video_id' => $video_id,
61
- ) );
 
 
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
64
  public function oembed( $matches, $attr, $url, $rawattr ) {
65
  return $this->shortcode( array( $url ) );
66
  }
67
 
 
 
 
 
 
 
68
  public function render( $args ) {
69
- $args = wp_parse_args( $args, array(
70
- 'video_id' => false,
71
- ) );
 
 
72
 
73
  if ( empty( $args['video_id'] ) ) {
74
- return AMP_HTML_Utils::build_tag( 'a', array( 'href' => esc_url( $args['url'] ), 'class' => 'amp-wp-embed-fallback' ), esc_html( $args['url'] ) );
 
 
 
 
 
75
  }
76
 
77
  $this->did_convert_elements = true;
@@ -80,25 +134,31 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
80
  'amp-youtube',
81
  array(
82
  'data-videoid' => $args['video_id'],
83
- 'layout' => 'responsive',
84
- 'width' => $this->args['width'],
85
- 'height' => $this->args['height'],
86
  )
87
  );
88
  }
89
 
 
 
 
 
 
 
90
  private function get_video_id_from_url( $url ) {
91
- $video_id = false;
92
- $parsed_url = AMP_WP_Utils::parse_url( $url );
93
 
94
  if ( self::SHORT_URL_HOST === substr( $parsed_url['host'], -strlen( self::SHORT_URL_HOST ) ) ) {
95
- // youtu.be/{id}
96
  $parts = explode( '/', $parsed_url['path'] );
97
  if ( ! empty( $parts ) ) {
98
  $video_id = $parts[1];
99
  }
100
  } else {
101
- // ?v={id} or ?list={id}
102
  parse_str( $parsed_url['query'], $query_args );
103
 
104
  if ( isset( $query_args['v'] ) ) {
@@ -107,7 +167,7 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
107
  }
108
 
109
  if ( empty( $video_id ) ) {
110
- // /(v|e|embed)/{id}
111
  $parts = explode( '/', $parsed_url['path'] );
112
 
113
  if ( in_array( $parts[1], array( 'v', 'e', 'embed' ), true ) ) {
@@ -118,8 +178,14 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
118
  return $video_id;
119
  }
120
 
 
 
 
 
 
 
121
  private function sanitize_v_arg( $value ) {
122
- // Deal with broken params like `?v=123?rel=0`
123
  if ( false !== strpos( $value, '?' ) ) {
124
  $value = strtok( $value, '?' );
125
  }
14
  const SHORT_URL_HOST = 'youtu.be';
15
  // Only handling single videos. Playlists are handled elsewhere.
16
  const URL_PATTERN = '#https?://(?:www\.)?(?:youtube.com/(?:v/|e/|embed/|watch[/\#?])|youtu\.be/).*#i';
17
+ const RATIO = 0.5625;
18
 
19
+ /**
20
+ * Default width.
21
+ *
22
+ * @var int
23
+ */
24
  protected $DEFAULT_WIDTH = 600;
25
+
26
+ /**
27
+ * Default height.
28
+ *
29
+ * @var int
30
+ */
31
  protected $DEFAULT_HEIGHT = 338;
32
 
33
+ /**
34
+ * AMP_YouTube_Embed_Handler constructor.
35
+ *
36
+ * @param array $args Height, width and maximum width for embed.
37
+ */
38
+ public function __construct( $args = array() ) {
39
  parent::__construct( $args );
40
 
41
  if ( isset( $this->args['content_max_width'] ) ) {
42
+ $max_width = $this->args['content_max_width'];
43
  $this->args['width'] = $max_width;
44
  $this->args['height'] = round( $max_width * self::RATIO );
45
  }
46
  }
47
 
48
+ /**
49
+ * Register embed.
50
+ */
51
+ public function register_embed() {
52
  wp_embed_register_handler( 'amp-youtube', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
53
  add_shortcode( 'youtube', array( $this, 'shortcode' ) );
54
  add_filter( 'wp_video_shortcode_override', array( $this, 'video_override' ), 10, 2 );
55
  }
56
 
57
+ /**
58
+ * Unregister embed.
59
+ */
60
  public function unregister_embed() {
61
  wp_embed_unregister_handler( 'amp-youtube', -1 );
62
  remove_shortcode( 'youtube' );
63
  }
64
 
65
+ /**
66
+ * Gets AMP-compliant markup for the YouTube shortcode.
67
+ *
68
+ * @param array $attr The YouTube attributes.
69
+ * @return string YouTube shortcode markup.
70
+ */
71
  public function shortcode( $attr ) {
72
+ $url = false;
73
  $video_id = false;
74
  if ( isset( $attr[0] ) ) {
75
+ $url = ltrim( $attr[0], '=' );
76
  } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
77
  $url = shortcode_new_to_old_params( $attr );
78
  }
83
 
84
  $video_id = $this->get_video_id_from_url( $url );
85
 
86
+ return $this->render(
87
+ array(
88
+ 'url' => $url,
89
+ 'video_id' => $video_id,
90
+ )
91
+ );
92
  }
93
 
94
+ /**
95
+ * Render oEmbed.
96
+ *
97
+ * @see \WP_Embed::shortcode()
98
+ *
99
+ * @param array $matches URL pattern matches.
100
+ * @param array $attr Shortcode attribues.
101
+ * @param string $url URL.
102
+ * @param string $rawattr Unmodified shortcode attributes.
103
+ * @return string Rendered oEmbed.
104
+ */
105
  public function oembed( $matches, $attr, $url, $rawattr ) {
106
  return $this->shortcode( array( $url ) );
107
  }
108
 
109
+ /**
110
+ * Render.
111
+ *
112
+ * @param array $args Args.
113
+ * @return string Rendered.
114
+ */
115
  public function render( $args ) {
116
+ $args = wp_parse_args(
117
+ $args, array(
118
+ 'video_id' => false,
119
+ )
120
+ );
121
 
122
  if ( empty( $args['video_id'] ) ) {
123
+ return AMP_HTML_Utils::build_tag(
124
+ 'a', array(
125
+ 'href' => esc_url( $args['url'] ),
126
+ 'class' => 'amp-wp-embed-fallback',
127
+ ), esc_html( $args['url'] )
128
+ );
129
  }
130
 
131
  $this->did_convert_elements = true;
134
  'amp-youtube',
135
  array(
136
  'data-videoid' => $args['video_id'],
137
+ 'layout' => 'responsive',
138
+ 'width' => $this->args['width'],
139
+ 'height' => $this->args['height'],
140
  )
141
  );
142
  }
143
 
144
+ /**
145
+ * Determine the video ID from the URL.
146
+ *
147
+ * @param string $url URL.
148
+ * @return integer Video ID.
149
+ */
150
  private function get_video_id_from_url( $url ) {
151
+ $video_id = false;
152
+ $parsed_url = wp_parse_url( $url );
153
 
154
  if ( self::SHORT_URL_HOST === substr( $parsed_url['host'], -strlen( self::SHORT_URL_HOST ) ) ) {
155
+ /* youtu.be/{id} */
156
  $parts = explode( '/', $parsed_url['path'] );
157
  if ( ! empty( $parts ) ) {
158
  $video_id = $parts[1];
159
  }
160
  } else {
161
+ /* The query looks like ?v={id} or ?list={id} */
162
  parse_str( $parsed_url['query'], $query_args );
163
 
164
  if ( isset( $query_args['v'] ) ) {
167
  }
168
 
169
  if ( empty( $video_id ) ) {
170
+ /* The path looks like /(v|e|embed)/{id} */
171
  $parts = explode( '/', $parsed_url['path'] );
172
 
173
  if ( in_array( $parts[1], array( 'v', 'e', 'embed' ), true ) ) {
178
  return $video_id;
179
  }
180
 
181
+ /**
182
+ * Sanitize the v= argument in the URL.
183
+ *
184
+ * @param string $value query parameters.
185
+ * @return string First set of query parameters.
186
+ */
187
  private function sanitize_v_arg( $value ) {
188
+ // Deal with broken params like `?v=123?rel=0`.
189
  if ( false !== strpos( $value, '?' ) ) {
190
  $value = strtok( $value, '?' );
191
  }
includes/options/class-amp-analytics-options-submenu.php CHANGED
@@ -16,8 +16,8 @@ class AMP_Analytics_Options_Submenu {
16
 
17
  public function __construct( $parent_menu_slug ) {
18
  $this->parent_menu_slug = $parent_menu_slug;
19
- $this->menu_slug = 'amp-analytics-options';
20
- $this->menu_page = new AMP_Analytics_Options_Submenu_Page();
21
  }
22
 
23
  public function init() {
16
 
17
  public function __construct( $parent_menu_slug ) {
18
  $this->parent_menu_slug = $parent_menu_slug;
19
+ $this->menu_slug = 'amp-analytics-options';
20
+ $this->menu_page = new AMP_Analytics_Options_Submenu_Page();
21
  }
22
 
23
  public function init() {
includes/options/class-amp-options-manager.php CHANGED
@@ -17,6 +17,24 @@ class AMP_Options_Manager {
17
  */
18
  const OPTION_NAME = 'amp-options';
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  /**
21
  * Register settings.
22
  */
@@ -31,6 +49,9 @@ class AMP_Options_Manager {
31
  );
32
 
33
  add_action( 'update_option_' . self::OPTION_NAME, array( __CLASS__, 'maybe_flush_rewrite_rules' ), 10, 2 );
 
 
 
34
  }
35
 
36
  /**
@@ -57,7 +78,12 @@ class AMP_Options_Manager {
57
  * @return array Options.
58
  */
59
  public static function get_options() {
60
- return get_option( self::OPTION_NAME, array() );
 
 
 
 
 
61
  }
62
 
63
  /**
@@ -85,19 +111,34 @@ class AMP_Options_Manager {
85
  * @return array Options.
86
  */
87
  public static function validate_options( $new_options ) {
88
- $defaults = array(
89
- 'supported_post_types' => array(),
90
- 'analytics' => array(),
91
- );
92
 
93
- $options = array_merge(
94
- $defaults,
95
- self::get_options()
 
 
 
 
 
 
96
  );
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  // Validate post type support.
 
99
  if ( isset( $new_options['supported_post_types'] ) ) {
100
- $options['supported_post_types'] = array();
101
  foreach ( $new_options['supported_post_types'] as $post_type ) {
102
  if ( ! post_type_exists( $post_type ) ) {
103
  add_settings_error( self::OPTION_NAME, 'unknown_post_type', __( 'Unrecognized post type.', 'amp' ) );
@@ -107,6 +148,22 @@ class AMP_Options_Manager {
107
  }
108
  }
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  // Validate analytics.
111
  if ( isset( $new_options['analytics'] ) ) {
112
  foreach ( $new_options['analytics'] as $id => $data ) {
@@ -124,7 +181,7 @@ class AMP_Options_Manager {
124
  continue;
125
  }
126
 
127
- $entry_vendor_type = sanitize_key( $data['type'] );
128
  $entry_config = trim( $data['config'] );
129
 
130
  if ( ! empty( $data['id'] ) && '__new__' !== $data['id'] ) {
@@ -152,10 +209,22 @@ class AMP_Options_Manager {
152
  }
153
  }
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  return $options;
156
  }
157
 
158
-
159
  /**
160
  * Check for errors with updating the supported post types.
161
  *
@@ -163,15 +232,20 @@ class AMP_Options_Manager {
163
  * @see add_settings_error()
164
  */
165
  public static function check_supported_post_type_update_errors() {
166
- $builtin_support = AMP_Post_Type_Support::get_builtin_supported_post_types();
 
 
 
 
 
167
  $supported_types = self::get_option( 'supported_post_types', array() );
168
  foreach ( AMP_Post_Type_Support::get_eligible_post_types() as $name ) {
169
  $post_type = get_post_type_object( $name );
170
- if ( empty( $post_type ) || in_array( $post_type->name, $builtin_support, true ) ) {
171
  continue;
172
  }
173
 
174
- $post_type_supported = post_type_supports( $post_type->name, amp_get_slug() );
175
  $is_support_elected = in_array( $post_type->name, $supported_types, true );
176
 
177
  $error = null;
@@ -259,4 +333,285 @@ class AMP_Options_Manager {
259
  _deprecated_function( __METHOD__, '0.6', __CLASS__ . '::update_option' );
260
  return self::update_option( 'analytics', wp_unslash( $data ) );
261
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  }
17
  */
18
  const OPTION_NAME = 'amp-options';
19
 
20
+ /**
21
+ * Default option values.
22
+ *
23
+ * @var array
24
+ */
25
+ protected static $defaults = array(
26
+ 'theme_support' => 'disabled',
27
+ 'supported_post_types' => array( 'post' ),
28
+ 'analytics' => array(),
29
+ 'auto_accept_sanitization' => true,
30
+ 'accept_tree_shaking' => true,
31
+ 'disable_admin_bar' => false,
32
+ 'all_templates_supported' => true,
33
+ 'supported_templates' => array( 'is_singular' ),
34
+ 'enable_response_caching' => true,
35
+ 'version' => AMP__VERSION,
36
+ );
37
+
38
  /**
39
  * Register settings.
40
  */
49
  );
50
 
51
  add_action( 'update_option_' . self::OPTION_NAME, array( __CLASS__, 'maybe_flush_rewrite_rules' ), 10, 2 );
52
+ add_action( 'admin_notices', array( __CLASS__, 'render_welcome_notice' ) );
53
+ add_action( 'admin_notices', array( __CLASS__, 'persistent_object_caching_notice' ) );
54
+ add_action( 'admin_notices', array( __CLASS__, 'render_cache_miss_notice' ) );
55
  }
56
 
57
  /**
78
  * @return array Options.
79
  */
80
  public static function get_options() {
81
+ $options = get_option( self::OPTION_NAME, array() );
82
+ if ( empty( $options ) ) {
83
+ $options = array();
84
+ }
85
+ self::$defaults['enable_response_caching'] = wp_using_ext_object_cache();
86
+ return array_merge( self::$defaults, $options );
87
  }
88
 
89
  /**
111
  * @return array Options.
112
  */
113
  public static function validate_options( $new_options ) {
114
+ $options = self::get_options();
 
 
 
115
 
116
+ if ( ! current_user_can( 'manage_options' ) ) {
117
+ return $options;
118
+ }
119
+
120
+ // Theme support.
121
+ $recognized_theme_supports = array(
122
+ 'disabled',
123
+ 'paired',
124
+ 'native',
125
  );
126
+ if ( isset( $new_options['theme_support'] ) && in_array( $new_options['theme_support'], $recognized_theme_supports, true ) ) {
127
+ $options['theme_support'] = $new_options['theme_support'];
128
+
129
+ // If this option was changed, display a notice with the new template mode.
130
+ if ( self::get_option( 'theme_support' ) !== $new_options['theme_support'] ) {
131
+ add_action( 'update_option_' . self::OPTION_NAME, array( __CLASS__, 'handle_updated_theme_support_option' ) );
132
+ }
133
+ }
134
+
135
+ $options['auto_accept_sanitization'] = ! empty( $new_options['auto_accept_sanitization'] );
136
+ $options['accept_tree_shaking'] = ! empty( $new_options['accept_tree_shaking'] );
137
+ $options['disable_admin_bar'] = ! empty( $new_options['disable_admin_bar'] );
138
 
139
  // Validate post type support.
140
+ $options['supported_post_types'] = array();
141
  if ( isset( $new_options['supported_post_types'] ) ) {
 
142
  foreach ( $new_options['supported_post_types'] as $post_type ) {
143
  if ( ! post_type_exists( $post_type ) ) {
144
  add_settings_error( self::OPTION_NAME, 'unknown_post_type', __( 'Unrecognized post type.', 'amp' ) );
148
  }
149
  }
150
 
151
+ $theme_support_args = AMP_Theme_Support::get_theme_support_args();
152
+
153
+ $is_template_support_required = ( isset( $theme_support_args['templates_supported'] ) && 'all' === $theme_support_args['templates_supported'] );
154
+ if ( ! $is_template_support_required && ! isset( $theme_support_args['available_callback'] ) ) {
155
+ $options['all_templates_supported'] = ! empty( $new_options['all_templates_supported'] );
156
+
157
+ // Validate supported templates.
158
+ $options['supported_templates'] = array();
159
+ if ( isset( $new_options['supported_templates'] ) ) {
160
+ $options['supported_templates'] = array_intersect(
161
+ $new_options['supported_templates'],
162
+ array_keys( AMP_Theme_Support::get_supportable_templates() )
163
+ );
164
+ }
165
+ }
166
+
167
  // Validate analytics.
168
  if ( isset( $new_options['analytics'] ) ) {
169
  foreach ( $new_options['analytics'] as $id => $data ) {
181
  continue;
182
  }
183
 
184
+ $entry_vendor_type = preg_replace( '/[^a-zA-Z0-9_\-]/', '', $data['type'] );
185
  $entry_config = trim( $data['config'] );
186
 
187
  if ( ! empty( $data['id'] ) && '__new__' !== $data['id'] ) {
209
  }
210
  }
211
 
212
+ // Store the current version with the options so we know the format.
213
+ $options['version'] = AMP__VERSION;
214
+
215
+ // Handle the caching option.
216
+ $options['enable_response_caching'] = (
217
+ wp_using_ext_object_cache()
218
+ &&
219
+ ! empty( $new_options['enable_response_caching'] )
220
+ );
221
+ if ( $options['enable_response_caching'] ) {
222
+ AMP_Theme_Support::reset_cache_miss_url_option();
223
+ }
224
+
225
  return $options;
226
  }
227
 
 
228
  /**
229
  * Check for errors with updating the supported post types.
230
  *
232
  * @see add_settings_error()
233
  */
234
  public static function check_supported_post_type_update_errors() {
235
+
236
+ // If all templates are supported then skip check since all post types are also supported. This option only applies with native/paired theme support.
237
+ if ( self::get_option( 'all_templates_supported', false ) && 'disabled' !== self::get_option( 'theme_support' ) ) {
238
+ return;
239
+ }
240
+
241
  $supported_types = self::get_option( 'supported_post_types', array() );
242
  foreach ( AMP_Post_Type_Support::get_eligible_post_types() as $name ) {
243
  $post_type = get_post_type_object( $name );
244
+ if ( empty( $post_type ) ) {
245
  continue;
246
  }
247
 
248
+ $post_type_supported = post_type_supports( $post_type->name, AMP_Post_Type_Support::SLUG );
249
  $is_support_elected = in_array( $post_type->name, $supported_types, true );
250
 
251
  $error = null;
333
  _deprecated_function( __METHOD__, '0.6', __CLASS__ . '::update_option' );
334
  return self::update_option( 'analytics', wp_unslash( $data ) );
335
  }
336
+
337
+ /**
338
+ * Renders the welcome notice on the 'AMP Settings' page.
339
+ *
340
+ * Uses the user meta values for the dismissed WP pointers.
341
+ * So once the user dismisses this notice, it will never appear again.
342
+ */
343
+ public static function render_welcome_notice() {
344
+ if ( 'toplevel_page_' . self::OPTION_NAME !== get_current_screen()->id ) {
345
+ return;
346
+ }
347
+
348
+ $notice_id = 'amp-welcome-notice-1';
349
+ $dismissed = get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true );
350
+ if ( in_array( $notice_id, explode( ',', strval( $dismissed ) ), true ) ) {
351
+ return;
352
+ }
353
+
354
+ ?>
355
+ <div class="amp-welcome-notice notice notice-info is-dismissible" id="<?php echo esc_attr( $notice_id ); ?>">
356
+ <div class="notice-dismiss"></div>
357
+ <div class="amp-welcome-icon-holder">
358
+ <img class="amp-welcome-icon" src="<?php echo esc_url( amp_get_asset_url( 'images/amp-welcome-icon.svg' ) ); ?>" alt="<?php esc_html_e( 'Illustration of WordPress running AMP plugin.', 'amp' ); ?>" />
359
+ </div>
360
+ <h1><?php esc_html_e( 'Welcome to AMP for WordPress', 'amp' ); ?></h1>
361
+ <h3><?php esc_html_e( 'Bring the speed and features of the open source AMP project to your site, complete with the tools to support content authoring and website development.', 'amp' ); ?></h3>
362
+ <h3><?php esc_html_e( 'From granular controls that help you create AMP content, to Core Gutenberg support, to a sanitizer that only shows visitors error-free pages, to a full error workflow for developers, this release enables rich, performant experiences for your WordPress site.', 'amp' ); ?></h3>
363
+ <a href="https://www.ampproject.org/docs/getting_started/" target="_blank" class="button button-primary"><?php esc_html_e( 'Learn More', 'amp' ); ?></a>
364
+ </div>
365
+
366
+ <script>
367
+ jQuery( function( $ ) {
368
+ // On dismissing the notice, make a POST request to store this notice with the dismissed WP pointers so it doesn't display again.
369
+ $( <?php echo wp_json_encode( "#$notice_id" ); ?> ).on( 'click', '.notice-dismiss', function() {
370
+ $.post( ajaxurl, {
371
+ pointer: <?php echo wp_json_encode( $notice_id ); ?>,
372
+ action: 'dismiss-wp-pointer'
373
+ } );
374
+ } );
375
+ } );
376
+ </script>
377
+ <style type="text/css">
378
+ .amp-welcome-notice {
379
+ padding: 38px;
380
+ }
381
+ .amp-welcome-icon-holder {
382
+ width: 200px;
383
+ height: 200px;
384
+ float: left;
385
+ margin: 0 38px 38px 0;
386
+ }
387
+ .amp-welcome-icon {
388
+ width: 100%;
389
+ height: 100%;
390
+ display: block;
391
+ }
392
+ .amp-welcome-notice h1 {
393
+ font-weight: bold;
394
+ }
395
+ .amp-welcome-notice h3 {
396
+ font-size: 16px;
397
+ font-weight: 500;
398
+ }
399
+
400
+ </style>
401
+ <?php
402
+ }
403
+
404
+ /**
405
+ * Outputs an admin notice if persistent object cache is not present.
406
+ *
407
+ * @return void
408
+ */
409
+ public static function persistent_object_caching_notice() {
410
+ if ( ! wp_using_ext_object_cache() && 'toplevel_page_' . self::OPTION_NAME === get_current_screen()->id ) {
411
+ printf(
412
+ '<div class="notice notice-warning"><p>%s <a href="%s">%s</a></p></div>',
413
+ esc_html__( 'The AMP plugin performs at its best when persistent object cache is enabled.', 'amp' ),
414
+ esc_url( 'https://codex.wordpress.org/Class_Reference/WP_Object_Cache#Persistent_Caching' ),
415
+ esc_html__( 'More details', 'amp' )
416
+ );
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Render the cache miss admin notice.
422
+ *
423
+ * @return void
424
+ */
425
+ public static function render_cache_miss_notice() {
426
+ if ( 'toplevel_page_' . self::OPTION_NAME !== get_current_screen()->id ) {
427
+ return;
428
+ }
429
+
430
+ if ( ! self::show_response_cache_disabled_notice() ) {
431
+ return;
432
+ }
433
+
434
+ printf(
435
+ '<div class="notice notice-warning is-dismissible"><p>%s <a href="%s">%s</a></p></div>',
436
+ esc_html__( "The AMP plugin's post-processor cache disabled due to the detection of highly-variable content.", 'amp' ),
437
+ esc_url( 'https://github.com/ampproject/amp-wp/wiki/Post-Processor-Cache' ),
438
+ esc_html__( 'More details', 'amp' )
439
+ );
440
+ }
441
+
442
+ /**
443
+ * Show the response cache disabled notice.
444
+ *
445
+ * @since 1.0
446
+ *
447
+ * @return bool
448
+ */
449
+ public static function show_response_cache_disabled_notice() {
450
+ return (
451
+ wp_using_ext_object_cache()
452
+ &&
453
+ ! self::get_option( 'enable_response_caching' )
454
+ &&
455
+ AMP_Theme_Support::exceeded_cache_miss_threshold()
456
+ );
457
+ }
458
+
459
+ /**
460
+ * Adds a message for an update of the theme support setting.
461
+ */
462
+ public static function handle_updated_theme_support_option() {
463
+ $template_mode = self::get_option( 'theme_support' );
464
+
465
+ // Make sure post type support has been added for sake of amp_admin_get_preview_permalink().
466
+ foreach ( AMP_Post_Type_Support::get_eligible_post_types() as $post_type ) {
467
+ remove_post_type_support( $post_type, AMP_Post_Type_Support::SLUG );
468
+ }
469
+ AMP_Post_Type_Support::add_post_type_support();
470
+
471
+ // Ensure theme support flags are set properly according to the new mode so that proper AMP URL can be generated.
472
+ $has_theme_support = ( 'native' === $template_mode || 'paired' === $template_mode );
473
+ if ( $has_theme_support ) {
474
+ $theme_support = current_theme_supports( AMP_Theme_Support::SLUG );
475
+ if ( ! is_array( $theme_support ) ) {
476
+ $theme_support = array();
477
+ }
478
+ $theme_support['paired'] = 'paired' === $template_mode;
479
+ add_theme_support( AMP_Theme_Support::SLUG, $theme_support );
480
+ } else {
481
+ remove_theme_support( AMP_Theme_Support::SLUG ); // So that the amp_get_permalink() will work for classic URL.
482
+ }
483
+
484
+ $url = amp_admin_get_preview_permalink();
485
+
486
+ $notice_type = 'updated';
487
+ $review_messages = array();
488
+ if ( $url && $has_theme_support ) {
489
+ $validation = AMP_Validation_Manager::validate_url( $url );
490
+
491
+ if ( is_wp_error( $validation ) ) {
492
+ $review_messages[] = esc_html( sprintf(
493
+ /* translators: %1$s is the error message, %2$s is the error code */
494
+ __( 'However, there was an error when checking the AMP validity for your site.', 'amp' ),
495
+ $validation->get_error_message(),
496
+ $validation->get_error_code()
497
+ ) );
498
+
499
+ $error_message = $validation->get_error_message();
500
+ if ( $error_message ) {
501
+ $review_messages[] = $error_message;
502
+ } else {
503
+ /* translators: %s is the error code */
504
+ $review_messages[] = esc_html( sprintf( __( 'Error code: %s.', 'amp' ), $validation->get_error_code() ) );
505
+ }
506
+ $notice_type = 'error';
507
+ } elseif ( is_array( $validation ) ) {
508
+ $new_errors = 0;
509
+ $rejected_errors = 0;
510
+
511
+ $errors = wp_list_pluck( $validation['results'], 'error' );
512
+ foreach ( $errors as $error ) {
513
+ $sanitization = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( $error );
514
+ $is_new_rejected = AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_REJECTED_STATUS === $sanitization['status'];
515
+ if ( $is_new_rejected || AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_ACCEPTED_STATUS === $sanitization['status'] ) {
516
+ $new_errors++;
517
+ }
518
+ if ( $is_new_rejected || AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACK_REJECTED_STATUS === $sanitization['status'] ) {
519
+ $rejected_errors++;
520
+ }
521
+ }
522
+
523
+ $invalid_url_post_id = AMP_Validated_URL_Post_Type::store_validation_errors( $errors, $url );
524
+ $invalid_url_screen_url = ! is_wp_error( $invalid_url_post_id ) ? get_edit_post_link( $invalid_url_post_id, 'raw' ) : null;
525
+
526
+ if ( $rejected_errors > 0 ) {
527
+ $notice_type = 'error';
528
+
529
+ $message = wp_kses_post(
530
+ sprintf(
531
+ /* translators: %s is count of rejected errors */
532
+ _n(
533
+ 'However, AMP is not yet available due to %s validation error (for one URL at least).',
534
+ 'However, AMP is not yet available due to %s validation errors (for one URL at least).',
535
+ number_format_i18n( $rejected_errors ),
536
+ 'amp'
537
+ ),
538
+ $rejected_errors,
539
+ esc_url( $invalid_url_screen_url )
540
+ )
541
+ );
542
+
543
+ if ( $invalid_url_screen_url ) {
544
+ $message .= ' ' . wp_kses_post(
545
+ sprintf(
546
+ /* translators: %s is URL to review issues */
547
+ _n(
548
+ '<a href="%s">Review Issue</a>.',
549
+ '<a href="%s">Review Issues</a>.',
550
+ $rejected_errors,
551
+ 'amp'
552
+ ),
553
+ esc_url( $invalid_url_screen_url )
554
+ )
555
+ );
556
+ }
557
+
558
+ $review_messages[] = $message;
559
+ } else {
560
+ $message = wp_kses_post(
561
+ sprintf(
562
+ /* translators: %s is an AMP URL */
563
+ __( 'View an <a href="%s">AMP version of your site</a>.', 'amp' ),
564
+ esc_url( $url )
565
+ )
566
+ );
567
+
568
+ if ( $new_errors > 0 && $invalid_url_screen_url ) {
569
+ $message .= ' ' . wp_kses_post(
570
+ sprintf(
571
+ /* translators: %1$s is URL to review issues, %2$s is count of new errors */
572
+ _n(
573
+ 'Please also <a href="%1$s">review %2$s issue</a> which may need to be fixed (for one URL at least).',
574
+ 'Please also <a href="%1$s">review %2$s issues</a> which may need to be fixed (for one URL at least).',
575
+ $new_errors,
576
+ 'amp'
577
+ ),
578
+ esc_url( $invalid_url_screen_url ),
579
+ number_format_i18n( $new_errors )
580
+ )
581
+ );
582
+ }
583
+
584
+ $review_messages[] = $message;
585
+ }
586
+ }
587
+ }
588
+
589
+ switch ( $template_mode ) {
590
+ case 'native':
591
+ $message = esc_html__( 'Native mode activated!', 'amp' );
592
+ if ( $review_messages ) {
593
+ $message .= ' ' . join( ' ', $review_messages );
594
+ }
595
+ break;
596
+ case 'paired':
597
+ $message = esc_html__( 'Paired mode activated!', 'amp' );
598
+ if ( $review_messages ) {
599
+ $message .= ' ' . join( ' ', $review_messages );
600
+ }
601
+ break;
602
+ case 'disabled':
603
+ $message = wp_kses_post(
604
+ sprintf(
605
+ /* translators: %s is an AMP URL */
606
+ __( 'Classic mode activated! View the <a href="%s">AMP version of a recent post</a>. It is recommended that you upgrade to Native or Paired mode.', 'amp' ),
607
+ esc_url( $url )
608
+ )
609
+ );
610
+ break;
611
+ }
612
+
613
+ if ( isset( $message ) ) {
614
+ add_settings_error( self::OPTION_NAME, 'template_mode_updated', $message, $notice_type );
615
+ }
616
+ }
617
  }
includes/options/class-amp-options-menu.php CHANGED
@@ -23,6 +23,28 @@ class AMP_Options_Menu {
23
  public function init() {
24
  add_action( 'admin_post_amp_analytics_options', 'AMP_Options_Manager::handle_analytics_submit' );
25
  add_action( 'admin_menu', array( $this, 'add_menu_items' ), 9 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
28
  /**
@@ -33,7 +55,7 @@ class AMP_Options_Menu {
33
  add_menu_page(
34
  __( 'AMP Options', 'amp' ),
35
  __( 'AMP', 'amp' ),
36
- 'manage_options',
37
  AMP_Options_Manager::OPTION_NAME,
38
  array( $this, 'render_screen' ),
39
  self::ICON_BASE64_SVG
@@ -43,24 +65,63 @@ class AMP_Options_Menu {
43
  AMP_Options_Manager::OPTION_NAME,
44
  __( 'AMP Settings', 'amp' ),
45
  __( 'General', 'amp' ),
46
- 'manage_options',
47
  AMP_Options_Manager::OPTION_NAME
48
  );
49
 
50
  add_settings_section(
51
- 'post_types',
52
  false,
53
  '__return_false',
54
  AMP_Options_Manager::OPTION_NAME
55
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  add_settings_field(
57
- 'supported_post_types',
58
- __( 'Post Type Support', 'amp' ),
59
- array( $this, 'render_post_types_support' ),
60
  AMP_Options_Manager::OPTION_NAME,
61
- 'post_types'
 
 
 
62
  );
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  $submenus = array(
65
  new AMP_Analytics_Options_Submenu( AMP_Options_Manager::OPTION_NAME ),
66
  );
@@ -71,50 +132,435 @@ class AMP_Options_Menu {
71
  }
72
  }
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  /**
75
  * Post types support section renderer.
76
  *
77
- * @since 0.6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  */
79
- public function render_post_types_support() {
80
- $builtin_support = AMP_Post_Type_Support::get_builtin_supported_post_types();
81
- $element_name = AMP_Options_Manager::OPTION_NAME . '[supported_post_types][]';
82
  ?>
83
- <fieldset>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  <?php foreach ( array_map( 'get_post_type_object', AMP_Post_Type_Support::get_eligible_post_types() ) as $post_type ) : ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  <?php
86
- $element_id = AMP_Options_Manager::OPTION_NAME . "-supported_post_types-{$post_type->name}";
87
- $is_builtin = amp_is_canonical() || in_array( $post_type->name, $builtin_support, true );
88
  ?>
89
- <?php if ( $is_builtin ) : ?>
90
- <input type="hidden" name="<?php echo esc_attr( $element_name ); ?>" value="<?php echo esc_attr( $post_type->name ); ?>">
91
- <?php endif; ?>
92
- <input
93
- type="checkbox"
94
- id="<?php echo esc_attr( $element_id ); ?>"
95
- name="<?php echo esc_attr( $element_name ); ?>"
96
- value="<?php echo esc_attr( $post_type->name ); ?>"
97
- <?php checked( true, amp_is_canonical() || post_type_supports( $post_type->name, amp_get_slug() ) ); ?>
98
- <?php disabled( $is_builtin ); ?>
99
- >
100
- <label for="<?php echo esc_attr( $element_id ); ?>">
101
- <?php echo esc_html( $post_type->label ); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  </label>
103
- <br>
104
- <?php endforeach; ?>
105
- <p class="description">
106
- <?php
107
- if ( ! amp_is_canonical() ) :
108
- esc_html_e( 'Enable/disable AMP post type(s) support', 'amp' );
109
- else :
110
- esc_html_e( 'Canonical AMP is enabled in your theme, so all post types will render.', 'amp' );
111
- endif;
112
- ?>
113
  </p>
 
114
  </fieldset>
115
  <?php
116
  }
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  /**
119
  * Display Settings.
120
  *
@@ -125,14 +571,19 @@ class AMP_Options_Menu {
125
  AMP_Options_Manager::check_supported_post_type_update_errors();
126
  }
127
  ?>
 
 
 
 
 
128
  <div class="wrap">
129
  <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
130
  <?php settings_errors(); ?>
131
- <form action="options.php" method="post">
132
  <?php
133
  settings_fields( AMP_Options_Manager::OPTION_NAME );
134
  do_settings_sections( AMP_Options_Manager::OPTION_NAME );
135
- if ( ! amp_is_canonical() ) {
136
  submit_button();
137
  }
138
  ?>
23
  public function init() {
24
  add_action( 'admin_post_amp_analytics_options', 'AMP_Options_Manager::handle_analytics_submit' );
25
  add_action( 'admin_menu', array( $this, 'add_menu_items' ), 9 );
26
+
27
+ $plugin_file = preg_replace( '#.+/(?=.+?/.+?)#', '', AMP__FILE__ );
28
+ add_filter( "plugin_action_links_{$plugin_file}", array( $this, 'add_plugin_action_links' ) );
29
+ }
30
+
31
+ /**
32
+ * Add plugin action links.
33
+ *
34
+ * @param array $links Links.
35
+ * @return array Modified links.
36
+ */
37
+ public function add_plugin_action_links( $links ) {
38
+ return array_merge(
39
+ array(
40
+ 'settings' => sprintf(
41
+ '<a href="%1$s">%2$s</a>',
42
+ esc_url( add_query_arg( 'page', AMP_Options_Manager::OPTION_NAME, admin_url( 'admin.php' ) ) ),
43
+ __( 'Settings', 'amp' )
44
+ ),
45
+ ),
46
+ $links
47
+ );
48
  }
49
 
50
  /**
55
  add_menu_page(
56
  __( 'AMP Options', 'amp' ),
57
  __( 'AMP', 'amp' ),
58
+ 'edit_posts',
59
  AMP_Options_Manager::OPTION_NAME,
60
  array( $this, 'render_screen' ),
61
  self::ICON_BASE64_SVG
65
  AMP_Options_Manager::OPTION_NAME,
66
  __( 'AMP Settings', 'amp' ),
67
  __( 'General', 'amp' ),
68
+ 'edit_posts',
69
  AMP_Options_Manager::OPTION_NAME
70
  );
71
 
72
  add_settings_section(
73
+ 'general',
74
  false,
75
  '__return_false',
76
  AMP_Options_Manager::OPTION_NAME
77
  );
78
+
79
+ add_settings_field(
80
+ 'theme_support',
81
+ __( 'Template Mode', 'amp' ),
82
+ array( $this, 'render_theme_support' ),
83
+ AMP_Options_Manager::OPTION_NAME,
84
+ 'general',
85
+ array(
86
+ 'class' => 'theme_support',
87
+ )
88
+ );
89
+
90
+ add_settings_field(
91
+ 'validation',
92
+ __( 'Validation Handling', 'amp' ),
93
+ array( $this, 'render_validation_handling' ),
94
+ AMP_Options_Manager::OPTION_NAME,
95
+ 'general',
96
+ array(
97
+ 'class' => 'amp-validation-field',
98
+ )
99
+ );
100
+
101
  add_settings_field(
102
+ 'supported_templates',
103
+ __( 'Supported Templates', 'amp' ),
104
+ array( $this, 'render_supported_templates' ),
105
  AMP_Options_Manager::OPTION_NAME,
106
+ 'general',
107
+ array(
108
+ 'class' => 'amp-template-support-field',
109
+ )
110
  );
111
 
112
+ if ( wp_using_ext_object_cache() ) {
113
+ add_settings_field(
114
+ 'caching',
115
+ __( 'Caching', 'amp' ),
116
+ array( $this, 'render_caching' ),
117
+ AMP_Options_Manager::OPTION_NAME,
118
+ 'general',
119
+ array(
120
+ 'class' => 'amp-caching-field',
121
+ )
122
+ );
123
+ }
124
+
125
  $submenus = array(
126
  new AMP_Analytics_Options_Submenu( AMP_Options_Manager::OPTION_NAME ),
127
  );
132
  }
133
  }
134
 
135
+ /**
136
+ * Render theme support.
137
+ *
138
+ * @since 1.0
139
+ */
140
+ public function render_theme_support() {
141
+ $theme_support = AMP_Options_Manager::get_option( 'theme_support' );
142
+ $native_description = __( 'Reuses active theme\'s templates to display AMP responses but does not use separate URLs for AMP. This means your site is <b>AMP-first</b> and your canonical URLs are AMP.', 'amp' );
143
+ $paired_description = __( 'Reuses active theme\'s templates to display AMP responses, but uses separate URLs for AMP. Each canonical URL may have a corresponding AMP URL, if the content is fully AMP valid.', 'amp' );
144
+
145
+ $builtin_support = in_array( get_template(), AMP_Core_Theme_Sanitizer::get_supported_themes(), true );
146
+ ?>
147
+ <?php if ( current_theme_supports( AMP_Theme_Support::SLUG ) && ! AMP_Theme_Support::is_support_added_via_option() ) : ?>
148
+ <div class="notice notice-info notice-alt inline">
149
+ <p><?php esc_html_e( 'Your active theme has built-in AMP support.', 'amp' ); ?></p>
150
+ </div>
151
+ <p>
152
+ <?php if ( amp_is_canonical() ) : ?>
153
+ <strong><?php esc_html_e( 'Native:', 'amp' ); ?></strong>
154
+ <?php echo wp_kses_post( $native_description ); ?>
155
+ <?php else : ?>
156
+ <strong><?php esc_html_e( 'Paired:', 'amp' ); ?></strong>
157
+ <?php echo wp_kses_post( $paired_description ); ?>
158
+ <?php endif; ?>
159
+ </p>
160
+ <?php else : ?>
161
+ <fieldset <?php disabled( ! current_user_can( 'manage_options' ) ); ?>>
162
+ <?php if ( $builtin_support ) : ?>
163
+ <div class="notice notice-success notice-alt inline">
164
+ <p><?php esc_html_e( 'Your active theme is known to work well in paired or native mode.', 'amp' ); ?></p>
165
+ </div>
166
+ <?php endif; ?>
167
+ <dl>
168
+ <dt>
169
+ <input type="radio" id="theme_support_native" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[theme_support]' ); ?>" value="native" <?php checked( $theme_support, 'native' ); ?>>
170
+ <label for="theme_support_native">
171
+ <strong><?php esc_html_e( 'Native', 'amp' ); ?></strong>
172
+ </label>
173
+ </dt>
174
+ <dd>
175
+ <?php echo wp_kses_post( $native_description ); ?>
176
+ </dd>
177
+ <dt>
178
+ <input type="radio" id="theme_support_paired" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[theme_support]' ); ?>" value="paired" <?php checked( $theme_support, 'paired' ); ?>>
179
+ <label for="theme_support_paired">
180
+ <strong><?php esc_html_e( 'Paired', 'amp' ); ?></strong>
181
+ </label>
182
+ </dt>
183
+ <dd>
184
+ <?php echo wp_kses_post( $paired_description ); ?>
185
+ </dd>
186
+ <dt>
187
+ <input type="radio" id="theme_support_disabled" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[theme_support]' ); ?>" value="disabled" <?php checked( $theme_support, 'disabled' ); ?>>
188
+ <label for="theme_support_disabled">
189
+ <strong><?php esc_html_e( 'Classic', 'amp' ); ?></strong>
190
+ </label>
191
+ </dt>
192
+ <dd>
193
+ <?php esc_html_e( 'Display AMP responses in classic (legacy) post templates in a basic design that does not match your theme\'s templates.', 'amp' ); ?>
194
+
195
+ <?php if ( ! current_theme_supports( AMP_Theme_Support::SLUG ) && wp_count_posts( AMP_Validated_URL_Post_Type::POST_TYPE_SLUG )->publish > 0 ) : ?>
196
+ <div class="notice notice-info inline notice-alt">
197
+ <p>
198
+ <?php
199
+ echo wp_kses_post(
200
+ sprintf(
201
+ /* translators: %1$s is link to invalid URLs and %2$s is link to validation errors */
202
+ __( 'View current site compatibility results for native and paired modes: %1$s and %2$s.', 'amp' ),
203
+ sprintf(
204
+ '<a href="%s">%s</a>',
205
+ esc_url( add_query_arg( 'post_type', AMP_Validated_URL_Post_Type::POST_TYPE_SLUG, admin_url( 'edit.php' ) ) ),
206
+ esc_html( get_post_type_object( AMP_Validated_URL_Post_Type::POST_TYPE_SLUG )->labels->name )
207
+ ),
208
+ sprintf(
209
+ '<a href="%s">%s</a>',
210
+ esc_url(
211
+ add_query_arg(
212
+ array(
213
+ 'taxonomy' => AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG,
214
+ 'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
215
+ ),
216
+ admin_url( 'edit-tags.php' )
217
+ )
218
+ ),
219
+ esc_html( get_taxonomy( AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG )->labels->name )
220
+ )
221
+ )
222
+ );
223
+ ?>
224
+ </p>
225
+ </div>
226
+ <?php endif; ?>
227
+ </dd>
228
+ </dl>
229
+ </fieldset>
230
+ <?php endif; ?>
231
+ <?php
232
+ }
233
+
234
  /**
235
  * Post types support section renderer.
236
  *
237
+ * @todo If dirty AMP is ever allowed (that is, post-processed documents which can be served with non-sanitized valdation errors), then automatically forcing sanitization in native should be able to be turned off.
238
+ *
239
+ * @since 1.0
240
+ */
241
+ public function render_validation_handling() {
242
+ ?>
243
+ <fieldset <?php disabled( ! current_user_can( 'manage_options' ) ); ?>>
244
+ <?php
245
+ $auto_sanitization = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( array(
246
+ 'code' => 'non_existent',
247
+ ) );
248
+ remove_filter( 'amp_validation_error_sanitized', array( 'AMP_Validation_Manager', 'filter_tree_shaking_validation_error_as_accepted' ) );
249
+ $tree_shaking_sanitization = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( array(
250
+ 'code' => AMP_Style_Sanitizer::TREE_SHAKING_ERROR_CODE,
251
+ ) );
252
+
253
+ $forced_sanitization = 'with_filter' === $auto_sanitization['forced'];
254
+ $forced_tree_shaking = $forced_sanitization || 'with_filter' === $tree_shaking_sanitization['forced'];
255
+ ?>
256
+
257
+ <?php if ( $forced_sanitization ) : ?>
258
+ <div class="notice notice-info notice-alt inline">
259
+ <p><?php esc_html_e( 'Your install is configured via a theme or plugin to automatically sanitize any AMP validation error that is encountered.', 'amp' ); ?></p>
260
+ </div>
261
+ <input type="hidden" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[auto_accept_sanitization]' ); ?>" value="<?php echo AMP_Options_Manager::get_option( 'auto_accept_sanitization' ) ? 'on' : ''; ?>">
262
+ <?php else : ?>
263
+ <div class="amp-auto-accept-sanitize-canonical notice notice-info notice-alt inline">
264
+ <p><?php esc_html_e( 'All new validation errors are automatically accepted when in native mode.', 'amp' ); ?></p>
265
+ </div>
266
+ <div class="amp-auto-accept-sanitize">
267
+ <p>
268
+ <label for="auto_accept_sanitization">
269
+ <input id="auto_accept_sanitization" type="checkbox" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[auto_accept_sanitization]' ); ?>" <?php checked( AMP_Options_Manager::get_option( 'auto_accept_sanitization' ) ); ?>>
270
+ <?php esc_html_e( 'Automatically accept sanitization for any newly encountered AMP validation errors.', 'amp' ); ?>
271
+ </label>
272
+ </p>
273
+ <p class="description">
274
+ <?php esc_html_e( 'This will ensure your responses are always valid AMP but some important content may get stripped out (e.g. scripts).', 'amp' ); ?>
275
+ <?php
276
+ echo wp_kses_post(
277
+ sprintf(
278
+ /* translators: %s is URL to validation errors screen */
279
+ __( 'Existing validation errors which you have already rejected will not be modified (you may want to consider <a href="%s">bulk-accepting them</a>).', 'amp' ),
280
+ esc_url(
281
+ add_query_arg(
282
+ array(
283
+ 'taxonomy' => AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG,
284
+ 'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
285
+ ),
286
+ admin_url( 'edit-tags.php' )
287
+ )
288
+ )
289
+ )
290
+ )
291
+ ?>
292
+ </p>
293
+ </div>
294
+ <?php endif; ?>
295
+
296
+ <?php if ( $forced_tree_shaking ) : ?>
297
+ <input type="hidden" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[accept_tree_shaking]' ); ?>" value="<?php echo AMP_Options_Manager::get_option( 'accept_tree_shaking' ) ? 'on' : ''; ?>">
298
+ <?php else : ?>
299
+ <div class="amp-tree-shaking">
300
+ <p>
301
+ <label for="accept_tree_shaking">
302
+ <input id="accept_tree_shaking" type="checkbox" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[accept_tree_shaking]' ); ?>" <?php checked( AMP_Options_Manager::get_option( 'accept_tree_shaking' ) ); ?>>
303
+ <?php esc_html_e( 'Automatically remove CSS rules that are not relevant to a given page (tree shaking).', 'amp' ); ?>
304
+ </label>
305
+ </p>
306
+ <p class="description">
307
+ <?php esc_html_e( 'AMP limits the total amount of CSS to no more than 50KB; if you have more, than it is a validation error. The need to tree shake the CSS is not done by default because in some situations (in particular for dynamic content) it can result in CSS rules being removed that are needed.', 'amp' ); ?>
308
+ </p>
309
+ </div>
310
+ <?php endif; ?>
311
+
312
+ <script>
313
+ (function( $ ) {
314
+ var getThemeSupportMode = function() {
315
+ var checkedInput = $( 'input[type=radio][name="amp-options[theme_support]"]:checked' );
316
+ if ( 0 === checkedInput.length ) {
317
+ return <?php echo wp_json_encode( amp_is_canonical() ? 'native' : 'paired' ); ?>;
318
+ }
319
+ return checkedInput.val();
320
+ };
321
+
322
+ var updateTreeShakingHiddenClass = function() {
323
+ var checkbox = $( '#auto_accept_sanitization' );
324
+ $( '.amp-tree-shaking' ).toggleClass( 'hidden', checkbox.prop( 'checked' ) && 'native' !== getThemeSupportMode() );
325
+ };
326
+
327
+ var updateHiddenClasses = function() {
328
+ var themeSupportMode = getThemeSupportMode();
329
+ $( '.amp-auto-accept-sanitize' ).toggleClass( 'hidden', 'native' === themeSupportMode );
330
+ $( '.amp-validation-field' ).toggleClass( 'hidden', 'disabled' === themeSupportMode );
331
+ $( '.amp-auto-accept-sanitize-canonical' ).toggleClass( 'hidden', 'native' !== themeSupportMode );
332
+ updateTreeShakingHiddenClass();
333
+ };
334
+
335
+ $( 'input[type=radio][name="amp-options[theme_support]"]' ).change( updateHiddenClasses );
336
+ $( '#auto_accept_sanitization' ).change( updateTreeShakingHiddenClass );
337
+
338
+ updateHiddenClasses();
339
+ })( jQuery );
340
+ </script>
341
+
342
+ <p>
343
+ <label for="disable_admin_bar">
344
+ <input id="disable_admin_bar" type="checkbox" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[disable_admin_bar]' ); ?>" <?php checked( AMP_Options_Manager::get_option( 'disable_admin_bar' ) ); ?>>
345
+ <?php esc_html_e( 'Disable admin bar on AMP pages.', 'amp' ); ?>
346
+ </label>
347
+ </p>
348
+ <p class="description">
349
+ <?php esc_html_e( 'An additional stylesheet is required to properly render the admin bar. If the additional stylesheet causes the total CSS to surpass 50KB then the admin bar should be disabled to prevent a validation error or an unstyled admin bar in AMP responses.', 'amp' ); ?>
350
+ </p>
351
+ </fieldset>
352
+ <?php
353
+ }
354
+
355
+ /**
356
+ * Supported templates section renderer.
357
+ *
358
+ * @since 1.0
359
  */
360
+ public function render_supported_templates() {
361
+ $theme_support_args = AMP_Theme_Support::get_theme_support_args();
 
362
  ?>
363
+
364
+ <?php if ( ! isset( $theme_support_args['available_callback'] ) ) : ?>
365
+ <fieldset id="all_templates_supported_fieldset" <?php disabled( ! current_user_can( 'manage_options' ) ); ?>>
366
+ <?php if ( isset( $theme_support_args['templates_supported'] ) && 'all' === $theme_support_args['templates_supported'] ) : ?>
367
+ <div class="notice notice-info notice-alt inline">
368
+ <p>
369
+ <?php esc_html_e( 'The current theme requires all templates to support AMP.', 'amp' ); ?>
370
+ </p>
371
+ </div>
372
+ <?php else : ?>
373
+ <p>
374
+ <label for="all_templates_supported">
375
+ <input id="all_templates_supported" type="checkbox" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[all_templates_supported]' ); ?>" <?php checked( AMP_Options_Manager::get_option( 'all_templates_supported' ) ); ?>>
376
+ <?php esc_html_e( 'Serve all templates as AMP regardless of what is being queried.', 'amp' ); ?>
377
+ </label>
378
+ </p>
379
+ <p class="description">
380
+ <?php esc_html_e( 'This will allow all of the URLs on your site to be served as AMP by default.', 'amp' ); ?>
381
+ </p>
382
+ <?php endif; ?>
383
+ </fieldset>
384
+ <?php else : ?>
385
+ <div class="notice notice-warning notice-alt inline">
386
+ <p>
387
+ <?php esc_html_e( 'Your theme is using the deprecated available_callback argument for AMP theme support.', 'amp' ); ?>
388
+ </p>
389
+ </div>
390
+ <?php endif; ?>
391
+
392
+ <fieldset id="supported_post_types_fieldset" <?php disabled( ! current_user_can( 'manage_options' ) ); ?>>
393
+ <?php $element_name = AMP_Options_Manager::OPTION_NAME . '[supported_post_types][]'; ?>
394
+ <h4 class="title"><?php esc_html_e( 'Content Types', 'amp' ); ?></h4>
395
+ <p>
396
+ <?php esc_html_e( 'The following content types will be available as AMP:', 'amp' ); ?>
397
+ </p>
398
+ <ul>
399
  <?php foreach ( array_map( 'get_post_type_object', AMP_Post_Type_Support::get_eligible_post_types() ) as $post_type ) : ?>
400
+ <li>
401
+ <?php $element_id = AMP_Options_Manager::OPTION_NAME . "-supported_post_types-{$post_type->name}"; ?>
402
+ <input
403
+ type="checkbox"
404
+ id="<?php echo esc_attr( $element_id ); ?>"
405
+ name="<?php echo esc_attr( $element_name ); ?>"
406
+ value="<?php echo esc_attr( $post_type->name ); ?>"
407
+ <?php checked( post_type_supports( $post_type->name, AMP_Post_Type_Support::SLUG ) ); ?>
408
+ >
409
+ <label for="<?php echo esc_attr( $element_id ); ?>">
410
+ <?php echo esc_html( $post_type->label ); ?>
411
+ </label>
412
+ </li>
413
+ <?php endforeach; ?>
414
+ </ul>
415
+ </fieldset>
416
+
417
+ <?php if ( ! isset( $theme_support_args['available_callback'] ) ) : ?>
418
+ <fieldset id="supported_templates_fieldset" <?php disabled( ! current_user_can( 'manage_options' ) ); ?>>
419
+ <style>
420
+ #supported_templates_fieldset ul ul {
421
+ margin-left: 40px;
422
+ }
423
+ </style>
424
+ <h4 class="title"><?php esc_html_e( 'Templates', 'amp' ); ?></h4>
425
  <?php
426
+ self::list_template_conditional_options( AMP_Theme_Support::get_supportable_templates() );
 
427
  ?>
428
+ <script>
429
+ // Let clicks on parent items automatically cause the children checkboxes to have same checked state applied.
430
+ (function ( $ ) {
431
+ $( '#supported_templates_fieldset input[type=checkbox]' ).on( 'click', function() {
432
+ $( this ).siblings( 'ul' ).find( 'input[type=checkbox]' ).prop( 'checked', this.checked );
433
+ } );
434
+ })( jQuery );
435
+ </script>
436
+ </fieldset>
437
+
438
+ <script>
439
+ // Update the visibility of the fieldsets based on the selected template mode and then whether all templates are indicated to be supported.
440
+ (function ( $ ) {
441
+ var templateModeInputs, themeSupportDisabledInput, allTemplatesSupportedInput, supportForced;
442
+ templateModeInputs = $( 'input[type=radio][name="amp-options[theme_support]"]' );
443
+ themeSupportDisabledInput = $( '#theme_support_disabled' );
444
+ allTemplatesSupportedInput = $( '#all_templates_supported' );
445
+ supportForced = <?php echo wp_json_encode( current_theme_supports( AMP_Theme_Support::SLUG ) && ! AMP_Theme_Support::is_support_added_via_option() ); ?>;
446
+
447
+ function isThemeSupportDisabled() {
448
+ return ! supportForced && themeSupportDisabledInput.prop( 'checked' );
449
+ }
450
+
451
+ function updateFieldsetVisibility() {
452
+ var allTemplatesSupported = 0 === allTemplatesSupportedInput.length || allTemplatesSupportedInput.prop( 'checked' );
453
+ $( '#all_templates_supported_fieldset, #supported_post_types_fieldset > .title' ).toggleClass(
454
+ 'hidden',
455
+ isThemeSupportDisabled()
456
+ );
457
+ $( '#supported_post_types_fieldset' ).toggleClass(
458
+ 'hidden',
459
+ allTemplatesSupported && ! isThemeSupportDisabled()
460
+ );
461
+ $( '#supported_templates_fieldset' ).toggleClass(
462
+ 'hidden',
463
+ allTemplatesSupported || isThemeSupportDisabled()
464
+ );
465
+ }
466
+
467
+ templateModeInputs.on( 'change', updateFieldsetVisibility );
468
+ allTemplatesSupportedInput.on( 'click', updateFieldsetVisibility );
469
+ updateFieldsetVisibility();
470
+ })( jQuery );
471
+ </script>
472
+ <?php endif; ?>
473
+ <?php
474
+ }
475
+
476
+ /**
477
+ * Render the caching settings section.
478
+ *
479
+ * @since 1.0
480
+ *
481
+ * @todo Change the messaging and description to be user-friendly and helpful.
482
+ */
483
+ public function render_caching() {
484
+ ?>
485
+ <fieldset <?php disabled( ! current_user_can( 'manage_options' ) ); ?>>
486
+ <?php if ( AMP_Options_Manager::show_response_cache_disabled_notice() ) : ?>
487
+ <div class="notice notice-info notice-alt inline">
488
+ <p><?php esc_html_e( 'The post-processor cache was disabled due to detecting randomly generated content found on', 'amp' ); ?> <a href="<?php echo esc_url( get_option( AMP_Theme_Support::CACHE_MISS_URL_OPTION, '' ) ); ?>"><?php esc_html_e( 'on this web page.', 'amp' ); ?></a></p>
489
+ <p><?php esc_html_e( 'Randomly generated content was detected on this web page. To avoid filling up the cache with unusable content, the AMP plugin\'s post-processor cache was automatically disabled.', 'amp' ); ?>
490
+ <a href="<?php echo esc_url( 'https://github.com/ampproject/amp-wp/wiki/Post-Processor-Cache' ); ?>"><?php esc_html_e( 'Read more', 'amp' ); ?></a>.</p>
491
+ </div>
492
+ <?php endif; ?>
493
+ <p>
494
+ <label for="enable_response_caching">
495
+ <input id="enable_response_caching" type="checkbox" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[enable_response_caching]' ); ?>" <?php checked( AMP_Options_Manager::get_option( 'enable_response_caching' ) ); ?>>
496
+ <?php esc_html_e( 'Enable post-processor caching.', 'amp' ); ?>
497
  </label>
 
 
 
 
 
 
 
 
 
 
498
  </p>
499
+ <p class="description"><?php esc_html_e( 'This will enable post-processor caching to speed up processing an AMP response after WordPress renders a template.', 'amp' ); ?></p>
500
  </fieldset>
501
  <?php
502
  }
503
 
504
+ /**
505
+ * List template conditional options.
506
+ *
507
+ * @param array $options Options.
508
+ * @param string|null $parent ID of the parent option.
509
+ */
510
+ private function list_template_conditional_options( $options, $parent = null ) {
511
+ $element_name = AMP_Options_Manager::OPTION_NAME . '[supported_templates][]';
512
+ ?>
513
+ <ul>
514
+ <?php foreach ( $options as $id => $option ) : ?>
515
+ <?php
516
+ $element_id = AMP_Options_Manager::OPTION_NAME . '-supported-templates-' . $id;
517
+ if ( $parent ? empty( $option['parent'] ) || $parent !== $option['parent'] : ! empty( $option['parent'] ) ) {
518
+ continue;
519
+ }
520
+
521
+ // Skip showing an option if it doesn't have a label.
522
+ if ( empty( $option['label'] ) ) {
523
+ continue;
524
+ }
525
+
526
+ ?>
527
+ <li>
528
+ <?php if ( empty( $option['immutable'] ) ) : ?>
529
+ <input
530
+ type="checkbox"
531
+ id="<?php echo esc_attr( $element_id ); ?>"
532
+ name="<?php echo esc_attr( $element_name ); ?>"
533
+ value="<?php echo esc_attr( $id ); ?>"
534
+ <?php checked( ! empty( $option['user_supported'] ) ); ?>
535
+ >
536
+ <?php else : // Persist user selection even when checkbox disabled, when selection forced by theme/filter. ?>
537
+ <input
538
+ type="checkbox"
539
+ id="<?php echo esc_attr( $element_id ); ?>"
540
+ <?php checked( ! empty( $option['supported'] ) ); ?>
541
+ <?php disabled( true ); ?>
542
+ >
543
+ <?php if ( ! empty( $option['user_supported'] ) ) : ?>
544
+ <input type="hidden" name="<?php echo esc_attr( $element_name ); ?>" value="<?php echo esc_attr( $id ); ?>">
545
+ <?php endif; ?>
546
+ <?php endif; ?>
547
+ <label for="<?php echo esc_attr( $element_id ); ?>">
548
+ <?php echo esc_html( $option['label'] ); ?>
549
+ </label>
550
+
551
+ <?php if ( ! empty( $option['description'] ) ) : ?>
552
+ <span class="description">
553
+ &mdash; <?php echo wp_kses_post( $option['description'] ); ?>
554
+ </span>
555
+ <?php endif; ?>
556
+
557
+ <?php self::list_template_conditional_options( $options, $id ); ?>
558
+ </li>
559
+ <?php endforeach; ?>
560
+ </ul>
561
+ <?php
562
+ }
563
+
564
  /**
565
  * Display Settings.
566
  *
571
  AMP_Options_Manager::check_supported_post_type_update_errors();
572
  }
573
  ?>
574
+ <?php if ( ! current_user_can( 'manage_options' ) ) : ?>
575
+ <div class="notice notice-info">
576
+ <p><?php esc_html_e( 'You do not have permission to modify these settings. They are shown here for your reference. Please contact your administrator to make changes.', 'amp' ); ?></p>
577
+ </div>
578
+ <?php endif; ?>
579
  <div class="wrap">
580
  <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
581
  <?php settings_errors(); ?>
582
+ <form id="amp-settings" action="options.php" method="post">
583
  <?php
584
  settings_fields( AMP_Options_Manager::OPTION_NAME );
585
  do_settings_sections( AMP_Options_Manager::OPTION_NAME );
586
+ if ( current_user_can( 'manage_options' ) ) {
587
  submit_button();
588
  }
589
  ?>
includes/options/views/class-amp-analytics-options-submenu-page.php CHANGED
@@ -17,18 +17,24 @@ class AMP_Analytics_Options_Submenu_Page {
17
  * @param string $type Entry type.
18
  * @param string $config Entry config as serialized JSON.
19
  */
20
- private function render_entry( $id = '', $type = '', $config = '' ) {
21
  $is_existing_entry = ! empty( $id );
22
 
23
  if ( $is_existing_entry ) {
24
  $entry_slug = sprintf( '%s%s', ( $type ? $type . '-' : '' ), substr( $id, - 6 ) );
25
- /* translators: %s is the entry slug */
26
  $analytics_title = sprintf( __( 'Analytics: %s', 'amp' ), $entry_slug );
27
  } else {
28
  $analytics_title = __( 'Add new entry:', 'amp' );
29
  $id = '__new__';
30
  }
31
 
 
 
 
 
 
 
32
  $id_base = sprintf( '%s[analytics][%s]', AMP_Options_Manager::OPTION_NAME, $id );
33
  ?>
34
  <div class="analytics-data-container">
@@ -40,7 +46,7 @@ class AMP_Analytics_Options_Submenu_Page {
40
  <p>
41
  <label>
42
  <?php esc_html_e( 'Type:', 'amp' ); ?>
43
- <input class="option-input" type="text" name="<?php echo esc_attr( $id_base . '[type]' ); ?>" value="<?php echo esc_attr( $type ); ?>" />
44
  </label>
45
  <label>
46
  <?php esc_html_e( 'ID:', 'amp' ); ?>
@@ -52,7 +58,15 @@ class AMP_Analytics_Options_Submenu_Page {
52
  <label>
53
  <?php esc_html_e( 'JSON Configuration:', 'amp' ); ?>
54
  <br />
55
- <textarea rows="10" cols="100" name="<?php echo esc_attr( $id_base . '[config]' ); ?>"><?php echo esc_textarea( $config ); ?></textarea>
 
 
 
 
 
 
 
 
56
  </label>
57
  </p>
58
  <input type="hidden" name="action" value="amp_analytics_options">
@@ -73,23 +87,92 @@ class AMP_Analytics_Options_Submenu_Page {
73
 
74
  /**
75
  * Render title.
 
 
76
  */
77
- public function render_title() {
78
  ?>
79
  <div class="wrap">
80
  <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
81
  <?php settings_errors(); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  </div><!-- .wrap -->
83
  <?php
84
  }
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  /**
87
  * Render.
88
  */
89
  public function render() {
 
 
90
  $analytics_entries = AMP_Options_Manager::get_option( 'analytics', array() );
91
 
92
- $this->render_title();
93
 
94
  // Render entries stored in the DB.
95
  foreach ( $analytics_entries as $entry_id => $entry ) {
@@ -98,5 +181,7 @@ class AMP_Analytics_Options_Submenu_Page {
98
 
99
  // Empty form for adding more entries.
100
  $this->render_entry();
 
 
101
  }
102
  }
17
  * @param string $type Entry type.
18
  * @param string $config Entry config as serialized JSON.
19
  */
20
+ private function render_entry( $id = '', $type = '', $config = '{}' ) {
21
  $is_existing_entry = ! empty( $id );
22
 
23
  if ( $is_existing_entry ) {
24
  $entry_slug = sprintf( '%s%s', ( $type ? $type . '-' : '' ), substr( $id, - 6 ) );
25
+ /* translators: %s: the entry slug. */
26
  $analytics_title = sprintf( __( 'Analytics: %s', 'amp' ), $entry_slug );
27
  } else {
28
  $analytics_title = __( 'Add new entry:', 'amp' );
29
  $id = '__new__';
30
  }
31
 
32
+ // Tidy-up the JSON for display.
33
+ if ( $config ) {
34
+ $options = ( 128 /* JSON_PRETTY_PRINT */ | 64 /* JSON_UNESCAPED_SLASHES */ );
35
+ $config = wp_json_encode( json_decode( $config ), $options );
36
+ }
37
+
38
  $id_base = sprintf( '%s[analytics][%s]', AMP_Options_Manager::OPTION_NAME, $id );
39
  ?>
40
  <div class="analytics-data-container">
46
  <p>
47
  <label>
48
  <?php esc_html_e( 'Type:', 'amp' ); ?>
49
+ <input class="option-input" type="text" required name="<?php echo esc_attr( $id_base . '[type]' ); ?>" placeholder="<?php esc_attr_e( 'e.g. googleanalytics', 'amp' ); ?>" value="<?php echo esc_attr( $type ); ?>" />
50
  </label>
51
  <label>
52
  <?php esc_html_e( 'ID:', 'amp' ); ?>
58
  <label>
59
  <?php esc_html_e( 'JSON Configuration:', 'amp' ); ?>
60
  <br />
61
+ <textarea
62
+ rows="10"
63
+ cols="100"
64
+ name="<?php echo esc_attr( $id_base . '[config]' ); ?>"
65
+ class="amp-analytics-input"
66
+ placeholder="{...}"
67
+ title="<?php esc_attr_e( 'A JSON object begins with a &#8220;{&#8221; and ends with a &#8220;}&#8221;. Do not include any HTML tags like "<amp-analytics>" or "<script>".', 'amp' ); ?>"
68
+ required
69
+ ><?php echo esc_textarea( $config ); ?></textarea>
70
  </label>
71
  </p>
72
  <input type="hidden" name="action" value="amp_analytics_options">
87
 
88
  /**
89
  * Render title.
90
+ *
91
+ * @param bool $has_entries Whether there are entries.
92
  */
93
+ public function render_title( $has_entries = false ) {
94
  ?>
95
  <div class="wrap">
96
  <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
97
  <?php settings_errors(); ?>
98
+
99
+ <details <?php echo ! $has_entries ? 'open' : ''; ?>>
100
+ <summary>
101
+ <?php esc_html_e( 'Learn about analytics for AMP.', 'amp' ); ?>
102
+ </summary>
103
+ <p>
104
+ <?php echo wp_kses_post( __( 'For Google Analytics, please see <a href="https://developers.google.com/analytics/devguides/collection/amp-analytics/" target="_blank">Adding Analytics to your AMP pages</a>; see also the <a href="https://github.com/ampproject/amp-wp/wiki/Analytics" target="_blank">Analytics wiki page</a> and the AMP project\'s <a href="https://www.ampproject.org/docs/reference/components/amp-analytics" target="_blank">amp-analytics documentation</a>. The analytics configuration supplied below must take the form of JSON objects, which begin with a &#8220;{&#8221; and end with a &#8220;}&#8221;. Do not include any HTML tags like &#8220;<code>&lt;amp-analytics&gt;</code>&#8221; or &#8220;<code>&lt;script&gt;</code>&#8221;. A common entry would have the type &#8220;<code>googleanalytics</code>&#8221; (see <a href="https://www.ampproject.org/docs/analytics/analytics-vendors" target="_blank">available vendors</a>) and a configuration that looks like the following (where <code>UA-XXXXX-Y</code> is replaced with your own site\'s account number):', 'amp' ) ); ?>
105
+
106
+ <pre>{
107
+ "vars": {
108
+ "account": "UA-XXXXX-Y"
109
+ },
110
+ "triggers": {
111
+ "trackPageview": {
112
+ "on": "visible",
113
+ "request": "pageview"
114
+ }
115
+ }
116
+ }</pre>
117
+ </p>
118
+ </details>
119
  </div><!-- .wrap -->
120
  <?php
121
  }
122
 
123
+ /**
124
+ * Render styles.
125
+ */
126
+ protected function render_styles() {
127
+ ?>
128
+ <style>
129
+ .amp-analytics-input {
130
+ font-family: monospace;
131
+ }
132
+ .amp-analytics-input:invalid {
133
+ border-color: red;
134
+ }
135
+ </style>
136
+ <?php
137
+ }
138
+
139
+ /**
140
+ * Render scripts.
141
+ */
142
+ protected function render_scripts() {
143
+ ?>
144
+ <script>
145
+ Array.prototype.forEach.call( document.querySelectorAll( '.amp-analytics-input' ), function( textarea ) {
146
+ textarea.addEventListener( 'input', function() {
147
+ if ( ! this.value ) {
148
+ this.setCustomValidity( '' );
149
+ return;
150
+ }
151
+ try {
152
+ var value = JSON.parse( this.value );
153
+ if ( null === value || typeof value !== 'object' || Array.isArray( value ) ) {
154
+ this.setCustomValidity( <?php echo wp_json_encode( __( 'A JSON object is required, e.g. {...}', 'amp' ) ); ?> )
155
+ } else {
156
+ this.setCustomValidity( '' );
157
+ }
158
+ } catch ( e ) {
159
+ this.setCustomValidity( e.message )
160
+ }
161
+ } );
162
+ } );
163
+ </script>
164
+ <?php
165
+ }
166
+
167
  /**
168
  * Render.
169
  */
170
  public function render() {
171
+ $this->render_styles();
172
+
173
  $analytics_entries = AMP_Options_Manager::get_option( 'analytics', array() );
174
 
175
+ $this->render_title( ! empty( $analytics_entries ) );
176
 
177
  // Render entries stored in the DB.
178
  foreach ( $analytics_entries as $entry_id => $entry ) {
181
 
182
  // Empty form for adding more entries.
183
  $this->render_entry();
184
+
185
+ $this->render_scripts();
186
  }
187
  }
includes/sanitizers/class-amp-allowed-tags-generated.php CHANGED
@@ -13,8 +13,8 @@
13
  */
14
  class AMP_Allowed_Tags_Generated {
15
 
16
- private static $spec_file_revision = 595;
17
- private static $minimum_validator_revision_required = 322;
18
 
19
  private static $allowed_tags = array(
20
  'a' => array(
@@ -27,14 +27,14 @@ class AMP_Allowed_Tags_Generated {
27
  'blacklisted_value_regex' => '__amp_source_origin',
28
  'value_url' => array(
29
  'allow_empty' => true,
30
- 'allow_relative' => true,
31
- 'allowed_protocol' => array(
32
  'ftp',
33
  'geo',
34
  'http',
35
  'https',
36
  'mailto',
37
  'maps',
 
38
  'bbmi',
39
  'fb-messenger',
40
  'intent',
@@ -47,6 +47,8 @@ class AMP_Allowed_Tags_Generated {
47
  'threema',
48
  'twitter',
49
  'viber',
 
 
50
  'whatsapp',
51
  ),
52
  ),
@@ -61,10 +63,16 @@ class AMP_Allowed_Tags_Generated {
61
  'role' => array(),
62
  'tabindex' => array(),
63
  'target' => array(
64
- 'value_regex' => '(_blank|_self|_top)',
 
 
 
 
65
  ),
66
  'type' => array(
67
- 'value_casei' => 'text/html',
 
 
68
  ),
69
  ),
70
  'tag_spec' => array(
@@ -90,21 +98,95 @@ class AMP_Allowed_Tags_Generated {
90
  'tag_spec' => array(),
91
  ),
92
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  'amp-3q-player' => array(
94
  array(
95
  'attr_spec_list' => array(
96
  'autoplay' => array(
97
- 'value' => '',
 
 
98
  ),
99
  'data-id' => array(
100
  'mandatory' => true,
101
  ),
102
  'media' => array(),
103
  'noloading' => array(
104
- 'value' => '',
 
 
105
  ),
106
  ),
107
  'tag_spec' => array(
 
 
 
 
 
 
 
 
108
  'requires_extension' => array(
109
  'amp-3q-player',
110
  ),
@@ -114,14 +196,34 @@ class AMP_Allowed_Tags_Generated {
114
  'amp-accordion' => array(
115
  array(
116
  'attr_spec_list' => array(
 
 
 
 
 
117
  'disable-session-states' => array(
118
- 'value' => '',
 
 
119
  ),
120
  'expand-single-section' => array(
121
- 'value' => '',
 
 
 
 
 
 
 
 
122
  ),
123
  ),
124
  'tag_spec' => array(
 
 
 
 
 
125
  'requires_extension' => array(
126
  'amp-accordion',
127
  ),
@@ -136,14 +238,16 @@ class AMP_Allowed_Tags_Generated {
136
  'json' => array(),
137
  'media' => array(),
138
  'noloading' => array(
139
- 'value' => '',
 
 
140
  ),
141
  'rtc-config' => array(),
142
  'src' => array(
143
  'blacklisted_value_regex' => '__amp_source_origin',
144
  'value_url' => array(
145
  'allow_relative' => true,
146
- 'allowed_protocol' => array(
147
  'https',
148
  ),
149
  ),
@@ -157,6 +261,18 @@ class AMP_Allowed_Tags_Generated {
157
  'also_requires_tag_warning' => array(
158
  'amp-ad extension .js script',
159
  ),
 
 
 
 
 
 
 
 
 
 
 
 
160
  'disallowed_ancestor' => array(
161
  'amp-app-banner',
162
  ),
@@ -172,19 +288,23 @@ class AMP_Allowed_Tags_Generated {
172
  'data-multi-size' => array(
173
  'dispatch_key' => 2,
174
  'mandatory' => true,
175
- 'value' => '',
 
 
176
  ),
177
  'json' => array(),
178
  'media' => array(),
179
  'noloading' => array(
180
- 'value' => '',
 
 
181
  ),
182
  'rtc-config' => array(),
183
  'src' => array(
184
  'blacklisted_value_regex' => '__amp_source_origin',
185
  'value_url' => array(
186
  'allow_relative' => true,
187
- 'allowed_protocol' => array(
188
  'https',
189
  ),
190
  ),
@@ -197,6 +317,18 @@ class AMP_Allowed_Tags_Generated {
197
  'also_requires_tag_warning' => array(
198
  'amp-ad extension .js script',
199
  ),
 
 
 
 
 
 
 
 
 
 
 
 
200
  'disallowed_ancestor' => array(
201
  'amp-app-banner',
202
  'amp-carousel',
@@ -217,18 +349,22 @@ class AMP_Allowed_Tags_Generated {
217
  'data-enable-refresh' => array(
218
  'dispatch_key' => 2,
219
  'mandatory' => true,
220
- 'value' => '',
 
 
221
  ),
222
  'json' => array(),
223
  'media' => array(),
224
  'noloading' => array(
225
- 'value' => '',
 
 
226
  ),
227
  'src' => array(
228
  'blacklisted_value_regex' => '__amp_source_origin',
229
  'value_url' => array(
230
  'allow_relative' => true,
231
- 'allowed_protocol' => array(
232
  'https',
233
  ),
234
  ),
@@ -241,6 +377,18 @@ class AMP_Allowed_Tags_Generated {
241
  'also_requires_tag_warning' => array(
242
  'amp-ad extension .js script',
243
  ),
 
 
 
 
 
 
 
 
 
 
 
 
244
  'disallowed_ancestor' => array(
245
  'amp-app-banner',
246
  'amp-fx-flying-carpet',
@@ -254,6 +402,57 @@ class AMP_Allowed_Tags_Generated {
254
  ),
255
  ),
256
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  'amp-analytics' => array(
258
  array(
259
  'attr_spec_list' => array(
@@ -262,7 +461,7 @@ class AMP_Allowed_Tags_Generated {
262
  'value_url' => array(
263
  'allow_empty' => true,
264
  'allow_relative' => true,
265
- 'allowed_protocol' => array(
266
  'https',
267
  ),
268
  ),
@@ -282,10 +481,11 @@ class AMP_Allowed_Tags_Generated {
282
  'attr_spec_list' => array(
283
  'alt' => array(),
284
  'attribution' => array(),
285
- 'controls' => array(),
286
  'media' => array(),
287
  'noloading' => array(
288
- 'value' => '',
 
 
289
  ),
290
  'src' => array(
291
  'alternative_names' => array(
@@ -294,8 +494,7 @@ class AMP_Allowed_Tags_Generated {
294
  'blacklisted_value_regex' => '__amp_source_origin',
295
  'mandatory' => true,
296
  'value_url' => array(
297
- 'allow_relative' => true,
298
- 'allowed_protocol' => array(
299
  'data',
300
  'http',
301
  'https',
@@ -304,6 +503,17 @@ class AMP_Allowed_Tags_Generated {
304
  ),
305
  ),
306
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
307
  'requires_extension' => array(
308
  'amp-anim',
309
  ),
@@ -316,13 +526,22 @@ class AMP_Allowed_Tags_Generated {
316
  'attr_spec_list' => array(
317
  'media' => array(),
318
  'noloading' => array(
319
- 'value' => '',
 
 
320
  ),
321
  'trigger' => array(
322
- 'value' => 'visibility',
 
 
323
  ),
324
  ),
325
  'tag_spec' => array(
 
 
 
 
 
326
  'requires_extension' => array(
327
  'amp-animation',
328
  ),
@@ -340,10 +559,22 @@ class AMP_Allowed_Tags_Generated {
340
  ),
341
  'media' => array(),
342
  'noloading' => array(
343
- 'value' => '',
 
 
344
  ),
345
  ),
346
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
347
  'requires_extension' => array(
348
  'amp-apester-media',
349
  ),
@@ -359,10 +590,17 @@ class AMP_Allowed_Tags_Generated {
359
  ),
360
  'media' => array(),
361
  'noloading' => array(
362
- 'value' => '',
 
 
363
  ),
364
  ),
365
  'tag_spec' => array(
 
 
 
 
 
366
  'mandatory_parent' => 'body',
367
  'requires_extension' => array(
368
  'amp-app-banner',
@@ -379,34 +617,55 @@ class AMP_Allowed_Tags_Generated {
379
  'artist' => array(),
380
  'artwork' => array(),
381
  'autoplay' => array(
382
- 'value' => '',
 
 
383
  ),
384
  'controls' => array(),
385
  'controlslist' => array(),
386
  'loop' => array(
387
- 'value' => '',
 
 
388
  ),
389
  'media' => array(),
390
  'muted' => array(
391
- 'value' => '',
 
 
392
  ),
393
  'noloading' => array(
394
- 'value' => '',
 
 
395
  ),
396
  'preload' => array(
397
- 'value_regex_casei' => '(auto|metadata|none|)',
 
 
 
 
398
  ),
399
  'src' => array(
400
  'blacklisted_value_regex' => '__amp_source_origin',
401
  'value_url' => array(
402
  'allow_relative' => true,
403
- 'allowed_protocol' => array(
404
  'https',
405
  ),
406
  ),
407
  ),
408
  ),
409
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
410
  'disallowed_ancestor' => array(
411
  'amp-story',
412
  ),
@@ -423,31 +682,48 @@ class AMP_Allowed_Tags_Generated {
423
  'artwork' => array(),
424
  'autoplay' => array(
425
  'mandatory' => true,
426
- 'value' => '',
 
 
427
  ),
428
  'controls' => array(),
429
  'controlslist' => array(),
430
  'loop' => array(
431
- 'value' => '',
 
 
432
  ),
433
  'media' => array(),
434
  'muted' => array(
435
- 'value' => '',
 
 
436
  ),
437
  'noloading' => array(
438
- 'value' => '',
 
 
439
  ),
440
  'src' => array(
441
  'blacklisted_value_regex' => '__amp_source_origin',
442
  'value_url' => array(
443
  'allow_relative' => true,
444
- 'allowed_protocol' => array(
445
  'https',
446
  ),
447
  ),
448
  ),
449
  ),
450
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
451
  'mandatory_ancestor' => 'amp-story',
452
  'requires_extension' => array(
453
  'amp-audio',
@@ -462,14 +738,18 @@ class AMP_Allowed_Tags_Generated {
462
  'attr_spec_list' => array(
463
  'media' => array(),
464
  'noloading' => array(
465
- 'value' => '',
 
 
466
  ),
467
  'type' => array(
468
  'mandatory' => true,
469
  ),
470
  ),
471
  'tag_spec' => array(
472
- 'mandatory_parent' => 'body',
 
 
473
  'requires_extension' => array(
474
  'amp-auto-ads',
475
  ),
@@ -488,15 +768,31 @@ class AMP_Allowed_Tags_Generated {
488
  'value_regex_casei' => '[0-9a-f]{24}',
489
  ),
490
  'data-my-content' => array(
491
- 'value_regex' => '0|1',
 
 
 
492
  ),
493
  'data-name' => array(),
494
  'media' => array(),
495
  'noloading' => array(
496
- 'value' => '',
 
 
497
  ),
498
  ),
499
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
500
  'requires_extension' => array(
501
  'amp-beopinion',
502
  ),
@@ -526,22 +822,44 @@ class AMP_Allowed_Tags_Generated {
526
  array(
527
  'attr_spec_list' => array(
528
  'loop' => array(
529
- 'value_regex_casei' => '(false|number|true)',
 
 
 
 
530
  ),
531
  'noautoplay' => array(
532
- 'value' => '',
 
 
 
 
 
 
 
 
533
  ),
534
  'src' => array(
535
  'mandatory' => true,
536
  'value_url' => array(
537
  'allow_relative' => false,
538
- 'allowed_protocol' => array(
539
  'https',
540
  ),
541
  ),
542
  ),
543
  ),
544
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
545
  'requires_extension' => array(
546
  'amp-bodymovin-animation',
547
  ),
@@ -572,10 +890,22 @@ class AMP_Allowed_Tags_Generated {
572
  ),
573
  'media' => array(),
574
  'noloading' => array(
575
- 'value' => '',
 
 
576
  ),
577
  ),
578
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
579
  'requires_extension' => array(
580
  'amp-brid-player',
581
  ),
@@ -591,16 +921,34 @@ class AMP_Allowed_Tags_Generated {
591
  '[data-player-id]' => array(),
592
  '[data-player]' => array(),
593
  '[data-playlist-id]' => array(),
 
594
  '[data-video-id]' => array(),
 
 
 
 
 
595
  'data-account' => array(
596
  'mandatory' => true,
597
  ),
598
  'media' => array(),
599
  'noloading' => array(
600
- 'value' => '',
 
 
601
  ),
602
  ),
603
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
604
  'requires_extension' => array(
605
  'amp-brightcove',
606
  ),
@@ -619,10 +967,22 @@ class AMP_Allowed_Tags_Generated {
619
  ),
620
  'media' => array(),
621
  'noloading' => array(
622
- 'value' => '',
 
 
623
  ),
624
  ),
625
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
626
  'requires_extension' => array(
627
  'amp-byside-content',
628
  ),
@@ -637,17 +997,29 @@ class AMP_Allowed_Tags_Generated {
637
  'mandatory' => true,
638
  'value_url' => array(
639
  'allow_relative' => false,
640
- 'allowed_protocol' => array(
641
  'https',
642
  ),
643
  ),
644
  ),
645
  'media' => array(),
646
  'noloading' => array(
647
- 'value' => '',
 
 
648
  ),
649
  ),
650
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
651
  'requires_extension' => array(
652
  'amp-call-tracking',
653
  ),
@@ -660,40 +1032,128 @@ class AMP_Allowed_Tags_Generated {
660
  'attr_spec_list' => array(
661
  '[slide]' => array(),
662
  'arrows' => array(
663
- 'value' => '',
 
 
664
  ),
665
  'autoplay' => array(
666
- 'value' => '',
667
  ),
668
  'controls' => array(),
669
  'delay' => array(
670
  'value_regex' => '[0-9]+',
671
  ),
672
  'dots' => array(
673
- 'value' => '',
 
 
674
  ),
675
- 'lightbox' => array(),
676
- 'lightbox-exclude' => array(
677
- 'value' => '',
 
678
  ),
679
- 'lightbox-thumbnail-id' => array(
680
- 'value_regex_casei' => '^[a-z][a-z\\d_-]*',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
  ),
682
  'loop' => array(
683
- 'value' => '',
 
 
684
  ),
685
  'media' => array(),
686
  'noloading' => array(
687
- 'value' => '',
 
 
688
  ),
689
  'type' => array(
690
- 'value_regex' => 'slides|carousel',
 
 
 
691
  ),
692
  ),
693
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
  'requires_extension' => array(
695
  'amp-carousel',
 
696
  ),
 
697
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-carousel',
698
  ),
699
  ),
@@ -703,10 +1163,17 @@ class AMP_Allowed_Tags_Generated {
703
  'attr_spec_list' => array(
704
  'media' => array(),
705
  'noloading' => array(
706
- 'value' => '',
 
 
707
  ),
708
  ),
709
  'tag_spec' => array(
 
 
 
 
 
710
  'requires_extension' => array(
711
  'amp-consent',
712
  ),
@@ -719,16 +1186,28 @@ class AMP_Allowed_Tags_Generated {
719
  'attr_spec_list' => array(
720
  'autoplay' => array(),
721
  'data-endscreen-enable' => array(
722
- 'value_regex' => 'true|false',
 
 
 
723
  ),
724
  'data-info' => array(
725
- 'value_regex' => 'true|false',
 
 
 
726
  ),
727
  'data-mute' => array(
728
- 'value_regex' => 'true|false',
 
 
 
729
  ),
730
  'data-sharing-enable' => array(
731
- 'value_regex' => 'true|false',
 
 
 
732
  ),
733
  'data-start' => array(
734
  'value_regex' => '[0-9]+',
@@ -737,7 +1216,10 @@ class AMP_Allowed_Tags_Generated {
737
  'value_regex_casei' => '([0-9a-f]{3}){1,2}',
738
  ),
739
  'data-ui-logo' => array(
740
- 'value_regex' => 'true|false',
 
 
 
741
  ),
742
  'data-videoid' => array(
743
  'mandatory' => true,
@@ -745,10 +1227,21 @@ class AMP_Allowed_Tags_Generated {
745
  ),
746
  'media' => array(),
747
  'noloading' => array(
748
- 'value' => '',
 
 
749
  ),
750
  ),
751
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
752
  'requires_extension' => array(
753
  'amp-dailymotion',
754
  ),
@@ -756,13 +1249,95 @@ class AMP_Allowed_Tags_Generated {
756
  ),
757
  ),
758
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
  'amp-date-picker' => array(
760
  array(
761
  'attr_spec_list' => array(
 
 
762
  'allow-blocked-ranges' => array(
763
- 'value' => '',
 
 
764
  ),
765
  'blocked' => array(),
 
766
  'day-size' => array(
767
  'value_regex' => '[0-9]+',
768
  ),
@@ -771,7 +1346,9 @@ class AMP_Allowed_Tags_Generated {
771
  ),
772
  'format' => array(),
773
  'fullscreen' => array(
774
- 'value' => '',
 
 
775
  ),
776
  'highlighted' => array(),
777
  'input-selector' => array(),
@@ -780,36 +1357,57 @@ class AMP_Allowed_Tags_Generated {
780
  'media' => array(),
781
  'min' => array(),
782
  'mode' => array(
783
- 'value_casei' => 'static',
 
 
784
  ),
785
  'month-format' => array(),
786
  'noloading' => array(
787
- 'value' => '',
 
 
788
  ),
789
  'number-of-months' => array(
790
  'value_regex' => '[0-9]+',
791
  ),
792
  'open-after-clear' => array(
793
- 'value' => '',
 
 
794
  ),
795
  'open-after-select' => array(
796
- 'value' => '',
 
 
797
  ),
798
  'src' => array(
799
  'blacklisted_value_regex' => '__amp_source_origin',
800
  'value_url' => array(
801
- 'allow_relative' => true,
802
- 'allowed_protocol' => array(
803
  'https',
804
  ),
805
  ),
806
  ),
807
  'type' => array(
808
- 'value_casei' => 'single',
 
 
809
  ),
810
  'week-day-format' => array(),
811
  ),
812
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
813
  'requires_extension' => array(
814
  'amp-date-picker',
815
  ),
@@ -818,10 +1416,15 @@ class AMP_Allowed_Tags_Generated {
818
  ),
819
  array(
820
  'attr_spec_list' => array(
 
 
821
  'allow-blocked-ranges' => array(
822
- 'value' => '',
 
 
823
  ),
824
  'blocked' => array(),
 
825
  'day-size' => array(
826
  'value_regex' => '[0-9]+',
827
  ),
@@ -837,36 +1440,57 @@ class AMP_Allowed_Tags_Generated {
837
  'min' => array(),
838
  'mode' => array(
839
  'mandatory' => true,
840
- 'value_casei' => 'overlay',
 
 
841
  ),
842
  'month-format' => array(),
843
  'noloading' => array(
844
- 'value' => '',
 
 
845
  ),
846
  'number-of-months' => array(
847
  'value_regex' => '[0-9]+',
848
  ),
849
  'open-after-clear' => array(
850
- 'value' => '',
 
 
851
  ),
852
  'open-after-select' => array(
853
- 'value' => '',
 
 
854
  ),
855
  'src' => array(
856
  'blacklisted_value_regex' => '__amp_source_origin',
857
  'value_url' => array(
858
- 'allow_relative' => true,
859
- 'allowed_protocol' => array(
860
  'https',
861
  ),
862
  ),
863
  ),
 
 
 
 
 
864
  'type' => array(
865
- 'value_casei' => 'single',
 
 
866
  ),
867
  'week-day-format' => array(),
868
  ),
869
  'tag_spec' => array(
 
 
 
 
 
 
870
  'requires_extension' => array(
871
  'amp-date-picker',
872
  ),
@@ -875,59 +1499,91 @@ class AMP_Allowed_Tags_Generated {
875
  ),
876
  array(
877
  'attr_spec_list' => array(
 
 
878
  'allow-blocked-ranges' => array(
879
- 'value' => '',
 
 
880
  ),
881
  'blocked' => array(),
882
  'day-size' => array(
883
  'value_regex' => '[0-9]+',
884
  ),
 
885
  'end-input-selector' => array(),
886
  'first-day-of-week' => array(
887
  'value_regex' => '[0-6]',
888
  ),
889
  'format' => array(),
890
  'fullscreen' => array(
891
- 'value' => '',
 
 
892
  ),
893
  'highlighted' => array(),
894
  'locale' => array(),
895
  'max' => array(),
896
  'media' => array(),
897
  'min' => array(),
 
 
 
898
  'mode' => array(
899
- 'value_casei' => 'static',
 
 
900
  ),
901
  'month-format' => array(),
902
  'noloading' => array(
903
- 'value' => '',
 
 
904
  ),
905
  'number-of-months' => array(
906
  'value_regex' => '[0-9]+',
907
  ),
908
  'open-after-clear' => array(
909
- 'value' => '',
 
 
910
  ),
911
  'open-after-select' => array(
912
- 'value' => '',
 
 
913
  ),
914
  'src' => array(
915
  'blacklisted_value_regex' => '__amp_source_origin',
916
  'value_url' => array(
917
- 'allow_relative' => true,
918
- 'allowed_protocol' => array(
919
  'https',
920
  ),
921
  ),
922
  ),
 
923
  'start-input-selector' => array(),
924
  'type' => array(
925
  'mandatory' => true,
926
- 'value_casei' => 'range',
 
 
927
  ),
928
  'week-day-format' => array(),
929
  ),
930
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
931
  'requires_extension' => array(
932
  'amp-date-picker',
933
  ),
@@ -936,13 +1592,18 @@ class AMP_Allowed_Tags_Generated {
936
  ),
937
  array(
938
  'attr_spec_list' => array(
 
 
939
  'allow-blocked-ranges' => array(
940
- 'value' => '',
 
 
941
  ),
942
  'blocked' => array(),
943
  'day-size' => array(
944
  'value_regex' => '[0-9]+',
945
  ),
 
946
  'end-input-selector' => array(),
947
  'first-day-of-week' => array(
948
  'value_regex' => '[0-6]',
@@ -953,40 +1614,65 @@ class AMP_Allowed_Tags_Generated {
953
  'max' => array(),
954
  'media' => array(),
955
  'min' => array(),
 
 
 
956
  'mode' => array(
957
  'mandatory' => true,
958
- 'value_casei' => 'overlay',
 
 
959
  ),
960
  'month-format' => array(),
961
  'noloading' => array(
962
- 'value' => '',
 
 
963
  ),
964
  'number-of-months' => array(
965
  'value_regex' => '[0-9]+',
966
  ),
967
  'open-after-clear' => array(
968
- 'value' => '',
 
 
969
  ),
970
  'open-after-select' => array(
971
- 'value' => '',
 
 
972
  ),
973
  'src' => array(
974
  'blacklisted_value_regex' => '__amp_source_origin',
975
  'value_url' => array(
976
- 'allow_relative' => true,
977
- 'allowed_protocol' => array(
978
  'https',
979
  ),
980
  ),
981
  ),
 
982
  'start-input-selector' => array(),
 
 
 
 
 
983
  'type' => array(
984
  'mandatory' => true,
985
- 'value_casei' => 'range',
 
 
986
  ),
987
  'week-day-format' => array(),
988
  ),
989
  'tag_spec' => array(
 
 
 
 
 
 
990
  'requires_extension' => array(
991
  'amp-date-picker',
992
  ),
@@ -994,15 +1680,33 @@ class AMP_Allowed_Tags_Generated {
994
  ),
995
  ),
996
  ),
997
- 'amp-document-recommendations' => array(
998
  array(
999
- 'attr_spec_list' => array(),
 
 
 
 
 
 
 
 
 
 
1000
  'tag_spec' => array(
1001
- 'mandatory_parent' => 'body',
 
 
 
 
 
 
 
 
 
1002
  'requires_extension' => array(
1003
- 'amp-document-recommendations',
1004
  ),
1005
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-document-recommendations',
1006
  ),
1007
  ),
1008
  ),
@@ -1013,14 +1717,16 @@ class AMP_Allowed_Tags_Generated {
1013
  'json' => array(),
1014
  'media' => array(),
1015
  'noloading' => array(
1016
- 'value' => '',
 
 
1017
  ),
1018
  'rtc-config' => array(),
1019
  'src' => array(
1020
  'blacklisted_value_regex' => '__amp_source_origin',
1021
  'value_url' => array(
1022
  'allow_relative' => true,
1023
- 'allowed_protocol' => array(
1024
  'https',
1025
  ),
1026
  ),
@@ -1034,6 +1740,18 @@ class AMP_Allowed_Tags_Generated {
1034
  'also_requires_tag_warning' => array(
1035
  'amp-ad extension .js script',
1036
  ),
 
 
 
 
 
 
 
 
 
 
 
 
1037
  'disallowed_ancestor' => array(
1038
  'amp-app-banner',
1039
  ),
@@ -1049,19 +1767,23 @@ class AMP_Allowed_Tags_Generated {
1049
  'data-multi-size' => array(
1050
  'dispatch_key' => 2,
1051
  'mandatory' => true,
1052
- 'value' => '',
 
 
1053
  ),
1054
  'json' => array(),
1055
  'media' => array(),
1056
  'noloading' => array(
1057
- 'value' => '',
 
 
1058
  ),
1059
  'rtc-config' => array(),
1060
  'src' => array(
1061
  'blacklisted_value_regex' => '__amp_source_origin',
1062
  'value_url' => array(
1063
  'allow_relative' => true,
1064
- 'allowed_protocol' => array(
1065
  'https',
1066
  ),
1067
  ),
@@ -1074,6 +1796,18 @@ class AMP_Allowed_Tags_Generated {
1074
  'also_requires_tag_warning' => array(
1075
  'amp-ad extension .js script',
1076
  ),
 
 
 
 
 
 
 
 
 
 
 
 
1077
  'disallowed_ancestor' => array(
1078
  'amp-app-banner',
1079
  'amp-carousel',
@@ -1089,6 +1823,58 @@ class AMP_Allowed_Tags_Generated {
1089
  ),
1090
  ),
1091
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  'amp-experiment' => array(
1093
  array(
1094
  'attr_spec_list' => array(),
@@ -1109,10 +1895,22 @@ class AMP_Allowed_Tags_Generated {
1109
  ),
1110
  'media' => array(),
1111
  'noloading' => array(
1112
- 'value' => '',
 
 
1113
  ),
1114
  ),
1115
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1116
  'requires_extension' => array(
1117
  'amp-facebook',
1118
  ),
@@ -1127,10 +1925,22 @@ class AMP_Allowed_Tags_Generated {
1127
  ),
1128
  'media' => array(),
1129
  'noloading' => array(
1130
- 'value' => '',
 
 
1131
  ),
1132
  ),
1133
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1134
  'requires_extension' => array(
1135
  'amp-facebook-comments',
1136
  ),
@@ -1144,7 +1954,7 @@ class AMP_Allowed_Tags_Generated {
1144
  'mandatory' => true,
1145
  'value_url' => array(
1146
  'allow_relative' => false,
1147
- 'allowed_protocol' => array(
1148
  'http',
1149
  'https',
1150
  ),
@@ -1152,10 +1962,22 @@ class AMP_Allowed_Tags_Generated {
1152
  ),
1153
  'media' => array(),
1154
  'noloading' => array(
1155
- 'value' => '',
 
 
1156
  ),
1157
  ),
1158
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1159
  'requires_extension' => array(
1160
  'amp-facebook-like',
1161
  ),
@@ -1169,7 +1991,7 @@ class AMP_Allowed_Tags_Generated {
1169
  'mandatory' => true,
1170
  'value_url' => array(
1171
  'allow_relative' => false,
1172
- 'allowed_protocol' => array(
1173
  'http',
1174
  'https',
1175
  ),
@@ -1177,10 +1999,22 @@ class AMP_Allowed_Tags_Generated {
1177
  ),
1178
  'media' => array(),
1179
  'noloading' => array(
1180
- 'value' => '',
 
 
1181
  ),
1182
  ),
1183
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1184
  'requires_extension' => array(
1185
  'amp-facebook-page',
1186
  ),
@@ -1194,10 +2028,23 @@ class AMP_Allowed_Tags_Generated {
1194
  'media' => array(),
1195
  'min-font-size' => array(),
1196
  'noloading' => array(
1197
- 'value' => '',
 
 
1198
  ),
1199
  ),
1200
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
1201
  'requires_extension' => array(
1202
  'amp-fit-text',
1203
  ),
@@ -1215,7 +2062,9 @@ class AMP_Allowed_Tags_Generated {
1215
  'font-weight' => array(),
1216
  'media' => array(),
1217
  'noloading' => array(
1218
- 'value' => '',
 
 
1219
  ),
1220
  'on-error-add-class' => array(),
1221
  'on-error-remove-class' => array(),
@@ -1226,6 +2075,11 @@ class AMP_Allowed_Tags_Generated {
1226
  ),
1227
  ),
1228
  'tag_spec' => array(
 
 
 
 
 
1229
  'requires_extension' => array(
1230
  'amp-font',
1231
  ),
@@ -1240,7 +2094,9 @@ class AMP_Allowed_Tags_Generated {
1240
  ),
1241
  'media' => array(),
1242
  'noloading' => array(
1243
- 'value' => '',
 
 
1244
  ),
1245
  ),
1246
  'tag_spec' => array(
@@ -1250,6 +2106,29 @@ class AMP_Allowed_Tags_Generated {
1250
  ),
1251
  ),
1252
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1253
  'amp-gfycat' => array(
1254
  array(
1255
  'attr_spec_list' => array(
@@ -1258,13 +2137,26 @@ class AMP_Allowed_Tags_Generated {
1258
  ),
1259
  'media' => array(),
1260
  'noautoplay' => array(
1261
- 'value' => '',
 
 
1262
  ),
1263
  'noloading' => array(
1264
- 'value' => '',
 
 
1265
  ),
1266
  ),
1267
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
1268
  'requires_extension' => array(
1269
  'amp-gfycat',
1270
  ),
@@ -1280,10 +2172,17 @@ class AMP_Allowed_Tags_Generated {
1280
  ),
1281
  'media' => array(),
1282
  'noloading' => array(
1283
- 'value' => '',
 
 
1284
  ),
1285
  ),
1286
  'tag_spec' => array(
 
 
 
 
 
1287
  'requires_extension' => array(
1288
  'amp-gist',
1289
  ),
@@ -1291,6 +2190,47 @@ class AMP_Allowed_Tags_Generated {
1291
  ),
1292
  ),
1293
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1294
  'amp-hulu' => array(
1295
  array(
1296
  'attr_spec_list' => array(
@@ -1299,10 +2239,21 @@ class AMP_Allowed_Tags_Generated {
1299
  ),
1300
  'media' => array(),
1301
  'noloading' => array(
1302
- 'value' => '',
 
 
1303
  ),
1304
  ),
1305
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
1306
  'requires_extension' => array(
1307
  'amp-hulu',
1308
  ),
@@ -1316,34 +2267,51 @@ class AMP_Allowed_Tags_Generated {
1316
  '[src]' => array(),
1317
  'allow' => array(),
1318
  'allowfullscreen' => array(
1319
- 'value' => '',
 
 
1320
  ),
1321
  'allowpaymentrequest' => array(
1322
- 'value' => '',
 
 
1323
  ),
1324
  'allowtransparency' => array(
1325
- 'value' => '',
 
 
1326
  ),
1327
  'frameborder' => array(
1328
- 'value_regex' => '0|1',
 
 
 
1329
  ),
1330
  'media' => array(),
1331
  'noloading' => array(
1332
- 'value' => '',
 
 
1333
  ),
1334
  'referrerpolicy' => array(),
1335
  'resizable' => array(
1336
- 'value' => '',
 
 
1337
  ),
1338
  'sandbox' => array(),
1339
  'scrolling' => array(
1340
- 'value_regex' => 'auto|yes|no',
 
 
 
 
1341
  ),
1342
  'src' => array(
1343
  'blacklisted_value_regex' => '__amp_source_origin',
1344
  'value_url' => array(
1345
  'allow_relative' => true,
1346
- 'allowed_protocol' => array(
1347
  'data',
1348
  'https',
1349
  ),
@@ -1352,6 +2320,17 @@ class AMP_Allowed_Tags_Generated {
1352
  'srcdoc' => array(),
1353
  ),
1354
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
1355
  'requires_extension' => array(
1356
  'amp-iframe',
1357
  ),
@@ -1362,13 +2341,15 @@ class AMP_Allowed_Tags_Generated {
1362
  array(
1363
  'attr_spec_list' => array(
1364
  'autoplay' => array(
1365
- 'value' => '',
 
 
1366
  ),
1367
  'data-src' => array(
1368
  'blacklisted_value_regex' => '__amp_source_origin',
1369
  'value_url' => array(
1370
  'allow_relative' => true,
1371
- 'allowed_protocol' => array(
1372
  'https',
1373
  ),
1374
  ),
@@ -1377,17 +2358,33 @@ class AMP_Allowed_Tags_Generated {
1377
  'mandatory' => true,
1378
  'value_url' => array(
1379
  'allow_relative' => true,
1380
- 'allowed_protocol' => array(
1381
  'https',
1382
  ),
1383
  ),
1384
  ),
1385
  'media' => array(),
1386
  'noloading' => array(
1387
- 'value' => '',
 
 
 
 
 
 
 
1388
  ),
1389
  ),
1390
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
1391
  'requires_extension' => array(
1392
  'amp-ima-video',
1393
  ),
@@ -1401,16 +2398,56 @@ class AMP_Allowed_Tags_Generated {
1401
  'controls' => array(),
1402
  'media' => array(),
1403
  'noloading' => array(
1404
- 'value' => '',
 
 
1405
  ),
1406
  ),
1407
  'tag_spec' => array(
 
 
 
 
 
1408
  'requires_extension' => array(
1409
  'amp-image-lightbox',
1410
  ),
1411
  ),
1412
  ),
1413
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1414
  'amp-img' => array(
1415
  array(
1416
  'attr_spec_list' => array(
@@ -1421,15 +2458,14 @@ class AMP_Allowed_Tags_Generated {
1421
  'alt' => array(),
1422
  'attribution' => array(),
1423
  'lightbox' => array(),
1424
- 'lightbox-exclude' => array(
1425
- 'value' => '',
1426
- ),
1427
  'lightbox-thumbnail-id' => array(
1428
  'value_regex_casei' => '^[a-z][a-z\\d_-]*',
1429
  ),
1430
  'media' => array(),
1431
  'noloading' => array(
1432
- 'value' => '',
 
 
1433
  ),
1434
  'placeholder' => array(),
1435
  'src' => array(
@@ -1439,8 +2475,7 @@ class AMP_Allowed_Tags_Generated {
1439
  'blacklisted_value_regex' => '__amp_source_origin',
1440
  'mandatory' => true,
1441
  'value_url' => array(
1442
- 'allow_relative' => true,
1443
- 'allowed_protocol' => array(
1444
  'data',
1445
  'http',
1446
  'https',
@@ -1449,6 +2484,17 @@ class AMP_Allowed_Tags_Generated {
1449
  ),
1450
  ),
1451
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
1452
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
1453
  ),
1454
  ),
@@ -1461,10 +2507,22 @@ class AMP_Allowed_Tags_Generated {
1461
  ),
1462
  'media' => array(),
1463
  'noloading' => array(
1464
- 'value' => '',
 
 
1465
  ),
1466
  ),
1467
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1468
  'requires_extension' => array(
1469
  'amp-imgur',
1470
  ),
@@ -1480,10 +2538,22 @@ class AMP_Allowed_Tags_Generated {
1480
  ),
1481
  'media' => array(),
1482
  'noloading' => array(
1483
- 'value' => '',
 
 
1484
  ),
1485
  ),
1486
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1487
  'requires_extension' => array(
1488
  'amp-instagram',
1489
  ),
@@ -1497,7 +2567,7 @@ class AMP_Allowed_Tags_Generated {
1497
  'blacklisted_value_regex' => '__amp_source_origin',
1498
  'value_url' => array(
1499
  'allow_relative' => true,
1500
- 'allowed_protocol' => array(
1501
  'https',
1502
  ),
1503
  ),
@@ -1507,13 +2577,18 @@ class AMP_Allowed_Tags_Generated {
1507
  'mandatory' => true,
1508
  'value_url' => array(
1509
  'allow_relative' => true,
1510
- 'allowed_protocol' => array(
1511
  'https',
1512
  ),
1513
  ),
1514
  ),
1515
  ),
1516
  'tag_spec' => array(
 
 
 
 
 
1517
  'requires_extension' => array(
1518
  'amp-install-serviceworker',
1519
  ),
@@ -1529,10 +2604,21 @@ class AMP_Allowed_Tags_Generated {
1529
  ),
1530
  'media' => array(),
1531
  'noloading' => array(
1532
- 'value' => '',
 
 
1533
  ),
1534
  ),
1535
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
1536
  'requires_extension' => array(
1537
  'amp-izlesene',
1538
  ),
@@ -1554,6 +2640,16 @@ class AMP_Allowed_Tags_Generated {
1554
  ),
1555
  ),
1556
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1557
  'requires_extension' => array(
1558
  'amp-jwplayer',
1559
  ),
@@ -1568,10 +2664,22 @@ class AMP_Allowed_Tags_Generated {
1568
  ),
1569
  'media' => array(),
1570
  'noloading' => array(
1571
- 'value' => '',
 
 
1572
  ),
1573
  ),
1574
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1575
  'requires_extension' => array(
1576
  'amp-kaltura-player',
1577
  ),
@@ -1583,10 +2691,24 @@ class AMP_Allowed_Tags_Generated {
1583
  'attr_spec_list' => array(
1584
  'media' => array(),
1585
  'noloading' => array(
1586
- 'value' => '',
 
 
1587
  ),
1588
  ),
1589
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
 
1590
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-layout',
1591
  ),
1592
  ),
@@ -1594,15 +2716,30 @@ class AMP_Allowed_Tags_Generated {
1594
  'amp-lightbox' => array(
1595
  array(
1596
  'attr_spec_list' => array(
 
 
 
 
 
 
 
 
1597
  'controls' => array(),
1598
  'from' => array(),
1599
  'media' => array(),
1600
  'noloading' => array(
1601
- 'value' => '',
 
 
1602
  ),
1603
  'scrollable' => array(),
1604
  ),
1605
  'tag_spec' => array(
 
 
 
 
 
1606
  'requires_extension' => array(
1607
  'amp-lightbox',
1608
  ),
@@ -1614,23 +2751,40 @@ class AMP_Allowed_Tags_Generated {
1614
  'attr_spec_list' => array(
1615
  '[src]' => array(),
1616
  '[state]' => array(),
 
 
 
 
 
 
 
 
 
 
 
 
1617
  'credentials' => array(),
1618
  'items' => array(),
1619
  'max-items' => array(),
1620
  'media' => array(),
1621
  'noloading' => array(
1622
- 'value' => '',
 
 
1623
  ),
1624
  'reset-on-refresh' => array(
1625
- 'value' => '',
 
 
 
 
1626
  ),
1627
  'single-item' => array(),
1628
  'src' => array(
1629
  'blacklisted_value_regex' => '__amp_source_origin',
1630
- 'mandatory' => true,
1631
  'value_url' => array(
1632
  'allow_relative' => true,
1633
- 'allowed_protocol' => array(
1634
  'https',
1635
  ),
1636
  ),
@@ -1638,86 +2792,208 @@ class AMP_Allowed_Tags_Generated {
1638
  'template' => array(),
1639
  ),
1640
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1641
  'requires_extension' => array(
1642
  'amp-list',
1643
  ),
1644
  ),
1645
  ),
 
 
1646
  array(
1647
  'attr_spec_list' => array(
1648
- '[src]' => array(
1649
  'mandatory' => true,
 
1650
  ),
1651
- 'credentials' => array(),
1652
- 'items' => array(),
1653
- 'max-items' => array(),
1654
- 'media' => array(),
1655
- 'noloading' => array(
1656
- 'value' => '',
1657
  ),
1658
- 'single-item' => array(),
1659
- 'src' => array(
1660
- 'blacklisted_value_regex' => '__amp_source_origin',
1661
- 'value_url' => array(
1662
- 'allow_relative' => true,
1663
- 'allowed_protocol' => array(
1664
- 'https',
1665
- ),
 
 
 
1666
  ),
1667
  ),
1668
- 'template' => array(),
1669
  ),
1670
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1671
  'requires_extension' => array(
1672
- 'amp-list',
1673
  ),
1674
- 'spec_name' => 'AMP-LIST [SRC]',
1675
  ),
1676
  ),
1677
  ),
1678
- 'amp-live-list' => array(
1679
  array(
1680
  'attr_spec_list' => array(
1681
- 'data-max-items-per-page' => array(
1682
  'mandatory' => true,
1683
- 'value_regex' => '\\d+',
1684
  ),
1685
- 'data-poll-interval' => array(
1686
- 'value_regex' => '\\d{5,}',
 
 
 
 
1687
  ),
1688
- 'disabled' => array(
1689
- 'value' => '',
 
 
 
 
1690
  ),
1691
- 'id' => array(
 
 
 
 
 
 
 
 
 
 
1692
  'mandatory' => true,
1693
  ),
1694
- 'sort' => array(
1695
- 'value_regex' => 'ascending',
 
 
 
1696
  ),
1697
  ),
1698
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1699
  'requires_extension' => array(
1700
- 'amp-live-list',
1701
  ),
1702
  ),
1703
  ),
1704
  ),
1705
- 'amp-mathml' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1706
  array(
1707
  'attr_spec_list' => array(
1708
- 'data-formula' => array(
 
1709
  'mandatory' => true,
 
 
 
 
 
 
1710
  ),
1711
- 'inline' => array(),
1712
- 'media' => array(),
1713
- 'noloading' => array(
1714
- 'value' => '',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1715
  ),
1716
  ),
1717
  'tag_spec' => array(
 
 
 
 
 
 
1718
  'requires_extension' => array(
1719
- 'amp-mathml',
1720
  ),
 
 
 
1721
  ),
1722
  ),
1723
  ),
@@ -1732,26 +3008,48 @@ class AMP_Allowed_Tags_Generated {
1732
  'value_regex' => '[^=/?:]+',
1733
  ),
1734
  'data-mode' => array(
1735
- 'value_regex' => 'api|static',
 
 
 
1736
  ),
1737
  'data-origin' => array(
1738
  'value_url' => array(
1739
  'allow_empty' => true,
1740
- 'allowed_protocol' => array(
1741
  'https',
1742
  'http',
1743
  ),
1744
  ),
1745
  ),
1746
  'data-streamtype' => array(
1747
- 'value_regex' => 'album|audio|live|playlist|playlist-marked|video',
 
 
 
 
 
 
 
1748
  ),
1749
  'media' => array(),
1750
  'noloading' => array(
1751
- 'value' => '',
 
 
1752
  ),
1753
  ),
1754
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1755
  'requires_extension' => array(
1756
  'amp-nexxtv-player',
1757
  ),
@@ -1769,10 +3067,22 @@ class AMP_Allowed_Tags_Generated {
1769
  ),
1770
  'media' => array(),
1771
  'noloading' => array(
1772
- 'value' => '',
 
 
1773
  ),
1774
  ),
1775
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1776
  'requires_extension' => array(
1777
  'amp-o2-player',
1778
  ),
@@ -1793,16 +3103,104 @@ class AMP_Allowed_Tags_Generated {
1793
  ),
1794
  'media' => array(),
1795
  'noloading' => array(
1796
- 'value' => '',
 
 
1797
  ),
1798
  ),
1799
  'tag_spec' => array(
 
 
 
 
 
 
 
 
1800
  'requires_extension' => array(
1801
  'amp-ooyala-player',
1802
  ),
1803
  ),
1804
  ),
1805
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1806
  'amp-pinterest' => array(
1807
  array(
1808
  'attr_spec_list' => array(
@@ -1812,10 +3210,22 @@ class AMP_Allowed_Tags_Generated {
1812
  ),
1813
  'media' => array(),
1814
  'noloading' => array(
1815
- 'value' => '',
 
 
1816
  ),
1817
  ),
1818
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1819
  'requires_extension' => array(
1820
  'amp-pinterest',
1821
  ),
@@ -1829,10 +3239,14 @@ class AMP_Allowed_Tags_Generated {
1829
  'allow-ssr-img' => array(),
1830
  'media' => array(),
1831
  'noloading' => array(
1832
- 'value' => '',
 
 
1833
  ),
1834
  'referrerpolicy' => array(
1835
- 'value' => 'no-referrer',
 
 
1836
  ),
1837
  'src' => array(
1838
  'blacklisted_value_regex' => '__amp_source_origin',
@@ -1840,13 +3254,21 @@ class AMP_Allowed_Tags_Generated {
1840
  'value_url' => array(
1841
  'allow_empty' => true,
1842
  'allow_relative' => true,
1843
- 'allowed_protocol' => array(
1844
  'https',
1845
  ),
1846
  ),
1847
  ),
1848
  ),
1849
  'tag_spec' => array(
 
 
 
 
 
 
 
 
1850
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-pixel',
1851
  ),
1852
  ),
@@ -1855,22 +3277,39 @@ class AMP_Allowed_Tags_Generated {
1855
  array(
1856
  'attr_spec_list' => array(
1857
  'data-comments' => array(
1858
- 'value_regex_casei' => '(false|true)',
 
 
 
1859
  ),
1860
  'data-item' => array(),
1861
  'data-item-info' => array(
1862
- 'value_regex_casei' => '(false|true)',
 
 
 
1863
  ),
1864
  'data-share-buttons' => array(
1865
- 'value_regex_casei' => '(false|true)',
 
 
 
1866
  ),
1867
  'media' => array(),
1868
  'noloading' => array(
1869
- 'value' => '',
 
 
1870
  ),
1871
  'src' => array(),
1872
  ),
1873
  'tag_spec' => array(
 
 
 
 
 
 
1874
  'requires_extension' => array(
1875
  'amp-playbuzz',
1876
  ),
@@ -1885,7 +3324,14 @@ class AMP_Allowed_Tags_Generated {
1885
  ),
1886
  'media' => array(),
1887
  'noloading' => array(
1888
- 'value' => '',
 
 
 
 
 
 
 
1889
  ),
1890
  'target' => array(),
1891
  'viewport-margins' => array(
@@ -1893,12 +3339,59 @@ class AMP_Allowed_Tags_Generated {
1893
  ),
1894
  ),
1895
  'tag_spec' => array(
 
 
 
 
 
1896
  'requires_extension' => array(
1897
  'amp-position-observer',
1898
  ),
1899
  ),
1900
  ),
1901
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1902
  'amp-reach-player' => array(
1903
  array(
1904
  'attr_spec_list' => array(
@@ -1908,10 +3401,21 @@ class AMP_Allowed_Tags_Generated {
1908
  ),
1909
  'media' => array(),
1910
  'noloading' => array(
1911
- 'value' => '',
 
 
1912
  ),
1913
  ),
1914
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
1915
  'requires_extension' => array(
1916
  'amp-reach-player',
1917
  ),
@@ -1922,24 +3426,45 @@ class AMP_Allowed_Tags_Generated {
1922
  array(
1923
  'attr_spec_list' => array(
1924
  'data-embedlive' => array(
1925
- 'value_regex_casei' => '(false|true)',
 
 
 
1926
  ),
1927
  'data-embedparent' => array(
1928
- 'value_regex_casei' => '(false|true)',
 
 
 
1929
  ),
1930
  'data-embedtype' => array(
1931
  'mandatory' => true,
1932
- 'value_regex_casei' => '(comment|post)',
 
 
 
1933
  ),
1934
  'data-src' => array(
1935
  'mandatory' => true,
1936
  ),
1937
  'media' => array(),
1938
  'noloading' => array(
1939
- 'value' => '',
 
 
1940
  ),
1941
  ),
1942
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1943
  'requires_extension' => array(
1944
  'amp-reddit',
1945
  ),
@@ -1955,10 +3480,17 @@ class AMP_Allowed_Tags_Generated {
1955
  ),
1956
  'media' => array(),
1957
  'noloading' => array(
1958
- 'value' => '',
 
 
1959
  ),
1960
  ),
1961
  'tag_spec' => array(
 
 
 
 
 
1962
  'requires_extension' => array(
1963
  'amp-riddle-quiz',
1964
  ),
@@ -1972,27 +3504,57 @@ class AMP_Allowed_Tags_Generated {
1972
  '[disabled]' => array(),
1973
  '[selected]' => array(),
1974
  'disabled' => array(
1975
- 'value' => '',
 
 
1976
  ),
1977
  'form' => array(),
1978
  'keyboard-select-mode' => array(
1979
- 'value_regex_casei' => 'focus|none|select',
 
 
 
 
1980
  ),
1981
  'media' => array(),
1982
  'multiple' => array(
1983
- 'value' => '',
 
 
1984
  ),
1985
  'name' => array(
1986
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
1987
  ),
1988
  'noloading' => array(
1989
- 'value' => '',
 
 
1990
  ),
1991
  ),
1992
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
1993
  'disallowed_ancestor' => array(
1994
  'amp-selector',
1995
  ),
 
 
 
 
 
 
 
 
 
 
1996
  'requires_extension' => array(
1997
  'amp-selector',
1998
  ),
@@ -2004,13 +3566,23 @@ class AMP_Allowed_Tags_Generated {
2004
  'attr_spec_list' => array(
2005
  'media' => array(),
2006
  'noloading' => array(
2007
- 'value' => '',
 
 
2008
  ),
2009
  'side' => array(
2010
- 'value_regex' => '(left|right)',
 
 
 
2011
  ),
2012
  ),
2013
  'tag_spec' => array(
 
 
 
 
 
2014
  'mandatory_parent' => 'body',
2015
  'requires_extension' => array(
2016
  'amp-sidebar',
@@ -2018,6 +3590,43 @@ class AMP_Allowed_Tags_Generated {
2018
  ),
2019
  ),
2020
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2021
  'amp-social-share' => array(
2022
  array(
2023
  'attr_spec_list' => array(
@@ -2025,7 +3634,7 @@ class AMP_Allowed_Tags_Generated {
2025
  'blacklisted_value_regex' => '__amp_source_origin',
2026
  'value_url' => array(
2027
  'allow_relative' => false,
2028
- 'allowed_protocol' => array(
2029
  'ftp',
2030
  'http',
2031
  'https',
@@ -2047,13 +3656,26 @@ class AMP_Allowed_Tags_Generated {
2047
  ),
2048
  'media' => array(),
2049
  'noloading' => array(
2050
- 'value' => '',
 
 
2051
  ),
2052
  'type' => array(
2053
  'mandatory' => true,
2054
  ),
2055
  ),
2056
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
2057
  'requires_extension' => array(
2058
  'amp-social-share',
2059
  ),
@@ -2076,14 +3698,24 @@ class AMP_Allowed_Tags_Generated {
2076
  'value_regex' => '[0-9]+',
2077
  ),
2078
  'data-visual' => array(
2079
- 'value_regex' => 'true|false',
 
 
 
2080
  ),
2081
  'media' => array(),
2082
  'noloading' => array(
2083
- 'value' => '',
 
 
2084
  ),
2085
  ),
2086
  'tag_spec' => array(
 
 
 
 
 
2087
  'requires_extension' => array(
2088
  'amp-soundcloud',
2089
  ),
@@ -2104,7 +3736,10 @@ class AMP_Allowed_Tags_Generated {
2104
  ),
2105
  'data-mode' => array(
2106
  'mandatory' => true,
2107
- 'value_regex_casei' => 'playlist|video',
 
 
 
2108
  ),
2109
  'data-player-id' => array(
2110
  'mandatory' => true,
@@ -2116,10 +3751,20 @@ class AMP_Allowed_Tags_Generated {
2116
  ),
2117
  'media' => array(),
2118
  'noloading' => array(
2119
- 'value' => '',
 
 
2120
  ),
2121
  ),
2122
  'tag_spec' => array(
 
 
 
 
 
 
 
 
2123
  'requires_extension' => array(
2124
  'amp-springboard-player',
2125
  ),
@@ -2139,7 +3784,7 @@ class AMP_Allowed_Tags_Generated {
2139
  'blacklisted_value_regex' => '__amp_source_origin',
2140
  'value_url' => array(
2141
  'allow_relative' => true,
2142
- 'allowed_protocol' => array(
2143
  'https',
2144
  ),
2145
  ),
@@ -2159,10 +3804,17 @@ class AMP_Allowed_Tags_Generated {
2159
  'attr_spec_list' => array(
2160
  'media' => array(),
2161
  'noloading' => array(
2162
- 'value' => '',
 
 
2163
  ),
2164
  ),
2165
  'tag_spec' => array(
 
 
 
 
 
2166
  'disallowed_ancestor' => array(
2167
  'amp-app-banner',
2168
  ),
@@ -2178,7 +3830,7 @@ class AMP_Allowed_Tags_Generated {
2178
  'attr_spec_list' => array(
2179
  'background-audio' => array(
2180
  'value_url' => array(
2181
- 'allowed_protocol' => array(
2182
  'http',
2183
  'https',
2184
  ),
@@ -2186,15 +3838,57 @@ class AMP_Allowed_Tags_Generated {
2186
  ),
2187
  'bookend-config-src' => array(
2188
  'value_url' => array(
2189
- 'allowed_protocol' => array(
2190
  'http',
2191
  'https',
2192
  ),
2193
  ),
2194
  ),
2195
- 'standalone' => array(
2196
- 'mandatory' => true,
2197
- 'value' => '',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2198
  ),
2199
  ),
2200
  'tag_spec' => array(
@@ -2205,6 +3899,30 @@ class AMP_Allowed_Tags_Generated {
2205
  ),
2206
  ),
2207
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2208
  'amp-story-auto-ads' => array(
2209
  array(
2210
  'attr_spec_list' => array(),
@@ -2218,11 +3936,61 @@ class AMP_Allowed_Tags_Generated {
2218
  ),
2219
  ),
2220
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2221
  'amp-story-cta-layer' => array(
2222
  array(
2223
  'attr_spec_list' => array(),
2224
  'tag_spec' => array(
2225
  'mandatory_ancestor' => 'amp-story-page',
 
 
 
 
 
 
2226
  ),
2227
  ),
2228
  ),
@@ -2231,11 +3999,26 @@ class AMP_Allowed_Tags_Generated {
2231
  'attr_spec_list' => array(
2232
  'template' => array(
2233
  'mandatory' => true,
2234
- 'value_regex' => '(fill|horizontal|vertical|thirds)',
 
 
 
 
 
2235
  ),
2236
  ),
2237
  'tag_spec' => array(
2238
  'mandatory_ancestor' => 'amp-story-page',
 
 
 
 
 
 
 
 
 
 
2239
  ),
2240
  ),
2241
  ),
@@ -2245,7 +4028,7 @@ class AMP_Allowed_Tags_Generated {
2245
  'auto-advance-after' => array(),
2246
  'background-audio' => array(
2247
  'value_url' => array(
2248
- 'allowed_protocol' => array(
2249
  'http',
2250
  'https',
2251
  ),
@@ -2266,6 +4049,8 @@ class AMP_Allowed_Tags_Generated {
2266
  'amp-timeago' => array(
2267
  array(
2268
  'attr_spec_list' => array(
 
 
2269
  'cutoff' => array(
2270
  'value_regex' => '\\d+',
2271
  ),
@@ -2276,10 +4061,19 @@ class AMP_Allowed_Tags_Generated {
2276
  'locale' => array(),
2277
  'media' => array(),
2278
  'noloading' => array(
2279
- 'value' => '',
 
 
2280
  ),
2281
  ),
2282
  'tag_spec' => array(
 
 
 
 
 
 
 
2283
  'requires_extension' => array(
2284
  'amp-timeago',
2285
  ),
@@ -2290,15 +4084,53 @@ class AMP_Allowed_Tags_Generated {
2290
  'amp-twitter' => array(
2291
  array(
2292
  'attr_spec_list' => array(
2293
- 'data-tweetid' => array(
2294
- 'mandatory' => true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2295
  ),
 
2296
  'media' => array(),
2297
  'noloading' => array(
2298
- 'value' => '',
 
 
2299
  ),
2300
  ),
2301
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
 
2302
  'requires_extension' => array(
2303
  'amp-twitter',
2304
  ),
@@ -2312,7 +4144,7 @@ class AMP_Allowed_Tags_Generated {
2312
  'value_url' => array(
2313
  'allow_empty' => false,
2314
  'allow_relative' => false,
2315
- 'allowed_protocol' => array(
2316
  'https',
2317
  ),
2318
  ),
@@ -2321,20 +4153,29 @@ class AMP_Allowed_Tags_Generated {
2321
  'value_url' => array(
2322
  'allow_empty' => false,
2323
  'allow_relative' => false,
2324
- 'allowed_protocol' => array(
2325
  'https',
2326
  ),
2327
  ),
2328
  ),
2329
  'enctype' => array(
2330
- 'value' => 'application/x-www-form-urlencoded',
 
 
2331
  ),
2332
  'media' => array(),
2333
  'noloading' => array(
2334
- 'value' => '',
 
 
2335
  ),
2336
  ),
2337
  'tag_spec' => array(
 
 
 
 
 
2338
  'requires_extension' => array(
2339
  'amp-user-notification',
2340
  ),
@@ -2362,43 +4203,68 @@ class AMP_Allowed_Tags_Generated {
2362
  'artwork' => array(),
2363
  'attribution' => array(),
2364
  'autoplay' => array(
2365
- 'value' => '',
 
 
2366
  ),
2367
  'controls' => array(
2368
- 'value' => '',
 
 
2369
  ),
2370
  'controlslist' => array(),
2371
  'crossorigin' => array(),
2372
  'disableremoteplayback' => array(
2373
- 'value' => '',
 
 
2374
  ),
 
2375
  'lightbox' => array(),
2376
- 'lightbox-exclude' => array(
2377
- 'value' => '',
2378
- ),
2379
  'lightbox-thumbnail-id' => array(
2380
  'value_regex_casei' => '^[a-z][a-z\\d_-]*',
2381
  ),
2382
  'loop' => array(
2383
- 'value' => '',
 
 
2384
  ),
2385
  'media' => array(),
2386
  'muted' => array(
2387
- 'value' => '',
 
 
 
 
 
 
 
2388
  ),
2389
  'noloading' => array(
2390
- 'value' => '',
 
 
2391
  ),
2392
  'placeholder' => array(),
2393
  'poster' => array(),
2394
  'preload' => array(
2395
- 'value_regex' => '(none|metadata|auto|)',
 
 
 
 
 
 
 
 
 
 
2396
  ),
2397
  'src' => array(
2398
  'blacklisted_value_regex' => '__amp_source_origin',
2399
  'value_url' => array(
2400
  'allow_relative' => true,
2401
- 'allowed_protocol' => array(
2402
  'https',
2403
  ),
2404
  ),
@@ -2408,6 +4274,16 @@ class AMP_Allowed_Tags_Generated {
2408
  'also_requires_tag_warning' => array(
2409
  'amp-video extension .js script',
2410
  ),
 
 
 
 
 
 
 
 
 
 
2411
  'disallowed_ancestor' => array(
2412
  'amp-story',
2413
  ),
@@ -2434,44 +4310,82 @@ class AMP_Allowed_Tags_Generated {
2434
  'artwork' => array(),
2435
  'attribution' => array(),
2436
  'autoplay' => array(
2437
- 'value' => '',
 
 
2438
  ),
2439
  'controls' => array(
2440
- 'value' => '',
 
 
2441
  ),
2442
  'controlslist' => array(),
2443
  'crossorigin' => array(),
2444
  'disableremoteplayback' => array(
2445
- 'value' => '',
 
 
2446
  ),
 
2447
  'loop' => array(
2448
- 'value' => '',
 
 
2449
  ),
2450
  'media' => array(),
2451
  'muted' => array(
2452
- 'value' => '',
 
 
 
 
 
 
 
2453
  ),
2454
  'noloading' => array(
2455
- 'value' => '',
 
 
2456
  ),
2457
  'placeholder' => array(),
2458
  'poster' => array(
2459
  'mandatory' => true,
2460
  ),
2461
  'preload' => array(
2462
- 'value_regex' => '(none|metadata|auto|)',
 
 
 
 
 
 
 
 
 
 
2463
  ),
2464
  'src' => array(
2465
  'blacklisted_value_regex' => '__amp_source_origin',
2466
  'value_url' => array(
2467
  'allow_relative' => true,
2468
- 'allowed_protocol' => array(
2469
  'https',
2470
  ),
2471
  ),
2472
  ),
2473
  ),
2474
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
2475
  'mandatory_ancestor' => 'amp-story',
2476
  'requires_extension' => array(
2477
  'amp-video',
@@ -2484,16 +4398,32 @@ class AMP_Allowed_Tags_Generated {
2484
  'amp-vimeo' => array(
2485
  array(
2486
  'attr_spec_list' => array(
 
 
 
 
 
2487
  'data-videoid' => array(
2488
  'mandatory' => true,
2489
  'value_regex' => '[0-9]+',
2490
  ),
2491
  'media' => array(),
2492
  'noloading' => array(
2493
- 'value' => '',
 
 
2494
  ),
2495
  ),
2496
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
2497
  'requires_extension' => array(
2498
  'amp-vimeo',
2499
  ),
@@ -2508,16 +4438,62 @@ class AMP_Allowed_Tags_Generated {
2508
  ),
2509
  'media' => array(),
2510
  'noloading' => array(
2511
- 'value' => '',
 
 
2512
  ),
2513
  ),
2514
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
2515
  'requires_extension' => array(
2516
  'amp-vine',
2517
  ),
2518
  ),
2519
  ),
2520
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2521
  'amp-vk' => array(
2522
  array(
2523
  'attr_spec_list' => array(
@@ -2526,10 +4502,19 @@ class AMP_Allowed_Tags_Generated {
2526
  ),
2527
  'media' => array(),
2528
  'noloading' => array(
2529
- 'value' => '',
 
 
2530
  ),
2531
  ),
2532
  'tag_spec' => array(
 
 
 
 
 
 
 
2533
  'requires_extension' => array(
2534
  'amp-vk',
2535
  ),
@@ -2543,24 +4528,28 @@ class AMP_Allowed_Tags_Generated {
2543
  'mandatory' => true,
2544
  'value_url' => array(
2545
  'allow_relative' => false,
2546
- 'allowed_protocol' => array(
2547
  'https',
2548
  ),
2549
  ),
2550
  ),
2551
  'id' => array(
2552
  'mandatory' => true,
2553
- 'value_regex' => 'amp-web-push',
 
 
2554
  ),
2555
  'media' => array(),
2556
  'noloading' => array(
2557
- 'value' => '',
 
 
2558
  ),
2559
  'permission-dialog-url' => array(
2560
  'mandatory' => true,
2561
  'value_url' => array(
2562
  'allow_relative' => false,
2563
- 'allowed_protocol' => array(
2564
  'https',
2565
  ),
2566
  ),
@@ -2569,13 +4558,18 @@ class AMP_Allowed_Tags_Generated {
2569
  'mandatory' => true,
2570
  'value_url' => array(
2571
  'allow_relative' => false,
2572
- 'allowed_protocol' => array(
2573
  'https',
2574
  ),
2575
  ),
2576
  ),
2577
  ),
2578
  'tag_spec' => array(
 
 
 
 
 
2579
  'requires_extension' => array(
2580
  'amp-web-push',
2581
  ),
@@ -2588,14 +4582,25 @@ class AMP_Allowed_Tags_Generated {
2588
  'attr_spec_list' => array(
2589
  'media' => array(),
2590
  'noloading' => array(
2591
- 'value' => '',
 
 
2592
  ),
2593
  'visibility' => array(
2594
  'mandatory' => true,
2595
- 'value_regex' => '(blocked|subscribed|unsubscribed)',
 
 
 
 
2596
  ),
2597
  ),
2598
  'tag_spec' => array(
 
 
 
 
 
2599
  'requires_extension' => array(
2600
  'amp-web-push',
2601
  ),
@@ -2612,23 +4617,76 @@ class AMP_Allowed_Tags_Generated {
2612
  ),
2613
  'media' => array(),
2614
  'noloading' => array(
2615
- 'value' => '',
 
 
 
 
 
 
 
2616
  ),
2617
  ),
2618
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
2619
  'requires_extension' => array(
2620
  'amp-wistia-player',
2621
  ),
2622
  ),
2623
  ),
2624
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2625
  'amp-youtube' => array(
2626
  array(
2627
  'attr_spec_list' => array(
2628
  '[data-videoid]' => array(),
2629
  'autoplay' => array(),
2630
  'credentials' => array(
2631
- 'value_regex_casei' => '(include|omit)',
 
 
 
2632
  ),
2633
  'data-live-channelid' => array(
2634
  'value_regex' => '[^=/?:]+',
@@ -2637,18 +4695,27 @@ class AMP_Allowed_Tags_Generated {
2637
  'value_regex' => '[^=/?:]+',
2638
  ),
2639
  'lightbox' => array(),
2640
- 'lightbox-exclude' => array(
2641
- 'value' => '',
2642
- ),
2643
  'lightbox-thumbnail-id' => array(
2644
  'value_regex_casei' => '^[a-z][a-z\\d_-]*',
2645
  ),
2646
  'media' => array(),
2647
  'noloading' => array(
2648
- 'value' => '',
 
 
2649
  ),
2650
  ),
2651
  'tag_spec' => array(
 
 
 
 
 
 
 
 
 
 
2652
  'requires_extension' => array(
2653
  'amp-youtube',
2654
  ),
@@ -2679,7 +4746,7 @@ class AMP_Allowed_Tags_Generated {
2679
  'blacklisted_value_regex' => '__amp_source_origin',
2680
  'value_url' => array(
2681
  'allow_relative' => false,
2682
- 'allowed_protocol' => array(
2683
  'data',
2684
  'https',
2685
  ),
@@ -2703,10 +4770,16 @@ class AMP_Allowed_Tags_Generated {
2703
  array(
2704
  'attr_spec_list' => array(
2705
  'href' => array(
2706
- 'value' => '/',
 
 
2707
  ),
2708
  'target' => array(
2709
- 'value_regex_casei' => '(_blank|_self|_top)',
 
 
 
 
2710
  ),
2711
  ),
2712
  'tag_spec' => array(
@@ -2743,8 +4816,7 @@ class AMP_Allowed_Tags_Generated {
2743
  'blacklisted_value_regex' => '__amp_source_origin',
2744
  'value_url' => array(
2745
  'allow_empty' => true,
2746
- 'allow_relative' => true,
2747
- 'allowed_protocol' => array(
2748
  'http',
2749
  'https',
2750
  ),
@@ -2778,7 +4850,9 @@ class AMP_Allowed_Tags_Generated {
2778
  '[type]' => array(),
2779
  '[value]' => array(),
2780
  'disabled' => array(
2781
- 'value' => '',
 
 
2782
  ),
2783
  'name' => array(
2784
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
@@ -2796,7 +4870,9 @@ class AMP_Allowed_Tags_Generated {
2796
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
2797
  ),
2798
  'open-button' => array(
2799
- 'value' => '',
 
 
2800
  ),
2801
  'role' => array(),
2802
  'tabindex' => array(),
@@ -2848,6 +4924,7 @@ class AMP_Allowed_Tags_Generated {
2848
  'filter' => array(),
2849
  'flood-color' => array(),
2850
  'flood-opacity' => array(),
 
2851
  'font-family' => array(),
2852
  'font-size' => array(),
2853
  'font-size-adjust' => array(),
@@ -2939,6 +5016,7 @@ class AMP_Allowed_Tags_Generated {
2939
  'filter' => array(),
2940
  'flood-color' => array(),
2941
  'flood-opacity' => array(),
 
2942
  'font-family' => array(),
2943
  'font-size' => array(),
2944
  'font-size-adjust' => array(),
@@ -3063,6 +5141,7 @@ class AMP_Allowed_Tags_Generated {
3063
  'filter' => array(),
3064
  'flood-color' => array(),
3065
  'flood-opacity' => array(),
 
3066
  'font-family' => array(),
3067
  'font-size' => array(),
3068
  'font-size-adjust' => array(),
@@ -3127,8 +5206,7 @@ class AMP_Allowed_Tags_Generated {
3127
  'blacklisted_value_regex' => '__amp_source_origin',
3128
  'value_url' => array(
3129
  'allow_empty' => true,
3130
- 'allow_relative' => true,
3131
- 'allowed_protocol' => array(
3132
  'http',
3133
  'https',
3134
  ),
@@ -3175,17 +5253,58 @@ class AMP_Allowed_Tags_Generated {
3175
  ),
3176
  'tag_spec' => array(),
3177
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3178
  array(
3179
  'attr_spec_list' => array(
3180
  'align' => array(),
3181
  'submitting' => array(
3182
- 'dispatch_key' => 1,
3183
  'mandatory' => true,
3184
  ),
3185
  ),
3186
  'tag_spec' => array(
3187
- 'mandatory_parent' => 'form',
3188
- 'spec_name' => 'FORM > DIV [submitting]',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3189
  ),
3190
  ),
3191
  array(
@@ -3196,8 +5315,8 @@ class AMP_Allowed_Tags_Generated {
3196
  ),
3197
  ),
3198
  'tag_spec' => array(
3199
- 'mandatory_parent' => 'form',
3200
- 'spec_name' => 'FORM > DIV [submit-success]',
3201
  ),
3202
  ),
3203
  array(
@@ -3211,8 +5330,8 @@ class AMP_Allowed_Tags_Generated {
3211
  ),
3212
  ),
3213
  'tag_spec' => array(
3214
- 'mandatory_parent' => 'form',
3215
- 'spec_name' => 'FORM > DIV [submit-success][template]',
3216
  ),
3217
  ),
3218
  array(
@@ -3223,8 +5342,8 @@ class AMP_Allowed_Tags_Generated {
3223
  ),
3224
  ),
3225
  'tag_spec' => array(
3226
- 'mandatory_parent' => 'form',
3227
- 'spec_name' => 'FORM > DIV [submit-error]',
3228
  ),
3229
  ),
3230
  array(
@@ -3238,12 +5357,36 @@ class AMP_Allowed_Tags_Generated {
3238
  ),
3239
  ),
3240
  'tag_spec' => array(
3241
- 'mandatory_parent' => 'form',
3242
- 'spec_name' => 'FORM > DIV [submit-error][template]',
3243
  ),
3244
  ),
3245
- ),
3246
- 'dl' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3247
  array(
3248
  'attr_spec_list' => array(),
3249
  'tag_spec' => array(),
@@ -3282,6 +5425,7 @@ class AMP_Allowed_Tags_Generated {
3282
  'filter' => array(),
3283
  'flood-color' => array(),
3284
  'flood-opacity' => array(),
 
3285
  'font-family' => array(),
3286
  'font-size' => array(),
3287
  'font-size-adjust' => array(),
@@ -3372,6 +5516,7 @@ class AMP_Allowed_Tags_Generated {
3372
  'filter' => array(),
3373
  'flood-color' => array(),
3374
  'flood-opacity' => array(),
 
3375
  'font-family' => array(),
3376
  'font-size' => array(),
3377
  'font-size-adjust' => array(),
@@ -3457,6 +5602,7 @@ class AMP_Allowed_Tags_Generated {
3457
  'filter' => array(),
3458
  'flood-color' => array(),
3459
  'flood-opacity' => array(),
 
3460
  'font-family' => array(),
3461
  'font-size' => array(),
3462
  'font-size-adjust' => array(),
@@ -3546,6 +5692,7 @@ class AMP_Allowed_Tags_Generated {
3546
  'filter' => array(),
3547
  'flood-color' => array(),
3548
  'flood-opacity' => array(),
 
3549
  'font-family' => array(),
3550
  'font-size' => array(),
3551
  'font-size-adjust' => array(),
@@ -3629,6 +5776,7 @@ class AMP_Allowed_Tags_Generated {
3629
  'filter' => array(),
3630
  'flood-color' => array(),
3631
  'flood-opacity' => array(),
 
3632
  'font-family' => array(),
3633
  'font-size' => array(),
3634
  'font-size-adjust' => array(),
@@ -3713,6 +5861,7 @@ class AMP_Allowed_Tags_Generated {
3713
  'filter' => array(),
3714
  'flood-color' => array(),
3715
  'flood-opacity' => array(),
 
3716
  'font-family' => array(),
3717
  'font-size' => array(),
3718
  'font-size-adjust' => array(),
@@ -3815,6 +5964,7 @@ class AMP_Allowed_Tags_Generated {
3815
  'filter' => array(),
3816
  'flood-color' => array(),
3817
  'flood-opacity' => array(),
 
3818
  'font-family' => array(),
3819
  'font-size' => array(),
3820
  'font-size-adjust' => array(),
@@ -3925,6 +6075,7 @@ class AMP_Allowed_Tags_Generated {
3925
  'filterunits' => array(),
3926
  'flood-color' => array(),
3927
  'flood-opacity' => array(),
 
3928
  'font-family' => array(),
3929
  'font-size' => array(),
3930
  'font-size-adjust' => array(),
@@ -3979,8 +6130,7 @@ class AMP_Allowed_Tags_Generated {
3979
  ),
3980
  'value_url' => array(
3981
  'allow_empty' => false,
3982
- 'allow_relative' => true,
3983
- 'allowed_protocol' => array(
3984
  'http',
3985
  'https',
3986
  ),
@@ -4008,92 +6158,6 @@ class AMP_Allowed_Tags_Generated {
4008
  'tag_spec' => array(),
4009
  ),
4010
  ),
4011
- 'foreignobject' => array(
4012
- array(
4013
- 'attr_spec_list' => array(
4014
- 'alignment-baseline' => array(),
4015
- 'baseline-shift' => array(),
4016
- 'clip' => array(),
4017
- 'clip-path' => array(),
4018
- 'clip-rule' => array(),
4019
- 'color' => array(),
4020
- 'color-interpolation' => array(),
4021
- 'color-interpolation-filters' => array(),
4022
- 'color-profile' => array(),
4023
- 'color-rendering' => array(),
4024
- 'cursor' => array(),
4025
- 'direction' => array(),
4026
- 'display' => array(),
4027
- 'dominant-baseline' => array(),
4028
- 'enable-background' => array(),
4029
- 'externalresourcesrequired' => array(),
4030
- 'fill' => array(),
4031
- 'fill-opacity' => array(),
4032
- 'fill-rule' => array(),
4033
- 'filter' => array(),
4034
- 'flood-color' => array(),
4035
- 'flood-opacity' => array(),
4036
- 'font-family' => array(),
4037
- 'font-size' => array(),
4038
- 'font-size-adjust' => array(),
4039
- 'font-stretch' => array(),
4040
- 'font-style' => array(),
4041
- 'font-variant' => array(),
4042
- 'font-weight' => array(),
4043
- 'glyph-orientation-horizontal' => array(),
4044
- 'glyph-orientation-vertical' => array(),
4045
- 'height' => array(),
4046
- 'image-rendering' => array(),
4047
- 'kerning' => array(),
4048
- 'letter-spacing' => array(),
4049
- 'lighting-color' => array(),
4050
- 'marker-end' => array(),
4051
- 'marker-mid' => array(),
4052
- 'marker-start' => array(),
4053
- 'mask' => array(),
4054
- 'opacity' => array(),
4055
- 'overflow' => array(),
4056
- 'pointer-events' => array(),
4057
- 'requiredextensions' => array(),
4058
- 'requiredfeatures' => array(),
4059
- 'shape-rendering' => array(),
4060
- 'stop-color' => array(),
4061
- 'stop-opacity' => array(),
4062
- 'stroke' => array(),
4063
- 'stroke-dasharray' => array(),
4064
- 'stroke-dashoffset' => array(),
4065
- 'stroke-linecap' => array(),
4066
- 'stroke-linejoin' => array(),
4067
- 'stroke-miterlimit' => array(),
4068
- 'stroke-opacity' => array(),
4069
- 'stroke-width' => array(),
4070
- 'style' => array(
4071
- 'blacklisted_value_regex' => '!important',
4072
- ),
4073
- 'systemlanguage' => array(),
4074
- 'text-anchor' => array(),
4075
- 'text-decoration' => array(),
4076
- 'text-rendering' => array(),
4077
- 'transform' => array(),
4078
- 'unicode-bidi' => array(),
4079
- 'vector-effect' => array(),
4080
- 'visibility' => array(),
4081
- 'width' => array(),
4082
- 'word-spacing' => array(),
4083
- 'writing-mode' => array(),
4084
- 'x' => array(),
4085
- 'xml:lang' => array(),
4086
- 'xml:space' => array(),
4087
- 'xmlns' => array(),
4088
- 'xmlns:xlink' => array(),
4089
- 'y' => array(),
4090
- ),
4091
- 'tag_spec' => array(
4092
- 'mandatory_ancestor' => 'svg',
4093
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4094
- ),
4095
- ),
4096
- ),
4097
  'form' => array(
4098
  array(
4099
  'attr_spec_list' => array(
@@ -4103,8 +6167,7 @@ class AMP_Allowed_Tags_Generated {
4103
  'blacklisted_value_regex' => '__amp_source_origin',
4104
  'mandatory' => true,
4105
  'value_url' => array(
4106
- 'allow_relative' => true,
4107
- 'allowed_protocol' => array(
4108
  'https',
4109
  ),
4110
  ),
@@ -4112,31 +6175,39 @@ class AMP_Allowed_Tags_Generated {
4112
  'action-xhr' => array(
4113
  'blacklisted_value_regex' => '__amp_source_origin',
4114
  'value_url' => array(
4115
- 'allow_relative' => true,
4116
- 'allowed_protocol' => array(
4117
  'https',
4118
  ),
4119
  ),
4120
  ),
4121
  'autocomplete' => array(),
4122
  'custom-validation-reporting' => array(
4123
- 'value_regex' => '(show-first-on-submit|show-all-on-submit|as-you-go|interact-and-submit)',
 
 
 
 
 
4124
  ),
4125
  'enctype' => array(),
4126
  'method' => array(
4127
- 'value_casei' => 'get',
 
 
4128
  ),
4129
  'name' => array(),
4130
  'novalidate' => array(),
4131
  'target' => array(
4132
  'mandatory' => true,
4133
- 'value_regex_casei' => '(_blank|_top)',
 
 
 
4134
  ),
4135
  'verify-xhr' => array(
4136
  'blacklisted_value_regex' => '__amp_source_origin',
4137
  'value_url' => array(
4138
- 'allow_relative' => true,
4139
- 'allowed_protocol' => array(
4140
  'https',
4141
  ),
4142
  ),
@@ -4160,33 +6231,40 @@ class AMP_Allowed_Tags_Generated {
4160
  'blacklisted_value_regex' => '__amp_source_origin',
4161
  'mandatory' => true,
4162
  'value_url' => array(
4163
- 'allow_relative' => true,
4164
- 'allowed_protocol' => array(
4165
  'https',
4166
  ),
4167
  ),
4168
  ),
4169
  'autocomplete' => array(),
4170
  'custom-validation-reporting' => array(
4171
- 'value_regex' => '(show-first-on-submit|show-all-on-submit|as-you-go)',
 
 
 
 
 
4172
  ),
4173
  'enctype' => array(),
4174
  'method' => array(
4175
  'dispatch_key' => 2,
4176
  'mandatory' => true,
4177
- 'value_casei' => 'post',
 
 
4178
  ),
4179
  'name' => array(),
4180
  'novalidate' => array(),
4181
  'target' => array(
4182
- 'mandatory' => true,
4183
- 'value_regex_casei' => '(_blank|_top)',
 
 
4184
  ),
4185
  'verify-xhr' => array(
4186
  'blacklisted_value_regex' => '__amp_source_origin',
4187
  'value_url' => array(
4188
- 'allow_relative' => true,
4189
- 'allowed_protocol' => array(
4190
  'https',
4191
  ),
4192
  ),
@@ -4228,6 +6306,7 @@ class AMP_Allowed_Tags_Generated {
4228
  'filter' => array(),
4229
  'flood-color' => array(),
4230
  'flood-opacity' => array(),
 
4231
  'font-family' => array(),
4232
  'font-size' => array(),
4233
  'font-size-adjust' => array(),
@@ -4311,6 +6390,7 @@ class AMP_Allowed_Tags_Generated {
4311
  'filter' => array(),
4312
  'flood-color' => array(),
4313
  'flood-opacity' => array(),
 
4314
  'font-family' => array(),
4315
  'font-size' => array(),
4316
  'font-size-adjust' => array(),
@@ -4397,6 +6477,7 @@ class AMP_Allowed_Tags_Generated {
4397
  'filter' => array(),
4398
  'flood-color' => array(),
4399
  'flood-opacity' => array(),
 
4400
  'font-family' => array(),
4401
  'font-size' => array(),
4402
  'font-size-adjust' => array(),
@@ -4450,8 +6531,7 @@ class AMP_Allowed_Tags_Generated {
4450
  ),
4451
  'value_url' => array(
4452
  'allow_empty' => false,
4453
- 'allow_relative' => true,
4454
- 'allowed_protocol' => array(
4455
  'http',
4456
  'https',
4457
  ),
@@ -4574,19 +6654,10 @@ class AMP_Allowed_Tags_Generated {
4574
  ),
4575
  'html' => array(
4576
  array(
4577
- 'attr_spec_list' => array(
4578
- '\\u26a1' => array(
4579
- 'alternative_names' => array(
4580
- 'amp',
4581
- ),
4582
- 'mandatory' => true,
4583
- 'value' => '',
4584
- ),
4585
- ),
4586
  'tag_spec' => array(
4587
  'mandatory' => true,
4588
  'mandatory_parent' => '!doctype',
4589
- 'spec_name' => 'html \\u26a1 for top-level html',
4590
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
4591
  'unique' => true,
4592
  ),
@@ -4602,23 +6673,32 @@ class AMP_Allowed_Tags_Generated {
4602
  array(
4603
  'attr_spec_list' => array(
4604
  'frameborder' => array(
4605
- 'value_regex' => '0|1',
 
 
 
4606
  ),
4607
  'height' => array(),
4608
  'name' => array(),
4609
  'referrerpolicy' => array(),
4610
  'resizable' => array(
4611
- 'value' => '',
 
 
4612
  ),
4613
  'sandbox' => array(),
4614
  'scrolling' => array(
4615
- 'value_regex' => 'auto|yes|no',
 
 
 
 
4616
  ),
4617
  'src' => array(
4618
  'blacklisted_value_regex' => '__amp_source_origin',
4619
  'value_url' => array(
4620
  'allow_relative' => false,
4621
- 'allowed_protocol' => array(
4622
  'data',
4623
  'https',
4624
  ),
@@ -4659,6 +6739,7 @@ class AMP_Allowed_Tags_Generated {
4659
  'filter' => array(),
4660
  'flood-color' => array(),
4661
  'flood-opacity' => array(),
 
4662
  'font-family' => array(),
4663
  'font-size' => array(),
4664
  'font-size-adjust' => array(),
@@ -4718,8 +6799,7 @@ class AMP_Allowed_Tags_Generated {
4718
  'blacklisted_value_regex' => '(^|\\s)data:image\\/svg\\+xml',
4719
  'value_url' => array(
4720
  'allow_empty' => false,
4721
- 'allow_relative' => true,
4722
- 'allowed_protocol' => array(
4723
  'data',
4724
  'http',
4725
  'https',
@@ -4746,19 +6826,27 @@ class AMP_Allowed_Tags_Generated {
4746
  array(
4747
  'attr_spec_list' => array(
4748
  'alt' => array(),
 
4749
  'border' => array(),
 
 
 
 
 
 
 
4750
  'height' => array(),
4751
  'ismap' => array(),
4752
  'longdesc' => array(
4753
  'blacklisted_value_regex' => '__amp_source_origin',
4754
  'value_url' => array(
4755
- 'allow_relative' => true,
4756
- 'allowed_protocol' => array(
4757
  'http',
4758
  'https',
4759
  ),
4760
  ),
4761
  ),
 
4762
  'src' => array(
4763
  'alternative_names' => array(
4764
  'srcset',
@@ -4767,7 +6855,7 @@ class AMP_Allowed_Tags_Generated {
4767
  'mandatory' => true,
4768
  'value_url' => array(
4769
  'allow_relative' => true,
4770
- 'allowed_protocol' => array(
4771
  'data',
4772
  'https',
4773
  ),
@@ -4825,6 +6913,11 @@ class AMP_Allowed_Tags_Generated {
4825
  'name' => array(
4826
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
4827
  ),
 
 
 
 
 
4828
  'pattern' => array(),
4829
  'placeholder' => array(),
4830
  'readonly' => array(),
@@ -4844,84 +6937,295 @@ class AMP_Allowed_Tags_Generated {
4844
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4845
  ),
4846
  ),
4847
- ),
4848
- 'ins' => array(
4849
  array(
4850
  'attr_spec_list' => array(
4851
- 'cite' => array(
4852
- 'blacklisted_value_regex' => '__amp_source_origin',
4853
- 'value_url' => array(
4854
- 'allow_empty' => true,
4855
- 'allow_relative' => true,
4856
- 'allowed_protocol' => array(
4857
- 'http',
4858
- 'https',
4859
- ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4860
  ),
4861
  ),
4862
- 'datetime' => array(),
4863
- ),
4864
- 'tag_spec' => array(),
4865
- ),
4866
- ),
4867
- 'kbd' => array(
4868
- array(
4869
- 'attr_spec_list' => array(),
4870
- 'tag_spec' => array(),
4871
- ),
4872
- ),
4873
- 'label' => array(
4874
- array(
4875
- 'attr_spec_list' => array(
4876
- 'for' => array(),
 
 
 
4877
  ),
4878
  'tag_spec' => array(
 
 
4879
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4880
  ),
4881
  ),
4882
- ),
4883
- 'legend' => array(
4884
- array(
4885
- 'attr_spec_list' => array(),
4886
- 'tag_spec' => array(),
4887
- ),
4888
- ),
4889
- 'li' => array(
4890
- array(
4891
- 'attr_spec_list' => array(
4892
- 'value' => array(
4893
- 'value_regex' => '[0-9]*',
4894
- ),
4895
- ),
4896
- 'tag_spec' => array(),
4897
- ),
4898
- ),
4899
- 'line' => array(
4900
  array(
4901
  'attr_spec_list' => array(
4902
- 'alignment-baseline' => array(),
4903
- 'baseline-shift' => array(),
4904
- 'clip' => array(),
4905
- 'clip-path' => array(),
4906
- 'clip-rule' => array(),
4907
- 'color' => array(),
4908
- 'color-interpolation' => array(),
4909
- 'color-interpolation-filters' => array(),
4910
- 'color-profile' => array(),
4911
- 'color-rendering' => array(),
4912
- 'cursor' => array(),
4913
- 'direction' => array(),
4914
- 'display' => array(),
4915
- 'dominant-baseline' => array(),
4916
- 'enable-background' => array(),
4917
- 'externalresourcesrequired' => array(),
4918
- 'fill' => array(),
4919
- 'fill-opacity' => array(),
4920
- 'fill-rule' => array(),
4921
- 'filter' => array(),
4922
- 'flood-color' => array(),
4923
- 'flood-opacity' => array(),
4924
- 'font-family' => array(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4925
  'font-size' => array(),
4926
  'font-size-adjust' => array(),
4927
  'font-stretch' => array(),
@@ -5008,6 +7312,7 @@ class AMP_Allowed_Tags_Generated {
5008
  'filter' => array(),
5009
  'flood-color' => array(),
5010
  'flood-opacity' => array(),
 
5011
  'font-family' => array(),
5012
  'font-size' => array(),
5013
  'font-size-adjust' => array(),
@@ -5063,8 +7368,7 @@ class AMP_Allowed_Tags_Generated {
5063
  ),
5064
  'value_url' => array(
5065
  'allow_empty' => false,
5066
- 'allow_relative' => true,
5067
- 'allowed_protocol' => array(
5068
  'http',
5069
  'https',
5070
  ),
@@ -5091,7 +7395,9 @@ class AMP_Allowed_Tags_Generated {
5091
  array(
5092
  'attr_spec_list' => array(
5093
  'charset' => array(
5094
- 'value_casei' => 'utf-8',
 
 
5095
  ),
5096
  'color' => array(),
5097
  'crossorigin' => array(),
@@ -5117,7 +7423,9 @@ class AMP_Allowed_Tags_Generated {
5117
  array(
5118
  'attr_spec_list' => array(
5119
  'charset' => array(
5120
- 'value_casei' => 'utf-8',
 
 
5121
  ),
5122
  'color' => array(),
5123
  'crossorigin' => array(),
@@ -5125,8 +7433,7 @@ class AMP_Allowed_Tags_Generated {
5125
  'blacklisted_value_regex' => '__amp_source_origin',
5126
  'mandatory' => true,
5127
  'value_url' => array(
5128
- 'allow_relative' => true,
5129
- 'allowed_protocol' => array(
5130
  'http',
5131
  'https',
5132
  ),
@@ -5137,7 +7444,9 @@ class AMP_Allowed_Tags_Generated {
5137
  'rel' => array(
5138
  'dispatch_key' => 2,
5139
  'mandatory' => true,
5140
- 'value_casei' => 'canonical',
 
 
5141
  ),
5142
  'sizes' => array(),
5143
  'target' => array(),
@@ -5154,7 +7463,9 @@ class AMP_Allowed_Tags_Generated {
5154
  array(
5155
  'attr_spec_list' => array(
5156
  'charset' => array(
5157
- 'value_casei' => 'utf-8',
 
 
5158
  ),
5159
  'color' => array(),
5160
  'crossorigin' => array(),
@@ -5162,8 +7473,7 @@ class AMP_Allowed_Tags_Generated {
5162
  'blacklisted_value_regex' => '__amp_source_origin',
5163
  'mandatory' => true,
5164
  'value_url' => array(
5165
- 'allow_relative' => true,
5166
- 'allowed_protocol' => array(
5167
  'https',
5168
  ),
5169
  ),
@@ -5173,7 +7483,9 @@ class AMP_Allowed_Tags_Generated {
5173
  'rel' => array(
5174
  'dispatch_key' => 2,
5175
  'mandatory' => true,
5176
- 'value_casei' => 'manifest',
 
 
5177
  ),
5178
  'sizes' => array(),
5179
  'target' => array(),
@@ -5189,7 +7501,9 @@ class AMP_Allowed_Tags_Generated {
5189
  'attr_spec_list' => array(
5190
  'as' => array(),
5191
  'charset' => array(
5192
- 'value_casei' => 'utf-8',
 
 
5193
  ),
5194
  'color' => array(),
5195
  'crossorigin' => array(),
@@ -5199,7 +7513,9 @@ class AMP_Allowed_Tags_Generated {
5199
  'rel' => array(
5200
  'dispatch_key' => 2,
5201
  'mandatory' => true,
5202
- 'value_casei' => 'preload',
 
 
5203
  ),
5204
  'sizes' => array(),
5205
  'target' => array(),
@@ -5219,17 +7535,21 @@ class AMP_Allowed_Tags_Generated {
5219
  'crossorigin' => array(),
5220
  'href' => array(
5221
  'mandatory' => true,
5222
- 'value_regex' => 'https://cdn\\.materialdesignicons\\.com/([0-9]+\\.?)+/css/materialdesignicons\\.min\\.css|https://cloud\\.typography\\.com/[0-9]*/[0-9]*/css/fonts\\.css|https://fast\\.fonts\\.net/.*|https://fonts\\.googleapis\\.com/css\\?.*|https://fonts\\.googleapis\\.com/icon\\?.*|https://fonts\\.googleapis\\.com/earlyaccess/.*\\.css|https://maxcdn\\.bootstrapcdn\\.com/font-awesome/([0-9]+\\.?)+/css/font-awesome\\.min\\.css(\\?.*)?|https://use\\.fontawesome\\.com/releases/v([0-9]+\\.?)+/css/(all|brands|solids|fontawesome)\\.css|https://use\\.typekit\\.net/[\\w\\p{L}\\p{N}_]+\\.css',
5223
  ),
5224
  'integrity' => array(),
5225
  'media' => array(),
5226
  'rel' => array(
5227
  'dispatch_key' => 2,
5228
  'mandatory' => true,
5229
- 'value_casei' => 'stylesheet',
 
 
5230
  ),
5231
  'type' => array(
5232
- 'value_casei' => 'text/css',
 
 
5233
  ),
5234
  ),
5235
  'tag_spec' => array(
@@ -5241,7 +7561,9 @@ class AMP_Allowed_Tags_Generated {
5241
  array(
5242
  'attr_spec_list' => array(
5243
  'charset' => array(
5244
- 'value_casei' => 'utf-8',
 
 
5245
  ),
5246
  'color' => array(),
5247
  'crossorigin' => array(),
@@ -5252,7 +7574,9 @@ class AMP_Allowed_Tags_Generated {
5252
  'itemprop' => array(
5253
  'dispatch_key' => 2,
5254
  'mandatory' => true,
5255
- 'value_casei' => 'sameas',
 
 
5256
  ),
5257
  'media' => array(),
5258
  'sizes' => array(),
@@ -5267,7 +7591,9 @@ class AMP_Allowed_Tags_Generated {
5267
  array(
5268
  'attr_spec_list' => array(
5269
  'charset' => array(
5270
- 'value_casei' => 'utf-8',
 
 
5271
  ),
5272
  'color' => array(),
5273
  'crossorigin' => array(),
@@ -5291,7 +7617,9 @@ class AMP_Allowed_Tags_Generated {
5291
  array(
5292
  'attr_spec_list' => array(
5293
  'charset' => array(
5294
- 'value_casei' => 'utf-8',
 
 
5295
  ),
5296
  'color' => array(),
5297
  'crossorigin' => array(),
@@ -5356,6 +7684,7 @@ class AMP_Allowed_Tags_Generated {
5356
  'filter' => array(),
5357
  'flood-color' => array(),
5358
  'flood-opacity' => array(),
 
5359
  'font-family' => array(),
5360
  'font-size' => array(),
5361
  'font-size-adjust' => array(),
@@ -5443,6 +7772,7 @@ class AMP_Allowed_Tags_Generated {
5443
  'filter' => array(),
5444
  'flood-color' => array(),
5445
  'flood-opacity' => array(),
 
5446
  'font-family' => array(),
5447
  'font-size' => array(),
5448
  'font-size-adjust' => array(),
@@ -5511,7 +7841,9 @@ class AMP_Allowed_Tags_Generated {
5511
  'charset' => array(
5512
  'dispatch_key' => 1,
5513
  'mandatory' => true,
5514
- 'value_casei' => 'utf-8',
 
 
5515
  ),
5516
  ),
5517
  'tag_spec' => array(
@@ -5530,10 +7862,7 @@ class AMP_Allowed_Tags_Generated {
5530
  'height' => array(),
5531
  'initial-scale' => array(),
5532
  'maximum-scale' => array(),
5533
- 'minimum-scale' => array(
5534
- 'mandatory' => true,
5535
- 'value_double' => 1.0,
5536
- ),
5537
  'shrink-to-fit' => array(),
5538
  'user-scalable' => array(),
5539
  'viewport-fit' => array(),
@@ -5546,7 +7875,9 @@ class AMP_Allowed_Tags_Generated {
5546
  'name' => array(
5547
  'dispatch_key' => 2,
5548
  'mandatory' => true,
5549
- 'value' => 'viewport',
 
 
5550
  ),
5551
  ),
5552
  'tag_spec' => array(
@@ -5573,7 +7904,9 @@ class AMP_Allowed_Tags_Generated {
5573
  'http-equiv' => array(
5574
  'dispatch_key' => 2,
5575
  'mandatory' => true,
5576
- 'value_casei' => 'x-ua-compatible',
 
 
5577
  ),
5578
  ),
5579
  'tag_spec' => array(
@@ -5591,7 +7924,9 @@ class AMP_Allowed_Tags_Generated {
5591
  'name' => array(
5592
  'dispatch_key' => 2,
5593
  'mandatory' => true,
5594
- 'value_casei' => 'apple-itunes-app',
 
 
5595
  ),
5596
  ),
5597
  'tag_spec' => array(
@@ -5608,7 +7943,9 @@ class AMP_Allowed_Tags_Generated {
5608
  'name' => array(
5609
  'dispatch_key' => 2,
5610
  'mandatory' => true,
5611
- 'value_casei' => 'amp-experiments-opt-in',
 
 
5612
  ),
5613
  ),
5614
  'tag_spec' => array(
@@ -5621,7 +7958,7 @@ class AMP_Allowed_Tags_Generated {
5621
  'content' => array(
5622
  'mandatory' => true,
5623
  'value_url' => array(
5624
- 'allowed_protocol' => array(
5625
  'https',
5626
  ),
5627
  ),
@@ -5629,7 +7966,9 @@ class AMP_Allowed_Tags_Generated {
5629
  'name' => array(
5630
  'dispatch_key' => 2,
5631
  'mandatory' => true,
5632
- 'value_casei' => 'amp-3p-iframe-src',
 
 
5633
  ),
5634
  ),
5635
  'tag_spec' => array(
@@ -5646,7 +7985,9 @@ class AMP_Allowed_Tags_Generated {
5646
  'name' => array(
5647
  'dispatch_key' => 2,
5648
  'mandatory' => true,
5649
- 'value_casei' => 'amp-experiment-token',
 
 
5650
  ),
5651
  ),
5652
  'tag_spec' => array(
@@ -5662,7 +8003,9 @@ class AMP_Allowed_Tags_Generated {
5662
  'name' => array(
5663
  'dispatch_key' => 2,
5664
  'mandatory' => true,
5665
- 'value_casei' => 'amp-link-variable-allowed-origin',
 
 
5666
  ),
5667
  ),
5668
  'tag_spec' => array(
@@ -5678,7 +8021,9 @@ class AMP_Allowed_Tags_Generated {
5678
  'name' => array(
5679
  'dispatch_key' => 2,
5680
  'mandatory' => true,
5681
- 'value_casei' => 'amp-google-client-id-api',
 
 
5682
  ),
5683
  ),
5684
  'tag_spec' => array(
@@ -5691,7 +8036,9 @@ class AMP_Allowed_Tags_Generated {
5691
  'name' => array(
5692
  'dispatch_key' => 2,
5693
  'mandatory' => true,
5694
- 'value_casei' => 'amp-ad-doubleclick-sra',
 
 
5695
  ),
5696
  ),
5697
  'tag_spec' => array(
@@ -5717,12 +8064,16 @@ class AMP_Allowed_Tags_Generated {
5717
  'attr_spec_list' => array(
5718
  'content' => array(
5719
  'mandatory' => true,
5720
- 'value_casei' => 'text/html; charset=utf-8',
 
 
5721
  ),
5722
  'http-equiv' => array(
5723
  'dispatch_key' => 2,
5724
  'mandatory' => true,
5725
- 'value_casei' => 'content-type',
 
 
5726
  ),
5727
  ),
5728
  'tag_spec' => array(
@@ -5739,7 +8090,9 @@ class AMP_Allowed_Tags_Generated {
5739
  'http-equiv' => array(
5740
  'dispatch_key' => 2,
5741
  'mandatory' => true,
5742
- 'value_casei' => 'content-language',
 
 
5743
  ),
5744
  ),
5745
  'tag_spec' => array(
@@ -5756,7 +8109,9 @@ class AMP_Allowed_Tags_Generated {
5756
  'http-equiv' => array(
5757
  'dispatch_key' => 2,
5758
  'mandatory' => true,
5759
- 'value_casei' => 'pics-label',
 
 
5760
  ),
5761
  ),
5762
  'tag_spec' => array(
@@ -5773,7 +8128,9 @@ class AMP_Allowed_Tags_Generated {
5773
  'http-equiv' => array(
5774
  'dispatch_key' => 2,
5775
  'mandatory' => true,
5776
- 'value_casei' => 'imagetoolbar',
 
 
5777
  ),
5778
  ),
5779
  'tag_spec' => array(
@@ -5786,12 +8143,16 @@ class AMP_Allowed_Tags_Generated {
5786
  'attr_spec_list' => array(
5787
  'content' => array(
5788
  'mandatory' => true,
5789
- 'value_casei' => 'text/css',
 
 
5790
  ),
5791
  'http-equiv' => array(
5792
  'dispatch_key' => 2,
5793
  'mandatory' => true,
5794
- 'value_casei' => 'content-style-type',
 
 
5795
  ),
5796
  ),
5797
  'tag_spec' => array(
@@ -5804,12 +8165,16 @@ class AMP_Allowed_Tags_Generated {
5804
  'attr_spec_list' => array(
5805
  'content' => array(
5806
  'mandatory' => true,
5807
- 'value_casei' => 'text/javascript',
 
 
5808
  ),
5809
  'http-equiv' => array(
5810
  'dispatch_key' => 2,
5811
  'mandatory' => true,
5812
- 'value_casei' => 'content-script-type',
 
 
5813
  ),
5814
  ),
5815
  'tag_spec' => array(
@@ -5826,7 +8191,9 @@ class AMP_Allowed_Tags_Generated {
5826
  'http-equiv' => array(
5827
  'dispatch_key' => 2,
5828
  'mandatory' => true,
5829
- 'value_casei' => 'origin-trial',
 
 
5830
  ),
5831
  ),
5832
  'tag_spec' => array(
@@ -5843,7 +8210,9 @@ class AMP_Allowed_Tags_Generated {
5843
  'http-equiv' => array(
5844
  'dispatch_key' => 2,
5845
  'mandatory' => true,
5846
- 'value_casei' => 'resource-type',
 
 
5847
  ),
5848
  ),
5849
  'tag_spec' => array(
@@ -5856,12 +8225,17 @@ class AMP_Allowed_Tags_Generated {
5856
  'attr_spec_list' => array(
5857
  'content' => array(
5858
  'mandatory' => true,
5859
- 'value_regex_casei' => '(off|on)',
 
 
 
5860
  ),
5861
  'http-equiv' => array(
5862
  'dispatch_key' => 2,
5863
  'mandatory' => true,
5864
- 'value_casei' => 'x-dns-prefetch-control',
 
 
5865
  ),
5866
  ),
5867
  'tag_spec' => array(
@@ -5878,7 +8252,9 @@ class AMP_Allowed_Tags_Generated {
5878
  'name' => array(
5879
  'dispatch_key' => 2,
5880
  'mandatory' => true,
5881
- 'value_casei' => 'amp-ad-enable-refresh',
 
 
5882
  ),
5883
  ),
5884
  'tag_spec' => array(
@@ -5894,7 +8270,9 @@ class AMP_Allowed_Tags_Generated {
5894
  'name' => array(
5895
  'dispatch_key' => 2,
5896
  'mandatory' => true,
5897
- 'value_casei' => 'amp-to-amp-navigation',
 
 
5898
  ),
5899
  ),
5900
  'tag_spec' => array(
@@ -6004,7 +8382,9 @@ class AMP_Allowed_Tags_Generated {
6004
  array(
6005
  'attr_spec_list' => array(
6006
  'reversed' => array(
6007
- 'value' => '',
 
 
6008
  ),
6009
  'start' => array(
6010
  'value_regex' => '[0-9]*',
@@ -6093,6 +8473,7 @@ class AMP_Allowed_Tags_Generated {
6093
  'filter' => array(),
6094
  'flood-color' => array(),
6095
  'flood-opacity' => array(),
 
6096
  'font-family' => array(),
6097
  'font-size' => array(),
6098
  'font-size-adjust' => array(),
@@ -6177,6 +8558,7 @@ class AMP_Allowed_Tags_Generated {
6177
  'filter' => array(),
6178
  'flood-color' => array(),
6179
  'flood-opacity' => array(),
 
6180
  'font-family' => array(),
6181
  'font-size' => array(),
6182
  'font-size-adjust' => array(),
@@ -6238,8 +8620,7 @@ class AMP_Allowed_Tags_Generated {
6238
  ),
6239
  'value_url' => array(
6240
  'allow_empty' => false,
6241
- 'allow_relative' => true,
6242
- 'allowed_protocol' => array(
6243
  'http',
6244
  'https',
6245
  ),
@@ -6261,6 +8642,15 @@ class AMP_Allowed_Tags_Generated {
6261
  ),
6262
  ),
6263
  ),
 
 
 
 
 
 
 
 
 
6264
  'polygon' => array(
6265
  array(
6266
  'attr_spec_list' => array(
@@ -6286,6 +8676,7 @@ class AMP_Allowed_Tags_Generated {
6286
  'filter' => array(),
6287
  'flood-color' => array(),
6288
  'flood-opacity' => array(),
 
6289
  'font-family' => array(),
6290
  'font-size' => array(),
6291
  'font-size-adjust' => array(),
@@ -6370,6 +8761,7 @@ class AMP_Allowed_Tags_Generated {
6370
  'filter' => array(),
6371
  'flood-color' => array(),
6372
  'flood-opacity' => array(),
 
6373
  'font-family' => array(),
6374
  'font-size' => array(),
6375
  'font-size-adjust' => array(),
@@ -6451,8 +8843,7 @@ class AMP_Allowed_Tags_Generated {
6451
  'blacklisted_value_regex' => '__amp_source_origin',
6452
  'value_url' => array(
6453
  'allow_empty' => true,
6454
- 'allow_relative' => true,
6455
- 'allowed_protocol' => array(
6456
  'http',
6457
  'https',
6458
  ),
@@ -6489,6 +8880,7 @@ class AMP_Allowed_Tags_Generated {
6489
  'filter' => array(),
6490
  'flood-color' => array(),
6491
  'flood-opacity' => array(),
 
6492
  'font-family' => array(),
6493
  'font-size' => array(),
6494
  'font-size-adjust' => array(),
@@ -6546,8 +8938,7 @@ class AMP_Allowed_Tags_Generated {
6546
  ),
6547
  'value_url' => array(
6548
  'allow_empty' => false,
6549
- 'allow_relative' => true,
6550
- 'allowed_protocol' => array(
6551
  'http',
6552
  'https',
6553
  ),
@@ -6599,6 +8990,7 @@ class AMP_Allowed_Tags_Generated {
6599
  'filter' => array(),
6600
  'flood-color' => array(),
6601
  'flood-opacity' => array(),
 
6602
  'font-family' => array(),
6603
  'font-size' => array(),
6604
  'font-size-adjust' => array(),
@@ -6704,16 +9096,22 @@ class AMP_Allowed_Tags_Generated {
6704
  'attr_spec_list' => array(
6705
  'async' => array(
6706
  'mandatory' => true,
6707
- 'value' => '',
 
 
6708
  ),
6709
  'nonce' => array(),
6710
  'src' => array(
6711
  'dispatch_key' => 2,
6712
  'mandatory' => true,
6713
- 'value' => 'https://cdn.ampproject.org/v0.js',
 
 
6714
  ),
6715
  'type' => array(
6716
- 'value_casei' => 'text/javascript',
 
 
6717
  ),
6718
  ),
6719
  'cdata' => array(
@@ -6736,7 +9134,9 @@ class AMP_Allowed_Tags_Generated {
6736
  'type' => array(
6737
  'dispatch_key' => 2,
6738
  'mandatory' => true,
6739
- 'value_casei' => 'application/ld+json',
 
 
6740
  ),
6741
  ),
6742
  'cdata' => array(
@@ -6754,12 +9154,16 @@ class AMP_Allowed_Tags_Generated {
6754
  'id' => array(
6755
  'dispatch_key' => 2,
6756
  'mandatory' => true,
6757
- 'value_casei' => 'amp-rtc',
 
 
6758
  ),
6759
  'nonce' => array(),
6760
  'type' => array(
6761
  'mandatory' => true,
6762
- 'value_casei' => 'application/json',
 
 
6763
  ),
6764
  ),
6765
  'cdata' => array(
@@ -6779,7 +9183,9 @@ class AMP_Allowed_Tags_Generated {
6779
  'type' => array(
6780
  'dispatch_key' => 3,
6781
  'mandatory' => true,
6782
- 'value_casei' => 'application/json',
 
 
6783
  ),
6784
  ),
6785
  'cdata' => array(
@@ -6797,20 +9203,24 @@ class AMP_Allowed_Tags_Generated {
6797
  'attr_spec_list' => array(
6798
  'async' => array(
6799
  'mandatory' => true,
6800
- 'value' => '',
 
 
6801
  ),
6802
  'nonce' => array(),
6803
  'type' => array(
6804
- 'value_casei' => 'text/javascript',
 
 
6805
  ),
6806
  ),
6807
  'tag_spec' => array(
6808
  'extension_spec' => array(
6809
- 'allowed_versions' => array(
 
6810
  '0.1',
6811
  'latest',
6812
  ),
6813
- 'name' => 'amp-3q-player',
6814
  ),
6815
  ),
6816
  ),
@@ -6818,21 +9228,51 @@ class AMP_Allowed_Tags_Generated {
6818
  'attr_spec_list' => array(
6819
  'async' => array(
6820
  'mandatory' => true,
6821
- 'value' => '',
 
 
6822
  ),
6823
  'nonce' => array(),
6824
  'type' => array(
6825
- 'value_casei' => 'text/javascript',
 
 
6826
  ),
6827
  ),
6828
  'tag_spec' => array(
6829
  'extension_spec' => array(
6830
- 'allowed_versions' => array(
 
6831
  '0.1',
6832
  'latest',
6833
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6834
  'name' => 'amp-access-laterpay',
6835
  'requires_usage' => 3,
 
 
 
 
 
6836
  ),
6837
  'requires_extension' => array(
6838
  'amp-access',
@@ -6843,21 +9283,25 @@ class AMP_Allowed_Tags_Generated {
6843
  'attr_spec_list' => array(
6844
  'async' => array(
6845
  'mandatory' => true,
6846
- 'value' => '',
 
 
6847
  ),
6848
  'nonce' => array(),
6849
  'type' => array(
6850
- 'value_casei' => 'text/javascript',
 
 
6851
  ),
6852
  ),
6853
  'tag_spec' => array(
6854
  'extension_spec' => array(
6855
- 'allowed_versions' => array(
 
 
6856
  '0.1',
6857
  'latest',
6858
  ),
6859
- 'name' => 'amp-access-scroll',
6860
- 'requires_usage' => 3,
6861
  ),
6862
  'requires_extension' => array(
6863
  'amp-access',
@@ -6868,22 +9312,26 @@ class AMP_Allowed_Tags_Generated {
6868
  'attr_spec_list' => array(
6869
  'async' => array(
6870
  'mandatory' => true,
6871
- 'value' => '',
 
 
6872
  ),
6873
  'nonce' => array(),
6874
  'type' => array(
6875
- 'value_casei' => 'text/javascript',
 
 
6876
  ),
6877
  ),
6878
  'tag_spec' => array(
6879
  'extension_spec' => array(
6880
- 'allowed_versions' => array(
6881
- '0.1',
6882
- 'latest',
6883
- ),
6884
  'deprecated_allow_duplicates' => true,
6885
  'name' => 'amp-access',
6886
  'requires_usage' => 2,
 
 
 
 
6887
  ),
6888
  ),
6889
  ),
@@ -6892,12 +9340,16 @@ class AMP_Allowed_Tags_Generated {
6892
  'id' => array(
6893
  'dispatch_key' => 2,
6894
  'mandatory' => true,
6895
- 'value' => 'amp-access',
 
 
6896
  ),
6897
  'nonce' => array(),
6898
  'type' => array(
6899
  'mandatory' => true,
6900
- 'value_casei' => 'application/json',
 
 
6901
  ),
6902
  ),
6903
  'cdata' => array(
@@ -6910,7 +9362,6 @@ class AMP_Allowed_Tags_Generated {
6910
  'mandatory_parent' => 'head',
6911
  'requires_extension' => array(
6912
  'amp-access',
6913
- 'amp-analytics',
6914
  ),
6915
  'spec_name' => 'amp-access extension .json script',
6916
  'unique' => true,
@@ -6920,22 +9371,26 @@ class AMP_Allowed_Tags_Generated {
6920
  'attr_spec_list' => array(
6921
  'async' => array(
6922
  'mandatory' => true,
6923
- 'value' => '',
 
 
6924
  ),
6925
  'nonce' => array(),
6926
  'type' => array(
6927
- 'value_casei' => 'text/javascript',
 
 
6928
  ),
6929
  ),
6930
  'tag_spec' => array(
6931
  'extension_spec' => array(
6932
- 'allowed_versions' => array(
6933
- '0.1',
6934
- 'latest',
6935
- ),
6936
  'deprecated_allow_duplicates' => true,
6937
  'name' => 'amp-accordion',
6938
  'requires_usage' => 2,
 
 
 
 
6939
  ),
6940
  ),
6941
  ),
@@ -6943,22 +9398,26 @@ class AMP_Allowed_Tags_Generated {
6943
  'attr_spec_list' => array(
6944
  'async' => array(
6945
  'mandatory' => true,
6946
- 'value' => '',
 
 
6947
  ),
6948
  'nonce' => array(),
6949
  'type' => array(
6950
- 'value_casei' => 'text/javascript',
 
 
6951
  ),
6952
  ),
6953
  'tag_spec' => array(
6954
  'extension_spec' => array(
6955
- 'allowed_versions' => array(
6956
- '0.1',
6957
- 'latest',
6958
- ),
6959
  'deprecated_allow_duplicates' => true,
6960
  'name' => 'amp-ad',
6961
  'requires_usage' => 2,
 
 
 
 
6962
  ),
6963
  'spec_name' => 'amp-ad extension .js script',
6964
  ),
@@ -6967,22 +9426,51 @@ class AMP_Allowed_Tags_Generated {
6967
  'attr_spec_list' => array(
6968
  'async' => array(
6969
  'mandatory' => true,
6970
- 'value' => '',
 
 
6971
  ),
6972
  'nonce' => array(),
6973
  'type' => array(
6974
- 'value_casei' => 'text/javascript',
 
 
6975
  ),
6976
  ),
6977
  'tag_spec' => array(
6978
  'extension_spec' => array(
6979
- 'allowed_versions' => array(
 
6980
  '0.1',
6981
  'latest',
6982
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6983
  'deprecated_allow_duplicates' => true,
6984
  'name' => 'amp-analytics',
6985
  'requires_usage' => 2,
 
 
 
 
6986
  ),
6987
  ),
6988
  ),
@@ -6992,7 +9480,9 @@ class AMP_Allowed_Tags_Generated {
6992
  'type' => array(
6993
  'dispatch_key' => 3,
6994
  'mandatory' => true,
6995
- 'value_casei' => 'application/json',
 
 
6996
  ),
6997
  ),
6998
  'cdata' => array(
@@ -7014,22 +9504,26 @@ class AMP_Allowed_Tags_Generated {
7014
  'attr_spec_list' => array(
7015
  'async' => array(
7016
  'mandatory' => true,
7017
- 'value' => '',
 
 
7018
  ),
7019
  'nonce' => array(),
7020
  'type' => array(
7021
- 'value_casei' => 'text/javascript',
 
 
7022
  ),
7023
  ),
7024
  'tag_spec' => array(
7025
  'extension_spec' => array(
7026
- 'allowed_versions' => array(
7027
- '0.1',
7028
- 'latest',
7029
- ),
7030
  'deprecated_allow_duplicates' => true,
7031
  'name' => 'amp-anim',
7032
  'requires_usage' => 2,
 
 
 
 
7033
  ),
7034
  ),
7035
  ),
@@ -7037,20 +9531,24 @@ class AMP_Allowed_Tags_Generated {
7037
  'attr_spec_list' => array(
7038
  'async' => array(
7039
  'mandatory' => true,
7040
- 'value' => '',
 
 
7041
  ),
7042
  'nonce' => array(),
7043
  'type' => array(
7044
- 'value_casei' => 'text/javascript',
 
 
7045
  ),
7046
  ),
7047
  'tag_spec' => array(
7048
  'extension_spec' => array(
7049
- 'allowed_versions' => array(
 
7050
  '0.1',
7051
  'latest',
7052
  ),
7053
- 'name' => 'amp-animation',
7054
  ),
7055
  ),
7056
  ),
@@ -7060,7 +9558,9 @@ class AMP_Allowed_Tags_Generated {
7060
  'type' => array(
7061
  'dispatch_key' => 3,
7062
  'mandatory' => true,
7063
- 'value_casei' => 'application/json',
 
 
7064
  ),
7065
  ),
7066
  'cdata' => array(
@@ -7081,22 +9581,26 @@ class AMP_Allowed_Tags_Generated {
7081
  'attr_spec_list' => array(
7082
  'async' => array(
7083
  'mandatory' => true,
7084
- 'value' => '',
 
 
7085
  ),
7086
  'nonce' => array(),
7087
  'type' => array(
7088
- 'value_casei' => 'text/javascript',
 
 
7089
  ),
7090
  ),
7091
  'tag_spec' => array(
7092
  'extension_spec' => array(
7093
- 'allowed_versions' => array(
7094
- '0.1',
7095
- 'latest',
7096
- ),
7097
  'deprecated_allow_duplicates' => true,
7098
  'name' => 'amp-apester-media',
7099
  'requires_usage' => 2,
 
 
 
 
7100
  ),
7101
  ),
7102
  ),
@@ -7104,21 +9608,25 @@ class AMP_Allowed_Tags_Generated {
7104
  'attr_spec_list' => array(
7105
  'async' => array(
7106
  'mandatory' => true,
7107
- 'value' => '',
 
 
7108
  ),
7109
  'nonce' => array(),
7110
  'type' => array(
7111
- 'value_casei' => 'text/javascript',
 
 
7112
  ),
7113
  ),
7114
  'tag_spec' => array(
7115
  'extension_spec' => array(
7116
- 'allowed_versions' => array(
 
 
7117
  '0.1',
7118
  'latest',
7119
  ),
7120
- 'deprecated_allow_duplicates' => true,
7121
- 'name' => 'amp-app-banner',
7122
  ),
7123
  ),
7124
  ),
@@ -7126,22 +9634,26 @@ class AMP_Allowed_Tags_Generated {
7126
  'attr_spec_list' => array(
7127
  'async' => array(
7128
  'mandatory' => true,
7129
- 'value' => '',
 
 
7130
  ),
7131
  'nonce' => array(),
7132
  'type' => array(
7133
- 'value_casei' => 'text/javascript',
 
 
7134
  ),
7135
  ),
7136
  'tag_spec' => array(
7137
  'extension_spec' => array(
7138
- 'allowed_versions' => array(
7139
- '0.1',
7140
- 'latest',
7141
- ),
7142
  'deprecated_allow_duplicates' => true,
7143
  'name' => 'amp-audio',
7144
  'requires_usage' => 2,
 
 
 
 
7145
  ),
7146
  ),
7147
  ),
@@ -7149,20 +9661,24 @@ class AMP_Allowed_Tags_Generated {
7149
  'attr_spec_list' => array(
7150
  'async' => array(
7151
  'mandatory' => true,
7152
- 'value' => '',
 
 
7153
  ),
7154
  'nonce' => array(),
7155
  'type' => array(
7156
- 'value_casei' => 'text/javascript',
 
 
7157
  ),
7158
  ),
7159
  'tag_spec' => array(
7160
  'extension_spec' => array(
7161
- 'allowed_versions' => array(
 
7162
  '0.1',
7163
  'latest',
7164
  ),
7165
- 'name' => 'amp-auto-ads',
7166
  ),
7167
  ),
7168
  ),
@@ -7170,20 +9686,24 @@ class AMP_Allowed_Tags_Generated {
7170
  'attr_spec_list' => array(
7171
  'async' => array(
7172
  'mandatory' => true,
7173
- 'value' => '',
 
 
7174
  ),
7175
  'nonce' => array(),
7176
  'type' => array(
7177
- 'value_casei' => 'text/javascript',
 
 
7178
  ),
7179
  ),
7180
  'tag_spec' => array(
7181
  'extension_spec' => array(
7182
- 'allowed_versions' => array(
 
7183
  '0.1',
7184
  'latest',
7185
  ),
7186
- 'name' => 'amp-beopinion',
7187
  ),
7188
  ),
7189
  ),
@@ -7191,21 +9711,25 @@ class AMP_Allowed_Tags_Generated {
7191
  'attr_spec_list' => array(
7192
  'async' => array(
7193
  'mandatory' => true,
7194
- 'value' => '',
 
 
7195
  ),
7196
  'nonce' => array(),
7197
  'type' => array(
7198
- 'value_casei' => 'text/javascript',
 
 
7199
  ),
7200
  ),
7201
  'tag_spec' => array(
7202
  'extension_spec' => array(
7203
- 'allowed_versions' => array(
 
 
7204
  '0.1',
7205
  'latest',
7206
  ),
7207
- 'name' => 'amp-bind',
7208
- 'requires_usage' => 3,
7209
  ),
7210
  ),
7211
  ),
@@ -7215,7 +9739,9 @@ class AMP_Allowed_Tags_Generated {
7215
  'type' => array(
7216
  'dispatch_key' => 3,
7217
  'mandatory' => true,
7218
- 'value_casei' => 'application/json',
 
 
7219
  ),
7220
  ),
7221
  'cdata' => array(
@@ -7223,6 +9749,8 @@ class AMP_Allowed_Tags_Generated {
7223
  'error_message' => 'html comments',
7224
  'regex' => '<!--',
7225
  ),
 
 
7226
  ),
7227
  'tag_spec' => array(
7228
  'mandatory_parent' => 'amp-state',
@@ -7237,20 +9765,24 @@ class AMP_Allowed_Tags_Generated {
7237
  'attr_spec_list' => array(
7238
  'async' => array(
7239
  'mandatory' => true,
7240
- 'value' => '',
 
 
7241
  ),
7242
  'nonce' => array(),
7243
  'type' => array(
7244
- 'value_casei' => 'text/javascript',
 
 
7245
  ),
7246
  ),
7247
  'tag_spec' => array(
7248
  'extension_spec' => array(
7249
- 'allowed_versions' => array(
 
7250
  '0.1',
7251
  'latest',
7252
  ),
7253
- 'name' => 'amp-bodymovin-animation',
7254
  ),
7255
  ),
7256
  ),
@@ -7258,22 +9790,53 @@ class AMP_Allowed_Tags_Generated {
7258
  'attr_spec_list' => array(
7259
  'async' => array(
7260
  'mandatory' => true,
7261
- 'value' => '',
 
 
7262
  ),
7263
  'nonce' => array(),
7264
  'type' => array(
7265
- 'value_casei' => 'text/javascript',
 
 
7266
  ),
7267
  ),
7268
  'tag_spec' => array(
7269
  'extension_spec' => array(
7270
- 'allowed_versions' => array(
 
 
 
7271
  '0.1',
7272
  'latest',
7273
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7274
  'deprecated_allow_duplicates' => true,
7275
- 'name' => 'amp-brid-player',
7276
  'requires_usage' => 2,
 
 
 
 
7277
  ),
7278
  ),
7279
  ),
@@ -7281,22 +9844,50 @@ class AMP_Allowed_Tags_Generated {
7281
  'attr_spec_list' => array(
7282
  'async' => array(
7283
  'mandatory' => true,
7284
- 'value' => '',
 
 
7285
  ),
7286
  'nonce' => array(),
7287
  'type' => array(
7288
- 'value_casei' => 'text/javascript',
 
 
7289
  ),
7290
  ),
7291
  'tag_spec' => array(
7292
  'extension_spec' => array(
7293
- 'allowed_versions' => array(
 
7294
  '0.1',
7295
  'latest',
7296
  ),
7297
- 'deprecated_allow_duplicates' => true,
7298
- 'name' => 'amp-brightcove',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7299
  'requires_usage' => 2,
 
 
 
 
7300
  ),
7301
  ),
7302
  ),
@@ -7304,20 +9895,26 @@ class AMP_Allowed_Tags_Generated {
7304
  'attr_spec_list' => array(
7305
  'async' => array(
7306
  'mandatory' => true,
7307
- 'value' => '',
 
 
7308
  ),
7309
  'nonce' => array(),
7310
  'type' => array(
7311
- 'value_casei' => 'text/javascript',
 
 
7312
  ),
7313
  ),
7314
  'tag_spec' => array(
7315
  'extension_spec' => array(
7316
- 'allowed_versions' => array(
 
 
 
7317
  '0.1',
7318
  'latest',
7319
  ),
7320
- 'name' => 'amp-byside-content',
7321
  ),
7322
  ),
7323
  ),
@@ -7325,21 +9922,77 @@ class AMP_Allowed_Tags_Generated {
7325
  'attr_spec_list' => array(
7326
  'async' => array(
7327
  'mandatory' => true,
7328
- 'value' => '',
 
 
7329
  ),
7330
  'nonce' => array(),
7331
  'type' => array(
7332
- 'value_casei' => 'text/javascript',
 
 
7333
  ),
7334
  ),
7335
  'tag_spec' => array(
7336
  'extension_spec' => array(
7337
- 'allowed_versions' => array(
 
7338
  '0.1',
7339
  'latest',
7340
  ),
7341
- 'name' => 'amp-call-tracking',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7342
  'requires_usage' => 2,
 
 
 
 
7343
  ),
7344
  ),
7345
  ),
@@ -7347,22 +10000,226 @@ class AMP_Allowed_Tags_Generated {
7347
  'attr_spec_list' => array(
7348
  'async' => array(
7349
  'mandatory' => true,
7350
- 'value' => '',
 
 
7351
  ),
7352
  'nonce' => array(),
7353
  'type' => array(
7354
- 'value_casei' => 'text/javascript',
 
 
7355
  ),
7356
  ),
7357
  'tag_spec' => array(
7358
  'extension_spec' => array(
7359
- 'allowed_versions' => array(
 
7360
  '0.1',
7361
  'latest',
7362
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7363
  'deprecated_allow_duplicates' => true,
7364
- 'name' => 'amp-carousel',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7365
  'requires_usage' => 2,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7366
  ),
7367
  ),
7368
  ),
@@ -7370,67 +10227,78 @@ class AMP_Allowed_Tags_Generated {
7370
  'attr_spec_list' => array(
7371
  'async' => array(
7372
  'mandatory' => true,
7373
- 'value' => '',
 
 
7374
  ),
7375
  'nonce' => array(),
7376
  'type' => array(
7377
- 'value_casei' => 'text/javascript',
 
 
7378
  ),
7379
  ),
7380
  'tag_spec' => array(
7381
  'extension_spec' => array(
7382
- 'allowed_versions' => array(
 
7383
  '0.1',
7384
  'latest',
7385
  ),
7386
- 'name' => 'amp-consent',
7387
  ),
7388
  ),
7389
  ),
7390
  array(
7391
  'attr_spec_list' => array(
7392
- 'nonce' => array(),
7393
- 'type' => array(
7394
- 'dispatch_key' => 3,
7395
  'mandatory' => true,
7396
- 'value_casei' => 'application/json',
 
 
7397
  ),
7398
- ),
7399
- 'cdata' => array(
7400
- 'blacklisted_cdata_regex' => array(
7401
- 'error_message' => 'html comments',
7402
- 'regex' => '<!--',
7403
  ),
7404
  ),
7405
  'tag_spec' => array(
7406
- 'mandatory_parent' => 'amp-consent',
7407
- 'requires_extension' => array(
7408
- 'amp-consent',
 
 
 
 
 
7409
  ),
7410
- 'spec_name' => 'amp-consent extension .json script',
7411
- 'unique' => true,
7412
  ),
7413
  ),
7414
  array(
7415
  'attr_spec_list' => array(
7416
  'async' => array(
7417
  'mandatory' => true,
7418
- 'value' => '',
 
 
7419
  ),
7420
  'nonce' => array(),
7421
  'type' => array(
7422
- 'value_casei' => 'text/javascript',
 
 
7423
  ),
7424
  ),
7425
  'tag_spec' => array(
7426
  'extension_spec' => array(
7427
- 'allowed_versions' => array(
 
 
 
7428
  '0.1',
7429
  'latest',
7430
  ),
7431
- 'deprecated_allow_duplicates' => true,
7432
- 'name' => 'amp-dailymotion',
7433
- 'requires_usage' => 2,
7434
  ),
7435
  ),
7436
  ),
@@ -7438,72 +10306,106 @@ class AMP_Allowed_Tags_Generated {
7438
  'attr_spec_list' => array(
7439
  'async' => array(
7440
  'mandatory' => true,
7441
- 'value' => '',
 
 
7442
  ),
7443
  'nonce' => array(),
7444
  'type' => array(
7445
- 'value_casei' => 'text/javascript',
 
 
7446
  ),
7447
  ),
7448
  'tag_spec' => array(
7449
  'extension_spec' => array(
7450
- 'allowed_versions' => array(
 
 
 
7451
  '0.1',
7452
  'latest',
7453
  ),
7454
- 'name' => 'amp-date-picker',
7455
  ),
7456
  ),
7457
  ),
7458
  array(
7459
- 'attr_spec_list' => array(),
 
 
 
 
 
 
 
 
 
 
 
 
 
7460
  'tag_spec' => array(
7461
  'extension_spec' => array(
7462
- 'allowed_versions' => array(
 
 
 
7463
  '0.1',
7464
  'latest',
7465
  ),
7466
- 'name' => 'amp-document-recommendations',
7467
  ),
7468
  ),
7469
  ),
7470
  array(
7471
  'attr_spec_list' => array(
7472
- 'recommendations' => array(
7473
- 'dispatch_key' => 3,
7474
  'mandatory' => true,
7475
- 'value_casei' => 'application/json',
 
 
 
 
 
 
 
 
7476
  ),
7477
  ),
7478
  'tag_spec' => array(
7479
- 'mandatory_parent' => 'amp-document-recommendations',
7480
- 'requires_extension' => array(
7481
- 'amp-document-recommendations',
 
 
 
 
7482
  ),
7483
- 'spec_name' => 'amp-document-recommendations extension .json configuration',
7484
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-document-recommendations',
7485
  ),
7486
  ),
7487
  array(
7488
  'attr_spec_list' => array(
7489
  'async' => array(
7490
  'mandatory' => true,
7491
- 'value' => '',
 
 
7492
  ),
7493
  'nonce' => array(),
7494
  'type' => array(
7495
- 'value_casei' => 'text/javascript',
 
 
7496
  ),
7497
  ),
7498
  'tag_spec' => array(
7499
  'extension_spec' => array(
7500
- 'allowed_versions' => array(
 
 
 
7501
  '0.1',
7502
  'latest',
7503
  ),
7504
- 'deprecated_allow_duplicates' => true,
7505
- 'name' => 'amp-dynamic-css-classes',
7506
- 'requires_usage' => 3,
7507
  ),
7508
  ),
7509
  ),
@@ -7511,22 +10413,24 @@ class AMP_Allowed_Tags_Generated {
7511
  'attr_spec_list' => array(
7512
  'async' => array(
7513
  'mandatory' => true,
7514
- 'value' => '',
 
 
7515
  ),
7516
  'nonce' => array(),
7517
  'type' => array(
7518
- 'value_casei' => 'text/javascript',
 
 
7519
  ),
7520
  ),
7521
  'tag_spec' => array(
7522
  'extension_spec' => array(
7523
- 'allowed_versions' => array(
 
7524
  '0.1',
7525
  'latest',
7526
  ),
7527
- 'deprecated_allow_duplicates' => true,
7528
- 'name' => 'amp-experiment',
7529
- 'requires_usage' => 2,
7530
  ),
7531
  ),
7532
  ),
@@ -7536,7 +10440,9 @@ class AMP_Allowed_Tags_Generated {
7536
  'type' => array(
7537
  'dispatch_key' => 3,
7538
  'mandatory' => true,
7539
- 'value_casei' => 'application/json',
 
 
7540
  ),
7541
  ),
7542
  'cdata' => array(
@@ -7546,29 +10452,38 @@ class AMP_Allowed_Tags_Generated {
7546
  ),
7547
  ),
7548
  'tag_spec' => array(
7549
- 'mandatory_parent' => 'amp-experiment',
7550
- 'spec_name' => 'amp-experiment extension .json script',
7551
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-experiment',
 
 
 
7552
  ),
7553
  ),
7554
  array(
7555
  'attr_spec_list' => array(
7556
  'async' => array(
7557
  'mandatory' => true,
7558
- 'value' => '',
 
 
7559
  ),
7560
  'nonce' => array(),
7561
  'type' => array(
7562
- 'value_casei' => 'text/javascript',
 
 
7563
  ),
7564
  ),
7565
  'tag_spec' => array(
7566
  'extension_spec' => array(
7567
- 'allowed_versions' => array(
 
 
 
7568
  '0.1',
7569
  'latest',
7570
  ),
7571
- 'name' => 'amp-facebook-comments',
7572
  ),
7573
  ),
7574
  ),
@@ -7576,20 +10491,24 @@ class AMP_Allowed_Tags_Generated {
7576
  'attr_spec_list' => array(
7577
  'async' => array(
7578
  'mandatory' => true,
7579
- 'value' => '',
 
 
7580
  ),
7581
  'nonce' => array(),
7582
  'type' => array(
7583
- 'value_casei' => 'text/javascript',
 
 
7584
  ),
7585
  ),
7586
  'tag_spec' => array(
7587
  'extension_spec' => array(
7588
- 'allowed_versions' => array(
 
7589
  '0.1',
7590
  'latest',
7591
  ),
7592
- 'name' => 'amp-facebook-like',
7593
  ),
7594
  ),
7595
  ),
@@ -7597,20 +10516,24 @@ class AMP_Allowed_Tags_Generated {
7597
  'attr_spec_list' => array(
7598
  'async' => array(
7599
  'mandatory' => true,
7600
- 'value' => '',
 
 
7601
  ),
7602
  'nonce' => array(),
7603
  'type' => array(
7604
- 'value_casei' => 'text/javascript',
 
 
7605
  ),
7606
  ),
7607
  'tag_spec' => array(
7608
  'extension_spec' => array(
7609
- 'allowed_versions' => array(
 
7610
  '0.1',
7611
  'latest',
7612
  ),
7613
- 'name' => 'amp-facebook-page',
7614
  ),
7615
  ),
7616
  ),
@@ -7618,22 +10541,24 @@ class AMP_Allowed_Tags_Generated {
7618
  'attr_spec_list' => array(
7619
  'async' => array(
7620
  'mandatory' => true,
7621
- 'value' => '',
 
 
7622
  ),
7623
  'nonce' => array(),
7624
  'type' => array(
7625
- 'value_casei' => 'text/javascript',
 
 
7626
  ),
7627
  ),
7628
  'tag_spec' => array(
7629
  'extension_spec' => array(
7630
- 'allowed_versions' => array(
 
7631
  '0.1',
7632
  'latest',
7633
  ),
7634
- 'deprecated_allow_duplicates' => true,
7635
- 'name' => 'amp-facebook',
7636
- 'requires_usage' => 2,
7637
  ),
7638
  ),
7639
  ),
@@ -7641,22 +10566,26 @@ class AMP_Allowed_Tags_Generated {
7641
  'attr_spec_list' => array(
7642
  'async' => array(
7643
  'mandatory' => true,
7644
- 'value' => '',
 
 
7645
  ),
7646
  'nonce' => array(),
7647
  'type' => array(
7648
- 'value_casei' => 'text/javascript',
 
 
7649
  ),
7650
  ),
7651
  'tag_spec' => array(
7652
  'extension_spec' => array(
7653
- 'allowed_versions' => array(
 
 
 
7654
  '0.1',
7655
  'latest',
7656
  ),
7657
- 'deprecated_allow_duplicates' => true,
7658
- 'name' => 'amp-fit-text',
7659
- 'requires_usage' => 2,
7660
  ),
7661
  ),
7662
  ),
@@ -7664,22 +10593,24 @@ class AMP_Allowed_Tags_Generated {
7664
  'attr_spec_list' => array(
7665
  'async' => array(
7666
  'mandatory' => true,
7667
- 'value' => '',
 
 
7668
  ),
7669
  'nonce' => array(),
7670
  'type' => array(
7671
- 'value_casei' => 'text/javascript',
 
 
7672
  ),
7673
  ),
7674
  'tag_spec' => array(
7675
  'extension_spec' => array(
7676
- 'allowed_versions' => array(
 
7677
  '0.1',
7678
  'latest',
7679
  ),
7680
- 'deprecated_allow_duplicates' => true,
7681
- 'name' => 'amp-font',
7682
- 'requires_usage' => 2,
7683
  ),
7684
  ),
7685
  ),
@@ -7687,22 +10618,26 @@ class AMP_Allowed_Tags_Generated {
7687
  'attr_spec_list' => array(
7688
  'async' => array(
7689
  'mandatory' => true,
7690
- 'value' => '',
 
 
7691
  ),
7692
  'nonce' => array(),
7693
  'type' => array(
7694
- 'value_casei' => 'text/javascript',
 
 
7695
  ),
7696
  ),
7697
  'tag_spec' => array(
7698
  'extension_spec' => array(
7699
- 'allowed_versions' => array(
 
 
 
7700
  '0.1',
7701
  'latest',
7702
  ),
7703
- 'deprecated_allow_duplicates' => true,
7704
- 'name' => 'amp-form',
7705
- 'requires_usage' => 2,
7706
  ),
7707
  ),
7708
  ),
@@ -7710,21 +10645,24 @@ class AMP_Allowed_Tags_Generated {
7710
  'attr_spec_list' => array(
7711
  'async' => array(
7712
  'mandatory' => true,
7713
- 'value' => '',
 
 
7714
  ),
7715
  'nonce' => array(),
7716
  'type' => array(
7717
- 'value_casei' => 'text/javascript',
 
 
7718
  ),
7719
  ),
7720
  'tag_spec' => array(
7721
  'extension_spec' => array(
7722
- 'allowed_versions' => array(
 
7723
  '0.1',
7724
  'latest',
7725
  ),
7726
- 'name' => 'amp-fx-collection',
7727
- 'requires_usage' => 3,
7728
  ),
7729
  ),
7730
  ),
@@ -7732,22 +10670,24 @@ class AMP_Allowed_Tags_Generated {
7732
  'attr_spec_list' => array(
7733
  'async' => array(
7734
  'mandatory' => true,
7735
- 'value' => '',
 
 
7736
  ),
7737
  'nonce' => array(),
7738
  'type' => array(
7739
- 'value_casei' => 'text/javascript',
 
 
7740
  ),
7741
  ),
7742
  'tag_spec' => array(
7743
  'extension_spec' => array(
7744
- 'allowed_versions' => array(
 
7745
  '0.1',
7746
  'latest',
7747
  ),
7748
- 'deprecated_allow_duplicates' => true,
7749
- 'name' => 'amp-fx-flying-carpet',
7750
- 'requires_usage' => 2,
7751
  ),
7752
  ),
7753
  ),
@@ -7755,22 +10695,25 @@ class AMP_Allowed_Tags_Generated {
7755
  'attr_spec_list' => array(
7756
  'async' => array(
7757
  'mandatory' => true,
7758
- 'value' => '',
 
 
7759
  ),
7760
  'nonce' => array(),
7761
  'type' => array(
7762
- 'value_casei' => 'text/javascript',
 
 
7763
  ),
7764
  ),
7765
  'tag_spec' => array(
7766
  'extension_spec' => array(
7767
- 'allowed_versions' => array(
 
 
7768
  '0.1',
7769
  'latest',
7770
  ),
7771
- 'deprecated_allow_duplicates' => true,
7772
- 'name' => 'amp-gfycat',
7773
- 'requires_usage' => 2,
7774
  ),
7775
  ),
7776
  ),
@@ -7778,20 +10721,26 @@ class AMP_Allowed_Tags_Generated {
7778
  'attr_spec_list' => array(
7779
  'async' => array(
7780
  'mandatory' => true,
7781
- 'value' => '',
 
 
7782
  ),
7783
  'nonce' => array(),
7784
  'type' => array(
7785
- 'value_casei' => 'text/javascript',
 
 
7786
  ),
7787
  ),
7788
  'tag_spec' => array(
7789
  'extension_spec' => array(
7790
- 'allowed_versions' => array(
 
 
 
7791
  '0.1',
7792
  'latest',
7793
  ),
7794
- 'name' => 'amp-gist',
7795
  ),
7796
  ),
7797
  ),
@@ -7799,20 +10748,26 @@ class AMP_Allowed_Tags_Generated {
7799
  'attr_spec_list' => array(
7800
  'async' => array(
7801
  'mandatory' => true,
7802
- 'value' => '',
 
 
7803
  ),
7804
  'nonce' => array(),
7805
  'type' => array(
7806
- 'value_casei' => 'text/javascript',
 
 
7807
  ),
7808
  ),
7809
  'tag_spec' => array(
7810
  'extension_spec' => array(
7811
- 'allowed_versions' => array(
 
 
 
7812
  '0.1',
7813
  'latest',
7814
  ),
7815
- 'name' => 'amp-hulu',
7816
  ),
7817
  ),
7818
  ),
@@ -7820,22 +10775,25 @@ class AMP_Allowed_Tags_Generated {
7820
  'attr_spec_list' => array(
7821
  'async' => array(
7822
  'mandatory' => true,
7823
- 'value' => '',
 
 
7824
  ),
7825
  'nonce' => array(),
7826
  'type' => array(
7827
- 'value_casei' => 'text/javascript',
 
 
7828
  ),
7829
  ),
7830
  'tag_spec' => array(
7831
  'extension_spec' => array(
7832
- 'allowed_versions' => array(
 
 
7833
  '0.1',
7834
  'latest',
7835
  ),
7836
- 'deprecated_allow_duplicates' => true,
7837
- 'name' => 'amp-iframe',
7838
- 'requires_usage' => 2,
7839
  ),
7840
  ),
7841
  ),
@@ -7843,20 +10801,26 @@ class AMP_Allowed_Tags_Generated {
7843
  'attr_spec_list' => array(
7844
  'async' => array(
7845
  'mandatory' => true,
7846
- 'value' => '',
 
 
7847
  ),
7848
  'nonce' => array(),
7849
  'type' => array(
7850
- 'value_casei' => 'text/javascript',
 
 
7851
  ),
7852
  ),
7853
  'tag_spec' => array(
7854
  'extension_spec' => array(
7855
- 'allowed_versions' => array(
 
 
 
7856
  '0.1',
7857
  'latest',
7858
  ),
7859
- 'name' => 'amp-ima-video',
7860
  ),
7861
  ),
7862
  ),
@@ -7864,22 +10828,26 @@ class AMP_Allowed_Tags_Generated {
7864
  'attr_spec_list' => array(
7865
  'async' => array(
7866
  'mandatory' => true,
7867
- 'value' => '',
 
 
7868
  ),
7869
  'nonce' => array(),
7870
  'type' => array(
7871
- 'value_casei' => 'text/javascript',
 
 
7872
  ),
7873
  ),
7874
  'tag_spec' => array(
7875
  'extension_spec' => array(
7876
- 'allowed_versions' => array(
 
 
 
7877
  '0.1',
7878
  'latest',
7879
  ),
7880
- 'deprecated_allow_duplicates' => true,
7881
- 'name' => 'amp-image-lightbox',
7882
- 'requires_usage' => 2,
7883
  ),
7884
  ),
7885
  ),
@@ -7887,20 +10855,25 @@ class AMP_Allowed_Tags_Generated {
7887
  'attr_spec_list' => array(
7888
  'async' => array(
7889
  'mandatory' => true,
7890
- 'value' => '',
 
 
7891
  ),
7892
  'nonce' => array(),
7893
  'type' => array(
7894
- 'value_casei' => 'text/javascript',
 
 
7895
  ),
7896
  ),
7897
  'tag_spec' => array(
7898
  'extension_spec' => array(
7899
- 'allowed_versions' => array(
 
 
7900
  '0.1',
7901
  'latest',
7902
  ),
7903
- 'name' => 'amp-imgur',
7904
  ),
7905
  ),
7906
  ),
@@ -7908,22 +10881,26 @@ class AMP_Allowed_Tags_Generated {
7908
  'attr_spec_list' => array(
7909
  'async' => array(
7910
  'mandatory' => true,
7911
- 'value' => '',
 
 
7912
  ),
7913
  'nonce' => array(),
7914
  'type' => array(
7915
- 'value_casei' => 'text/javascript',
 
 
7916
  ),
7917
  ),
7918
  'tag_spec' => array(
7919
  'extension_spec' => array(
7920
- 'allowed_versions' => array(
 
 
 
7921
  '0.1',
7922
  'latest',
7923
  ),
7924
- 'deprecated_allow_duplicates' => true,
7925
- 'name' => 'amp-instagram',
7926
- 'requires_usage' => 2,
7927
  ),
7928
  ),
7929
  ),
@@ -7931,22 +10908,26 @@ class AMP_Allowed_Tags_Generated {
7931
  'attr_spec_list' => array(
7932
  'async' => array(
7933
  'mandatory' => true,
7934
- 'value' => '',
 
 
7935
  ),
7936
  'nonce' => array(),
7937
  'type' => array(
7938
- 'value_casei' => 'text/javascript',
 
 
7939
  ),
7940
  ),
7941
  'tag_spec' => array(
7942
  'extension_spec' => array(
7943
- 'allowed_versions' => array(
 
 
 
7944
  '0.1',
7945
  'latest',
7946
  ),
7947
- 'deprecated_allow_duplicates' => true,
7948
- 'name' => 'amp-install-serviceworker',
7949
- 'requires_usage' => 2,
7950
  ),
7951
  ),
7952
  ),
@@ -7954,44 +10935,52 @@ class AMP_Allowed_Tags_Generated {
7954
  'attr_spec_list' => array(
7955
  'async' => array(
7956
  'mandatory' => true,
7957
- 'value' => '',
 
 
7958
  ),
7959
  'nonce' => array(),
7960
  'type' => array(
7961
- 'value_casei' => 'text/javascript',
 
 
7962
  ),
7963
  ),
7964
  'tag_spec' => array(
7965
  'extension_spec' => array(
7966
- 'allowed_versions' => array(
 
 
7967
  '0.1',
7968
  'latest',
7969
  ),
7970
- 'name' => 'amp-izlesene',
7971
- 'requires_usage' => 2,
7972
  ),
 
 
7973
  ),
7974
  ),
7975
  array(
7976
  'attr_spec_list' => array(
7977
  'async' => array(
7978
  'mandatory' => true,
7979
- 'value' => '',
 
 
7980
  ),
7981
  'nonce' => array(),
7982
  'type' => array(
7983
- 'value_casei' => 'text/javascript',
 
 
7984
  ),
7985
  ),
7986
  'tag_spec' => array(
7987
  'extension_spec' => array(
7988
- 'allowed_versions' => array(
 
7989
  '0.1',
7990
  'latest',
7991
  ),
7992
- 'deprecated_allow_duplicates' => true,
7993
- 'name' => 'amp-jwplayer',
7994
- 'requires_usage' => 2,
7995
  ),
7996
  ),
7997
  ),
@@ -7999,22 +10988,24 @@ class AMP_Allowed_Tags_Generated {
7999
  'attr_spec_list' => array(
8000
  'async' => array(
8001
  'mandatory' => true,
8002
- 'value' => '',
 
 
8003
  ),
8004
  'nonce' => array(),
8005
  'type' => array(
8006
- 'value_casei' => 'text/javascript',
 
 
8007
  ),
8008
  ),
8009
  'tag_spec' => array(
8010
  'extension_spec' => array(
8011
- 'allowed_versions' => array(
 
8012
  '0.1',
8013
  'latest',
8014
  ),
8015
- 'deprecated_allow_duplicates' => true,
8016
- 'name' => 'amp-kaltura-player',
8017
- 'requires_usage' => 2,
8018
  ),
8019
  ),
8020
  ),
@@ -8022,21 +11013,31 @@ class AMP_Allowed_Tags_Generated {
8022
  'attr_spec_list' => array(
8023
  'async' => array(
8024
  'mandatory' => true,
8025
- 'value' => '',
 
 
8026
  ),
8027
  'nonce' => array(),
8028
  'type' => array(
8029
- 'value_casei' => 'text/javascript',
 
 
8030
  ),
8031
  ),
8032
  'tag_spec' => array(
8033
  'extension_spec' => array(
8034
- 'allowed_versions' => array(
 
 
 
 
 
 
 
8035
  '0.1',
 
8036
  'latest',
8037
  ),
8038
- 'name' => 'amp-lightbox-gallery',
8039
- 'requires_usage' => 3,
8040
  ),
8041
  ),
8042
  ),
@@ -8044,45 +11045,68 @@ class AMP_Allowed_Tags_Generated {
8044
  'attr_spec_list' => array(
8045
  'async' => array(
8046
  'mandatory' => true,
8047
- 'value' => '',
 
 
8048
  ),
8049
  'nonce' => array(),
8050
  'type' => array(
8051
- 'value_casei' => 'text/javascript',
 
 
8052
  ),
8053
  ),
8054
  'tag_spec' => array(
8055
  'extension_spec' => array(
8056
- 'allowed_versions' => array(
 
8057
  '0.1',
8058
  'latest',
8059
  ),
8060
- 'deprecated_allow_duplicates' => true,
8061
- 'name' => 'amp-lightbox',
8062
- 'requires_usage' => 2,
8063
  ),
8064
  ),
8065
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8066
  array(
8067
  'attr_spec_list' => array(
8068
  'async' => array(
8069
  'mandatory' => true,
8070
- 'value' => '',
 
 
8071
  ),
8072
  'nonce' => array(),
8073
  'type' => array(
8074
- 'value_casei' => 'text/javascript',
 
 
8075
  ),
8076
  ),
8077
  'tag_spec' => array(
8078
  'extension_spec' => array(
8079
- 'allowed_versions' => array(
 
8080
  '0.1',
8081
- 'latest',
8082
- ),
8083
- 'deprecated_allow_duplicates' => true,
8084
- 'name' => 'amp-list',
8085
- 'requires_usage' => 2,
8086
  ),
8087
  ),
8088
  ),
@@ -8090,44 +11114,51 @@ class AMP_Allowed_Tags_Generated {
8090
  'attr_spec_list' => array(
8091
  'async' => array(
8092
  'mandatory' => true,
8093
- 'value' => '',
 
 
8094
  ),
8095
  'nonce' => array(),
8096
  'type' => array(
8097
- 'value_casei' => 'text/javascript',
 
 
8098
  ),
8099
  ),
8100
  'tag_spec' => array(
8101
  'extension_spec' => array(
8102
- 'allowed_versions' => array(
 
 
 
8103
  '0.1',
8104
  'latest',
8105
  ),
8106
- 'name' => 'amp-live-list',
8107
- 'requires_usage' => 2,
8108
  ),
8109
- 'mandatory_parent' => 'head',
8110
- 'unique_warning' => true,
8111
  ),
8112
  ),
8113
  array(
8114
  'attr_spec_list' => array(
8115
  'async' => array(
8116
  'mandatory' => true,
8117
- 'value' => '',
 
 
8118
  ),
8119
  'nonce' => array(),
8120
  'type' => array(
8121
- 'value_casei' => 'text/javascript',
 
 
8122
  ),
8123
  ),
8124
  'tag_spec' => array(
8125
  'extension_spec' => array(
8126
- 'allowed_versions' => array(
 
8127
  '0.1',
8128
  'latest',
8129
  ),
8130
- 'name' => 'amp-mathml',
8131
  ),
8132
  ),
8133
  ),
@@ -8135,23 +11166,24 @@ class AMP_Allowed_Tags_Generated {
8135
  'attr_spec_list' => array(
8136
  'async' => array(
8137
  'mandatory' => true,
8138
- 'value' => '',
 
 
8139
  ),
8140
  'nonce' => array(),
8141
  'type' => array(
8142
- 'value_casei' => 'text/javascript',
 
 
8143
  ),
8144
  ),
8145
  'tag_spec' => array(
8146
  'extension_spec' => array(
8147
- 'allowed_versions' => array(
 
8148
  '0.1',
8149
  'latest',
8150
  ),
8151
- 'deprecated_allow_duplicates' => true,
8152
- 'is_custom_template' => true,
8153
- 'name' => 'amp-mustache',
8154
- 'requires_usage' => 2,
8155
  ),
8156
  ),
8157
  ),
@@ -8159,20 +11191,24 @@ class AMP_Allowed_Tags_Generated {
8159
  'attr_spec_list' => array(
8160
  'async' => array(
8161
  'mandatory' => true,
8162
- 'value' => '',
 
 
8163
  ),
8164
  'nonce' => array(),
8165
  'type' => array(
8166
- 'value_casei' => 'text/javascript',
 
 
8167
  ),
8168
  ),
8169
  'tag_spec' => array(
8170
  'extension_spec' => array(
8171
- 'allowed_versions' => array(
 
8172
  '0.1',
8173
  'latest',
8174
  ),
8175
- 'name' => 'amp-nexxtv-player',
8176
  ),
8177
  ),
8178
  ),
@@ -8180,22 +11216,26 @@ class AMP_Allowed_Tags_Generated {
8180
  'attr_spec_list' => array(
8181
  'async' => array(
8182
  'mandatory' => true,
8183
- 'value' => '',
 
 
8184
  ),
8185
  'nonce' => array(),
8186
  'type' => array(
8187
- 'value_casei' => 'text/javascript',
 
 
8188
  ),
8189
  ),
8190
  'tag_spec' => array(
8191
  'extension_spec' => array(
8192
- 'allowed_versions' => array(
 
 
 
8193
  '0.1',
8194
  'latest',
8195
  ),
8196
- 'deprecated_allow_duplicates' => true,
8197
- 'name' => 'amp-o2-player',
8198
- 'requires_usage' => 2,
8199
  ),
8200
  ),
8201
  ),
@@ -8203,20 +11243,24 @@ class AMP_Allowed_Tags_Generated {
8203
  'attr_spec_list' => array(
8204
  'async' => array(
8205
  'mandatory' => true,
8206
- 'value' => '',
 
 
8207
  ),
8208
  'nonce' => array(),
8209
  'type' => array(
8210
- 'value_casei' => 'text/javascript',
 
 
8211
  ),
8212
  ),
8213
  'tag_spec' => array(
8214
  'extension_spec' => array(
8215
- 'allowed_versions' => array(
 
8216
  '0.1',
8217
  'latest',
8218
  ),
8219
- 'name' => 'amp-ooyala-player',
8220
  ),
8221
  ),
8222
  ),
@@ -8224,22 +11268,24 @@ class AMP_Allowed_Tags_Generated {
8224
  'attr_spec_list' => array(
8225
  'async' => array(
8226
  'mandatory' => true,
8227
- 'value' => '',
 
 
8228
  ),
8229
  'nonce' => array(),
8230
  'type' => array(
8231
- 'value_casei' => 'text/javascript',
 
 
8232
  ),
8233
  ),
8234
  'tag_spec' => array(
8235
  'extension_spec' => array(
8236
- 'allowed_versions' => array(
 
8237
  '0.1',
8238
  'latest',
8239
  ),
8240
- 'deprecated_allow_duplicates' => true,
8241
- 'name' => 'amp-pinterest',
8242
- 'requires_usage' => 2,
8243
  ),
8244
  ),
8245
  ),
@@ -8247,20 +11293,24 @@ class AMP_Allowed_Tags_Generated {
8247
  'attr_spec_list' => array(
8248
  'async' => array(
8249
  'mandatory' => true,
8250
- 'value' => '',
 
 
8251
  ),
8252
  'nonce' => array(),
8253
  'type' => array(
8254
- 'value_casei' => 'text/javascript',
 
 
8255
  ),
8256
  ),
8257
  'tag_spec' => array(
8258
  'extension_spec' => array(
8259
- 'allowed_versions' => array(
 
8260
  '0.1',
8261
  'latest',
8262
  ),
8263
- 'name' => 'amp-playbuzz',
8264
  ),
8265
  ),
8266
  ),
@@ -8268,20 +11318,26 @@ class AMP_Allowed_Tags_Generated {
8268
  'attr_spec_list' => array(
8269
  'async' => array(
8270
  'mandatory' => true,
8271
- 'value' => '',
 
 
8272
  ),
8273
  'nonce' => array(),
8274
  'type' => array(
8275
- 'value_casei' => 'text/javascript',
 
 
8276
  ),
8277
  ),
8278
  'tag_spec' => array(
8279
  'extension_spec' => array(
8280
- 'allowed_versions' => array(
 
 
 
8281
  '0.1',
8282
  'latest',
8283
  ),
8284
- 'name' => 'amp-position-observer',
8285
  ),
8286
  ),
8287
  ),
@@ -8289,22 +11345,25 @@ class AMP_Allowed_Tags_Generated {
8289
  'attr_spec_list' => array(
8290
  'async' => array(
8291
  'mandatory' => true,
8292
- 'value' => '',
 
 
8293
  ),
8294
  'nonce' => array(),
8295
  'type' => array(
8296
- 'value_casei' => 'text/javascript',
 
 
8297
  ),
8298
  ),
8299
  'tag_spec' => array(
8300
  'extension_spec' => array(
8301
- 'allowed_versions' => array(
 
 
8302
  '0.1',
8303
  'latest',
8304
  ),
8305
- 'deprecated_allow_duplicates' => true,
8306
- 'name' => 'amp-reach-player',
8307
- 'requires_usage' => 2,
8308
  ),
8309
  ),
8310
  ),
@@ -8312,21 +11371,24 @@ class AMP_Allowed_Tags_Generated {
8312
  'attr_spec_list' => array(
8313
  'async' => array(
8314
  'mandatory' => true,
8315
- 'value' => '',
 
 
8316
  ),
8317
  'nonce' => array(),
8318
  'type' => array(
8319
- 'value_casei' => 'text/javascript',
 
 
8320
  ),
8321
  ),
8322
  'tag_spec' => array(
8323
  'extension_spec' => array(
8324
- 'allowed_versions' => array(
 
8325
  '0.1',
8326
  'latest',
8327
  ),
8328
- 'deprecated_allow_duplicates' => true,
8329
- 'name' => 'amp-reddit',
8330
  ),
8331
  ),
8332
  ),
@@ -8334,20 +11396,25 @@ class AMP_Allowed_Tags_Generated {
8334
  'attr_spec_list' => array(
8335
  'async' => array(
8336
  'mandatory' => true,
8337
- 'value' => '',
 
 
8338
  ),
8339
  'nonce' => array(),
8340
  'type' => array(
8341
- 'value_casei' => 'text/javascript',
 
 
8342
  ),
8343
  ),
8344
  'tag_spec' => array(
8345
  'extension_spec' => array(
8346
- 'allowed_versions' => array(
 
 
8347
  '0.1',
8348
  'latest',
8349
  ),
8350
- 'name' => 'amp-riddle-quiz',
8351
  ),
8352
  ),
8353
  ),
@@ -8355,21 +11422,26 @@ class AMP_Allowed_Tags_Generated {
8355
  'attr_spec_list' => array(
8356
  'async' => array(
8357
  'mandatory' => true,
8358
- 'value' => '',
 
 
8359
  ),
8360
  'nonce' => array(),
8361
  'type' => array(
8362
- 'value_casei' => 'text/javascript',
 
 
8363
  ),
8364
  ),
8365
  'tag_spec' => array(
8366
  'extension_spec' => array(
8367
- 'allowed_versions' => array(
 
 
 
8368
  '0.1',
8369
  'latest',
8370
  ),
8371
- 'name' => 'amp-selector',
8372
- 'requires_usage' => 2,
8373
  ),
8374
  ),
8375
  ),
@@ -8377,22 +11449,24 @@ class AMP_Allowed_Tags_Generated {
8377
  'attr_spec_list' => array(
8378
  'async' => array(
8379
  'mandatory' => true,
8380
- 'value' => '',
 
 
8381
  ),
8382
  'nonce' => array(),
8383
  'type' => array(
8384
- 'value_casei' => 'text/javascript',
 
 
8385
  ),
8386
  ),
8387
  'tag_spec' => array(
8388
  'extension_spec' => array(
8389
- 'allowed_versions' => array(
 
8390
  '0.1',
8391
  'latest',
8392
  ),
8393
- 'deprecated_allow_duplicates' => true,
8394
- 'name' => 'amp-sidebar',
8395
- 'requires_usage' => 2,
8396
  ),
8397
  ),
8398
  ),
@@ -8400,22 +11474,26 @@ class AMP_Allowed_Tags_Generated {
8400
  'attr_spec_list' => array(
8401
  'async' => array(
8402
  'mandatory' => true,
8403
- 'value' => '',
 
 
8404
  ),
8405
  'nonce' => array(),
8406
  'type' => array(
8407
- 'value_casei' => 'text/javascript',
 
 
8408
  ),
8409
  ),
8410
  'tag_spec' => array(
8411
  'extension_spec' => array(
8412
- 'allowed_versions' => array(
8413
- '0.1',
8414
- 'latest',
8415
- ),
8416
  'deprecated_allow_duplicates' => true,
8417
  'name' => 'amp-social-share',
8418
  'requires_usage' => 2,
 
 
 
 
8419
  ),
8420
  ),
8421
  ),
@@ -8423,22 +11501,26 @@ class AMP_Allowed_Tags_Generated {
8423
  'attr_spec_list' => array(
8424
  'async' => array(
8425
  'mandatory' => true,
8426
- 'value' => '',
 
 
8427
  ),
8428
  'nonce' => array(),
8429
  'type' => array(
8430
- 'value_casei' => 'text/javascript',
 
 
8431
  ),
8432
  ),
8433
  'tag_spec' => array(
8434
  'extension_spec' => array(
8435
- 'allowed_versions' => array(
8436
- '0.1',
8437
- 'latest',
8438
- ),
8439
  'deprecated_allow_duplicates' => true,
8440
  'name' => 'amp-soundcloud',
8441
  'requires_usage' => 2,
 
 
 
 
8442
  ),
8443
  ),
8444
  ),
@@ -8446,22 +11528,26 @@ class AMP_Allowed_Tags_Generated {
8446
  'attr_spec_list' => array(
8447
  'async' => array(
8448
  'mandatory' => true,
8449
- 'value' => '',
 
 
8450
  ),
8451
  'nonce' => array(),
8452
  'type' => array(
8453
- 'value_casei' => 'text/javascript',
 
 
8454
  ),
8455
  ),
8456
  'tag_spec' => array(
8457
  'extension_spec' => array(
8458
- 'allowed_versions' => array(
8459
- '0.1',
8460
- 'latest',
8461
- ),
8462
  'deprecated_allow_duplicates' => true,
8463
  'name' => 'amp-springboard-player',
8464
  'requires_usage' => 2,
 
 
 
 
8465
  ),
8466
  ),
8467
  ),
@@ -8469,25 +11555,29 @@ class AMP_Allowed_Tags_Generated {
8469
  'attr_spec_list' => array(
8470
  'async' => array(
8471
  'mandatory' => true,
8472
- 'value' => '',
 
 
8473
  ),
8474
  'nonce' => array(),
8475
  'type' => array(
8476
- 'value_casei' => 'text/javascript',
 
 
8477
  ),
8478
  ),
8479
  'tag_spec' => array(
8480
  'extension_spec' => array(
8481
- 'allowed_versions' => array(
8482
- '0.1',
8483
- '1.0',
8484
- 'latest',
8485
- ),
8486
- 'deprecated_versions' => array(
8487
  '0.1',
8488
  ),
8489
  'name' => 'amp-sticky-ad',
8490
  'requires_usage' => 2,
 
 
 
 
 
8491
  ),
8492
  ),
8493
  ),
@@ -8495,20 +11585,24 @@ class AMP_Allowed_Tags_Generated {
8495
  'attr_spec_list' => array(
8496
  'async' => array(
8497
  'mandatory' => true,
8498
- 'value' => '',
 
 
8499
  ),
8500
  'nonce' => array(),
8501
  'type' => array(
8502
- 'value_casei' => 'text/javascript',
 
 
8503
  ),
8504
  ),
8505
  'tag_spec' => array(
8506
  'extension_spec' => array(
8507
- 'allowed_versions' => array(
 
8508
  '0.1',
8509
  'latest',
8510
  ),
8511
- 'name' => 'amp-story-auto-ads',
8512
  ),
8513
  ),
8514
  ),
@@ -8518,7 +11612,9 @@ class AMP_Allowed_Tags_Generated {
8518
  'type' => array(
8519
  'dispatch_key' => 3,
8520
  'mandatory' => true,
8521
- 'value_casei' => 'application/json',
 
 
8522
  ),
8523
  ),
8524
  'cdata' => array(
@@ -8540,42 +11636,97 @@ class AMP_Allowed_Tags_Generated {
8540
  'attr_spec_list' => array(
8541
  'async' => array(
8542
  'mandatory' => true,
8543
- 'value' => '',
 
 
8544
  ),
8545
  'nonce' => array(),
8546
  'type' => array(
8547
- 'value_casei' => 'text/javascript',
 
 
8548
  ),
8549
  ),
8550
  'tag_spec' => array(
8551
  'extension_spec' => array(
8552
- 'allowed_versions' => array(
 
8553
  '0.1',
 
8554
  'latest',
8555
  ),
8556
- 'name' => 'amp-story',
8557
  ),
8558
  ),
8559
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8560
  array(
8561
  'attr_spec_list' => array(
8562
  'async' => array(
8563
  'mandatory' => true,
8564
- 'value' => '',
 
 
8565
  ),
8566
  'nonce' => array(),
8567
  'type' => array(
8568
- 'value_casei' => 'text/javascript',
 
 
8569
  ),
8570
  ),
8571
  'tag_spec' => array(
8572
  'extension_spec' => array(
8573
- 'allowed_versions' => array(
 
 
8574
  '0.1',
8575
  'latest',
8576
  ),
8577
- 'name' => 'amp-subscriptions',
8578
- 'requires_usage' => 3,
8579
  ),
8580
  ),
8581
  ),
@@ -8584,12 +11735,16 @@ class AMP_Allowed_Tags_Generated {
8584
  'id' => array(
8585
  'dispatch_key' => 2,
8586
  'mandatory' => true,
8587
- 'value' => 'amp-subscriptions',
 
 
8588
  ),
8589
  'nonce' => array(),
8590
  'type' => array(
8591
  'mandatory' => true,
8592
- 'value_casei' => 'application/json',
 
 
8593
  ),
8594
  ),
8595
  'cdata' => array(
@@ -8611,21 +11766,25 @@ class AMP_Allowed_Tags_Generated {
8611
  'attr_spec_list' => array(
8612
  'async' => array(
8613
  'mandatory' => true,
8614
- 'value' => '',
 
 
8615
  ),
8616
  'nonce' => array(),
8617
  'type' => array(
8618
- 'value_casei' => 'text/javascript',
 
 
8619
  ),
8620
  ),
8621
  'tag_spec' => array(
8622
  'extension_spec' => array(
8623
- 'allowed_versions' => array(
 
 
8624
  '0.1',
8625
  'latest',
8626
  ),
8627
- 'name' => 'amp-subscriptions-google',
8628
- 'requires_usage' => 3,
8629
  ),
8630
  'requires_extension' => array(
8631
  'amp-subscriptions',
@@ -8636,20 +11795,24 @@ class AMP_Allowed_Tags_Generated {
8636
  'attr_spec_list' => array(
8637
  'async' => array(
8638
  'mandatory' => true,
8639
- 'value' => '',
 
 
8640
  ),
8641
  'nonce' => array(),
8642
  'type' => array(
8643
- 'value_casei' => 'text/javascript',
 
 
8644
  ),
8645
  ),
8646
  'tag_spec' => array(
8647
  'extension_spec' => array(
8648
- 'allowed_versions' => array(
 
8649
  '0.1',
8650
  'latest',
8651
  ),
8652
- 'name' => 'amp-timeago',
8653
  ),
8654
  ),
8655
  ),
@@ -8657,22 +11820,53 @@ class AMP_Allowed_Tags_Generated {
8657
  'attr_spec_list' => array(
8658
  'async' => array(
8659
  'mandatory' => true,
8660
- 'value' => '',
 
 
8661
  ),
8662
  'nonce' => array(),
8663
  'type' => array(
8664
- 'value_casei' => 'text/javascript',
 
 
8665
  ),
8666
  ),
8667
  'tag_spec' => array(
8668
  'extension_spec' => array(
8669
- 'allowed_versions' => array(
 
 
 
8670
  '0.1',
8671
  'latest',
8672
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8673
  'deprecated_allow_duplicates' => true,
8674
- 'name' => 'amp-twitter',
8675
  'requires_usage' => 2,
 
 
 
 
8676
  ),
8677
  ),
8678
  ),
@@ -8680,22 +11874,53 @@ class AMP_Allowed_Tags_Generated {
8680
  'attr_spec_list' => array(
8681
  'async' => array(
8682
  'mandatory' => true,
8683
- 'value' => '',
 
 
8684
  ),
8685
  'nonce' => array(),
8686
  'type' => array(
8687
- 'value_casei' => 'text/javascript',
 
 
8688
  ),
8689
  ),
8690
  'tag_spec' => array(
8691
  'extension_spec' => array(
8692
- 'allowed_versions' => array(
 
 
8693
  '0.1',
8694
  'latest',
8695
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8696
  'deprecated_allow_duplicates' => true,
8697
- 'name' => 'amp-user-notification',
8698
  'requires_usage' => 2,
 
 
 
 
8699
  ),
8700
  ),
8701
  ),
@@ -8703,45 +11928,51 @@ class AMP_Allowed_Tags_Generated {
8703
  'attr_spec_list' => array(
8704
  'async' => array(
8705
  'mandatory' => true,
8706
- 'value' => '',
 
 
8707
  ),
8708
  'nonce' => array(),
8709
  'type' => array(
8710
- 'value_casei' => 'text/javascript',
 
 
8711
  ),
8712
  ),
8713
  'tag_spec' => array(
8714
  'extension_spec' => array(
8715
- 'allowed_versions' => array(
 
 
 
8716
  '0.1',
8717
  'latest',
8718
  ),
8719
- 'name' => 'amp-video',
8720
- 'requires_usage' => 3,
8721
  ),
8722
- 'spec_name' => 'amp-video extension .js script',
8723
  ),
8724
  ),
8725
  array(
8726
  'attr_spec_list' => array(
8727
  'async' => array(
8728
  'mandatory' => true,
8729
- 'value' => '',
 
 
8730
  ),
8731
  'nonce' => array(),
8732
  'type' => array(
8733
- 'value_casei' => 'text/javascript',
 
 
8734
  ),
8735
  ),
8736
  'tag_spec' => array(
8737
  'extension_spec' => array(
8738
- 'allowed_versions' => array(
 
8739
  '0.1',
8740
  'latest',
8741
  ),
8742
- 'deprecated_allow_duplicates' => true,
8743
- 'name' => 'amp-vimeo',
8744
- 'requires_usage' => 2,
8745
  ),
8746
  ),
8747
  ),
@@ -8749,22 +11980,24 @@ class AMP_Allowed_Tags_Generated {
8749
  'attr_spec_list' => array(
8750
  'async' => array(
8751
  'mandatory' => true,
8752
- 'value' => '',
 
 
8753
  ),
8754
  'nonce' => array(),
8755
  'type' => array(
8756
- 'value_casei' => 'text/javascript',
 
 
8757
  ),
8758
  ),
8759
  'tag_spec' => array(
8760
  'extension_spec' => array(
8761
- 'allowed_versions' => array(
 
8762
  '0.1',
8763
  'latest',
8764
  ),
8765
- 'deprecated_allow_duplicates' => true,
8766
- 'name' => 'amp-vine',
8767
- 'requires_usage' => 2,
8768
  ),
8769
  ),
8770
  ),
@@ -8772,20 +12005,24 @@ class AMP_Allowed_Tags_Generated {
8772
  'attr_spec_list' => array(
8773
  'async' => array(
8774
  'mandatory' => true,
8775
- 'value' => '',
 
 
8776
  ),
8777
  'nonce' => array(),
8778
  'type' => array(
8779
- 'value_casei' => 'text/javascript',
 
 
8780
  ),
8781
  ),
8782
  'tag_spec' => array(
8783
  'extension_spec' => array(
8784
- 'allowed_versions' => array(
 
8785
  '0.1',
8786
  'latest',
8787
  ),
8788
- 'name' => 'amp-vk',
8789
  ),
8790
  ),
8791
  ),
@@ -8793,20 +12030,24 @@ class AMP_Allowed_Tags_Generated {
8793
  'attr_spec_list' => array(
8794
  'async' => array(
8795
  'mandatory' => true,
8796
- 'value' => '',
 
 
8797
  ),
8798
  'nonce' => array(),
8799
  'type' => array(
8800
- 'value_casei' => 'text/javascript',
 
 
8801
  ),
8802
  ),
8803
  'tag_spec' => array(
8804
  'extension_spec' => array(
8805
- 'allowed_versions' => array(
 
8806
  '0.1',
8807
  'latest',
8808
  ),
8809
- 'name' => 'amp-web-push',
8810
  ),
8811
  ),
8812
  ),
@@ -8814,43 +12055,52 @@ class AMP_Allowed_Tags_Generated {
8814
  'attr_spec_list' => array(
8815
  'async' => array(
8816
  'mandatory' => true,
8817
- 'value' => '',
 
 
8818
  ),
8819
  'nonce' => array(),
8820
  'type' => array(
8821
- 'value_casei' => 'text/javascript',
 
 
8822
  ),
8823
  ),
8824
  'tag_spec' => array(
8825
  'extension_spec' => array(
8826
- 'allowed_versions' => array(
 
8827
  '0.1',
8828
  'latest',
8829
  ),
8830
- 'name' => 'amp-wistia-player',
8831
  ),
 
8832
  ),
8833
  ),
8834
  array(
8835
  'attr_spec_list' => array(
8836
  'async' => array(
8837
  'mandatory' => true,
8838
- 'value' => '',
 
 
8839
  ),
8840
  'nonce' => array(),
8841
  'type' => array(
8842
- 'value_casei' => 'text/javascript',
 
 
8843
  ),
8844
  ),
8845
  'tag_spec' => array(
8846
  'extension_spec' => array(
8847
- 'allowed_versions' => array(
8848
- '0.1',
8849
- 'latest',
8850
- ),
8851
  'deprecated_allow_duplicates' => true,
8852
  'name' => 'amp-youtube',
8853
  'requires_usage' => 2,
 
 
 
 
8854
  ),
8855
  ),
8856
  ),
@@ -8867,7 +12117,9 @@ class AMP_Allowed_Tags_Generated {
8867
  array(
8868
  'attr_spec_list' => array(
8869
  'expanded' => array(
8870
- 'value' => '',
 
 
8871
  ),
8872
  ),
8873
  'tag_spec' => array(
@@ -8890,6 +12142,11 @@ class AMP_Allowed_Tags_Generated {
8890
  'name' => array(
8891
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
8892
  ),
 
 
 
 
 
8893
  'required' => array(),
8894
  'size' => array(),
8895
  ),
@@ -8936,6 +12193,7 @@ class AMP_Allowed_Tags_Generated {
8936
  'filter' => array(),
8937
  'flood-color' => array(),
8938
  'flood-opacity' => array(),
 
8939
  'font-family' => array(),
8940
  'font-size' => array(),
8941
  'font-size-adjust' => array(),
@@ -8992,6 +12250,29 @@ class AMP_Allowed_Tags_Generated {
8992
  ),
8993
  ),
8994
  'source' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8995
  array(
8996
  'attr_spec_list' => array(
8997
  '[src]' => array(),
@@ -9001,7 +12282,7 @@ class AMP_Allowed_Tags_Generated {
9001
  'blacklisted_value_regex' => '__amp_source_origin',
9002
  'value_url' => array(
9003
  'allow_relative' => true,
9004
- 'allowed_protocol' => array(
9005
  'https',
9006
  ),
9007
  ),
@@ -9023,7 +12304,7 @@ class AMP_Allowed_Tags_Generated {
9023
  'blacklisted_value_regex' => '__amp_source_origin',
9024
  'value_url' => array(
9025
  'allow_relative' => true,
9026
- 'allowed_protocol' => array(
9027
  'https',
9028
  ),
9029
  ),
@@ -9044,7 +12325,7 @@ class AMP_Allowed_Tags_Generated {
9044
  'mandatory' => true,
9045
  'value_url' => array(
9046
  'allow_relative' => true,
9047
- 'allowed_protocol' => array(
9048
  'https',
9049
  ),
9050
  ),
@@ -9067,7 +12348,7 @@ class AMP_Allowed_Tags_Generated {
9067
  'mandatory' => true,
9068
  'value_url' => array(
9069
  'allow_relative' => true,
9070
- 'allowed_protocol' => array(
9071
  'https',
9072
  ),
9073
  ),
@@ -9091,7 +12372,7 @@ class AMP_Allowed_Tags_Generated {
9091
  'blacklisted_value_regex' => '__amp_source_origin',
9092
  'value_url' => array(
9093
  'allow_relative' => true,
9094
- 'allowed_protocol' => array(
9095
  'https',
9096
  ),
9097
  ),
@@ -9168,11 +12449,15 @@ class AMP_Allowed_Tags_Generated {
9168
  'attr_spec_list' => array(
9169
  'amp-custom' => array(
9170
  'mandatory' => true,
9171
- 'value' => '',
 
 
9172
  ),
9173
  'nonce' => array(),
9174
  'type' => array(
9175
- 'value_casei' => 'text/css',
 
 
9176
  ),
9177
  ),
9178
  'cdata' => array(
@@ -9180,6 +12465,34 @@ class AMP_Allowed_Tags_Generated {
9180
  'error_message' => 'CSS !important',
9181
  'regex' => '!important',
9182
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9183
  'max_bytes' => 50000,
9184
  'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#maximum-size',
9185
  ),
@@ -9190,17 +12503,34 @@ class AMP_Allowed_Tags_Generated {
9190
  'unique' => true,
9191
  ),
9192
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9193
  array(
9194
  'attr_spec_list' => array(
9195
  'amp-boilerplate' => array(
9196
  'dispatch_key' => 3,
9197
  'mandatory' => true,
9198
- 'value' => '',
 
 
9199
  ),
9200
  'nonce' => array(),
9201
  ),
9202
  'cdata' => array(
9203
- 'cdata_regex' => '\\s*body{-webkit-animation:-amp-start\\s+8s\\s+steps\\(1,end\\)\\s+0s\\s+1\\s+normal\\s+both;-moz-animation:-amp-start\\s+8s\\s+steps\\(1,end\\)\\s+0s\\s+1\\s+normal\\s+both;-ms-animation:-amp-start\\s+8s\\s+steps\\(1,end\\)\\s+0s\\s+1\\s+normal\\s+both;animation:-amp-start\\s+8s\\s+steps\\(1,end\\)\\s+0s\\s+1\\s+normal\\s+both}@-webkit-keyframes\\s+-amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes\\s+-amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes\\s+-amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes\\s+-amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes\\s+-amp-start{from{visibility:hidden}to{visibility:visible}}\\s*',
9204
  ),
9205
  'tag_spec' => array(
9206
  'mandatory_alternatives' => 'head > style[amp-boilerplate]',
@@ -9215,12 +12545,14 @@ class AMP_Allowed_Tags_Generated {
9215
  'amp-boilerplate' => array(
9216
  'dispatch_key' => 3,
9217
  'mandatory' => true,
9218
- 'value' => '',
 
 
9219
  ),
9220
  'nonce' => array(),
9221
  ),
9222
  'cdata' => array(
9223
- 'cdata_regex' => '\\s*body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}\\s*',
9224
  ),
9225
  'tag_spec' => array(
9226
  'mandatory_alternatives' => 'noscript > style[amp-boilerplate]',
@@ -9236,10 +12568,29 @@ class AMP_Allowed_Tags_Generated {
9236
  'amp-keyframes' => array(
9237
  'dispatch_key' => 1,
9238
  'mandatory' => true,
9239
- 'value' => '',
 
 
9240
  ),
9241
  ),
9242
  'cdata' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9243
  'max_bytes' => 500000,
9244
  'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#keyframes-stylesheet',
9245
  ),
@@ -9289,6 +12640,7 @@ class AMP_Allowed_Tags_Generated {
9289
  'filter' => array(),
9290
  'flood-color' => array(),
9291
  'flood-opacity' => array(),
 
9292
  'font-family' => array(),
9293
  'font-size' => array(),
9294
  'font-size-adjust' => array(),
@@ -9324,6 +12676,9 @@ class AMP_Allowed_Tags_Generated {
9324
  'stroke-miterlimit' => array(),
9325
  'stroke-opacity' => array(),
9326
  'stroke-width' => array(),
 
 
 
9327
  'systemlanguage' => array(),
9328
  'text-anchor' => array(),
9329
  'text-decoration' => array(),
@@ -9331,7 +12686,10 @@ class AMP_Allowed_Tags_Generated {
9331
  'unicode-bidi' => array(),
9332
  'vector-effect' => array(),
9333
  'version' => array(
9334
- 'value_regex' => '(1.0|1.1)',
 
 
 
9335
  ),
9336
  'viewbox' => array(),
9337
  'visibility' => array(),
@@ -9375,6 +12733,7 @@ class AMP_Allowed_Tags_Generated {
9375
  'filter' => array(),
9376
  'flood-color' => array(),
9377
  'flood-opacity' => array(),
 
9378
  'font-family' => array(),
9379
  'font-size' => array(),
9380
  'font-size-adjust' => array(),
@@ -9456,6 +12815,7 @@ class AMP_Allowed_Tags_Generated {
9456
  'filter' => array(),
9457
  'flood-color' => array(),
9458
  'flood-opacity' => array(),
 
9459
  'font-family' => array(),
9460
  'font-size' => array(),
9461
  'font-size-adjust' => array(),
@@ -9517,7 +12877,10 @@ class AMP_Allowed_Tags_Generated {
9517
  'align' => array(),
9518
  'bgcolor' => array(),
9519
  'border' => array(
9520
- 'value_regex' => '0|1',
 
 
 
9521
  ),
9522
  'cellpadding' => array(),
9523
  'cellspacing' => array(),
@@ -9549,19 +12912,71 @@ class AMP_Allowed_Tags_Generated {
9549
  ),
9550
  ),
9551
  'template' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9552
  array(
9553
  'attr_spec_list' => array(
9554
  'type' => array(
9555
  'mandatory' => true,
9556
- 'value' => 'amp-mustache',
 
 
9557
  ),
9558
  ),
9559
  'tag_spec' => array(
9560
  'disallowed_ancestor' => array(
9561
  'template',
 
9562
  'amp-story-auto-ads',
9563
- 'form > div [submit-success][template]',
9564
- 'form > div [submit-error][template]',
 
 
9565
  ),
9566
  'requires_extension' => array(
9567
  'amp-mustache',
@@ -9571,12 +12986,25 @@ class AMP_Allowed_Tags_Generated {
9571
  array(
9572
  'attr_spec_list' => array(
9573
  'type' => array(
 
9574
  'mandatory' => true,
9575
- 'value' => 'amp-mustache',
 
 
9576
  ),
9577
  ),
9578
  'tag_spec' => array(
9579
  'mandatory_parent' => 'amp-story-auto-ads',
 
 
 
 
 
 
 
 
 
 
9580
  'requires_extension' => array(
9581
  'amp-mustache',
9582
  ),
@@ -9611,6 +13039,7 @@ class AMP_Allowed_Tags_Generated {
9611
  'filter' => array(),
9612
  'flood-color' => array(),
9613
  'flood-opacity' => array(),
 
9614
  'font-family' => array(),
9615
  'font-size' => array(),
9616
  'font-size-adjust' => array(),
@@ -9700,6 +13129,11 @@ class AMP_Allowed_Tags_Generated {
9700
  'name' => array(
9701
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
9702
  ),
 
 
 
 
 
9703
  'placeholder' => array(),
9704
  'readonly' => array(),
9705
  'required' => array(),
@@ -9740,6 +13174,7 @@ class AMP_Allowed_Tags_Generated {
9740
  'filter' => array(),
9741
  'flood-color' => array(),
9742
  'flood-opacity' => array(),
 
9743
  'font-family' => array(),
9744
  'font-size' => array(),
9745
  'font-size-adjust' => array(),
@@ -9796,8 +13231,7 @@ class AMP_Allowed_Tags_Generated {
9796
  ),
9797
  'value_url' => array(
9798
  'allow_empty' => false,
9799
- 'allow_relative' => true,
9800
- 'allowed_protocol' => array(
9801
  'http',
9802
  'https',
9803
  ),
@@ -9858,7 +13292,9 @@ class AMP_Allowed_Tags_Generated {
9858
  ),
9859
  'title' => array(
9860
  array(
9861
- 'attr_spec_list' => array(),
 
 
9862
  'tag_spec' => array(
9863
  'spec_name' => 'title',
9864
  ),
@@ -9895,10 +13331,17 @@ class AMP_Allowed_Tags_Generated {
9895
  array(
9896
  'attr_spec_list' => array(
9897
  'default' => array(
9898
- 'value' => '',
 
 
9899
  ),
9900
  'kind' => array(
9901
- 'value_regex' => '(captions|descriptions|chapters|metadata)',
 
 
 
 
 
9902
  ),
9903
  'label' => array(),
9904
  'src' => array(
@@ -9906,7 +13349,7 @@ class AMP_Allowed_Tags_Generated {
9906
  'mandatory' => true,
9907
  'value_url' => array(
9908
  'allow_relative' => false,
9909
- 'allowed_protocol' => array(
9910
  'https',
9911
  ),
9912
  ),
@@ -9921,11 +13364,15 @@ class AMP_Allowed_Tags_Generated {
9921
  array(
9922
  'attr_spec_list' => array(
9923
  'default' => array(
9924
- 'value' => '',
 
 
9925
  ),
9926
  'kind' => array(
9927
  'mandatory' => true,
9928
- 'value_casei' => 'subtitles',
 
 
9929
  ),
9930
  'label' => array(),
9931
  'src' => array(
@@ -9933,7 +13380,7 @@ class AMP_Allowed_Tags_Generated {
9933
  'mandatory' => true,
9934
  'value_url' => array(
9935
  'allow_relative' => false,
9936
- 'allowed_protocol' => array(
9937
  'https',
9938
  ),
9939
  ),
@@ -9950,10 +13397,17 @@ class AMP_Allowed_Tags_Generated {
9950
  array(
9951
  'attr_spec_list' => array(
9952
  'default' => array(
9953
- 'value' => '',
 
 
9954
  ),
9955
  'kind' => array(
9956
- 'value_regex' => '(captions|descriptions|chapters|metadata)',
 
 
 
 
 
9957
  ),
9958
  'label' => array(),
9959
  'src' => array(
@@ -9961,7 +13415,7 @@ class AMP_Allowed_Tags_Generated {
9961
  'mandatory' => true,
9962
  'value_url' => array(
9963
  'allow_relative' => false,
9964
- 'allowed_protocol' => array(
9965
  'https',
9966
  ),
9967
  ),
@@ -9976,11 +13430,15 @@ class AMP_Allowed_Tags_Generated {
9976
  array(
9977
  'attr_spec_list' => array(
9978
  'default' => array(
9979
- 'value' => '',
 
 
9980
  ),
9981
  'kind' => array(
9982
  'mandatory' => true,
9983
- 'value_casei' => 'subtitles',
 
 
9984
  ),
9985
  'label' => array(),
9986
  'src' => array(
@@ -9988,7 +13446,7 @@ class AMP_Allowed_Tags_Generated {
9988
  'mandatory' => true,
9989
  'value_url' => array(
9990
  'allow_relative' => false,
9991
- 'allowed_protocol' => array(
9992
  'https',
9993
  ),
9994
  ),
@@ -10008,10 +13466,17 @@ class AMP_Allowed_Tags_Generated {
10008
  '[src]' => array(),
10009
  '[srclang]' => array(),
10010
  'default' => array(
10011
- 'value' => '',
 
 
10012
  ),
10013
  'kind' => array(
10014
- 'value_regex' => '(captions|descriptions|chapters|metadata)',
 
 
 
 
 
10015
  ),
10016
  'label' => array(),
10017
  'src' => array(
@@ -10019,7 +13484,7 @@ class AMP_Allowed_Tags_Generated {
10019
  'mandatory' => true,
10020
  'value_url' => array(
10021
  'allow_relative' => false,
10022
- 'allowed_protocol' => array(
10023
  'https',
10024
  ),
10025
  ),
@@ -10037,11 +13502,15 @@ class AMP_Allowed_Tags_Generated {
10037
  '[src]' => array(),
10038
  '[srclang]' => array(),
10039
  'default' => array(
10040
- 'value' => '',
 
 
10041
  ),
10042
  'kind' => array(
10043
  'mandatory' => true,
10044
- 'value_casei' => 'subtitles',
 
 
10045
  ),
10046
  'label' => array(),
10047
  'src' => array(
@@ -10049,7 +13518,7 @@ class AMP_Allowed_Tags_Generated {
10049
  'mandatory' => true,
10050
  'value_url' => array(
10051
  'allow_relative' => false,
10052
- 'allowed_protocol' => array(
10053
  'https',
10054
  ),
10055
  ),
@@ -10069,10 +13538,17 @@ class AMP_Allowed_Tags_Generated {
10069
  '[src]' => array(),
10070
  '[srclang]' => array(),
10071
  'default' => array(
10072
- 'value' => '',
 
 
10073
  ),
10074
  'kind' => array(
10075
- 'value_regex' => '(captions|descriptions|chapters|metadata)',
 
 
 
 
 
10076
  ),
10077
  'label' => array(),
10078
  'src' => array(
@@ -10080,7 +13556,7 @@ class AMP_Allowed_Tags_Generated {
10080
  'mandatory' => true,
10081
  'value_url' => array(
10082
  'allow_relative' => false,
10083
- 'allowed_protocol' => array(
10084
  'https',
10085
  ),
10086
  ),
@@ -10098,11 +13574,15 @@ class AMP_Allowed_Tags_Generated {
10098
  '[src]' => array(),
10099
  '[srclang]' => array(),
10100
  'default' => array(
10101
- 'value' => '',
 
 
10102
  ),
10103
  'kind' => array(
10104
  'mandatory' => true,
10105
- 'value_casei' => 'subtitles',
 
 
10106
  ),
10107
  'label' => array(),
10108
  'src' => array(
@@ -10110,7 +13590,7 @@ class AMP_Allowed_Tags_Generated {
10110
  'mandatory' => true,
10111
  'value_url' => array(
10112
  'allow_relative' => false,
10113
- 'allowed_protocol' => array(
10114
  'https',
10115
  ),
10116
  ),
@@ -10130,11 +13610,15 @@ class AMP_Allowed_Tags_Generated {
10130
  '[src]' => array(),
10131
  '[srclang]' => array(),
10132
  'default' => array(
10133
- 'value' => '',
 
 
10134
  ),
10135
  'kind' => array(
10136
  'mandatory' => true,
10137
- 'value_casei' => 'subtitles',
 
 
10138
  ),
10139
  'label' => array(),
10140
  'src' => array(
@@ -10142,7 +13626,7 @@ class AMP_Allowed_Tags_Generated {
10142
  'mandatory' => true,
10143
  'value_url' => array(
10144
  'allow_relative' => false,
10145
- 'allowed_protocol' => array(
10146
  'https',
10147
  ),
10148
  ),
@@ -10183,6 +13667,7 @@ class AMP_Allowed_Tags_Generated {
10183
  'filter' => array(),
10184
  'flood-color' => array(),
10185
  'flood-opacity' => array(),
 
10186
  'font-family' => array(),
10187
  'font-size' => array(),
10188
  'font-size-adjust' => array(),
@@ -10236,8 +13721,7 @@ class AMP_Allowed_Tags_Generated {
10236
  ),
10237
  'value_url' => array(
10238
  'allow_empty' => false,
10239
- 'allow_relative' => true,
10240
- 'allowed_protocol' => array(
10241
  'http',
10242
  'https',
10243
  ),
@@ -10285,6 +13769,7 @@ class AMP_Allowed_Tags_Generated {
10285
  'filter' => array(),
10286
  'flood-color' => array(),
10287
  'flood-opacity' => array(),
 
10288
  'font-family' => array(),
10289
  'font-size' => array(),
10290
  'font-size-adjust' => array(),
@@ -10389,6 +13874,7 @@ class AMP_Allowed_Tags_Generated {
10389
  'filter' => array(),
10390
  'flood-color' => array(),
10391
  'flood-opacity' => array(),
 
10392
  'font-family' => array(),
10393
  'font-size' => array(),
10394
  'font-size-adjust' => array(),
@@ -10446,8 +13932,7 @@ class AMP_Allowed_Tags_Generated {
10446
  ),
10447
  'value_url' => array(
10448
  'allow_empty' => false,
10449
- 'allow_relative' => true,
10450
- 'allowed_protocol' => array(
10451
  'http',
10452
  'https',
10453
  ),
@@ -10490,7 +13975,7 @@ class AMP_Allowed_Tags_Generated {
10490
  'blacklisted_value_regex' => '__amp_source_origin',
10491
  'value_url' => array(
10492
  'allow_relative' => false,
10493
- 'allowed_protocol' => array(
10494
  'data',
10495
  'https',
10496
  ),
@@ -10555,12 +14040,6 @@ class AMP_Allowed_Tags_Generated {
10555
  'tag_spec' => array(),
10556
  ),
10557
  ),
10558
- 'xmp' => array(
10559
- array(
10560
- 'attr_spec_list' => array(),
10561
- 'tag_spec' => array(),
10562
- ),
10563
- ),
10564
  );
10565
 
10566
  private static $layout_allowed_attrs = array(
@@ -10627,7 +14106,7 @@ class AMP_Allowed_Tags_Generated {
10627
  'amp-access-style' => array(),
10628
  'amp-access-template' => array(),
10629
  'amp-fx' => array(
10630
- 'value_casei' => 'parallax',
10631
  ),
10632
  'aria-activedescendant' => array(),
10633
  'aria-atomic' => array(),
@@ -10673,10 +14152,14 @@ class AMP_Allowed_Tags_Generated {
10673
  'dir' => array(),
10674
  'draggable' => array(),
10675
  'fallback' => array(
10676
- 'value' => '',
 
 
10677
  ),
10678
  'hidden' => array(
10679
- 'value' => '',
 
 
10680
  ),
10681
  'i-amp-access-id' => array(),
10682
  'id' => array(
@@ -10689,11 +14172,12 @@ class AMP_Allowed_Tags_Generated {
10689
  'itemscope' => array(),
10690
  'itemtype' => array(),
10691
  'lang' => array(),
10692
- 'lightbox' => array(),
10693
  'on' => array(),
10694
  'overflow' => array(),
10695
  'placeholder' => array(
10696
- 'value' => '',
 
 
10697
  ),
10698
  'prefix' => array(),
10699
  'property' => array(),
@@ -10703,16 +14187,29 @@ class AMP_Allowed_Tags_Generated {
10703
  'resource' => array(),
10704
  'rev' => array(),
10705
  'role' => array(),
 
 
 
10706
  'subscriptions-action' => array(),
10707
  'subscriptions-actions' => array(
10708
- 'value' => '',
 
 
10709
  ),
 
10710
  'subscriptions-dialog' => array(
10711
- 'value' => '',
 
 
10712
  ),
10713
  'subscriptions-display' => array(),
10714
  'subscriptions-section' => array(
10715
- 'value_regex_casei' => '(actions|content|content-not-granted)',
 
 
 
 
 
10716
  ),
10717
  'subscriptions-service' => array(),
10718
  'tabindex' => array(),
@@ -10721,12 +14218,349 @@ class AMP_Allowed_Tags_Generated {
10721
  'typeof' => array(),
10722
  'validation-for' => array(),
10723
  'visible-when-invalid' => array(
10724
- 'value_regex' => '(badInput|customError|patternMismatch|rangeOverflow|rangeUnderflow|stepMismatch|tooLong|typeMismatch|valueMissing)',
 
 
 
 
 
 
 
 
 
 
10725
  ),
10726
  'vocab' => array(),
10727
  );
10728
 
10729
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10730
  /**
10731
  * Get allowed tags.
10732
  *
@@ -10753,6 +14587,20 @@ class AMP_Allowed_Tags_Generated {
10753
  return null;
10754
  }
10755
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10756
  /**
10757
  * Get list of globally-allowed attributes.
10758
  *
13
  */
14
  class AMP_Allowed_Tags_Generated {
15
 
16
+ private static $spec_file_revision = 767;
17
+ private static $minimum_validator_revision_required = 348;
18
 
19
  private static $allowed_tags = array(
20
  'a' => array(
27
  'blacklisted_value_regex' => '__amp_source_origin',
28
  'value_url' => array(
29
  'allow_empty' => true,
30
+ 'protocol' => array(
 
31
  'ftp',
32
  'geo',
33
  'http',
34
  'https',
35
  'mailto',
36
  'maps',
37
+ 'bip',
38
  'bbmi',
39
  'fb-messenger',
40
  'intent',
47
  'threema',
48
  'twitter',
49
  'viber',
50
+ 'webcal',
51
+ 'web+mastodon',
52
  'whatsapp',
53
  ),
54
  ),
63
  'role' => array(),
64
  'tabindex' => array(),
65
  'target' => array(
66
+ 'value' => array(
67
+ '_blank',
68
+ '_self',
69
+ '_top',
70
+ ),
71
  ),
72
  'type' => array(
73
+ 'value_casei' => array(
74
+ 'text/html',
75
+ ),
76
  ),
77
  ),
78
  'tag_spec' => array(
98
  'tag_spec' => array(),
99
  ),
100
  ),
101
+ 'amp-3d-gltf' => array(
102
+ array(
103
+ 'attr_spec_list' => array(
104
+ 'alpha' => array(
105
+ 'value' => array(
106
+ 'false',
107
+ 'true',
108
+ ),
109
+ ),
110
+ 'antialiasing' => array(
111
+ 'value' => array(
112
+ 'false',
113
+ 'true',
114
+ ),
115
+ ),
116
+ 'autorotate' => array(
117
+ 'value' => array(
118
+ 'false',
119
+ 'true',
120
+ ),
121
+ ),
122
+ 'clearcolor' => array(),
123
+ 'enablezoom' => array(
124
+ 'value' => array(
125
+ 'false',
126
+ 'true',
127
+ ),
128
+ ),
129
+ 'maxpixelratio' => array(
130
+ 'value_regex' => '[+-]?(\\d*\\.)?\\d+',
131
+ ),
132
+ 'media' => array(),
133
+ 'noloading' => array(
134
+ 'value' => array(
135
+ '',
136
+ ),
137
+ ),
138
+ 'src' => array(
139
+ 'mandatory' => true,
140
+ 'value_url' => array(
141
+ 'protocol' => array(
142
+ 'https',
143
+ ),
144
+ ),
145
+ ),
146
+ ),
147
+ 'tag_spec' => array(
148
+ 'amp_layout' => array(
149
+ 'supported_layouts' => array(
150
+ 6,
151
+ 2,
152
+ 3,
153
+ 7,
154
+ 4,
155
+ ),
156
+ ),
157
+ 'requires_extension' => array(
158
+ 'amp-3d-gltf',
159
+ ),
160
+ ),
161
+ ),
162
+ ),
163
  'amp-3q-player' => array(
164
  array(
165
  'attr_spec_list' => array(
166
  'autoplay' => array(
167
+ 'value' => array(
168
+ '',
169
+ ),
170
  ),
171
  'data-id' => array(
172
  'mandatory' => true,
173
  ),
174
  'media' => array(),
175
  'noloading' => array(
176
+ 'value' => array(
177
+ '',
178
+ ),
179
  ),
180
  ),
181
  'tag_spec' => array(
182
+ 'amp_layout' => array(
183
+ 'supported_layouts' => array(
184
+ 6,
185
+ 2,
186
+ 7,
187
+ 4,
188
+ ),
189
+ ),
190
  'requires_extension' => array(
191
  'amp-3q-player',
192
  ),
196
  'amp-accordion' => array(
197
  array(
198
  'attr_spec_list' => array(
199
+ 'animate' => array(
200
+ 'value' => array(
201
+ '',
202
+ ),
203
+ ),
204
  'disable-session-states' => array(
205
+ 'value' => array(
206
+ '',
207
+ ),
208
  ),
209
  'expand-single-section' => array(
210
+ 'value' => array(
211
+ '',
212
+ ),
213
+ ),
214
+ 'media' => array(),
215
+ 'noloading' => array(
216
+ 'value' => array(
217
+ '',
218
+ ),
219
  ),
220
  ),
221
  'tag_spec' => array(
222
+ 'amp_layout' => array(
223
+ 'supported_layouts' => array(
224
+ 5,
225
+ ),
226
+ ),
227
  'requires_extension' => array(
228
  'amp-accordion',
229
  ),
238
  'json' => array(),
239
  'media' => array(),
240
  'noloading' => array(
241
+ 'value' => array(
242
+ '',
243
+ ),
244
  ),
245
  'rtc-config' => array(),
246
  'src' => array(
247
  'blacklisted_value_regex' => '__amp_source_origin',
248
  'value_url' => array(
249
  'allow_relative' => true,
250
+ 'protocol' => array(
251
  'https',
252
  ),
253
  ),
261
  'also_requires_tag_warning' => array(
262
  'amp-ad extension .js script',
263
  ),
264
+ 'amp_layout' => array(
265
+ 'supported_layouts' => array(
266
+ 6,
267
+ 2,
268
+ 3,
269
+ 7,
270
+ 8,
271
+ 9,
272
+ 1,
273
+ 4,
274
+ ),
275
+ ),
276
  'disallowed_ancestor' => array(
277
  'amp-app-banner',
278
  ),
288
  'data-multi-size' => array(
289
  'dispatch_key' => 2,
290
  'mandatory' => true,
291
+ 'value' => array(
292
+ '',
293
+ ),
294
  ),
295
  'json' => array(),
296
  'media' => array(),
297
  'noloading' => array(
298
+ 'value' => array(
299
+ '',
300
+ ),
301
  ),
302
  'rtc-config' => array(),
303
  'src' => array(
304
  'blacklisted_value_regex' => '__amp_source_origin',
305
  'value_url' => array(
306
  'allow_relative' => true,
307
+ 'protocol' => array(
308
  'https',
309
  ),
310
  ),
317
  'also_requires_tag_warning' => array(
318
  'amp-ad extension .js script',
319
  ),
320
+ 'amp_layout' => array(
321
+ 'supported_layouts' => array(
322
+ 6,
323
+ 2,
324
+ 3,
325
+ 7,
326
+ 8,
327
+ 9,
328
+ 1,
329
+ 4,
330
+ ),
331
+ ),
332
  'disallowed_ancestor' => array(
333
  'amp-app-banner',
334
  'amp-carousel',
349
  'data-enable-refresh' => array(
350
  'dispatch_key' => 2,
351
  'mandatory' => true,
352
+ 'value' => array(
353
+ '',
354
+ ),
355
  ),
356
  'json' => array(),
357
  'media' => array(),
358
  'noloading' => array(
359
+ 'value' => array(
360
+ '',
361
+ ),
362
  ),
363
  'src' => array(
364
  'blacklisted_value_regex' => '__amp_source_origin',
365
  'value_url' => array(
366
  'allow_relative' => true,
367
+ 'protocol' => array(
368
  'https',
369
  ),
370
  ),
377
  'also_requires_tag_warning' => array(
378
  'amp-ad extension .js script',
379
  ),
380
+ 'amp_layout' => array(
381
+ 'supported_layouts' => array(
382
+ 6,
383
+ 2,
384
+ 3,
385
+ 7,
386
+ 8,
387
+ 9,
388
+ 1,
389
+ 4,
390
+ ),
391
+ ),
392
  'disallowed_ancestor' => array(
393
  'amp-app-banner',
394
  'amp-fx-flying-carpet',
402
  ),
403
  ),
404
  ),
405
+ 'amp-addthis' => array(
406
+ array(
407
+ 'attr_spec_list' => array(
408
+ 'data-pub-id' => array(
409
+ 'mandatory' => true,
410
+ ),
411
+ 'data-share-media' => array(
412
+ 'value_url' => array(
413
+ 'allow_empty' => true,
414
+ 'protocol' => array(
415
+ 'http',
416
+ 'https',
417
+ ),
418
+ ),
419
+ ),
420
+ 'data-share-url' => array(
421
+ 'value_url' => array(
422
+ 'allow_empty' => true,
423
+ 'protocol' => array(
424
+ 'http',
425
+ 'https',
426
+ ),
427
+ ),
428
+ ),
429
+ 'data-widget-id' => array(
430
+ 'mandatory' => true,
431
+ ),
432
+ 'media' => array(),
433
+ 'noloading' => array(
434
+ 'value' => array(
435
+ '',
436
+ ),
437
+ ),
438
+ ),
439
+ 'tag_spec' => array(
440
+ 'amp_layout' => array(
441
+ 'supported_layouts' => array(
442
+ 6,
443
+ 2,
444
+ 3,
445
+ 7,
446
+ 1,
447
+ 4,
448
+ ),
449
+ ),
450
+ 'requires_extension' => array(
451
+ 'amp-addthis',
452
+ ),
453
+ ),
454
+ ),
455
+ ),
456
  'amp-analytics' => array(
457
  array(
458
  'attr_spec_list' => array(
461
  'value_url' => array(
462
  'allow_empty' => true,
463
  'allow_relative' => true,
464
+ 'protocol' => array(
465
  'https',
466
  ),
467
  ),
481
  'attr_spec_list' => array(
482
  'alt' => array(),
483
  'attribution' => array(),
 
484
  'media' => array(),
485
  'noloading' => array(
486
+ 'value' => array(
487
+ '',
488
+ ),
489
  ),
490
  'src' => array(
491
  'alternative_names' => array(
494
  'blacklisted_value_regex' => '__amp_source_origin',
495
  'mandatory' => true,
496
  'value_url' => array(
497
+ 'protocol' => array(
 
498
  'data',
499
  'http',
500
  'https',
503
  ),
504
  ),
505
  'tag_spec' => array(
506
+ 'amp_layout' => array(
507
+ 'supported_layouts' => array(
508
+ 6,
509
+ 2,
510
+ 3,
511
+ 7,
512
+ 9,
513
+ 1,
514
+ 4,
515
+ ),
516
+ ),
517
  'requires_extension' => array(
518
  'amp-anim',
519
  ),
526
  'attr_spec_list' => array(
527
  'media' => array(),
528
  'noloading' => array(
529
+ 'value' => array(
530
+ '',
531
+ ),
532
  ),
533
  'trigger' => array(
534
+ 'value' => array(
535
+ 'visibility',
536
+ ),
537
  ),
538
  ),
539
  'tag_spec' => array(
540
+ 'amp_layout' => array(
541
+ 'supported_layouts' => array(
542
+ 1,
543
+ ),
544
+ ),
545
  'requires_extension' => array(
546
  'amp-animation',
547
  ),
559
  ),
560
  'media' => array(),
561
  'noloading' => array(
562
+ 'value' => array(
563
+ '',
564
+ ),
565
  ),
566
  ),
567
  'tag_spec' => array(
568
+ 'amp_layout' => array(
569
+ 'supported_layouts' => array(
570
+ 6,
571
+ 2,
572
+ 3,
573
+ 7,
574
+ 1,
575
+ 4,
576
+ ),
577
+ ),
578
  'requires_extension' => array(
579
  'amp-apester-media',
580
  ),
590
  ),
591
  'media' => array(),
592
  'noloading' => array(
593
+ 'value' => array(
594
+ '',
595
+ ),
596
  ),
597
  ),
598
  'tag_spec' => array(
599
+ 'amp_layout' => array(
600
+ 'supported_layouts' => array(
601
+ 1,
602
+ ),
603
+ ),
604
  'mandatory_parent' => 'body',
605
  'requires_extension' => array(
606
  'amp-app-banner',
617
  'artist' => array(),
618
  'artwork' => array(),
619
  'autoplay' => array(
620
+ 'value' => array(
621
+ '',
622
+ ),
623
  ),
624
  'controls' => array(),
625
  'controlslist' => array(),
626
  'loop' => array(
627
+ 'value' => array(
628
+ '',
629
+ ),
630
  ),
631
  'media' => array(),
632
  'muted' => array(
633
+ 'value' => array(
634
+ '',
635
+ ),
636
  ),
637
  'noloading' => array(
638
+ 'value' => array(
639
+ '',
640
+ ),
641
  ),
642
  'preload' => array(
643
+ 'value_casei' => array(
644
+ 'auto',
645
+ 'metadata',
646
+ 'none',
647
+ ),
648
  ),
649
  'src' => array(
650
  'blacklisted_value_regex' => '__amp_source_origin',
651
  'value_url' => array(
652
  'allow_relative' => true,
653
+ 'protocol' => array(
654
  'https',
655
  ),
656
  ),
657
  ),
658
  ),
659
  'tag_spec' => array(
660
+ 'amp_layout' => array(
661
+ 'defines_default_height' => true,
662
+ 'defines_default_width' => true,
663
+ 'supported_layouts' => array(
664
+ 2,
665
+ 3,
666
+ 1,
667
+ ),
668
+ ),
669
  'disallowed_ancestor' => array(
670
  'amp-story',
671
  ),
682
  'artwork' => array(),
683
  'autoplay' => array(
684
  'mandatory' => true,
685
+ 'value' => array(
686
+ '',
687
+ ),
688
  ),
689
  'controls' => array(),
690
  'controlslist' => array(),
691
  'loop' => array(
692
+ 'value' => array(
693
+ '',
694
+ ),
695
  ),
696
  'media' => array(),
697
  'muted' => array(
698
+ 'value' => array(
699
+ '',
700
+ ),
701
  ),
702
  'noloading' => array(
703
+ 'value' => array(
704
+ '',
705
+ ),
706
  ),
707
  'src' => array(
708
  'blacklisted_value_regex' => '__amp_source_origin',
709
  'value_url' => array(
710
  'allow_relative' => true,
711
+ 'protocol' => array(
712
  'https',
713
  ),
714
  ),
715
  ),
716
  ),
717
  'tag_spec' => array(
718
+ 'amp_layout' => array(
719
+ 'defines_default_height' => true,
720
+ 'defines_default_width' => true,
721
+ 'supported_layouts' => array(
722
+ 2,
723
+ 3,
724
+ 1,
725
+ ),
726
+ ),
727
  'mandatory_ancestor' => 'amp-story',
728
  'requires_extension' => array(
729
  'amp-audio',
738
  'attr_spec_list' => array(
739
  'media' => array(),
740
  'noloading' => array(
741
+ 'value' => array(
742
+ '',
743
+ ),
744
  ),
745
  'type' => array(
746
  'mandatory' => true,
747
  ),
748
  ),
749
  'tag_spec' => array(
750
+ 'disallowed_ancestor' => array(
751
+ 'amp-auto-ads',
752
+ ),
753
  'requires_extension' => array(
754
  'amp-auto-ads',
755
  ),
768
  'value_regex_casei' => '[0-9a-f]{24}',
769
  ),
770
  'data-my-content' => array(
771
+ 'value' => array(
772
+ '0',
773
+ '1',
774
+ ),
775
  ),
776
  'data-name' => array(),
777
  'media' => array(),
778
  'noloading' => array(
779
+ 'value' => array(
780
+ '',
781
+ ),
782
  ),
783
  ),
784
  'tag_spec' => array(
785
+ 'amp_layout' => array(
786
+ 'supported_layouts' => array(
787
+ 6,
788
+ 2,
789
+ 3,
790
+ 7,
791
+ 9,
792
+ 1,
793
+ 4,
794
+ ),
795
+ ),
796
  'requires_extension' => array(
797
  'amp-beopinion',
798
  ),
822
  array(
823
  'attr_spec_list' => array(
824
  'loop' => array(
825
+ 'value_casei' => array(
826
+ 'false',
827
+ 'number',
828
+ 'true',
829
+ ),
830
  ),
831
  'noautoplay' => array(
832
+ 'value' => array(
833
+ '',
834
+ ),
835
+ ),
836
+ 'renderer' => array(
837
+ 'value_casei' => array(
838
+ 'svg',
839
+ 'html',
840
+ ),
841
  ),
842
  'src' => array(
843
  'mandatory' => true,
844
  'value_url' => array(
845
  'allow_relative' => false,
846
+ 'protocol' => array(
847
  'https',
848
  ),
849
  ),
850
  ),
851
  ),
852
  'tag_spec' => array(
853
+ 'amp_layout' => array(
854
+ 'supported_layouts' => array(
855
+ 6,
856
+ 2,
857
+ 3,
858
+ 7,
859
+ 1,
860
+ 4,
861
+ ),
862
+ ),
863
  'requires_extension' => array(
864
  'amp-bodymovin-animation',
865
  ),
890
  ),
891
  'media' => array(),
892
  'noloading' => array(
893
+ 'value' => array(
894
+ '',
895
+ ),
896
  ),
897
  ),
898
  'tag_spec' => array(
899
+ 'amp_layout' => array(
900
+ 'supported_layouts' => array(
901
+ 6,
902
+ 2,
903
+ 3,
904
+ 7,
905
+ 1,
906
+ 4,
907
+ ),
908
+ ),
909
  'requires_extension' => array(
910
  'amp-brid-player',
911
  ),
921
  '[data-player-id]' => array(),
922
  '[data-player]' => array(),
923
  '[data-playlist-id]' => array(),
924
+ '[data-referrer]' => array(),
925
  '[data-video-id]' => array(),
926
+ 'autoplay' => array(
927
+ 'value' => array(
928
+ '',
929
+ ),
930
+ ),
931
  'data-account' => array(
932
  'mandatory' => true,
933
  ),
934
  'media' => array(),
935
  'noloading' => array(
936
+ 'value' => array(
937
+ '',
938
+ ),
939
  ),
940
  ),
941
  'tag_spec' => array(
942
+ 'amp_layout' => array(
943
+ 'supported_layouts' => array(
944
+ 6,
945
+ 2,
946
+ 3,
947
+ 7,
948
+ 1,
949
+ 4,
950
+ ),
951
+ ),
952
  'requires_extension' => array(
953
  'amp-brightcove',
954
  ),
967
  ),
968
  'media' => array(),
969
  'noloading' => array(
970
+ 'value' => array(
971
+ '',
972
+ ),
973
  ),
974
  ),
975
  'tag_spec' => array(
976
+ 'amp_layout' => array(
977
+ 'supported_layouts' => array(
978
+ 6,
979
+ 2,
980
+ 3,
981
+ 7,
982
+ 1,
983
+ 4,
984
+ ),
985
+ ),
986
  'requires_extension' => array(
987
  'amp-byside-content',
988
  ),
997
  'mandatory' => true,
998
  'value_url' => array(
999
  'allow_relative' => false,
1000
+ 'protocol' => array(
1001
  'https',
1002
  ),
1003
  ),
1004
  ),
1005
  'media' => array(),
1006
  'noloading' => array(
1007
+ 'value' => array(
1008
+ '',
1009
+ ),
1010
  ),
1011
  ),
1012
  'tag_spec' => array(
1013
+ 'amp_layout' => array(
1014
+ 'supported_layouts' => array(
1015
+ 5,
1016
+ 6,
1017
+ 2,
1018
+ 3,
1019
+ 7,
1020
+ 4,
1021
+ ),
1022
+ ),
1023
  'requires_extension' => array(
1024
  'amp-call-tracking',
1025
  ),
1032
  'attr_spec_list' => array(
1033
  '[slide]' => array(),
1034
  'arrows' => array(
1035
+ 'value' => array(
1036
+ '',
1037
+ ),
1038
  ),
1039
  'autoplay' => array(
1040
+ 'value_regex' => '(|[0-9]+)',
1041
  ),
1042
  'controls' => array(),
1043
  'delay' => array(
1044
  'value_regex' => '[0-9]+',
1045
  ),
1046
  'dots' => array(
1047
+ 'value' => array(
1048
+ '',
1049
+ ),
1050
  ),
1051
+ 'loop' => array(
1052
+ 'value' => array(
1053
+ '',
1054
+ ),
1055
  ),
1056
+ 'media' => array(),
1057
+ 'noloading' => array(
1058
+ 'value' => array(
1059
+ '',
1060
+ ),
1061
+ ),
1062
+ 'type' => array(
1063
+ 'value' => array(
1064
+ 'slides',
1065
+ 'carousel',
1066
+ ),
1067
+ ),
1068
+ ),
1069
+ 'tag_spec' => array(
1070
+ 'amp_layout' => array(
1071
+ 'supported_layouts' => array(
1072
+ 6,
1073
+ 2,
1074
+ 3,
1075
+ 7,
1076
+ 9,
1077
+ 1,
1078
+ 4,
1079
+ ),
1080
+ ),
1081
+ 'requires_extension' => array(
1082
+ 'amp-carousel',
1083
+ ),
1084
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-carousel',
1085
+ ),
1086
+ ),
1087
+ array(
1088
+ 'attr_spec_list' => array(
1089
+ '[slide]' => array(),
1090
+ 'arrows' => array(
1091
+ 'value' => array(
1092
+ '',
1093
+ ),
1094
+ ),
1095
+ 'autoplay' => array(
1096
+ 'value' => array(
1097
+ '',
1098
+ ),
1099
+ ),
1100
+ 'controls' => array(),
1101
+ 'delay' => array(
1102
+ 'value_regex' => '[0-9]+',
1103
+ ),
1104
+ 'dots' => array(
1105
+ 'value' => array(
1106
+ '',
1107
+ ),
1108
+ ),
1109
+ 'lightbox' => array(
1110
+ 'mandatory' => true,
1111
  ),
1112
  'loop' => array(
1113
+ 'value' => array(
1114
+ '',
1115
+ ),
1116
  ),
1117
  'media' => array(),
1118
  'noloading' => array(
1119
+ 'value' => array(
1120
+ '',
1121
+ ),
1122
  ),
1123
  'type' => array(
1124
+ 'value' => array(
1125
+ 'slides',
1126
+ 'carousel',
1127
+ ),
1128
  ),
1129
  ),
1130
  'tag_spec' => array(
1131
+ 'amp_layout' => array(
1132
+ 'supported_layouts' => array(
1133
+ 6,
1134
+ 2,
1135
+ 3,
1136
+ 7,
1137
+ 9,
1138
+ 1,
1139
+ 4,
1140
+ ),
1141
+ ),
1142
+ 'reference_points' => array(
1143
+ 'AMP-CAROUSEL lightbox [child]' => array(
1144
+ 'mandatory' => false,
1145
+ 'unique' => false,
1146
+ ),
1147
+ 'AMP-CAROUSEL lightbox [lightbox-exclude]' => array(
1148
+ 'mandatory' => false,
1149
+ 'unique' => false,
1150
+ ),
1151
+ ),
1152
  'requires_extension' => array(
1153
  'amp-carousel',
1154
+ 'amp-lightbox-gallery',
1155
  ),
1156
+ 'spec_name' => 'AMP-CAROUSEL [lightbox]',
1157
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-carousel',
1158
  ),
1159
  ),
1163
  'attr_spec_list' => array(
1164
  'media' => array(),
1165
  'noloading' => array(
1166
+ 'value' => array(
1167
+ '',
1168
+ ),
1169
  ),
1170
  ),
1171
  'tag_spec' => array(
1172
+ 'amp_layout' => array(
1173
+ 'supported_layouts' => array(
1174
+ 1,
1175
+ ),
1176
+ ),
1177
  'requires_extension' => array(
1178
  'amp-consent',
1179
  ),
1186
  'attr_spec_list' => array(
1187
  'autoplay' => array(),
1188
  'data-endscreen-enable' => array(
1189
+ 'value' => array(
1190
+ 'false',
1191
+ 'true',
1192
+ ),
1193
  ),
1194
  'data-info' => array(
1195
+ 'value' => array(
1196
+ 'false',
1197
+ 'true',
1198
+ ),
1199
  ),
1200
  'data-mute' => array(
1201
+ 'value' => array(
1202
+ 'false',
1203
+ 'true',
1204
+ ),
1205
  ),
1206
  'data-sharing-enable' => array(
1207
+ 'value' => array(
1208
+ 'false',
1209
+ 'true',
1210
+ ),
1211
  ),
1212
  'data-start' => array(
1213
  'value_regex' => '[0-9]+',
1216
  'value_regex_casei' => '([0-9a-f]{3}){1,2}',
1217
  ),
1218
  'data-ui-logo' => array(
1219
+ 'value' => array(
1220
+ 'false',
1221
+ 'true',
1222
+ ),
1223
  ),
1224
  'data-videoid' => array(
1225
  'mandatory' => true,
1227
  ),
1228
  'media' => array(),
1229
  'noloading' => array(
1230
+ 'value' => array(
1231
+ '',
1232
+ ),
1233
  ),
1234
  ),
1235
  'tag_spec' => array(
1236
+ 'amp_layout' => array(
1237
+ 'supported_layouts' => array(
1238
+ 6,
1239
+ 2,
1240
+ 3,
1241
+ 7,
1242
+ 4,
1243
+ ),
1244
+ ),
1245
  'requires_extension' => array(
1246
  'amp-dailymotion',
1247
  ),
1249
  ),
1250
  ),
1251
  ),
1252
+ 'amp-date-countdown' => array(
1253
+ array(
1254
+ 'attr_spec_list' => array(
1255
+ 'biggest-unit' => array(
1256
+ 'value_casei' => array(
1257
+ 'days',
1258
+ 'hours',
1259
+ 'minutes',
1260
+ 'seconds',
1261
+ ),
1262
+ ),
1263
+ 'end-date' => array(
1264
+ 'value_regex' => '\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d(:[0-5]\\d(\\.\\d+)?)?(Z|[+-][0-1][0-9]:[0-5][0-9])',
1265
+ ),
1266
+ 'locale' => array(
1267
+ 'value_casei' => array(
1268
+ 'de',
1269
+ 'en',
1270
+ 'es',
1271
+ 'fr',
1272
+ 'id',
1273
+ 'it',
1274
+ 'ja',
1275
+ 'ko',
1276
+ 'nl',
1277
+ 'pt',
1278
+ 'ru',
1279
+ 'th',
1280
+ 'tr',
1281
+ 'vi',
1282
+ 'zh-cn',
1283
+ 'zh-tw',
1284
+ ),
1285
+ ),
1286
+ 'media' => array(),
1287
+ 'noloading' => array(
1288
+ 'value' => array(
1289
+ '',
1290
+ ),
1291
+ ),
1292
+ 'offset-seconds' => array(
1293
+ 'value_regex' => '-?\\d+',
1294
+ ),
1295
+ 'template' => array(),
1296
+ 'timeleft-ms' => array(
1297
+ 'value_regex' => '\\d+',
1298
+ ),
1299
+ 'timestamp-ms' => array(
1300
+ 'value_regex' => '\\d{13}',
1301
+ ),
1302
+ 'timestamp-seconds' => array(
1303
+ 'value_regex' => '\\d{10}',
1304
+ ),
1305
+ 'when-ended' => array(
1306
+ 'value_casei' => array(
1307
+ 'continue',
1308
+ 'stop',
1309
+ ),
1310
+ ),
1311
+ ),
1312
+ 'tag_spec' => array(
1313
+ 'amp_layout' => array(
1314
+ 'supported_layouts' => array(
1315
+ 6,
1316
+ 2,
1317
+ 3,
1318
+ 7,
1319
+ 1,
1320
+ 4,
1321
+ ),
1322
+ ),
1323
+ 'requires_extension' => array(
1324
+ 'amp-date-countdown',
1325
+ ),
1326
+ ),
1327
+ ),
1328
+ ),
1329
  'amp-date-picker' => array(
1330
  array(
1331
  'attr_spec_list' => array(
1332
+ '[max]' => array(),
1333
+ '[min]' => array(),
1334
  'allow-blocked-ranges' => array(
1335
+ 'value' => array(
1336
+ '',
1337
+ ),
1338
  ),
1339
  'blocked' => array(),
1340
+ 'date' => array(),
1341
  'day-size' => array(
1342
  'value_regex' => '[0-9]+',
1343
  ),
1346
  ),
1347
  'format' => array(),
1348
  'fullscreen' => array(
1349
+ 'value' => array(
1350
+ '',
1351
+ ),
1352
  ),
1353
  'highlighted' => array(),
1354
  'input-selector' => array(),
1357
  'media' => array(),
1358
  'min' => array(),
1359
  'mode' => array(
1360
+ 'value_casei' => array(
1361
+ 'static',
1362
+ ),
1363
  ),
1364
  'month-format' => array(),
1365
  'noloading' => array(
1366
+ 'value' => array(
1367
+ '',
1368
+ ),
1369
  ),
1370
  'number-of-months' => array(
1371
  'value_regex' => '[0-9]+',
1372
  ),
1373
  'open-after-clear' => array(
1374
+ 'value' => array(
1375
+ '',
1376
+ ),
1377
  ),
1378
  'open-after-select' => array(
1379
+ 'value' => array(
1380
+ '',
1381
+ ),
1382
  ),
1383
  'src' => array(
1384
  'blacklisted_value_regex' => '__amp_source_origin',
1385
  'value_url' => array(
1386
+ 'allow_relative' => false,
1387
+ 'protocol' => array(
1388
  'https',
1389
  ),
1390
  ),
1391
  ),
1392
  'type' => array(
1393
+ 'value_casei' => array(
1394
+ 'single',
1395
+ ),
1396
  ),
1397
  'week-day-format' => array(),
1398
  ),
1399
  'tag_spec' => array(
1400
+ 'amp_layout' => array(
1401
+ 'supported_layouts' => array(
1402
+ 6,
1403
+ 2,
1404
+ 3,
1405
+ 7,
1406
+ 9,
1407
+ 1,
1408
+ 4,
1409
+ ),
1410
+ ),
1411
  'requires_extension' => array(
1412
  'amp-date-picker',
1413
  ),
1416
  ),
1417
  array(
1418
  'attr_spec_list' => array(
1419
+ '[max]' => array(),
1420
+ '[min]' => array(),
1421
  'allow-blocked-ranges' => array(
1422
+ 'value' => array(
1423
+ '',
1424
+ ),
1425
  ),
1426
  'blocked' => array(),
1427
+ 'date' => array(),
1428
  'day-size' => array(
1429
  'value_regex' => '[0-9]+',
1430
  ),
1440
  'min' => array(),
1441
  'mode' => array(
1442
  'mandatory' => true,
1443
+ 'value_casei' => array(
1444
+ 'overlay',
1445
+ ),
1446
  ),
1447
  'month-format' => array(),
1448
  'noloading' => array(
1449
+ 'value' => array(
1450
+ '',
1451
+ ),
1452
  ),
1453
  'number-of-months' => array(
1454
  'value_regex' => '[0-9]+',
1455
  ),
1456
  'open-after-clear' => array(
1457
+ 'value' => array(
1458
+ '',
1459
+ ),
1460
  ),
1461
  'open-after-select' => array(
1462
+ 'value' => array(
1463
+ '',
1464
+ ),
1465
  ),
1466
  'src' => array(
1467
  'blacklisted_value_regex' => '__amp_source_origin',
1468
  'value_url' => array(
1469
+ 'allow_relative' => false,
1470
+ 'protocol' => array(
1471
  'https',
1472
  ),
1473
  ),
1474
  ),
1475
+ 'touch-keyboard-editable' => array(
1476
+ 'value' => array(
1477
+ '',
1478
+ ),
1479
+ ),
1480
  'type' => array(
1481
+ 'value_casei' => array(
1482
+ 'single',
1483
+ ),
1484
  ),
1485
  'week-day-format' => array(),
1486
  ),
1487
  'tag_spec' => array(
1488
+ 'amp_layout' => array(
1489
+ 'supported_layouts' => array(
1490
+ 5,
1491
+ 1,
1492
+ ),
1493
+ ),
1494
  'requires_extension' => array(
1495
  'amp-date-picker',
1496
  ),
1499
  ),
1500
  array(
1501
  'attr_spec_list' => array(
1502
+ '[max]' => array(),
1503
+ '[min]' => array(),
1504
  'allow-blocked-ranges' => array(
1505
+ 'value' => array(
1506
+ '',
1507
+ ),
1508
  ),
1509
  'blocked' => array(),
1510
  'day-size' => array(
1511
  'value_regex' => '[0-9]+',
1512
  ),
1513
+ 'end-date' => array(),
1514
  'end-input-selector' => array(),
1515
  'first-day-of-week' => array(
1516
  'value_regex' => '[0-6]',
1517
  ),
1518
  'format' => array(),
1519
  'fullscreen' => array(
1520
+ 'value' => array(
1521
+ '',
1522
+ ),
1523
  ),
1524
  'highlighted' => array(),
1525
  'locale' => array(),
1526
  'max' => array(),
1527
  'media' => array(),
1528
  'min' => array(),
1529
+ 'minimum-nights' => array(
1530
+ 'value_regex' => '[0-9]+',
1531
+ ),
1532
  'mode' => array(
1533
+ 'value_casei' => array(
1534
+ 'static',
1535
+ ),
1536
  ),
1537
  'month-format' => array(),
1538
  'noloading' => array(
1539
+ 'value' => array(
1540
+ '',
1541
+ ),
1542
  ),
1543
  'number-of-months' => array(
1544
  'value_regex' => '[0-9]+',
1545
  ),
1546
  'open-after-clear' => array(
1547
+ 'value' => array(
1548
+ '',
1549
+ ),
1550
  ),
1551
  'open-after-select' => array(
1552
+ 'value' => array(
1553
+ '',
1554
+ ),
1555
  ),
1556
  'src' => array(
1557
  'blacklisted_value_regex' => '__amp_source_origin',
1558
  'value_url' => array(
1559
+ 'allow_relative' => false,
1560
+ 'protocol' => array(
1561
  'https',
1562
  ),
1563
  ),
1564
  ),
1565
+ 'start-date' => array(),
1566
  'start-input-selector' => array(),
1567
  'type' => array(
1568
  'mandatory' => true,
1569
+ 'value_casei' => array(
1570
+ 'range',
1571
+ ),
1572
  ),
1573
  'week-day-format' => array(),
1574
  ),
1575
  'tag_spec' => array(
1576
+ 'amp_layout' => array(
1577
+ 'supported_layouts' => array(
1578
+ 6,
1579
+ 2,
1580
+ 3,
1581
+ 7,
1582
+ 9,
1583
+ 1,
1584
+ 4,
1585
+ ),
1586
+ ),
1587
  'requires_extension' => array(
1588
  'amp-date-picker',
1589
  ),
1592
  ),
1593
  array(
1594
  'attr_spec_list' => array(
1595
+ '[max]' => array(),
1596
+ '[min]' => array(),
1597
  'allow-blocked-ranges' => array(
1598
+ 'value' => array(
1599
+ '',
1600
+ ),
1601
  ),
1602
  'blocked' => array(),
1603
  'day-size' => array(
1604
  'value_regex' => '[0-9]+',
1605
  ),
1606
+ 'end-date' => array(),
1607
  'end-input-selector' => array(),
1608
  'first-day-of-week' => array(
1609
  'value_regex' => '[0-6]',
1614
  'max' => array(),
1615
  'media' => array(),
1616
  'min' => array(),
1617
+ 'minimum-nights' => array(
1618
+ 'value_regex' => '[0-9]+',
1619
+ ),
1620
  'mode' => array(
1621
  'mandatory' => true,
1622
+ 'value_casei' => array(
1623
+ 'overlay',
1624
+ ),
1625
  ),
1626
  'month-format' => array(),
1627
  'noloading' => array(
1628
+ 'value' => array(
1629
+ '',
1630
+ ),
1631
  ),
1632
  'number-of-months' => array(
1633
  'value_regex' => '[0-9]+',
1634
  ),
1635
  'open-after-clear' => array(
1636
+ 'value' => array(
1637
+ '',
1638
+ ),
1639
  ),
1640
  'open-after-select' => array(
1641
+ 'value' => array(
1642
+ '',
1643
+ ),
1644
  ),
1645
  'src' => array(
1646
  'blacklisted_value_regex' => '__amp_source_origin',
1647
  'value_url' => array(
1648
+ 'allow_relative' => false,
1649
+ 'protocol' => array(
1650
  'https',
1651
  ),
1652
  ),
1653
  ),
1654
+ 'start-date' => array(),
1655
  'start-input-selector' => array(),
1656
+ 'touch-keyboard-editable' => array(
1657
+ 'value' => array(
1658
+ '',
1659
+ ),
1660
+ ),
1661
  'type' => array(
1662
  'mandatory' => true,
1663
+ 'value_casei' => array(
1664
+ 'range',
1665
+ ),
1666
  ),
1667
  'week-day-format' => array(),
1668
  ),
1669
  'tag_spec' => array(
1670
+ 'amp_layout' => array(
1671
+ 'supported_layouts' => array(
1672
+ 5,
1673
+ 1,
1674
+ ),
1675
+ ),
1676
  'requires_extension' => array(
1677
  'amp-date-picker',
1678
  ),
1680
  ),
1681
  ),
1682
  ),
1683
+ 'amp-delight-player' => array(
1684
  array(
1685
+ 'attr_spec_list' => array(
1686
+ 'data-content-id' => array(
1687
+ 'mandatory' => true,
1688
+ ),
1689
+ 'media' => array(),
1690
+ 'noloading' => array(
1691
+ 'value' => array(
1692
+ '',
1693
+ ),
1694
+ ),
1695
+ ),
1696
  'tag_spec' => array(
1697
+ 'amp_layout' => array(
1698
+ 'supported_layouts' => array(
1699
+ 6,
1700
+ 2,
1701
+ 3,
1702
+ 7,
1703
+ 1,
1704
+ 4,
1705
+ ),
1706
+ ),
1707
  'requires_extension' => array(
1708
+ 'amp-delight-player',
1709
  ),
 
1710
  ),
1711
  ),
1712
  ),
1717
  'json' => array(),
1718
  'media' => array(),
1719
  'noloading' => array(
1720
+ 'value' => array(
1721
+ '',
1722
+ ),
1723
  ),
1724
  'rtc-config' => array(),
1725
  'src' => array(
1726
  'blacklisted_value_regex' => '__amp_source_origin',
1727
  'value_url' => array(
1728
  'allow_relative' => true,
1729
+ 'protocol' => array(
1730
  'https',
1731
  ),
1732
  ),
1740
  'also_requires_tag_warning' => array(
1741
  'amp-ad extension .js script',
1742
  ),
1743
+ 'amp_layout' => array(
1744
+ 'supported_layouts' => array(
1745
+ 6,
1746
+ 2,
1747
+ 3,
1748
+ 7,
1749
+ 8,
1750
+ 9,
1751
+ 1,
1752
+ 4,
1753
+ ),
1754
+ ),
1755
  'disallowed_ancestor' => array(
1756
  'amp-app-banner',
1757
  ),
1767
  'data-multi-size' => array(
1768
  'dispatch_key' => 2,
1769
  'mandatory' => true,
1770
+ 'value' => array(
1771
+ '',
1772
+ ),
1773
  ),
1774
  'json' => array(),
1775
  'media' => array(),
1776
  'noloading' => array(
1777
+ 'value' => array(
1778
+ '',
1779
+ ),
1780
  ),
1781
  'rtc-config' => array(),
1782
  'src' => array(
1783
  'blacklisted_value_regex' => '__amp_source_origin',
1784
  'value_url' => array(
1785
  'allow_relative' => true,
1786
+ 'protocol' => array(
1787
  'https',
1788
  ),
1789
  ),
1796
  'also_requires_tag_warning' => array(
1797
  'amp-ad extension .js script',
1798
  ),
1799
+ 'amp_layout' => array(
1800
+ 'supported_layouts' => array(
1801
+ 6,
1802
+ 2,
1803
+ 3,
1804
+ 7,
1805
+ 8,
1806
+ 9,
1807
+ 1,
1808
+ 4,
1809
+ ),
1810
+ ),
1811
  'disallowed_ancestor' => array(
1812
  'amp-app-banner',
1813
  'amp-carousel',
1823
  ),
1824
  ),
1825
  ),
1826
+ 'amp-embedly-card' => array(
1827
+ array(
1828
+ 'attr_spec_list' => array(
1829
+ 'data-url' => array(
1830
+ 'mandatory' => true,
1831
+ 'value_url' => array(
1832
+ 'allow_relative' => false,
1833
+ 'protocol' => array(
1834
+ 'https',
1835
+ ),
1836
+ ),
1837
+ ),
1838
+ 'media' => array(),
1839
+ 'noloading' => array(
1840
+ 'value' => array(
1841
+ '',
1842
+ ),
1843
+ ),
1844
+ ),
1845
+ 'tag_spec' => array(
1846
+ 'amp_layout' => array(
1847
+ 'supported_layouts' => array(
1848
+ 4,
1849
+ ),
1850
+ ),
1851
+ 'requires_extension' => array(
1852
+ 'amp-embedly-card',
1853
+ ),
1854
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-embedly-card',
1855
+ ),
1856
+ ),
1857
+ ),
1858
+ 'amp-embedly-key' => array(
1859
+ array(
1860
+ 'attr_spec_list' => array(
1861
+ 'value' => array(
1862
+ 'mandatory' => true,
1863
+ ),
1864
+ ),
1865
+ 'tag_spec' => array(
1866
+ 'amp_layout' => array(
1867
+ 'supported_layouts' => array(
1868
+ 1,
1869
+ ),
1870
+ ),
1871
+ 'requires_extension' => array(
1872
+ 'amp-embedly-card',
1873
+ ),
1874
+ 'unique' => true,
1875
+ ),
1876
+ ),
1877
+ ),
1878
  'amp-experiment' => array(
1879
  array(
1880
  'attr_spec_list' => array(),
1895
  ),
1896
  'media' => array(),
1897
  'noloading' => array(
1898
+ 'value' => array(
1899
+ '',
1900
+ ),
1901
  ),
1902
  ),
1903
  'tag_spec' => array(
1904
+ 'amp_layout' => array(
1905
+ 'supported_layouts' => array(
1906
+ 6,
1907
+ 2,
1908
+ 3,
1909
+ 7,
1910
+ 1,
1911
+ 4,
1912
+ ),
1913
+ ),
1914
  'requires_extension' => array(
1915
  'amp-facebook',
1916
  ),
1925
  ),
1926
  'media' => array(),
1927
  'noloading' => array(
1928
+ 'value' => array(
1929
+ '',
1930
+ ),
1931
  ),
1932
  ),
1933
  'tag_spec' => array(
1934
+ 'amp_layout' => array(
1935
+ 'supported_layouts' => array(
1936
+ 6,
1937
+ 2,
1938
+ 3,
1939
+ 7,
1940
+ 1,
1941
+ 4,
1942
+ ),
1943
+ ),
1944
  'requires_extension' => array(
1945
  'amp-facebook-comments',
1946
  ),
1954
  'mandatory' => true,
1955
  'value_url' => array(
1956
  'allow_relative' => false,
1957
+ 'protocol' => array(
1958
  'http',
1959
  'https',
1960
  ),
1962
  ),
1963
  'media' => array(),
1964
  'noloading' => array(
1965
+ 'value' => array(
1966
+ '',
1967
+ ),
1968
  ),
1969
  ),
1970
  'tag_spec' => array(
1971
+ 'amp_layout' => array(
1972
+ 'supported_layouts' => array(
1973
+ 6,
1974
+ 2,
1975
+ 3,
1976
+ 7,
1977
+ 1,
1978
+ 4,
1979
+ ),
1980
+ ),
1981
  'requires_extension' => array(
1982
  'amp-facebook-like',
1983
  ),
1991
  'mandatory' => true,
1992
  'value_url' => array(
1993
  'allow_relative' => false,
1994
+ 'protocol' => array(
1995
  'http',
1996
  'https',
1997
  ),
1999
  ),
2000
  'media' => array(),
2001
  'noloading' => array(
2002
+ 'value' => array(
2003
+ '',
2004
+ ),
2005
  ),
2006
  ),
2007
  'tag_spec' => array(
2008
+ 'amp_layout' => array(
2009
+ 'supported_layouts' => array(
2010
+ 6,
2011
+ 2,
2012
+ 3,
2013
+ 7,
2014
+ 1,
2015
+ 4,
2016
+ ),
2017
+ ),
2018
  'requires_extension' => array(
2019
  'amp-facebook-page',
2020
  ),
2028
  'media' => array(),
2029
  'min-font-size' => array(),
2030
  'noloading' => array(
2031
+ 'value' => array(
2032
+ '',
2033
+ ),
2034
  ),
2035
  ),
2036
  'tag_spec' => array(
2037
+ 'amp_layout' => array(
2038
+ 'supported_layouts' => array(
2039
+ 6,
2040
+ 2,
2041
+ 3,
2042
+ 7,
2043
+ 9,
2044
+ 1,
2045
+ 4,
2046
+ ),
2047
+ ),
2048
  'requires_extension' => array(
2049
  'amp-fit-text',
2050
  ),
2062
  'font-weight' => array(),
2063
  'media' => array(),
2064
  'noloading' => array(
2065
+ 'value' => array(
2066
+ '',
2067
+ ),
2068
  ),
2069
  'on-error-add-class' => array(),
2070
  'on-error-remove-class' => array(),
2075
  ),
2076
  ),
2077
  'tag_spec' => array(
2078
+ 'amp_layout' => array(
2079
+ 'supported_layouts' => array(
2080
+ 1,
2081
+ ),
2082
+ ),
2083
  'requires_extension' => array(
2084
  'amp-font',
2085
  ),
2094
  ),
2095
  'media' => array(),
2096
  'noloading' => array(
2097
+ 'value' => array(
2098
+ '',
2099
+ ),
2100
  ),
2101
  ),
2102
  'tag_spec' => array(
2106
  ),
2107
  ),
2108
  ),
2109
+ 'amp-geo' => array(
2110
+ array(
2111
+ 'attr_spec_list' => array(
2112
+ 'media' => array(),
2113
+ 'noloading' => array(
2114
+ 'value' => array(
2115
+ '',
2116
+ ),
2117
+ ),
2118
+ ),
2119
+ 'tag_spec' => array(
2120
+ 'amp_layout' => array(
2121
+ 'supported_layouts' => array(
2122
+ 1,
2123
+ ),
2124
+ ),
2125
+ 'requires_extension' => array(
2126
+ 'amp-geo',
2127
+ ),
2128
+ 'unique' => true,
2129
+ ),
2130
+ ),
2131
+ ),
2132
  'amp-gfycat' => array(
2133
  array(
2134
  'attr_spec_list' => array(
2137
  ),
2138
  'media' => array(),
2139
  'noautoplay' => array(
2140
+ 'value' => array(
2141
+ '',
2142
+ ),
2143
  ),
2144
  'noloading' => array(
2145
+ 'value' => array(
2146
+ '',
2147
+ ),
2148
  ),
2149
  ),
2150
  'tag_spec' => array(
2151
+ 'amp_layout' => array(
2152
+ 'supported_layouts' => array(
2153
+ 6,
2154
+ 2,
2155
+ 3,
2156
+ 7,
2157
+ 4,
2158
+ ),
2159
+ ),
2160
  'requires_extension' => array(
2161
  'amp-gfycat',
2162
  ),
2172
  ),
2173
  'media' => array(),
2174
  'noloading' => array(
2175
+ 'value' => array(
2176
+ '',
2177
+ ),
2178
  ),
2179
  ),
2180
  'tag_spec' => array(
2181
+ 'amp_layout' => array(
2182
+ 'supported_layouts' => array(
2183
+ 3,
2184
+ ),
2185
+ ),
2186
  'requires_extension' => array(
2187
  'amp-gist',
2188
  ),
2190
  ),
2191
  ),
2192
  ),
2193
+ 'amp-google-document-embed' => array(
2194
+ array(
2195
+ 'attr_spec_list' => array(
2196
+ '[src]' => array(),
2197
+ '[title]' => array(),
2198
+ 'media' => array(),
2199
+ 'noloading' => array(
2200
+ 'value' => array(
2201
+ '',
2202
+ ),
2203
+ ),
2204
+ 'src' => array(
2205
+ 'blacklisted_value_regex' => '__amp_source_origin',
2206
+ 'mandatory' => true,
2207
+ 'value_url' => array(
2208
+ 'allow_relative' => false,
2209
+ 'protocol' => array(
2210
+ 'https',
2211
+ ),
2212
+ ),
2213
+ ),
2214
+ ),
2215
+ 'tag_spec' => array(
2216
+ 'amp_layout' => array(
2217
+ 'supported_layouts' => array(
2218
+ 6,
2219
+ 2,
2220
+ 3,
2221
+ 7,
2222
+ 9,
2223
+ 1,
2224
+ 4,
2225
+ ),
2226
+ ),
2227
+ 'requires_extension' => array(
2228
+ 'amp-google-document-embed',
2229
+ ),
2230
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-google-document-embed',
2231
+ ),
2232
+ ),
2233
+ ),
2234
  'amp-hulu' => array(
2235
  array(
2236
  'attr_spec_list' => array(
2239
  ),
2240
  'media' => array(),
2241
  'noloading' => array(
2242
+ 'value' => array(
2243
+ '',
2244
+ ),
2245
  ),
2246
  ),
2247
  'tag_spec' => array(
2248
+ 'amp_layout' => array(
2249
+ 'supported_layouts' => array(
2250
+ 6,
2251
+ 2,
2252
+ 3,
2253
+ 7,
2254
+ 4,
2255
+ ),
2256
+ ),
2257
  'requires_extension' => array(
2258
  'amp-hulu',
2259
  ),
2267
  '[src]' => array(),
2268
  'allow' => array(),
2269
  'allowfullscreen' => array(
2270
+ 'value' => array(
2271
+ '',
2272
+ ),
2273
  ),
2274
  'allowpaymentrequest' => array(
2275
+ 'value' => array(
2276
+ '',
2277
+ ),
2278
  ),
2279
  'allowtransparency' => array(
2280
+ 'value' => array(
2281
+ '',
2282
+ ),
2283
  ),
2284
  'frameborder' => array(
2285
+ 'value' => array(
2286
+ '0',
2287
+ '1',
2288
+ ),
2289
  ),
2290
  'media' => array(),
2291
  'noloading' => array(
2292
+ 'value' => array(
2293
+ '',
2294
+ ),
2295
  ),
2296
  'referrerpolicy' => array(),
2297
  'resizable' => array(
2298
+ 'value' => array(
2299
+ '',
2300
+ ),
2301
  ),
2302
  'sandbox' => array(),
2303
  'scrolling' => array(
2304
+ 'value' => array(
2305
+ 'auto',
2306
+ 'no',
2307
+ 'yes',
2308
+ ),
2309
  ),
2310
  'src' => array(
2311
  'blacklisted_value_regex' => '__amp_source_origin',
2312
  'value_url' => array(
2313
  'allow_relative' => true,
2314
+ 'protocol' => array(
2315
  'data',
2316
  'https',
2317
  ),
2320
  'srcdoc' => array(),
2321
  ),
2322
  'tag_spec' => array(
2323
+ 'amp_layout' => array(
2324
+ 'supported_layouts' => array(
2325
+ 6,
2326
+ 2,
2327
+ 3,
2328
+ 7,
2329
+ 9,
2330
+ 1,
2331
+ 4,
2332
+ ),
2333
+ ),
2334
  'requires_extension' => array(
2335
  'amp-iframe',
2336
  ),
2341
  array(
2342
  'attr_spec_list' => array(
2343
  'autoplay' => array(
2344
+ 'value' => array(
2345
+ '',
2346
+ ),
2347
  ),
2348
  'data-src' => array(
2349
  'blacklisted_value_regex' => '__amp_source_origin',
2350
  'value_url' => array(
2351
  'allow_relative' => true,
2352
+ 'protocol' => array(
2353
  'https',
2354
  ),
2355
  ),
2358
  'mandatory' => true,
2359
  'value_url' => array(
2360
  'allow_relative' => true,
2361
+ 'protocol' => array(
2362
  'https',
2363
  ),
2364
  ),
2365
  ),
2366
  'media' => array(),
2367
  'noloading' => array(
2368
+ 'value' => array(
2369
+ '',
2370
+ ),
2371
+ ),
2372
+ 'rotate-to-fullscreen' => array(
2373
+ 'value' => array(
2374
+ '',
2375
+ ),
2376
  ),
2377
  ),
2378
  'tag_spec' => array(
2379
+ 'amp_layout' => array(
2380
+ 'supported_layouts' => array(
2381
+ 6,
2382
+ 2,
2383
+ 3,
2384
+ 1,
2385
+ 4,
2386
+ ),
2387
+ ),
2388
  'requires_extension' => array(
2389
  'amp-ima-video',
2390
  ),
2398
  'controls' => array(),
2399
  'media' => array(),
2400
  'noloading' => array(
2401
+ 'value' => array(
2402
+ '',
2403
+ ),
2404
  ),
2405
  ),
2406
  'tag_spec' => array(
2407
+ 'amp_layout' => array(
2408
+ 'supported_layouts' => array(
2409
+ 1,
2410
+ ),
2411
+ ),
2412
  'requires_extension' => array(
2413
  'amp-image-lightbox',
2414
  ),
2415
  ),
2416
  ),
2417
  ),
2418
+ 'amp-image-slider' => array(
2419
+ array(
2420
+ 'attr_spec_list' => array(
2421
+ 'disable-hint-reappear' => array(),
2422
+ 'initial-slider-position' => array(
2423
+ 'value_regex' => '0(\\.[0-9]+)?|1(\\.0+)?',
2424
+ ),
2425
+ 'media' => array(),
2426
+ 'noloading' => array(
2427
+ 'value' => array(
2428
+ '',
2429
+ ),
2430
+ ),
2431
+ 'step-size' => array(
2432
+ 'value_regex' => '0(\\.[0-9]+)?|1(\\.0+)?',
2433
+ ),
2434
+ ),
2435
+ 'tag_spec' => array(
2436
+ 'amp_layout' => array(
2437
+ 'supported_layouts' => array(
2438
+ 2,
2439
+ 9,
2440
+ 1,
2441
+ 4,
2442
+ ),
2443
+ ),
2444
+ 'requires_extension' => array(
2445
+ 'amp-image-slider',
2446
+ ),
2447
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-image-slider',
2448
+ ),
2449
+ ),
2450
+ ),
2451
  'amp-img' => array(
2452
  array(
2453
  'attr_spec_list' => array(
2458
  'alt' => array(),
2459
  'attribution' => array(),
2460
  'lightbox' => array(),
 
 
 
2461
  'lightbox-thumbnail-id' => array(
2462
  'value_regex_casei' => '^[a-z][a-z\\d_-]*',
2463
  ),
2464
  'media' => array(),
2465
  'noloading' => array(
2466
+ 'value' => array(
2467
+ '',
2468
+ ),
2469
  ),
2470
  'placeholder' => array(),
2471
  'src' => array(
2475
  'blacklisted_value_regex' => '__amp_source_origin',
2476
  'mandatory' => true,
2477
  'value_url' => array(
2478
+ 'protocol' => array(
 
2479
  'data',
2480
  'http',
2481
  'https',
2484
  ),
2485
  ),
2486
  'tag_spec' => array(
2487
+ 'amp_layout' => array(
2488
+ 'supported_layouts' => array(
2489
+ 6,
2490
+ 2,
2491
+ 3,
2492
+ 7,
2493
+ 9,
2494
+ 1,
2495
+ 4,
2496
+ ),
2497
+ ),
2498
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
2499
  ),
2500
  ),
2507
  ),
2508
  'media' => array(),
2509
  'noloading' => array(
2510
+ 'value' => array(
2511
+ '',
2512
+ ),
2513
  ),
2514
  ),
2515
  'tag_spec' => array(
2516
+ 'amp_layout' => array(
2517
+ 'supported_layouts' => array(
2518
+ 6,
2519
+ 2,
2520
+ 3,
2521
+ 7,
2522
+ 1,
2523
+ 4,
2524
+ ),
2525
+ ),
2526
  'requires_extension' => array(
2527
  'amp-imgur',
2528
  ),
2538
  ),
2539
  'media' => array(),
2540
  'noloading' => array(
2541
+ 'value' => array(
2542
+ '',
2543
+ ),
2544
  ),
2545
  ),
2546
  'tag_spec' => array(
2547
+ 'amp_layout' => array(
2548
+ 'supported_layouts' => array(
2549
+ 6,
2550
+ 2,
2551
+ 3,
2552
+ 7,
2553
+ 1,
2554
+ 4,
2555
+ ),
2556
+ ),
2557
  'requires_extension' => array(
2558
  'amp-instagram',
2559
  ),
2567
  'blacklisted_value_regex' => '__amp_source_origin',
2568
  'value_url' => array(
2569
  'allow_relative' => true,
2570
+ 'protocol' => array(
2571
  'https',
2572
  ),
2573
  ),
2577
  'mandatory' => true,
2578
  'value_url' => array(
2579
  'allow_relative' => true,
2580
+ 'protocol' => array(
2581
  'https',
2582
  ),
2583
  ),
2584
  ),
2585
  ),
2586
  'tag_spec' => array(
2587
+ 'amp_layout' => array(
2588
+ 'supported_layouts' => array(
2589
+ 1,
2590
+ ),
2591
+ ),
2592
  'requires_extension' => array(
2593
  'amp-install-serviceworker',
2594
  ),
2604
  ),
2605
  'media' => array(),
2606
  'noloading' => array(
2607
+ 'value' => array(
2608
+ '',
2609
+ ),
2610
  ),
2611
  ),
2612
  'tag_spec' => array(
2613
+ 'amp_layout' => array(
2614
+ 'supported_layouts' => array(
2615
+ 6,
2616
+ 2,
2617
+ 3,
2618
+ 7,
2619
+ 4,
2620
+ ),
2621
+ ),
2622
  'requires_extension' => array(
2623
  'amp-izlesene',
2624
  ),
2640
  ),
2641
  ),
2642
  'tag_spec' => array(
2643
+ 'amp_layout' => array(
2644
+ 'supported_layouts' => array(
2645
+ 6,
2646
+ 2,
2647
+ 3,
2648
+ 7,
2649
+ 1,
2650
+ 4,
2651
+ ),
2652
+ ),
2653
  'requires_extension' => array(
2654
  'amp-jwplayer',
2655
  ),
2664
  ),
2665
  'media' => array(),
2666
  'noloading' => array(
2667
+ 'value' => array(
2668
+ '',
2669
+ ),
2670
  ),
2671
  ),
2672
  'tag_spec' => array(
2673
+ 'amp_layout' => array(
2674
+ 'supported_layouts' => array(
2675
+ 6,
2676
+ 2,
2677
+ 3,
2678
+ 7,
2679
+ 1,
2680
+ 4,
2681
+ ),
2682
+ ),
2683
  'requires_extension' => array(
2684
  'amp-kaltura-player',
2685
  ),
2691
  'attr_spec_list' => array(
2692
  'media' => array(),
2693
  'noloading' => array(
2694
+ 'value' => array(
2695
+ '',
2696
+ ),
2697
  ),
2698
  ),
2699
  'tag_spec' => array(
2700
+ 'amp_layout' => array(
2701
+ 'supported_layouts' => array(
2702
+ 6,
2703
+ 2,
2704
+ 3,
2705
+ 7,
2706
+ 9,
2707
+ 1,
2708
+ 4,
2709
+ 5,
2710
+ ),
2711
+ ),
2712
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-layout',
2713
  ),
2714
  ),
2716
  'amp-lightbox' => array(
2717
  array(
2718
  'attr_spec_list' => array(
2719
+ '[open]' => array(),
2720
+ 'animate-in' => array(
2721
+ 'value_casei' => array(
2722
+ 'fade-in',
2723
+ 'fly-in-bottom',
2724
+ 'fly-in-top',
2725
+ ),
2726
+ ),
2727
  'controls' => array(),
2728
  'from' => array(),
2729
  'media' => array(),
2730
  'noloading' => array(
2731
+ 'value' => array(
2732
+ '',
2733
+ ),
2734
  ),
2735
  'scrollable' => array(),
2736
  ),
2737
  'tag_spec' => array(
2738
+ 'amp_layout' => array(
2739
+ 'supported_layouts' => array(
2740
+ 1,
2741
+ ),
2742
+ ),
2743
  'requires_extension' => array(
2744
  'amp-lightbox',
2745
  ),
2751
  'attr_spec_list' => array(
2752
  '[src]' => array(),
2753
  '[state]' => array(),
2754
+ 'auto-resize' => array(
2755
+ 'value' => array(
2756
+ '',
2757
+ ),
2758
+ ),
2759
+ 'binding' => array(
2760
+ 'value' => array(
2761
+ 'always',
2762
+ 'no',
2763
+ 'refresh',
2764
+ ),
2765
+ ),
2766
  'credentials' => array(),
2767
  'items' => array(),
2768
  'max-items' => array(),
2769
  'media' => array(),
2770
  'noloading' => array(
2771
+ 'value' => array(
2772
+ '',
2773
+ ),
2774
  ),
2775
  'reset-on-refresh' => array(
2776
+ 'value' => array(
2777
+ '',
2778
+ 'always',
2779
+ 'fetch',
2780
+ ),
2781
  ),
2782
  'single-item' => array(),
2783
  'src' => array(
2784
  'blacklisted_value_regex' => '__amp_source_origin',
 
2785
  'value_url' => array(
2786
  'allow_relative' => true,
2787
+ 'protocol' => array(
2788
  'https',
2789
  ),
2790
  ),
2792
  'template' => array(),
2793
  ),
2794
  'tag_spec' => array(
2795
+ 'amp_layout' => array(
2796
+ 'supported_layouts' => array(
2797
+ 6,
2798
+ 2,
2799
+ 3,
2800
+ 7,
2801
+ 1,
2802
+ 4,
2803
+ ),
2804
+ ),
2805
  'requires_extension' => array(
2806
  'amp-list',
2807
  ),
2808
  ),
2809
  ),
2810
+ ),
2811
+ 'amp-live-list' => array(
2812
  array(
2813
  'attr_spec_list' => array(
2814
+ 'data-max-items-per-page' => array(
2815
  'mandatory' => true,
2816
+ 'value_regex' => '\\d+',
2817
  ),
2818
+ 'data-poll-interval' => array(
2819
+ 'value_regex' => '\\d{5,}',
 
 
 
 
2820
  ),
2821
+ 'disabled' => array(
2822
+ 'value' => array(
2823
+ '',
2824
+ ),
2825
+ ),
2826
+ 'id' => array(
2827
+ 'mandatory' => true,
2828
+ ),
2829
+ 'sort' => array(
2830
+ 'value' => array(
2831
+ 'ascending',
2832
  ),
2833
  ),
 
2834
  ),
2835
  'tag_spec' => array(
2836
+ 'amp_layout' => array(
2837
+ 'supported_layouts' => array(
2838
+ 5,
2839
+ 3,
2840
+ ),
2841
+ ),
2842
+ 'reference_points' => array(
2843
+ 'AMP-LIVE-LIST [items]' => array(
2844
+ 'mandatory' => true,
2845
+ 'unique' => true,
2846
+ ),
2847
+ 'AMP-LIVE-LIST [pagination]' => array(
2848
+ 'mandatory' => false,
2849
+ 'unique' => true,
2850
+ ),
2851
+ 'AMP-LIVE-LIST [update]' => array(
2852
+ 'mandatory' => true,
2853
+ 'unique' => true,
2854
+ ),
2855
+ ),
2856
  'requires_extension' => array(
2857
+ 'amp-live-list',
2858
  ),
 
2859
  ),
2860
  ),
2861
  ),
2862
+ 'amp-mathml' => array(
2863
  array(
2864
  'attr_spec_list' => array(
2865
+ 'data-formula' => array(
2866
  'mandatory' => true,
 
2867
  ),
2868
+ 'inline' => array(),
2869
+ 'media' => array(),
2870
+ 'noloading' => array(
2871
+ 'value' => array(
2872
+ '',
2873
+ ),
2874
  ),
2875
+ ),
2876
+ 'tag_spec' => array(
2877
+ 'amp_layout' => array(
2878
+ 'supported_layouts' => array(
2879
+ 5,
2880
+ ),
2881
  ),
2882
+ 'requires_extension' => array(
2883
+ 'amp-mathml',
2884
+ ),
2885
+ ),
2886
+ ),
2887
+ ),
2888
+ 'amp-mowplayer' => array(
2889
+ array(
2890
+ 'attr_spec_list' => array(
2891
+ 'autoplay' => array(),
2892
+ 'data-mediaid' => array(
2893
  'mandatory' => true,
2894
  ),
2895
+ 'media' => array(),
2896
+ 'noloading' => array(
2897
+ 'value' => array(
2898
+ '',
2899
+ ),
2900
  ),
2901
  ),
2902
  'tag_spec' => array(
2903
+ 'amp_layout' => array(
2904
+ 'supported_layouts' => array(
2905
+ 6,
2906
+ 2,
2907
+ 3,
2908
+ 7,
2909
+ 1,
2910
+ 4,
2911
+ ),
2912
+ ),
2913
  'requires_extension' => array(
2914
+ 'amp-mowplayer',
2915
  ),
2916
  ),
2917
  ),
2918
  ),
2919
+ 'amp-next-page' => array(
2920
+ array(
2921
+ 'attr_spec_list' => array(),
2922
+ 'tag_spec' => array(
2923
+ 'reference_points' => array(
2924
+ 'AMP-NEXT-PAGE > [separator]' => array(
2925
+ 'mandatory' => false,
2926
+ 'unique' => true,
2927
+ ),
2928
+ 'amp-next-page extension .json configuration' => array(
2929
+ 'mandatory' => true,
2930
+ 'unique' => true,
2931
+ ),
2932
+ ),
2933
+ 'requires_extension' => array(
2934
+ 'amp-next-page',
2935
+ ),
2936
+ 'spec_name' => 'amp-next-page with inline config',
2937
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-next-page',
2938
+ 'unique' => true,
2939
+ ),
2940
+ ),
2941
  array(
2942
  'attr_spec_list' => array(
2943
+ 'src' => array(
2944
+ 'blacklisted_value_regex' => '__amp_source_origin',
2945
  'mandatory' => true,
2946
+ 'value_url' => array(
2947
+ 'allow_relative' => false,
2948
+ 'protocol' => array(
2949
+ 'https',
2950
+ ),
2951
+ ),
2952
  ),
2953
+ ),
2954
+ 'tag_spec' => array(
2955
+ 'reference_points' => array(
2956
+ 'AMP-NEXT-PAGE > [separator]' => array(
2957
+ 'mandatory' => false,
2958
+ 'unique' => true,
2959
+ ),
2960
+ ),
2961
+ 'requires_extension' => array(
2962
+ 'amp-next-page',
2963
+ ),
2964
+ 'spec_name' => 'amp-next-page with src attribute',
2965
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-next-page',
2966
+ 'unique' => true,
2967
+ ),
2968
+ ),
2969
+ array(
2970
+ 'attr_spec_list' => array(
2971
+ 'data-client' => array(
2972
+ 'mandatory' => true,
2973
+ ),
2974
+ 'data-slot' => array(
2975
+ 'mandatory' => true,
2976
+ ),
2977
+ 'type' => array(
2978
+ 'mandatory' => true,
2979
+ 'value' => array(
2980
+ 'adsense',
2981
+ ),
2982
  ),
2983
  ),
2984
  'tag_spec' => array(
2985
+ 'reference_points' => array(
2986
+ 'AMP-NEXT-PAGE > [separator]' => array(
2987
+ 'mandatory' => false,
2988
+ 'unique' => true,
2989
+ ),
2990
+ ),
2991
  'requires_extension' => array(
2992
+ 'amp-next-page',
2993
  ),
2994
+ 'spec_name' => 'amp-next-page [type=adsense]',
2995
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-next-page',
2996
+ 'unique' => true,
2997
  ),
2998
  ),
2999
  ),
3008
  'value_regex' => '[^=/?:]+',
3009
  ),
3010
  'data-mode' => array(
3011
+ 'value' => array(
3012
+ 'api',
3013
+ 'static',
3014
+ ),
3015
  ),
3016
  'data-origin' => array(
3017
  'value_url' => array(
3018
  'allow_empty' => true,
3019
+ 'protocol' => array(
3020
  'https',
3021
  'http',
3022
  ),
3023
  ),
3024
  ),
3025
  'data-streamtype' => array(
3026
+ 'value' => array(
3027
+ 'album',
3028
+ 'audio',
3029
+ 'live',
3030
+ 'playlist',
3031
+ 'playlist-marked',
3032
+ 'video',
3033
+ ),
3034
  ),
3035
  'media' => array(),
3036
  'noloading' => array(
3037
+ 'value' => array(
3038
+ '',
3039
+ ),
3040
  ),
3041
  ),
3042
  'tag_spec' => array(
3043
+ 'amp_layout' => array(
3044
+ 'supported_layouts' => array(
3045
+ 6,
3046
+ 2,
3047
+ 3,
3048
+ 7,
3049
+ 1,
3050
+ 4,
3051
+ ),
3052
+ ),
3053
  'requires_extension' => array(
3054
  'amp-nexxtv-player',
3055
  ),
3067
  ),
3068
  'media' => array(),
3069
  'noloading' => array(
3070
+ 'value' => array(
3071
+ '',
3072
+ ),
3073
  ),
3074
  ),
3075
  'tag_spec' => array(
3076
+ 'amp_layout' => array(
3077
+ 'supported_layouts' => array(
3078
+ 6,
3079
+ 2,
3080
+ 3,
3081
+ 7,
3082
+ 1,
3083
+ 4,
3084
+ ),
3085
+ ),
3086
  'requires_extension' => array(
3087
  'amp-o2-player',
3088
  ),
3103
  ),
3104
  'media' => array(),
3105
  'noloading' => array(
3106
+ 'value' => array(
3107
+ '',
3108
+ ),
3109
  ),
3110
  ),
3111
  'tag_spec' => array(
3112
+ 'amp_layout' => array(
3113
+ 'supported_layouts' => array(
3114
+ 6,
3115
+ 2,
3116
+ 7,
3117
+ 4,
3118
+ ),
3119
+ ),
3120
  'requires_extension' => array(
3121
  'amp-ooyala-player',
3122
  ),
3123
  ),
3124
  ),
3125
  ),
3126
+ 'amp-orientation-observer' => array(
3127
+ array(
3128
+ 'attr_spec_list' => array(
3129
+ 'alpha-range' => array(
3130
+ 'value_regex' => '(\\d+)\\s{1}(\\d+)',
3131
+ ),
3132
+ 'beta-range' => array(
3133
+ 'value_regex' => '(\\d+)\\s{1}(\\d+)',
3134
+ ),
3135
+ 'gamma-range' => array(
3136
+ 'value_regex' => '(\\d+)\\s{1}(\\d+)',
3137
+ ),
3138
+ 'media' => array(),
3139
+ 'noloading' => array(
3140
+ 'value' => array(
3141
+ '',
3142
+ ),
3143
+ ),
3144
+ ),
3145
+ 'tag_spec' => array(
3146
+ 'amp_layout' => array(
3147
+ 'supported_layouts' => array(
3148
+ 1,
3149
+ ),
3150
+ ),
3151
+ 'requires_extension' => array(
3152
+ 'amp-orientation-observer',
3153
+ ),
3154
+ ),
3155
+ ),
3156
+ ),
3157
+ 'amp-pan-zoom' => array(
3158
+ array(
3159
+ 'attr_spec_list' => array(
3160
+ 'disable-double-tap' => array(
3161
+ 'value' => array(
3162
+ '',
3163
+ ),
3164
+ ),
3165
+ 'initial-scale' => array(
3166
+ 'value_regex' => '[0-9]+(\\.[0-9]+)?',
3167
+ ),
3168
+ 'initial-x' => array(
3169
+ 'value_regex' => '[0-9]+',
3170
+ ),
3171
+ 'initial-y' => array(
3172
+ 'value_regex' => '[0-9]+',
3173
+ ),
3174
+ 'max-scale' => array(
3175
+ 'value_regex' => '[0-9]+(\\.[0-9]+)?',
3176
+ ),
3177
+ 'media' => array(),
3178
+ 'noloading' => array(
3179
+ 'value' => array(
3180
+ '',
3181
+ ),
3182
+ ),
3183
+ 'reset-on-resize' => array(
3184
+ 'value' => array(
3185
+ '',
3186
+ ),
3187
+ ),
3188
+ ),
3189
+ 'tag_spec' => array(
3190
+ 'amp_layout' => array(
3191
+ 'supported_layouts' => array(
3192
+ 6,
3193
+ 2,
3194
+ 3,
3195
+ 4,
3196
+ ),
3197
+ ),
3198
+ 'requires_extension' => array(
3199
+ 'amp-pan-zoom',
3200
+ ),
3201
+ ),
3202
+ ),
3203
+ ),
3204
  'amp-pinterest' => array(
3205
  array(
3206
  'attr_spec_list' => array(
3210
  ),
3211
  'media' => array(),
3212
  'noloading' => array(
3213
+ 'value' => array(
3214
+ '',
3215
+ ),
3216
  ),
3217
  ),
3218
  'tag_spec' => array(
3219
+ 'amp_layout' => array(
3220
+ 'supported_layouts' => array(
3221
+ 6,
3222
+ 2,
3223
+ 3,
3224
+ 7,
3225
+ 1,
3226
+ 4,
3227
+ ),
3228
+ ),
3229
  'requires_extension' => array(
3230
  'amp-pinterest',
3231
  ),
3239
  'allow-ssr-img' => array(),
3240
  'media' => array(),
3241
  'noloading' => array(
3242
+ 'value' => array(
3243
+ '',
3244
+ ),
3245
  ),
3246
  'referrerpolicy' => array(
3247
+ 'value' => array(
3248
+ 'no-referrer',
3249
+ ),
3250
  ),
3251
  'src' => array(
3252
  'blacklisted_value_regex' => '__amp_source_origin',
3254
  'value_url' => array(
3255
  'allow_empty' => true,
3256
  'allow_relative' => true,
3257
+ 'protocol' => array(
3258
  'https',
3259
  ),
3260
  ),
3261
  ),
3262
  ),
3263
  'tag_spec' => array(
3264
+ 'amp_layout' => array(
3265
+ 'defines_default_height' => true,
3266
+ 'defines_default_width' => true,
3267
+ 'supported_layouts' => array(
3268
+ 2,
3269
+ 1,
3270
+ ),
3271
+ ),
3272
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-pixel',
3273
  ),
3274
  ),
3277
  array(
3278
  'attr_spec_list' => array(
3279
  'data-comments' => array(
3280
+ 'value_casei' => array(
3281
+ 'false',
3282
+ 'true',
3283
+ ),
3284
  ),
3285
  'data-item' => array(),
3286
  'data-item-info' => array(
3287
+ 'value_casei' => array(
3288
+ 'false',
3289
+ 'true',
3290
+ ),
3291
  ),
3292
  'data-share-buttons' => array(
3293
+ 'value_casei' => array(
3294
+ 'false',
3295
+ 'true',
3296
+ ),
3297
  ),
3298
  'media' => array(),
3299
  'noloading' => array(
3300
+ 'value' => array(
3301
+ '',
3302
+ ),
3303
  ),
3304
  'src' => array(),
3305
  ),
3306
  'tag_spec' => array(
3307
+ 'amp_layout' => array(
3308
+ 'supported_layouts' => array(
3309
+ 4,
3310
+ 3,
3311
+ ),
3312
+ ),
3313
  'requires_extension' => array(
3314
  'amp-playbuzz',
3315
  ),
3324
  ),
3325
  'media' => array(),
3326
  'noloading' => array(
3327
+ 'value' => array(
3328
+ '',
3329
+ ),
3330
+ ),
3331
+ 'once' => array(
3332
+ 'value' => array(
3333
+ '',
3334
+ ),
3335
  ),
3336
  'target' => array(),
3337
  'viewport-margins' => array(
3339
  ),
3340
  ),
3341
  'tag_spec' => array(
3342
+ 'amp_layout' => array(
3343
+ 'supported_layouts' => array(
3344
+ 1,
3345
+ ),
3346
+ ),
3347
  'requires_extension' => array(
3348
  'amp-position-observer',
3349
  ),
3350
  ),
3351
  ),
3352
  ),
3353
+ 'amp-powr-player' => array(
3354
+ array(
3355
+ 'attr_spec_list' => array(
3356
+ '[data-referrer]' => array(),
3357
+ 'autoplay' => array(),
3358
+ 'data-account' => array(
3359
+ 'mandatory' => true,
3360
+ 'value_regex' => '[0-9a-zA-Z-]+',
3361
+ ),
3362
+ 'data-player' => array(
3363
+ 'mandatory' => true,
3364
+ 'value_regex' => '[0-9a-zA-Z-]+',
3365
+ ),
3366
+ 'data-terms' => array(),
3367
+ 'data-video' => array(
3368
+ 'value_regex' => '[0-9a-zA-Z-]+',
3369
+ ),
3370
+ 'media' => array(),
3371
+ 'noloading' => array(
3372
+ 'value' => array(
3373
+ '',
3374
+ ),
3375
+ ),
3376
+ ),
3377
+ 'tag_spec' => array(
3378
+ 'amp_layout' => array(
3379
+ 'supported_layouts' => array(
3380
+ 6,
3381
+ 2,
3382
+ 3,
3383
+ 7,
3384
+ 1,
3385
+ 4,
3386
+ ),
3387
+ ),
3388
+ 'requires_extension' => array(
3389
+ 'amp-powr-player',
3390
+ ),
3391
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-powr-player',
3392
+ ),
3393
+ ),
3394
+ ),
3395
  'amp-reach-player' => array(
3396
  array(
3397
  'attr_spec_list' => array(
3401
  ),
3402
  'media' => array(),
3403
  'noloading' => array(
3404
+ 'value' => array(
3405
+ '',
3406
+ ),
3407
  ),
3408
  ),
3409
  'tag_spec' => array(
3410
+ 'amp_layout' => array(
3411
+ 'supported_layouts' => array(
3412
+ 6,
3413
+ 2,
3414
+ 3,
3415
+ 7,
3416
+ 4,
3417
+ ),
3418
+ ),
3419
  'requires_extension' => array(
3420
  'amp-reach-player',
3421
  ),
3426
  array(
3427
  'attr_spec_list' => array(
3428
  'data-embedlive' => array(
3429
+ 'value_casei' => array(
3430
+ 'false',
3431
+ 'true',
3432
+ ),
3433
  ),
3434
  'data-embedparent' => array(
3435
+ 'value_casei' => array(
3436
+ 'false',
3437
+ 'true',
3438
+ ),
3439
  ),
3440
  'data-embedtype' => array(
3441
  'mandatory' => true,
3442
+ 'value_casei' => array(
3443
+ 'comment',
3444
+ 'post',
3445
+ ),
3446
  ),
3447
  'data-src' => array(
3448
  'mandatory' => true,
3449
  ),
3450
  'media' => array(),
3451
  'noloading' => array(
3452
+ 'value' => array(
3453
+ '',
3454
+ ),
3455
  ),
3456
  ),
3457
  'tag_spec' => array(
3458
+ 'amp_layout' => array(
3459
+ 'supported_layouts' => array(
3460
+ 6,
3461
+ 2,
3462
+ 3,
3463
+ 7,
3464
+ 1,
3465
+ 4,
3466
+ ),
3467
+ ),
3468
  'requires_extension' => array(
3469
  'amp-reddit',
3470
  ),
3480
  ),
3481
  'media' => array(),
3482
  'noloading' => array(
3483
+ 'value' => array(
3484
+ '',
3485
+ ),
3486
  ),
3487
  ),
3488
  'tag_spec' => array(
3489
+ 'amp_layout' => array(
3490
+ 'supported_layouts' => array(
3491
+ 4,
3492
+ ),
3493
+ ),
3494
  'requires_extension' => array(
3495
  'amp-riddle-quiz',
3496
  ),
3504
  '[disabled]' => array(),
3505
  '[selected]' => array(),
3506
  'disabled' => array(
3507
+ 'value' => array(
3508
+ '',
3509
+ ),
3510
  ),
3511
  'form' => array(),
3512
  'keyboard-select-mode' => array(
3513
+ 'value_casei' => array(
3514
+ 'focus',
3515
+ 'none',
3516
+ 'select',
3517
+ ),
3518
  ),
3519
  'media' => array(),
3520
  'multiple' => array(
3521
+ 'value' => array(
3522
+ '',
3523
+ ),
3524
  ),
3525
  'name' => array(
3526
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
3527
  ),
3528
  'noloading' => array(
3529
+ 'value' => array(
3530
+ '',
3531
+ ),
3532
  ),
3533
  ),
3534
  'tag_spec' => array(
3535
+ 'amp_layout' => array(
3536
+ 'supported_layouts' => array(
3537
+ 6,
3538
+ 2,
3539
+ 3,
3540
+ 1,
3541
+ 4,
3542
+ 5,
3543
+ ),
3544
+ ),
3545
  'disallowed_ancestor' => array(
3546
  'amp-selector',
3547
  ),
3548
+ 'reference_points' => array(
3549
+ 'AMP-SELECTOR child' => array(
3550
+ 'mandatory' => false,
3551
+ 'unique' => false,
3552
+ ),
3553
+ 'AMP-SELECTOR option' => array(
3554
+ 'mandatory' => false,
3555
+ 'unique' => false,
3556
+ ),
3557
+ ),
3558
  'requires_extension' => array(
3559
  'amp-selector',
3560
  ),
3566
  'attr_spec_list' => array(
3567
  'media' => array(),
3568
  'noloading' => array(
3569
+ 'value' => array(
3570
+ '',
3571
+ ),
3572
  ),
3573
  'side' => array(
3574
+ 'value' => array(
3575
+ 'left',
3576
+ 'right',
3577
+ ),
3578
  ),
3579
  ),
3580
  'tag_spec' => array(
3581
+ 'amp_layout' => array(
3582
+ 'supported_layouts' => array(
3583
+ 1,
3584
+ ),
3585
+ ),
3586
  'mandatory_parent' => 'body',
3587
  'requires_extension' => array(
3588
  'amp-sidebar',
3590
  ),
3591
  ),
3592
  ),
3593
+ 'amp-skimlinks' => array(
3594
+ array(
3595
+ 'attr_spec_list' => array(
3596
+ 'custom-tracking-id' => array(
3597
+ 'value_regex_casei' => '^.{0,50}$',
3598
+ ),
3599
+ 'excluded-domains' => array(),
3600
+ 'link-selector' => array(),
3601
+ 'media' => array(),
3602
+ 'noloading' => array(
3603
+ 'value' => array(
3604
+ '',
3605
+ ),
3606
+ ),
3607
+ 'publisher-code' => array(
3608
+ 'mandatory' => true,
3609
+ 'value_regex_casei' => '^[0-9]+X[0-9]+$',
3610
+ ),
3611
+ 'tracking' => array(
3612
+ 'value' => array(
3613
+ 'false',
3614
+ 'true',
3615
+ ),
3616
+ ),
3617
+ ),
3618
+ 'tag_spec' => array(
3619
+ 'amp_layout' => array(
3620
+ 'supported_layouts' => array(
3621
+ 1,
3622
+ ),
3623
+ ),
3624
+ 'requires_extension' => array(
3625
+ 'amp-skimlinks',
3626
+ ),
3627
+ ),
3628
+ ),
3629
+ ),
3630
  'amp-social-share' => array(
3631
  array(
3632
  'attr_spec_list' => array(
3634
  'blacklisted_value_regex' => '__amp_source_origin',
3635
  'value_url' => array(
3636
  'allow_relative' => false,
3637
+ 'protocol' => array(
3638
  'ftp',
3639
  'http',
3640
  'https',
3656
  ),
3657
  'media' => array(),
3658
  'noloading' => array(
3659
+ 'value' => array(
3660
+ '',
3661
+ ),
3662
  ),
3663
  'type' => array(
3664
  'mandatory' => true,
3665
  ),
3666
  ),
3667
  'tag_spec' => array(
3668
+ 'amp_layout' => array(
3669
+ 'supported_layouts' => array(
3670
+ 5,
3671
+ 6,
3672
+ 2,
3673
+ 3,
3674
+ 7,
3675
+ 1,
3676
+ 4,
3677
+ ),
3678
+ ),
3679
  'requires_extension' => array(
3680
  'amp-social-share',
3681
  ),
3698
  'value_regex' => '[0-9]+',
3699
  ),
3700
  'data-visual' => array(
3701
+ 'value_casei' => array(
3702
+ 'false',
3703
+ 'true',
3704
+ ),
3705
  ),
3706
  'media' => array(),
3707
  'noloading' => array(
3708
+ 'value' => array(
3709
+ '',
3710
+ ),
3711
  ),
3712
  ),
3713
  'tag_spec' => array(
3714
+ 'amp_layout' => array(
3715
+ 'supported_layouts' => array(
3716
+ 3,
3717
+ ),
3718
+ ),
3719
  'requires_extension' => array(
3720
  'amp-soundcloud',
3721
  ),
3736
  ),
3737
  'data-mode' => array(
3738
  'mandatory' => true,
3739
+ 'value_casei' => array(
3740
+ 'playlist',
3741
+ 'video',
3742
+ ),
3743
  ),
3744
  'data-player-id' => array(
3745
  'mandatory' => true,
3751
  ),
3752
  'media' => array(),
3753
  'noloading' => array(
3754
+ 'value' => array(
3755
+ '',
3756
+ ),
3757
  ),
3758
  ),
3759
  'tag_spec' => array(
3760
+ 'amp_layout' => array(
3761
+ 'supported_layouts' => array(
3762
+ 6,
3763
+ 2,
3764
+ 7,
3765
+ 4,
3766
+ ),
3767
+ ),
3768
  'requires_extension' => array(
3769
  'amp-springboard-player',
3770
  ),
3784
  'blacklisted_value_regex' => '__amp_source_origin',
3785
  'value_url' => array(
3786
  'allow_relative' => true,
3787
+ 'protocol' => array(
3788
  'https',
3789
  ),
3790
  ),
3804
  'attr_spec_list' => array(
3805
  'media' => array(),
3806
  'noloading' => array(
3807
+ 'value' => array(
3808
+ '',
3809
+ ),
3810
  ),
3811
  ),
3812
  'tag_spec' => array(
3813
+ 'amp_layout' => array(
3814
+ 'supported_layouts' => array(
3815
+ 1,
3816
+ ),
3817
+ ),
3818
  'disallowed_ancestor' => array(
3819
  'amp-app-banner',
3820
  ),
3830
  'attr_spec_list' => array(
3831
  'background-audio' => array(
3832
  'value_url' => array(
3833
+ 'protocol' => array(
3834
  'http',
3835
  'https',
3836
  ),
3838
  ),
3839
  'bookend-config-src' => array(
3840
  'value_url' => array(
3841
+ 'protocol' => array(
3842
  'http',
3843
  'https',
3844
  ),
3845
  ),
3846
  ),
3847
+ 'poster-landscape-src' => array(
3848
+ 'value_url' => array(
3849
+ 'protocol' => array(
3850
+ 'http',
3851
+ 'https',
3852
+ ),
3853
+ ),
3854
+ ),
3855
+ 'poster-portrait-src' => array(
3856
+ 'mandatory' => true,
3857
+ 'value_url' => array(
3858
+ 'protocol' => array(
3859
+ 'http',
3860
+ 'https',
3861
+ ),
3862
+ ),
3863
+ ),
3864
+ 'poster-square-src' => array(
3865
+ 'value_url' => array(
3866
+ 'protocol' => array(
3867
+ 'http',
3868
+ 'https',
3869
+ ),
3870
+ ),
3871
+ ),
3872
+ 'publisher' => array(
3873
+ 'mandatory' => true,
3874
+ ),
3875
+ 'publisher-logo-src' => array(
3876
+ 'mandatory' => true,
3877
+ 'value_url' => array(
3878
+ 'protocol' => array(
3879
+ 'http',
3880
+ 'https',
3881
+ ),
3882
+ ),
3883
+ ),
3884
+ 'standalone' => array(
3885
+ 'mandatory' => true,
3886
+ 'value' => array(
3887
+ '',
3888
+ ),
3889
+ ),
3890
+ 'title' => array(
3891
+ 'mandatory' => true,
3892
  ),
3893
  ),
3894
  'tag_spec' => array(
3899
  ),
3900
  ),
3901
  ),
3902
+ 'amp-story-access' => array(
3903
+ array(
3904
+ 'attr_spec_list' => array(
3905
+ 'media' => array(),
3906
+ 'noloading' => array(
3907
+ 'value' => array(
3908
+ '',
3909
+ ),
3910
+ ),
3911
+ 'type' => array(
3912
+ 'value' => array(
3913
+ 'blocking',
3914
+ 'notification',
3915
+ ),
3916
+ ),
3917
+ ),
3918
+ 'tag_spec' => array(
3919
+ 'mandatory_parent' => 'amp-story',
3920
+ 'requires_extension' => array(
3921
+ 'amp-access',
3922
+ ),
3923
+ ),
3924
+ ),
3925
+ ),
3926
  'amp-story-auto-ads' => array(
3927
  array(
3928
  'attr_spec_list' => array(),
3936
  ),
3937
  ),
3938
  ),
3939
+ 'amp-story-bookend' => array(
3940
+ array(
3941
+ 'attr_spec_list' => array(
3942
+ 'layout' => array(
3943
+ 'mandatory' => true,
3944
+ 'value' => array(
3945
+ 'nodisplay',
3946
+ ),
3947
+ ),
3948
+ 'src' => array(
3949
+ 'value_url' => array(
3950
+ 'protocol' => array(
3951
+ 'http',
3952
+ 'https',
3953
+ ),
3954
+ ),
3955
+ ),
3956
+ ),
3957
+ 'tag_spec' => array(
3958
+ 'mandatory_ancestor' => 'amp-story',
3959
+ ),
3960
+ ),
3961
+ ),
3962
+ 'amp-story-consent' => array(
3963
+ array(
3964
+ 'attr_spec_list' => array(
3965
+ 'id' => array(
3966
+ 'mandatory' => true,
3967
+ ),
3968
+ ),
3969
+ 'tag_spec' => array(
3970
+ 'amp_layout' => array(
3971
+ 'supported_layouts' => array(
3972
+ 1,
3973
+ ),
3974
+ ),
3975
+ 'mandatory_parent' => 'amp-consent',
3976
+ 'requires_extension' => array(
3977
+ 'amp-consent',
3978
+ 'amp-story',
3979
+ ),
3980
+ ),
3981
+ ),
3982
+ ),
3983
  'amp-story-cta-layer' => array(
3984
  array(
3985
  'attr_spec_list' => array(),
3986
  'tag_spec' => array(
3987
  'mandatory_ancestor' => 'amp-story-page',
3988
+ 'reference_points' => array(
3989
+ 'AMP-STORY-CTA-LAYER animate-in' => array(
3990
+ 'mandatory' => false,
3991
+ 'unique' => false,
3992
+ ),
3993
+ ),
3994
  ),
3995
  ),
3996
  ),
3999
  'attr_spec_list' => array(
4000
  'template' => array(
4001
  'mandatory' => true,
4002
+ 'value' => array(
4003
+ 'fill',
4004
+ 'horizontal',
4005
+ 'thirds',
4006
+ 'vertical',
4007
+ ),
4008
  ),
4009
  ),
4010
  'tag_spec' => array(
4011
  'mandatory_ancestor' => 'amp-story-page',
4012
+ 'reference_points' => array(
4013
+ 'AMP-STORY-GRID-LAYER animate-in' => array(
4014
+ 'mandatory' => false,
4015
+ 'unique' => false,
4016
+ ),
4017
+ 'AMP-STORY-GRID-LAYER default' => array(
4018
+ 'mandatory' => false,
4019
+ 'unique' => false,
4020
+ ),
4021
+ ),
4022
  ),
4023
  ),
4024
  ),
4028
  'auto-advance-after' => array(),
4029
  'background-audio' => array(
4030
  'value_url' => array(
4031
+ 'protocol' => array(
4032
  'http',
4033
  'https',
4034
  ),
4049
  'amp-timeago' => array(
4050
  array(
4051
  'attr_spec_list' => array(
4052
+ '[datetime]' => array(),
4053
+ '[title]' => array(),
4054
  'cutoff' => array(
4055
  'value_regex' => '\\d+',
4056
  ),
4061
  'locale' => array(),
4062
  'media' => array(),
4063
  'noloading' => array(
4064
+ 'value' => array(
4065
+ '',
4066
+ ),
4067
  ),
4068
  ),
4069
  'tag_spec' => array(
4070
+ 'amp_layout' => array(
4071
+ 'supported_layouts' => array(
4072
+ 2,
4073
+ 3,
4074
+ 4,
4075
+ ),
4076
+ ),
4077
  'requires_extension' => array(
4078
  'amp-timeago',
4079
  ),
4084
  'amp-twitter' => array(
4085
  array(
4086
  'attr_spec_list' => array(
4087
+ 'data-cards' => array(),
4088
+ 'data-conversation' => array(),
4089
+ 'data-limit' => array(),
4090
+ 'data-link-color' => array(),
4091
+ 'data-momentid' => array(
4092
+ 'value_regex' => '\\d+',
4093
+ ),
4094
+ 'data-theme' => array(),
4095
+ 'data-timeline-id' => array(
4096
+ 'value_regex' => '\\d+',
4097
+ ),
4098
+ 'data-timeline-owner-screen-name' => array(),
4099
+ 'data-timeline-screen-name' => array(),
4100
+ 'data-timeline-slug' => array(),
4101
+ 'data-timeline-source-type' => array(),
4102
+ 'data-timeline-url' => array(
4103
+ 'value_url' => array(
4104
+ 'allow_relative' => false,
4105
+ 'protocol' => array(
4106
+ 'http',
4107
+ 'https',
4108
+ ),
4109
+ ),
4110
+ ),
4111
+ 'data-timeline-user-id' => array(
4112
+ 'value_regex' => '\\d+',
4113
  ),
4114
+ 'data-tweetid' => array(),
4115
  'media' => array(),
4116
  'noloading' => array(
4117
+ 'value' => array(
4118
+ '',
4119
+ ),
4120
  ),
4121
  ),
4122
  'tag_spec' => array(
4123
+ 'amp_layout' => array(
4124
+ 'supported_layouts' => array(
4125
+ 6,
4126
+ 2,
4127
+ 3,
4128
+ 7,
4129
+ 9,
4130
+ 1,
4131
+ 4,
4132
+ ),
4133
+ ),
4134
  'requires_extension' => array(
4135
  'amp-twitter',
4136
  ),
4144
  'value_url' => array(
4145
  'allow_empty' => false,
4146
  'allow_relative' => false,
4147
+ 'protocol' => array(
4148
  'https',
4149
  ),
4150
  ),
4153
  'value_url' => array(
4154
  'allow_empty' => false,
4155
  'allow_relative' => false,
4156
+ 'protocol' => array(
4157
  'https',
4158
  ),
4159
  ),
4160
  ),
4161
  'enctype' => array(
4162
+ 'value' => array(
4163
+ 'application/x-www-form-urlencoded',
4164
+ ),
4165
  ),
4166
  'media' => array(),
4167
  'noloading' => array(
4168
+ 'value' => array(
4169
+ '',
4170
+ ),
4171
  ),
4172
  ),
4173
  'tag_spec' => array(
4174
+ 'amp_layout' => array(
4175
+ 'supported_layouts' => array(
4176
+ 1,
4177
+ ),
4178
+ ),
4179
  'requires_extension' => array(
4180
  'amp-user-notification',
4181
  ),
4203
  'artwork' => array(),
4204
  'attribution' => array(),
4205
  'autoplay' => array(
4206
+ 'value' => array(
4207
+ '',
4208
+ ),
4209
  ),
4210
  'controls' => array(
4211
+ 'value' => array(
4212
+ '',
4213
+ ),
4214
  ),
4215
  'controlslist' => array(),
4216
  'crossorigin' => array(),
4217
  'disableremoteplayback' => array(
4218
+ 'value' => array(
4219
+ '',
4220
+ ),
4221
  ),
4222
+ 'dock' => array(),
4223
  'lightbox' => array(),
 
 
 
4224
  'lightbox-thumbnail-id' => array(
4225
  'value_regex_casei' => '^[a-z][a-z\\d_-]*',
4226
  ),
4227
  'loop' => array(
4228
+ 'value' => array(
4229
+ '',
4230
+ ),
4231
  ),
4232
  'media' => array(),
4233
  'muted' => array(
4234
+ 'value' => array(
4235
+ '',
4236
+ ),
4237
+ ),
4238
+ 'noaudio' => array(
4239
+ 'value' => array(
4240
+ '',
4241
+ ),
4242
  ),
4243
  'noloading' => array(
4244
+ 'value' => array(
4245
+ '',
4246
+ ),
4247
  ),
4248
  'placeholder' => array(),
4249
  'poster' => array(),
4250
  'preload' => array(
4251
+ 'value' => array(
4252
+ 'auto',
4253
+ 'metadata',
4254
+ 'none',
4255
+ '',
4256
+ ),
4257
+ ),
4258
+ 'rotate-to-fullscreen' => array(
4259
+ 'value' => array(
4260
+ '',
4261
+ ),
4262
  ),
4263
  'src' => array(
4264
  'blacklisted_value_regex' => '__amp_source_origin',
4265
  'value_url' => array(
4266
  'allow_relative' => true,
4267
+ 'protocol' => array(
4268
  'https',
4269
  ),
4270
  ),
4274
  'also_requires_tag_warning' => array(
4275
  'amp-video extension .js script',
4276
  ),
4277
+ 'amp_layout' => array(
4278
+ 'supported_layouts' => array(
4279
+ 6,
4280
+ 2,
4281
+ 3,
4282
+ 7,
4283
+ 1,
4284
+ 4,
4285
+ ),
4286
+ ),
4287
  'disallowed_ancestor' => array(
4288
  'amp-story',
4289
  ),
4310
  'artwork' => array(),
4311
  'attribution' => array(),
4312
  'autoplay' => array(
4313
+ 'value' => array(
4314
+ '',
4315
+ ),
4316
  ),
4317
  'controls' => array(
4318
+ 'value' => array(
4319
+ '',
4320
+ ),
4321
  ),
4322
  'controlslist' => array(),
4323
  'crossorigin' => array(),
4324
  'disableremoteplayback' => array(
4325
+ 'value' => array(
4326
+ '',
4327
+ ),
4328
  ),
4329
+ 'dock' => array(),
4330
  'loop' => array(
4331
+ 'value' => array(
4332
+ '',
4333
+ ),
4334
  ),
4335
  'media' => array(),
4336
  'muted' => array(
4337
+ 'value' => array(
4338
+ '',
4339
+ ),
4340
+ ),
4341
+ 'noaudio' => array(
4342
+ 'value' => array(
4343
+ '',
4344
+ ),
4345
  ),
4346
  'noloading' => array(
4347
+ 'value' => array(
4348
+ '',
4349
+ ),
4350
  ),
4351
  'placeholder' => array(),
4352
  'poster' => array(
4353
  'mandatory' => true,
4354
  ),
4355
  'preload' => array(
4356
+ 'value' => array(
4357
+ 'auto',
4358
+ 'metadata',
4359
+ 'none',
4360
+ '',
4361
+ ),
4362
+ ),
4363
+ 'rotate-to-fullscreen' => array(
4364
+ 'value' => array(
4365
+ '',
4366
+ ),
4367
  ),
4368
  'src' => array(
4369
  'blacklisted_value_regex' => '__amp_source_origin',
4370
  'value_url' => array(
4371
  'allow_relative' => true,
4372
+ 'protocol' => array(
4373
  'https',
4374
  ),
4375
  ),
4376
  ),
4377
  ),
4378
  'tag_spec' => array(
4379
+ 'amp_layout' => array(
4380
+ 'supported_layouts' => array(
4381
+ 6,
4382
+ 2,
4383
+ 3,
4384
+ 7,
4385
+ 1,
4386
+ 4,
4387
+ ),
4388
+ ),
4389
  'mandatory_ancestor' => 'amp-story',
4390
  'requires_extension' => array(
4391
  'amp-video',
4398
  'amp-vimeo' => array(
4399
  array(
4400
  'attr_spec_list' => array(
4401
+ 'autoplay' => array(
4402
+ 'value' => array(
4403
+ '',
4404
+ ),
4405
+ ),
4406
  'data-videoid' => array(
4407
  'mandatory' => true,
4408
  'value_regex' => '[0-9]+',
4409
  ),
4410
  'media' => array(),
4411
  'noloading' => array(
4412
+ 'value' => array(
4413
+ '',
4414
+ ),
4415
  ),
4416
  ),
4417
  'tag_spec' => array(
4418
+ 'amp_layout' => array(
4419
+ 'supported_layouts' => array(
4420
+ 6,
4421
+ 2,
4422
+ 3,
4423
+ 7,
4424
+ 4,
4425
+ ),
4426
+ ),
4427
  'requires_extension' => array(
4428
  'amp-vimeo',
4429
  ),
4438
  ),
4439
  'media' => array(),
4440
  'noloading' => array(
4441
+ 'value' => array(
4442
+ '',
4443
+ ),
4444
  ),
4445
  ),
4446
  'tag_spec' => array(
4447
+ 'amp_layout' => array(
4448
+ 'supported_layouts' => array(
4449
+ 6,
4450
+ 2,
4451
+ 3,
4452
+ 7,
4453
+ 1,
4454
+ 4,
4455
+ ),
4456
+ ),
4457
  'requires_extension' => array(
4458
  'amp-vine',
4459
  ),
4460
  ),
4461
  ),
4462
  ),
4463
+ 'amp-viqeo-player' => array(
4464
+ array(
4465
+ 'attr_spec_list' => array(
4466
+ 'autoplay' => array(),
4467
+ 'data-profileid' => array(
4468
+ 'mandatory' => true,
4469
+ 'value_regex' => '[0-9a-f]*',
4470
+ ),
4471
+ 'data-videoid' => array(
4472
+ 'mandatory' => true,
4473
+ ),
4474
+ 'media' => array(),
4475
+ 'noloading' => array(
4476
+ 'value' => array(
4477
+ '',
4478
+ ),
4479
+ ),
4480
+ ),
4481
+ 'tag_spec' => array(
4482
+ 'amp_layout' => array(
4483
+ 'supported_layouts' => array(
4484
+ 6,
4485
+ 2,
4486
+ 3,
4487
+ 7,
4488
+ 4,
4489
+ ),
4490
+ ),
4491
+ 'requires_extension' => array(
4492
+ 'amp-viqeo-player',
4493
+ ),
4494
+ ),
4495
+ ),
4496
+ ),
4497
  'amp-vk' => array(
4498
  array(
4499
  'attr_spec_list' => array(
4502
  ),
4503
  'media' => array(),
4504
  'noloading' => array(
4505
+ 'value' => array(
4506
+ '',
4507
+ ),
4508
  ),
4509
  ),
4510
  'tag_spec' => array(
4511
+ 'amp_layout' => array(
4512
+ 'supported_layouts' => array(
4513
+ 2,
4514
+ 7,
4515
+ 4,
4516
+ ),
4517
+ ),
4518
  'requires_extension' => array(
4519
  'amp-vk',
4520
  ),
4528
  'mandatory' => true,
4529
  'value_url' => array(
4530
  'allow_relative' => false,
4531
+ 'protocol' => array(
4532
  'https',
4533
  ),
4534
  ),
4535
  ),
4536
  'id' => array(
4537
  'mandatory' => true,
4538
+ 'value' => array(
4539
+ 'amp-web-push',
4540
+ ),
4541
  ),
4542
  'media' => array(),
4543
  'noloading' => array(
4544
+ 'value' => array(
4545
+ '',
4546
+ ),
4547
  ),
4548
  'permission-dialog-url' => array(
4549
  'mandatory' => true,
4550
  'value_url' => array(
4551
  'allow_relative' => false,
4552
+ 'protocol' => array(
4553
  'https',
4554
  ),
4555
  ),
4558
  'mandatory' => true,
4559
  'value_url' => array(
4560
  'allow_relative' => false,
4561
+ 'protocol' => array(
4562
  'https',
4563
  ),
4564
  ),
4565
  ),
4566
  ),
4567
  'tag_spec' => array(
4568
+ 'amp_layout' => array(
4569
+ 'supported_layouts' => array(
4570
+ 1,
4571
+ ),
4572
+ ),
4573
  'requires_extension' => array(
4574
  'amp-web-push',
4575
  ),
4582
  'attr_spec_list' => array(
4583
  'media' => array(),
4584
  'noloading' => array(
4585
+ 'value' => array(
4586
+ '',
4587
+ ),
4588
  ),
4589
  'visibility' => array(
4590
  'mandatory' => true,
4591
+ 'value' => array(
4592
+ 'blocked',
4593
+ 'subscribed',
4594
+ 'unsubscribed',
4595
+ ),
4596
  ),
4597
  ),
4598
  'tag_spec' => array(
4599
+ 'amp_layout' => array(
4600
+ 'supported_layouts' => array(
4601
+ 2,
4602
+ ),
4603
+ ),
4604
  'requires_extension' => array(
4605
  'amp-web-push',
4606
  ),
4617
  ),
4618
  'media' => array(),
4619
  'noloading' => array(
4620
+ 'value' => array(
4621
+ '',
4622
+ ),
4623
+ ),
4624
+ 'rotate-to-fullscreen' => array(
4625
+ 'value' => array(
4626
+ '',
4627
+ ),
4628
  ),
4629
  ),
4630
  'tag_spec' => array(
4631
+ 'amp_layout' => array(
4632
+ 'supported_layouts' => array(
4633
+ 6,
4634
+ 2,
4635
+ 3,
4636
+ 7,
4637
+ 4,
4638
+ ),
4639
+ ),
4640
  'requires_extension' => array(
4641
  'amp-wistia-player',
4642
  ),
4643
  ),
4644
  ),
4645
  ),
4646
+ 'amp-yotpo' => array(
4647
+ array(
4648
+ 'attr_spec_list' => array(
4649
+ 'data-app-key' => array(
4650
+ 'mandatory' => true,
4651
+ ),
4652
+ 'data-widget-type' => array(
4653
+ 'mandatory' => true,
4654
+ ),
4655
+ 'media' => array(),
4656
+ 'noloading' => array(
4657
+ 'value' => array(
4658
+ '',
4659
+ ),
4660
+ ),
4661
+ ),
4662
+ 'tag_spec' => array(
4663
+ 'amp_layout' => array(
4664
+ 'supported_layouts' => array(
4665
+ 6,
4666
+ 2,
4667
+ 3,
4668
+ 7,
4669
+ 1,
4670
+ 4,
4671
+ ),
4672
+ ),
4673
+ 'requires_extension' => array(
4674
+ 'amp-yotpo',
4675
+ ),
4676
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-yotpo',
4677
+ ),
4678
+ ),
4679
+ ),
4680
  'amp-youtube' => array(
4681
  array(
4682
  'attr_spec_list' => array(
4683
  '[data-videoid]' => array(),
4684
  'autoplay' => array(),
4685
  'credentials' => array(
4686
+ 'value_casei' => array(
4687
+ 'include',
4688
+ 'omit',
4689
+ ),
4690
  ),
4691
  'data-live-channelid' => array(
4692
  'value_regex' => '[^=/?:]+',
4695
  'value_regex' => '[^=/?:]+',
4696
  ),
4697
  'lightbox' => array(),
 
 
 
4698
  'lightbox-thumbnail-id' => array(
4699
  'value_regex_casei' => '^[a-z][a-z\\d_-]*',
4700
  ),
4701
  'media' => array(),
4702
  'noloading' => array(
4703
+ 'value' => array(
4704
+ '',
4705
+ ),
4706
  ),
4707
  ),
4708
  'tag_spec' => array(
4709
+ 'amp_layout' => array(
4710
+ 'supported_layouts' => array(
4711
+ 6,
4712
+ 2,
4713
+ 3,
4714
+ 7,
4715
+ 1,
4716
+ 4,
4717
+ ),
4718
+ ),
4719
  'requires_extension' => array(
4720
  'amp-youtube',
4721
  ),
4746
  'blacklisted_value_regex' => '__amp_source_origin',
4747
  'value_url' => array(
4748
  'allow_relative' => false,
4749
+ 'protocol' => array(
4750
  'data',
4751
  'https',
4752
  ),
4770
  array(
4771
  'attr_spec_list' => array(
4772
  'href' => array(
4773
+ 'value' => array(
4774
+ '/',
4775
+ ),
4776
  ),
4777
  'target' => array(
4778
+ 'value_casei' => array(
4779
+ '_blank',
4780
+ '_self',
4781
+ '_top',
4782
+ ),
4783
  ),
4784
  ),
4785
  'tag_spec' => array(
4816
  'blacklisted_value_regex' => '__amp_source_origin',
4817
  'value_url' => array(
4818
  'allow_empty' => true,
4819
+ 'protocol' => array(
 
4820
  'http',
4821
  'https',
4822
  ),
4850
  '[type]' => array(),
4851
  '[value]' => array(),
4852
  'disabled' => array(
4853
+ 'value' => array(
4854
+ '',
4855
+ ),
4856
  ),
4857
  'name' => array(
4858
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
4870
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
4871
  ),
4872
  'open-button' => array(
4873
+ 'value' => array(
4874
+ '',
4875
+ ),
4876
  ),
4877
  'role' => array(),
4878
  'tabindex' => array(),
4924
  'filter' => array(),
4925
  'flood-color' => array(),
4926
  'flood-opacity' => array(),
4927
+ 'focusable' => array(),
4928
  'font-family' => array(),
4929
  'font-size' => array(),
4930
  'font-size-adjust' => array(),
5016
  'filter' => array(),
5017
  'flood-color' => array(),
5018
  'flood-opacity' => array(),
5019
+ 'focusable' => array(),
5020
  'font-family' => array(),
5021
  'font-size' => array(),
5022
  'font-size-adjust' => array(),
5141
  'filter' => array(),
5142
  'flood-color' => array(),
5143
  'flood-opacity' => array(),
5144
+ 'focusable' => array(),
5145
  'font-family' => array(),
5146
  'font-size' => array(),
5147
  'font-size-adjust' => array(),
5206
  'blacklisted_value_regex' => '__amp_source_origin',
5207
  'value_url' => array(
5208
  'allow_empty' => true,
5209
+ 'protocol' => array(
 
5210
  'http',
5211
  'https',
5212
  ),
5253
  ),
5254
  'tag_spec' => array(),
5255
  ),
5256
+ array(
5257
+ 'attr_spec_list' => array(
5258
+ 'align' => array(),
5259
+ 'verify-error' => array(
5260
+ 'mandatory' => true,
5261
+ ),
5262
+ ),
5263
+ 'tag_spec' => array(
5264
+ 'mandatory_ancestor' => 'form',
5265
+ 'spec_name' => 'FORM DIV [verify-error]',
5266
+ ),
5267
+ ),
5268
+ array(
5269
+ 'attr_spec_list' => array(
5270
+ 'align' => array(),
5271
+ 'template' => array(
5272
+ 'mandatory' => true,
5273
+ ),
5274
+ 'verify-error' => array(
5275
+ 'mandatory' => true,
5276
+ ),
5277
+ ),
5278
+ 'tag_spec' => array(
5279
+ 'mandatory_ancestor' => 'form',
5280
+ 'spec_name' => 'FORM DIV [verify-error][template]',
5281
+ ),
5282
+ ),
5283
  array(
5284
  'attr_spec_list' => array(
5285
  'align' => array(),
5286
  'submitting' => array(
 
5287
  'mandatory' => true,
5288
  ),
5289
  ),
5290
  'tag_spec' => array(
5291
+ 'mandatory_ancestor' => 'form',
5292
+ 'spec_name' => 'FORM DIV [submitting]',
5293
+ ),
5294
+ ),
5295
+ array(
5296
+ 'attr_spec_list' => array(
5297
+ 'align' => array(),
5298
+ 'submitting' => array(
5299
+ 'mandatory' => true,
5300
+ ),
5301
+ 'template' => array(
5302
+ 'mandatory' => true,
5303
+ ),
5304
+ ),
5305
+ 'tag_spec' => array(
5306
+ 'mandatory_ancestor' => 'form',
5307
+ 'spec_name' => 'FORM DIV [submitting][template]',
5308
  ),
5309
  ),
5310
  array(
5315
  ),
5316
  ),
5317
  'tag_spec' => array(
5318
+ 'mandatory_ancestor' => 'form',
5319
+ 'spec_name' => 'FORM DIV [submit-success]',
5320
  ),
5321
  ),
5322
  array(
5330
  ),
5331
  ),
5332
  'tag_spec' => array(
5333
+ 'mandatory_ancestor' => 'form',
5334
+ 'spec_name' => 'FORM DIV [submit-success][template]',
5335
  ),
5336
  ),
5337
  array(
5342
  ),
5343
  ),
5344
  'tag_spec' => array(
5345
+ 'mandatory_ancestor' => 'form',
5346
+ 'spec_name' => 'FORM DIV [submit-error]',
5347
  ),
5348
  ),
5349
  array(
5357
  ),
5358
  ),
5359
  'tag_spec' => array(
5360
+ 'mandatory_ancestor' => 'form',
5361
+ 'spec_name' => 'FORM DIV [submit-error][template]',
5362
  ),
5363
  ),
5364
+ array(
5365
+ 'attr_spec_list' => array(
5366
+ 'first' => array(
5367
+ 'mandatory' => true,
5368
+ ),
5369
+ ),
5370
+ 'tag_spec' => array(
5371
+ 'mandatory_parent' => 'amp-image-slider',
5372
+ 'spec_name' => 'AMP-IMAGE-SLIDER > DIV [first]',
5373
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-image-slider',
5374
+ ),
5375
+ ),
5376
+ array(
5377
+ 'attr_spec_list' => array(
5378
+ 'second' => array(
5379
+ 'mandatory' => true,
5380
+ ),
5381
+ ),
5382
+ 'tag_spec' => array(
5383
+ 'mandatory_parent' => 'amp-image-slider',
5384
+ 'spec_name' => 'AMP-IMAGE-SLIDER > DIV [second]',
5385
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-image-slider',
5386
+ ),
5387
+ ),
5388
+ ),
5389
+ 'dl' => array(
5390
  array(
5391
  'attr_spec_list' => array(),
5392
  'tag_spec' => array(),
5425
  'filter' => array(),
5426
  'flood-color' => array(),
5427
  'flood-opacity' => array(),
5428
+ 'focusable' => array(),
5429
  'font-family' => array(),
5430
  'font-size' => array(),
5431
  'font-size-adjust' => array(),
5516
  'filter' => array(),
5517
  'flood-color' => array(),
5518
  'flood-opacity' => array(),
5519
+ 'focusable' => array(),
5520
  'font-family' => array(),
5521
  'font-size' => array(),
5522
  'font-size-adjust' => array(),
5602
  'filter' => array(),
5603
  'flood-color' => array(),
5604
  'flood-opacity' => array(),
5605
+ 'focusable' => array(),
5606
  'font-family' => array(),
5607
  'font-size' => array(),
5608
  'font-size-adjust' => array(),
5692
  'filter' => array(),
5693
  'flood-color' => array(),
5694
  'flood-opacity' => array(),
5695
+ 'focusable' => array(),
5696
  'font-family' => array(),
5697
  'font-size' => array(),
5698
  'font-size-adjust' => array(),
5776
  'filter' => array(),
5777
  'flood-color' => array(),
5778
  'flood-opacity' => array(),
5779
+ 'focusable' => array(),
5780
  'font-family' => array(),
5781
  'font-size' => array(),
5782
  'font-size-adjust' => array(),
5861
  'filter' => array(),
5862
  'flood-color' => array(),
5863
  'flood-opacity' => array(),
5864
+ 'focusable' => array(),
5865
  'font-family' => array(),
5866
  'font-size' => array(),
5867
  'font-size-adjust' => array(),
5964
  'filter' => array(),
5965
  'flood-color' => array(),
5966
  'flood-opacity' => array(),
5967
+ 'focusable' => array(),
5968
  'font-family' => array(),
5969
  'font-size' => array(),
5970
  'font-size-adjust' => array(),
6075
  'filterunits' => array(),
6076
  'flood-color' => array(),
6077
  'flood-opacity' => array(),
6078
+ 'focusable' => array(),
6079
  'font-family' => array(),
6080
  'font-size' => array(),
6081
  'font-size-adjust' => array(),
6130
  ),
6131
  'value_url' => array(
6132
  'allow_empty' => false,
6133
+ 'protocol' => array(
 
6134
  'http',
6135
  'https',
6136
  ),
6158
  'tag_spec' => array(),
6159
  ),
6160
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6161
  'form' => array(
6162
  array(
6163
  'attr_spec_list' => array(
6167
  'blacklisted_value_regex' => '__amp_source_origin',
6168
  'mandatory' => true,
6169
  'value_url' => array(
6170
+ 'protocol' => array(
 
6171
  'https',
6172
  ),
6173
  ),
6175
  'action-xhr' => array(
6176
  'blacklisted_value_regex' => '__amp_source_origin',
6177
  'value_url' => array(
6178
+ 'protocol' => array(
 
6179
  'https',
6180
  ),
6181
  ),
6182
  ),
6183
  'autocomplete' => array(),
6184
  'custom-validation-reporting' => array(
6185
+ 'value' => array(
6186
+ 'as-you-go',
6187
+ 'interact-and-submit',
6188
+ 'show-all-on-submit',
6189
+ 'show-first-on-submit',
6190
+ ),
6191
  ),
6192
  'enctype' => array(),
6193
  'method' => array(
6194
+ 'value_casei' => array(
6195
+ 'get',
6196
+ ),
6197
  ),
6198
  'name' => array(),
6199
  'novalidate' => array(),
6200
  'target' => array(
6201
  'mandatory' => true,
6202
+ 'value_casei' => array(
6203
+ '_blank',
6204
+ '_top',
6205
+ ),
6206
  ),
6207
  'verify-xhr' => array(
6208
  'blacklisted_value_regex' => '__amp_source_origin',
6209
  'value_url' => array(
6210
+ 'protocol' => array(
 
6211
  'https',
6212
  ),
6213
  ),
6231
  'blacklisted_value_regex' => '__amp_source_origin',
6232
  'mandatory' => true,
6233
  'value_url' => array(
6234
+ 'protocol' => array(
 
6235
  'https',
6236
  ),
6237
  ),
6238
  ),
6239
  'autocomplete' => array(),
6240
  'custom-validation-reporting' => array(
6241
+ 'value' => array(
6242
+ 'as-you-go',
6243
+ 'interact-and-submit',
6244
+ 'show-all-on-submit',
6245
+ 'show-first-on-submit',
6246
+ ),
6247
  ),
6248
  'enctype' => array(),
6249
  'method' => array(
6250
  'dispatch_key' => 2,
6251
  'mandatory' => true,
6252
+ 'value_casei' => array(
6253
+ 'post',
6254
+ ),
6255
  ),
6256
  'name' => array(),
6257
  'novalidate' => array(),
6258
  'target' => array(
6259
+ 'value_casei' => array(
6260
+ '_blank',
6261
+ '_top',
6262
+ ),
6263
  ),
6264
  'verify-xhr' => array(
6265
  'blacklisted_value_regex' => '__amp_source_origin',
6266
  'value_url' => array(
6267
+ 'protocol' => array(
 
6268
  'https',
6269
  ),
6270
  ),
6306
  'filter' => array(),
6307
  'flood-color' => array(),
6308
  'flood-opacity' => array(),
6309
+ 'focusable' => array(),
6310
  'font-family' => array(),
6311
  'font-size' => array(),
6312
  'font-size-adjust' => array(),
6390
  'filter' => array(),
6391
  'flood-color' => array(),
6392
  'flood-opacity' => array(),
6393
+ 'focusable' => array(),
6394
  'font-family' => array(),
6395
  'font-size' => array(),
6396
  'font-size-adjust' => array(),
6477
  'filter' => array(),
6478
  'flood-color' => array(),
6479
  'flood-opacity' => array(),
6480
+ 'focusable' => array(),
6481
  'font-family' => array(),
6482
  'font-size' => array(),
6483
  'font-size-adjust' => array(),
6531
  ),
6532
  'value_url' => array(
6533
  'allow_empty' => false,
6534
+ 'protocol' => array(
 
6535
  'http',
6536
  'https',
6537
  ),
6654
  ),
6655
  'html' => array(
6656
  array(
6657
+ 'attr_spec_list' => array(),
 
 
 
 
 
 
 
 
6658
  'tag_spec' => array(
6659
  'mandatory' => true,
6660
  'mandatory_parent' => '!doctype',
 
6661
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
6662
  'unique' => true,
6663
  ),
6673
  array(
6674
  'attr_spec_list' => array(
6675
  'frameborder' => array(
6676
+ 'value' => array(
6677
+ '0',
6678
+ '1',
6679
+ ),
6680
  ),
6681
  'height' => array(),
6682
  'name' => array(),
6683
  'referrerpolicy' => array(),
6684
  'resizable' => array(
6685
+ 'value' => array(
6686
+ '',
6687
+ ),
6688
  ),
6689
  'sandbox' => array(),
6690
  'scrolling' => array(
6691
+ 'value' => array(
6692
+ 'auto',
6693
+ 'yes',
6694
+ 'no',
6695
+ ),
6696
  ),
6697
  'src' => array(
6698
  'blacklisted_value_regex' => '__amp_source_origin',
6699
  'value_url' => array(
6700
  'allow_relative' => false,
6701
+ 'protocol' => array(
6702
  'data',
6703
  'https',
6704
  ),
6739
  'filter' => array(),
6740
  'flood-color' => array(),
6741
  'flood-opacity' => array(),
6742
+ 'focusable' => array(),
6743
  'font-family' => array(),
6744
  'font-size' => array(),
6745
  'font-size-adjust' => array(),
6799
  'blacklisted_value_regex' => '(^|\\s)data:image\\/svg\\+xml',
6800
  'value_url' => array(
6801
  'allow_empty' => false,
6802
+ 'protocol' => array(
 
6803
  'data',
6804
  'http',
6805
  'https',
6826
  array(
6827
  'attr_spec_list' => array(
6828
  'alt' => array(),
6829
+ 'attribution' => array(),
6830
  'border' => array(),
6831
+ 'decoding' => array(
6832
+ 'value' => array(
6833
+ 'async',
6834
+ 'auto',
6835
+ 'sync',
6836
+ ),
6837
+ ),
6838
  'height' => array(),
6839
  'ismap' => array(),
6840
  'longdesc' => array(
6841
  'blacklisted_value_regex' => '__amp_source_origin',
6842
  'value_url' => array(
6843
+ 'protocol' => array(
 
6844
  'http',
6845
  'https',
6846
  ),
6847
  ),
6848
  ),
6849
+ 'sizes' => array(),
6850
  'src' => array(
6851
  'alternative_names' => array(
6852
  'srcset',
6855
  'mandatory' => true,
6856
  'value_url' => array(
6857
  'allow_relative' => true,
6858
+ 'protocol' => array(
6859
  'data',
6860
  'https',
6861
  ),
6913
  'name' => array(
6914
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
6915
  ),
6916
+ 'no-verify' => array(
6917
+ 'value' => array(
6918
+ '',
6919
+ ),
6920
+ ),
6921
  'pattern' => array(),
6922
  'placeholder' => array(),
6923
  'readonly' => array(),
6937
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
6938
  ),
6939
  ),
 
 
6940
  array(
6941
  'attr_spec_list' => array(
6942
+ '[accept]' => array(),
6943
+ '[accesskey]' => array(),
6944
+ '[autocomplete]' => array(),
6945
+ '[checked]' => array(),
6946
+ '[disabled]' => array(),
6947
+ '[height]' => array(),
6948
+ '[inputmode]' => array(),
6949
+ '[max]' => array(),
6950
+ '[maxlength]' => array(),
6951
+ '[min]' => array(),
6952
+ '[minlength]' => array(),
6953
+ '[multiple]' => array(),
6954
+ '[pattern]' => array(),
6955
+ '[placeholder]' => array(),
6956
+ '[readonly]' => array(),
6957
+ '[required]' => array(),
6958
+ '[selectiondirection]' => array(),
6959
+ '[size]' => array(),
6960
+ '[spellcheck]' => array(),
6961
+ '[step]' => array(),
6962
+ '[value]' => array(),
6963
+ '[width]' => array(),
6964
+ 'accept' => array(),
6965
+ 'accesskey' => array(),
6966
+ 'autocomplete' => array(),
6967
+ 'autofocus' => array(),
6968
+ 'checked' => array(),
6969
+ 'disabled' => array(),
6970
+ 'height' => array(),
6971
+ 'inputmode' => array(),
6972
+ 'list' => array(),
6973
+ 'max' => array(),
6974
+ 'maxlength' => array(),
6975
+ 'min' => array(),
6976
+ 'minlength' => array(),
6977
+ 'multiple' => array(),
6978
+ 'name' => array(
6979
+ 'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
6980
+ ),
6981
+ 'no-verify' => array(
6982
+ 'value' => array(
6983
+ '',
6984
  ),
6985
  ),
6986
+ 'pattern' => array(),
6987
+ 'placeholder' => array(),
6988
+ 'readonly' => array(),
6989
+ 'required' => array(),
6990
+ 'selectiondirection' => array(),
6991
+ 'size' => array(),
6992
+ 'spellcheck' => array(),
6993
+ 'step' => array(),
6994
+ 'tabindex' => array(),
6995
+ 'type' => array(
6996
+ 'dispatch_key' => 2,
6997
+ 'mandatory' => true,
6998
+ 'value_casei' => array(
6999
+ 'file',
7000
+ ),
7001
+ ),
7002
+ 'value' => array(),
7003
+ 'width' => array(),
7004
  ),
7005
  'tag_spec' => array(
7006
+ 'mandatory_ancestor' => 'form [method=post]',
7007
+ 'spec_name' => 'INPUT [type=file]',
7008
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
7009
  ),
7010
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7011
  array(
7012
  'attr_spec_list' => array(
7013
+ '[accept]' => array(),
7014
+ '[accesskey]' => array(),
7015
+ '[autocomplete]' => array(),
7016
+ '[checked]' => array(),
7017
+ '[disabled]' => array(),
7018
+ '[height]' => array(),
7019
+ '[inputmode]' => array(),
7020
+ '[max]' => array(),
7021
+ '[maxlength]' => array(),
7022
+ '[min]' => array(),
7023
+ '[minlength]' => array(),
7024
+ '[multiple]' => array(),
7025
+ '[pattern]' => array(),
7026
+ '[placeholder]' => array(),
7027
+ '[readonly]' => array(),
7028
+ '[required]' => array(),
7029
+ '[selectiondirection]' => array(),
7030
+ '[size]' => array(),
7031
+ '[spellcheck]' => array(),
7032
+ '[step]' => array(),
7033
+ '[value]' => array(),
7034
+ '[width]' => array(),
7035
+ 'accept' => array(),
7036
+ 'accesskey' => array(),
7037
+ 'autocomplete' => array(),
7038
+ 'autofocus' => array(),
7039
+ 'checked' => array(),
7040
+ 'disabled' => array(),
7041
+ 'height' => array(),
7042
+ 'inputmode' => array(),
7043
+ 'list' => array(),
7044
+ 'max' => array(),
7045
+ 'maxlength' => array(),
7046
+ 'min' => array(),
7047
+ 'minlength' => array(),
7048
+ 'multiple' => array(),
7049
+ 'name' => array(
7050
+ 'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
7051
+ ),
7052
+ 'pattern' => array(),
7053
+ 'placeholder' => array(),
7054
+ 'readonly' => array(),
7055
+ 'required' => array(),
7056
+ 'selectiondirection' => array(),
7057
+ 'size' => array(),
7058
+ 'spellcheck' => array(),
7059
+ 'step' => array(),
7060
+ 'tabindex' => array(),
7061
+ 'type' => array(
7062
+ 'dispatch_key' => 2,
7063
+ 'mandatory' => true,
7064
+ 'value_casei' => array(
7065
+ 'password',
7066
+ ),
7067
+ ),
7068
+ 'value' => array(),
7069
+ 'width' => array(),
7070
+ ),
7071
+ 'tag_spec' => array(
7072
+ 'mandatory_ancestor' => 'form [method=post]',
7073
+ 'spec_name' => 'INPUT [type=password]',
7074
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
7075
+ ),
7076
+ ),
7077
+ array(
7078
+ 'attr_spec_list' => array(
7079
+ '[accept]' => array(),
7080
+ '[accesskey]' => array(),
7081
+ '[autocomplete]' => array(),
7082
+ '[checked]' => array(),
7083
+ '[disabled]' => array(),
7084
+ '[height]' => array(),
7085
+ '[inputmode]' => array(),
7086
+ '[max]' => array(),
7087
+ '[maxlength]' => array(),
7088
+ '[min]' => array(),
7089
+ '[minlength]' => array(),
7090
+ '[multiple]' => array(),
7091
+ '[pattern]' => array(),
7092
+ '[placeholder]' => array(),
7093
+ '[readonly]' => array(),
7094
+ '[required]' => array(),
7095
+ '[selectiondirection]' => array(),
7096
+ '[size]' => array(),
7097
+ '[spellcheck]' => array(),
7098
+ '[step]' => array(),
7099
+ '[type]' => array(),
7100
+ '[value]' => array(),
7101
+ '[width]' => array(),
7102
+ 'accept' => array(),
7103
+ 'accesskey' => array(),
7104
+ 'autocomplete' => array(),
7105
+ 'autofocus' => array(),
7106
+ 'checked' => array(),
7107
+ 'disabled' => array(),
7108
+ 'height' => array(),
7109
+ 'inputmode' => array(),
7110
+ 'list' => array(),
7111
+ 'mask' => array(
7112
+ 'dispatch_key' => 1,
7113
+ 'mandatory' => true,
7114
+ ),
7115
+ 'mask-output' => array(),
7116
+ 'max' => array(),
7117
+ 'maxlength' => array(),
7118
+ 'min' => array(),
7119
+ 'minlength' => array(),
7120
+ 'multiple' => array(),
7121
+ 'name' => array(
7122
+ 'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
7123
+ ),
7124
+ 'pattern' => array(),
7125
+ 'placeholder' => array(),
7126
+ 'readonly' => array(),
7127
+ 'required' => array(),
7128
+ 'selectiondirection' => array(),
7129
+ 'size' => array(),
7130
+ 'spellcheck' => array(),
7131
+ 'step' => array(),
7132
+ 'tabindex' => array(),
7133
+ 'type' => array(
7134
+ 'value' => array(
7135
+ 'text',
7136
+ 'tel',
7137
+ 'search',
7138
+ ),
7139
+ ),
7140
+ 'value' => array(),
7141
+ 'width' => array(),
7142
+ ),
7143
+ 'tag_spec' => array(
7144
+ 'requires_extension' => array(
7145
+ 'amp-inputmask',
7146
+ ),
7147
+ 'spec_name' => 'input [mask]',
7148
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-inputmask',
7149
+ ),
7150
+ ),
7151
+ ),
7152
+ 'ins' => array(
7153
+ array(
7154
+ 'attr_spec_list' => array(
7155
+ 'cite' => array(
7156
+ 'blacklisted_value_regex' => '__amp_source_origin',
7157
+ 'value_url' => array(
7158
+ 'allow_empty' => true,
7159
+ 'protocol' => array(
7160
+ 'http',
7161
+ 'https',
7162
+ ),
7163
+ ),
7164
+ ),
7165
+ 'datetime' => array(),
7166
+ ),
7167
+ 'tag_spec' => array(),
7168
+ ),
7169
+ ),
7170
+ 'kbd' => array(
7171
+ array(
7172
+ 'attr_spec_list' => array(),
7173
+ 'tag_spec' => array(),
7174
+ ),
7175
+ ),
7176
+ 'label' => array(
7177
+ array(
7178
+ 'attr_spec_list' => array(
7179
+ 'for' => array(),
7180
+ ),
7181
+ 'tag_spec' => array(
7182
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
7183
+ ),
7184
+ ),
7185
+ ),
7186
+ 'legend' => array(
7187
+ array(
7188
+ 'attr_spec_list' => array(),
7189
+ 'tag_spec' => array(),
7190
+ ),
7191
+ ),
7192
+ 'li' => array(
7193
+ array(
7194
+ 'attr_spec_list' => array(
7195
+ 'value' => array(
7196
+ 'value_regex' => '[0-9]*',
7197
+ ),
7198
+ ),
7199
+ 'tag_spec' => array(),
7200
+ ),
7201
+ ),
7202
+ 'line' => array(
7203
+ array(
7204
+ 'attr_spec_list' => array(
7205
+ 'alignment-baseline' => array(),
7206
+ 'baseline-shift' => array(),
7207
+ 'clip' => array(),
7208
+ 'clip-path' => array(),
7209
+ 'clip-rule' => array(),
7210
+ 'color' => array(),
7211
+ 'color-interpolation' => array(),
7212
+ 'color-interpolation-filters' => array(),
7213
+ 'color-profile' => array(),
7214
+ 'color-rendering' => array(),
7215
+ 'cursor' => array(),
7216
+ 'direction' => array(),
7217
+ 'display' => array(),
7218
+ 'dominant-baseline' => array(),
7219
+ 'enable-background' => array(),
7220
+ 'externalresourcesrequired' => array(),
7221
+ 'fill' => array(),
7222
+ 'fill-opacity' => array(),
7223
+ 'fill-rule' => array(),
7224
+ 'filter' => array(),
7225
+ 'flood-color' => array(),
7226
+ 'flood-opacity' => array(),
7227
+ 'focusable' => array(),
7228
+ 'font-family' => array(),
7229
  'font-size' => array(),
7230
  'font-size-adjust' => array(),
7231
  'font-stretch' => array(),
7312
  'filter' => array(),
7313
  'flood-color' => array(),
7314
  'flood-opacity' => array(),
7315
+ 'focusable' => array(),
7316
  'font-family' => array(),
7317
  'font-size' => array(),
7318
  'font-size-adjust' => array(),
7368
  ),
7369
  'value_url' => array(
7370
  'allow_empty' => false,
7371
+ 'protocol' => array(
 
7372
  'http',
7373
  'https',
7374
  ),
7395
  array(
7396
  'attr_spec_list' => array(
7397
  'charset' => array(
7398
+ 'value_casei' => array(
7399
+ 'utf-8',
7400
+ ),
7401
  ),
7402
  'color' => array(),
7403
  'crossorigin' => array(),
7423
  array(
7424
  'attr_spec_list' => array(
7425
  'charset' => array(
7426
+ 'value_casei' => array(
7427
+ 'utf-8',
7428
+ ),
7429
  ),
7430
  'color' => array(),
7431
  'crossorigin' => array(),
7433
  'blacklisted_value_regex' => '__amp_source_origin',
7434
  'mandatory' => true,
7435
  'value_url' => array(
7436
+ 'protocol' => array(
 
7437
  'http',
7438
  'https',
7439
  ),
7444
  'rel' => array(
7445
  'dispatch_key' => 2,
7446
  'mandatory' => true,
7447
+ 'value_casei' => array(
7448
+ 'canonical',
7449
+ ),
7450
  ),
7451
  'sizes' => array(),
7452
  'target' => array(),
7463
  array(
7464
  'attr_spec_list' => array(
7465
  'charset' => array(
7466
+ 'value_casei' => array(
7467
+ 'utf-8',
7468
+ ),
7469
  ),
7470
  'color' => array(),
7471
  'crossorigin' => array(),
7473
  'blacklisted_value_regex' => '__amp_source_origin',
7474
  'mandatory' => true,
7475
  'value_url' => array(
7476
+ 'protocol' => array(
 
7477
  'https',
7478
  ),
7479
  ),
7483
  'rel' => array(
7484
  'dispatch_key' => 2,
7485
  'mandatory' => true,
7486
+ 'value_casei' => array(
7487
+ 'manifest',
7488
+ ),
7489
  ),
7490
  'sizes' => array(),
7491
  'target' => array(),
7501
  'attr_spec_list' => array(
7502
  'as' => array(),
7503
  'charset' => array(
7504
+ 'value_casei' => array(
7505
+ 'utf-8',
7506
+ ),
7507
  ),
7508
  'color' => array(),
7509
  'crossorigin' => array(),
7513
  'rel' => array(
7514
  'dispatch_key' => 2,
7515
  'mandatory' => true,
7516
+ 'value_casei' => array(
7517
+ 'preload',
7518
+ ),
7519
  ),
7520
  'sizes' => array(),
7521
  'target' => array(),
7535
  'crossorigin' => array(),
7536
  'href' => array(
7537
  'mandatory' => true,
7538
+ 'value_regex' => 'https://cdn\\.materialdesignicons\\.com/([0-9]+\\.?)+/css/materialdesignicons\\.min\\.css|https://cloud\\.typography\\.com/[0-9]*/[0-9]*/css/fonts\\.css|https://fast\\.fonts\\.net/.*|https://fonts\\.googleapis\\.com/css\\?.*|https://fonts\\.googleapis\\.com/icon\\?.*|https://fonts\\.googleapis\\.com/earlyaccess/.*\\.css|https://maxcdn\\.bootstrapcdn\\.com/font-awesome/([0-9]+\\.?)+/css/font-awesome\\.min\\.css(\\?.*)?|https://(use|pro)\\.fontawesome\\.com/releases/v([0-9]+\\.?)+/css/(all|brands|solid|regular|light|fontawesome)\\.css|https://(use|pro)\\.fontawesome\\.com/[0-9a-zA-Z]+\\.css|https://use\\.typekit\\.net/[\\w\\p{L}\\p{N}_]+\\.css',
7539
  ),
7540
  'integrity' => array(),
7541
  'media' => array(),
7542
  'rel' => array(
7543
  'dispatch_key' => 2,
7544
  'mandatory' => true,
7545
+ 'value_casei' => array(
7546
+ 'stylesheet',
7547
+ ),
7548
  ),
7549
  'type' => array(
7550
+ 'value_casei' => array(
7551
+ 'text/css',
7552
+ ),
7553
  ),
7554
  ),
7555
  'tag_spec' => array(
7561
  array(
7562
  'attr_spec_list' => array(
7563
  'charset' => array(
7564
+ 'value_casei' => array(
7565
+ 'utf-8',
7566
+ ),
7567
  ),
7568
  'color' => array(),
7569
  'crossorigin' => array(),
7574
  'itemprop' => array(
7575
  'dispatch_key' => 2,
7576
  'mandatory' => true,
7577
+ 'value_casei' => array(
7578
+ 'sameas',
7579
+ ),
7580
  ),
7581
  'media' => array(),
7582
  'sizes' => array(),
7591
  array(
7592
  'attr_spec_list' => array(
7593
  'charset' => array(
7594
+ 'value_casei' => array(
7595
+ 'utf-8',
7596
+ ),
7597
  ),
7598
  'color' => array(),
7599
  'crossorigin' => array(),
7617
  array(
7618
  'attr_spec_list' => array(
7619
  'charset' => array(
7620
+ 'value_casei' => array(
7621
+ 'utf-8',
7622
+ ),
7623
  ),
7624
  'color' => array(),
7625
  'crossorigin' => array(),
7684
  'filter' => array(),
7685
  'flood-color' => array(),
7686
  'flood-opacity' => array(),
7687
+ 'focusable' => array(),
7688
  'font-family' => array(),
7689
  'font-size' => array(),
7690
  'font-size-adjust' => array(),
7772
  'filter' => array(),
7773
  'flood-color' => array(),
7774
  'flood-opacity' => array(),
7775
+ 'focusable' => array(),
7776
  'font-family' => array(),
7777
  'font-size' => array(),
7778
  'font-size-adjust' => array(),
7841
  'charset' => array(
7842
  'dispatch_key' => 1,
7843
  'mandatory' => true,
7844
+ 'value_casei' => array(
7845
+ 'utf-8',
7846
+ ),
7847
  ),
7848
  ),
7849
  'tag_spec' => array(
7862
  'height' => array(),
7863
  'initial-scale' => array(),
7864
  'maximum-scale' => array(),
7865
+ 'minimum-scale' => array(),
 
 
 
7866
  'shrink-to-fit' => array(),
7867
  'user-scalable' => array(),
7868
  'viewport-fit' => array(),
7875
  'name' => array(
7876
  'dispatch_key' => 2,
7877
  'mandatory' => true,
7878
+ 'value' => array(
7879
+ 'viewport',
7880
+ ),
7881
  ),
7882
  ),
7883
  'tag_spec' => array(
7904
  'http-equiv' => array(
7905
  'dispatch_key' => 2,
7906
  'mandatory' => true,
7907
+ 'value_casei' => array(
7908
+ 'x-ua-compatible',
7909
+ ),
7910
  ),
7911
  ),
7912
  'tag_spec' => array(
7924
  'name' => array(
7925
  'dispatch_key' => 2,
7926
  'mandatory' => true,
7927
+ 'value_casei' => array(
7928
+ 'apple-itunes-app',
7929
+ ),
7930
  ),
7931
  ),
7932
  'tag_spec' => array(
7943
  'name' => array(
7944
  'dispatch_key' => 2,
7945
  'mandatory' => true,
7946
+ 'value_casei' => array(
7947
+ 'amp-experiments-opt-in',
7948
+ ),
7949
  ),
7950
  ),
7951
  'tag_spec' => array(
7958
  'content' => array(
7959
  'mandatory' => true,
7960
  'value_url' => array(
7961
+ 'protocol' => array(
7962
  'https',
7963
  ),
7964
  ),
7966
  'name' => array(
7967
  'dispatch_key' => 2,
7968
  'mandatory' => true,
7969
+ 'value_casei' => array(
7970
+ 'amp-3p-iframe-src',
7971
+ ),
7972
  ),
7973
  ),
7974
  'tag_spec' => array(
7985
  'name' => array(
7986
  'dispatch_key' => 2,
7987
  'mandatory' => true,
7988
+ 'value_casei' => array(
7989
+ 'amp-experiment-token',
7990
+ ),
7991
  ),
7992
  ),
7993
  'tag_spec' => array(
8003
  'name' => array(
8004
  'dispatch_key' => 2,
8005
  'mandatory' => true,
8006
+ 'value_casei' => array(
8007
+ 'amp-link-variable-allowed-origin',
8008
+ ),
8009
  ),
8010
  ),
8011
  'tag_spec' => array(
8021
  'name' => array(
8022
  'dispatch_key' => 2,
8023
  'mandatory' => true,
8024
+ 'value_casei' => array(
8025
+ 'amp-google-client-id-api',
8026
+ ),
8027
  ),
8028
  ),
8029
  'tag_spec' => array(
8036
  'name' => array(
8037
  'dispatch_key' => 2,
8038
  'mandatory' => true,
8039
+ 'value_casei' => array(
8040
+ 'amp-ad-doubleclick-sra',
8041
+ ),
8042
  ),
8043
  ),
8044
  'tag_spec' => array(
8064
  'attr_spec_list' => array(
8065
  'content' => array(
8066
  'mandatory' => true,
8067
+ 'value_casei' => array(
8068
+ 'text/html; charset=utf-8',
8069
+ ),
8070
  ),
8071
  'http-equiv' => array(
8072
  'dispatch_key' => 2,
8073
  'mandatory' => true,
8074
+ 'value_casei' => array(
8075
+ 'content-type',
8076
+ ),
8077
  ),
8078
  ),
8079
  'tag_spec' => array(
8090
  'http-equiv' => array(
8091
  'dispatch_key' => 2,
8092
  'mandatory' => true,
8093
+ 'value_casei' => array(
8094
+ 'content-language',
8095
+ ),
8096
  ),
8097
  ),
8098
  'tag_spec' => array(
8109
  'http-equiv' => array(
8110
  'dispatch_key' => 2,
8111
  'mandatory' => true,
8112
+ 'value_casei' => array(
8113
+ 'pics-label',
8114
+ ),
8115
  ),
8116
  ),
8117
  'tag_spec' => array(
8128
  'http-equiv' => array(
8129
  'dispatch_key' => 2,
8130
  'mandatory' => true,
8131
+ 'value_casei' => array(
8132
+ 'imagetoolbar',
8133
+ ),
8134
  ),
8135
  ),
8136
  'tag_spec' => array(
8143
  'attr_spec_list' => array(
8144
  'content' => array(
8145
  'mandatory' => true,
8146
+ 'value_casei' => array(
8147
+ 'text/css',
8148
+ ),
8149
  ),
8150
  'http-equiv' => array(
8151
  'dispatch_key' => 2,
8152
  'mandatory' => true,
8153
+ 'value_casei' => array(
8154
+ 'content-style-type',
8155
+ ),
8156
  ),
8157
  ),
8158
  'tag_spec' => array(
8165
  'attr_spec_list' => array(
8166
  'content' => array(
8167
  'mandatory' => true,
8168
+ 'value_casei' => array(
8169
+ 'text/javascript',
8170
+ ),
8171
  ),
8172
  'http-equiv' => array(
8173
  'dispatch_key' => 2,
8174
  'mandatory' => true,
8175
+ 'value_casei' => array(
8176
+ 'content-script-type',
8177
+ ),
8178
  ),
8179
  ),
8180
  'tag_spec' => array(
8191
  'http-equiv' => array(
8192
  'dispatch_key' => 2,
8193
  'mandatory' => true,
8194
+ 'value_casei' => array(
8195
+ 'origin-trial',
8196
+ ),
8197
  ),
8198
  ),
8199
  'tag_spec' => array(
8210
  'http-equiv' => array(
8211
  'dispatch_key' => 2,
8212
  'mandatory' => true,
8213
+ 'value_casei' => array(
8214
+ 'resource-type',
8215
+ ),
8216
  ),
8217
  ),
8218
  'tag_spec' => array(
8225
  'attr_spec_list' => array(
8226
  'content' => array(
8227
  'mandatory' => true,
8228
+ 'value_casei' => array(
8229
+ 'off',
8230
+ 'on',
8231
+ ),
8232
  ),
8233
  'http-equiv' => array(
8234
  'dispatch_key' => 2,
8235
  'mandatory' => true,
8236
+ 'value_casei' => array(
8237
+ 'x-dns-prefetch-control',
8238
+ ),
8239
  ),
8240
  ),
8241
  'tag_spec' => array(
8252
  'name' => array(
8253
  'dispatch_key' => 2,
8254
  'mandatory' => true,
8255
+ 'value_casei' => array(
8256
+ 'amp-ad-enable-refresh',
8257
+ ),
8258
  ),
8259
  ),
8260
  'tag_spec' => array(
8270
  'name' => array(
8271
  'dispatch_key' => 2,
8272
  'mandatory' => true,
8273
+ 'value_casei' => array(
8274
+ 'amp-to-amp-navigation',
8275
+ ),
8276
  ),
8277
  ),
8278
  'tag_spec' => array(
8382
  array(
8383
  'attr_spec_list' => array(
8384
  'reversed' => array(
8385
+ 'value' => array(
8386
+ '',
8387
+ ),
8388
  ),
8389
  'start' => array(
8390
  'value_regex' => '[0-9]*',
8473
  'filter' => array(),
8474
  'flood-color' => array(),
8475
  'flood-opacity' => array(),
8476
+ 'focusable' => array(),
8477
  'font-family' => array(),
8478
  'font-size' => array(),
8479
  'font-size-adjust' => array(),
8558
  'filter' => array(),
8559
  'flood-color' => array(),
8560
  'flood-opacity' => array(),
8561
+ 'focusable' => array(),
8562
  'font-family' => array(),
8563
  'font-size' => array(),
8564
  'font-size-adjust' => array(),
8620
  ),
8621
  'value_url' => array(
8622
  'allow_empty' => false,
8623
+ 'protocol' => array(
 
8624
  'http',
8625
  'https',
8626
  ),
8642
  ),
8643
  ),
8644
  ),
8645
+ 'picture' => array(
8646
+ array(
8647
+ 'attr_spec_list' => array(),
8648
+ 'tag_spec' => array(
8649
+ 'mandatory_parent' => 'noscript',
8650
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
8651
+ ),
8652
+ ),
8653
+ ),
8654
  'polygon' => array(
8655
  array(
8656
  'attr_spec_list' => array(
8676
  'filter' => array(),
8677
  'flood-color' => array(),
8678
  'flood-opacity' => array(),
8679
+ 'focusable' => array(),
8680
  'font-family' => array(),
8681
  'font-size' => array(),
8682
  'font-size-adjust' => array(),
8761
  'filter' => array(),
8762
  'flood-color' => array(),
8763
  'flood-opacity' => array(),
8764
+ 'focusable' => array(),
8765
  'font-family' => array(),
8766
  'font-size' => array(),
8767
  'font-size-adjust' => array(),
8843
  'blacklisted_value_regex' => '__amp_source_origin',
8844
  'value_url' => array(
8845
  'allow_empty' => true,
8846
+ 'protocol' => array(
 
8847
  'http',
8848
  'https',
8849
  ),
8880
  'filter' => array(),
8881
  'flood-color' => array(),
8882
  'flood-opacity' => array(),
8883
+ 'focusable' => array(),
8884
  'font-family' => array(),
8885
  'font-size' => array(),
8886
  'font-size-adjust' => array(),
8938
  ),
8939
  'value_url' => array(
8940
  'allow_empty' => false,
8941
+ 'protocol' => array(
 
8942
  'http',
8943
  'https',
8944
  ),
8990
  'filter' => array(),
8991
  'flood-color' => array(),
8992
  'flood-opacity' => array(),
8993
+ 'focusable' => array(),
8994
  'font-family' => array(),
8995
  'font-size' => array(),
8996
  'font-size-adjust' => array(),
9096
  'attr_spec_list' => array(
9097
  'async' => array(
9098
  'mandatory' => true,
9099
+ 'value' => array(
9100
+ '',
9101
+ ),
9102
  ),
9103
  'nonce' => array(),
9104
  'src' => array(
9105
  'dispatch_key' => 2,
9106
  'mandatory' => true,
9107
+ 'value' => array(
9108
+ 'https://cdn.ampproject.org/v0.js',
9109
+ ),
9110
  ),
9111
  'type' => array(
9112
+ 'value_casei' => array(
9113
+ 'text/javascript',
9114
+ ),
9115
  ),
9116
  ),
9117
  'cdata' => array(
9134
  'type' => array(
9135
  'dispatch_key' => 2,
9136
  'mandatory' => true,
9137
+ 'value_casei' => array(
9138
+ 'application/ld+json',
9139
+ ),
9140
  ),
9141
  ),
9142
  'cdata' => array(
9154
  'id' => array(
9155
  'dispatch_key' => 2,
9156
  'mandatory' => true,
9157
+ 'value_casei' => array(
9158
+ 'amp-rtc',
9159
+ ),
9160
  ),
9161
  'nonce' => array(),
9162
  'type' => array(
9163
  'mandatory' => true,
9164
+ 'value_casei' => array(
9165
+ 'application/json',
9166
+ ),
9167
  ),
9168
  ),
9169
  'cdata' => array(
9183
  'type' => array(
9184
  'dispatch_key' => 3,
9185
  'mandatory' => true,
9186
+ 'value_casei' => array(
9187
+ 'application/json',
9188
+ ),
9189
  ),
9190
  ),
9191
  'cdata' => array(
9203
  'attr_spec_list' => array(
9204
  'async' => array(
9205
  'mandatory' => true,
9206
+ 'value' => array(
9207
+ '',
9208
+ ),
9209
  ),
9210
  'nonce' => array(),
9211
  'type' => array(
9212
+ 'value_casei' => array(
9213
+ 'text/javascript',
9214
+ ),
9215
  ),
9216
  ),
9217
  'tag_spec' => array(
9218
  'extension_spec' => array(
9219
+ 'name' => 'amp-3d-gltf',
9220
+ 'version' => array(
9221
  '0.1',
9222
  'latest',
9223
  ),
 
9224
  ),
9225
  ),
9226
  ),
9228
  'attr_spec_list' => array(
9229
  'async' => array(
9230
  'mandatory' => true,
9231
+ 'value' => array(
9232
+ '',
9233
+ ),
9234
  ),
9235
  'nonce' => array(),
9236
  'type' => array(
9237
+ 'value_casei' => array(
9238
+ 'text/javascript',
9239
+ ),
9240
  ),
9241
  ),
9242
  'tag_spec' => array(
9243
  'extension_spec' => array(
9244
+ 'name' => 'amp-3q-player',
9245
+ 'version' => array(
9246
  '0.1',
9247
  'latest',
9248
  ),
9249
+ ),
9250
+ ),
9251
+ ),
9252
+ array(
9253
+ 'attr_spec_list' => array(
9254
+ 'async' => array(
9255
+ 'mandatory' => true,
9256
+ 'value' => array(
9257
+ '',
9258
+ ),
9259
+ ),
9260
+ 'nonce' => array(),
9261
+ 'type' => array(
9262
+ 'value_casei' => array(
9263
+ 'text/javascript',
9264
+ ),
9265
+ ),
9266
+ ),
9267
+ 'tag_spec' => array(
9268
+ 'extension_spec' => array(
9269
  'name' => 'amp-access-laterpay',
9270
  'requires_usage' => 3,
9271
+ 'version' => array(
9272
+ '0.1',
9273
+ '0.2',
9274
+ 'latest',
9275
+ ),
9276
  ),
9277
  'requires_extension' => array(
9278
  'amp-access',
9283
  'attr_spec_list' => array(
9284
  'async' => array(
9285
  'mandatory' => true,
9286
+ 'value' => array(
9287
+ '',
9288
+ ),
9289
  ),
9290
  'nonce' => array(),
9291
  'type' => array(
9292
+ 'value_casei' => array(
9293
+ 'text/javascript',
9294
+ ),
9295
  ),
9296
  ),
9297
  'tag_spec' => array(
9298
  'extension_spec' => array(
9299
+ 'name' => 'amp-access-scroll',
9300
+ 'requires_usage' => 3,
9301
+ 'version' => array(
9302
  '0.1',
9303
  'latest',
9304
  ),
 
 
9305
  ),
9306
  'requires_extension' => array(
9307
  'amp-access',
9312
  'attr_spec_list' => array(
9313
  'async' => array(
9314
  'mandatory' => true,
9315
+ 'value' => array(
9316
+ '',
9317
+ ),
9318
  ),
9319
  'nonce' => array(),
9320
  'type' => array(
9321
+ 'value_casei' => array(
9322
+ 'text/javascript',
9323
+ ),
9324
  ),
9325
  ),
9326
  'tag_spec' => array(
9327
  'extension_spec' => array(
 
 
 
 
9328
  'deprecated_allow_duplicates' => true,
9329
  'name' => 'amp-access',
9330
  'requires_usage' => 2,
9331
+ 'version' => array(
9332
+ '0.1',
9333
+ 'latest',
9334
+ ),
9335
  ),
9336
  ),
9337
  ),
9340
  'id' => array(
9341
  'dispatch_key' => 2,
9342
  'mandatory' => true,
9343
+ 'value' => array(
9344
+ 'amp-access',
9345
+ ),
9346
  ),
9347
  'nonce' => array(),
9348
  'type' => array(
9349
  'mandatory' => true,
9350
+ 'value_casei' => array(
9351
+ 'application/json',
9352
+ ),
9353
  ),
9354
  ),
9355
  'cdata' => array(
9362
  'mandatory_parent' => 'head',
9363
  'requires_extension' => array(
9364
  'amp-access',
 
9365
  ),
9366
  'spec_name' => 'amp-access extension .json script',
9367
  'unique' => true,
9371
  'attr_spec_list' => array(
9372
  'async' => array(
9373
  'mandatory' => true,
9374
+ 'value' => array(
9375
+ '',
9376
+ ),
9377
  ),
9378
  'nonce' => array(),
9379
  'type' => array(
9380
+ 'value_casei' => array(
9381
+ 'text/javascript',
9382
+ ),
9383
  ),
9384
  ),
9385
  'tag_spec' => array(
9386
  'extension_spec' => array(
 
 
 
 
9387
  'deprecated_allow_duplicates' => true,
9388
  'name' => 'amp-accordion',
9389
  'requires_usage' => 2,
9390
+ 'version' => array(
9391
+ '0.1',
9392
+ 'latest',
9393
+ ),
9394
  ),
9395
  ),
9396
  ),
9398
  'attr_spec_list' => array(
9399
  'async' => array(
9400
  'mandatory' => true,
9401
+ 'value' => array(
9402
+ '',
9403
+ ),
9404
  ),
9405
  'nonce' => array(),
9406
  'type' => array(
9407
+ 'value_casei' => array(
9408
+ 'text/javascript',
9409
+ ),
9410
  ),
9411
  ),
9412
  'tag_spec' => array(
9413
  'extension_spec' => array(
 
 
 
 
9414
  'deprecated_allow_duplicates' => true,
9415
  'name' => 'amp-ad',
9416
  'requires_usage' => 2,
9417
+ 'version' => array(
9418
+ '0.1',
9419
+ 'latest',
9420
+ ),
9421
  ),
9422
  'spec_name' => 'amp-ad extension .js script',
9423
  ),
9426
  'attr_spec_list' => array(
9427
  'async' => array(
9428
  'mandatory' => true,
9429
+ 'value' => array(
9430
+ '',
9431
+ ),
9432
  ),
9433
  'nonce' => array(),
9434
  'type' => array(
9435
+ 'value_casei' => array(
9436
+ 'text/javascript',
9437
+ ),
9438
  ),
9439
  ),
9440
  'tag_spec' => array(
9441
  'extension_spec' => array(
9442
+ 'name' => 'amp-addthis',
9443
+ 'version' => array(
9444
  '0.1',
9445
  'latest',
9446
  ),
9447
+ ),
9448
+ ),
9449
+ ),
9450
+ array(
9451
+ 'attr_spec_list' => array(
9452
+ 'async' => array(
9453
+ 'mandatory' => true,
9454
+ 'value' => array(
9455
+ '',
9456
+ ),
9457
+ ),
9458
+ 'nonce' => array(),
9459
+ 'type' => array(
9460
+ 'value_casei' => array(
9461
+ 'text/javascript',
9462
+ ),
9463
+ ),
9464
+ ),
9465
+ 'tag_spec' => array(
9466
+ 'extension_spec' => array(
9467
  'deprecated_allow_duplicates' => true,
9468
  'name' => 'amp-analytics',
9469
  'requires_usage' => 2,
9470
+ 'version' => array(
9471
+ '0.1',
9472
+ 'latest',
9473
+ ),
9474
  ),
9475
  ),
9476
  ),
9480
  'type' => array(
9481
  'dispatch_key' => 3,
9482
  'mandatory' => true,
9483
+ 'value_casei' => array(
9484
+ 'application/json',
9485
+ ),
9486
  ),
9487
  ),
9488
  'cdata' => array(
9504
  'attr_spec_list' => array(
9505
  'async' => array(
9506
  'mandatory' => true,
9507
+ 'value' => array(
9508
+ '',
9509
+ ),
9510
  ),
9511
  'nonce' => array(),
9512
  'type' => array(
9513
+ 'value_casei' => array(
9514
+ 'text/javascript',
9515
+ ),
9516
  ),
9517
  ),
9518
  'tag_spec' => array(
9519
  'extension_spec' => array(
 
 
 
 
9520
  'deprecated_allow_duplicates' => true,
9521
  'name' => 'amp-anim',
9522
  'requires_usage' => 2,
9523
+ 'version' => array(
9524
+ '0.1',
9525
+ 'latest',
9526
+ ),
9527
  ),
9528
  ),
9529
  ),
9531
  'attr_spec_list' => array(
9532
  'async' => array(
9533
  'mandatory' => true,
9534
+ 'value' => array(
9535
+ '',
9536
+ ),
9537
  ),
9538
  'nonce' => array(),
9539
  'type' => array(
9540
+ 'value_casei' => array(
9541
+ 'text/javascript',
9542
+ ),
9543
  ),
9544
  ),
9545
  'tag_spec' => array(
9546
  'extension_spec' => array(
9547
+ 'name' => 'amp-animation',
9548
+ 'version' => array(
9549
  '0.1',
9550
  'latest',
9551
  ),
 
9552
  ),
9553
  ),
9554
  ),
9558
  'type' => array(
9559
  'dispatch_key' => 3,
9560
  'mandatory' => true,
9561
+ 'value_casei' => array(
9562
+ 'application/json',
9563
+ ),
9564
  ),
9565
  ),
9566
  'cdata' => array(
9581
  'attr_spec_list' => array(
9582
  'async' => array(
9583
  'mandatory' => true,
9584
+ 'value' => array(
9585
+ '',
9586
+ ),
9587
  ),
9588
  'nonce' => array(),
9589
  'type' => array(
9590
+ 'value_casei' => array(
9591
+ 'text/javascript',
9592
+ ),
9593
  ),
9594
  ),
9595
  'tag_spec' => array(
9596
  'extension_spec' => array(
 
 
 
 
9597
  'deprecated_allow_duplicates' => true,
9598
  'name' => 'amp-apester-media',
9599
  'requires_usage' => 2,
9600
+ 'version' => array(
9601
+ '0.1',
9602
+ 'latest',
9603
+ ),
9604
  ),
9605
  ),
9606
  ),
9608
  'attr_spec_list' => array(
9609
  'async' => array(
9610
  'mandatory' => true,
9611
+ 'value' => array(
9612
+ '',
9613
+ ),
9614
  ),
9615
  'nonce' => array(),
9616
  'type' => array(
9617
+ 'value_casei' => array(
9618
+ 'text/javascript',
9619
+ ),
9620
  ),
9621
  ),
9622
  'tag_spec' => array(
9623
  'extension_spec' => array(
9624
+ 'deprecated_allow_duplicates' => true,
9625
+ 'name' => 'amp-app-banner',
9626
+ 'version' => array(
9627
  '0.1',
9628
  'latest',
9629
  ),
 
 
9630
  ),
9631
  ),
9632
  ),
9634
  'attr_spec_list' => array(
9635
  'async' => array(
9636
  'mandatory' => true,
9637
+ 'value' => array(
9638
+ '',
9639
+ ),
9640
  ),
9641
  'nonce' => array(),
9642
  'type' => array(
9643
+ 'value_casei' => array(
9644
+ 'text/javascript',
9645
+ ),
9646
  ),
9647
  ),
9648
  'tag_spec' => array(
9649
  'extension_spec' => array(
 
 
 
 
9650
  'deprecated_allow_duplicates' => true,
9651
  'name' => 'amp-audio',
9652
  'requires_usage' => 2,
9653
+ 'version' => array(
9654
+ '0.1',
9655
+ 'latest',
9656
+ ),
9657
  ),
9658
  ),
9659
  ),
9661
  'attr_spec_list' => array(
9662
  'async' => array(
9663
  'mandatory' => true,
9664
+ 'value' => array(
9665
+ '',
9666
+ ),
9667
  ),
9668
  'nonce' => array(),
9669
  'type' => array(
9670
+ 'value_casei' => array(
9671
+ 'text/javascript',
9672
+ ),
9673
  ),
9674
  ),
9675
  'tag_spec' => array(
9676
  'extension_spec' => array(
9677
+ 'name' => 'amp-auto-ads',
9678
+ 'version' => array(
9679
  '0.1',
9680
  'latest',
9681
  ),
 
9682
  ),
9683
  ),
9684
  ),
9686
  'attr_spec_list' => array(
9687
  'async' => array(
9688
  'mandatory' => true,
9689
+ 'value' => array(
9690
+ '',
9691
+ ),
9692
  ),
9693
  'nonce' => array(),
9694
  'type' => array(
9695
+ 'value_casei' => array(
9696
+ 'text/javascript',
9697
+ ),
9698
  ),
9699
  ),
9700
  'tag_spec' => array(
9701
  'extension_spec' => array(
9702
+ 'name' => 'amp-beopinion',
9703
+ 'version' => array(
9704
  '0.1',
9705
  'latest',
9706
  ),
 
9707
  ),
9708
  ),
9709
  ),
9711
  'attr_spec_list' => array(
9712
  'async' => array(
9713
  'mandatory' => true,
9714
+ 'value' => array(
9715
+ '',
9716
+ ),
9717
  ),
9718
  'nonce' => array(),
9719
  'type' => array(
9720
+ 'value_casei' => array(
9721
+ 'text/javascript',
9722
+ ),
9723
  ),
9724
  ),
9725
  'tag_spec' => array(
9726
  'extension_spec' => array(
9727
+ 'name' => 'amp-bind',
9728
+ 'requires_usage' => 3,
9729
+ 'version' => array(
9730
  '0.1',
9731
  'latest',
9732
  ),
 
 
9733
  ),
9734
  ),
9735
  ),
9739
  'type' => array(
9740
  'dispatch_key' => 3,
9741
  'mandatory' => true,
9742
+ 'value_casei' => array(
9743
+ 'application/json',
9744
+ ),
9745
  ),
9746
  ),
9747
  'cdata' => array(
9749
  'error_message' => 'html comments',
9750
  'regex' => '<!--',
9751
  ),
9752
+ 'max_bytes' => 100000,
9753
+ 'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/components/dynamic/amp-bind#state',
9754
  ),
9755
  'tag_spec' => array(
9756
  'mandatory_parent' => 'amp-state',
9765
  'attr_spec_list' => array(
9766
  'async' => array(
9767
  'mandatory' => true,
9768
+ 'value' => array(
9769
+ '',
9770
+ ),
9771
  ),
9772
  'nonce' => array(),
9773
  'type' => array(
9774
+ 'value_casei' => array(
9775
+ 'text/javascript',
9776
+ ),
9777
  ),
9778
  ),
9779
  'tag_spec' => array(
9780
  'extension_spec' => array(
9781
+ 'name' => 'amp-bodymovin-animation',
9782
+ 'version' => array(
9783
  '0.1',
9784
  'latest',
9785
  ),
 
9786
  ),
9787
  ),
9788
  ),
9790
  'attr_spec_list' => array(
9791
  'async' => array(
9792
  'mandatory' => true,
9793
+ 'value' => array(
9794
+ '',
9795
+ ),
9796
  ),
9797
  'nonce' => array(),
9798
  'type' => array(
9799
+ 'value_casei' => array(
9800
+ 'text/javascript',
9801
+ ),
9802
  ),
9803
  ),
9804
  'tag_spec' => array(
9805
  'extension_spec' => array(
9806
+ 'deprecated_allow_duplicates' => true,
9807
+ 'name' => 'amp-brid-player',
9808
+ 'requires_usage' => 2,
9809
+ 'version' => array(
9810
  '0.1',
9811
  'latest',
9812
  ),
9813
+ ),
9814
+ ),
9815
+ ),
9816
+ array(
9817
+ 'attr_spec_list' => array(
9818
+ 'async' => array(
9819
+ 'mandatory' => true,
9820
+ 'value' => array(
9821
+ '',
9822
+ ),
9823
+ ),
9824
+ 'nonce' => array(),
9825
+ 'type' => array(
9826
+ 'value_casei' => array(
9827
+ 'text/javascript',
9828
+ ),
9829
+ ),
9830
+ ),
9831
+ 'tag_spec' => array(
9832
+ 'extension_spec' => array(
9833
  'deprecated_allow_duplicates' => true,
9834
+ 'name' => 'amp-brightcove',
9835
  'requires_usage' => 2,
9836
+ 'version' => array(
9837
+ '0.1',
9838
+ 'latest',
9839
+ ),
9840
  ),
9841
  ),
9842
  ),
9844
  'attr_spec_list' => array(
9845
  'async' => array(
9846
  'mandatory' => true,
9847
+ 'value' => array(
9848
+ '',
9849
+ ),
9850
  ),
9851
  'nonce' => array(),
9852
  'type' => array(
9853
+ 'value_casei' => array(
9854
+ 'text/javascript',
9855
+ ),
9856
  ),
9857
  ),
9858
  'tag_spec' => array(
9859
  'extension_spec' => array(
9860
+ 'name' => 'amp-byside-content',
9861
+ 'version' => array(
9862
  '0.1',
9863
  'latest',
9864
  ),
9865
+ ),
9866
+ ),
9867
+ ),
9868
+ array(
9869
+ 'attr_spec_list' => array(
9870
+ 'async' => array(
9871
+ 'mandatory' => true,
9872
+ 'value' => array(
9873
+ '',
9874
+ ),
9875
+ ),
9876
+ 'nonce' => array(),
9877
+ 'type' => array(
9878
+ 'value_casei' => array(
9879
+ 'text/javascript',
9880
+ ),
9881
+ ),
9882
+ ),
9883
+ 'tag_spec' => array(
9884
+ 'extension_spec' => array(
9885
+ 'name' => 'amp-call-tracking',
9886
  'requires_usage' => 2,
9887
+ 'version' => array(
9888
+ '0.1',
9889
+ 'latest',
9890
+ ),
9891
  ),
9892
  ),
9893
  ),
9895
  'attr_spec_list' => array(
9896
  'async' => array(
9897
  'mandatory' => true,
9898
+ 'value' => array(
9899
+ '',
9900
+ ),
9901
  ),
9902
  'nonce' => array(),
9903
  'type' => array(
9904
+ 'value_casei' => array(
9905
+ 'text/javascript',
9906
+ ),
9907
  ),
9908
  ),
9909
  'tag_spec' => array(
9910
  'extension_spec' => array(
9911
+ 'deprecated_allow_duplicates' => true,
9912
+ 'name' => 'amp-carousel',
9913
+ 'requires_usage' => 2,
9914
+ 'version' => array(
9915
  '0.1',
9916
  'latest',
9917
  ),
 
9918
  ),
9919
  ),
9920
  ),
9922
  'attr_spec_list' => array(
9923
  'async' => array(
9924
  'mandatory' => true,
9925
+ 'value' => array(
9926
+ '',
9927
+ ),
9928
  ),
9929
  'nonce' => array(),
9930
  'type' => array(
9931
+ 'value_casei' => array(
9932
+ 'text/javascript',
9933
+ ),
9934
  ),
9935
  ),
9936
  'tag_spec' => array(
9937
  'extension_spec' => array(
9938
+ 'name' => 'amp-consent',
9939
+ 'version' => array(
9940
  '0.1',
9941
  'latest',
9942
  ),
9943
+ ),
9944
+ ),
9945
+ ),
9946
+ array(
9947
+ 'attr_spec_list' => array(
9948
+ 'nonce' => array(),
9949
+ 'type' => array(
9950
+ 'dispatch_key' => 3,
9951
+ 'mandatory' => true,
9952
+ 'value_casei' => array(
9953
+ 'application/json',
9954
+ ),
9955
+ ),
9956
+ ),
9957
+ 'cdata' => array(
9958
+ 'blacklisted_cdata_regex' => array(
9959
+ 'error_message' => 'html comments',
9960
+ 'regex' => '<!--',
9961
+ ),
9962
+ ),
9963
+ 'tag_spec' => array(
9964
+ 'mandatory_parent' => 'amp-consent',
9965
+ 'requires_extension' => array(
9966
+ 'amp-consent',
9967
+ ),
9968
+ 'spec_name' => 'amp-consent extension .json script',
9969
+ 'unique' => true,
9970
+ ),
9971
+ ),
9972
+ array(
9973
+ 'attr_spec_list' => array(
9974
+ 'async' => array(
9975
+ 'mandatory' => true,
9976
+ 'value' => array(
9977
+ '',
9978
+ ),
9979
+ ),
9980
+ 'nonce' => array(),
9981
+ 'type' => array(
9982
+ 'value_casei' => array(
9983
+ 'text/javascript',
9984
+ ),
9985
+ ),
9986
+ ),
9987
+ 'tag_spec' => array(
9988
+ 'extension_spec' => array(
9989
+ 'deprecated_allow_duplicates' => true,
9990
+ 'name' => 'amp-dailymotion',
9991
  'requires_usage' => 2,
9992
+ 'version' => array(
9993
+ '0.1',
9994
+ 'latest',
9995
+ ),
9996
  ),
9997
  ),
9998
  ),
10000
  'attr_spec_list' => array(
10001
  'async' => array(
10002
  'mandatory' => true,
10003
+ 'value' => array(
10004
+ '',
10005
+ ),
10006
  ),
10007
  'nonce' => array(),
10008
  'type' => array(
10009
+ 'value_casei' => array(
10010
+ 'text/javascript',
10011
+ ),
10012
  ),
10013
  ),
10014
  'tag_spec' => array(
10015
  'extension_spec' => array(
10016
+ 'name' => 'amp-date-countdown',
10017
+ 'version' => array(
10018
  '0.1',
10019
  'latest',
10020
  ),
10021
+ ),
10022
+ ),
10023
+ ),
10024
+ array(
10025
+ 'attr_spec_list' => array(
10026
+ 'async' => array(
10027
+ 'mandatory' => true,
10028
+ 'value' => array(
10029
+ '',
10030
+ ),
10031
+ ),
10032
+ 'nonce' => array(),
10033
+ 'type' => array(
10034
+ 'value_casei' => array(
10035
+ 'text/javascript',
10036
+ ),
10037
+ ),
10038
+ ),
10039
+ 'tag_spec' => array(
10040
+ 'extension_spec' => array(
10041
+ 'name' => 'amp-date-picker',
10042
+ 'version' => array(
10043
+ '0.1',
10044
+ 'latest',
10045
+ ),
10046
+ ),
10047
+ ),
10048
+ ),
10049
+ array(
10050
+ 'attr_spec_list' => array(
10051
+ 'async' => array(
10052
+ 'mandatory' => true,
10053
+ 'value' => array(
10054
+ '',
10055
+ ),
10056
+ ),
10057
+ 'nonce' => array(),
10058
+ 'type' => array(
10059
+ 'value_casei' => array(
10060
+ 'text/javascript',
10061
+ ),
10062
+ ),
10063
+ ),
10064
+ 'tag_spec' => array(
10065
+ 'extension_spec' => array(
10066
+ 'name' => 'amp-delight-player',
10067
+ 'version' => array(
10068
+ '0.1',
10069
+ 'latest',
10070
+ ),
10071
+ ),
10072
+ ),
10073
+ ),
10074
+ array(
10075
+ 'attr_spec_list' => array(
10076
+ 'async' => array(
10077
+ 'mandatory' => true,
10078
+ 'value' => array(
10079
+ '',
10080
+ ),
10081
+ ),
10082
+ 'nonce' => array(),
10083
+ 'type' => array(
10084
+ 'value_casei' => array(
10085
+ 'text/javascript',
10086
+ ),
10087
+ ),
10088
+ ),
10089
+ 'tag_spec' => array(
10090
+ 'extension_spec' => array(
10091
  'deprecated_allow_duplicates' => true,
10092
+ 'name' => 'amp-dynamic-css-classes',
10093
+ 'requires_usage' => 3,
10094
+ 'version' => array(
10095
+ '0.1',
10096
+ 'latest',
10097
+ ),
10098
+ ),
10099
+ ),
10100
+ ),
10101
+ array(
10102
+ 'attr_spec_list' => array(
10103
+ 'async' => array(
10104
+ 'mandatory' => true,
10105
+ 'value' => array(
10106
+ '',
10107
+ ),
10108
+ ),
10109
+ 'nonce' => array(),
10110
+ 'type' => array(
10111
+ 'value_casei' => array(
10112
+ 'text/javascript',
10113
+ ),
10114
+ ),
10115
+ ),
10116
+ 'tag_spec' => array(
10117
+ 'extension_spec' => array(
10118
+ 'name' => 'amp-embedly-card',
10119
+ 'version' => array(
10120
+ '0.1',
10121
+ 'latest',
10122
+ ),
10123
+ ),
10124
+ ),
10125
+ ),
10126
+ array(
10127
+ 'attr_spec_list' => array(
10128
+ 'async' => array(
10129
+ 'mandatory' => true,
10130
+ 'value' => array(
10131
+ '',
10132
+ ),
10133
+ ),
10134
+ 'nonce' => array(),
10135
+ 'type' => array(
10136
+ 'value_casei' => array(
10137
+ 'text/javascript',
10138
+ ),
10139
+ ),
10140
+ ),
10141
+ 'tag_spec' => array(
10142
+ 'extension_spec' => array(
10143
+ 'deprecated_allow_duplicates' => true,
10144
+ 'name' => 'amp-experiment',
10145
  'requires_usage' => 2,
10146
+ 'version' => array(
10147
+ '0.1',
10148
+ 'latest',
10149
+ ),
10150
+ ),
10151
+ ),
10152
+ ),
10153
+ array(
10154
+ 'attr_spec_list' => array(
10155
+ 'nonce' => array(),
10156
+ 'type' => array(
10157
+ 'dispatch_key' => 3,
10158
+ 'mandatory' => true,
10159
+ 'value_casei' => array(
10160
+ 'application/json',
10161
+ ),
10162
+ ),
10163
+ ),
10164
+ 'cdata' => array(
10165
+ 'blacklisted_cdata_regex' => array(
10166
+ 'error_message' => 'html comments',
10167
+ 'regex' => '<!--',
10168
+ ),
10169
+ ),
10170
+ 'tag_spec' => array(
10171
+ 'mandatory_parent' => 'amp-experiment',
10172
+ 'spec_name' => 'amp-experiment extension .json script',
10173
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-experiment',
10174
+ ),
10175
+ ),
10176
+ array(
10177
+ 'attr_spec_list' => array(
10178
+ 'async' => array(
10179
+ 'mandatory' => true,
10180
+ 'value' => array(
10181
+ '',
10182
+ ),
10183
+ ),
10184
+ 'nonce' => array(),
10185
+ 'type' => array(
10186
+ 'value_casei' => array(
10187
+ 'text/javascript',
10188
+ ),
10189
+ ),
10190
+ ),
10191
+ 'tag_spec' => array(
10192
+ 'extension_spec' => array(
10193
+ 'name' => 'amp-facebook-comments',
10194
+ 'version' => array(
10195
+ '0.1',
10196
+ 'latest',
10197
+ ),
10198
+ ),
10199
+ ),
10200
+ ),
10201
+ array(
10202
+ 'attr_spec_list' => array(
10203
+ 'async' => array(
10204
+ 'mandatory' => true,
10205
+ 'value' => array(
10206
+ '',
10207
+ ),
10208
+ ),
10209
+ 'nonce' => array(),
10210
+ 'type' => array(
10211
+ 'value_casei' => array(
10212
+ 'text/javascript',
10213
+ ),
10214
+ ),
10215
+ ),
10216
+ 'tag_spec' => array(
10217
+ 'extension_spec' => array(
10218
+ 'name' => 'amp-facebook-like',
10219
+ 'version' => array(
10220
+ '0.1',
10221
+ 'latest',
10222
+ ),
10223
  ),
10224
  ),
10225
  ),
10227
  'attr_spec_list' => array(
10228
  'async' => array(
10229
  'mandatory' => true,
10230
+ 'value' => array(
10231
+ '',
10232
+ ),
10233
  ),
10234
  'nonce' => array(),
10235
  'type' => array(
10236
+ 'value_casei' => array(
10237
+ 'text/javascript',
10238
+ ),
10239
  ),
10240
  ),
10241
  'tag_spec' => array(
10242
  'extension_spec' => array(
10243
+ 'name' => 'amp-facebook-page',
10244
+ 'version' => array(
10245
  '0.1',
10246
  'latest',
10247
  ),
 
10248
  ),
10249
  ),
10250
  ),
10251
  array(
10252
  'attr_spec_list' => array(
10253
+ 'async' => array(
 
 
10254
  'mandatory' => true,
10255
+ 'value' => array(
10256
+ '',
10257
+ ),
10258
  ),
10259
+ 'nonce' => array(),
10260
+ 'type' => array(
10261
+ 'value_casei' => array(
10262
+ 'text/javascript',
10263
+ ),
10264
  ),
10265
  ),
10266
  'tag_spec' => array(
10267
+ 'extension_spec' => array(
10268
+ 'deprecated_allow_duplicates' => true,
10269
+ 'name' => 'amp-facebook',
10270
+ 'requires_usage' => 2,
10271
+ 'version' => array(
10272
+ '0.1',
10273
+ 'latest',
10274
+ ),
10275
  ),
 
 
10276
  ),
10277
  ),
10278
  array(
10279
  'attr_spec_list' => array(
10280
  'async' => array(
10281
  'mandatory' => true,
10282
+ 'value' => array(
10283
+ '',
10284
+ ),
10285
  ),
10286
  'nonce' => array(),
10287
  'type' => array(
10288
+ 'value_casei' => array(
10289
+ 'text/javascript',
10290
+ ),
10291
  ),
10292
  ),
10293
  'tag_spec' => array(
10294
  'extension_spec' => array(
10295
+ 'deprecated_allow_duplicates' => true,
10296
+ 'name' => 'amp-fit-text',
10297
+ 'requires_usage' => 2,
10298
+ 'version' => array(
10299
  '0.1',
10300
  'latest',
10301
  ),
 
 
 
10302
  ),
10303
  ),
10304
  ),
10306
  'attr_spec_list' => array(
10307
  'async' => array(
10308
  'mandatory' => true,
10309
+ 'value' => array(
10310
+ '',
10311
+ ),
10312
  ),
10313
  'nonce' => array(),
10314
  'type' => array(
10315
+ 'value_casei' => array(
10316
+ 'text/javascript',
10317
+ ),
10318
  ),
10319
  ),
10320
  'tag_spec' => array(
10321
  'extension_spec' => array(
10322
+ 'deprecated_allow_duplicates' => true,
10323
+ 'name' => 'amp-font',
10324
+ 'requires_usage' => 2,
10325
+ 'version' => array(
10326
  '0.1',
10327
  'latest',
10328
  ),
 
10329
  ),
10330
  ),
10331
  ),
10332
  array(
10333
+ 'attr_spec_list' => array(
10334
+ 'async' => array(
10335
+ 'mandatory' => true,
10336
+ 'value' => array(
10337
+ '',
10338
+ ),
10339
+ ),
10340
+ 'nonce' => array(),
10341
+ 'type' => array(
10342
+ 'value_casei' => array(
10343
+ 'text/javascript',
10344
+ ),
10345
+ ),
10346
+ ),
10347
  'tag_spec' => array(
10348
  'extension_spec' => array(
10349
+ 'deprecated_allow_duplicates' => true,
10350
+ 'name' => 'amp-form',
10351
+ 'requires_usage' => 2,
10352
+ 'version' => array(
10353
  '0.1',
10354
  'latest',
10355
  ),
 
10356
  ),
10357
  ),
10358
  ),
10359
  array(
10360
  'attr_spec_list' => array(
10361
+ 'async' => array(
 
10362
  'mandatory' => true,
10363
+ 'value' => array(
10364
+ '',
10365
+ ),
10366
+ ),
10367
+ 'nonce' => array(),
10368
+ 'type' => array(
10369
+ 'value_casei' => array(
10370
+ 'text/javascript',
10371
+ ),
10372
  ),
10373
  ),
10374
  'tag_spec' => array(
10375
+ 'extension_spec' => array(
10376
+ 'name' => 'amp-fx-collection',
10377
+ 'requires_usage' => 3,
10378
+ 'version' => array(
10379
+ '0.1',
10380
+ 'latest',
10381
+ ),
10382
  ),
 
 
10383
  ),
10384
  ),
10385
  array(
10386
  'attr_spec_list' => array(
10387
  'async' => array(
10388
  'mandatory' => true,
10389
+ 'value' => array(
10390
+ '',
10391
+ ),
10392
  ),
10393
  'nonce' => array(),
10394
  'type' => array(
10395
+ 'value_casei' => array(
10396
+ 'text/javascript',
10397
+ ),
10398
  ),
10399
  ),
10400
  'tag_spec' => array(
10401
  'extension_spec' => array(
10402
+ 'deprecated_allow_duplicates' => true,
10403
+ 'name' => 'amp-fx-flying-carpet',
10404
+ 'requires_usage' => 2,
10405
+ 'version' => array(
10406
  '0.1',
10407
  'latest',
10408
  ),
 
 
 
10409
  ),
10410
  ),
10411
  ),
10413
  'attr_spec_list' => array(
10414
  'async' => array(
10415
  'mandatory' => true,
10416
+ 'value' => array(
10417
+ '',
10418
+ ),
10419
  ),
10420
  'nonce' => array(),
10421
  'type' => array(
10422
+ 'value_casei' => array(
10423
+ 'text/javascript',
10424
+ ),
10425
  ),
10426
  ),
10427
  'tag_spec' => array(
10428
  'extension_spec' => array(
10429
+ 'name' => 'amp-geo',
10430
+ 'version' => array(
10431
  '0.1',
10432
  'latest',
10433
  ),
 
 
 
10434
  ),
10435
  ),
10436
  ),
10440
  'type' => array(
10441
  'dispatch_key' => 3,
10442
  'mandatory' => true,
10443
+ 'value_casei' => array(
10444
+ 'application/json',
10445
+ ),
10446
  ),
10447
  ),
10448
  'cdata' => array(
10452
  ),
10453
  ),
10454
  'tag_spec' => array(
10455
+ 'mandatory_parent' => 'amp-geo',
10456
+ 'requires_extension' => array(
10457
+ 'amp-geo',
10458
+ ),
10459
+ 'spec_name' => 'amp-geo extension .json script',
10460
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-geo',
10461
  ),
10462
  ),
10463
  array(
10464
  'attr_spec_list' => array(
10465
  'async' => array(
10466
  'mandatory' => true,
10467
+ 'value' => array(
10468
+ '',
10469
+ ),
10470
  ),
10471
  'nonce' => array(),
10472
  'type' => array(
10473
+ 'value_casei' => array(
10474
+ 'text/javascript',
10475
+ ),
10476
  ),
10477
  ),
10478
  'tag_spec' => array(
10479
  'extension_spec' => array(
10480
+ 'deprecated_allow_duplicates' => true,
10481
+ 'name' => 'amp-gfycat',
10482
+ 'requires_usage' => 2,
10483
+ 'version' => array(
10484
  '0.1',
10485
  'latest',
10486
  ),
 
10487
  ),
10488
  ),
10489
  ),
10491
  'attr_spec_list' => array(
10492
  'async' => array(
10493
  'mandatory' => true,
10494
+ 'value' => array(
10495
+ '',
10496
+ ),
10497
  ),
10498
  'nonce' => array(),
10499
  'type' => array(
10500
+ 'value_casei' => array(
10501
+ 'text/javascript',
10502
+ ),
10503
  ),
10504
  ),
10505
  'tag_spec' => array(
10506
  'extension_spec' => array(
10507
+ 'name' => 'amp-gist',
10508
+ 'version' => array(
10509
  '0.1',
10510
  'latest',
10511
  ),
 
10512
  ),
10513
  ),
10514
  ),
10516
  'attr_spec_list' => array(
10517
  'async' => array(
10518
  'mandatory' => true,
10519
+ 'value' => array(
10520
+ '',
10521
+ ),
10522
  ),
10523
  'nonce' => array(),
10524
  'type' => array(
10525
+ 'value_casei' => array(
10526
+ 'text/javascript',
10527
+ ),
10528
  ),
10529
  ),
10530
  'tag_spec' => array(
10531
  'extension_spec' => array(
10532
+ 'name' => 'amp-google-document-embed',
10533
+ 'version' => array(
10534
  '0.1',
10535
  'latest',
10536
  ),
 
10537
  ),
10538
  ),
10539
  ),
10541
  'attr_spec_list' => array(
10542
  'async' => array(
10543
  'mandatory' => true,
10544
+ 'value' => array(
10545
+ '',
10546
+ ),
10547
  ),
10548
  'nonce' => array(),
10549
  'type' => array(
10550
+ 'value_casei' => array(
10551
+ 'text/javascript',
10552
+ ),
10553
  ),
10554
  ),
10555
  'tag_spec' => array(
10556
  'extension_spec' => array(
10557
+ 'name' => 'amp-hulu',
10558
+ 'version' => array(
10559
  '0.1',
10560
  'latest',
10561
  ),
 
 
 
10562
  ),
10563
  ),
10564
  ),
10566
  'attr_spec_list' => array(
10567
  'async' => array(
10568
  'mandatory' => true,
10569
+ 'value' => array(
10570
+ '',
10571
+ ),
10572
  ),
10573
  'nonce' => array(),
10574
  'type' => array(
10575
+ 'value_casei' => array(
10576
+ 'text/javascript',
10577
+ ),
10578
  ),
10579
  ),
10580
  'tag_spec' => array(
10581
  'extension_spec' => array(
10582
+ 'deprecated_allow_duplicates' => true,
10583
+ 'name' => 'amp-iframe',
10584
+ 'requires_usage' => 2,
10585
+ 'version' => array(
10586
  '0.1',
10587
  'latest',
10588
  ),
 
 
 
10589
  ),
10590
  ),
10591
  ),
10593
  'attr_spec_list' => array(
10594
  'async' => array(
10595
  'mandatory' => true,
10596
+ 'value' => array(
10597
+ '',
10598
+ ),
10599
  ),
10600
  'nonce' => array(),
10601
  'type' => array(
10602
+ 'value_casei' => array(
10603
+ 'text/javascript',
10604
+ ),
10605
  ),
10606
  ),
10607
  'tag_spec' => array(
10608
  'extension_spec' => array(
10609
+ 'name' => 'amp-ima-video',
10610
+ 'version' => array(
10611
  '0.1',
10612
  'latest',
10613
  ),
 
 
 
10614
  ),
10615
  ),
10616
  ),
10618
  'attr_spec_list' => array(
10619
  'async' => array(
10620
  'mandatory' => true,
10621
+ 'value' => array(
10622
+ '',
10623
+ ),
10624
  ),
10625
  'nonce' => array(),
10626
  'type' => array(
10627
+ 'value_casei' => array(
10628
+ 'text/javascript',
10629
+ ),
10630
  ),
10631
  ),
10632
  'tag_spec' => array(
10633
  'extension_spec' => array(
10634
+ 'deprecated_allow_duplicates' => true,
10635
+ 'name' => 'amp-image-lightbox',
10636
+ 'requires_usage' => 2,
10637
+ 'version' => array(
10638
  '0.1',
10639
  'latest',
10640
  ),
 
 
 
10641
  ),
10642
  ),
10643
  ),
10645
  'attr_spec_list' => array(
10646
  'async' => array(
10647
  'mandatory' => true,
10648
+ 'value' => array(
10649
+ '',
10650
+ ),
10651
  ),
10652
  'nonce' => array(),
10653
  'type' => array(
10654
+ 'value_casei' => array(
10655
+ 'text/javascript',
10656
+ ),
10657
  ),
10658
  ),
10659
  'tag_spec' => array(
10660
  'extension_spec' => array(
10661
+ 'name' => 'amp-image-slider',
10662
+ 'version' => array(
10663
  '0.1',
10664
  'latest',
10665
  ),
 
 
10666
  ),
10667
  ),
10668
  ),
10670
  'attr_spec_list' => array(
10671
  'async' => array(
10672
  'mandatory' => true,
10673
+ 'value' => array(
10674
+ '',
10675
+ ),
10676
  ),
10677
  'nonce' => array(),
10678
  'type' => array(
10679
+ 'value_casei' => array(
10680
+ 'text/javascript',
10681
+ ),
10682
  ),
10683
  ),
10684
  'tag_spec' => array(
10685
  'extension_spec' => array(
10686
+ 'name' => 'amp-imgur',
10687
+ 'version' => array(
10688
  '0.1',
10689
  'latest',
10690
  ),
 
 
 
10691
  ),
10692
  ),
10693
  ),
10695
  'attr_spec_list' => array(
10696
  'async' => array(
10697
  'mandatory' => true,
10698
+ 'value' => array(
10699
+ '',
10700
+ ),
10701
  ),
10702
  'nonce' => array(),
10703
  'type' => array(
10704
+ 'value_casei' => array(
10705
+ 'text/javascript',
10706
+ ),
10707
  ),
10708
  ),
10709
  'tag_spec' => array(
10710
  'extension_spec' => array(
10711
+ 'name' => 'amp-inputmask',
10712
+ 'requires_usage' => 3,
10713
+ 'version' => array(
10714
  '0.1',
10715
  'latest',
10716
  ),
 
 
 
10717
  ),
10718
  ),
10719
  ),
10721
  'attr_spec_list' => array(
10722
  'async' => array(
10723
  'mandatory' => true,
10724
+ 'value' => array(
10725
+ '',
10726
+ ),
10727
  ),
10728
  'nonce' => array(),
10729
  'type' => array(
10730
+ 'value_casei' => array(
10731
+ 'text/javascript',
10732
+ ),
10733
  ),
10734
  ),
10735
  'tag_spec' => array(
10736
  'extension_spec' => array(
10737
+ 'deprecated_allow_duplicates' => true,
10738
+ 'name' => 'amp-instagram',
10739
+ 'requires_usage' => 2,
10740
+ 'version' => array(
10741
  '0.1',
10742
  'latest',
10743
  ),
 
10744
  ),
10745
  ),
10746
  ),
10748
  'attr_spec_list' => array(
10749
  'async' => array(
10750
  'mandatory' => true,
10751
+ 'value' => array(
10752
+ '',
10753
+ ),
10754
  ),
10755
  'nonce' => array(),
10756
  'type' => array(
10757
+ 'value_casei' => array(
10758
+ 'text/javascript',
10759
+ ),
10760
  ),
10761
  ),
10762
  'tag_spec' => array(
10763
  'extension_spec' => array(
10764
+ 'deprecated_allow_duplicates' => true,
10765
+ 'name' => 'amp-install-serviceworker',
10766
+ 'requires_usage' => 2,
10767
+ 'version' => array(
10768
  '0.1',
10769
  'latest',
10770
  ),
 
10771
  ),
10772
  ),
10773
  ),
10775
  'attr_spec_list' => array(
10776
  'async' => array(
10777
  'mandatory' => true,
10778
+ 'value' => array(
10779
+ '',
10780
+ ),
10781
  ),
10782
  'nonce' => array(),
10783
  'type' => array(
10784
+ 'value_casei' => array(
10785
+ 'text/javascript',
10786
+ ),
10787
  ),
10788
  ),
10789
  'tag_spec' => array(
10790
  'extension_spec' => array(
10791
+ 'name' => 'amp-izlesene',
10792
+ 'requires_usage' => 2,
10793
+ 'version' => array(
10794
  '0.1',
10795
  'latest',
10796
  ),
 
 
 
10797
  ),
10798
  ),
10799
  ),
10801
  'attr_spec_list' => array(
10802
  'async' => array(
10803
  'mandatory' => true,
10804
+ 'value' => array(
10805
+ '',
10806
+ ),
10807
  ),
10808
  'nonce' => array(),
10809
  'type' => array(
10810
+ 'value_casei' => array(
10811
+ 'text/javascript',
10812
+ ),
10813
  ),
10814
  ),
10815
  'tag_spec' => array(
10816
  'extension_spec' => array(
10817
+ 'deprecated_allow_duplicates' => true,
10818
+ 'name' => 'amp-jwplayer',
10819
+ 'requires_usage' => 2,
10820
+ 'version' => array(
10821
  '0.1',
10822
  'latest',
10823
  ),
 
10824
  ),
10825
  ),
10826
  ),
10828
  'attr_spec_list' => array(
10829
  'async' => array(
10830
  'mandatory' => true,
10831
+ 'value' => array(
10832
+ '',
10833
+ ),
10834
  ),
10835
  'nonce' => array(),
10836
  'type' => array(
10837
+ 'value_casei' => array(
10838
+ 'text/javascript',
10839
+ ),
10840
  ),
10841
  ),
10842
  'tag_spec' => array(
10843
  'extension_spec' => array(
10844
+ 'deprecated_allow_duplicates' => true,
10845
+ 'name' => 'amp-kaltura-player',
10846
+ 'requires_usage' => 2,
10847
+ 'version' => array(
10848
  '0.1',
10849
  'latest',
10850
  ),
 
 
 
10851
  ),
10852
  ),
10853
  ),
10855
  'attr_spec_list' => array(
10856
  'async' => array(
10857
  'mandatory' => true,
10858
+ 'value' => array(
10859
+ '',
10860
+ ),
10861
  ),
10862
  'nonce' => array(),
10863
  'type' => array(
10864
+ 'value_casei' => array(
10865
+ 'text/javascript',
10866
+ ),
10867
  ),
10868
  ),
10869
  'tag_spec' => array(
10870
  'extension_spec' => array(
10871
+ 'name' => 'amp-lightbox-gallery',
10872
+ 'requires_usage' => 3,
10873
+ 'version' => array(
10874
  '0.1',
10875
  'latest',
10876
  ),
 
10877
  ),
10878
  ),
10879
  ),
10881
  'attr_spec_list' => array(
10882
  'async' => array(
10883
  'mandatory' => true,
10884
+ 'value' => array(
10885
+ '',
10886
+ ),
10887
  ),
10888
  'nonce' => array(),
10889
  'type' => array(
10890
+ 'value_casei' => array(
10891
+ 'text/javascript',
10892
+ ),
10893
  ),
10894
  ),
10895
  'tag_spec' => array(
10896
  'extension_spec' => array(
10897
+ 'deprecated_allow_duplicates' => true,
10898
+ 'name' => 'amp-lightbox',
10899
+ 'requires_usage' => 2,
10900
+ 'version' => array(
10901
  '0.1',
10902
  'latest',
10903
  ),
 
 
 
10904
  ),
10905
  ),
10906
  ),
10908
  'attr_spec_list' => array(
10909
  'async' => array(
10910
  'mandatory' => true,
10911
+ 'value' => array(
10912
+ '',
10913
+ ),
10914
  ),
10915
  'nonce' => array(),
10916
  'type' => array(
10917
+ 'value_casei' => array(
10918
+ 'text/javascript',
10919
+ ),
10920
  ),
10921
  ),
10922
  'tag_spec' => array(
10923
  'extension_spec' => array(
10924
+ 'deprecated_allow_duplicates' => true,
10925
+ 'name' => 'amp-list',
10926
+ 'requires_usage' => 2,
10927
+ 'version' => array(
10928
  '0.1',
10929
  'latest',
10930
  ),
 
 
 
10931
  ),
10932
  ),
10933
  ),
10935
  'attr_spec_list' => array(
10936
  'async' => array(
10937
  'mandatory' => true,
10938
+ 'value' => array(
10939
+ '',
10940
+ ),
10941
  ),
10942
  'nonce' => array(),
10943
  'type' => array(
10944
+ 'value_casei' => array(
10945
+ 'text/javascript',
10946
+ ),
10947
  ),
10948
  ),
10949
  'tag_spec' => array(
10950
  'extension_spec' => array(
10951
+ 'name' => 'amp-live-list',
10952
+ 'requires_usage' => 2,
10953
+ 'version' => array(
10954
  '0.1',
10955
  'latest',
10956
  ),
 
 
10957
  ),
10958
+ 'mandatory_parent' => 'head',
10959
+ 'unique_warning' => true,
10960
  ),
10961
  ),
10962
  array(
10963
  'attr_spec_list' => array(
10964
  'async' => array(
10965
  'mandatory' => true,
10966
+ 'value' => array(
10967
+ '',
10968
+ ),
10969
  ),
10970
  'nonce' => array(),
10971
  'type' => array(
10972
+ 'value_casei' => array(
10973
+ 'text/javascript',
10974
+ ),
10975
  ),
10976
  ),
10977
  'tag_spec' => array(
10978
  'extension_spec' => array(
10979
+ 'name' => 'amp-mathml',
10980
+ 'version' => array(
10981
  '0.1',
10982
  'latest',
10983
  ),
 
 
 
10984
  ),
10985
  ),
10986
  ),
10988
  'attr_spec_list' => array(
10989
  'async' => array(
10990
  'mandatory' => true,
10991
+ 'value' => array(
10992
+ '',
10993
+ ),
10994
  ),
10995
  'nonce' => array(),
10996
  'type' => array(
10997
+ 'value_casei' => array(
10998
+ 'text/javascript',
10999
+ ),
11000
  ),
11001
  ),
11002
  'tag_spec' => array(
11003
  'extension_spec' => array(
11004
+ 'name' => 'amp-mowplayer',
11005
+ 'version' => array(
11006
  '0.1',
11007
  'latest',
11008
  ),
 
 
 
11009
  ),
11010
  ),
11011
  ),
11013
  'attr_spec_list' => array(
11014
  'async' => array(
11015
  'mandatory' => true,
11016
+ 'value' => array(
11017
+ '',
11018
+ ),
11019
  ),
11020
  'nonce' => array(),
11021
  'type' => array(
11022
+ 'value_casei' => array(
11023
+ 'text/javascript',
11024
+ ),
11025
  ),
11026
  ),
11027
  'tag_spec' => array(
11028
  'extension_spec' => array(
11029
+ 'deprecated_allow_duplicates' => true,
11030
+ 'deprecated_version' => array(
11031
+ '0.1',
11032
+ ),
11033
+ 'is_custom_template' => true,
11034
+ 'name' => 'amp-mustache',
11035
+ 'requires_usage' => 2,
11036
+ 'version' => array(
11037
  '0.1',
11038
+ '0.2',
11039
  'latest',
11040
  ),
 
 
11041
  ),
11042
  ),
11043
  ),
11045
  'attr_spec_list' => array(
11046
  'async' => array(
11047
  'mandatory' => true,
11048
+ 'value' => array(
11049
+ '',
11050
+ ),
11051
  ),
11052
  'nonce' => array(),
11053
  'type' => array(
11054
+ 'value_casei' => array(
11055
+ 'text/javascript',
11056
+ ),
11057
  ),
11058
  ),
11059
  'tag_spec' => array(
11060
  'extension_spec' => array(
11061
+ 'name' => 'amp-next-page',
11062
+ 'version' => array(
11063
  '0.1',
11064
  'latest',
11065
  ),
 
 
 
11066
  ),
11067
  ),
11068
  ),
11069
+ array(
11070
+ 'attr_spec_list' => array(
11071
+ 'type' => array(
11072
+ 'dispatch_key' => 3,
11073
+ 'mandatory' => true,
11074
+ 'value_casei' => array(
11075
+ 'application/json',
11076
+ ),
11077
+ ),
11078
+ ),
11079
+ 'tag_spec' => array(
11080
+ 'mandatory_parent' => 'amp-next-page',
11081
+ 'requires_extension' => array(
11082
+ 'amp-next-page',
11083
+ ),
11084
+ 'spec_name' => 'amp-next-page extension .json configuration',
11085
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-next-page',
11086
+ ),
11087
+ ),
11088
  array(
11089
  'attr_spec_list' => array(
11090
  'async' => array(
11091
  'mandatory' => true,
11092
+ 'value' => array(
11093
+ '',
11094
+ ),
11095
  ),
11096
  'nonce' => array(),
11097
  'type' => array(
11098
+ 'value_casei' => array(
11099
+ 'text/javascript',
11100
+ ),
11101
  ),
11102
  ),
11103
  'tag_spec' => array(
11104
  'extension_spec' => array(
11105
+ 'name' => 'amp-nexxtv-player',
11106
+ 'version' => array(
11107
  '0.1',
11108
+ 'latest',
11109
+ ),
 
 
 
11110
  ),
11111
  ),
11112
  ),
11114
  'attr_spec_list' => array(
11115
  'async' => array(
11116
  'mandatory' => true,
11117
+ 'value' => array(
11118
+ '',
11119
+ ),
11120
  ),
11121
  'nonce' => array(),
11122
  'type' => array(
11123
+ 'value_casei' => array(
11124
+ 'text/javascript',
11125
+ ),
11126
  ),
11127
  ),
11128
  'tag_spec' => array(
11129
  'extension_spec' => array(
11130
+ 'deprecated_allow_duplicates' => true,
11131
+ 'name' => 'amp-o2-player',
11132
+ 'requires_usage' => 2,
11133
+ 'version' => array(
11134
  '0.1',
11135
  'latest',
11136
  ),
 
 
11137
  ),
 
 
11138
  ),
11139
  ),
11140
  array(
11141
  'attr_spec_list' => array(
11142
  'async' => array(
11143
  'mandatory' => true,
11144
+ 'value' => array(
11145
+ '',
11146
+ ),
11147
  ),
11148
  'nonce' => array(),
11149
  'type' => array(
11150
+ 'value_casei' => array(
11151
+ 'text/javascript',
11152
+ ),
11153
  ),
11154
  ),
11155
  'tag_spec' => array(
11156
  'extension_spec' => array(
11157
+ 'name' => 'amp-ooyala-player',
11158
+ 'version' => array(
11159
  '0.1',
11160
  'latest',
11161
  ),
 
11162
  ),
11163
  ),
11164
  ),
11166
  'attr_spec_list' => array(
11167
  'async' => array(
11168
  'mandatory' => true,
11169
+ 'value' => array(
11170
+ '',
11171
+ ),
11172
  ),
11173
  'nonce' => array(),
11174
  'type' => array(
11175
+ 'value_casei' => array(
11176
+ 'text/javascript',
11177
+ ),
11178
  ),
11179
  ),
11180
  'tag_spec' => array(
11181
  'extension_spec' => array(
11182
+ 'name' => 'amp-orientation-observer',
11183
+ 'version' => array(
11184
  '0.1',
11185
  'latest',
11186
  ),
 
 
 
 
11187
  ),
11188
  ),
11189
  ),
11191
  'attr_spec_list' => array(
11192
  'async' => array(
11193
  'mandatory' => true,
11194
+ 'value' => array(
11195
+ '',
11196
+ ),
11197
  ),
11198
  'nonce' => array(),
11199
  'type' => array(
11200
+ 'value_casei' => array(
11201
+ 'text/javascript',
11202
+ ),
11203
  ),
11204
  ),
11205
  'tag_spec' => array(
11206
  'extension_spec' => array(
11207
+ 'name' => 'amp-pan-zoom',
11208
+ 'version' => array(
11209
  '0.1',
11210
  'latest',
11211
  ),
 
11212
  ),
11213
  ),
11214
  ),
11216
  'attr_spec_list' => array(
11217
  'async' => array(
11218
  'mandatory' => true,
11219
+ 'value' => array(
11220
+ '',
11221
+ ),
11222
  ),
11223
  'nonce' => array(),
11224
  'type' => array(
11225
+ 'value_casei' => array(
11226
+ 'text/javascript',
11227
+ ),
11228
  ),
11229
  ),
11230
  'tag_spec' => array(
11231
  'extension_spec' => array(
11232
+ 'deprecated_allow_duplicates' => true,
11233
+ 'name' => 'amp-pinterest',
11234
+ 'requires_usage' => 2,
11235
+ 'version' => array(
11236
  '0.1',
11237
  'latest',
11238
  ),
 
 
 
11239
  ),
11240
  ),
11241
  ),
11243
  'attr_spec_list' => array(
11244
  'async' => array(
11245
  'mandatory' => true,
11246
+ 'value' => array(
11247
+ '',
11248
+ ),
11249
  ),
11250
  'nonce' => array(),
11251
  'type' => array(
11252
+ 'value_casei' => array(
11253
+ 'text/javascript',
11254
+ ),
11255
  ),
11256
  ),
11257
  'tag_spec' => array(
11258
  'extension_spec' => array(
11259
+ 'name' => 'amp-playbuzz',
11260
+ 'version' => array(
11261
  '0.1',
11262
  'latest',
11263
  ),
 
11264
  ),
11265
  ),
11266
  ),
11268
  'attr_spec_list' => array(
11269
  'async' => array(
11270
  'mandatory' => true,
11271
+ 'value' => array(
11272
+ '',
11273
+ ),
11274
  ),
11275
  'nonce' => array(),
11276
  'type' => array(
11277
+ 'value_casei' => array(
11278
+ 'text/javascript',
11279
+ ),
11280
  ),
11281
  ),
11282
  'tag_spec' => array(
11283
  'extension_spec' => array(
11284
+ 'name' => 'amp-position-observer',
11285
+ 'version' => array(
11286
  '0.1',
11287
  'latest',
11288
  ),
 
 
 
11289
  ),
11290
  ),
11291
  ),
11293
  'attr_spec_list' => array(
11294
  'async' => array(
11295
  'mandatory' => true,
11296
+ 'value' => array(
11297
+ '',
11298
+ ),
11299
  ),
11300
  'nonce' => array(),
11301
  'type' => array(
11302
+ 'value_casei' => array(
11303
+ 'text/javascript',
11304
+ ),
11305
  ),
11306
  ),
11307
  'tag_spec' => array(
11308
  'extension_spec' => array(
11309
+ 'name' => 'amp-powr-player',
11310
+ 'version' => array(
11311
  '0.1',
11312
  'latest',
11313
  ),
 
11314
  ),
11315
  ),
11316
  ),
11318
  'attr_spec_list' => array(
11319
  'async' => array(
11320
  'mandatory' => true,
11321
+ 'value' => array(
11322
+ '',
11323
+ ),
11324
  ),
11325
  'nonce' => array(),
11326
  'type' => array(
11327
+ 'value_casei' => array(
11328
+ 'text/javascript',
11329
+ ),
11330
  ),
11331
  ),
11332
  'tag_spec' => array(
11333
  'extension_spec' => array(
11334
+ 'deprecated_allow_duplicates' => true,
11335
+ 'name' => 'amp-reach-player',
11336
+ 'requires_usage' => 2,
11337
+ 'version' => array(
11338
  '0.1',
11339
  'latest',
11340
  ),
 
11341
  ),
11342
  ),
11343
  ),
11345
  'attr_spec_list' => array(
11346
  'async' => array(
11347
  'mandatory' => true,
11348
+ 'value' => array(
11349
+ '',
11350
+ ),
11351
  ),
11352
  'nonce' => array(),
11353
  'type' => array(
11354
+ 'value_casei' => array(
11355
+ 'text/javascript',
11356
+ ),
11357
  ),
11358
  ),
11359
  'tag_spec' => array(
11360
  'extension_spec' => array(
11361
+ 'deprecated_allow_duplicates' => true,
11362
+ 'name' => 'amp-reddit',
11363
+ 'version' => array(
11364
  '0.1',
11365
  'latest',
11366
  ),
 
 
 
11367
  ),
11368
  ),
11369
  ),
11371
  'attr_spec_list' => array(
11372
  'async' => array(
11373
  'mandatory' => true,
11374
+ 'value' => array(
11375
+ '',
11376
+ ),
11377
  ),
11378
  'nonce' => array(),
11379
  'type' => array(
11380
+ 'value_casei' => array(
11381
+ 'text/javascript',
11382
+ ),
11383
  ),
11384
  ),
11385
  'tag_spec' => array(
11386
  'extension_spec' => array(
11387
+ 'name' => 'amp-riddle-quiz',
11388
+ 'version' => array(
11389
  '0.1',
11390
  'latest',
11391
  ),
 
 
11392
  ),
11393
  ),
11394
  ),
11396
  'attr_spec_list' => array(
11397
  'async' => array(
11398
  'mandatory' => true,
11399
+ 'value' => array(
11400
+ '',
11401
+ ),
11402
  ),
11403
  'nonce' => array(),
11404
  'type' => array(
11405
+ 'value_casei' => array(
11406
+ 'text/javascript',
11407
+ ),
11408
  ),
11409
  ),
11410
  'tag_spec' => array(
11411
  'extension_spec' => array(
11412
+ 'name' => 'amp-selector',
11413
+ 'requires_usage' => 2,
11414
+ 'version' => array(
11415
  '0.1',
11416
  'latest',
11417
  ),
 
11418
  ),
11419
  ),
11420
  ),
11422
  'attr_spec_list' => array(
11423
  'async' => array(
11424
  'mandatory' => true,
11425
+ 'value' => array(
11426
+ '',
11427
+ ),
11428
  ),
11429
  'nonce' => array(),
11430
  'type' => array(
11431
+ 'value_casei' => array(
11432
+ 'text/javascript',
11433
+ ),
11434
  ),
11435
  ),
11436
  'tag_spec' => array(
11437
  'extension_spec' => array(
11438
+ 'deprecated_allow_duplicates' => true,
11439
+ 'name' => 'amp-sidebar',
11440
+ 'requires_usage' => 2,
11441
+ 'version' => array(
11442
  '0.1',
11443
  'latest',
11444
  ),
 
 
11445
  ),
11446
  ),
11447
  ),
11449
  'attr_spec_list' => array(
11450
  'async' => array(
11451
  'mandatory' => true,
11452
+ 'value' => array(
11453
+ '',
11454
+ ),
11455
  ),
11456
  'nonce' => array(),
11457
  'type' => array(
11458
+ 'value_casei' => array(
11459
+ 'text/javascript',
11460
+ ),
11461
  ),
11462
  ),
11463
  'tag_spec' => array(
11464
  'extension_spec' => array(
11465
+ 'name' => 'amp-skimlinks',
11466
+ 'version' => array(
11467
  '0.1',
11468
  'latest',
11469
  ),
 
 
 
11470
  ),
11471
  ),
11472
  ),
11474
  'attr_spec_list' => array(
11475
  'async' => array(
11476
  'mandatory' => true,
11477
+ 'value' => array(
11478
+ '',
11479
+ ),
11480
  ),
11481
  'nonce' => array(),
11482
  'type' => array(
11483
+ 'value_casei' => array(
11484
+ 'text/javascript',
11485
+ ),
11486
  ),
11487
  ),
11488
  'tag_spec' => array(
11489
  'extension_spec' => array(
 
 
 
 
11490
  'deprecated_allow_duplicates' => true,
11491
  'name' => 'amp-social-share',
11492
  'requires_usage' => 2,
11493
+ 'version' => array(
11494
+ '0.1',
11495
+ 'latest',
11496
+ ),
11497
  ),
11498
  ),
11499
  ),
11501
  'attr_spec_list' => array(
11502
  'async' => array(
11503
  'mandatory' => true,
11504
+ 'value' => array(
11505
+ '',
11506
+ ),
11507
  ),
11508
  'nonce' => array(),
11509
  'type' => array(
11510
+ 'value_casei' => array(
11511
+ 'text/javascript',
11512
+ ),
11513
  ),
11514
  ),
11515
  'tag_spec' => array(
11516
  'extension_spec' => array(
 
 
 
 
11517
  'deprecated_allow_duplicates' => true,
11518
  'name' => 'amp-soundcloud',
11519
  'requires_usage' => 2,
11520
+ 'version' => array(
11521
+ '0.1',
11522
+ 'latest',
11523
+ ),
11524
  ),
11525
  ),
11526
  ),
11528
  'attr_spec_list' => array(
11529
  'async' => array(
11530
  'mandatory' => true,
11531
+ 'value' => array(
11532
+ '',
11533
+ ),
11534
  ),
11535
  'nonce' => array(),
11536
  'type' => array(
11537
+ 'value_casei' => array(
11538
+ 'text/javascript',
11539
+ ),
11540
  ),
11541
  ),
11542
  'tag_spec' => array(
11543
  'extension_spec' => array(
 
 
 
 
11544
  'deprecated_allow_duplicates' => true,
11545
  'name' => 'amp-springboard-player',
11546
  'requires_usage' => 2,
11547
+ 'version' => array(
11548
+ '0.1',
11549
+ 'latest',
11550
+ ),
11551
  ),
11552
  ),
11553
  ),
11555
  'attr_spec_list' => array(
11556
  'async' => array(
11557
  'mandatory' => true,
11558
+ 'value' => array(
11559
+ '',
11560
+ ),
11561
  ),
11562
  'nonce' => array(),
11563
  'type' => array(
11564
+ 'value_casei' => array(
11565
+ 'text/javascript',
11566
+ ),
11567
  ),
11568
  ),
11569
  'tag_spec' => array(
11570
  'extension_spec' => array(
11571
+ 'deprecated_version' => array(
 
 
 
 
 
11572
  '0.1',
11573
  ),
11574
  'name' => 'amp-sticky-ad',
11575
  'requires_usage' => 2,
11576
+ 'version' => array(
11577
+ '0.1',
11578
+ '1.0',
11579
+ 'latest',
11580
+ ),
11581
  ),
11582
  ),
11583
  ),
11585
  'attr_spec_list' => array(
11586
  'async' => array(
11587
  'mandatory' => true,
11588
+ 'value' => array(
11589
+ '',
11590
+ ),
11591
  ),
11592
  'nonce' => array(),
11593
  'type' => array(
11594
+ 'value_casei' => array(
11595
+ 'text/javascript',
11596
+ ),
11597
  ),
11598
  ),
11599
  'tag_spec' => array(
11600
  'extension_spec' => array(
11601
+ 'name' => 'amp-story-auto-ads',
11602
+ 'version' => array(
11603
  '0.1',
11604
  'latest',
11605
  ),
 
11606
  ),
11607
  ),
11608
  ),
11612
  'type' => array(
11613
  'dispatch_key' => 3,
11614
  'mandatory' => true,
11615
+ 'value_casei' => array(
11616
+ 'application/json',
11617
+ ),
11618
  ),
11619
  ),
11620
  'cdata' => array(
11636
  'attr_spec_list' => array(
11637
  'async' => array(
11638
  'mandatory' => true,
11639
+ 'value' => array(
11640
+ '',
11641
+ ),
11642
  ),
11643
  'nonce' => array(),
11644
  'type' => array(
11645
+ 'value_casei' => array(
11646
+ 'text/javascript',
11647
+ ),
11648
  ),
11649
  ),
11650
  'tag_spec' => array(
11651
  'extension_spec' => array(
11652
+ 'name' => 'amp-story',
11653
+ 'version' => array(
11654
  '0.1',
11655
+ '1.0',
11656
  'latest',
11657
  ),
 
11658
  ),
11659
  ),
11660
  ),
11661
+ array(
11662
+ 'attr_spec_list' => array(
11663
+ 'type' => array(
11664
+ 'dispatch_key' => 3,
11665
+ 'mandatory' => true,
11666
+ 'value_casei' => array(
11667
+ 'application/json',
11668
+ ),
11669
+ ),
11670
+ ),
11671
+ 'tag_spec' => array(
11672
+ 'mandatory_parent' => 'amp-story-bookend',
11673
+ 'requires_extension' => array(
11674
+ 'amp-story',
11675
+ ),
11676
+ 'spec_name' => 'amp-story-bookend extension .json script',
11677
+ 'unique' => true,
11678
+ ),
11679
+ ),
11680
+ array(
11681
+ 'attr_spec_list' => array(
11682
+ 'nonce' => array(),
11683
+ 'type' => array(
11684
+ 'dispatch_key' => 3,
11685
+ 'mandatory' => true,
11686
+ 'value_casei' => array(
11687
+ 'application/json',
11688
+ ),
11689
+ ),
11690
+ ),
11691
+ 'cdata' => array(
11692
+ 'blacklisted_cdata_regex' => array(
11693
+ 'error_message' => 'html comments',
11694
+ 'regex' => '<!--',
11695
+ ),
11696
+ ),
11697
+ 'tag_spec' => array(
11698
+ 'mandatory_parent' => 'amp-story-consent',
11699
+ 'requires_extension' => array(
11700
+ 'amp-consent',
11701
+ 'amp-story',
11702
+ ),
11703
+ 'spec_name' => 'amp-story-consent extension .json script',
11704
+ 'unique' => true,
11705
+ ),
11706
+ ),
11707
  array(
11708
  'attr_spec_list' => array(
11709
  'async' => array(
11710
  'mandatory' => true,
11711
+ 'value' => array(
11712
+ '',
11713
+ ),
11714
  ),
11715
  'nonce' => array(),
11716
  'type' => array(
11717
+ 'value_casei' => array(
11718
+ 'text/javascript',
11719
+ ),
11720
  ),
11721
  ),
11722
  'tag_spec' => array(
11723
  'extension_spec' => array(
11724
+ 'name' => 'amp-subscriptions',
11725
+ 'requires_usage' => 3,
11726
+ 'version' => array(
11727
  '0.1',
11728
  'latest',
11729
  ),
 
 
11730
  ),
11731
  ),
11732
  ),
11735
  'id' => array(
11736
  'dispatch_key' => 2,
11737
  'mandatory' => true,
11738
+ 'value' => array(
11739
+ 'amp-subscriptions',
11740
+ ),
11741
  ),
11742
  'nonce' => array(),
11743
  'type' => array(
11744
  'mandatory' => true,
11745
+ 'value_casei' => array(
11746
+ 'application/json',
11747
+ ),
11748
  ),
11749
  ),
11750
  'cdata' => array(
11766
  'attr_spec_list' => array(
11767
  'async' => array(
11768
  'mandatory' => true,
11769
+ 'value' => array(
11770
+ '',
11771
+ ),
11772
  ),
11773
  'nonce' => array(),
11774
  'type' => array(
11775
+ 'value_casei' => array(
11776
+ 'text/javascript',
11777
+ ),
11778
  ),
11779
  ),
11780
  'tag_spec' => array(
11781
  'extension_spec' => array(
11782
+ 'name' => 'amp-subscriptions-google',
11783
+ 'requires_usage' => 3,
11784
+ 'version' => array(
11785
  '0.1',
11786
  'latest',
11787
  ),
 
 
11788
  ),
11789
  'requires_extension' => array(
11790
  'amp-subscriptions',
11795
  'attr_spec_list' => array(
11796
  'async' => array(
11797
  'mandatory' => true,
11798
+ 'value' => array(
11799
+ '',
11800
+ ),
11801
  ),
11802
  'nonce' => array(),
11803
  'type' => array(
11804
+ 'value_casei' => array(
11805
+ 'text/javascript',
11806
+ ),
11807
  ),
11808
  ),
11809
  'tag_spec' => array(
11810
  'extension_spec' => array(
11811
+ 'name' => 'amp-timeago',
11812
+ 'version' => array(
11813
  '0.1',
11814
  'latest',
11815
  ),
 
11816
  ),
11817
  ),
11818
  ),
11820
  'attr_spec_list' => array(
11821
  'async' => array(
11822
  'mandatory' => true,
11823
+ 'value' => array(
11824
+ '',
11825
+ ),
11826
  ),
11827
  'nonce' => array(),
11828
  'type' => array(
11829
+ 'value_casei' => array(
11830
+ 'text/javascript',
11831
+ ),
11832
  ),
11833
  ),
11834
  'tag_spec' => array(
11835
  'extension_spec' => array(
11836
+ 'deprecated_allow_duplicates' => true,
11837
+ 'name' => 'amp-twitter',
11838
+ 'requires_usage' => 2,
11839
+ 'version' => array(
11840
  '0.1',
11841
  'latest',
11842
  ),
11843
+ ),
11844
+ ),
11845
+ ),
11846
+ array(
11847
+ 'attr_spec_list' => array(
11848
+ 'async' => array(
11849
+ 'mandatory' => true,
11850
+ 'value' => array(
11851
+ '',
11852
+ ),
11853
+ ),
11854
+ 'nonce' => array(),
11855
+ 'type' => array(
11856
+ 'value_casei' => array(
11857
+ 'text/javascript',
11858
+ ),
11859
+ ),
11860
+ ),
11861
+ 'tag_spec' => array(
11862
+ 'extension_spec' => array(
11863
  'deprecated_allow_duplicates' => true,
11864
+ 'name' => 'amp-user-notification',
11865
  'requires_usage' => 2,
11866
+ 'version' => array(
11867
+ '0.1',
11868
+ 'latest',
11869
+ ),
11870
  ),
11871
  ),
11872
  ),
11874
  'attr_spec_list' => array(
11875
  'async' => array(
11876
  'mandatory' => true,
11877
+ 'value' => array(
11878
+ '',
11879
+ ),
11880
  ),
11881
  'nonce' => array(),
11882
  'type' => array(
11883
+ 'value_casei' => array(
11884
+ 'text/javascript',
11885
+ ),
11886
  ),
11887
  ),
11888
  'tag_spec' => array(
11889
  'extension_spec' => array(
11890
+ 'name' => 'amp-video',
11891
+ 'requires_usage' => 3,
11892
+ 'version' => array(
11893
  '0.1',
11894
  'latest',
11895
  ),
11896
+ ),
11897
+ 'spec_name' => 'amp-video extension .js script',
11898
+ ),
11899
+ ),
11900
+ array(
11901
+ 'attr_spec_list' => array(
11902
+ 'async' => array(
11903
+ 'mandatory' => true,
11904
+ 'value' => array(
11905
+ '',
11906
+ ),
11907
+ ),
11908
+ 'nonce' => array(),
11909
+ 'type' => array(
11910
+ 'value_casei' => array(
11911
+ 'text/javascript',
11912
+ ),
11913
+ ),
11914
+ ),
11915
+ 'tag_spec' => array(
11916
+ 'extension_spec' => array(
11917
  'deprecated_allow_duplicates' => true,
11918
+ 'name' => 'amp-vimeo',
11919
  'requires_usage' => 2,
11920
+ 'version' => array(
11921
+ '0.1',
11922
+ 'latest',
11923
+ ),
11924
  ),
11925
  ),
11926
  ),
11928
  'attr_spec_list' => array(
11929
  'async' => array(
11930
  'mandatory' => true,
11931
+ 'value' => array(
11932
+ '',
11933
+ ),
11934
  ),
11935
  'nonce' => array(),
11936
  'type' => array(
11937
+ 'value_casei' => array(
11938
+ 'text/javascript',
11939
+ ),
11940
  ),
11941
  ),
11942
  'tag_spec' => array(
11943
  'extension_spec' => array(
11944
+ 'deprecated_allow_duplicates' => true,
11945
+ 'name' => 'amp-vine',
11946
+ 'requires_usage' => 2,
11947
+ 'version' => array(
11948
  '0.1',
11949
  'latest',
11950
  ),
 
 
11951
  ),
 
11952
  ),
11953
  ),
11954
  array(
11955
  'attr_spec_list' => array(
11956
  'async' => array(
11957
  'mandatory' => true,
11958
+ 'value' => array(
11959
+ '',
11960
+ ),
11961
  ),
11962
  'nonce' => array(),
11963
  'type' => array(
11964
+ 'value_casei' => array(
11965
+ 'text/javascript',
11966
+ ),
11967
  ),
11968
  ),
11969
  'tag_spec' => array(
11970
  'extension_spec' => array(
11971
+ 'name' => 'amp-viqeo-player',
11972
+ 'version' => array(
11973
  '0.1',
11974
  'latest',
11975
  ),
 
 
 
11976
  ),
11977
  ),
11978
  ),
11980
  'attr_spec_list' => array(
11981
  'async' => array(
11982
  'mandatory' => true,
11983
+ 'value' => array(
11984
+ '',
11985
+ ),
11986
  ),
11987
  'nonce' => array(),
11988
  'type' => array(
11989
+ 'value_casei' => array(
11990
+ 'text/javascript',
11991
+ ),
11992
  ),
11993
  ),
11994
  'tag_spec' => array(
11995
  'extension_spec' => array(
11996
+ 'name' => 'amp-vk',
11997
+ 'version' => array(
11998
  '0.1',
11999
  'latest',
12000
  ),
 
 
 
12001
  ),
12002
  ),
12003
  ),
12005
  'attr_spec_list' => array(
12006
  'async' => array(
12007
  'mandatory' => true,
12008
+ 'value' => array(
12009
+ '',
12010
+ ),
12011
  ),
12012
  'nonce' => array(),
12013
  'type' => array(
12014
+ 'value_casei' => array(
12015
+ 'text/javascript',
12016
+ ),
12017
  ),
12018
  ),
12019
  'tag_spec' => array(
12020
  'extension_spec' => array(
12021
+ 'name' => 'amp-web-push',
12022
+ 'version' => array(
12023
  '0.1',
12024
  'latest',
12025
  ),
 
12026
  ),
12027
  ),
12028
  ),
12030
  'attr_spec_list' => array(
12031
  'async' => array(
12032
  'mandatory' => true,
12033
+ 'value' => array(
12034
+ '',
12035
+ ),
12036
  ),
12037
  'nonce' => array(),
12038
  'type' => array(
12039
+ 'value_casei' => array(
12040
+ 'text/javascript',
12041
+ ),
12042
  ),
12043
  ),
12044
  'tag_spec' => array(
12045
  'extension_spec' => array(
12046
+ 'name' => 'amp-wistia-player',
12047
+ 'version' => array(
12048
  '0.1',
12049
  'latest',
12050
  ),
 
12051
  ),
12052
  ),
12053
  ),
12055
  'attr_spec_list' => array(
12056
  'async' => array(
12057
  'mandatory' => true,
12058
+ 'value' => array(
12059
+ '',
12060
+ ),
12061
  ),
12062
  'nonce' => array(),
12063
  'type' => array(
12064
+ 'value_casei' => array(
12065
+ 'text/javascript',
12066
+ ),
12067
  ),
12068
  ),
12069
  'tag_spec' => array(
12070
  'extension_spec' => array(
12071
+ 'name' => 'amp-yotpo',
12072
+ 'version' => array(
12073
  '0.1',
12074
  'latest',
12075
  ),
 
12076
  ),
12077
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-yotpo',
12078
  ),
12079
  ),
12080
  array(
12081
  'attr_spec_list' => array(
12082
  'async' => array(
12083
  'mandatory' => true,
12084
+ 'value' => array(
12085
+ '',
12086
+ ),
12087
  ),
12088
  'nonce' => array(),
12089
  'type' => array(
12090
+ 'value_casei' => array(
12091
+ 'text/javascript',
12092
+ ),
12093
  ),
12094
  ),
12095
  'tag_spec' => array(
12096
  'extension_spec' => array(
 
 
 
 
12097
  'deprecated_allow_duplicates' => true,
12098
  'name' => 'amp-youtube',
12099
  'requires_usage' => 2,
12100
+ 'version' => array(
12101
+ '0.1',
12102
+ 'latest',
12103
+ ),
12104
  ),
12105
  ),
12106
  ),
12117
  array(
12118
  'attr_spec_list' => array(
12119
  'expanded' => array(
12120
+ 'value' => array(
12121
+ '',
12122
+ ),
12123
  ),
12124
  ),
12125
  'tag_spec' => array(
12142
  'name' => array(
12143
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
12144
  ),
12145
+ 'no-verify' => array(
12146
+ 'value' => array(
12147
+ '',
12148
+ ),
12149
+ ),
12150
  'required' => array(),
12151
  'size' => array(),
12152
  ),
12193
  'filter' => array(),
12194
  'flood-color' => array(),
12195
  'flood-opacity' => array(),
12196
+ 'focusable' => array(),
12197
  'font-family' => array(),
12198
  'font-size' => array(),
12199
  'font-size-adjust' => array(),
12250
  ),
12251
  ),
12252
  'source' => array(
12253
+ array(
12254
+ 'attr_spec_list' => array(
12255
+ 'media' => array(),
12256
+ 'sizes' => array(),
12257
+ 'srcset' => array(
12258
+ 'blacklisted_value_regex' => '__amp_source_origin',
12259
+ 'value_url' => array(
12260
+ 'allow_relative' => true,
12261
+ 'protocol' => array(
12262
+ 'data',
12263
+ 'http',
12264
+ 'https',
12265
+ ),
12266
+ ),
12267
+ ),
12268
+ 'type' => array(),
12269
+ ),
12270
+ 'tag_spec' => array(
12271
+ 'mandatory_parent' => 'picture',
12272
+ 'spec_name' => 'picture > source',
12273
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
12274
+ ),
12275
+ ),
12276
  array(
12277
  'attr_spec_list' => array(
12278
  '[src]' => array(),
12282
  'blacklisted_value_regex' => '__amp_source_origin',
12283
  'value_url' => array(
12284
  'allow_relative' => true,
12285
+ 'protocol' => array(
12286
  'https',
12287
  ),
12288
  ),
12304
  'blacklisted_value_regex' => '__amp_source_origin',
12305
  'value_url' => array(
12306
  'allow_relative' => true,
12307
+ 'protocol' => array(
12308
  'https',
12309
  ),
12310
  ),
12325
  'mandatory' => true,
12326
  'value_url' => array(
12327
  'allow_relative' => true,
12328
+ 'protocol' => array(
12329
  'https',
12330
  ),
12331
  ),
12348
  'mandatory' => true,
12349
  'value_url' => array(
12350
  'allow_relative' => true,
12351
+ 'protocol' => array(
12352
  'https',
12353
  ),
12354
  ),
12372
  'blacklisted_value_regex' => '__amp_source_origin',
12373
  'value_url' => array(
12374
  'allow_relative' => true,
12375
+ 'protocol' => array(
12376
  'https',
12377
  ),
12378
  ),
12449
  'attr_spec_list' => array(
12450
  'amp-custom' => array(
12451
  'mandatory' => true,
12452
+ 'value' => array(
12453
+ '',
12454
+ ),
12455
  ),
12456
  'nonce' => array(),
12457
  'type' => array(
12458
+ 'value_casei' => array(
12459
+ 'text/css',
12460
+ ),
12461
  ),
12462
  ),
12463
  'cdata' => array(
12465
  'error_message' => 'CSS !important',
12466
  'regex' => '!important',
12467
  ),
12468
+ 'css_spec' => array(
12469
+ 'allowed_at_rules' => array(
12470
+ 'font-face',
12471
+ 'keyframes',
12472
+ 'media',
12473
+ 'page',
12474
+ 'supports',
12475
+ ),
12476
+ 'declaration' => array(),
12477
+ 'font_url_spec' => array(
12478
+ 'allow_empty' => true,
12479
+ 'protocol' => array(
12480
+ 'https',
12481
+ 'http',
12482
+ 'data',
12483
+ ),
12484
+ ),
12485
+ 'image_url_spec' => array(
12486
+ 'allow_empty' => true,
12487
+ 'protocol' => array(
12488
+ 'https',
12489
+ 'http',
12490
+ 'data',
12491
+ 'absolute',
12492
+ ),
12493
+ ),
12494
+ 'validate_keyframes' => false,
12495
+ ),
12496
  'max_bytes' => 50000,
12497
  'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#maximum-size',
12498
  ),
12503
  'unique' => true,
12504
  ),
12505
  ),
12506
+ array(
12507
+ 'attr_spec_list' => array(
12508
+ 'nonce' => array(),
12509
+ ),
12510
+ 'cdata' => array(
12511
+ 'cdata_regex' => 'body ?{opacity: ?0}',
12512
+ ),
12513
+ 'tag_spec' => array(
12514
+ 'mandatory_alternatives' => 'head > style[amp-boilerplate]',
12515
+ 'mandatory_parent' => 'head',
12516
+ 'spec_name' => 'head > style[amp-boilerplate] - old variant',
12517
+ 'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
12518
+ 'unique' => true,
12519
+ ),
12520
+ ),
12521
  array(
12522
  'attr_spec_list' => array(
12523
  'amp-boilerplate' => array(
12524
  'dispatch_key' => 3,
12525
  'mandatory' => true,
12526
+ 'value' => array(
12527
+ '',
12528
+ ),
12529
  ),
12530
  'nonce' => array(),
12531
  ),
12532
  'cdata' => array(
12533
+ 'cdata_regex' => '\\s*body\\s*{\\s*-webkit-animation:\\s*-amp-start\\s+8s\\s+steps\\(1,\\s*end\\)\\s+0s\\s+1\\s+normal\\s+both;\\s*-moz-animation:\\s*-amp-start\\s+8s\\s+steps\\s*\\(1\\s*,\\s*end\\s*\\)\\s+0s\\s+1\\s+normal\\s+both;\\s*-ms-animation:\\s*-amp-start\\s+8s\\s+steps\\s*\\(1\\s*,\\s*end\\s*\\)\\s+0s\\s+1\\s+normal\\s+both;\\s*animation:\\s*-amp-start\\s+8s\\s+steps\\(1,\\s*end\\)\\s+0s\\s+1\\s+normal\\s+both;?\\s*}\\s*@-webkit-keyframes\\s+-amp-start\\s*{\\s*from\\s*{\\s*visibility:\\s*hidden;?\\s*}\\s*to\\s*{\\s*visibility:\\s*visible;?\\s*}\\s*}\\s*@-moz-keyframes\\s+-amp-start\\s*{\\s*from\\s*{\\s*visibility:\\s*hidden;?\\s*}\\s*to\\s*{\\s*visibility:\\s*visible;?\\s*}\\s*}\\s*@-ms-keyframes\\s+-amp-start\\s*{\\s*from\\s*{\\s*visibility:\\s*hidden;?\\s*}\\s*to\\s*{\\s*visibility:\\s*visible;?\\s*}\\s*}\\s*@-o-keyframes\\s+-amp-start\\s*{\\s*from\\s*{\\s*visibility:\\s*hidden;?\\s*}\\s*to\\s*{\\s*visibility:\\s*visible;?\\s*}\\s*}\\s*@keyframes\\s+-amp-start\\s*{\\s*from\\s*{\\s*visibility:\\s*hidden;?\\s*}\\s*to\\s*{\\s*visibility:\\s*visible;?\\s*}\\s*}\\s*',
12534
  ),
12535
  'tag_spec' => array(
12536
  'mandatory_alternatives' => 'head > style[amp-boilerplate]',
12545
  'amp-boilerplate' => array(
12546
  'dispatch_key' => 3,
12547
  'mandatory' => true,
12548
+ 'value' => array(
12549
+ '',
12550
+ ),
12551
  ),
12552
  'nonce' => array(),
12553
  ),
12554
  'cdata' => array(
12555
+ 'cdata_regex' => '\\s*body\\s*{\\s*-webkit-animation:\\s*none;\\s*-moz-animation:\\s*none;\\s*-ms-animation:\\s*none;\\s*animation:\\s*none;?\\s*}\\s*',
12556
  ),
12557
  'tag_spec' => array(
12558
  'mandatory_alternatives' => 'noscript > style[amp-boilerplate]',
12568
  'amp-keyframes' => array(
12569
  'dispatch_key' => 1,
12570
  'mandatory' => true,
12571
+ 'value' => array(
12572
+ '',
12573
+ ),
12574
  ),
12575
  ),
12576
  'cdata' => array(
12577
+ 'css_spec' => array(
12578
+ 'allowed_at_rules' => array(
12579
+ 'keyframes',
12580
+ 'media',
12581
+ 'supports',
12582
+ ),
12583
+ 'declaration' => array(
12584
+ 'animation-timing-function',
12585
+ 'offset-distance',
12586
+ 'opacity',
12587
+ 'transform',
12588
+ 'visibility',
12589
+ ),
12590
+ 'font_url_spec' => array(),
12591
+ 'image_url_spec' => array(),
12592
+ 'validate_keyframes' => true,
12593
+ ),
12594
  'max_bytes' => 500000,
12595
  'max_bytes_spec_url' => 'https://www.ampproject.org/docs/reference/spec#keyframes-stylesheet',
12596
  ),
12640
  'filter' => array(),
12641
  'flood-color' => array(),
12642
  'flood-opacity' => array(),
12643
+ 'focusable' => array(),
12644
  'font-family' => array(),
12645
  'font-size' => array(),
12646
  'font-size-adjust' => array(),
12676
  'stroke-miterlimit' => array(),
12677
  'stroke-opacity' => array(),
12678
  'stroke-width' => array(),
12679
+ 'style' => array(
12680
+ 'blacklisted_value_regex' => '!important',
12681
+ ),
12682
  'systemlanguage' => array(),
12683
  'text-anchor' => array(),
12684
  'text-decoration' => array(),
12686
  'unicode-bidi' => array(),
12687
  'vector-effect' => array(),
12688
  'version' => array(
12689
+ 'value' => array(
12690
+ '1.0',
12691
+ '1.1',
12692
+ ),
12693
  ),
12694
  'viewbox' => array(),
12695
  'visibility' => array(),
12733
  'filter' => array(),
12734
  'flood-color' => array(),
12735
  'flood-opacity' => array(),
12736
+ 'focusable' => array(),
12737
  'font-family' => array(),
12738
  'font-size' => array(),
12739
  'font-size-adjust' => array(),
12815
  'filter' => array(),
12816
  'flood-color' => array(),
12817
  'flood-opacity' => array(),
12818
+ 'focusable' => array(),
12819
  'font-family' => array(),
12820
  'font-size' => array(),
12821
  'font-size-adjust' => array(),
12877
  'align' => array(),
12878
  'bgcolor' => array(),
12879
  'border' => array(
12880
+ 'value' => array(
12881
+ '0',
12882
+ '1',
12883
+ ),
12884
  ),
12885
  'cellpadding' => array(),
12886
  'cellspacing' => array(),
12912
  ),
12913
  ),
12914
  'template' => array(
12915
+ array(
12916
+ 'attr_spec_list' => array(
12917
+ 'date-template' => array(
12918
+ 'dispatch_key' => 1,
12919
+ 'mandatory' => true,
12920
+ 'value' => array(
12921
+ '',
12922
+ ),
12923
+ ),
12924
+ 'dates' => array(),
12925
+ 'default' => array(),
12926
+ 'type' => array(
12927
+ 'mandatory' => true,
12928
+ 'value' => array(
12929
+ 'amp-mustache',
12930
+ ),
12931
+ ),
12932
+ ),
12933
+ 'tag_spec' => array(
12934
+ 'mandatory_parent' => 'amp-date-picker',
12935
+ 'requires_extension' => array(
12936
+ 'amp-mustache',
12937
+ ),
12938
+ 'spec_name' => 'amp-date-picker > template [date-template]',
12939
+ ),
12940
+ ),
12941
+ array(
12942
+ 'attr_spec_list' => array(
12943
+ 'info-template' => array(
12944
+ 'dispatch_key' => 1,
12945
+ 'mandatory' => true,
12946
+ ),
12947
+ 'type' => array(
12948
+ 'mandatory' => true,
12949
+ 'value' => array(
12950
+ 'amp-mustache',
12951
+ ),
12952
+ ),
12953
+ ),
12954
+ 'tag_spec' => array(
12955
+ 'mandatory_parent' => 'amp-date-picker',
12956
+ 'requires_extension' => array(
12957
+ 'amp-mustache',
12958
+ ),
12959
+ 'spec_name' => 'amp-date-picker > template [info-template]',
12960
+ ),
12961
+ ),
12962
  array(
12963
  'attr_spec_list' => array(
12964
  'type' => array(
12965
  'mandatory' => true,
12966
+ 'value' => array(
12967
+ 'amp-mustache',
12968
+ ),
12969
  ),
12970
  ),
12971
  'tag_spec' => array(
12972
  'disallowed_ancestor' => array(
12973
  'template',
12974
+ 'amp-date-picker',
12975
  'amp-story-auto-ads',
12976
+ 'form div [submit-success][template]',
12977
+ 'form div [submit-error][template]',
12978
+ 'form div [submitting][template]',
12979
+ 'form div [verify-error][template]',
12980
  ),
12981
  'requires_extension' => array(
12982
  'amp-mustache',
12986
  array(
12987
  'attr_spec_list' => array(
12988
  'type' => array(
12989
+ 'dispatch_key' => 3,
12990
  'mandatory' => true,
12991
+ 'value' => array(
12992
+ 'amp-mustache',
12993
+ ),
12994
  ),
12995
  ),
12996
  'tag_spec' => array(
12997
  'mandatory_parent' => 'amp-story-auto-ads',
12998
+ 'reference_points' => array(
12999
+ 'AMP-STORY-GRID-LAYER animate-in' => array(
13000
+ 'mandatory' => false,
13001
+ 'unique' => false,
13002
+ ),
13003
+ 'AMP-STORY-GRID-LAYER default' => array(
13004
+ 'mandatory' => false,
13005
+ 'unique' => false,
13006
+ ),
13007
+ ),
13008
  'requires_extension' => array(
13009
  'amp-mustache',
13010
  ),
13039
  'filter' => array(),
13040
  'flood-color' => array(),
13041
  'flood-opacity' => array(),
13042
+ 'focusable' => array(),
13043
  'font-family' => array(),
13044
  'font-size' => array(),
13045
  'font-size-adjust' => array(),
13129
  'name' => array(
13130
  'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|baseURI|checkValidity|childElementCount|childNodes|classList|className|clientHeight|clientLeft|clientTop|clientWidth|compareDocumentPosition|computedName|computedRole|contentEditable|createShadowRoot|enqueAction|firstChild|firstElementChild|getAnimations|getAttribute|getAttributeNS|getAttributeNode|getAttributeNodeNS|getBoundingClientRect|getClientRects|getDestinationInsertionPoints|getElementsByClassName|getElementsByTagName|getElementsByTagNameNS|getRootNode|hasAttribute|hasAttributeNS|hasAttributes|hasChildNodes|hasPointerCapture|innerHTML|innerText|inputMode|insertAdjacentElement|insertAdjacentHTML|insertAdjacentText|isContentEditable|isDefaultNamespace|isEqualNode|isSameNode|lastChild|lastElementChild|lookupNamespaceURI|namespaceURI|nextElementSibling|nextSibling|nodeName|nodeType|nodeValue|offsetHeight|offsetLeft|offsetParent|offsetTop|offsetWidth|outerHTML|outerText|ownerDocument|parentElement|parentNode|previousElementSibling|previousSibling|querySelector|querySelectorAll|releasePointerCapture|removeAttribute|removeAttributeNS|removeAttributeNode|removeChild|removeEventListener|replaceChild|reportValidity|requestPointerLock|scrollHeight|scrollIntoView|scrollIntoViewIfNeeded|scrollLeft|scrollWidth|setAttribute|setAttributeNS|setAttributeNode|setAttributeNodeNS|setPointerCapture|shadowRoot|styleMap|tabIndex|tagName|textContent|toString|valueOf|(webkit|ms|moz|o)dropzone|(webkit|moz|ms|o)MatchesSelector|(webkit|moz|ms|o)RequestFullScreen|(webkit|moz|ms|o)RequestFullscreen)(\\s|$)',
13131
  ),
13132
+ 'no-verify' => array(
13133
+ 'value' => array(
13134
+ '',
13135
+ ),
13136
+ ),
13137
  'placeholder' => array(),
13138
  'readonly' => array(),
13139
  'required' => array(),
13174
  'filter' => array(),
13175
  'flood-color' => array(),
13176
  'flood-opacity' => array(),
13177
+ 'focusable' => array(),
13178
  'font-family' => array(),
13179
  'font-size' => array(),
13180
  'font-size-adjust' => array(),
13231
  ),
13232
  'value_url' => array(
13233
  'allow_empty' => false,
13234
+ 'protocol' => array(
 
13235
  'http',
13236
  'https',
13237
  ),
13292
  ),
13293
  'title' => array(
13294
  array(
13295
+ 'attr_spec_list' => array(
13296
+ '[text]' => array(),
13297
+ ),
13298
  'tag_spec' => array(
13299
  'spec_name' => 'title',
13300
  ),
13331
  array(
13332
  'attr_spec_list' => array(
13333
  'default' => array(
13334
+ 'value' => array(
13335
+ '',
13336
+ ),
13337
  ),
13338
  'kind' => array(
13339
+ 'value' => array(
13340
+ 'captions',
13341
+ 'chapters',
13342
+ 'descriptions',
13343
+ 'metadata',
13344
+ ),
13345
  ),
13346
  'label' => array(),
13347
  'src' => array(
13349
  'mandatory' => true,
13350
  'value_url' => array(
13351
  'allow_relative' => false,
13352
+ 'protocol' => array(
13353
  'https',
13354
  ),
13355
  ),
13364
  array(
13365
  'attr_spec_list' => array(
13366
  'default' => array(
13367
+ 'value' => array(
13368
+ '',
13369
+ ),
13370
  ),
13371
  'kind' => array(
13372
  'mandatory' => true,
13373
+ 'value_casei' => array(
13374
+ 'subtitles',
13375
+ ),
13376
  ),
13377
  'label' => array(),
13378
  'src' => array(
13380
  'mandatory' => true,
13381
  'value_url' => array(
13382
  'allow_relative' => false,
13383
+ 'protocol' => array(
13384
  'https',
13385
  ),
13386
  ),
13397
  array(
13398
  'attr_spec_list' => array(
13399
  'default' => array(
13400
+ 'value' => array(
13401
+ '',
13402
+ ),
13403
  ),
13404
  'kind' => array(
13405
+ 'value' => array(
13406
+ 'captions',
13407
+ 'chapters',
13408
+ 'descriptions',
13409
+ 'metadata',
13410
+ ),
13411
  ),
13412
  'label' => array(),
13413
  'src' => array(
13415
  'mandatory' => true,
13416
  'value_url' => array(
13417
  'allow_relative' => false,
13418
+ 'protocol' => array(
13419
  'https',
13420
  ),
13421
  ),
13430
  array(
13431
  'attr_spec_list' => array(
13432
  'default' => array(
13433
+ 'value' => array(
13434
+ '',
13435
+ ),
13436
  ),
13437
  'kind' => array(
13438
  'mandatory' => true,
13439
+ 'value_casei' => array(
13440
+ 'subtitles',
13441
+ ),
13442
  ),
13443
  'label' => array(),
13444
  'src' => array(
13446
  'mandatory' => true,
13447
  'value_url' => array(
13448
  'allow_relative' => false,
13449
+ 'protocol' => array(
13450
  'https',
13451
  ),
13452
  ),
13466
  '[src]' => array(),
13467
  '[srclang]' => array(),
13468
  'default' => array(
13469
+ 'value' => array(
13470
+ '',
13471
+ ),
13472
  ),
13473
  'kind' => array(
13474
+ 'value' => array(
13475
+ 'captions',
13476
+ 'chapters',
13477
+ 'descriptions',
13478
+ 'metadata',
13479
+ ),
13480
  ),
13481
  'label' => array(),
13482
  'src' => array(
13484
  'mandatory' => true,
13485
  'value_url' => array(
13486
  'allow_relative' => false,
13487
+ 'protocol' => array(
13488
  'https',
13489
  ),
13490
  ),
13502
  '[src]' => array(),
13503
  '[srclang]' => array(),
13504
  'default' => array(
13505
+ 'value' => array(
13506
+ '',
13507
+ ),
13508
  ),
13509
  'kind' => array(
13510
  'mandatory' => true,
13511
+ 'value_casei' => array(
13512
+ 'subtitles',
13513
+ ),
13514
  ),
13515
  'label' => array(),
13516
  'src' => array(
13518
  'mandatory' => true,
13519
  'value_url' => array(
13520
  'allow_relative' => false,
13521
+ 'protocol' => array(
13522
  'https',
13523
  ),
13524
  ),
13538
  '[src]' => array(),
13539
  '[srclang]' => array(),
13540
  'default' => array(
13541
+ 'value' => array(
13542
+ '',
13543
+ ),
13544
  ),
13545
  'kind' => array(
13546
+ 'value' => array(
13547
+ 'captions',
13548
+ 'chapters',
13549
+ 'descriptions',
13550
+ 'metadata',
13551
+ ),
13552
  ),
13553
  'label' => array(),
13554
  'src' => array(
13556
  'mandatory' => true,
13557
  'value_url' => array(
13558
  'allow_relative' => false,
13559
+ 'protocol' => array(
13560
  'https',
13561
  ),
13562
  ),
13574
  '[src]' => array(),
13575
  '[srclang]' => array(),
13576
  'default' => array(
13577
+ 'value' => array(
13578
+ '',
13579
+ ),
13580
  ),
13581
  'kind' => array(
13582
  'mandatory' => true,
13583
+ 'value_casei' => array(
13584
+ 'subtitles',
13585
+ ),
13586
  ),
13587
  'label' => array(),
13588
  'src' => array(
13590
  'mandatory' => true,
13591
  'value_url' => array(
13592
  'allow_relative' => false,
13593
+ 'protocol' => array(
13594
  'https',
13595
  ),
13596
  ),
13610
  '[src]' => array(),
13611
  '[srclang]' => array(),
13612
  'default' => array(
13613
+ 'value' => array(
13614
+ '',
13615
+ ),
13616
  ),
13617
  'kind' => array(
13618
  'mandatory' => true,
13619
+ 'value_casei' => array(
13620
+ 'subtitles',
13621
+ ),
13622
  ),
13623
  'label' => array(),
13624
  'src' => array(
13626
  'mandatory' => true,
13627
  'value_url' => array(
13628
  'allow_relative' => false,
13629
+ 'protocol' => array(
13630
  'https',
13631
  ),
13632
  ),
13667
  'filter' => array(),
13668
  'flood-color' => array(),
13669
  'flood-opacity' => array(),
13670
+ 'focusable' => array(),
13671
  'font-family' => array(),
13672
  'font-size' => array(),
13673
  'font-size-adjust' => array(),
13721
  ),
13722
  'value_url' => array(
13723
  'allow_empty' => false,
13724
+ 'protocol' => array(
 
13725
  'http',
13726
  'https',
13727
  ),
13769
  'filter' => array(),
13770
  'flood-color' => array(),
13771
  'flood-opacity' => array(),
13772
+ 'focusable' => array(),
13773
  'font-family' => array(),
13774
  'font-size' => array(),
13775
  'font-size-adjust' => array(),
13874
  'filter' => array(),
13875
  'flood-color' => array(),
13876
  'flood-opacity' => array(),
13877
+ 'focusable' => array(),
13878
  'font-family' => array(),
13879
  'font-size' => array(),
13880
  'font-size-adjust' => array(),
13932
  ),
13933
  'value_url' => array(
13934
  'allow_empty' => false,
13935
+ 'protocol' => array(
 
13936
  'http',
13937
  'https',
13938
  ),
13975
  'blacklisted_value_regex' => '__amp_source_origin',
13976
  'value_url' => array(
13977
  'allow_relative' => false,
13978
+ 'protocol' => array(
13979
  'data',
13980
  'https',
13981
  ),
14040
  'tag_spec' => array(),
14041
  ),
14042
  ),
 
 
 
 
 
 
14043
  );
14044
 
14045
  private static $layout_allowed_attrs = array(
14106
  'amp-access-style' => array(),
14107
  'amp-access-template' => array(),
14108
  'amp-fx' => array(
14109
+ 'value_regex_casei' => '(fade-in|fade-in-scroll|fly-in-bottom|fly-in-left|fly-in-right|fly-in-top|parallax)(\\s|fade-in|fade-in-scroll|fly-in-bottom|fly-in-left|fly-in-right|fly-in-top|parallax)*',
14110
  ),
14111
  'aria-activedescendant' => array(),
14112
  'aria-atomic' => array(),
14152
  'dir' => array(),
14153
  'draggable' => array(),
14154
  'fallback' => array(
14155
+ 'value' => array(
14156
+ '',
14157
+ ),
14158
  ),
14159
  'hidden' => array(
14160
+ 'value' => array(
14161
+ '',
14162
+ ),
14163
  ),
14164
  'i-amp-access-id' => array(),
14165
  'id' => array(
14172
  'itemscope' => array(),
14173
  'itemtype' => array(),
14174
  'lang' => array(),
 
14175
  'on' => array(),
14176
  'overflow' => array(),
14177
  'placeholder' => array(
14178
+ 'value' => array(
14179
+ '',
14180
+ ),
14181
  ),
14182
  'prefix' => array(),
14183
  'property' => array(),
14187
  'resource' => array(),
14188
  'rev' => array(),
14189
  'role' => array(),
14190
+ 'style' => array(
14191
+ 'blacklisted_value_regex' => '(!important|<!--)',
14192
+ ),
14193
  'subscriptions-action' => array(),
14194
  'subscriptions-actions' => array(
14195
+ 'value' => array(
14196
+ '',
14197
+ ),
14198
  ),
14199
+ 'subscriptions-decorate' => array(),
14200
  'subscriptions-dialog' => array(
14201
+ 'value' => array(
14202
+ '',
14203
+ ),
14204
  ),
14205
  'subscriptions-display' => array(),
14206
  'subscriptions-section' => array(
14207
+ 'value_casei' => array(
14208
+ 'actions',
14209
+ 'content',
14210
+ 'content-not-granted',
14211
+ 'loading',
14212
+ ),
14213
  ),
14214
  'subscriptions-service' => array(),
14215
  'tabindex' => array(),
14218
  'typeof' => array(),
14219
  'validation-for' => array(),
14220
  'visible-when-invalid' => array(
14221
+ 'value' => array(
14222
+ 'badInput',
14223
+ 'customError',
14224
+ 'patternMismatch',
14225
+ 'rangeOverflow',
14226
+ 'rangeUnderflow',
14227
+ 'stepMismatch',
14228
+ 'tooLong',
14229
+ 'typeMismatch',
14230
+ 'valueMissing',
14231
+ ),
14232
  ),
14233
  'vocab' => array(),
14234
  );
14235
 
14236
 
14237
+ private static $reference_points = array(
14238
+ 'AMP-CAROUSEL lightbox [child]' => array(
14239
+ 'attr_spec_list' => array(
14240
+ 'lightbox-thumbnail-id' => array(
14241
+ 'value_regex_casei' => '^[a-z][a-z\\d_-]*',
14242
+ ),
14243
+ ),
14244
+ 'tag_spec' => array(
14245
+ 'spec_name' => 'AMP-CAROUSEL lightbox [child]',
14246
+ ),
14247
+ ),
14248
+ 'AMP-CAROUSEL lightbox [lightbox-exclude]' => array(
14249
+ 'attr_spec_list' => array(
14250
+ 'lightbox-exclude' => array(
14251
+ 'mandatory' => true,
14252
+ ),
14253
+ ),
14254
+ 'tag_spec' => array(
14255
+ 'spec_name' => 'AMP-CAROUSEL lightbox [lightbox-exclude]',
14256
+ ),
14257
+ ),
14258
+ 'AMP-LIVE-LIST [items]' => array(
14259
+ 'attr_spec_list' => array(
14260
+ 'items' => array(
14261
+ 'mandatory' => true,
14262
+ ),
14263
+ ),
14264
+ 'tag_spec' => array(
14265
+ 'reference_points' => array(
14266
+ 'AMP-LIVE-LIST [items] item' => array(
14267
+ 'mandatory' => false,
14268
+ 'unique' => false,
14269
+ ),
14270
+ ),
14271
+ 'spec_name' => 'AMP-LIVE-LIST [items]',
14272
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-live-list#items',
14273
+ ),
14274
+ ),
14275
+ 'AMP-LIVE-LIST [items] item' => array(
14276
+ 'attr_spec_list' => array(
14277
+ 'data-sort-time' => array(
14278
+ 'mandatory' => true,
14279
+ ),
14280
+ 'data-tombstone' => array(),
14281
+ 'data-update-time' => array(),
14282
+ 'id' => array(
14283
+ 'mandatory' => true,
14284
+ ),
14285
+ ),
14286
+ 'tag_spec' => array(
14287
+ 'spec_name' => 'AMP-LIVE-LIST [items] item',
14288
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-live-list#items',
14289
+ ),
14290
+ ),
14291
+ 'AMP-LIVE-LIST [pagination]' => array(
14292
+ 'attr_spec_list' => array(
14293
+ 'pagination' => array(
14294
+ 'mandatory' => true,
14295
+ ),
14296
+ ),
14297
+ 'tag_spec' => array(
14298
+ 'spec_name' => 'AMP-LIVE-LIST [pagination]',
14299
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-live-list#pagination',
14300
+ ),
14301
+ ),
14302
+ 'AMP-LIVE-LIST [update]' => array(
14303
+ 'attr_spec_list' => array(
14304
+ 'update' => array(
14305
+ 'mandatory' => true,
14306
+ ),
14307
+ ),
14308
+ 'tag_spec' => array(
14309
+ 'spec_name' => 'AMP-LIVE-LIST [update]',
14310
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-live-list#update',
14311
+ ),
14312
+ ),
14313
+ 'AMP-NEXT-PAGE > [separator]' => array(
14314
+ 'attr_spec_list' => array(
14315
+ 'separator' => array(
14316
+ 'mandatory' => true,
14317
+ ),
14318
+ ),
14319
+ 'tag_spec' => array(
14320
+ 'mandatory_parent' => 'amp-next-page',
14321
+ 'spec_name' => 'AMP-NEXT-PAGE > [separator]',
14322
+ ),
14323
+ ),
14324
+ 'AMP-SELECTOR child' => array(
14325
+ 'attr_spec_list' => array(),
14326
+ 'tag_spec' => array(
14327
+ 'reference_points' => array(
14328
+ 'AMP-SELECTOR child' => array(
14329
+ 'mandatory' => false,
14330
+ 'unique' => false,
14331
+ ),
14332
+ 'AMP-SELECTOR option' => array(
14333
+ 'mandatory' => false,
14334
+ 'unique' => false,
14335
+ ),
14336
+ ),
14337
+ 'spec_name' => 'AMP-SELECTOR child',
14338
+ ),
14339
+ ),
14340
+ 'AMP-SELECTOR option' => array(
14341
+ 'attr_spec_list' => array(
14342
+ 'disabled' => array(
14343
+ 'value' => array(
14344
+ '',
14345
+ ),
14346
+ ),
14347
+ 'option' => array(
14348
+ 'mandatory' => true,
14349
+ ),
14350
+ 'selected' => array(
14351
+ 'value' => array(
14352
+ '',
14353
+ ),
14354
+ ),
14355
+ ),
14356
+ 'tag_spec' => array(
14357
+ 'spec_name' => 'AMP-SELECTOR option',
14358
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-selector',
14359
+ ),
14360
+ ),
14361
+ 'AMP-STORY-CTA-LAYER animate-in' => array(
14362
+ 'attr_spec_list' => array(
14363
+ 'animate-in' => array(
14364
+ 'value' => array(
14365
+ 'drop',
14366
+ 'fade-in',
14367
+ 'fly-in-bottom',
14368
+ 'fly-in-left',
14369
+ 'fly-in-right',
14370
+ 'fly-in-top',
14371
+ 'pan-down',
14372
+ 'pan-left',
14373
+ 'pan-right',
14374
+ 'pan-up',
14375
+ 'pulse',
14376
+ 'rotate-in-left',
14377
+ 'rotate-in-right',
14378
+ 'twirl-in',
14379
+ 'whoosh-in-left',
14380
+ 'whoosh-in-right',
14381
+ 'zoom-in',
14382
+ 'zoom-out',
14383
+ ),
14384
+ ),
14385
+ 'animate-in-after' => array(),
14386
+ 'animate-in-delay' => array(),
14387
+ 'animate-in-duration' => array(),
14388
+ ),
14389
+ 'tag_spec' => array(
14390
+ 'reference_points' => array(
14391
+ 'AMP-STORY-CTA-LAYER animate-in' => array(
14392
+ 'mandatory' => false,
14393
+ 'unique' => false,
14394
+ ),
14395
+ ),
14396
+ 'spec_name' => 'AMP-STORY-CTA-LAYER animate-in',
14397
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-story',
14398
+ ),
14399
+ ),
14400
+ 'AMP-STORY-GRID-LAYER animate-in' => array(
14401
+ 'attr_spec_list' => array(
14402
+ 'animate-in' => array(
14403
+ 'value' => array(
14404
+ 'drop',
14405
+ 'fade-in',
14406
+ 'fly-in-bottom',
14407
+ 'fly-in-left',
14408
+ 'fly-in-right',
14409
+ 'fly-in-top',
14410
+ 'pan-down',
14411
+ 'pan-left',
14412
+ 'pan-right',
14413
+ 'pan-up',
14414
+ 'pulse',
14415
+ 'rotate-in-left',
14416
+ 'rotate-in-right',
14417
+ 'twirl-in',
14418
+ 'whoosh-in-left',
14419
+ 'whoosh-in-right',
14420
+ 'zoom-in',
14421
+ 'zoom-out',
14422
+ ),
14423
+ ),
14424
+ 'animate-in-after' => array(),
14425
+ 'animate-in-delay' => array(),
14426
+ 'animate-in-duration' => array(),
14427
+ 'data-tooltip-icon' => array(
14428
+ 'value_url' => array(
14429
+ 'protocol' => array(
14430
+ 'http',
14431
+ 'https',
14432
+ 'data',
14433
+ ),
14434
+ ),
14435
+ ),
14436
+ 'target' => array(
14437
+ 'value' => array(
14438
+ '_blank',
14439
+ ),
14440
+ ),
14441
+ ),
14442
+ 'tag_spec' => array(
14443
+ 'reference_points' => array(
14444
+ 'AMP-STORY-GRID-LAYER animate-in' => array(
14445
+ 'mandatory' => false,
14446
+ 'unique' => false,
14447
+ ),
14448
+ ),
14449
+ 'spec_name' => 'AMP-STORY-GRID-LAYER animate-in',
14450
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-story',
14451
+ ),
14452
+ ),
14453
+ 'AMP-STORY-GRID-LAYER default' => array(
14454
+ 'attr_spec_list' => array(
14455
+ 'align-content' => array(
14456
+ 'value' => array(
14457
+ 'center',
14458
+ 'end',
14459
+ 'space-around',
14460
+ 'space-between',
14461
+ 'space-evenly',
14462
+ 'start',
14463
+ 'stretch',
14464
+ ),
14465
+ ),
14466
+ 'align-items' => array(
14467
+ 'value' => array(
14468
+ 'center',
14469
+ 'end',
14470
+ 'start',
14471
+ 'stretch',
14472
+ ),
14473
+ ),
14474
+ 'align-self' => array(
14475
+ 'value' => array(
14476
+ 'center',
14477
+ 'end',
14478
+ 'start',
14479
+ 'stretch',
14480
+ ),
14481
+ ),
14482
+ 'animate-in' => array(
14483
+ 'value' => array(
14484
+ 'drop',
14485
+ 'fade-in',
14486
+ 'fly-in-bottom',
14487
+ 'fly-in-left',
14488
+ 'fly-in-right',
14489
+ 'fly-in-top',
14490
+ 'pan-down',
14491
+ 'pan-left',
14492
+ 'pan-right',
14493
+ 'pan-up',
14494
+ 'pulse',
14495
+ 'rotate-in-left',
14496
+ 'rotate-in-right',
14497
+ 'twirl-in',
14498
+ 'whoosh-in-left',
14499
+ 'whoosh-in-right',
14500
+ 'zoom-in',
14501
+ 'zoom-out',
14502
+ ),
14503
+ ),
14504
+ 'animate-in-after' => array(),
14505
+ 'animate-in-delay' => array(),
14506
+ 'animate-in-duration' => array(),
14507
+ 'data-tooltip-icon' => array(
14508
+ 'value_url' => array(
14509
+ 'protocol' => array(
14510
+ 'http',
14511
+ 'https',
14512
+ 'data',
14513
+ ),
14514
+ ),
14515
+ ),
14516
+ 'grid-area' => array(),
14517
+ 'justify-content' => array(
14518
+ 'value' => array(
14519
+ 'center',
14520
+ 'end',
14521
+ 'space-around',
14522
+ 'space-between',
14523
+ 'space-evenly',
14524
+ 'start',
14525
+ 'stretch',
14526
+ ),
14527
+ ),
14528
+ 'justify-items' => array(
14529
+ 'value' => array(
14530
+ 'center',
14531
+ 'end',
14532
+ 'start',
14533
+ 'stretch',
14534
+ ),
14535
+ ),
14536
+ 'justify-self' => array(
14537
+ 'value' => array(
14538
+ 'center',
14539
+ 'end',
14540
+ 'start',
14541
+ 'stretch',
14542
+ ),
14543
+ ),
14544
+ 'target' => array(
14545
+ 'value' => array(
14546
+ '_blank',
14547
+ ),
14548
+ ),
14549
+ ),
14550
+ 'tag_spec' => array(
14551
+ 'reference_points' => array(
14552
+ 'AMP-STORY-GRID-LAYER animate-in' => array(
14553
+ 'mandatory' => false,
14554
+ 'unique' => false,
14555
+ ),
14556
+ ),
14557
+ 'spec_name' => 'AMP-STORY-GRID-LAYER default',
14558
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-story',
14559
+ ),
14560
+ ),
14561
+ );
14562
+
14563
+
14564
  /**
14565
  * Get allowed tags.
14566
  *
14587
  return null;
14588
  }
14589
 
14590
+ /**
14591
+ * Get reference point spec.
14592
+ *
14593
+ * @since 1.0
14594
+ * @param string $tag_spec_name Tag spec name.
14595
+ * @return array|null Reference point spec, or null if does not exist.
14596
+ */
14597
+ public static function get_reference_point_spec( $tag_spec_name ) {
14598
+ if ( isset( self::$reference_points[ $tag_spec_name ] ) ) {
14599
+ return self::$reference_points[ $tag_spec_name ];
14600
+ }
14601
+ return null;
14602
+ }
14603
+
14604
  /**
14605
  * Get list of globally-allowed attributes.
14606
  *
includes/sanitizers/class-amp-audio-sanitizer.php CHANGED
@@ -20,6 +20,17 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
20
  */
21
  public static $tag = 'audio';
22
 
 
 
 
 
 
 
 
 
 
 
 
23
  /**
24
  * Sanitize the <audio> elements from the HTML contained in this instance's DOMDocument.
25
  *
@@ -34,7 +45,9 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
34
 
35
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
36
  $node = $nodes->item( $i );
 
37
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
 
38
 
39
  $new_attributes = $this->filter_attributes( $old_attributes );
40
 
@@ -83,6 +96,14 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
83
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
84
  $this->remove_invalid_child( $node );
85
  } else {
 
 
 
 
 
 
 
 
86
  $node->parentNode->replaceChild( $new_node, $node );
87
  }
88
 
@@ -133,6 +154,14 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
133
  }
134
  break;
135
 
 
 
 
 
 
 
 
 
136
  default:
137
  break;
138
  }
20
  */
21
  public static $tag = 'audio';
22
 
23
+ /**
24
+ * Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
25
+ *
26
+ * @return array Mapping.
27
+ */
28
+ public function get_selector_conversion_mapping() {
29
+ return array(
30
+ 'audio' => array( 'amp-audio' ),
31
+ );
32
+ }
33
+
34
  /**
35
  * Sanitize the <audio> elements from the HTML contained in this instance's DOMDocument.
36
  *
45
 
46
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
47
  $node = $nodes->item( $i );
48
+ $amp_data = $this->get_data_amp_attributes( $node );
49
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
50
+ $old_attributes = $this->filter_data_amp_attributes( $old_attributes, $amp_data );
51
 
52
  $new_attributes = $this->filter_attributes( $old_attributes );
53
 
96
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
97
  $this->remove_invalid_child( $node );
98
  } else {
99
+
100
+ $layout = isset( $new_attributes['layout'] ) ? $new_attributes['layout'] : false;
101
+
102
+ // The width has to be unset / auto in case of fixed-height.
103
+ if ( 'fixed-height' === $layout ) {
104
+ $new_node->setAttribute( 'width', 'auto' );
105
+ }
106
+
107
  $node->parentNode->replaceChild( $new_node, $node );
108
  }
109
 
154
  }
155
  break;
156
 
157
+ case 'data-amp-layout':
158
+ $out['layout'] = $value;
159
+ break;
160
+
161
+ case 'data-amp-noloading':
162
+ $out['noloading'] = $value;
163
+ break;
164
+
165
  default:
166
  break;
167
  }
includes/sanitizers/class-amp-base-sanitizer.php CHANGED
@@ -19,6 +19,15 @@ abstract class AMP_Base_Sanitizer {
19
  */
20
  const FALLBACK_HEIGHT = 400;
21
 
 
 
 
 
 
 
 
 
 
22
  /**
23
  * Placeholder for default args, to be set in child classes.
24
  *
@@ -54,8 +63,8 @@ abstract class AMP_Base_Sanitizer {
54
  * @type array $amp_bind_placeholder_prefix
55
  * @type bool $allow_dirty_styles
56
  * @type bool $allow_dirty_scripts
57
- * @type bool $disable_invalid_removal
58
- * @type callable $remove_invalid_callback
59
  * }
60
  */
61
  protected $args;
@@ -77,6 +86,13 @@ abstract class AMP_Base_Sanitizer {
77
  */
78
  protected $root_element;
79
 
 
 
 
 
 
 
 
80
  /**
81
  * AMP_Base_Sanitizer constructor.
82
  *
@@ -105,6 +121,41 @@ abstract class AMP_Base_Sanitizer {
105
  }
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  /**
109
  * Sanitize the HTML contained in the DOMDocument received by the constructor
110
  */
@@ -130,6 +181,7 @@ abstract class AMP_Base_Sanitizer {
130
  * Return array of values that would be valid as an HTML `style` attribute.
131
  *
132
  * @since 0.4
 
133
  *
134
  * @return array[][] Mapping of CSS selectors to arrays of properties.
135
  */
@@ -211,9 +263,12 @@ abstract class AMP_Base_Sanitizer {
211
  * @type string $class
212
  * @type string $layout
213
  * }
214
- * @return string[]
215
  */
216
  public function set_layout( $attributes ) {
 
 
 
217
  if ( empty( $attributes['height'] ) ) {
218
  unset( $attributes['width'] );
219
  $attributes['height'] = self::FALLBACK_HEIGHT;
@@ -262,7 +317,7 @@ abstract class AMP_Base_Sanitizer {
262
  * @return string URL which may have been updated with HTTPS, or may have been made empty.
263
  */
264
  public function maybe_enforce_https_src( $src, $force_https = false ) {
265
- $protocol = strtok( $src, ':' );
266
  if ( 'https' !== $protocol ) {
267
  // Check if https is required.
268
  if ( isset( $this->args['require_https_src'] ) && true === $this->args['require_https_src'] ) {
@@ -286,20 +341,24 @@ abstract class AMP_Base_Sanitizer {
286
  *
287
  * @since 0.7
288
  *
289
- * @param DOMNode|DOMElement $node The node to remove.
290
- * @param array $args Additional args to pass to validation error callback.
291
- *
292
- * @return void
293
  */
294
- public function remove_invalid_child( $node, $args = array() ) {
295
- if ( isset( $this->args['validation_error_callback'] ) ) {
296
- call_user_func( $this->args['validation_error_callback'],
297
- array_merge( compact( 'node' ), $args )
298
- );
299
  }
300
- if ( empty( $this->args['disable_invalid_removal'] ) ) {
 
 
301
  $node->parentNode->removeChild( $node );
 
 
302
  }
 
303
  }
304
 
305
  /**
@@ -312,33 +371,213 @@ abstract class AMP_Base_Sanitizer {
312
  *
313
  * @param DOMElement $element The node for which to remove the attribute.
314
  * @param DOMAttr|string $attribute The attribute to remove from the element.
315
- * @param array $args Additional args to pass to validation error callback.
316
- * @return void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  */
318
- public function remove_invalid_attribute( $element, $attribute, $args = array() ) {
319
- if ( isset( $this->args['validation_error_callback'] ) ) {
320
- if ( is_string( $attribute ) ) {
321
- $attribute = $element->getAttributeNode( $attribute );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
323
- if ( $attribute ) {
324
- call_user_func( $this->args['validation_error_callback'],
325
- array_merge(
326
- array(
327
- 'node' => $attribute,
328
- ),
329
- $args
330
- )
331
- );
332
- if ( empty( $this->args['disable_invalid_removal'] ) ) {
333
- $element->removeAttributeNode( $attribute );
 
 
 
 
334
  }
335
  }
336
- } elseif ( empty( $this->args['disable_invalid_removal'] ) ) {
337
- if ( is_string( $attribute ) ) {
338
- $element->removeAttribute( $attribute );
339
- } else {
340
- $element->removeAttributeNode( $attribute );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
 
 
 
 
 
 
 
343
  }
344
  }
19
  */
20
  const FALLBACK_HEIGHT = 400;
21
 
22
+ /**
23
+ * Value for <amp-image-lightbox> ID.
24
+ *
25
+ * @since 1.0
26
+ *
27
+ * @const string
28
+ */
29
+ const AMP_IMAGE_LIGHTBOX_ID = 'amp-image-lightbox';
30
+
31
  /**
32
  * Placeholder for default args, to be set in child classes.
33
  *
63
  * @type array $amp_bind_placeholder_prefix
64
  * @type bool $allow_dirty_styles
65
  * @type bool $allow_dirty_scripts
66
+ * @type bool $should_locate_sources
67
+ * @type callable $validation_error_callback
68
  * }
69
  */
70
  protected $args;
86
  */
87
  protected $root_element;
88
 
89
+ /**
90
+ * Keep track of nodes that should not be removed to prevent duplicated validation errors since sanitization is rejected.
91
+ *
92
+ * @var array
93
+ */
94
+ private $should_not_removed_nodes = array();
95
+
96
  /**
97
  * AMP_Base_Sanitizer constructor.
98
  *
121
  }
122
  }
123
 
124
+ /**
125
+ * Add filters to manipulate output during output buffering before the DOM is constructed.
126
+ *
127
+ * Add actions and filters before the page is rendered so that the sanitizer can fix issues during output buffering.
128
+ * This provides an alternative to manipulating the DOM in the sanitize method. This is a static function because
129
+ * it is invoked before the class is instantiated, as the DOM is not available yet. This method is only called
130
+ * when 'amp' theme support is present. It is conceptually similar to the AMP_Base_Embed_Handler class's register_embed
131
+ * method.
132
+ *
133
+ * @since 1.0
134
+ * @see \AMP_Base_Embed_Handler::register_embed()
135
+ *
136
+ * @param array $args Args.
137
+ */
138
+ public static function add_buffering_hooks( $args = array() ) {}
139
+
140
+ /**
141
+ * Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
142
+ *
143
+ * @return array Mapping.
144
+ */
145
+ public function get_selector_conversion_mapping() {
146
+ return array();
147
+ }
148
+
149
+ /**
150
+ * Run logic before any sanitizers are run.
151
+ *
152
+ * After the sanitizers are instantiated but before calling sanitize on each of them, this
153
+ * method is called with list of all the instantiated sanitizers.
154
+ *
155
+ * @param AMP_Base_Sanitizer[] $sanitizers Sanitizers.
156
+ */
157
+ public function init( $sanitizers ) {}
158
+
159
  /**
160
  * Sanitize the HTML contained in the DOMDocument received by the constructor
161
  */
181
  * Return array of values that would be valid as an HTML `style` attribute.
182
  *
183
  * @since 0.4
184
+ * @deprecated As of 1.0, use get_stylesheets().
185
  *
186
  * @return array[][] Mapping of CSS selectors to arrays of properties.
187
  */
263
  * @type string $class
264
  * @type string $layout
265
  * }
266
+ * @return array Attributes.
267
  */
268
  public function set_layout( $attributes ) {
269
+ if ( isset( $attributes['layout'] ) && ( 'fill' === $attributes['layout'] || 'flex-item' !== $attributes['layout'] ) ) {
270
+ return $attributes;
271
+ }
272
  if ( empty( $attributes['height'] ) ) {
273
  unset( $attributes['width'] );
274
  $attributes['height'] = self::FALLBACK_HEIGHT;
317
  * @return string URL which may have been updated with HTTPS, or may have been made empty.
318
  */
319
  public function maybe_enforce_https_src( $src, $force_https = false ) {
320
+ $protocol = strtok( $src, ':' ); // @todo What about relative URLs? This should use wp_parse_url( $src, PHP_URL_SCHEME )
321
  if ( 'https' !== $protocol ) {
322
  // Check if https is required.
323
  if ( isset( $this->args['require_https_src'] ) && true === $this->args['require_https_src'] ) {
341
  *
342
  * @since 0.7
343
  *
344
+ * @param DOMNode|DOMElement $node The node to remove.
345
+ * @param array $validation_error Validation error details.
346
+ * @return bool Whether the node should have been removed, that is, that the node was sanitized for validity.
 
347
  */
348
+ public function remove_invalid_child( $node, $validation_error = array() ) {
349
+
350
+ // Prevent double-reporting nodes that are rejected for sanitization.
351
+ if ( isset( $this->should_not_removed_nodes[ $node->nodeName ] ) && in_array( $node, $this->should_not_removed_nodes[ $node->nodeName ], true ) ) {
352
+ return false;
353
  }
354
+
355
+ $should_remove = $this->should_sanitize_validation_error( $validation_error, compact( 'node' ) );
356
+ if ( $should_remove ) {
357
  $node->parentNode->removeChild( $node );
358
+ } else {
359
+ $this->should_not_removed_nodes[ $node->nodeName ][] = $node;
360
  }
361
+ return $should_remove;
362
  }
363
 
364
  /**
371
  *
372
  * @param DOMElement $element The node for which to remove the attribute.
373
  * @param DOMAttr|string $attribute The attribute to remove from the element.
374
+ * @param array $validation_error Validation error details.
375
+ * @return bool Whether the node should have been removed, that is, that the node was sanitized for validity.
376
+ */
377
+ public function remove_invalid_attribute( $element, $attribute, $validation_error = array() ) {
378
+ if ( is_string( $attribute ) ) {
379
+ $node = $element->getAttributeNode( $attribute );
380
+ } else {
381
+ $node = $attribute;
382
+ }
383
+ $should_remove = $this->should_sanitize_validation_error( $validation_error, compact( 'node' ) );
384
+ if ( $should_remove ) {
385
+ $element->removeAttributeNode( $node );
386
+ }
387
+ return $should_remove;
388
+ }
389
+
390
+ /**
391
+ * Check whether or not sanitization should occur in response to validation error.
392
+ *
393
+ * @since 1.0
394
+ *
395
+ * @param array $validation_error Validation error.
396
+ * @param array $data Data including the node.
397
+ * @return bool Whether to sanitize.
398
  */
399
+ public function should_sanitize_validation_error( $validation_error, $data = array() ) {
400
+ if ( empty( $this->args['validation_error_callback'] ) || ! is_callable( $this->args['validation_error_callback'] ) ) {
401
+ return true;
402
+ }
403
+ $validation_error = $this->prepare_validation_error( $validation_error, $data );
404
+ return false !== call_user_func( $this->args['validation_error_callback'], $validation_error, $data );
405
+ }
406
+
407
+ /**
408
+ * Prepare validation error.
409
+ *
410
+ * @param array $error {
411
+ * Error.
412
+ *
413
+ * @type string $code Error code.
414
+ * }
415
+ * @param array $data {
416
+ * Data.
417
+ *
418
+ * @type DOMElement|DOMNode $node The removed node.
419
+ * }
420
+ * @return array Error.
421
+ */
422
+ public function prepare_validation_error( array $error = array(), array $data = array() ) {
423
+ $node = null;
424
+ $matches = null;
425
+
426
+ if ( isset( $data['node'] ) && $data['node'] instanceof DOMNode ) {
427
+ $node = $data['node'];
428
+
429
+ $error['node_name'] = $node->nodeName;
430
+ if ( $node->parentNode ) {
431
+ $error['parent_name'] = $node->parentNode->nodeName;
432
  }
433
+ }
434
+
435
+ if ( $node instanceof DOMElement ) {
436
+ if ( ! isset( $error['code'] ) ) {
437
+ $error['code'] = AMP_Validation_Error_Taxonomy::INVALID_ELEMENT_CODE;
438
+ }
439
+
440
+ if ( ! isset( $error['type'] ) ) {
441
+ $error['type'] = 'script' === $node->nodeName ? AMP_Validation_Error_Taxonomy::JS_ERROR_TYPE : AMP_Validation_Error_Taxonomy::HTML_ELEMENT_ERROR_TYPE;
442
+ }
443
+
444
+ if ( ! isset( $error['node_attributes'] ) ) {
445
+ $error['node_attributes'] = array();
446
+ foreach ( $node->attributes as $attribute ) {
447
+ $error['node_attributes'][ $attribute->nodeName ] = $attribute->nodeValue;
448
  }
449
  }
450
+
451
+ // Capture script contents.
452
+ if ( 'script' === $node->nodeName && ! $node->hasAttribute( 'src' ) ) {
453
+ $error['text'] = $node->textContent;
454
+ }
455
+
456
+ // Suppress 'ver' param from enqueued scripts and styles.
457
+ if ( 'script' === $node->nodeName && isset( $error['node_attributes']['src'] ) && false !== strpos( $error['node_attributes']['src'], 'ver=' ) ) {
458
+ $error['node_attributes']['src'] = add_query_arg( 'ver', '__normalized__', $error['node_attributes']['src'] );
459
+ } elseif ( 'link' === $node->nodeName && isset( $error['node_attributes']['href'] ) && false !== strpos( $error['node_attributes']['href'], 'ver=' ) ) {
460
+ $error['node_attributes']['href'] = add_query_arg( 'ver', '__normalized__', $error['node_attributes']['href'] );
461
+ }
462
+ } elseif ( $node instanceof DOMAttr ) {
463
+ if ( ! isset( $error['code'] ) ) {
464
+ $error['code'] = AMP_Validation_Error_Taxonomy::INVALID_ATTRIBUTE_CODE;
465
+ }
466
+ if ( ! isset( $error['type'] ) ) {
467
+ // If this is an attribute that begins with on, like onclick, it should be a js_error.
468
+ $error['type'] = preg_match( '/^on\w+/', $node->nodeName ) ? AMP_Validation_Error_Taxonomy::JS_ERROR_TYPE : AMP_Validation_Error_Taxonomy::HTML_ATTRIBUTE_ERROR_TYPE;
469
  }
470
+ if ( ! isset( $error['element_attributes'] ) ) {
471
+ $error['element_attributes'] = array();
472
+ if ( $node->parentNode && $node->parentNode->hasAttributes() ) {
473
+ foreach ( $node->parentNode->attributes as $attribute ) {
474
+ $error['element_attributes'][ $attribute->nodeName ] = $attribute->nodeValue;
475
+ }
476
+ }
477
+ }
478
+ }
479
+
480
+ return $error;
481
+ }
482
+
483
+ /**
484
+ * Get data-amp-* values from the parent node 'figure' added by editor block.
485
+ *
486
+ * @param DOMElement $node Base node.
487
+ * @return array AMP data array.
488
+ */
489
+ public function get_data_amp_attributes( $node ) {
490
+ $attributes = array();
491
+
492
+ // Editor blocks add 'figure' as the parent node for images. If this node has data-amp-layout then we should add this as the layout attribute.
493
+ $parent_node = $node->parentNode;
494
+ if ( 'figure' === $parent_node->tagName ) {
495
+ $parent_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $parent_node );
496
+ if ( isset( $parent_attributes['data-amp-layout'] ) ) {
497
+ $attributes['layout'] = $parent_attributes['data-amp-layout'];
498
+ }
499
+ if ( isset( $parent_attributes['data-amp-noloading'] ) && true === filter_var( $parent_attributes['data-amp-noloading'], FILTER_VALIDATE_BOOLEAN ) ) {
500
+ $attributes['noloading'] = $parent_attributes['data-amp-noloading'];
501
+ }
502
+ }
503
+
504
+ return $attributes;
505
+ }
506
+
507
+ /**
508
+ * Set AMP attributes.
509
+ *
510
+ * @param array $attributes Array of attributes.
511
+ * @param array $amp_data Array of AMP attributes.
512
+ * @return array Updated attributes.
513
+ */
514
+ public function filter_data_amp_attributes( $attributes, $amp_data ) {
515
+ if ( isset( $amp_data['layout'] ) ) {
516
+ $attributes['data-amp-layout'] = $amp_data['layout'];
517
+ }
518
+ if ( isset( $amp_data['noloading'] ) ) {
519
+ $attributes['data-amp-noloading'] = '';
520
+ }
521
+ return $attributes;
522
+ }
523
+
524
+ /**
525
+ * Set attributes to node's parent element according to layout.
526
+ *
527
+ * @param DOMElement $node Node.
528
+ * @param array $new_attributes Attributes array.
529
+ * @param string $layout Layout.
530
+ * @return array New attributes.
531
+ */
532
+ public function filter_attachment_layout_attributes( $node, $new_attributes, $layout ) {
533
+
534
+ // The width has to be unset / auto in case of fixed-height.
535
+ if ( 'fixed-height' === $layout ) {
536
+ if ( ! isset( $new_attributes['height'] ) ) {
537
+ $new_attributes['height'] = self::FALLBACK_HEIGHT;
538
+ }
539
+ $new_attributes['width'] = 'auto';
540
+ $node->parentNode->setAttribute( 'style', 'height: ' . $new_attributes['height'] . 'px; width: auto;' );
541
+
542
+ // The parent element should have width/height set and position set in case of 'fill'.
543
+ } elseif ( 'fill' === $layout ) {
544
+ if ( ! isset( $new_attributes['height'] ) ) {
545
+ $new_attributes['height'] = self::FALLBACK_HEIGHT;
546
+ }
547
+ $node->parentNode->setAttribute( 'style', 'position:relative; width: 100%; height: ' . $new_attributes['height'] . 'px;' );
548
+ unset( $new_attributes['width'] );
549
+ unset( $new_attributes['height'] );
550
+ } elseif ( 'responsive' === $layout ) {
551
+ $node->parentNode->setAttribute( 'style', 'position:relative; width: 100%; height: auto' );
552
+ } elseif ( 'fixed' === $layout ) {
553
+ if ( ! isset( $new_attributes['height'] ) ) {
554
+ $new_attributes['height'] = self::FALLBACK_HEIGHT;
555
+ }
556
+ }
557
+
558
+ return $new_attributes;
559
+ }
560
+
561
+ /**
562
+ * Add <amp-image-lightbox> element to body tag if it doesn't exist yet.
563
+ */
564
+ public function maybe_add_amp_image_lightbox_node() {
565
+
566
+ $nodes = $this->dom->getElementById( self::AMP_IMAGE_LIGHTBOX_ID );
567
+ if ( null !== $nodes ) {
568
+ return;
569
+ }
570
+
571
+ $nodes = $this->dom->getElementsByTagName( 'body' );
572
+ if ( ! $nodes->length ) {
573
+ return;
574
  }
575
+ $body_node = $nodes->item( 0 );
576
+ $amp_image_lightbox = AMP_DOM_Utils::create_node( $this->dom, 'amp-image-lightbox', array(
577
+ 'id' => self::AMP_IMAGE_LIGHTBOX_ID,
578
+ 'layout' => 'nodisplay',
579
+ 'data-close-button-aria-label' => __( 'Close', 'amp' ),
580
+ ) );
581
+ $body_node->appendChild( $amp_image_lightbox );
582
  }
583
  }
includes/sanitizers/class-amp-block-sanitizer.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Block_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Block_Sanitizer
10
+ *
11
+ * Modifies elements created as blocks to match the blocks' AMP-specific configuration.
12
+ */
13
+ class AMP_Block_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Tag.
17
+ *
18
+ * @var string Figure tag to identify wrapper around AMP elements.
19
+ * @since 1.0
20
+ */
21
+ public static $tag = 'figure';
22
+
23
+ /**
24
+ * Sanitize the AMP elements contained by <figure> element where necessary.
25
+ *
26
+ * @since 0.2
27
+ */
28
+ public function sanitize() {
29
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
30
+ $num_nodes = $nodes->length;
31
+ if ( 0 === $num_nodes ) {
32
+ return;
33
+ }
34
+
35
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
36
+ $node = $nodes->item( $i );
37
+
38
+ // We are only looking for <figure> elements which have wp-block-embed as class.
39
+ $class = (string) $node->getAttribute( 'class' );
40
+ if ( false === strpos( $class, 'wp-block-embed' ) ) {
41
+ continue;
42
+ }
43
+
44
+ // Remove classes like wp-embed-aspect-16-9 since responsive layout is handled by AMP's layout system.
45
+ $node->setAttribute( 'class', preg_replace( '/(?<=^|\s)wp-embed-aspect-\d+-\d+(?=\s|$)/', '', $class ) );
46
+
47
+ // We're looking for <figure> elements that have one child node only.
48
+ if ( 1 !== count( $node->childNodes ) ) {
49
+ continue;
50
+ }
51
+
52
+ $attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
53
+
54
+ // We are looking for <figure> elements with layout attribute only.
55
+ if (
56
+ ! isset( $attributes['data-amp-layout'] ) &&
57
+ ! isset( $attributes['data-amp-noloading'] ) &&
58
+ ! isset( $attributes['data-amp-lightbox'] )
59
+ ) {
60
+ continue;
61
+ }
62
+
63
+ $amp_el_found = false;
64
+
65
+ foreach ( $node->childNodes as $child_node ) {
66
+
67
+ // We are looking for child elements which start with 'amp-'.
68
+ if ( 0 !== strpos( $child_node->tagName, 'amp-' ) ) {
69
+ continue;
70
+ }
71
+ $amp_el_found = true;
72
+
73
+ $this->set_attributes( $child_node, $node, $attributes );
74
+ }
75
+
76
+ if ( false === $amp_el_found ) {
77
+ continue;
78
+ }
79
+ $this->did_convert_elements = true;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Sets necessary attributes to both parent and AMP element node.
85
+ *
86
+ * @param DOMNode $node AMP element node.
87
+ * @param DOMNode $parent_node <figure> node.
88
+ * @param array $attributes Current attributes of the AMP element.
89
+ */
90
+ protected function set_attributes( $node, $parent_node, $attributes ) {
91
+
92
+ if ( isset( $attributes['data-amp-layout'] ) ) {
93
+ $node->setAttribute( 'layout', $attributes['data-amp-layout'] );
94
+ }
95
+ if ( isset( $attributes['data-amp-noloading'] ) && true === filter_var( $attributes['data-amp-noloading'], FILTER_VALIDATE_BOOLEAN ) ) {
96
+ $node->setAttribute( 'noloading', '' );
97
+ }
98
+
99
+ $layout = $node->getAttribute( 'layout' );
100
+
101
+ // The width has to be unset / auto in case of fixed-height.
102
+ if ( 'fixed-height' === $layout ) {
103
+ if ( ! isset( $attributes['height'] ) ) {
104
+ $node->setAttribute( 'height', self::FALLBACK_HEIGHT );
105
+ }
106
+ $node->setAttribute( 'width', 'auto' );
107
+
108
+ $height = $node->getAttribute( 'height' );
109
+ if ( is_numeric( $height ) ) {
110
+ $height .= 'px';
111
+ }
112
+ $parent_node->setAttribute( 'style', "height: $height; width: auto;" );
113
+
114
+ // The parent element should have width/height set and position set in case of 'fill'.
115
+ } elseif ( 'fill' === $layout ) {
116
+ if ( ! isset( $attributes['height'] ) ) {
117
+ $attributes['height'] = self::FALLBACK_HEIGHT;
118
+ }
119
+ $parent_node->setAttribute( 'style', 'position:relative; width: 100%; height: ' . $attributes['height'] . 'px;' );
120
+ $node->removeAttribute( 'width' );
121
+ $node->removeAttribute( 'height' );
122
+ } elseif ( 'responsive' === $layout ) {
123
+ $parent_node->setAttribute( 'style', 'position:relative; width: 100%; height: auto' );
124
+ } elseif ( 'fixed' === $layout ) {
125
+ if ( ! isset( $attributes['height'] ) ) {
126
+ $node->setAttribute( 'height', self::FALLBACK_HEIGHT );
127
+ }
128
+ }
129
+
130
+ // Set the fallback layout in case needed.
131
+ $attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
132
+ $attributes = $this->set_layout( $attributes );
133
+ if ( $layout !== $attributes['layout'] ) {
134
+ $node->setAttribute( 'layout', $attributes['layout'] );
135
+ }
136
+ }
137
+ }
includes/sanitizers/class-amp-core-theme-sanitizer.php ADDED
@@ -0,0 +1,1151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Core_Theme_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ * @since 1.0
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Core_Theme_Sanitizer
11
+ *
12
+ * Fixes up common issues in core themes and others.
13
+ *
14
+ * @see AMP_Validation_Error_Taxonomy::accept_core_theme_validation_errors()
15
+ * @since 1.0
16
+ */
17
+ class AMP_Core_Theme_Sanitizer extends AMP_Base_Sanitizer {
18
+
19
+ /**
20
+ * Array of flags used to control sanitization.
21
+ *
22
+ * @since 1.0
23
+ * @var array {
24
+ * @type string $stylesheet Stylesheet slug.
25
+ * @type string $template Template slug.
26
+ * @type array $theme_features List of theme features that need to be applied. Features are method names,
27
+ * }
28
+ */
29
+ protected $args;
30
+
31
+ /**
32
+ * Body element.
33
+ *
34
+ * @since 1.0
35
+ * @var DOMElement
36
+ */
37
+ protected $body;
38
+
39
+ /**
40
+ * XPath.
41
+ *
42
+ * @since 1.0
43
+ * @var DOMXPath
44
+ */
45
+ protected $xpath;
46
+
47
+ /**
48
+ * Config for features needed by themes.
49
+ *
50
+ * @since 1.0
51
+ * @var array
52
+ */
53
+ protected static $theme_features = array(
54
+ // Twenty Nineteen.
55
+ 'twentynineteen' => array(
56
+ 'dequeue_scripts' => array(
57
+ 'twentynineteen-skip-link-focus-fix', // This is part of AMP. See <https://github.com/ampproject/amphtml/issues/18671>.
58
+ 'twentynineteen-priority-menu',
59
+ 'twentynineteen-touch-navigation', // @todo There could be an AMP implementation of this, similar to what is implemented on ampproject.org.
60
+ ),
61
+ 'remove_actions' => array(
62
+ 'wp_print_footer_scripts' => array(
63
+ 'twentynineteen_skip_link_focus_fix', // See <https://github.com/WordPress/twentynineteen/pull/47>.
64
+ ),
65
+ ),
66
+ 'add_twentynineteen_masthead_styles' => array(),
67
+ 'add_twentynineteen_image_styles' => array(),
68
+ 'remove_twentynineteen_thumbnail_image_sizes' => array(),
69
+
70
+ ),
71
+
72
+ // Twenty Seventeen.
73
+ 'twentyseventeen' => array(
74
+ // @todo Try to implement belowEntryMetaClass().
75
+ 'dequeue_scripts' => array(
76
+ 'twentyseventeen-html5', // Only relevant for IE<9.
77
+ 'twentyseventeen-global', // There are somethings not yet implemented in AMP. See todos below.
78
+ 'jquery-scrollto', // Implemented via add_smooth_scrolling().
79
+ 'twentyseventeen-navigation', // Handled by add_nav_menu_styles, add_nav_menu_toggle, add_nav_sub_menu_buttons.
80
+ 'twentyseventeen-skip-link-focus-fix', // Only needed by IE11 and when admin bar is present.
81
+ ),
82
+ 'remove_actions' => array(
83
+ 'wp_head' => array(
84
+ 'twentyseventeen_javascript_detection', // AMP is essentially no-js, with any interactively added explicitly via amp-bind.
85
+ ),
86
+ ),
87
+ 'force_svg_support' => array(),
88
+ 'force_fixed_background_support' => array(),
89
+ 'add_twentyseventeen_masthead_styles' => array(),
90
+ 'add_twentyseventeen_image_styles' => array(),
91
+ 'add_twentyseventeen_sticky_nav_menu' => array(),
92
+ 'add_has_header_video_body_class' => array(),
93
+ 'add_nav_menu_styles' => array(),
94
+ 'add_nav_menu_toggle' => array(),
95
+ 'add_nav_sub_menu_buttons' => array(),
96
+ 'add_smooth_scrolling' => array(
97
+ '//header[@id = "masthead"]//a[ contains( @class, "menu-scroll-down" ) ]',
98
+ ),
99
+ 'set_twentyseventeen_quotes_icon' => array(),
100
+ 'add_twentyseventeen_attachment_image_attributes' => array(),
101
+ ),
102
+
103
+ // Twenty Sixteen.
104
+ 'twentysixteen' => array(
105
+ // @todo Figure out an AMP solution for onResizeARIA().
106
+ // @todo Try to implement belowEntryMetaClass().
107
+ 'dequeue_scripts' => array(
108
+ 'twentysixteen-script',
109
+ 'twentysixteen-html5', // Only relevant for IE<9.
110
+ 'twentysixteen-keyboard-image-navigation', // AMP does not yet allow for listening to keydown events.
111
+ 'twentysixteen-skip-link-focus-fix', // Only needed by IE11 and when admin bar is present.
112
+ ),
113
+ 'remove_actions' => array(
114
+ 'wp_head' => array(
115
+ 'twentysixteen_javascript_detection', // AMP is essentially no-js, with any interactively added explicitly via amp-bind.
116
+ ),
117
+ ),
118
+ 'add_nav_menu_styles' => array(),
119
+ 'add_nav_menu_toggle' => array(),
120
+ 'add_nav_sub_menu_buttons' => array(),
121
+ ),
122
+
123
+ // Twenty Fifteen.
124
+ 'twentyfifteen' => array(
125
+ // @todo Figure out an AMP solution for onResizeARIA().
126
+ 'dequeue_scripts' => array(
127
+ 'twentyfifteen-script',
128
+ 'twentyfifteen-keyboard-image-navigation', // AMP does not yet allow for listening to keydown events.
129
+ 'twentyfifteen-skip-link-focus-fix', // Only needed by IE11 and when admin bar is present.
130
+ ),
131
+ 'remove_actions' => array(
132
+ 'wp_head' => array(
133
+ 'twentyfifteen_javascript_detection', // AMP is essentially no-js, with any interactively added explicitly via amp-bind.
134
+ ),
135
+ ),
136
+ 'add_nav_menu_styles' => array(),
137
+ 'add_nav_menu_toggle' => array(),
138
+ 'add_nav_sub_menu_buttons' => array(),
139
+ ),
140
+ );
141
+
142
+ /**
143
+ * Get list of supported core themes.
144
+ *
145
+ * @since 1.0
146
+ *
147
+ * @return string[] Slugs for supported themes.
148
+ */
149
+ public static function get_supported_themes() {
150
+ return array_keys( self::$theme_features );
151
+ }
152
+
153
+ /**
154
+ * Get the acceptable validation errors.
155
+ *
156
+ * @since 1.0
157
+ *
158
+ * @param string $template Template.
159
+ * @return array Acceptable errors.
160
+ */
161
+ public static function get_acceptable_errors( $template ) {
162
+ switch ( $template ) {
163
+ case 'twentyfifteen':
164
+ return array(
165
+ 'removed_unused_css_rules' => true,
166
+ 'illegal_css_at_rule' => array(
167
+ array(
168
+ 'at_rule' => 'viewport',
169
+ 'node_attributes' => array(
170
+ 'id' => 'twentyfifteen-style-css',
171
+ ),
172
+ ),
173
+ array(
174
+ 'at_rule' => '-ms-viewport',
175
+ 'node_attributes' => array(
176
+ 'id' => 'twentyfifteen-style-css',
177
+ ),
178
+ ),
179
+ ),
180
+ );
181
+ case 'twentysixteen':
182
+ return array(
183
+ 'removed_unused_css_rules' => true,
184
+ 'illegal_css_at_rule' => array(
185
+ array(
186
+ 'at_rule' => 'viewport',
187
+ 'node_attributes' => array(
188
+ 'id' => 'twentysixteen-style-css',
189
+ ),
190
+ ),
191
+ array(
192
+ 'at_rule' => '-ms-viewport',
193
+ 'node_attributes' => array(
194
+ 'id' => 'twentysixteen-style-css',
195
+ ),
196
+ ),
197
+ ),
198
+ );
199
+ case 'twentyseventeen':
200
+ return array(
201
+ 'removed_unused_css_rules' => true,
202
+ );
203
+ }
204
+ return array();
205
+ }
206
+
207
+ /**
208
+ * Get theme config.
209
+ *
210
+ * @since 1.0
211
+ *
212
+ * @param string $theme Theme slug.
213
+ * @return array Class names.
214
+ */
215
+ protected static function get_theme_config( $theme ) {
216
+ // phpcs:disable WordPress.WP.I18n.TextDomainMismatch
217
+ $config = array(
218
+ 'sub_menu_button_class' => 'dropdown-toggle',
219
+ );
220
+ switch ( $theme ) {
221
+ case 'twentyfifteen':
222
+ return array_merge(
223
+ $config,
224
+ array(
225
+ 'nav_container_id' => 'secondary',
226
+ 'nav_container_toggle_class' => 'toggled-on',
227
+ 'menu_button_class' => 'secondary-toggle',
228
+ 'menu_button_xpath' => '//header[ @id = "masthead" ]//button[ contains( @class, "secondary-toggle" ) ]',
229
+ 'menu_button_toggle_class' => 'toggled-on',
230
+ 'sub_menu_button_toggle_class' => 'toggle-on',
231
+ 'expand_text ' => __( 'expand child menu', 'twentyfifteen' ),
232
+ 'collapse_text' => __( 'collapse child menu', 'twentyfifteen' ),
233
+ )
234
+ );
235
+
236
+ case 'twentysixteen':
237
+ return array_merge(
238
+ $config,
239
+ array(
240
+ 'nav_container_id' => 'site-header-menu',
241
+ 'nav_container_toggle_class' => 'toggled-on',
242
+ 'menu_button_class' => 'menu-toggle',
243
+ 'menu_button_xpath' => '//header[@id = "masthead"]//button[ @id = "menu-toggle" ]',
244
+ 'menu_button_toggle_class' => 'toggled-on',
245
+ 'sub_menu_button_toggle_class' => 'toggled-on',
246
+ 'expand_text ' => __( 'expand child menu', 'twentysixteen' ),
247
+ 'collapse_text' => __( 'collapse child menu', 'twentysixteen' ),
248
+ )
249
+ );
250
+
251
+ case 'twentyseventeen':
252
+ default:
253
+ return array_merge(
254
+ $config,
255
+ array(
256
+ 'nav_container_id' => 'site-navigation',
257
+ 'nav_container_toggle_class' => 'toggled-on',
258
+ 'menu_button_class' => 'menu-toggle',
259
+ 'menu_button_xpath' => '//nav[@id = "site-navigation"]//button[ contains( @class, "menu-toggle" ) ]',
260
+ 'menu_button_toggle_class' => 'toggled-on',
261
+ 'sub_menu_button_toggle_class' => 'toggled-on',
262
+ 'expand_text ' => __( 'expand child menu', 'twentyseventeen' ),
263
+ 'collapse_text' => __( 'collapse child menu', 'twentyseventeen' ),
264
+ )
265
+ );
266
+ }
267
+ // phpcs:enable WordPress.WP.I18n.TextDomainMismatch
268
+ }
269
+
270
+ /**
271
+ * Find theme features for core theme.
272
+ *
273
+ * @since 1.0
274
+ *
275
+ * @param array $args Args.
276
+ * @param bool $static Static. that is, whether should run during output buffering.
277
+ * @return array Theme features.
278
+ */
279
+ protected static function get_theme_features( $args, $static = false ) {
280
+ $theme_features = array();
281
+ $theme_candidates = wp_array_slice_assoc( $args, array( 'stylesheet', 'template' ) );
282
+ foreach ( $theme_candidates as $theme_candidate ) {
283
+ if ( isset( self::$theme_features[ $theme_candidate ] ) ) {
284
+ $theme_features = self::$theme_features[ $theme_candidate ];
285
+ break;
286
+ }
287
+ }
288
+
289
+ // Allow specific theme features to be requested even if the theme is not in core.
290
+ if ( isset( $args['theme_features'] ) ) {
291
+ $theme_features = array_merge( $args['theme_features'], $theme_features );
292
+ }
293
+
294
+ $final_theme_features = array();
295
+ foreach ( $theme_features as $theme_feature => $feature_args ) {
296
+ if ( ! method_exists( __CLASS__, $theme_feature ) ) {
297
+ continue;
298
+ }
299
+ try {
300
+ $reflection = new ReflectionMethod( __CLASS__, $theme_feature );
301
+ if ( $reflection->isStatic() === $static ) {
302
+ $final_theme_features[ $theme_feature ] = $feature_args;
303
+ }
304
+ } catch ( Exception $e ) {
305
+ unset( $e );
306
+ }
307
+ }
308
+ return $final_theme_features;
309
+ }
310
+
311
+ /**
312
+ * Add filters to manipulate output during output buffering before the DOM is constructed.
313
+ *
314
+ * @since 1.0
315
+ *
316
+ * @param array $args Args.
317
+ */
318
+ public static function add_buffering_hooks( $args = array() ) {
319
+ $theme_features = self::get_theme_features( $args, true );
320
+ foreach ( $theme_features as $theme_feature => $feature_args ) {
321
+ if ( method_exists( __CLASS__, $theme_feature ) ) {
322
+ call_user_func( array( __CLASS__, $theme_feature ), $feature_args );
323
+ }
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Add filter to output the quote icons in front of the article content.
329
+ *
330
+ * This is only used in Twenty Seventeen.
331
+ *
332
+ * @since 1.0
333
+ * @link https://github.com/WordPress/wordpress-develop/blob/f4580c122b7d0d2d66d22f806c6fe6e11023c6f0/src/wp-content/themes/twentyseventeen/assets/js/global.js#L105-L108
334
+ */
335
+ public static function set_twentyseventeen_quotes_icon() {
336
+ add_filter( 'the_content', function ( $content ) {
337
+
338
+ // Why isn't Twenty Seventeen doing this to begin with? Why is it using JS to add the quote icon?
339
+ if ( function_exists( 'twentyseventeen_get_svg' ) && 'quote' === get_post_format() ) {
340
+ $icon = twentyseventeen_get_svg( array( 'icon' => 'quote-right' ) );
341
+ $content = preg_replace( '#(<blockquote.*?>)#s', '$1' . $icon, $content );
342
+ }
343
+
344
+ return $content;
345
+ } );
346
+ }
347
+
348
+ /**
349
+ * Remove the sizes attribute from thumbnail images in Twenty Nineteen.
350
+ *
351
+ * The AMP runtime sets an inline style on an <amp-img> based on the sizes attribute if it's present.
352
+ * For example, <amp-img style="width:calc(50vw)">.
353
+ * Removing the 'sizes' attribute isn't ideal, but it looks like it's not possible to override that inline style.
354
+ *
355
+ * @todo: remove when this is resolved: https://github.com/ampproject/amphtml/issues/17053
356
+ * @since 1.0
357
+ */
358
+ public static function remove_twentynineteen_thumbnail_image_sizes() {
359
+ add_filter( 'wp_get_attachment_image_attributes', function( $attr ) {
360
+ if ( isset( $attr['class'] ) && false !== strpos( $attr['class'], 'attachment-post-thumbnail' ) ) {
361
+ unset( $attr['sizes'] );
362
+ }
363
+
364
+ return $attr;
365
+ }, 11 );
366
+ }
367
+
368
+ /**
369
+ * Add filter to adjust the attachment image attributes to ensure attachment pages have a consistent <amp-img> rendering.
370
+ *
371
+ * This is only used in Twenty Seventeen.
372
+ *
373
+ * @since 1.0
374
+ * @link https://github.com/WordPress/wordpress-develop/blob/ddc8f803c6e99118998191fd2ea24124feb53659/src/wp-content/themes/twentyseventeen/functions.php#L545:L554
375
+ */
376
+ public static function add_twentyseventeen_attachment_image_attributes() {
377
+ add_filter( 'wp_get_attachment_image_attributes', function ( $attr, $attachment, $size ) {
378
+ if (
379
+ isset( $attr['class'] )
380
+ &&
381
+ (
382
+ 'custom-logo' === $attr['class']
383
+ ||
384
+ false !== strpos( $attr['class'], 'attachment-twentyseventeen-featured-image' )
385
+ )
386
+ ) {
387
+ /*
388
+ * The AMP runtime sets an inline style on an <amp-img> based on the sizes attribute if it's present.
389
+ * For example, <amp-img style="width:100%">.
390
+ * Removing the 'sizes' attribute is only a workaround, as it looks like it's not possible to override that inline style.
391
+ *
392
+ * @todo: remove when this is resolved: https://github.com/ampproject/amphtml/issues/17053
393
+ */
394
+ unset( $attr['sizes'] );
395
+ } elseif ( is_attachment() ) {
396
+ $sizes = wp_get_attachment_image_sizes( $attachment->ID, $size );
397
+ if ( false !== $sizes ) {
398
+ $attr['sizes'] = $sizes;
399
+ }
400
+ }
401
+ return $attr;
402
+ }, 11, 3 );
403
+
404
+ /*
405
+ * The max-height of the `.custom-logo-link img` is defined as being 80px, unless
406
+ * there is header media in which case it is 200px. Issues related to vertically-squashed
407
+ * images can be avoided if we just make sure that the image has this height to begin with.
408
+ */
409
+ add_filter( 'get_custom_logo', function( $html ) {
410
+ $src = wp_get_attachment_image_src( get_theme_mod( 'custom_logo' ), 'full' );
411
+ if ( ! $src ) {
412
+ return $html;
413
+ }
414
+
415
+ if ( 'blank' === get_header_textcolor() && has_custom_header() ) {
416
+ $height = 200;
417
+ } else {
418
+ $height = 80;
419
+ }
420
+ $width = $height * ( $src[1] / $src[2] ); // Note that float values are allowed.
421
+
422
+ $html = preg_replace( '/(?<=width=")\d+(?=")/', $width, $html );
423
+ $html = preg_replace( '/(?<=height=")\d+(?=")/', $height, $html );
424
+ return $html;
425
+ } );
426
+ }
427
+
428
+ /**
429
+ * Fix up core themes to do things in the AMP way.
430
+ *
431
+ * @since 1.0
432
+ */
433
+ public function sanitize() {
434
+ $this->body = $this->dom->getElementsByTagName( 'body' )->item( 0 );
435
+ if ( ! $this->body ) {
436
+ return;
437
+ }
438
+
439
+ $this->xpath = new DOMXPath( $this->dom );
440
+
441
+ $theme_features = self::get_theme_features( $this->args, false );
442
+ foreach ( $theme_features as $theme_feature => $feature_args ) {
443
+ if ( method_exists( $this, $theme_feature ) ) {
444
+ call_user_func( array( $this, $theme_feature ), $feature_args );
445
+ }
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Dequeue scripts.
451
+ *
452
+ * @since 1.0
453
+ *
454
+ * @param string[] $handles Handles, where each item value is the script handle.
455
+ */
456
+ public static function dequeue_scripts( $handles = array() ) {
457
+ add_action( 'wp_enqueue_scripts', function() use ( $handles ) {
458
+ foreach ( $handles as $handle ) {
459
+ wp_dequeue_script( $handle );
460
+ }
461
+ }, PHP_INT_MAX );
462
+ }
463
+
464
+ /**
465
+ * Remove actions.
466
+ *
467
+ * @since 1.0
468
+ *
469
+ * @param array $actions Actions, with action name as key and value being callback.
470
+ */
471
+ public static function remove_actions( $actions = array() ) {
472
+ foreach ( $actions as $action => $callbacks ) {
473
+ foreach ( $callbacks as $callback ) {
474
+ $priority = has_action( $action, $callback );
475
+ if ( false !== $priority ) {
476
+ remove_action( $action, $callback, $priority );
477
+ }
478
+ }
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Add smooth scrolling from link to target element.
484
+ *
485
+ * @since 1.0
486
+ *
487
+ * @param string[] $link_xpaths XPath queries to the links that should smooth scroll.
488
+ */
489
+ public function add_smooth_scrolling( $link_xpaths ) {
490
+ foreach ( $link_xpaths as $link_xpath ) {
491
+ foreach ( $this->xpath->query( $link_xpath ) as $link ) {
492
+ if ( $link instanceof DOMElement && preg_match( '/#(.+)/', $link->getAttribute( 'href' ), $matches ) ) {
493
+ $link->setAttribute( 'on', sprintf( 'tap:%s.scrollTo(duration=600)', $matches[1] ) );
494
+ }
495
+ }
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Force SVG support, replacing no-svg class name with svg class name.
501
+ *
502
+ * @since 1.0
503
+ *
504
+ * @link https://github.com/WordPress/wordpress-develop/blob/1af1f65a21a1a697fb5f33027497f9e5ae638453/src/wp-content/themes/twentyseventeen/assets/js/global.js#L211-L213
505
+ * @link https://caniuse.com/#feat=svg
506
+ */
507
+ public function force_svg_support() {
508
+ $this->dom->documentElement->setAttribute(
509
+ 'class',
510
+ preg_replace(
511
+ '/(^|\s)no-svg(\s|$)/',
512
+ ' svg ',
513
+ $this->dom->documentElement->getAttribute( 'class' )
514
+ )
515
+ );
516
+ }
517
+
518
+ /**
519
+ * Force support for fixed background-attachment.
520
+ *
521
+ * @since 1.0
522
+ *
523
+ * @link https://github.com/WordPress/wordpress-develop/blob/1af1f65a21a1a697fb5f33027497f9e5ae638453/src/wp-content/themes/twentyseventeen/assets/js/global.js#L215-L217
524
+ * @link https://caniuse.com/#feat=background-attachment
525
+ */
526
+ public function force_fixed_background_support() {
527
+ $this->dom->documentElement->setAttribute(
528
+ 'class',
529
+ $this->dom->documentElement->getAttribute( 'class' ) . ' background-fixed'
530
+ );
531
+ }
532
+
533
+ /**
534
+ * Add body class when there is a header video.
535
+ *
536
+ * @since 1.0
537
+ * @link https://github.com/WordPress/wordpress-develop/blob/a26c24226c6b131a0ed22c722a836c100d3ba254/src/wp-content/themes/twentyseventeen/assets/js/global.js#L244-L247
538
+ *
539
+ * @param array $args Args.
540
+ */
541
+ public static function add_has_header_video_body_class( $args = array() ) {
542
+ $args = array_merge(
543
+ array(
544
+ 'class_name' => 'has-header-video',
545
+ ),
546
+ $args
547
+ );
548
+
549
+ add_filter( 'body_class', function( $body_classes ) use ( $args ) {
550
+ if ( has_header_video() ) {
551
+ $body_classes[] = $args['class_name'];
552
+ }
553
+ return $body_classes;
554
+ } );
555
+ }
556
+
557
+ /**
558
+ * Get the (common) navigation outer height.
559
+ *
560
+ * @todo If the nav menu has many items and it spans multiple rows, this will be too small.
561
+ * @link https://github.com/WordPress/wordpress-develop/blob/fd5ba80c5c3d9cf62348567073945e246285fbca/src/wp-content/themes/twentyseventeen/assets/js/global.js#L50
562
+ *
563
+ * @return int Navigation outer height.
564
+ */
565
+ protected static function get_twentyseventeen_navigation_outer_height() {
566
+ return 72;
567
+ }
568
+
569
+ /**
570
+ * Add required styles for featured image header in Twenty Nineteen.
571
+ *
572
+ * The following is necessary because the styles in the theme apply to the featured img,
573
+ * and the CSS parser will then convert the selectors to amp-img. Nevertheless, object-fit
574
+ * does not apply on amp-img and it needs to apply on an actual img.
575
+ *
576
+ * @link https://github.com/WordPress/wordpress-develop/blob/5.0/src/wp-content/themes/twentynineteen/style.css#L2276-L2299
577
+ * @since 1.0
578
+ */
579
+ public static function add_twentynineteen_masthead_styles() {
580
+ add_action( 'wp_enqueue_scripts', function() {
581
+ ob_start();
582
+ ?>
583
+ <style>
584
+ .site-header.featured-image .site-featured-image .post-thumbnail amp-img > img {
585
+ height: auto;
586
+ left: 50%;
587
+ max-width: 1000%;
588
+ min-height: 100%;
589
+ min-width: 100vw;
590
+ position: absolute;
591
+ top: 50%;
592
+ transform: translateX(-50%) translateY(-50%);
593
+ width: auto;
594
+ z-index: 1;
595
+ /* When image filters are active, make it grayscale to colorize it blue. */
596
+ }
597
+
598
+ @supports (object-fit: cover) {
599
+ .site-header.featured-image .site-featured-image .post-thumbnail amp-img > img {
600
+ height: 100%;
601
+ left: 0;
602
+ object-fit: cover;
603
+ top: 0;
604
+ transform: none;
605
+ width: 100%;
606
+ }
607
+ }
608
+ </style>
609
+ <?php
610
+ $styles = str_replace( array( '<style>', '</style>' ), '', ob_get_clean() );
611
+ wp_add_inline_style( get_template() . '-style', $styles );
612
+ }, 11 );
613
+ }
614
+
615
+ /**
616
+ * Add required styles for video and image headers.
617
+ *
618
+ * This is currently used exclusively for Twenty Seventeen.
619
+ *
620
+ * @since 1.0
621
+ * @link https://github.com/WordPress/wordpress-develop/blob/1af1f65a21a1a697fb5f33027497f9e5ae638453/src/wp-content/themes/twentyseventeen/style.css#L1687
622
+ * @link https://github.com/WordPress/wordpress-develop/blob/1af1f65a21a1a697fb5f33027497f9e5ae638453/src/wp-content/themes/twentyseventeen/style.css#L1743
623
+ */
624
+ public static function add_twentyseventeen_masthead_styles() {
625
+ $args = self::get_theme_config( get_template() );
626
+
627
+ /*
628
+ * The following is necessary because the styles in the theme apply to img and video,
629
+ * and the CSS parser will then convert the selectors to amp-img and amp-video respectively.
630
+ * Nevertheless, object-fit does not apply on amp-img and it needs to apply on an actual img.
631
+ */
632
+ add_action( 'wp_enqueue_scripts', function() use ( $args ) {
633
+ $is_front_page_layout = ( is_front_page() && 'posts' !== get_option( 'show_on_front' ) ) || ( is_home() && is_front_page() );
634
+ ob_start();
635
+ ?>
636
+ <style>
637
+ .has-header-image .custom-header-media amp-img > img,
638
+ .has-header-video .custom-header-media amp-video > video{
639
+ position: fixed;
640
+ height: auto;
641
+ left: 50%;
642
+ max-width: 1000%;
643
+ min-height: 100%;
644
+ min-width: 100%;
645
+ min-width: 100vw; /* vw prevents 1px gap on left that 100% has */
646
+ width: auto;
647
+ top: 50%;
648
+ padding-bottom: 1px; /* Prevent header from extending beyond the footer */
649
+ -ms-transform: translateX(-50%) translateY(-50%);
650
+ -moz-transform: translateX(-50%) translateY(-50%);
651
+ -webkit-transform: translateX(-50%) translateY(-50%);
652
+ transform: translateX(-50%) translateY(-50%);
653
+ }
654
+ .has-header-image:not(.twentyseventeen-front-page):not(.home) .custom-header-media amp-img > img {
655
+ bottom: 0;
656
+ position: absolute;
657
+ top: auto;
658
+ -ms-transform: translateX(-50%) translateY(0);
659
+ -moz-transform: translateX(-50%) translateY(0);
660
+ -webkit-transform: translateX(-50%) translateY(0);
661
+ transform: translateX(-50%) translateY(0);
662
+ }
663
+ /* For browsers that support object-fit */
664
+ @supports ( object-fit: cover ) {
665
+ .has-header-image .custom-header-media amp-img > img,
666
+ .has-header-video .custom-header-media amp-video > video,
667
+ .has-header-image:not(.twentyseventeen-front-page):not(.home) .custom-header-media amp-img > img {
668
+ height: 100%;
669
+ left: 0;
670
+ -o-object-fit: cover;
671
+ object-fit: cover;
672
+ top: 0;
673
+ -ms-transform: none;
674
+ -moz-transform: none;
675
+ -webkit-transform: none;
676
+ transform: none;
677
+ width: 100%;
678
+ }
679
+ }
680
+
681
+ .navigation-top.site-navigation-fixed {
682
+ display: none;
683
+ }
684
+
685
+ <?php if ( $is_front_page_layout && ! has_custom_header() ) : ?>
686
+ /* https://github.com/WordPress/wordpress-develop/blob/fd5ba80c5c3d9cf62348567073945e246285fbca/src/wp-content/themes/twentyseventeen/assets/js/global.js#L92-L94 */
687
+ .site-branding {
688
+ margin-bottom: <?php echo (int) AMP_Core_Theme_Sanitizer::get_twentyseventeen_navigation_outer_height(); ?>px;
689
+ }
690
+ <?php endif; ?>
691
+
692
+ @media screen and (min-width: 48em) {
693
+ /* Note that adjustHeaderHeight() is irrelevant with this change */
694
+ <?php if ( ! $is_front_page_layout ) : ?>
695
+ .navigation-top {
696
+ position: static;
697
+ }
698
+ <?php endif; ?>
699
+
700
+ /* Initial styles that amp-animations for navigationTopShow and navigationTopHide will override */
701
+ .navigation-top.site-navigation-fixed {
702
+ opacity: 0;
703
+ transform: translateY( -<?php echo (int) AMP_Core_Theme_Sanitizer::get_twentyseventeen_navigation_outer_height(); ?>px );
704
+ display: block;
705
+ }
706
+ }
707
+ </style>
708
+ <?php
709
+ $styles = str_replace( array( '<style>', '</style>' ), '', ob_get_clean() );
710
+ wp_add_inline_style( get_template() . '-style', $styles );
711
+ }, 11 );
712
+ }
713
+
714
+ /**
715
+ * Override the featured image header styling in style.css.
716
+ * Used only for Twenty Seventeen.
717
+ *
718
+ * @since 1.0
719
+ * @link https://github.com/WordPress/wordpress-develop/blob/1af1f65a21a1a697fb5f33027497f9e5ae638453/src/wp-content/themes/twentyseventeen/style.css#L2100
720
+ */
721
+ public static function add_twentyseventeen_image_styles() {
722
+ add_action( 'wp_enqueue_scripts', function() {
723
+ ob_start();
724
+ ?>
725
+ <style>
726
+ /* Override the display: block in twentyseventeen/style.css, as <amp-img> is usually inline-block. */
727
+ .single-featured-image-header amp-img {
728
+ display: inline-block;
729
+ }
730
+
731
+ /* Because the <amp-img> is inline-block, its container needs this rule to center it. */
732
+ .single-featured-image-header {
733
+ text-align: center;
734
+ }
735
+ </style>
736
+ <?php
737
+ $styles = str_replace( array( '<style>', '</style>' ), '', ob_get_clean() );
738
+ wp_add_inline_style( get_template() . '-style', $styles );
739
+ }, 11 );
740
+ }
741
+
742
+ /**
743
+ * Add sticky nav menu to Twenty Seventeen.
744
+ *
745
+ * This is implemented by cloning the navigation-top element, giving it a fixed position outside of the viewport,
746
+ * and then showing it at the top of the window as soon as the original nav begins to get scrolled out of view.
747
+ * In order to improve accessibility, the cloned nav gets aria-hidden=true and all of the links get tabindex=-1
748
+ * to prevent the keyboard from focusing on elements off the screen; it is not necessary to focus on the elements
749
+ * in the fixed nav menu because as soon as the original nav menu is focused then the window is scrolled to the
750
+ * top anyway.
751
+ *
752
+ * @since 1.0
753
+ */
754
+ public function add_twentyseventeen_sticky_nav_menu() {
755
+ /**
756
+ * Elements.
757
+ *
758
+ * @var DOMElement $link
759
+ * @var DOMElement $navigation_top
760
+ * @var DOMElement $navigation_top_fixed
761
+ */
762
+ $navigation_top = $this->xpath->query( '//header[ @id = "masthead" ]//div[ contains( @class, "navigation-top" ) ]' )->item( 0 );
763
+ if ( ! $navigation_top ) {
764
+ return;
765
+ }
766
+
767
+ $navigation_top_fixed = $navigation_top->cloneNode( true );
768
+ $navigation_top_fixed->setAttribute( 'class', $navigation_top_fixed->getAttribute( 'class' ) . ' site-navigation-fixed' );
769
+
770
+ $navigation_top_fixed->setAttribute( 'aria-hidden', 'true' );
771
+ foreach ( $navigation_top_fixed->getElementsByTagName( 'a' ) as $link ) {
772
+ $link->setAttribute( 'tabindex', '-1' );
773
+ }
774
+
775
+ $navigation_top->parentNode->insertBefore( $navigation_top_fixed, $navigation_top->nextSibling );
776
+
777
+ $attributes = array(
778
+ 'layout' => 'nodisplay',
779
+ 'intersection-ratios' => 1,
780
+ 'on' => implode( ';', array(
781
+ 'exit:navigationTopShow.start',
782
+ 'enter:navigationTopHide.start',
783
+ ) ),
784
+ );
785
+ if ( is_admin_bar_showing() ) {
786
+ $attributes['viewport-margins'] = '32px 0';
787
+ }
788
+ $position_observer = AMP_DOM_Utils::create_node( $this->dom, 'amp-position-observer', $attributes );
789
+ $navigation_top->appendChild( $position_observer );
790
+
791
+ $animations = array(
792
+ 'navigationTopShow' => array(
793
+ 'duration' => 0,
794
+ 'fill' => 'both',
795
+ 'animations' => array(
796
+ 'selector' => '.navigation-top.site-navigation-fixed',
797
+ 'media' => '(min-width: 48em)',
798
+ 'keyframes' => array(
799
+ 'opacity' => 1.0,
800
+ 'transform' => 'translateY( 0 )',
801
+ ),
802
+ ),
803
+ ),
804
+ 'navigationTopHide' => array(
805
+ 'duration' => 0,
806
+ 'fill' => 'both',
807
+ 'animations' => array(
808
+ 'selector' => '.navigation-top.site-navigation-fixed',
809
+ 'media' => '(min-width: 48em)',
810
+ 'keyframes' => array(
811
+ 'opacity' => 0.0,
812
+ 'transform' => sprintf( 'translateY( -%dpx )', self::get_twentyseventeen_navigation_outer_height() ),
813
+ ),
814
+ ),
815
+ ),
816
+ );
817
+
818
+ foreach ( $animations as $animation_id => $animation ) {
819
+ $amp_animation = AMP_DOM_Utils::create_node( $this->dom, 'amp-animation', array(
820
+ 'id' => $animation_id,
821
+ 'layout' => 'nodisplay',
822
+ ) );
823
+ $position_script = $this->dom->createElement( 'script' );
824
+ $position_script->setAttribute( 'type', 'application/json' );
825
+ $position_script->appendChild( $this->dom->createTextNode( wp_json_encode( $animation ) ) );
826
+ $amp_animation->appendChild( $position_script );
827
+ $this->body->appendChild( $amp_animation );
828
+ }
829
+ }
830
+
831
+ /**
832
+ * Add styles for the nav menu specifically to deal with AMP running in a no-js context.
833
+ *
834
+ * @since 1.0
835
+ *
836
+ * @param array $args Args.
837
+ */
838
+ public static function add_nav_menu_styles( $args = array() ) {
839
+ $args = array_merge(
840
+ self::get_theme_config( get_template() ),
841
+ $args
842
+ );
843
+
844
+ add_action( 'wp_enqueue_scripts', function() use ( $args ) {
845
+ ob_start();
846
+ ?>
847
+ <style>
848
+ /* Override no-js selector in parent theme. */
849
+ .no-js .main-navigation ul ul {
850
+ display: none;
851
+ }
852
+
853
+ /* Use sibling selector and re-use class on button instead of toggling toggle-on class on ul.sub-menu */
854
+ .main-navigation ul .<?php echo esc_html( $args['sub_menu_button_toggle_class'] ); ?> + .sub-menu {
855
+ display: block;
856
+ }
857
+
858
+ <?php if ( 'twentyseventeen' === get_template() ) : ?>
859
+ /* Show the button*/
860
+ .no-js .<?php echo esc_html( $args['menu_button_class'] ); ?> {
861
+ display: block;
862
+ }
863
+ .no-js .main-navigation > div > ul {
864
+ display: none;
865
+ }
866
+ .no-js .main-navigation.<?php echo esc_html( $args['nav_container_toggle_class'] ); ?> > div > ul {
867
+ display: block;
868
+ }
869
+ @media screen and (min-width: 48em) {
870
+ .no-js .<?php echo esc_html( $args['menu_button_class'] ); ?>,
871
+ .no-js .<?php echo esc_html( $args['sub_menu_button_class'] ); ?> {
872
+ display: none;
873
+ }
874
+ .no-js .main-navigation ul,
875
+ .no-js .main-navigation ul ul,
876
+ .no-js .main-navigation > div > ul {
877
+ display: block;
878
+ }
879
+ .main-navigation ul li.menu-item-has-children:focus-within:before,
880
+ .main-navigation ul li.menu-item-has-children:focus-within:after,
881
+ .main-navigation ul li.page_item_has_children:focus-within:before,
882
+ .main-navigation ul li.page_item_has_children:focus-within:after {
883
+ display: block;
884
+ }
885
+ .main-navigation ul ul li:focus-within > ul {
886
+ <?php if ( is_rtl() ) : ?>
887
+ left: auto;
888
+ right: 100%;
889
+ <?php else : ?>
890
+ left: 100%;
891
+ right: auto;
892
+ <?php endif; ?>
893
+ }
894
+ .main-navigation li li:focus-within {
895
+ background: #767676;
896
+ }
897
+ .main-navigation li li:focus-within > a,
898
+ .main-navigation li li a:focus-within,
899
+ .main-navigation li li.current_page_item a:focus-within,
900
+ .main-navigation li li.current-menu-item a:focus-within {
901
+ color: #fff;
902
+ }
903
+ .main-navigation ul li:focus-within > ul {
904
+ <?php if ( is_rtl() ) : ?>
905
+ left: auto;
906
+ right: 0.5em;
907
+ <?php else : ?>
908
+ left: 0.5em;
909
+ right: auto;
910
+ <?php endif; ?>
911
+ }
912
+
913
+ .main-navigation ul ul li.menu-item-has-children:focus-within:before,
914
+ .main-navigation ul ul li.menu-item-has-children:focus-within:after,
915
+ .main-navigation ul ul li.page_item_has_children:focus-within:before,
916
+ .main-navigation ul ul li.page_item_has_children:focus-within:after {
917
+ display: none;
918
+ }
919
+ }
920
+ <?php elseif ( 'twentysixteen' === get_template() ) : ?>
921
+ @media screen and (max-width: 56.875em) {
922
+ /* Show the button*/
923
+ .no-js .<?php echo esc_html( $args['menu_button_class'] ); ?> {
924
+ display: block;
925
+ }
926
+ .no-js .site-header-menu {
927
+ display: none;
928
+ }
929
+ .no-js .site-header-menu.toggled-on {
930
+ display: block;
931
+ }
932
+ }
933
+ @media screen and (min-width: 56.875em) {
934
+ .no-js .main-navigation ul ul {
935
+ display: block;
936
+ }
937
+ .main-navigation li:focus-within > a {
938
+ color: #007acc;
939
+ }
940
+ .main-navigation li:focus-within > ul {
941
+ <?php if ( is_rtl() ) : ?>
942
+ left: auto;
943
+ right: 0;
944
+ <?php else : ?>
945
+ left: 0;
946
+ right: auto;
947
+ <?php endif; ?>
948
+ }
949
+ .main-navigation ul ul li:focus-within > ul {
950
+ <?php if ( is_rtl() ) : ?>
951
+ left: 100%;
952
+ right: auto;
953
+ <?php else : ?>
954
+ left: auto;
955
+ right: 100%;
956
+ <?php endif; ?>
957
+ }
958
+ }
959
+ <?php elseif ( 'twentyfifteen' === get_template() ) : ?>
960
+ @media screen and (min-width: 59.6875em) {
961
+ /* Attempt to emulate https://github.com/WordPress/wordpress-develop/blob/5e9a39baa7d4368f7d3c36dcbcd53db6317677c9/src/wp-content/themes/twentyfifteen/js/functions.js#L108-L149 */
962
+ #sidebar {
963
+ position: sticky;
964
+ top: -9vh;
965
+ max-height: 109vh;
966
+ overflow-y: auto;
967
+ }
968
+ }
969
+
970
+ <?php endif; ?>
971
+ </style>
972
+ <?php
973
+ $styles = str_replace( array( '<style>', '</style>' ), '', ob_get_clean() );
974
+ wp_add_inline_style( get_template() . '-style', $styles );
975
+ }, 11 );
976
+ }
977
+
978
+ /**
979
+ * Ensure that JS-only nav menu styles apply to AMP as well since even though scripts are not allowed, there are AMP-bind implementations.
980
+ *
981
+ * @since 1.0
982
+ *
983
+ * @param array $args Args.
984
+ */
985
+ public function add_nav_menu_toggle( $args = array() ) {
986
+ $args = array_merge(
987
+ self::get_theme_config( get_template() ),
988
+ $args
989
+ );
990
+
991
+ $nav_el = $this->dom->getElementById( $args['nav_container_id'] );
992
+ $button_el = $this->xpath->query( $args['menu_button_xpath'] )->item( 0 );
993
+ if ( ! $nav_el ) {
994
+ if ( $button_el ) {
995
+
996
+ // Remove the button since it won't be used.
997
+ $button_el->parentNode->removeChild( $button_el );
998
+ }
999
+ return;
1000
+ }
1001
+
1002
+ if ( ! $button_el ) {
1003
+ return;
1004
+ }
1005
+
1006
+ $state_id = 'navMenuToggledOn';
1007
+ $expanded = false;
1008
+
1009
+ $nav_el->setAttribute(
1010
+ AMP_DOM_Utils::get_amp_bind_placeholder_prefix() . 'class',
1011
+ sprintf(
1012
+ "%s + ( $state_id ? %s : '' )",
1013
+ wp_json_encode( $nav_el->getAttribute( 'class' ) ),
1014
+ wp_json_encode( ' ' . $args['nav_container_toggle_class'] )
1015
+ )
1016
+ );
1017
+
1018
+ $state_el = $this->dom->createElement( 'amp-state' );
1019
+ $state_el->setAttribute( 'id', $state_id );
1020
+ $script_el = $this->dom->createElement( 'script' );
1021
+ $script_el->setAttribute( 'type', 'application/json' );
1022
+ $script_el->appendChild( $this->dom->createTextNode( wp_json_encode( $expanded ) ) );
1023
+ $state_el->appendChild( $script_el );
1024
+ $nav_el->parentNode->insertBefore( $state_el, $nav_el );
1025
+
1026
+ $button_on = sprintf( "tap:AMP.setState({ $state_id: ! $state_id })" );
1027
+ $button_el->setAttribute( 'on', $button_on );
1028
+ $button_el->setAttribute( 'aria-expanded', 'false' );
1029
+ $button_el->setAttribute( AMP_DOM_Utils::get_amp_bind_placeholder_prefix() . 'aria-expanded', "$state_id ? 'true' : 'false'" );
1030
+ $button_el->setAttribute(
1031
+ AMP_DOM_Utils::get_amp_bind_placeholder_prefix() . 'class',
1032
+ sprintf( "%s + ( $state_id ? %s : '' )", wp_json_encode( $button_el->getAttribute( 'class' ) ), wp_json_encode( ' ' . $args['menu_button_toggle_class'] ) )
1033
+ );
1034
+ }
1035
+
1036
+ /**
1037
+ * Add buttons for nav sub-menu items.
1038
+ *
1039
+ * @since 1.0
1040
+ * @link https://github.com/WordPress/wordpress-develop/blob/a26c24226c6b131a0ed22c722a836c100d3ba254/src/wp-content/themes/twentyseventeen/assets/js/navigation.js#L11-L43
1041
+ *
1042
+ * @param array $args Args.
1043
+ */
1044
+ public static function add_nav_sub_menu_buttons( $args = array() ) {
1045
+ $default_args = self::get_theme_config( get_template() );
1046
+ switch ( get_template() ) {
1047
+ case 'twentyseventeen':
1048
+ if ( function_exists( 'twentyseventeen_get_svg' ) ) {
1049
+ $default_args['icon'] = twentyseventeen_get_svg( array(
1050
+ 'icon' => 'angle-down',
1051
+ 'fallback' => true,
1052
+ ) );
1053
+ }
1054
+ break;
1055
+ }
1056
+ $args = array_merge( $default_args, $args );
1057
+
1058
+ /**
1059
+ * Filter the HTML output of a nav menu item to add the AMP dropdown button to reveal the sub-menu.
1060
+ *
1061
+ * @see twentyfifteen_amp_setup_hooks()
1062
+ *
1063
+ * @param string $item_output Nav menu item HTML.
1064
+ * @param object $item Nav menu item.
1065
+ * @return string Modified nav menu item HTML.
1066
+ */
1067
+ add_filter( 'walker_nav_menu_start_el', function( $item_output, $item, $depth, $nav_menu_args ) use ( $args ) {
1068
+ unset( $depth );
1069
+
1070
+ // Skip adding buttons to nav menu widgets for now.
1071
+ if ( empty( $nav_menu_args->theme_location ) ) {
1072
+ return $item_output;
1073
+ }
1074
+
1075
+ if ( ! in_array( 'menu-item-has-children', $item->classes, true ) ) {
1076
+ return $item_output;
1077
+ }
1078
+ static $nav_menu_item_number = 0;
1079
+ $nav_menu_item_number++;
1080
+
1081
+ $expanded = in_array( 'current-menu-ancestor', $item->classes, true );
1082
+
1083
+ $expanded_state_id = 'navMenuItemExpanded' . $nav_menu_item_number;
1084
+
1085
+ // Create new state for managing storing the whether the sub-menu is expanded.
1086
+ $item_output .= sprintf(
1087
+ '<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
1088
+ esc_attr( $expanded_state_id ),
1089
+ wp_json_encode( $expanded )
1090
+ );
1091
+
1092
+ $dropdown_button = '<button';
1093
+ $dropdown_button .= sprintf(
1094
+ ' class="%s" [class]="%s"',
1095
+ esc_attr( $args['sub_menu_button_class'] . ( $expanded ? ' ' . $args['sub_menu_button_toggle_class'] : '' ) ),
1096
+ esc_attr( sprintf( "%s + ( $expanded_state_id ? %s : '' )", wp_json_encode( $args['sub_menu_button_class'] ), wp_json_encode( ' ' . $args['sub_menu_button_toggle_class'] ) ) )
1097
+ );
1098
+ $dropdown_button .= sprintf(
1099
+ ' aria-expanded="%s" [aria-expanded]="%s"',
1100
+ esc_attr( wp_json_encode( $expanded ) ),
1101
+ esc_attr( "$expanded_state_id ? 'true' : 'false'" )
1102
+ );
1103
+ $dropdown_button .= sprintf(
1104
+ ' on="%s"',
1105
+ esc_attr( "tap:AMP.setState( { $expanded_state_id: ! $expanded_state_id } )" )
1106
+ );
1107
+ $dropdown_button .= '>';
1108
+
1109
+ if ( isset( $args['icon'] ) ) {
1110
+ $dropdown_button .= $args['icon'];
1111
+ }
1112
+ if ( isset( $args['expand_text'] ) && isset( $args['collapse_text'] ) ) {
1113
+ $dropdown_button .= sprintf(
1114
+ '<span class="screen-reader-text" [text]="%s">%s</span>',
1115
+ esc_attr( sprintf( "$expanded_state_id ? %s : %s", wp_json_encode( $args['collapse_text'] ), wp_json_encode( $args['expand_text'] ) ) ),
1116
+ esc_html( $expanded ? $args['collapse_text'] : $args['expand_text'] )
1117
+ );
1118
+ }
1119
+
1120
+ $dropdown_button .= '</button>';
1121
+
1122
+ $item_output .= $dropdown_button;
1123
+ return $item_output;
1124
+ }, 10, 4 );
1125
+ }
1126
+
1127
+ /**
1128
+ * Output image styles for twentynineteen.
1129
+ *
1130
+ * When <img> tags have an 'aligncenter' class, AMP_Img_Sanitizer::handle_centering() wraps theme in <figure class="aligncenter">.
1131
+ * This ensures that the image inside it is centered.
1132
+ *
1133
+ * @since 1.0
1134
+ *
1135
+ * @param array $args Arguments.
1136
+ */
1137
+ public static function add_twentynineteen_image_styles( $args = array() ) {
1138
+ add_action( 'wp_enqueue_scripts', function() use ( $args ) {
1139
+ ob_start();
1140
+ ?>
1141
+ <style>
1142
+ figure.aligncenter {
1143
+ text-align: center
1144
+ }
1145
+ </style>
1146
+ <?php
1147
+ $styles = str_replace( array( '<style>', '</style>' ), '', ob_get_clean() );
1148
+ wp_add_inline_style( get_template() . '-style', $styles );
1149
+ }, 11 );
1150
+ }
1151
+ }
includes/sanitizers/class-amp-embed-sanitizer.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Embed_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Embed_Sanitizer
10
+ *
11
+ * Calls sanitize_raw_embeds method on embed handlers.
12
+ */
13
+ class AMP_Embed_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Embed handlers.
17
+ *
18
+ * @var AMP_Base_Embed_Handler[] AMP_Base_Embed_Handler[]
19
+ */
20
+ private $embed_handlers = array();
21
+
22
+ /**
23
+ * AMP_Embed_Sanitizer constructor.
24
+ *
25
+ * @param DOMDocument $dom DOM.
26
+ * @param array $args Args.
27
+ */
28
+ public function __construct( $dom, $args = array() ) {
29
+ parent::__construct( $dom, $args );
30
+
31
+ if ( ! empty( $this->args['embed_handlers'] ) ) {
32
+ $this->embed_handlers = $this->args['embed_handlers'];
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Checks if each embed_handler has sanitize_raw_method and calls it.
38
+ */
39
+ public function sanitize() {
40
+
41
+ foreach ( $this->embed_handlers as $embed_handler ) {
42
+ if ( method_exists( $embed_handler, 'sanitize_raw_embeds' ) ) {
43
+ $embed_handler->sanitize_raw_embeds( $this->dom );
44
+ }
45
+ }
46
+ }
47
+ }
includes/sanitizers/class-amp-form-sanitizer.php CHANGED
@@ -24,6 +24,17 @@ class AMP_Form_Sanitizer extends AMP_Base_Sanitizer {
24
  */
25
  public static $tag = 'form';
26
 
 
 
 
 
 
 
 
 
 
 
 
27
  /**
28
  * Sanitize the <form> elements from the HTML contained in this instance's DOMDocument.
29
  *
@@ -67,6 +78,10 @@ class AMP_Form_Sanitizer extends AMP_Base_Sanitizer {
67
  $action_url = esc_url_raw( '//' . $_SERVER['HTTP_HOST'] . wp_unslash( $_SERVER['REQUEST_URI'] ) ); // WPCS: ignore. input var okay, sanitization ok.
68
  } else {
69
  $action_url = $node->getAttribute( 'action' );
 
 
 
 
70
  }
71
  $xhr_action = $node->getAttribute( 'action-xhr' );
72
 
24
  */
25
  public static $tag = 'form';
26
 
27
+ /**
28
+ * Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
29
+ *
30
+ * @return array Mapping.
31
+ */
32
+ public function get_selector_conversion_mapping() {
33
+ return array(
34
+ 'form' => array( 'amp-form' ),
35
+ );
36
+ }
37
+
38
  /**
39
  * Sanitize the <form> elements from the HTML contained in this instance's DOMDocument.
40
  *
78
  $action_url = esc_url_raw( '//' . $_SERVER['HTTP_HOST'] . wp_unslash( $_SERVER['REQUEST_URI'] ) ); // WPCS: ignore. input var okay, sanitization ok.
79
  } else {
80
  $action_url = $node->getAttribute( 'action' );
81
+ // Check if action_url is a relative path and add the host to it.
82
+ if ( ! preg_match( '#^(https?:)?//#', $action_url ) ) {
83
+ $action_url = esc_url_raw( '//' . $_SERVER['HTTP_HOST'] . $action_url );
84
+ }
85
  }
86
  $xhr_action = $node->getAttribute( 'action-xhr' );
87
 
includes/sanitizers/class-amp-gallery-block-sanitizer.php ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Gallery_Block_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Gallery_Block_Sanitizer
10
+ *
11
+ * Modifies gallery block to match the block's AMP-specific configuration.
12
+ */
13
+ class AMP_Gallery_Block_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Value used for width of amp-carousel.
17
+ *
18
+ * @since 1.0
19
+ *
20
+ * @const int
21
+ */
22
+ const FALLBACK_WIDTH = 600;
23
+
24
+ /**
25
+ * Value used for height of amp-carousel.
26
+ *
27
+ * @since 1.0
28
+ *
29
+ * @const int
30
+ */
31
+ const FALLBACK_HEIGHT = 480;
32
+
33
+ /**
34
+ * Tag.
35
+ *
36
+ * @since 1.0
37
+ *
38
+ * @var string Ul tag to identify wrapper around gallery block.
39
+ */
40
+ public static $tag = 'ul';
41
+
42
+ /**
43
+ * Expected class of the wrapper around the gallery block.
44
+ *
45
+ * @since 1.0
46
+ *
47
+ * @var string
48
+ */
49
+ public static $class = 'wp-block-gallery';
50
+
51
+ /**
52
+ * Array of flags used to control sanitization.
53
+ *
54
+ * @var array {
55
+ * @type int $content_max_width Max width of content.
56
+ * @type bool $carousel_required Whether carousels are required. This is used when amp theme support is not present, for back-compat.
57
+ * }
58
+ */
59
+ protected $args;
60
+
61
+ /**
62
+ * Default args.
63
+ *
64
+ * @var array
65
+ */
66
+ protected $DEFAULT_ARGS = array(
67
+ 'carousel_required' => false,
68
+ );
69
+
70
+ /**
71
+ * Sanitize the gallery block contained by <ul> element where necessary.
72
+ *
73
+ * @since 0.2
74
+ */
75
+ public function sanitize() {
76
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
77
+ $num_nodes = $nodes->length;
78
+ if ( 0 === $num_nodes ) {
79
+ return;
80
+ }
81
+
82
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
83
+ $node = $nodes->item( $i );
84
+
85
+ // We're looking for <ul> elements that have at least one child and the proper class.
86
+ if ( 0 === count( $node->childNodes ) || false === strpos( $node->getAttribute( 'class' ), self::$class ) ) {
87
+ continue;
88
+ }
89
+
90
+ $attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
91
+ $is_amp_lightbox = isset( $attributes['data-amp-lightbox'] ) && true === filter_var( $attributes['data-amp-lightbox'], FILTER_VALIDATE_BOOLEAN );
92
+ $is_amp_carousel = ! empty( $this->args['carousel_required'] ) || ( isset( $attributes['data-amp-carousel'] ) && true === filter_var( $attributes['data-amp-carousel'], FILTER_VALIDATE_BOOLEAN ) );
93
+
94
+ // We are only looking for <ul> elements which have amp-carousel / amp-lightbox true.
95
+ if ( ! $is_amp_carousel && ! $is_amp_lightbox ) {
96
+ continue;
97
+ }
98
+
99
+ // If lightbox is set, we should add lightbox feature to the gallery images.
100
+ if ( $is_amp_lightbox ) {
101
+ $this->add_lightbox_attributes_to_image_nodes( $node );
102
+ $this->maybe_add_amp_image_lightbox_node();
103
+ }
104
+
105
+ // If amp-carousel is not set, nothing else to do here.
106
+ if ( ! $is_amp_carousel ) {
107
+ continue;
108
+ }
109
+
110
+ $images = array();
111
+
112
+ // If it's not AMP lightbox, look for links first.
113
+ if ( ! $is_amp_lightbox ) {
114
+ foreach ( $node->getElementsByTagName( 'a' ) as $element ) {
115
+ $images[] = $element;
116
+ }
117
+ }
118
+
119
+ // If not linking to anything then look for <amp-img>.
120
+ if ( empty( $images ) ) {
121
+ foreach ( $node->getElementsByTagName( 'amp-img' ) as $element ) {
122
+ $images[] = $element;
123
+ }
124
+ }
125
+
126
+ // Skip if no images found.
127
+ if ( empty( $images ) ) {
128
+ continue;
129
+ }
130
+
131
+ $amp_carousel = AMP_DOM_Utils::create_node( $this->dom, 'amp-carousel', array(
132
+ 'height' => $this->get_carousel_height( $node ),
133
+ 'type' => 'slides',
134
+ 'layout' => 'fixed-height',
135
+ ) );
136
+ foreach ( $images as $image ) {
137
+ $amp_carousel->appendChild( $image );
138
+ }
139
+
140
+ $node->parentNode->replaceChild( $amp_carousel, $node );
141
+ }
142
+ $this->did_convert_elements = true;
143
+ }
144
+
145
+ /**
146
+ * Get carousel height by containing images.
147
+ *
148
+ * @param DOMElement $element The UL element.
149
+ * @return int Height.
150
+ */
151
+ protected function get_carousel_height( $element ) {
152
+ $images = $element->getElementsByTagName( 'amp-img' );
153
+ $num_images = $images->length;
154
+ $max_height = 0;
155
+ $max_width = 0;
156
+ if ( 0 === $num_images ) {
157
+ return self::FALLBACK_HEIGHT;
158
+ }
159
+ foreach ( $images as $image ) {
160
+ /**
161
+ * Image.
162
+ *
163
+ * @var DOMElement $image
164
+ */
165
+ $image_height = $image->getAttribute( 'height' );
166
+ if ( is_numeric( $image_height ) ) {
167
+ $max_height = max( $max_height, $image_height );
168
+ }
169
+ $image_width = $image->getAttribute( 'height' );
170
+ if ( is_numeric( $image_width ) ) {
171
+ $max_width = max( $max_width, $image_width );
172
+ }
173
+ }
174
+
175
+ if ( ! empty( $this->args['content_max_width'] ) && $max_height > 0 && $max_width > $this->args['content_max_width'] ) {
176
+ $max_height = ( $max_width * $this->args['content_max_width'] ) / $max_height;
177
+ }
178
+
179
+ return ! $max_height ? self::FALLBACK_HEIGHT : $max_height;
180
+ }
181
+
182
+ /**
183
+ * Set lightbox related attributes to <amp-img> within gallery.
184
+ *
185
+ * @param DOMElement $element The UL element.
186
+ */
187
+ protected function add_lightbox_attributes_to_image_nodes( $element ) {
188
+ $images = $element->getElementsByTagName( 'amp-img' );
189
+ $num_images = $images->length;
190
+ if ( 0 === $num_images ) {
191
+ return;
192
+ }
193
+ $attributes = array(
194
+ 'data-amp-lightbox' => '',
195
+ 'on' => 'tap:' . self::AMP_IMAGE_LIGHTBOX_ID,
196
+ 'role' => 'button',
197
+ 'tabindex' => 0,
198
+ );
199
+
200
+ for ( $j = $num_images - 1; $j >= 0; $j-- ) {
201
+ $image_node = $images->item( $j );
202
+ foreach ( $attributes as $att => $value ) {
203
+ $image_node->setAttribute( $att, $value );
204
+ }
205
+ }
206
+ }
207
+ }
includes/sanitizers/class-amp-iframe-sanitizer.php CHANGED
@@ -48,6 +48,19 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
48
  'add_placeholder' => false,
49
  );
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  /**
52
  * Sanitize the <iframe> elements from the HTML contained in this instance's DOMDocument.
53
  *
@@ -62,9 +75,7 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
62
 
63
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
64
  $node = $nodes->item( $i );
65
- $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
66
-
67
- $new_attributes = $this->filter_attributes( $old_attributes );
68
 
69
  /**
70
  * If the src doesn't exist, remove the node. Either it never
@@ -73,44 +84,32 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
73
  * @todo: add a filter to allow for a fallback element in this instance.
74
  * @see: https://github.com/ampproject/amphtml/issues/2261
75
  */
76
- if ( empty( $new_attributes['src'] ) ) {
77
  $this->remove_invalid_child( $node );
78
  continue;
79
  }
80
 
81
  $this->did_convert_elements = true;
82
- $new_attributes = $this->set_layout( $new_attributes );
83
- if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['width'] ) && ! empty( $new_attributes['height'] ) ) {
84
- $new_attributes['layout'] = 'intrinsic';
85
- $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-enforced-sizes' );
86
  }
87
 
88
- $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-iframe', $new_attributes );
89
 
90
  if ( true === $this->args['add_placeholder'] ) {
91
- $placeholder_node = $this->build_placeholder( $new_attributes );
92
  $new_node->appendChild( $placeholder_node );
93
  }
94
 
95
- $parent_node = $node->parentNode;
96
- if ( 'p' !== strtolower( $parent_node->tagName ) ) {
97
- $parent_node->replaceChild( $new_node, $node );
98
- } else {
99
- // AMP does not like iframes in <p> tags.
100
- $parent_node->removeChild( $node );
101
- $parent_node->parentNode->insertBefore( $new_node, $parent_node->nextSibling );
102
-
103
- if ( AMP_DOM_Utils::is_node_empty( $parent_node ) ) {
104
- $parent_node->parentNode->removeChild( $parent_node );
105
- }
106
- }
107
  }
108
  }
109
 
110
  /**
111
- * "Filter" HTML attributes for <amp-iframe> elements.
112
  *
113
- * @since 0.2
114
  *
115
  * @param string[] $attributes {
116
  * Attributes.
@@ -121,23 +120,18 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
121
  * @type string $sandbox <iframe> `sandbox` attribute - Pass along if found; default to value of self::SANDBOX_DEFAULTS
122
  * @type string $class <iframe> `class` attribute - Pass along if found
123
  * @type string $sizes <iframe> `sizes` attribute - Pass along if found
 
124
  * @type int $frameborder <iframe> `frameborder` attribute - Filter to '0' or '1'; default to '0'
125
  * @type bool $allowfullscreen <iframe> `allowfullscreen` attribute - Convert 'false' to empty string ''
126
  * @type bool $allowtransparency <iframe> `allowtransparency` attribute - Convert 'false' to empty string ''
127
  * }
128
- * @return array Returns HTML attributes; removes any not specifically declared above from input.
129
  */
130
- private function filter_attributes( $attributes ) {
131
  $out = array();
132
 
133
  foreach ( $attributes as $name => $value ) {
134
  switch ( $name ) {
135
- case 'sandbox':
136
- case 'class':
137
- case 'sizes':
138
- $out[ $name ] = $value;
139
- break;
140
-
141
  case 'src':
142
  $out[ $name ] = $this->maybe_enforce_https_src( $value, true );
143
  break;
@@ -162,6 +156,7 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
162
  break;
163
 
164
  default:
 
165
  break;
166
  }
167
  }
@@ -176,6 +171,9 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
176
  /**
177
  * Builds a DOMElement to use as a placeholder for an <iframe>.
178
  *
 
 
 
179
  * @since 0.2
180
  *
181
  * @param string[] $parent_attributes {
@@ -187,9 +185,9 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
187
  * @return DOMElement|false
188
  */
189
  private function build_placeholder( $parent_attributes ) {
190
- $placeholder_node = AMP_DOM_Utils::create_node( $this->dom, 'div', array(
191
  'placeholder' => '',
192
- 'class' => 'amp-wp-iframe-placeholder',
193
  ) );
194
 
195
  return $placeholder_node;
48
  'add_placeholder' => false,
49
  );
50
 
51
+ /**
52
+ * Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
53
+ *
54
+ * @return array Mapping.
55
+ */
56
+ public function get_selector_conversion_mapping() {
57
+ return array(
58
+ 'iframe' => array(
59
+ 'amp-iframe',
60
+ ),
61
+ );
62
+ }
63
+
64
  /**
65
  * Sanitize the <iframe> elements from the HTML contained in this instance's DOMDocument.
66
  *
75
 
76
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
77
  $node = $nodes->item( $i );
78
+ $normalized_attributes = $this->normalize_attributes( AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node ) );
 
 
79
 
80
  /**
81
  * If the src doesn't exist, remove the node. Either it never
84
  * @todo: add a filter to allow for a fallback element in this instance.
85
  * @see: https://github.com/ampproject/amphtml/issues/2261
86
  */
87
+ if ( empty( $normalized_attributes['src'] ) ) {
88
  $this->remove_invalid_child( $node );
89
  continue;
90
  }
91
 
92
  $this->did_convert_elements = true;
93
+ $normalized_attributes = $this->set_layout( $normalized_attributes );
94
+ if ( empty( $normalized_attributes['layout'] ) && ! empty( $normalized_attributes['width'] ) && ! empty( $normalized_attributes['height'] ) ) {
95
+ $normalized_attributes['layout'] = 'intrinsic';
96
+ $this->add_or_append_attribute( $normalized_attributes, 'class', 'amp-wp-enforced-sizes' );
97
  }
98
 
99
+ $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-iframe', $normalized_attributes );
100
 
101
  if ( true === $this->args['add_placeholder'] ) {
102
+ $placeholder_node = $this->build_placeholder( $normalized_attributes );
103
  $new_node->appendChild( $placeholder_node );
104
  }
105
 
106
+ $node->parentNode->replaceChild( $new_node, $node );
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
  }
109
 
110
  /**
111
+ * Normalize HTML attributes for <amp-iframe> elements.
112
  *
 
113
  *
114
  * @param string[] $attributes {
115
  * Attributes.
120
  * @type string $sandbox <iframe> `sandbox` attribute - Pass along if found; default to value of self::SANDBOX_DEFAULTS
121
  * @type string $class <iframe> `class` attribute - Pass along if found
122
  * @type string $sizes <iframe> `sizes` attribute - Pass along if found
123
+ * @type string $id <iframe> `id` attribute - Pass along if found
124
  * @type int $frameborder <iframe> `frameborder` attribute - Filter to '0' or '1'; default to '0'
125
  * @type bool $allowfullscreen <iframe> `allowfullscreen` attribute - Convert 'false' to empty string ''
126
  * @type bool $allowtransparency <iframe> `allowtransparency` attribute - Convert 'false' to empty string ''
127
  * }
128
+ * @return array Returns HTML attributes; normalizes src, dimensions, frameborder, sandox, allowtransparency and allowfullscreen
129
  */
130
+ private function normalize_attributes( $attributes ) {
131
  $out = array();
132
 
133
  foreach ( $attributes as $name => $value ) {
134
  switch ( $name ) {
 
 
 
 
 
 
135
  case 'src':
136
  $out[ $name ] = $this->maybe_enforce_https_src( $value, true );
137
  break;
156
  break;
157
 
158
  default:
159
+ $out[ $name ] = $value;
160
  break;
161
  }
162
  }
171
  /**
172
  * Builds a DOMElement to use as a placeholder for an <iframe>.
173
  *
174
+ * Important: The element returned must not be block-level (e.g. div) as the PHP DOM parser
175
+ * will move it out from inside any containing paragraph. So this is why a span is used.
176
+ *
177
  * @since 0.2
178
  *
179
  * @param string[] $parent_attributes {
185
  * @return DOMElement|false
186
  */
187
  private function build_placeholder( $parent_attributes ) {
188
+ $placeholder_node = AMP_DOM_Utils::create_node( $this->dom, 'span', array(
189
  'placeholder' => '',
190
+ 'class' => 'amp-wp-iframe-placeholder',
191
  ) );
192
 
193
  return $placeholder_node;
includes/sanitizers/class-amp-img-sanitizer.php CHANGED
@@ -46,6 +46,20 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
46
  */
47
  private static $anim_extension = '.gif';
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  /**
50
  * Sanitize the <img> elements from the HTML contained in this instance's DOMDocument.
51
  *
@@ -115,15 +129,6 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
115
 
116
  foreach ( $attributes as $name => $value ) {
117
  switch ( $name ) {
118
- case 'src':
119
- case 'alt':
120
- case 'class':
121
- case 'srcset':
122
- case 'on':
123
- case 'attribution':
124
- $out[ $name ] = $value;
125
- break;
126
-
127
  case 'width':
128
  case 'height':
129
  $out[ $name ] = $this->sanitize_dimension( $value, $name );
@@ -133,7 +138,12 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
133
  $out['layout'] = $value;
134
  break;
135
 
 
 
 
 
136
  default:
 
137
  break;
138
  }
139
  }
@@ -219,15 +229,24 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
219
  /**
220
  * Make final modifications to DOMNode
221
  *
222
- * @param DOMNode $node The DOMNode to adjust and replace.
223
  */
224
  private function adjust_and_replace_node( $node ) {
 
 
225
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
 
 
 
226
  $new_attributes = $this->filter_attributes( $old_attributes );
 
 
 
227
  $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-enforced-sizes' );
228
  if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['height'] ) && ! empty( $new_attributes['width'] ) ) {
229
  $new_attributes['layout'] = 'intrinsic';
230
  }
 
231
  if ( $this->is_gif_url( $new_attributes['src'] ) ) {
232
  $this->did_convert_elements = true;
233
 
@@ -238,6 +257,34 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
238
  $new_node = AMP_DOM_Utils::create_node( $this->dom, $new_tag, $new_attributes );
239
  $new_node = $this->handle_centering( $new_node );
240
  $node->parentNode->replaceChild( $new_node, $node );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  }
242
 
243
  /**
@@ -251,7 +298,7 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
251
  */
252
  private function is_gif_url( $url ) {
253
  $ext = self::$anim_extension;
254
- $path = AMP_WP_Utils::parse_url( $url, PHP_URL_PATH );
255
  return substr( $path, -strlen( $ext ) ) === $ext;
256
  }
257
 
@@ -263,7 +310,7 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
263
  * So this strips that class, and instead wraps the image in a <figure> to center it.
264
  *
265
  * @since 0.7
266
- * @see https://github.com/Automattic/amp-wp/issues/1104
267
  *
268
  * @param DOMElement $node The <amp-img> node.
269
  * @return DOMElement $node The <amp-img> node, possibly wrapped in a <figure>.
@@ -293,4 +340,37 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
293
 
294
  return $figure;
295
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  }
46
  */
47
  private static $anim_extension = '.gif';
48
 
49
+ /**
50
+ * Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
51
+ *
52
+ * @return array Mapping.
53
+ */
54
+ public function get_selector_conversion_mapping() {
55
+ return array(
56
+ 'img' => array(
57
+ 'amp-img',
58
+ 'amp-anim',
59
+ ),
60
+ );
61
+ }
62
+
63
  /**
64
  * Sanitize the <img> elements from the HTML contained in this instance's DOMDocument.
65
  *
129
 
130
  foreach ( $attributes as $name => $value ) {
131
  switch ( $name ) {
 
 
 
 
 
 
 
 
 
132
  case 'width':
133
  case 'height':
134
  $out[ $name ] = $this->sanitize_dimension( $value, $name );
138
  $out['layout'] = $value;
139
  break;
140
 
141
+ case 'data-amp-noloading':
142
+ $out['noloading'] = $value;
143
+ break;
144
+
145
  default:
146
+ $out[ $name ] = $value;
147
  break;
148
  }
149
  }
229
  /**
230
  * Make final modifications to DOMNode
231
  *
232
+ * @param DOMElement $node The DOMNode to adjust and replace.
233
  */
234
  private function adjust_and_replace_node( $node ) {
235
+
236
+ $amp_data = $this->get_data_amp_attributes( $node );
237
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
238
+ $old_attributes = $this->filter_data_amp_attributes( $old_attributes, $amp_data );
239
+ $old_attributes = $this->maybe_add_lightbox_attributes( $old_attributes, $node );
240
+
241
  $new_attributes = $this->filter_attributes( $old_attributes );
242
+ $layout = isset( $amp_data['layout'] ) ? $amp_data['layout'] : false;
243
+ $new_attributes = $this->filter_attachment_layout_attributes( $node, $new_attributes, $layout );
244
+
245
  $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-enforced-sizes' );
246
  if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['height'] ) && ! empty( $new_attributes['width'] ) ) {
247
  $new_attributes['layout'] = 'intrinsic';
248
  }
249
+
250
  if ( $this->is_gif_url( $new_attributes['src'] ) ) {
251
  $this->did_convert_elements = true;
252
 
257
  $new_node = AMP_DOM_Utils::create_node( $this->dom, $new_tag, $new_attributes );
258
  $new_node = $this->handle_centering( $new_node );
259
  $node->parentNode->replaceChild( $new_node, $node );
260
+ $this->add_auto_width_to_figure( $new_node );
261
+ }
262
+
263
+ /**
264
+ * Set lightbox attributes.
265
+ *
266
+ * @param array $attributes Array of attributes.
267
+ * @param DomNode $node Array of AMP attributes.
268
+ * @return array Updated attributes.
269
+ */
270
+ private function maybe_add_lightbox_attributes( $attributes, $node ) {
271
+ $parent_node = $node->parentNode;
272
+ if ( ! ( $parent_node instanceof DOMElement ) || 'figure' !== $parent_node->tagName ) {
273
+ return $attributes;
274
+ }
275
+
276
+ $parent_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $parent_node );
277
+
278
+ if ( isset( $parent_attributes['data-amp-lightbox'] ) && true === filter_var( $parent_attributes['data-amp-lightbox'], FILTER_VALIDATE_BOOLEAN ) ) {
279
+ $attributes['data-amp-lightbox'] = '';
280
+ $attributes['on'] = 'tap:' . self::AMP_IMAGE_LIGHTBOX_ID;
281
+ $attributes['role'] = 'button';
282
+ $attributes['tabindex'] = 0;
283
+
284
+ $this->maybe_add_amp_image_lightbox_node();
285
+ }
286
+
287
+ return $attributes;
288
  }
289
 
290
  /**
298
  */
299
  private function is_gif_url( $url ) {
300
  $ext = self::$anim_extension;
301
+ $path = wp_parse_url( $url, PHP_URL_PATH );
302
  return substr( $path, -strlen( $ext ) ) === $ext;
303
  }
304
 
310
  * So this strips that class, and instead wraps the image in a <figure> to center it.
311
  *
312
  * @since 0.7
313
+ * @see https://github.com/ampproject/amp-wp/issues/1104
314
  *
315
  * @param DOMElement $node The <amp-img> node.
316
  * @return DOMElement $node The <amp-img> node, possibly wrapped in a <figure>.
340
 
341
  return $figure;
342
  }
343
+
344
+ /**
345
+ * Add an inline style to set the `<figure>` element's width to `auto` instead of `fit-content`.
346
+ *
347
+ * @since 1.0
348
+ * @see https://github.com/ampproject/amp-wp/issues/1086
349
+ *
350
+ * @param DOMElement $node The DOMNode to adjust and replace.
351
+ */
352
+ protected function add_auto_width_to_figure( $node ) {
353
+ $figure = $node->parentNode;
354
+ if ( ! ( $figure instanceof DOMElement ) || 'figure' !== $figure->tagName ) {
355
+ return;
356
+ }
357
+
358
+ $class = $figure->getAttribute( 'class' );
359
+ // Target only the <figure> with a 'wp-block-image' class attribute.
360
+ if ( false === strpos( $class, 'wp-block-image' ) ) {
361
+ return;
362
+ }
363
+
364
+ // Target only <figure> without a 'is-resized' class attribute.
365
+ if ( false !== strpos( $class, 'is-resized' ) ) {
366
+ return;
367
+ }
368
+
369
+ $new_style = 'width: auto;';
370
+ if ( $figure->hasAttribute( 'style' ) ) {
371
+ $figure->setAttribute( 'style', $new_style . $figure->getAttribute( 'style' ) );
372
+ } else {
373
+ $figure->setAttribute( 'style', $new_style );
374
+ }
375
+ }
376
  }
includes/sanitizers/class-amp-o2-player-sanitizer.php ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_O2_Player_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_O2_Player_Sanitizer
10
+ *
11
+ * Converts <div class="vdb_player><script></script></div> embed to <amp-o2-player>
12
+ *
13
+ * @since 1.0
14
+ * @see https://www.ampproject.org/docs/reference/components/amp-o2-player
15
+ */
16
+ class AMP_O2_Player_Sanitizer extends AMP_Base_Sanitizer {
17
+ /**
18
+ * Pattern to extract the information required for amp-o2-player element: data-pid, data-vid, data-bcid.
19
+ *
20
+ * @since 1.0
21
+ */
22
+ const URL_PATTERN = '#.*delivery.vidible.tv\/jsonp\/pid=(?<data_pid>.*)\/vid=(?<data_vid>.*)\/(?<data_bcid>.*).js.*#i';
23
+
24
+ /**
25
+ * AMP Tag.
26
+ *
27
+ * @since 1.0
28
+ * @var string AMP Tag.
29
+ */
30
+ private static $amp_tag = 'amp-o2-player';
31
+
32
+ /**
33
+ * Amp O2 Player class.
34
+ *
35
+ * @since 1.0
36
+ * @var string CSS class to identify O2 Player <div> to replace with AMP version.
37
+ */
38
+ private static $xpath_selector = '//div[ contains( @class, \'vdb_player\' ) ]/script';
39
+
40
+ /**
41
+ * Height to set for O2 Player elements.
42
+ *
43
+ * @since 1.0
44
+ * @var string
45
+ */
46
+ private static $height = '270';
47
+
48
+ /**
49
+ * Width to set for O2 Player elements.
50
+ *
51
+ * @since 1.0
52
+ * @var string
53
+ */
54
+ private static $width = '480';
55
+
56
+ /**
57
+ * Sanitize the O2 Player elements from the HTML contained in this instance's DOMDocument.
58
+ *
59
+ * @since 1.0
60
+ */
61
+ public function sanitize() {
62
+ /**
63
+ * XPath.
64
+ *
65
+ * @var DOMXPath $xpath
66
+ */
67
+ $xpath = new DOMXPath( $this->dom );
68
+
69
+ /**
70
+ * Node list.
71
+ *
72
+ * @var DOMNodeList $nodes
73
+ */
74
+ $nodes = $xpath->query( self::$xpath_selector );
75
+ $num_nodes = $nodes->length;
76
+
77
+ if ( 0 === $num_nodes ) {
78
+ return;
79
+ }
80
+
81
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
82
+ $node = $nodes->item( $i );
83
+
84
+ $this->create_amp_o2_player( $this->dom, $node );
85
+ }
86
+
87
+ }
88
+
89
+ /**
90
+ * Replaces node with amp-o2-player
91
+ *
92
+ * @since 1.0
93
+ * @param DOMDocument $dom The HTML Document.
94
+ * @param DOMElement $node The DOMNode to adjust and replace.
95
+ */
96
+ private function create_amp_o2_player( $dom, $node ) {
97
+ $o2_attributes = $this->get_o2_player_attributes( $node->getAttribute( 'src' ) );
98
+
99
+ if ( ! empty( $o2_attributes ) ) {
100
+ $component_attributes = array_merge(
101
+ $o2_attributes, array(
102
+ 'data-macros' => 'm.playback=click',
103
+ 'layout' => 'responsive',
104
+ 'width' => self::$width,
105
+ 'height' => self::$height,
106
+ )
107
+ );
108
+
109
+ $amp_o2_player = AMP_DOM_Utils::create_node( $dom, self::$amp_tag, $component_attributes );
110
+
111
+ $parent_node = $node->parentNode;
112
+
113
+ // replaces the wrapper that contains the script with amp-o2-player element.
114
+ $parent_node->parentNode->replaceChild( $amp_o2_player, $parent_node );
115
+
116
+ $this->did_convert_elements = true;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Gets O2 Player's required attributes from script src
122
+ *
123
+ * @since 1.0
124
+ * @param string $src Script src.
125
+ *
126
+ * @return array The data-* attributes for o2 player.
127
+ */
128
+ private function get_o2_player_attributes( $src ) {
129
+ $found = preg_match( self::URL_PATTERN, $src, $matches );
130
+ if ( $found ) {
131
+ return array(
132
+ 'data-pid' => $matches['data_pid'],
133
+ 'data-vid' => $matches['data_vid'],
134
+ 'data-bcid' => $matches['data_bcid'],
135
+ );
136
+ }
137
+ return array();
138
+ }
139
+
140
+ }
includes/sanitizers/class-amp-playbuzz-sanitizer.php CHANGED
@@ -40,6 +40,17 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
40
  */
41
  private static $height = '500';
42
 
 
 
 
 
 
 
 
 
 
 
 
43
  /**
44
  * Sanitize the Playbuzz elements from the HTML contained in this instance's DOMDocument.
45
  *
@@ -113,16 +124,14 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
113
  }
114
  break;
115
 
116
- case 'data-game-info':
117
- $out['data-item-info'] = $value;
118
- break;
119
-
120
  case 'data-shares':
121
  $out['data-share-buttons'] = $value;
122
  break;
123
 
 
124
  case 'data-comments':
125
- $out['data-comments'] = $value;
 
126
  break;
127
 
128
  default:
40
  */
41
  private static $height = '500';
42
 
43
+ /**
44
+ * Get mapping of HTML selectors to the AMP component selectors which they may be converted into.
45
+ *
46
+ * @return array Mapping.
47
+ */
48
+ public function get_selector_conversion_mapping() {
49
+ return array(
50
+ 'div.pb_feed' => array( 'amp-playbuzz.pb_feed' ),
51
+ );
52
+ }
53
+
54
  /**
55
  * Sanitize the Playbuzz elements from the HTML contained in this instance's DOMDocument.
56
  *
124
  }
125
  break;
126
 
 
 
 
 
127
  case 'data-shares':
128
  $out['data-share-buttons'] = $value;
129
  break;
130
 
131
+ case 'data-game-info':
132
  case 'data-comments':
133
+ case 'class':
134
+ $out[ $name ] = $value;
135
  break;
136
 
137
  default:
includes/sanitizers/class-amp-rule-spec.php CHANGED
@@ -40,7 +40,7 @@ abstract class AMP_Rule_Spec {
40
  */
41
  const ALLOW_EMPTY = 'allow_empty';
42
  const ALLOW_RELATIVE = 'allow_relative';
43
- const ALLOWED_PROTOCOL = 'allowed_protocol';
44
  const ALTERNATIVE_NAMES = 'alternative_names';
45
  const BLACKLISTED_VALUE_REGEX = 'blacklisted_value_regex';
46
  const DISALLOWED_DOMAIN = 'disallowed_domain';
@@ -53,36 +53,21 @@ abstract class AMP_Rule_Spec {
53
  const VALUE_URL = 'value_url';
54
 
55
  /**
56
- * If a node type listed here is invalid, it and it's subtree will be
57
- * removed if it is invalid. This is mainly because any children will be
58
- * non-functional without this parent.
59
- *
60
- * If a tag is not listed here, it will be replaced by its children if it
61
- * is invalid.
62
- *
63
- * @todo There are other nodes that should probably be listed here as well.
64
- *
65
- * @var array
66
- */
67
- public static $node_types_to_remove_if_invalid = array(
68
- 'form',
69
- 'input',
70
- 'link',
71
- 'meta',
72
- 'style',
73
- // Include 'script' here?
74
- );
75
-
76
- /**
77
- * It is mentioned in the documentation in several places that data-*
78
- * is generally allowed, but there is no specific rule for it in the
79
- * protoascii file, so we include it here.
80
  *
 
81
  * @var array
82
  */
83
- public static $whitelisted_attr_regex = array(
84
- '@^data-[a-zA-Z][\\w:.-]*$@uis',
85
- '(update|item|pagination|option|selected|disabled)', // Allowed for live reference points.
 
 
 
 
 
 
 
86
  );
87
 
88
  /**
40
  */
41
  const ALLOW_EMPTY = 'allow_empty';
42
  const ALLOW_RELATIVE = 'allow_relative';
43
+ const ALLOWED_PROTOCOL = 'protocol';
44
  const ALTERNATIVE_NAMES = 'alternative_names';
45
  const BLACKLISTED_VALUE_REGEX = 'blacklisted_value_regex';
46
  const DISALLOWED_DOMAIN = 'disallowed_domain';
53
  const VALUE_URL = 'value_url';
54
 
55
  /**
56
+ * Supported layout values.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  *
58
+ * @since 1.0
59
  * @var array
60
  */
61
+ public static $layout_enum = array(
62
+ 1 => 'nodisplay',
63
+ 2 => 'fixed',
64
+ 3 => 'fixed-height',
65
+ 4 => 'responsive',
66
+ 5 => 'container',
67
+ 6 => 'fill',
68
+ 7 => 'flex-item',
69
+ 8 => 'fluid',
70
+ 9 => 'intrinsic',
71
  );
72
 
73
  /**
includes/sanitizers/class-amp-script-sanitizer.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Script_Sanitizer
4
+ *
5
+ * @since 1.0
6
+ * @package AMP
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Script_Sanitizer
11
+ *
12
+ * @since 1.0
13
+ */
14
+ class AMP_Script_Sanitizer extends AMP_Base_Sanitizer {
15
+
16
+ /**
17
+ * Sanitize noscript elements.
18
+ *
19
+ * Eventually this should also handle script elements, if there is a known AMP equivalent.
20
+ * If nothing is done with script elements, the whitelist sanitizer will deal with them ultimately.
21
+ *
22
+ * @todo Eventually this try to automatically convert script tags to AMP when they are recognized. See <https://github.com/ampproject/amp-wp/issues/1032>.
23
+ * @todo When a script has an adjacent noscript, consider removing the script here to prevent validation error later. See <https://github.com/ampproject/amp-wp/issues/1213>.
24
+ *
25
+ * @since 1.0
26
+ */
27
+ public function sanitize() {
28
+ $noscripts = $this->dom->getElementsByTagName( 'noscript' );
29
+
30
+ for ( $i = $noscripts->length - 1; $i >= 0; $i-- ) {
31
+ $noscript = $noscripts->item( $i );
32
+
33
+ // Skip AMP boilerplate.
34
+ if ( $noscript->firstChild instanceof DOMElement && $noscript->firstChild->hasAttribute( 'amp-boilerplate' ) ) {
35
+ continue;
36
+ }
37
+
38
+ $fragment = $this->dom->createDocumentFragment();
39
+ $fragment->appendChild( $this->dom->createComment( 'noscript' ) );
40
+ while ( $noscript->firstChild ) {
41
+ $fragment->appendChild( $noscript->firstChild );
42
+ }
43
+ $fragment->appendChild( $this->dom->createComment( '/noscript' ) );
44
+ $noscript->parentNode->replaceChild( $fragment, $noscript );
45
+
46
+ $this->did_convert_elements = true;
47
+ }
48
+ }
49
+ }
includes/sanitizers/class-amp-style-sanitizer.php CHANGED
@@ -5,6 +5,19 @@
5
  * @package AMP
6
  */
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  /**
9
  * Class AMP_Style_Sanitizer
10
  *
@@ -13,49 +26,93 @@
13
  class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
14
 
15
  /**
16
- * Styles.
17
  *
18
- * List of CSS styles in HTML content of DOMDocument ($this->dom).
 
 
 
 
 
19
  *
20
- * @since 0.4
21
- * @var array[]
22
  */
23
- private $styles = array();
24
 
25
  /**
26
- * Stylesheets.
27
  *
28
- * Values are the CSS stylesheets. Keys are MD5 hashes of the stylesheets
 
 
 
 
 
29
  *
30
- * @since 0.7
31
- * @var string[]
 
 
 
 
 
 
 
 
 
32
  */
33
- private $stylesheets = array();
34
 
35
  /**
36
- * Maximum number of bytes allowed for a keyframes style.
37
  *
38
- * @since 0.7
39
- * @var int
40
  */
41
- private $keyframes_max_size;
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  /**
44
- * Maximum number of bytes allowed for a AMP Custom style.
 
 
45
  *
46
  * @since 0.7
47
- * @var int
48
  */
49
- private $custom_max_size;
50
 
51
  /**
52
- * Current CSS size.
53
  *
54
- * Sum of CSS located in $styles and $stylesheets.
55
  *
56
- * @var int
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  */
58
- private $current_custom_size = 0;
59
 
60
  /**
61
  * The style[amp-custom] element.
@@ -64,6 +121,14 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
64
  */
65
  private $amp_custom_style_element;
66
 
 
 
 
 
 
 
 
 
67
  /**
68
  * Regex for allowed font stylesheet URL.
69
  *
@@ -87,6 +152,104 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
87
  */
88
  private $content_url;
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  /**
91
  * AMP_Base_Sanitizer constructor.
92
  *
@@ -98,19 +261,14 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
98
  public function __construct( DOMDocument $dom, array $args = array() ) {
99
  parent::__construct( $dom, $args );
100
 
101
- $spec_name = 'style[amp-keyframes]';
102
  foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'style' ) as $spec_rule ) {
103
- if ( isset( $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) && $spec_name === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) {
104
- $this->keyframes_max_size = $spec_rule[ AMP_Rule_Spec::CDATA ]['max_bytes'];
105
- break;
106
  }
107
- }
108
-
109
- $spec_name = 'style amp-custom';
110
- foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'style' ) as $spec_rule ) {
111
- if ( isset( $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) && $spec_name === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) {
112
- $this->custom_max_size = $spec_rule[ AMP_Rule_Spec::CDATA ]['max_bytes'];
113
- break;
114
  }
115
  }
116
 
@@ -128,20 +286,19 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
128
  }
129
  $this->base_url = $guessurl;
130
  $this->content_url = WP_CONTENT_URL;
 
131
  }
132
 
133
  /**
134
  * Get list of CSS styles in HTML content of DOMDocument ($this->dom).
135
  *
136
  * @since 0.4
 
137
  *
138
  * @return array[] Mapping CSS selectors to array of properties, or mapping of keys starting with 'stylesheet:' with value being the stylesheet.
139
  */
140
  public function get_styles() {
141
- if ( ! $this->did_convert_elements ) {
142
- return array();
143
- }
144
- return $this->styles;
145
  }
146
 
147
  /**
@@ -151,7 +308,80 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
151
  * @returns array Values are the CSS stylesheets. Keys are MD5 hashes of the stylesheets.
152
  */
153
  public function get_stylesheets() {
154
- return array_merge( $this->stylesheets, parent::get_stylesheets() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
 
157
  /**
@@ -167,11 +397,19 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
167
  return;
168
  }
169
 
 
 
 
 
 
 
 
 
170
  /*
171
  * Note that xpath is used to query the DOM so that the link and style elements will be
172
  * in document order. DOMNode::compareDocumentPosition() is not yet implemented.
173
  */
174
- $xpath = new DOMXPath( $this->dom );
175
 
176
  $lower_case = 'translate( %s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz" )'; // In XPath 2.0 this is lower-case().
177
  $predicates = array(
@@ -183,6 +421,28 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
183
  $elements[] = $element;
184
  }
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  /**
187
  * Element.
188
  *
@@ -204,83 +464,113 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
204
  foreach ( $elements as $element ) {
205
  $this->collect_inline_styles( $element );
206
  }
207
- $this->did_convert_elements = true;
208
 
209
- // Now make sure the amp-custom style is in the DOM and populated, if we're working with the document element.
210
- if ( ! empty( $this->args['use_document_element'] ) ) {
211
- if ( ! $this->amp_custom_style_element ) {
212
- $this->amp_custom_style_element = $this->dom->createElement( 'style' );
213
- $this->amp_custom_style_element->setAttribute( 'amp-custom', '' );
214
- $head = $this->dom->getElementsByTagName( 'head' )->item( 0 );
215
- if ( ! $head ) {
216
- $head = $this->dom->createElement( 'head' );
217
- $this->dom->documentElement->insertBefore( $head, $this->dom->documentElement->firstChild );
218
- }
219
- $head->appendChild( $this->amp_custom_style_element );
220
- }
221
 
222
- $css = implode( '', $this->get_stylesheets() );
223
 
224
- /*
225
- * Let the style[amp-custom] be populated with the concatenated CSS.
226
- * !important: Updating the contents of this style element by setting textContent is not
227
- * reliable across PHP/libxml versions, so this is why the children are removed and the
228
- * text node is then explicitly added containing the CSS.
229
- */
230
- while ( $this->amp_custom_style_element->firstChild ) {
231
- $this->amp_custom_style_element->removeChild( $this->amp_custom_style_element->firstChild );
232
- }
233
- $this->amp_custom_style_element->appendChild( $this->dom->createTextNode( $css ) );
234
  }
235
  }
236
 
237
  /**
238
- * Generates an enqueued style's fully-qualified file path.
239
  *
240
  * @since 0.7
241
  * @see WP_Styles::_css_href()
242
  *
243
- * @param string $src The source URL of the enqueued style.
 
244
  * @return string|WP_Error Style's absolute validated filesystem path, or WP_Error when error.
245
  */
246
- public function get_validated_css_file_path( $src ) {
247
  $needs_base_url = (
248
- ! is_bool( $src )
249
  &&
250
- ! preg_match( '|^(https?:)?//|', $src )
251
  &&
252
- ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) )
253
  );
254
  if ( $needs_base_url ) {
255
- $src = $this->base_url . $src;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  }
257
 
258
- // Strip query and fragment from URL.
259
- $src = preg_replace( ':[\?#].*$:', '', $src );
 
 
260
 
261
- if ( ! preg_match( '/\.(css|less|scss|sass)$/i', $src ) ) {
262
- /* translators: %s is stylesheet URL */
263
- return new WP_Error( 'amp_css_bad_file_extension', sprintf( __( 'Skipped stylesheet which does not have recognized CSS file extension (%s).', 'amp' ), $src ) );
 
 
 
 
 
 
 
 
264
  }
265
 
266
- $includes_url = includes_url( '/' );
267
- $content_url = content_url( '/' );
268
- $admin_url = get_admin_url( null, '/' );
269
- $css_path = null;
270
- if ( 0 === strpos( $src, $content_url ) ) {
271
- $css_path = WP_CONTENT_DIR . substr( $src, strlen( $content_url ) - 1 );
272
- } elseif ( 0 === strpos( $src, $includes_url ) ) {
273
- $css_path = ABSPATH . WPINC . substr( $src, strlen( $includes_url ) - 1 );
274
- } elseif ( 0 === strpos( $src, $admin_url ) ) {
275
- $css_path = ABSPATH . 'wp-admin' . substr( $src, strlen( $admin_url ) - 1 );
276
  }
 
 
 
 
 
 
 
277
 
278
- if ( ! $css_path || false !== strpos( '../', $css_path ) || 0 !== validate_file( $css_path ) || ! file_exists( $css_path ) ) {
279
- /* translators: %s is stylesheet URL */
280
- return new WP_Error( 'amp_css_path_not_found', sprintf( __( 'Unable to locate filesystem path for stylesheet %s.', 'amp' ), $src ) );
 
 
 
 
 
 
281
  }
282
 
283
- return $css_path;
 
 
 
 
 
284
  }
285
 
286
  /**
@@ -289,30 +579,33 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
289
  * @param DOMElement $element Style element.
290
  */
291
  private function process_style_element( DOMElement $element ) {
292
- if ( $element->hasAttribute( 'amp-keyframes' ) ) {
293
- $validity = $this->validate_amp_keyframe( $element );
294
- if ( is_wp_error( $validity ) ) {
295
- $this->remove_invalid_child( $element, array(
296
- 'message' => $validity->get_error_message(),
297
- ) );
298
- }
299
- return;
300
- }
301
 
302
- $rules = trim( $element->textContent );
303
- $rules = $this->remove_illegal_css( $rules, $element );
 
 
304
 
305
- // Remove if surpasses max size.
306
- $length = strlen( $rules );
307
- if ( $this->current_custom_size + $length > $this->custom_max_size ) {
308
- $this->remove_invalid_child( $element, array(
309
- 'message' => __( 'Too much CSS enqueued.', 'amp' ),
310
- ) );
311
- return;
312
  }
313
 
314
- $this->stylesheets[ md5( $rules ) ] = $rules;
315
- $this->current_custom_size += $length;
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
  if ( $element->hasAttribute( 'amp-custom' ) ) {
318
  if ( ! $this->amp_custom_style_element ) {
@@ -325,6 +618,8 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
325
  // Remove from DOM since we'll be adding it to amp-custom.
326
  $element->parentNode->removeChild( $element );
327
  }
 
 
328
  }
329
 
330
  /**
@@ -335,244 +630,1596 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
335
  private function process_link_element( DOMElement $element ) {
336
  $href = $element->getAttribute( 'href' );
337
 
338
- // Allow font URLs.
339
- if ( $this->allowed_font_src_regex && preg_match( $this->allowed_font_src_regex, $href ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  return;
341
  }
342
 
343
- $css_file_path = $this->get_validated_css_file_path( $href );
344
- if ( is_wp_error( $css_file_path ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  $this->remove_invalid_child( $element, array(
 
346
  'message' => $css_file_path->get_error_message(),
 
347
  ) );
348
  return;
 
 
349
  }
350
 
351
- // Load the CSS from the filesystem.
352
- $rules = "\n/* $href */\n";
353
- $rules .= file_get_contents( $css_file_path ); // phpcs:ignore -- It's a local filesystem path not a remote request.
354
-
355
- $rules = $this->remove_illegal_css( $rules, $element );
356
-
357
- $media = $element->getAttribute( 'media' );
358
- if ( $media && 'all' !== $media ) {
359
- $rules = sprintf( '@media %s { %s }', $media, $rules );
360
- }
361
-
362
- // Remove if surpasses max size.
363
- $length = strlen( $rules );
364
- if ( $this->current_custom_size + $length > $this->custom_max_size ) {
365
  $this->remove_invalid_child( $element, array(
366
- 'message' => __( 'Too much CSS enqueued.', 'amp' ),
 
367
  ) );
368
  return;
369
  }
370
 
371
- $this->current_custom_size += $length;
372
- $this->stylesheets[ $href ] = $rules;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
  // Remove now that styles have been processed.
375
  $element->parentNode->removeChild( $element );
376
- }
377
 
378
- /**
379
- * Remove illegal CSS from the stylesheet.
380
- *
381
- * @since 0.7
382
- *
383
- * @todo This needs proper CSS parser and to take an alternative approach to removing !important by extracting
384
- * the rule into a separate style rule with a very specific selector.
385
- * @param string $stylesheet Stylesheet.
386
- * @param DOMElement $element Element where the stylesheet came from.
387
- * @return string Scrubbed stylesheet.
388
- */
389
- private function remove_illegal_css( $stylesheet, $element ) {
390
- $stylesheet = preg_replace( '/\s*!important/', '', $stylesheet, -1, $important_count ); // Note this has to also replace inside comments to be valid.
391
- if ( $important_count > 0 && ! empty( $this->args['validation_error_callback'] ) ) {
392
- call_user_func( $this->args['validation_error_callback'], array(
393
- 'code' => 'css_important_removed',
394
- 'node' => $element,
395
- ) );
396
- }
397
- $stylesheet = preg_replace( '/overflow(-[xy])?\s*:\s*(auto|scroll)\s*;?\s*/', '', $stylesheet, -1, $overlow_count );
398
- if ( $overlow_count > 0 && ! empty( $this->args['validation_error_callback'] ) ) {
399
- call_user_func( $this->args['validation_error_callback'], array(
400
- 'code' => 'css_overflow_property_removed',
401
- 'node' => $element,
402
- ) );
403
- }
404
- return $stylesheet;
405
  }
406
 
407
  /**
408
- * Validate amp-keyframe style.
409
- *
410
- * @since 0.7
411
- * @link https://github.com/ampproject/amphtml/blob/b685a0780a7f59313666225478b2b79b463bcd0b/validator/validator-main.protoascii#L1002-L1043
412
  *
413
- * @param DOMElement $style Style element.
414
- * @return true|WP_Error Validity.
415
  */
416
- private function validate_amp_keyframe( $style ) {
417
- if ( 'body' !== $style->parentNode->nodeName ) {
418
- return new WP_Error( 'mandatory_body_child', __( 'amp-keyframes is not child of body element.', 'amp' ) );
419
- }
420
-
421
- if ( $this->keyframes_max_size && strlen( $style->textContent ) > $this->keyframes_max_size ) {
422
- return new WP_Error( 'max_bytes', __( 'amp-keyframes is too large', 'amp' ) );
423
- }
424
-
425
- // This logic could be in AMP_Tag_And_Attribute_Sanitizer, but since it only applies to amp-keyframes it seems unnecessary.
426
- $next_sibling = $style->nextSibling;
427
- while ( $next_sibling ) {
428
- if ( $next_sibling instanceof DOMElement ) {
429
- return new WP_Error( 'mandatory_last_child', __( 'amp-keyframes is not last element in body.', 'amp' ) );
430
  }
431
- $next_sibling = $next_sibling->nextSibling;
432
  }
433
-
434
- // @todo Also add validation of the CSS spec itself.
435
- return true;
436
  }
437
 
438
  /**
439
- * Collect and store all CSS style attributes.
440
  *
441
- * Collects the CSS styles from within the HTML contained in this instance's DOMDocument.
 
442
  *
443
- * @see Retrieve array of styles using $this->get_styles() after calling this method.
444
  *
445
- * @since 0.4
446
- * @since 0.7 Modified to use element passed by XPath query.
 
447
  *
448
- * @note Uses recursion to traverse down the tree of DOMDocument nodes.
 
 
 
 
 
 
 
 
449
  *
450
- * @param DOMElement $element Node.
 
 
 
451
  */
452
- private function collect_inline_styles( $element ) {
453
- $value = $element->getAttribute( 'style' );
454
- if ( ! $value ) {
455
- return;
456
- }
457
- $class = $element->getAttribute( 'class' );
458
-
459
- $properties = $this->process_style( $value );
 
 
 
 
 
 
 
 
 
 
460
 
461
- if ( ! empty( $properties ) ) {
462
- $class_name = $this->generate_class_name( $properties );
463
- $new_class = trim( $class . ' ' . $class_name );
464
 
465
- $selector = '.' . $class_name;
466
- $length = strlen( sprintf( '%s { %s }', $selector, join( '; ', $properties ) . ';' ) );
 
 
 
467
 
468
- if ( $this->current_custom_size + $length > $this->custom_max_size ) {
469
- $this->remove_invalid_attribute( $element, 'style', array(
470
- 'message' => __( 'Too much CSS.', 'amp' ),
471
- ) );
472
- return;
 
 
 
 
 
 
473
  }
 
474
 
475
- $element->setAttribute( 'class', $new_class );
476
- $this->styles[ $selector ] = $properties;
 
 
 
 
 
 
 
 
 
 
 
 
477
  }
478
- $element->removeAttribute( 'style' );
 
479
  }
480
 
481
  /**
482
- * Sanitize and convert individual styles.
483
  *
484
- * @since 0.4
 
 
 
485
  *
486
- * @param string $css Style string.
487
- * @return array Style properties.
 
488
  */
489
- private function process_style( $css ) {
490
-
491
- // Normalize whitespace.
492
- $css = str_replace( array( "\n", "\r", "\t" ), '', $css );
493
-
494
- /*
495
- * Use preg_split to break up rules by `;` but only if the
496
- * semi-colon is not inside parens (like a data-encoded image).
497
- */
498
- $styles = preg_split( '/\s*;\s*(?![^(]*\))/', trim( $css, '; ' ) );
499
- $styles = array_filter( $styles );
500
 
501
- // Normalize the order of the styles.
502
- sort( $styles );
503
 
504
- $processed_styles = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
 
506
- // Normalize whitespace and filter rules.
507
- foreach ( $styles as $index => $rule ) {
508
- $tuple = preg_split( '/\s*:\s*/', $rule, 2 );
509
- if ( 2 !== count( $tuple ) ) {
510
- continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
512
 
513
- list( $property, $value ) = $this->filter_style( $tuple[0], $tuple[1] );
514
- if ( empty( $property ) || empty( $value ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  continue;
516
  }
517
 
518
- $processed_styles[ $index ] = "{$property}:{$value}";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  }
520
 
521
- return $processed_styles;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
522
  }
523
 
524
  /**
525
- * Filter individual CSS name/value pairs.
526
  *
527
- * - Remove overflow if value is `auto` or `scroll`
528
- * - Change `width` to `max-width`
529
- * - Remove !important
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  * @package AMP
6
  */
7
 
8
+ use \Sabberworm\CSS\RuleSet\DeclarationBlock;
9
+ use \Sabberworm\CSS\CSSList\CSSList;
10
+ use \Sabberworm\CSS\Property\Selector;
11
+ use \Sabberworm\CSS\RuleSet\RuleSet;
12
+ use \Sabberworm\CSS\Property\AtRule;
13
+ use \Sabberworm\CSS\CSSList\KeyFrame;
14
+ use \Sabberworm\CSS\RuleSet\AtRuleSet;
15
+ use \Sabberworm\CSS\Property\Import;
16
+ use \Sabberworm\CSS\CSSList\AtRuleBlockList;
17
+ use \Sabberworm\CSS\Value\RuleValueList;
18
+ use \Sabberworm\CSS\Value\URL;
19
+ use \Sabberworm\CSS\CSSList\Document;
20
+
21
  /**
22
  * Class AMP_Style_Sanitizer
23
  *
26
  class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
27
 
28
  /**
29
+ * Error code for tree shaking.
30
  *
31
+ * @var string
32
+ */
33
+ const TREE_SHAKING_ERROR_CODE = 'removed_unused_css_rules';
34
+
35
+ /**
36
+ * Error code for illegal at-rule.
37
  *
38
+ * @var string
 
39
  */
40
+ const ILLEGAL_AT_RULE_ERROR_CODE = 'illegal_css_at_rule';
41
 
42
  /**
43
+ * Inline style selector's specificity multiplier, i.e. used to generate the number of ':not(#_)' placeholders.
44
  *
45
+ * @var int
46
+ */
47
+ const INLINE_SPECIFICITY_MULTIPLIER = 5; // @todo The correctness of using "5" should be validated.
48
+
49
+ /**
50
+ * Array of flags used to control sanitization.
51
  *
52
+ * @var array {
53
+ * @type string $remove_unused_rules Enum 'never', 'sometimes' (default), 'always'. If total CSS is greater than max_bytes, whether to strip selectors (and then empty rules) when they are not found to be used in doc. A validation error will be emitted when stripping happens since it is not completely safe in the case of dynamic content.
54
+ * @type string[] $dynamic_element_selectors Selectors for elements (or their ancestors) which contain dynamic content; selectors containing these will not be filtered.
55
+ * @type bool $use_document_element Whether the root of the document should be used rather than the body.
56
+ * @type bool $require_https_src Require HTTPS URLs.
57
+ * @type bool $allow_dirty_styles Allow dirty styles. This short-circuits the sanitize logic; it is used primarily in Customizer preview.
58
+ * @type callable $validation_error_callback Function to call when a validation error is encountered.
59
+ * @type bool $should_locate_sources Whether to locate the sources when reporting validation errors.
60
+ * @type string $parsed_cache_variant Additional value by which to vary parsed cache.
61
+ * @type bool $accept_tree_shaking Whether to accept tree-shaking by default and bypass a validation error.
62
+ * }
63
  */
64
+ protected $args;
65
 
66
  /**
67
+ * Default args.
68
  *
69
+ * @var array
 
70
  */
71
+ protected $DEFAULT_ARGS = array(
72
+ 'remove_unused_rules' => 'sometimes',
73
+ 'dynamic_element_selectors' => array(
74
+ 'amp-list',
75
+ 'amp-live-list',
76
+ '[submit-error]',
77
+ '[submit-success]',
78
+ ),
79
+ 'should_locate_sources' => false,
80
+ 'parsed_cache_variant' => null,
81
+ 'accept_tree_shaking' => false,
82
+ );
83
 
84
  /**
85
+ * Stylesheets.
86
+ *
87
+ * Values are the CSS stylesheets. Keys are MD5 hashes of the stylesheets,
88
  *
89
  * @since 0.7
90
+ * @var string[]
91
  */
92
+ private $stylesheets = array();
93
 
94
  /**
95
+ * List of stylesheet parts prior to selector/rule removal (tree shaking).
96
  *
97
+ * Keys are MD5 hashes of stylesheets.
98
  *
99
+ * @since 1.0
100
+ * @var array[] {
101
+ * @type array $stylesheet Array of stylesheet chunked, with declaration blocks being represented as arrays.
102
+ * @type DOMElement|DOMAttr $node Origin for styles.
103
+ * @type array $sources Sources for the node.
104
+ * @type bool $keyframes Whether an amp-keyframes.
105
+ * }
106
+ */
107
+ private $pending_stylesheets = array();
108
+
109
+ /**
110
+ * Spec for style[amp-custom] cdata.
111
+ *
112
+ * @since 1.0
113
+ * @var array
114
  */
115
+ private $style_custom_cdata_spec;
116
 
117
  /**
118
  * The style[amp-custom] element.
121
  */
122
  private $amp_custom_style_element;
123
 
124
+ /**
125
+ * Spec for style[amp-keyframes] cdata.
126
+ *
127
+ * @since 1.0
128
+ * @var array
129
+ */
130
+ private $style_keyframes_cdata_spec;
131
+
132
  /**
133
  * Regex for allowed font stylesheet URL.
134
  *
152
  */
153
  private $content_url;
154
 
155
+ /**
156
+ * Class names used in document.
157
+ *
158
+ * @since 1.0
159
+ * @var array
160
+ */
161
+ private $used_class_names = array();
162
+
163
+ /**
164
+ * Tag names used in document.
165
+ *
166
+ * @since 1.0
167
+ * @var array
168
+ */
169
+ private $used_tag_names = array();
170
+
171
+ /**
172
+ * XPath.
173
+ *
174
+ * @since 1.0
175
+ * @var DOMXPath
176
+ */
177
+ private $xpath;
178
+
179
+ /**
180
+ * Amount of time that was spent parsing CSS.
181
+ *
182
+ * @since 1.0
183
+ * @var float
184
+ */
185
+ private $parse_css_duration = 0.0;
186
+
187
+ /**
188
+ * THe HEAD element.
189
+ *
190
+ * @var DOMElement
191
+ */
192
+ private $head;
193
+
194
+ /**
195
+ * Current node being processed.
196
+ *
197
+ * @var DOMElement|DOMAttr
198
+ */
199
+ private $current_node;
200
+
201
+ /**
202
+ * Current sources for a given node.
203
+ *
204
+ * @var array
205
+ */
206
+ private $current_sources;
207
+
208
+ /**
209
+ * Log of the stylesheet URLs that have been imported to guard against infinite loops.
210
+ *
211
+ * @var array
212
+ */
213
+ private $processed_imported_stylesheet_urls = array();
214
+
215
+ /**
216
+ * List of font stylesheets that were @import'ed which should have been <link>'ed to instead.
217
+ *
218
+ * These font URLs will be cached with the parsed CSS and then converted into stylesheet links.
219
+ *
220
+ * @var array
221
+ */
222
+ private $imported_font_urls = array();
223
+
224
+ /**
225
+ * Mapping of HTML element selectors to AMP selector elements.
226
+ *
227
+ * @var array
228
+ */
229
+ private $selector_mappings = array();
230
+
231
+ /**
232
+ * Get error codes that can be raised during parsing of CSS.
233
+ *
234
+ * This is used to determine which validation errors should be taken into account
235
+ * when determining which validation errors should vary the parse cache.
236
+ *
237
+ * @return array
238
+ */
239
+ public static function get_css_parser_validation_error_codes() {
240
+ return array(
241
+ 'css_parse_error',
242
+ 'excessive_css',
243
+ self::ILLEGAL_AT_RULE_ERROR_CODE,
244
+ 'illegal_css_important',
245
+ 'illegal_css_property',
246
+ self::TREE_SHAKING_ERROR_CODE,
247
+ 'unrecognized_css',
248
+ 'disallowed_file_extension',
249
+ 'file_path_not_found',
250
+ );
251
+ }
252
+
253
  /**
254
  * AMP_Base_Sanitizer constructor.
255
  *
261
  public function __construct( DOMDocument $dom, array $args = array() ) {
262
  parent::__construct( $dom, $args );
263
 
 
264
  foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'style' ) as $spec_rule ) {
265
+ if ( ! isset( $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) ) {
266
+ continue;
 
267
  }
268
+ if ( 'style[amp-keyframes]' === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) {
269
+ $this->style_keyframes_cdata_spec = $spec_rule[ AMP_Rule_Spec::CDATA ];
270
+ } elseif ( 'style amp-custom' === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) {
271
+ $this->style_custom_cdata_spec = $spec_rule[ AMP_Rule_Spec::CDATA ];
 
 
 
272
  }
273
  }
274
 
286
  }
287
  $this->base_url = $guessurl;
288
  $this->content_url = WP_CONTENT_URL;
289
+ $this->xpath = new DOMXPath( $dom );
290
  }
291
 
292
  /**
293
  * Get list of CSS styles in HTML content of DOMDocument ($this->dom).
294
  *
295
  * @since 0.4
296
+ * @deprecated As of 1.0, use get_stylesheets().
297
  *
298
  * @return array[] Mapping CSS selectors to array of properties, or mapping of keys starting with 'stylesheet:' with value being the stylesheet.
299
  */
300
  public function get_styles() {
301
+ return array();
 
 
 
302
  }
303
 
304
  /**
308
  * @returns array Values are the CSS stylesheets. Keys are MD5 hashes of the stylesheets.
309
  */
310
  public function get_stylesheets() {
311
+ return $this->stylesheets;
312
+ }
313
+
314
+ /**
315
+ * Get list of all the class names used in the document, including those used in [class] attributes.
316
+ *
317
+ * @since 1.0
318
+ * @return array Used class names.
319
+ */
320
+ private function get_used_class_names() {
321
+ if ( empty( $this->used_class_names ) ) {
322
+ $classes = ' ';
323
+ foreach ( $this->xpath->query( '//*/@class' ) as $class_attribute ) {
324
+ $classes .= ' ' . $class_attribute->nodeValue;
325
+ }
326
+
327
+ // Find all [class] attributes and capture the contents of any single- or double-quoted strings.
328
+ foreach ( $this->xpath->query( '//*/@' . AMP_DOM_Utils::get_amp_bind_placeholder_prefix() . 'class' ) as $bound_class_attribute ) {
329
+ if ( preg_match_all( '/([\'"])([^\1]*?)\1/', $bound_class_attribute->nodeValue, $matches ) ) {
330
+ $classes .= ' ' . implode( ' ', $matches[2] );
331
+ }
332
+ }
333
+
334
+ $this->used_class_names = array_unique( array_filter( preg_split( '/\s+/', trim( $classes ) ) ) );
335
+ }
336
+ return $this->used_class_names;
337
+ }
338
+
339
+
340
+ /**
341
+ * Get list of all the tag names used in the document.
342
+ *
343
+ * @since 1.0
344
+ * @return array Used tag names.
345
+ */
346
+ private function get_used_tag_names() {
347
+ if ( empty( $this->used_tag_names ) ) {
348
+ $used_tag_names = array();
349
+ foreach ( $this->dom->getElementsByTagName( '*' ) as $el ) {
350
+ $used_tag_names[ $el->tagName ] = true;
351
+ }
352
+ $this->used_tag_names = array_keys( $used_tag_names );
353
+ }
354
+ return $this->used_tag_names;
355
+ }
356
+
357
+ /**
358
+ * Run logic before any sanitizers are run.
359
+ *
360
+ * After the sanitizers are instantiated but before calling sanitize on each of them, this
361
+ * method is called with list of all the instantiated sanitizers.
362
+ *
363
+ * @param AMP_Base_Sanitizer[] $sanitizers Sanitizers.
364
+ */
365
+ public function init( $sanitizers ) {
366
+ parent::init( $sanitizers );
367
+
368
+ foreach ( $sanitizers as $sanitizer ) {
369
+ foreach ( $sanitizer->get_selector_conversion_mapping() as $html_selectors => $amp_selectors ) {
370
+ if ( ! isset( $this->selector_mappings[ $html_selectors ] ) ) {
371
+ $this->selector_mappings[ $html_selectors ] = $amp_selectors;
372
+ } else {
373
+ $this->selector_mappings[ $html_selectors ] = array_unique(
374
+ array_merge( $this->selector_mappings[ $html_selectors ], $amp_selectors )
375
+ );
376
+ }
377
+
378
+ // Prevent selectors like `amp-img img` getting deleted since `img` does not occur in the DOM.
379
+ $this->args['dynamic_element_selectors'] = array_merge(
380
+ $this->args['dynamic_element_selectors'],
381
+ $this->selector_mappings[ $html_selectors ]
382
+ );
383
+ }
384
+ }
385
  }
386
 
387
  /**
397
  return;
398
  }
399
 
400
+ $this->head = $this->dom->getElementsByTagName( 'head' )->item( 0 );
401
+ if ( ! $this->head ) {
402
+ $this->head = $this->dom->createElement( 'head' );
403
+ $this->dom->documentElement->insertBefore( $this->head, $this->dom->documentElement->firstChild );
404
+ }
405
+
406
+ $this->parse_css_duration = 0.0;
407
+
408
  /*
409
  * Note that xpath is used to query the DOM so that the link and style elements will be
410
  * in document order. DOMNode::compareDocumentPosition() is not yet implemented.
411
  */
412
+ $xpath = $this->xpath;
413
 
414
  $lower_case = 'translate( %s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz" )'; // In XPath 2.0 this is lower-case().
415
  $predicates = array(
421
  $elements[] = $element;
422
  }
423
 
424
+ // If 'width' attribute is present for 'col' tag, convert to proper CSS rule.
425
+ foreach ( $this->dom->getElementsByTagName( 'col' ) as $col ) {
426
+ /**
427
+ * Col element.
428
+ *
429
+ * @var DOMElement $col
430
+ */
431
+ $width_attr = $col->getAttribute( 'width' );
432
+ if ( ! empty( $width_attr ) && ( false === strpos( $width_attr, '*' ) ) ) {
433
+ $width_style = 'width: ' . $width_attr;
434
+ if ( is_numeric( $width_attr ) ) {
435
+ $width_style .= 'px';
436
+ }
437
+ if ( $col->hasAttribute( 'style' ) ) {
438
+ $col->setAttribute( 'style', $width_style . ';' . $col->getAttribute( 'style' ) );
439
+ } else {
440
+ $col->setAttribute( 'style', $width_style );
441
+ }
442
+ $col->removeAttribute( 'width' );
443
+ }
444
+ }
445
+
446
  /**
447
  * Element.
448
  *
464
  foreach ( $elements as $element ) {
465
  $this->collect_inline_styles( $element );
466
  }
 
467
 
468
+ $this->finalize_styles();
 
 
 
 
 
 
 
 
 
 
 
469
 
470
+ $this->did_convert_elements = true;
471
 
472
+ if ( $this->parse_css_duration > 0.0 ) {
473
+ AMP_HTTP::send_server_timing( 'amp_parse_css', $this->parse_css_duration, 'AMP Parse CSS' );
 
 
 
 
 
 
 
 
474
  }
475
  }
476
 
477
  /**
478
+ * Generate a URL's fully-qualified file path.
479
  *
480
  * @since 0.7
481
  * @see WP_Styles::_css_href()
482
  *
483
+ * @param string $url The file URL.
484
+ * @param string[] $allowed_extensions Allowed file extensions for local files.
485
  * @return string|WP_Error Style's absolute validated filesystem path, or WP_Error when error.
486
  */
487
+ public function get_validated_url_file_path( $url, $allowed_extensions = array() ) {
488
  $needs_base_url = (
489
+ ! is_bool( $url )
490
  &&
491
+ ! preg_match( '|^(https?:)?//|', $url )
492
  &&
493
+ ! ( $this->content_url && 0 === strpos( $url, $this->content_url ) )
494
  );
495
  if ( $needs_base_url ) {
496
+ $url = $this->base_url . $url;
497
+ }
498
+
499
+ $remove_url_scheme = function( $schemed_url ) {
500
+ return preg_replace( '#^\w+:(?=//)#', '', $schemed_url );
501
+ };
502
+
503
+ // Strip URL scheme, query, and fragment.
504
+ $url = $remove_url_scheme( preg_replace( ':[\?#].*$:', '', $url ) );
505
+
506
+ $includes_url = $remove_url_scheme( includes_url( '/' ) );
507
+ $content_url = $remove_url_scheme( content_url( '/' ) );
508
+ $admin_url = $remove_url_scheme( get_admin_url( null, '/' ) );
509
+
510
+ $allowed_hosts = array(
511
+ wp_parse_url( $includes_url, PHP_URL_HOST ),
512
+ wp_parse_url( $content_url, PHP_URL_HOST ),
513
+ wp_parse_url( $admin_url, PHP_URL_HOST ),
514
+ );
515
+
516
+ $url_host = wp_parse_url( $url, PHP_URL_HOST );
517
+
518
+ // Validate file extensions.
519
+ if ( ! empty( $allowed_extensions ) ) {
520
+ $pattern = sprintf( '/\.(%s)$/i', implode( '|', $allowed_extensions ) );
521
+ if ( ! preg_match( $pattern, $url ) ) {
522
+ /* translators: %s: the file URL. */
523
+ return new WP_Error( 'disallowed_file_extension', sprintf( __( 'File does not have an allowed file extension for filesystem access (%s).', 'amp' ), $url ) );
524
+ }
525
  }
526
 
527
+ if ( ! in_array( $url_host, $allowed_hosts, true ) ) {
528
+ /* translators: %s: the file URL */
529
+ return new WP_Error( 'external_file_url', sprintf( __( 'URL is located on an external domain: %s.', 'amp' ), $url_host ) );
530
+ }
531
 
532
+ $base_path = null;
533
+ $file_path = null;
534
+ if ( 0 === strpos( $url, $content_url ) ) {
535
+ $base_path = WP_CONTENT_DIR;
536
+ $file_path = substr( $url, strlen( $content_url ) - 1 );
537
+ } elseif ( 0 === strpos( $url, $includes_url ) ) {
538
+ $base_path = ABSPATH . WPINC;
539
+ $file_path = substr( $url, strlen( $includes_url ) - 1 );
540
+ } elseif ( 0 === strpos( $url, $admin_url ) ) {
541
+ $base_path = ABSPATH . 'wp-admin';
542
+ $file_path = substr( $url, strlen( $admin_url ) - 1 );
543
  }
544
 
545
+ if ( ! $file_path || false !== strpos( $file_path, '../' ) || false !== strpos( $file_path, '..\\' ) ) {
546
+ /* translators: %s: the file URL. */
547
+ return new WP_Error( 'file_path_not_allowed', sprintf( __( 'Disallowed URL filesystem path for %s.', 'amp' ), $url ) );
 
 
 
 
 
 
 
548
  }
549
+ if ( ! file_exists( $base_path . $file_path ) ) {
550
+ /* translators: %s: the file URL. */
551
+ return new WP_Error( 'file_path_not_found', sprintf( __( 'Unable to locate filesystem path for %s.', 'amp' ), $url ) );
552
+ }
553
+
554
+ return $base_path . $file_path;
555
+ }
556
 
557
+ /**
558
+ * Set the current node (and its sources when required).
559
+ *
560
+ * @since 1.0
561
+ * @param DOMElement|DOMAttr|null $node Current node, or null to reset.
562
+ */
563
+ private function set_current_node( $node ) {
564
+ if ( $this->current_node === $node ) {
565
+ return;
566
  }
567
 
568
+ $this->current_node = $node;
569
+ if ( empty( $node ) ) {
570
+ $this->current_sources = null;
571
+ } elseif ( ! empty( $this->args['should_locate_sources'] ) ) {
572
+ $this->current_sources = AMP_Validation_Manager::locate_sources( $node );
573
+ }
574
  }
575
 
576
  /**
579
  * @param DOMElement $element Style element.
580
  */
581
  private function process_style_element( DOMElement $element ) {
582
+ $this->set_current_node( $element ); // And sources when needing to be located.
 
 
 
 
 
 
 
 
583
 
584
+ // @todo Any @keyframes rules could be removed from amp-custom and instead added to amp-keyframes.
585
+ $is_keyframes = $element->hasAttribute( 'amp-keyframes' );
586
+ $stylesheet = trim( $element->textContent );
587
+ $cdata_spec = $is_keyframes ? $this->style_keyframes_cdata_spec : $this->style_custom_cdata_spec;
588
 
589
+ // Honor the style's media attribute.
590
+ $media = $element->getAttribute( 'media' );
591
+ if ( $media && 'all' !== $media ) {
592
+ $stylesheet = sprintf( '@media %s { %s }', $media, $stylesheet );
 
 
 
593
  }
594
 
595
+ $processed = $this->process_stylesheet( $stylesheet, array(
596
+ 'allowed_at_rules' => $cdata_spec['css_spec']['allowed_at_rules'],
597
+ 'property_whitelist' => $cdata_spec['css_spec']['declaration'],
598
+ 'validate_keyframes' => $cdata_spec['css_spec']['validate_keyframes'],
599
+ ) );
600
+
601
+ $this->pending_stylesheets[] = array_merge(
602
+ array(
603
+ 'keyframes' => $is_keyframes,
604
+ 'node' => $element,
605
+ 'sources' => $this->current_sources,
606
+ ),
607
+ wp_array_slice_assoc( $processed, array( 'stylesheet', 'imported_font_urls' ) )
608
+ );
609
 
610
  if ( $element->hasAttribute( 'amp-custom' ) ) {
611
  if ( ! $this->amp_custom_style_element ) {
618
  // Remove from DOM since we'll be adding it to amp-custom.
619
  $element->parentNode->removeChild( $element );
620
  }
621
+
622
+ $this->set_current_node( null );
623
  }
624
 
625
  /**
630
  private function process_link_element( DOMElement $element ) {
631
  $href = $element->getAttribute( 'href' );
632
 
633
+ // Allow font URLs, including protocol-less URLs and recognized URLs that use HTTP instead of HTTPS.
634
+ $normalized_url = preg_replace( '#^(http:)?(?=//)#', 'https:', $href );
635
+ if ( $this->allowed_font_src_regex && preg_match( $this->allowed_font_src_regex, $normalized_url ) ) {
636
+ if ( $href !== $normalized_url ) {
637
+ $element->setAttribute( 'href', $normalized_url );
638
+ }
639
+
640
+ /*
641
+ * Make sure rel=preconnect link is present for Google Fonts stylesheet.
642
+ * Note that core themes normally do this already, per <https://core.trac.wordpress.org/ticket/37171>.
643
+ * But not always, per <https://core.trac.wordpress.org/ticket/44668>.
644
+ * This also ensures that other themes will get the preconnect link when
645
+ * they don't implement the resource hint.
646
+ */
647
+ $needs_preconnect_link = (
648
+ 'https://fonts.googleapis.com/' === substr( $normalized_url, 0, 29 )
649
+ &&
650
+ 0 === $this->xpath->query( '//link[ @rel = "preconnect" and @crossorigin and starts-with( @href, "https://fonts.gstatic.com" ) ]', $this->head )->length
651
+ );
652
+ if ( $needs_preconnect_link ) {
653
+ $link = AMP_DOM_Utils::create_node( $this->dom, 'link', array(
654
+ 'rel' => 'preconnect',
655
+ 'href' => 'https://fonts.gstatic.com/',
656
+ 'crossorigin' => '',
657
+ ) );
658
+ $this->head->insertBefore( $link ); // Note that \AMP_Theme_Support::ensure_required_markup() will put this in the optimal order.
659
+ }
660
  return;
661
  }
662
 
663
+ $css_file_path = $this->get_validated_url_file_path( $href, array( 'css', 'less', 'scss', 'sass' ) );
664
+
665
+ if ( is_wp_error( $css_file_path ) && ( 'disallowed_file_extension' === $css_file_path->get_error_code() || 'external_file_url' === $css_file_path->get_error_code() ) ) {
666
+ $contents = $this->fetch_external_stylesheet( $normalized_url );
667
+ if ( is_wp_error( $contents ) ) {
668
+ $this->remove_invalid_child( $element, array(
669
+ 'code' => $css_file_path->get_error_code(),
670
+ 'message' => $css_file_path->get_error_message(),
671
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
672
+ ) );
673
+ return;
674
+ } else {
675
+ $stylesheet = $contents;
676
+ }
677
+ } elseif ( is_wp_error( $css_file_path ) ) {
678
  $this->remove_invalid_child( $element, array(
679
+ 'code' => $css_file_path->get_error_code(),
680
  'message' => $css_file_path->get_error_message(),
681
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
682
  ) );
683
  return;
684
+ } else {
685
+ $stylesheet = file_get_contents( $css_file_path ); // phpcs:ignore -- It's a local filesystem path not a remote request.
686
  }
687
 
688
+ if ( false === $stylesheet ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
689
  $this->remove_invalid_child( $element, array(
690
+ 'code' => 'stylesheet_file_missing',
691
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
692
  ) );
693
  return;
694
  }
695
 
696
+ // Honor the link's media attribute.
697
+ $media = $element->getAttribute( 'media' );
698
+ if ( $media && 'all' !== $media ) {
699
+ $stylesheet = sprintf( '@media %s { %s }', $media, $stylesheet );
700
+ }
701
+
702
+ $this->set_current_node( $element ); // And sources when needing to be located.
703
+
704
+ $processed = $this->process_stylesheet( $stylesheet, array(
705
+ 'allowed_at_rules' => $this->style_custom_cdata_spec['css_spec']['allowed_at_rules'],
706
+ 'property_whitelist' => $this->style_custom_cdata_spec['css_spec']['declaration'],
707
+ 'stylesheet_url' => $href,
708
+ 'stylesheet_path' => $css_file_path,
709
+ ) );
710
+
711
+ $this->pending_stylesheets[] = array_merge(
712
+ array(
713
+ 'keyframes' => false,
714
+ 'node' => $element,
715
+ 'sources' => $this->current_sources, // Needed because node is removed below.
716
+ ),
717
+ wp_array_slice_assoc( $processed, array( 'stylesheet', 'imported_font_urls' ) )
718
+ );
719
 
720
  // Remove now that styles have been processed.
721
  $element->parentNode->removeChild( $element );
 
722
 
723
+ $this->set_current_node( null );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  }
725
 
726
  /**
727
+ * Fetch external stylesheet.
 
 
 
728
  *
729
+ * @param string $url External stylesheet URL.
730
+ * @return string|WP_Error Stylesheet contents or WP_Error.
731
  */
732
+ private function fetch_external_stylesheet( $url ) {
733
+ $cache_key = md5( $url );
734
+ $contents = get_transient( $cache_key );
735
+ if ( false === $contents ) {
736
+ $r = wp_remote_get( $url );
737
+ if ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
738
+ $contents = new WP_Error(
739
+ wp_remote_retrieve_response_code( $r ),
740
+ wp_remote_retrieve_response_message( $r )
741
+ );
742
+ } else {
743
+ $contents = wp_remote_retrieve_body( $r );
 
 
744
  }
745
+ set_transient( $cache_key, $contents, MONTH_IN_SECONDS );
746
  }
747
+ return $contents;
 
 
748
  }
749
 
750
  /**
751
+ * Process stylesheet.
752
  *
753
+ * Sanitized invalid CSS properties and rules, removes rules which do not
754
+ * apply to the current document, and compresses the CSS to remove whitespace and comments.
755
  *
756
+ * @since 1.0
757
  *
758
+ * @param string $stylesheet Stylesheet.
759
+ * @param array $options {
760
+ * Options.
761
  *
762
+ * @type string[] $property_whitelist Exclusively-allowed properties.
763
+ * @type string[] $property_blacklist Disallowed properties.
764
+ * @type string $stylesheet_url Original URL for stylesheet when originating via link or @import.
765
+ * @type string $stylesheet_path Original filesystem path for stylesheet when originating via link or @import.
766
+ * @type array $allowed_at_rules Allowed @-rules.
767
+ * @type bool $validate_keyframes Whether keyframes should be validated.
768
+ * }
769
+ * @return array {
770
+ * Processed stylesheet.
771
  *
772
+ * @type array $stylesheet Stylesheet parts, where arrays are tuples for declaration blocks.
773
+ * @type array $validation_results Validation results, array containing arrays with error and sanitized keys.
774
+ * @type array $imported_font_urls Imported font stylesheet URLs.
775
+ * }
776
  */
777
+ private function process_stylesheet( $stylesheet, $options = array() ) {
778
+ $parsed = null;
779
+ $cache_key = null;
780
+ $cache_group = 'amp-parsed-stylesheet-v13'; // This should be bumped whenever the PHP-CSS-Parser is updated.
781
+
782
+ $cache_impacting_options = array_merge(
783
+ wp_array_slice_assoc(
784
+ $options,
785
+ array( 'property_whitelist', 'property_blacklist', 'stylesheet_url', 'allowed_at_rules' )
786
+ ),
787
+ wp_array_slice_assoc(
788
+ $this->args,
789
+ array( 'should_locate_sources', 'parsed_cache_variant' )
790
+ ),
791
+ array(
792
+ 'language' => get_bloginfo( 'language' ), // Used to tree-shake html[lang] selectors.
793
+ )
794
+ );
795
 
796
+ $cache_key = md5( $stylesheet . wp_json_encode( $cache_impacting_options ) );
 
 
797
 
798
+ if ( wp_using_ext_object_cache() ) {
799
+ $parsed = wp_cache_get( $cache_key, $cache_group );
800
+ } else {
801
+ $parsed = get_transient( $cache_key . $cache_group );
802
+ }
803
 
804
+ /*
805
+ * Make sure that the parsed stylesheet was cached with current sanitizations.
806
+ * The should_sanitize_validation_error method prevents duplicates from being reported.
807
+ */
808
+ if ( ! empty( $parsed['validation_results'] ) ) {
809
+ foreach ( $parsed['validation_results'] as $validation_result ) {
810
+ $sanitized = $this->should_sanitize_validation_error( $validation_result['error'] );
811
+ if ( $sanitized !== $validation_result['sanitized'] ) {
812
+ $parsed = null; // Change to sanitization of validation error detected, so cache cannot be used.
813
+ break;
814
+ }
815
  }
816
+ }
817
 
818
+ if ( ! $parsed || ! isset( $parsed['stylesheet'] ) || ! is_array( $parsed['stylesheet'] ) ) {
819
+ $parsed = $this->prepare_stylesheet( $stylesheet, $options );
820
+
821
+ /*
822
+ * When an object cache is not available, we cache with an expiration to prevent the options table from
823
+ * getting filled infinitely. On the other hand, if an external object cache is available then we don't
824
+ * set an expiration because it should implement LRU cache expulsion policy.
825
+ */
826
+ if ( wp_using_ext_object_cache() ) {
827
+ wp_cache_set( $cache_key, $parsed, $cache_group );
828
+ } else {
829
+ // The expiration is to ensure transient doesn't stick around forever since no LRU flushing like with external object cache.
830
+ set_transient( $cache_key . $cache_group, $parsed, MONTH_IN_SECONDS );
831
+ }
832
  }
833
+
834
+ return $parsed;
835
  }
836
 
837
  /**
838
+ * Parse imported stylesheet.
839
  *
840
+ * @param Import $item Import object.
841
+ * @param CSSList $css_list CSS List.
842
+ * @param array $options {
843
+ * Options.
844
  *
845
+ * @type string $stylesheet_url Original URL for stylesheet when originating via link or @import.
846
+ * }
847
+ * @return array Validation results.
848
  */
849
+ private function parse_import_stylesheet( Import $item, CSSList $css_list, $options ) {
850
+ $results = array();
851
+ $at_rule_args = $item->atRuleArgs();
852
+ $location = array_shift( $at_rule_args );
853
+ $media_query = array_shift( $at_rule_args );
854
+
855
+ if ( isset( $options['stylesheet_url'] ) ) {
856
+ $this->real_path_urls( array( $location ), $options['stylesheet_url'] );
857
+ }
 
 
858
 
859
+ $import_stylesheet_url = $location->getURL()->getString();
 
860
 
861
+ // Prevent importing something that has already been imported, and avoid infinite recursion.
862
+ if ( isset( $this->processed_imported_stylesheet_urls[ $import_stylesheet_url ] ) ) {
863
+ $css_list->remove( $item );
864
+ return array();
865
+ }
866
+ $this->processed_imported_stylesheet_urls[ $import_stylesheet_url ] = true;
867
+
868
+ // Prevent importing font stylesheets from allowed font CDNs. These will get added to the document as links instead.
869
+ $https_import_stylesheet_url = preg_replace( '#^(http:)?(?=//)#', 'https:', $import_stylesheet_url );
870
+ if ( $this->allowed_font_src_regex && preg_match( $this->allowed_font_src_regex, $https_import_stylesheet_url ) ) {
871
+ $this->imported_font_urls[] = $https_import_stylesheet_url;
872
+ $css_list->remove( $item );
873
+ _doing_it_wrong(
874
+ 'wp_enqueue_style',
875
+ esc_html( sprintf(
876
+ /* translators: %s is URL to font CDN */
877
+ __( 'It is not a best practice to use @import to load font CDN stylesheets. Please use wp_enqueue_style() to enqueue %s as its own separate script.', 'amp' ),
878
+ $import_stylesheet_url
879
+ ) ),
880
+ '1.0'
881
+ );
882
+ return array();
883
+ }
884
 
885
+ $css_file_path = $this->get_validated_url_file_path( $import_stylesheet_url, array( 'css', 'less', 'scss', 'sass' ) );
886
+
887
+ if ( is_wp_error( $css_file_path ) && ( 'disallowed_file_extension' === $css_file_path->get_error_code() || 'external_file_url' === $css_file_path->get_error_code() ) ) {
888
+ $contents = $this->fetch_external_stylesheet( $import_stylesheet_url );
889
+ if ( is_wp_error( $contents ) ) {
890
+ $error = array(
891
+ 'code' => $contents->get_error_code(),
892
+ 'message' => $contents->get_error_message(),
893
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
894
+ );
895
+ $sanitized = $this->should_sanitize_validation_error( $error );
896
+ if ( $sanitized ) {
897
+ $css_list->remove( $item );
898
+ }
899
+ $results[] = compact( 'error', 'sanitized' );
900
+ return $results;
901
+ } else {
902
+ $stylesheet = $contents;
903
+ }
904
+ } elseif ( is_wp_error( $css_file_path ) ) {
905
+ $error = array(
906
+ 'code' => $css_file_path->get_error_code(),
907
+ 'message' => $css_file_path->get_error_message(),
908
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
909
+ );
910
+ $sanitized = $this->should_sanitize_validation_error( $error );
911
+ if ( $sanitized ) {
912
+ $css_list->remove( $item );
913
+ }
914
+ $results[] = compact( 'error', 'sanitized' );
915
+ return $results;
916
+ } else {
917
+ $stylesheet = file_get_contents( $css_file_path ); // phpcs:ignore -- It's a local filesystem path not a remote request.
918
+ }
919
+
920
+ if ( $media_query ) {
921
+ $stylesheet = sprintf( '@media %s { %s }', $media_query, $stylesheet );
922
+ }
923
+
924
+ $options['stylesheet_url'] = $import_stylesheet_url;
925
+
926
+ $parsed_stylesheet = $this->parse_stylesheet( $stylesheet, $options );
927
+
928
+ $results = array_merge(
929
+ $results,
930
+ $parsed_stylesheet['validation_results']
931
+ );
932
+
933
+ /**
934
+ * CSS Doc.
935
+ *
936
+ * @var Document $css_document
937
+ */
938
+ $css_document = $parsed_stylesheet['css_document'];
939
+
940
+ if ( ! empty( $parsed_stylesheet['css_document'] ) ) {
941
+ $css_list->replace( $item, $css_document->getContents() );
942
+ } else {
943
+ $css_list->remove( $item );
944
+ }
945
+
946
+ return $results;
947
+ }
948
+
949
+ /**
950
+ * Parse stylesheet.
951
+ *
952
+ * @since 1.0
953
+ *
954
+ * @param string $stylesheet_string Stylesheet.
955
+ * @param array $options Options. See definition in \AMP_Style_Sanitizer::process_stylesheet().
956
+ * @return array {
957
+ * Parsed stylesheet.
958
+ *
959
+ * @type Document $css_document CSS Document.
960
+ * @type array $validation_results Validation results, array containing arrays with error and sanitized keys.
961
+ * }
962
+ */
963
+ private function parse_stylesheet( $stylesheet_string, $options ) {
964
+ $validation_results = array();
965
+ $css_document = null;
966
+
967
+ $this->imported_font_urls = array();
968
+ try {
969
+ // Remove spaces from data URLs, which cause errors and PHP-CSS-Parser can't handle them.
970
+ $stylesheet_string = $this->remove_spaces_from_data_urls( $stylesheet_string );
971
+
972
+ $parser_settings = Sabberworm\CSS\Settings::create();
973
+ $css_parser = new Sabberworm\CSS\Parser( $stylesheet_string, $parser_settings );
974
+ $css_document = $css_parser->parse(); // @todo If 'utf-8' is not $css_parser->getCharset() then issue warning?
975
+
976
+ if ( ! empty( $options['stylesheet_url'] ) ) {
977
+ $this->real_path_urls(
978
+ array_filter(
979
+ $css_document->getAllValues(),
980
+ function ( $value ) {
981
+ return $value instanceof URL;
982
+ }
983
+ ),
984
+ $options['stylesheet_url']
985
+ );
986
  }
987
 
988
+ $validation_results = array_merge(
989
+ $validation_results,
990
+ $this->process_css_list( $css_document, $options )
991
+ );
992
+ } catch ( Exception $exception ) {
993
+ $error = array(
994
+ 'code' => 'css_parse_error',
995
+ 'message' => $exception->getMessage(),
996
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
997
+ );
998
+
999
+ /*
1000
+ * This is not a recoverable error, so sanitized here is just used to give user control
1001
+ * over whether to proceed with serving this exception-raising stylesheet in AMP.
1002
+ */
1003
+ $sanitized = $this->should_sanitize_validation_error( $error );
1004
+
1005
+ $validation_results[] = compact( 'error', 'sanitized' );
1006
+ }
1007
+ return array_merge(
1008
+ compact( 'validation_results', 'css_document' ),
1009
+ array(
1010
+ 'imported_font_urls' => $this->imported_font_urls,
1011
+ )
1012
+ );
1013
+ }
1014
+
1015
+ /**
1016
+ * Prepare stylesheet.
1017
+ *
1018
+ * @since 1.0
1019
+ *
1020
+ * @param string $stylesheet_string Stylesheet.
1021
+ * @param array $options Options. See definition in \AMP_Style_Sanitizer::process_stylesheet().
1022
+ * @return array {
1023
+ * Prepared stylesheet.
1024
+ *
1025
+ * @type array $stylesheet Stylesheet parts, where arrays are tuples for declaration blocks.
1026
+ * @type array $validation_results Validation results, array containing arrays with error and sanitized keys.
1027
+ * @type array $imported_font_urls Imported font stylesheet URLs.
1028
+ * }
1029
+ */
1030
+ private function prepare_stylesheet( $stylesheet_string, $options = array() ) {
1031
+ $start_time = microtime( true );
1032
+
1033
+ $options = array_merge(
1034
+ array(
1035
+ 'allowed_at_rules' => array(),
1036
+ 'property_blacklist' => array(
1037
+ // See <https://www.ampproject.org/docs/design/responsive/style_pages#disallowed-styles>.
1038
+ 'behavior',
1039
+ '-moz-binding',
1040
+ ),
1041
+ 'property_whitelist' => array(),
1042
+ 'validate_keyframes' => false,
1043
+ 'stylesheet_url' => null,
1044
+ 'stylesheet_path' => null,
1045
+ ),
1046
+ $options
1047
+ );
1048
+
1049
+ // Strip the dreaded UTF-8 byte order mark (BOM, \uFEFF). This should ideally get handled by PHP-CSS-Parser <https://github.com/sabberworm/PHP-CSS-Parser/issues/150>.
1050
+ $stylesheet_string = preg_replace( '/^\xEF\xBB\xBF/', '', $stylesheet_string );
1051
+
1052
+ $stylesheet = array();
1053
+ $parsed_stylesheet = $this->parse_stylesheet( $stylesheet_string, $options );
1054
+ $validation_results = $parsed_stylesheet['validation_results'];
1055
+ if ( ! empty( $parsed_stylesheet['css_document'] ) ) {
1056
+ $css_document = $parsed_stylesheet['css_document'];
1057
+
1058
+ $output_format = Sabberworm\CSS\OutputFormat::createCompact();
1059
+ $output_format->setSemicolonAfterLastRule( false );
1060
+
1061
+ $before_declaration_block = '/*AMP_WP_BEFORE_DECLARATION_BLOCK*/';
1062
+ $between_selectors = '/*AMP_WP_BETWEEN_SELECTORS*/';
1063
+ $after_declaration_block_selectors = '/*AMP_WP_BEFORE_DECLARATION_SELECTORS*/';
1064
+ $after_declaration_block = '/*AMP_WP_AFTER_DECLARATION*/';
1065
+ $before_at_rule = '/*AMP_WP_BEFORE_AT_RULE*/';
1066
+ $after_at_rule = '/*AMP_WP_AFTER_AT_RULE*/';
1067
+
1068
+ $output_format->set( 'BeforeDeclarationBlock', $before_declaration_block );
1069
+ $output_format->set( 'SpaceBeforeSelectorSeparator', $between_selectors );
1070
+ $output_format->set( 'AfterDeclarationBlockSelectors', $after_declaration_block_selectors );
1071
+ $output_format->set( 'AfterDeclarationBlock', $after_declaration_block );
1072
+ $output_format->set( 'BeforeAtRuleBlock', $before_at_rule );
1073
+ $output_format->set( 'AfterAtRuleBlock', $after_at_rule );
1074
+
1075
+ $stylesheet_string = $css_document->render( $output_format );
1076
+
1077
+ $pattern = '#';
1078
+ $pattern .= preg_quote( $before_at_rule, '#' );
1079
+ $pattern .= '|';
1080
+ $pattern .= preg_quote( $after_at_rule, '#' );
1081
+ $pattern .= '|';
1082
+ $pattern .= '(' . preg_quote( $before_declaration_block, '#' ) . ')';
1083
+ $pattern .= '(.+?)';
1084
+ $pattern .= preg_quote( $after_declaration_block_selectors, '#' );
1085
+ $pattern .= '(.+?)';
1086
+ $pattern .= preg_quote( $after_declaration_block, '#' );
1087
+ $pattern .= '#s';
1088
+
1089
+ $dynamic_selector_pattern = null;
1090
+ if ( ! empty( $this->args['dynamic_element_selectors'] ) ) {
1091
+ $dynamic_selector_pattern = implode( '|', array_map(
1092
+ function( $selector ) {
1093
+ return preg_quote( $selector, '#' );
1094
+ },
1095
+ $this->args['dynamic_element_selectors']
1096
+ ) );
1097
+ }
1098
+
1099
+ $split_stylesheet = preg_split( $pattern, $stylesheet_string, -1, PREG_SPLIT_DELIM_CAPTURE );
1100
+ $length = count( $split_stylesheet );
1101
+ for ( $i = 0; $i < $length; $i++ ) {
1102
+ if ( $before_declaration_block === $split_stylesheet[ $i ] ) {
1103
+
1104
+ // Skip keyframe-selector, which is can be: from | to | <percentage>.
1105
+ if ( preg_match( '/^((from|to)\b|-?\d+(\.\d+)?%)/i', $split_stylesheet[ $i + 1 ] ) ) {
1106
+ $stylesheet[] = str_replace( $between_selectors, '', $split_stylesheet[ ++$i ] ) . $split_stylesheet[ ++$i ];
1107
+ continue;
1108
+ }
1109
+
1110
+ $selectors = explode( $between_selectors . ',', $split_stylesheet[ ++$i ] );
1111
+ $declaration = $split_stylesheet[ ++$i ];
1112
+
1113
+ $selectors_parsed = array();
1114
+ foreach ( $selectors as $selector ) {
1115
+ $selectors_parsed[ $selector ] = array();
1116
+
1117
+ // Remove :not() and pseudo selectors to eliminate false negatives, such as with `body:not(.title-tagline-hidden) .site-branding-text`.
1118
+ $reduced_selector = preg_replace( '/:[a-zA-Z0-9_-]+(\(.+?\))?/', '', $selector );
1119
+
1120
+ // Remove attribute selectors to eliminate false negative, such as with `.social-navigation a[href*="example.com"]:before`.
1121
+ $reduced_selector = preg_replace( '/\[\w.*?\]/', '', $reduced_selector );
1122
+
1123
+ // Ignore any selector terms that occur under a dynamic selector.
1124
+ if ( $dynamic_selector_pattern ) {
1125
+ $reduced_selector = preg_replace( '#((?:' . $dynamic_selector_pattern . ')(?:\.[a-z0-9_-]+)*)[^a-z0-9_-].*#si', '$1', $reduced_selector . ' ' );
1126
+ }
1127
+
1128
+ $reduced_selector = preg_replace_callback(
1129
+ '/\.([a-zA-Z0-9_-]+)/',
1130
+ function( $matches ) use ( $selector, &$selectors_parsed ) {
1131
+ $selectors_parsed[ $selector ]['classes'][] = $matches[1];
1132
+ return '';
1133
+ },
1134
+ $reduced_selector
1135
+ );
1136
+ $reduced_selector = preg_replace_callback(
1137
+ '/#([a-zA-Z0-9_-]+)/',
1138
+ function( $matches ) use ( $selector, &$selectors_parsed ) {
1139
+ $selectors_parsed[ $selector ]['ids'][] = $matches[1];
1140
+ return '';
1141
+ },
1142
+ $reduced_selector
1143
+ );
1144
+
1145
+ if ( preg_match_all( '/[a-zA-Z0-9_-]+/', $reduced_selector, $matches ) ) {
1146
+ $selectors_parsed[ $selector ]['tags'] = $matches[0];
1147
+ }
1148
+ }
1149
+
1150
+ $stylesheet[] = array(
1151
+ $selectors_parsed,
1152
+ $declaration,
1153
+ );
1154
+ } else {
1155
+ $stylesheet[] = $split_stylesheet[ $i ];
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ $this->parse_css_duration += ( microtime( true ) - $start_time );
1161
+
1162
+ return array_merge(
1163
+ compact( 'stylesheet', 'validation_results' ),
1164
+ array(
1165
+ 'imported_font_urls' => $parsed_stylesheet['imported_font_urls'],
1166
+ )
1167
+ );
1168
+ }
1169
+
1170
+ /**
1171
+ * Previous return values from calls to should_sanitize_validation_error().
1172
+ *
1173
+ * This is used to prevent duplicates from being reported when the sanitization status
1174
+ * changes for a validation error in a previously-cached stylesheet.
1175
+ *
1176
+ * @see AMP_Style_Sanitizer::should_sanitize_validation_error()
1177
+ * @var array
1178
+ */
1179
+ protected $previous_should_sanitize_validation_error_results = array();
1180
+
1181
+ /**
1182
+ * Check whether or not sanitization should occur in response to validation error.
1183
+ *
1184
+ * Supply sources to the error and the current node to data.
1185
+ *
1186
+ * @since 1.0
1187
+ *
1188
+ * @param array $validation_error Validation error.
1189
+ * @param array $data Data including the node.
1190
+ * @return bool Whether to sanitize.
1191
+ */
1192
+ public function should_sanitize_validation_error( $validation_error, $data = array() ) {
1193
+ if ( ! isset( $data['node'] ) ) {
1194
+ $data['node'] = $this->current_node;
1195
+ }
1196
+ if ( ! isset( $validation_error['sources'] ) ) {
1197
+ $validation_error['sources'] = $this->current_sources;
1198
+ }
1199
+
1200
+ /*
1201
+ * This is used to prevent duplicates from being reported when the sanitization status
1202
+ * changes for a validation error in a previously-cached stylesheet.
1203
+ */
1204
+ $args = compact( 'validation_error', 'data' );
1205
+ foreach ( $this->previous_should_sanitize_validation_error_results as $result ) {
1206
+ if ( $result['args'] === $args ) {
1207
+ return $result['sanitized'];
1208
+ }
1209
+ }
1210
+
1211
+ $sanitized = parent::should_sanitize_validation_error( $validation_error, $data );
1212
+
1213
+ $this->previous_should_sanitize_validation_error_results[] = compact( 'args', 'sanitized' );
1214
+ return $sanitized;
1215
+ }
1216
+
1217
+ /**
1218
+ * Remove spaces from data URLs which PHP-CSS-Parser doesn't handle.
1219
+ *
1220
+ * @since 1.0
1221
+ *
1222
+ * @param string $css CSS.
1223
+ * @return string CSS with spaces removed from data URLs.
1224
+ */
1225
+ private function remove_spaces_from_data_urls( $css ) {
1226
+ return preg_replace_callback(
1227
+ '/\burl\([^}]*?\)/',
1228
+ function( $matches ) {
1229
+ return preg_replace( '/\s+/', '', $matches[0] );
1230
+ },
1231
+ $css
1232
+ );
1233
+ }
1234
+
1235
+ /**
1236
+ * Process CSS list.
1237
+ *
1238
+ * @since 1.0
1239
+ *
1240
+ * @param CSSList $css_list CSS List.
1241
+ * @param array $options Options.
1242
+ * @return array Validation errors.
1243
+ */
1244
+ private function process_css_list( CSSList $css_list, $options ) {
1245
+ $results = array();
1246
+
1247
+ foreach ( $css_list->getContents() as $css_item ) {
1248
+ $sanitized = false;
1249
+ if ( $css_item instanceof DeclarationBlock && empty( $options['validate_keyframes'] ) ) {
1250
+ $results = array_merge(
1251
+ $results,
1252
+ $this->process_css_declaration_block( $css_item, $css_list, $options )
1253
+ );
1254
+ } elseif ( $css_item instanceof AtRuleBlockList ) {
1255
+ if ( ! in_array( $css_item->atRuleName(), $options['allowed_at_rules'], true ) ) {
1256
+ $error = array(
1257
+ 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE,
1258
+ 'at_rule' => $css_item->atRuleName(),
1259
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1260
+ );
1261
+ $sanitized = $this->should_sanitize_validation_error( $error );
1262
+ $results[] = compact( 'error', 'sanitized' );
1263
+ }
1264
+ if ( ! $sanitized ) {
1265
+ $results = array_merge(
1266
+ $results,
1267
+ $this->process_css_list( $css_item, $options )
1268
+ );
1269
+ }
1270
+ } elseif ( $css_item instanceof Import ) {
1271
+ $results = array_merge(
1272
+ $results,
1273
+ $this->parse_import_stylesheet( $css_item, $css_list, $options )
1274
+ );
1275
+ } elseif ( $css_item instanceof AtRuleSet ) {
1276
+ if ( ! in_array( $css_item->atRuleName(), $options['allowed_at_rules'], true ) ) {
1277
+ $error = array(
1278
+ 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE,
1279
+ 'at_rule' => $css_item->atRuleName(),
1280
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1281
+ );
1282
+ $sanitized = $this->should_sanitize_validation_error( $error );
1283
+ $results[] = compact( 'error', 'sanitized' );
1284
+ }
1285
+
1286
+ if ( ! $sanitized ) {
1287
+ $results = array_merge(
1288
+ $results,
1289
+ $this->process_css_declaration_block( $css_item, $css_list, $options )
1290
+ );
1291
+ }
1292
+ } elseif ( $css_item instanceof KeyFrame ) {
1293
+ if ( ! in_array( 'keyframes', $options['allowed_at_rules'], true ) ) {
1294
+ $error = array(
1295
+ 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE,
1296
+ 'at_rule' => $css_item->atRuleName(),
1297
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1298
+ );
1299
+ $sanitized = $this->should_sanitize_validation_error( $error );
1300
+ $results[] = compact( 'error', 'sanitized' );
1301
+ }
1302
+
1303
+ if ( ! $sanitized ) {
1304
+ $results = array_merge(
1305
+ $results,
1306
+ $this->process_css_keyframes( $css_item, $options )
1307
+ );
1308
+ }
1309
+ } elseif ( $css_item instanceof AtRule ) {
1310
+ if ( 'charset' === $css_item->atRuleName() ) {
1311
+ /*
1312
+ * The @charset at-rule is not allowed in style elements, so it is not allowed in AMP.
1313
+ * If the @charset is defined, then it really should have already been acknowledged
1314
+ * by PHP-CSS-Parser when the CSS was parsed in the first place, so at this point
1315
+ * it is irrelevant and can be removed.
1316
+ */
1317
+ $sanitized = true;
1318
+ } else {
1319
+ $error = array(
1320
+ 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE,
1321
+ 'at_rule' => $css_item->atRuleName(),
1322
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1323
+ );
1324
+ $sanitized = $this->should_sanitize_validation_error( $error );
1325
+ $results[] = compact( 'error', 'sanitized' );
1326
+ }
1327
+ } else {
1328
+ $error = array(
1329
+ 'code' => 'unrecognized_css',
1330
+ 'item' => get_class( $css_item ),
1331
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1332
+ );
1333
+ $sanitized = $this->should_sanitize_validation_error( $error );
1334
+ $results[] = compact( 'error', 'sanitized' );
1335
+ }
1336
+
1337
+ if ( $sanitized ) {
1338
+ $css_list->remove( $css_item );
1339
+ }
1340
+ }
1341
+ return $results;
1342
+ }
1343
+
1344
+ /**
1345
+ * Convert URLs in to non-relative real-paths.
1346
+ *
1347
+ * @param URL[] $urls URLs.
1348
+ * @param string $stylesheet_url Stylesheet URL.
1349
+ */
1350
+ private function real_path_urls( $urls, $stylesheet_url ) {
1351
+ $base_url = preg_replace( ':[^/]+(\?.*)?(#.*)?$:', '', $stylesheet_url );
1352
+ if ( empty( $base_url ) ) {
1353
+ return;
1354
+ }
1355
+
1356
+ foreach ( $urls as $url ) {
1357
+ // URLs cannot have spaces in them, so strip them (especially when spaces get erroneously injected in data: URLs).
1358
+ $url_string = $url->getURL()->getString();
1359
+
1360
+ // For data: URLs, all that is needed is to remove spaces so set and continue.
1361
+ if ( 'data:' === substr( $url_string, 0, 5 ) ) {
1362
  continue;
1363
  }
1364
 
1365
+ // If the URL is already absolute, continue since there there is nothing left to do.
1366
+ $parsed_url = wp_parse_url( $url_string );
1367
+ if ( ! empty( $parsed_url['host'] ) || empty( $parsed_url['path'] ) || '/' === substr( $parsed_url['path'], 0, 1 ) ) {
1368
+ continue;
1369
+ }
1370
+
1371
+ $relative_url = preg_replace( '#^\./#', '', $url->getURL()->getString() );
1372
+
1373
+ // Resolve any relative parent directory paths.
1374
+ $real_url = $base_url . $relative_url;
1375
+ do {
1376
+ $real_url = preg_replace( '#[^/]+/../#', '', $real_url, -1, $count );
1377
+ } while ( 0 !== $count );
1378
+
1379
+ $url->getURL()->setString( $real_url );
1380
+ }
1381
+ }
1382
+
1383
+ /**
1384
+ * Process CSS rule set.
1385
+ *
1386
+ * @since 1.0
1387
+ * @link https://www.ampproject.org/docs/design/responsive/style_pages#disallowed-styles
1388
+ * @link https://www.ampproject.org/docs/design/responsive/style_pages#restricted-styles
1389
+ *
1390
+ * @param RuleSet $ruleset Ruleset.
1391
+ * @param CSSList $css_list CSS List.
1392
+ * @param array $options Options.
1393
+ *
1394
+ * @return array Validation results.
1395
+ */
1396
+ private function process_css_declaration_block( RuleSet $ruleset, CSSList $css_list, $options ) {
1397
+ $results = array();
1398
+
1399
+ if ( $ruleset instanceof DeclarationBlock ) {
1400
+ $this->ampify_ruleset_selectors( $ruleset );
1401
+ if ( 0 === count( $ruleset->getSelectors() ) ) {
1402
+ $css_list->remove( $ruleset );
1403
+ return $results;
1404
+ }
1405
+ }
1406
+
1407
+ // Remove disallowed properties.
1408
+ if ( ! empty( $options['property_whitelist'] ) ) {
1409
+ $properties = $ruleset->getRules();
1410
+ foreach ( $properties as $property ) {
1411
+ $vendorless_property_name = preg_replace( '/^-\w+-/', '', $property->getRule() );
1412
+ if ( ! in_array( $vendorless_property_name, $options['property_whitelist'], true ) ) {
1413
+ $error = array(
1414
+ 'code' => 'illegal_css_property',
1415
+ 'property_name' => $property->getRule(),
1416
+ 'property_value' => $property->getValue(),
1417
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1418
+ );
1419
+ $sanitized = $this->should_sanitize_validation_error( $error );
1420
+ if ( $sanitized ) {
1421
+ $ruleset->removeRule( $property->getRule() );
1422
+ }
1423
+ $results[] = compact( 'error', 'sanitized' );
1424
+ }
1425
+ }
1426
+ } else {
1427
+ foreach ( $options['property_blacklist'] as $illegal_property_name ) {
1428
+ $properties = $ruleset->getRules( $illegal_property_name );
1429
+ foreach ( $properties as $property ) {
1430
+ $error = array(
1431
+ 'code' => 'illegal_css_property',
1432
+ 'property_name' => $property->getRule(),
1433
+ 'property_value' => (string) $property->getValue(),
1434
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1435
+ );
1436
+ $sanitized = $this->should_sanitize_validation_error( $error );
1437
+ if ( $sanitized ) {
1438
+ $ruleset->removeRule( $property->getRule() );
1439
+ }
1440
+ $results[] = compact( 'error', 'sanitized' );
1441
+ }
1442
+ }
1443
  }
1444
 
1445
+ if ( $ruleset instanceof AtRuleSet && 'font-face' === $ruleset->atRuleName() ) {
1446
+ $this->process_font_face_at_rule( $ruleset );
1447
+ }
1448
+
1449
+ $results = array_merge(
1450
+ $results,
1451
+ $this->transform_important_qualifiers( $ruleset, $css_list )
1452
+ );
1453
+
1454
+ // Remove the ruleset if it is now empty.
1455
+ if ( 0 === count( $ruleset->getRules() ) ) {
1456
+ $css_list->remove( $ruleset );
1457
+ }
1458
+ // @todo Delete rules with selectors for -amphtml- class and i-amphtml- tags.
1459
+ return $results;
1460
  }
1461
 
1462
  /**
1463
+ * Process @font-face by making src URLs non-relative and converting data: URLs into (assumed) file URLs.
1464
  *
1465
+ * @since 1.0
1466
+ *
1467
+ * @param AtRuleSet $ruleset Ruleset for @font-face.
1468
+ */
1469
+ private function process_font_face_at_rule( AtRuleSet $ruleset ) {
1470
+ $src_properties = $ruleset->getRules( 'src' );
1471
+ if ( empty( $src_properties ) ) {
1472
+ return;
1473
+ }
1474
+
1475
+ foreach ( $src_properties as $src_property ) {
1476
+ $value = $src_property->getValue();
1477
+ if ( ! ( $value instanceof RuleValueList ) ) {
1478
+ continue;
1479
+ }
1480
+
1481
+ /*
1482
+ * The CSS Parser parses a src such as:
1483
+ *
1484
+ * url(data:application/font-woff;...) format('woff'),
1485
+ * url('Genericons.ttf') format('truetype'),
1486
+ * url('Genericons.svg#genericonsregular') format('svg')
1487
+ *
1488
+ * As a list of components consisting of:
1489
+ *
1490
+ * URL,
1491
+ * RuleValueList( CSSFunction, URL ),
1492
+ * RuleValueList( CSSFunction, URL ),
1493
+ * CSSFunction
1494
+ *
1495
+ * Clearly the components here are not logically grouped. So the first step is to fix the order.
1496
+ */
1497
+ $sources = array();
1498
+ foreach ( $value->getListComponents() as $component ) {
1499
+ if ( $component instanceof RuleValueList ) {
1500
+ $subcomponents = $component->getListComponents();
1501
+ $subcomponent = array_shift( $subcomponents );
1502
+ if ( $subcomponent ) {
1503
+ if ( empty( $sources ) ) {
1504
+ $sources[] = array( $subcomponent );
1505
+ } else {
1506
+ $sources[ count( $sources ) - 1 ][] = $subcomponent;
1507
+ }
1508
+ }
1509
+ foreach ( $subcomponents as $subcomponent ) {
1510
+ $sources[] = array( $subcomponent );
1511
+ }
1512
+ } else {
1513
+ if ( empty( $sources ) ) {
1514
+ $sources[] = array( $component );
1515
+ } else {
1516
+ $sources[ count( $sources ) - 1 ][] = $component;
1517
+ }
1518
+ }
1519
+ }
1520
+
1521
+ /**
1522
+ * Source URL lists.
1523
+ *
1524
+ * @var URL[] $source_file_urls
1525
+ * @var URL[] $source_data_urls
1526
+ */
1527
+ $source_file_urls = array();
1528
+ $source_data_urls = array();
1529
+ foreach ( $sources as $i => $source ) {
1530
+ if ( $source[0] instanceof URL ) {
1531
+ if ( 'data:' === substr( $source[0]->getURL()->getString(), 0, 5 ) ) {
1532
+ $source_data_urls[ $i ] = $source[0];
1533
+ } else {
1534
+ $source_file_urls[ $i ] = $source[0];
1535
+ }
1536
+ }
1537
+ }
1538
+
1539
+ // Convert data: URLs into regular URLs, assuming there will be a file present (e.g. woff fonts in core themes).
1540
+ if ( empty( $source_file_urls ) ) {
1541
+ continue;
1542
+ }
1543
+ $source_file_url = current( $source_file_urls );
1544
+ foreach ( $source_data_urls as $i => $data_url ) {
1545
+ $mime_type = strtok( substr( $data_url->getURL()->getString(), 5 ), ';' );
1546
+ if ( ! $mime_type ) {
1547
+ continue;
1548
+ }
1549
+ $extension = preg_replace( ':.+/(.+-)?:', '', $mime_type );
1550
+ $guessed_url = preg_replace(
1551
+ ':(?<=\.)\w+(\?.*)?(#.*)?$:', // Match the file extension in the URL.
1552
+ $extension,
1553
+ $source_file_url->getURL()->getString(),
1554
+ 1,
1555
+ $count
1556
+ );
1557
+ if ( 1 !== $count ) {
1558
+ continue;
1559
+ }
1560
+
1561
+ // Ensure font file exists.
1562
+ $path = $this->get_validated_url_file_path( $guessed_url, array( 'woff', 'woff2', 'ttf', 'otf', 'svg' ) );
1563
+ if ( is_wp_error( $path ) ) {
1564
+ continue;
1565
+ }
1566
+
1567
+ $data_url->getURL()->setString( $guessed_url );
1568
+ break;
1569
+ }
1570
+ }
1571
+ }
1572
+
1573
+ /**
1574
+ * Process CSS keyframes.
1575
+ *
1576
+ * @since 1.0
1577
+ * @link https://www.ampproject.org/docs/design/responsive/style_pages#restricted-styles.
1578
+ * @link https://github.com/ampproject/amphtml/blob/b685a0780a7f59313666225478b2b79b463bcd0b/validator/validator-main.protoascii#L1002-L1043
1579
+ * @todo Tree shaking could be extended to keyframes, to omit a keyframe if it is not referenced by any rule.
1580
+ *
1581
+ * @param KeyFrame $css_list Ruleset.
1582
+ * @param array $options Options.
1583
+ * @return array Validation results.
1584
+ */
1585
+ private function process_css_keyframes( KeyFrame $css_list, $options ) {
1586
+ $results = array();
1587
+ if ( ! empty( $options['property_whitelist'] ) ) {
1588
+ foreach ( $css_list->getContents() as $rules ) {
1589
+ if ( ! ( $rules instanceof DeclarationBlock ) ) {
1590
+ $error = array(
1591
+ 'code' => 'unrecognized_css',
1592
+ 'item' => get_class( $rules ),
1593
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1594
+ );
1595
+ $sanitized = $this->should_sanitize_validation_error( $error );
1596
+ if ( $sanitized ) {
1597
+ $css_list->remove( $rules );
1598
+ }
1599
+ $results[] = compact( 'error', 'sanitized' );
1600
+ continue;
1601
+ }
1602
+
1603
+ $results = array_merge(
1604
+ $results,
1605
+ $this->transform_important_qualifiers( $rules, $css_list )
1606
+ );
1607
+
1608
+ $properties = $rules->getRules();
1609
+ foreach ( $properties as $property ) {
1610
+ $vendorless_property_name = preg_replace( '/^-\w+-/', '', $property->getRule() );
1611
+ if ( ! in_array( $vendorless_property_name, $options['property_whitelist'], true ) ) {
1612
+ $error = array(
1613
+ 'code' => 'illegal_css_property',
1614
+ 'property_name' => $property->getRule(),
1615
+ 'property_value' => (string) $property->getValue(),
1616
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1617
+ );
1618
+ $sanitized = $this->should_sanitize_validation_error( $error );
1619
+ if ( $sanitized ) {
1620
+ $rules->removeRule( $property->getRule() );
1621
+ }
1622
+ $results[] = compact( 'error', 'sanitized' );
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ return $results;
1628
+ }
1629
+
1630
+ /**
1631
+ * Replace !important qualifiers with more specific rules.
1632
+ *
1633
+ * @since 1.0
1634
+ * @see https://www.npmjs.com/package/replace-important
1635
+ * @see https://www.ampproject.org/docs/fundamentals/spec#important
1636
+ *
1637
+ * @param RuleSet|DeclarationBlock $ruleset Rule set.
1638
+ * @param CSSList $css_list CSS List.
1639
+ * @return array Validation results.
1640
+ */
1641
+ private function transform_important_qualifiers( RuleSet $ruleset, CSSList $css_list ) {
1642
+ $results = array();
1643
+
1644
+ // An !important only makes sense for rulesets that have selectors.
1645
+ $allow_transformation = (
1646
+ $ruleset instanceof DeclarationBlock
1647
+ &&
1648
+ ! ( $css_list instanceof KeyFrame )
1649
+ );
1650
+
1651
+ $properties = $ruleset->getRules();
1652
+ $importants = array();
1653
+ foreach ( $properties as $property ) {
1654
+ if ( $property->getIsImportant() ) {
1655
+ if ( $allow_transformation ) {
1656
+ $importants[] = $property;
1657
+ $property->setIsImportant( false );
1658
+ $ruleset->removeRule( $property->getRule() );
1659
+ } else {
1660
+ $error = array(
1661
+ 'code' => 'illegal_css_important',
1662
+ 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE,
1663
+ );
1664
+ $sanitized = $this->should_sanitize_validation_error( $error );
1665
+ if ( $sanitized ) {
1666
+ $property->setIsImportant( false );
1667
+ }
1668
+ $results[] = compact( 'error', 'sanitized' );
1669
+ }
1670
+ }
1671
+ }
1672
+ if ( ! $allow_transformation || empty( $importants ) ) {
1673
+ return $results;
1674
+ }
1675
+
1676
+ $important_ruleset = clone $ruleset;
1677
+ $important_ruleset->setSelectors( array_map(
1678
+ /**
1679
+ * Modify selectors to be more specific to roughly match the effect of !important.
1680
+ *
1681
+ * @link https://github.com/ampproject/ampstart/blob/4c21d69afdd07b4c60cd190937bda09901955829/tools/replace-important/lib/index.js#L88-L109
1682
+ *
1683
+ * @param Selector $old_selector Original selector.
1684
+ * @return Selector The new more-specific selector.
1685
+ */
1686
+ function( Selector $old_selector ) {
1687
+ // Calculate the specificity multiplier for the placeholder.
1688
+ $specificity_multiplier = AMP_Style_Sanitizer::INLINE_SPECIFICITY_MULTIPLIER + 1 + floor( $old_selector->getSpecificity() / 100 );
1689
+ if ( $old_selector->getSpecificity() % 100 > 0 ) {
1690
+ $specificity_multiplier++;
1691
+