AMP for WordPress - Version 0.7.0

Version Description

(2018-05-03) =

  • Render an entire site as "Native AMP" if the theme calls add_theme_support( 'amp' ). See #857, #852, #865, #888. Props westonruter, kaitnyl, ThierryA.
  • Use the AMP spec to automatically discover the required AMP component scripts to include on the page while post-processing. See #882, #885. Props westonruter.
  • Automatically concatenate stylesheets from style tags with loaded stylesheets from link tags combined in one style[amp-custom]. See #887, #890, #935. Props westonruter.
  • Update serialization to use HTML instead of XML; update minimum version of PHP fro, 5.2 to 5.3. See #891.
  • Add support for widgets. See #870. Props kienstra.
  • Add support for forms. See #907, #923. Props DavidCramer.
  • Use "Paired Mode" if the theme calls add_theme_support( 'amp' ) and passes a 'template_dir' value for the AMP templates. See #856, #877. Props westonruter, kaitnyl.
  • Add AMP implementations of audio/video playlists. See #954. Props kienstra.
  • Allow full Customization when the theme supports 'amp'. See #952. Props westonruter.
  • Add support for all default WordPress widgets. See #921, #917. Props kienstra, westonruter.
  • Add support for more default embeds: Issuu, Post, Meetup, Reddit, Screencast, Tumblr, and WordPress Plugin Directory. See #889. Props kaitnyl.
  • Allow native WordPress commenting, in fully valid AMP. See #1024, #1029, #871, #909. Props DavidCramer, westonruter.
  • Add a UI for displaying validation errors, including invalid tags and attributes, with tracing for the source for each error according to which theme/plugin's shortcode, widget, or other hook is responsible. Includes debug mode to suspend sanitizer. See #971, #1012, #1016. Props westonruter, kienstra.
  • On activating a plugin, validate a front-end page and display a notice if there were errors. See #971. Props westonruter, kienstra.
  • Creation of AMP-related notifications, on entering invalid content in the 'classic' editor. See #912. Props kienstra, westonruter, ThierryA.
  • Optionally use <amp-live-list> to display comments, avoiding full-page refreshes on adding comments. And enable making requests for an <amp-live-list>, like for displaying posts. See #1029, #915. Props DavidCramer, westonruter.
  • Support <amp-bind>, enabling more dynamic elements. See #895. Props westonruter.
  • Add output buffering, ensuring the entire page is valid AMP. See #929, #857, #931. Props westonruter, ThierryA.
  • Add validation of host names in URLs. See #983. Props rubengonzalezmrf.
  • Add WP-CLI scripts to test AMP support of comments and widgets. See #924, #859. Props DavidCramer, kienstra.
  • Improve test coverage, including for AMP_Theme_Support. See #1034. Props DavidCramer, kienstra.
  • Update the generated sanitizer file to the AMP spec, and simplify the file that generates it. See #929, #926. Props westonruter.
  • Several sanitizer updates, including for styles, and preventing valid tags from being removed. See #935, #944, #952. Props westonruter, davisshaver.
  • Improve sanitization of <amp-img>, <amp-video>, and <amp-iframe>. See #937, #1054. Props kienstra, amedina.
  • Fix an issue where the JSON inside <script type="application/json"> was wrapped with CDATA. See #891. Props westonruter.
  • Allow use of AMP components outside of AMP documents, including in PWA. See #1013. Props westonruter.
  • Access the AMP query var with amp_get_slug(), instead of AMP_QUERY_VAR. See #986. Props westonruter, mjangda.
  • Update build scripts, including PHP versions in .travis.yml. See #1058, #949. Props westonruter.
  • Prevent New Relic script from being injected in AMP responses. See #932. Props westonruter.
  • Fix handling of 0 and empty height/width attributes. See #979. Props davisshaver.

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

Contributors in this release, including design, development, testing, and project management: Adam Silverstein (adamsilverstein), Alberto Medina (amedina), Christian Chung (christianc1), Claudio Sossi, David Cramer (DavidCramer), Davis Shaver (davisshaver), Douglas Paul (douglyuckling), Jason Johnston (jhnstn), Joshua Wold (jwold), Kaitlyn (kaitnyl), Leo Postovoit (postphotos), Mackenzie Hartung (MackenzieHartung), Maxim Siebert (MaximSiebert), Mike Crantea (mehigh), Mohammad Jangda (mjangda), Oscar Sanchez (oscarssanchez), Philip John (philipjohn), Piotr Delawski (delawski), Renato Alves (renatonascalves), Rubn (rubengonzalezmrf), Ryan Kienstra (kienstra), Thierry Muller (ThierryA), vortfu, Weston Ruter (westonruter), Ziga Sancin (zigasancin).

Download this release

Release Info

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

Code changes from version 0.6.2 to 0.7.0

Files changed (56) hide show
  1. amp.php +128 -60
  2. assets/css/amp-default.css +11 -0
  3. assets/css/amp-playlist-shortcode.css +19 -0
  4. assets/js/amp-post-meta-box.js +4 -1
  5. includes/admin/class-amp-customizer.php +1 -1
  6. includes/admin/class-amp-post-meta-box.php +9 -2
  7. includes/admin/functions.php +28 -14
  8. includes/amp-frontend-actions.php +14 -2
  9. includes/amp-helper-functions.php +542 -6
  10. includes/amp-post-template-actions.php +29 -49
  11. includes/class-amp-autoloader.php +15 -0
  12. includes/class-amp-comment-walker.php +121 -0
  13. includes/class-amp-post-type-support.php +3 -3
  14. includes/class-amp-theme-support.php +1201 -0
  15. includes/embeds/class-amp-base-embed-handler.php +10 -0
  16. includes/embeds/class-amp-dailymotion-embed.php +0 -11
  17. includes/embeds/class-amp-facebook-embed.php +0 -11
  18. includes/embeds/class-amp-gallery-embed.php +92 -49
  19. includes/embeds/class-amp-instagram-embed.php +0 -11
  20. includes/embeds/class-amp-issuu-embed-handler.php +65 -0
  21. includes/embeds/class-amp-meetup-embed-handler.php +45 -0
  22. includes/embeds/class-amp-pinterest-embed.php +0 -11
  23. includes/embeds/class-amp-playlist-embed-handler.php +324 -0
  24. includes/embeds/class-amp-reddit-embed-handler.php +74 -0
  25. includes/embeds/class-amp-soundcloud-embed.php +1 -28
  26. includes/embeds/class-amp-tumblr-embed-handler.php +60 -0
  27. includes/embeds/class-amp-twitter-embed.php +0 -11
  28. includes/embeds/class-amp-vimeo-embed.php +24 -11
  29. includes/embeds/class-amp-vine-embed.php +0 -11
  30. includes/embeds/class-amp-youtube-embed.php +25 -12
  31. includes/options/class-amp-options-manager.php +2 -2
  32. includes/options/class-amp-options-menu.php +15 -5
  33. includes/sanitizers/class-amp-allowed-tags-generated.php +3953 -1320
  34. includes/sanitizers/class-amp-audio-sanitizer.php +1 -37
  35. includes/sanitizers/class-amp-base-sanitizer.php +122 -48
  36. includes/sanitizers/class-amp-blacklist-sanitizer.php +11 -9
  37. includes/sanitizers/class-amp-comments-sanitizer.php +156 -0
  38. includes/sanitizers/class-amp-form-sanitizer.php +141 -0
  39. includes/sanitizers/class-amp-iframe-sanitizer.php +7 -40
  40. includes/sanitizers/class-amp-img-sanitizer.php +70 -49
  41. includes/sanitizers/class-amp-playbuzz-sanitizer.php +1 -38
  42. includes/sanitizers/class-amp-rule-spec.php +63 -16
  43. includes/sanitizers/class-amp-style-sanitizer.php +431 -45
  44. includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php +594 -129
  45. includes/sanitizers/class-amp-video-sanitizer.php +5 -40
  46. includes/templates/class-amp-content-sanitizer.php +58 -13
  47. includes/templates/class-amp-post-template.php +4 -124
  48. includes/utils/class-amp-dom-utils.php +320 -70
  49. includes/utils/class-amp-validation-utils.php +1848 -0
  50. includes/widgets/class-amp-widget-archives.php +103 -0
  51. includes/widgets/class-amp-widget-categories.php +91 -0
  52. includes/widgets/class-amp-widget-media-video.php +33 -0
  53. includes/widgets/class-amp-widget-recent-comments.php +38 -0
  54. includes/widgets/class-amp-widget-text.php +33 -0
  55. readme.txt +55 -12
  56. wpcom-helper.php +97 -31
amp.php CHANGED
@@ -3,9 +3,9 @@
3
  * Plugin Name: AMP
4
  * Description: Add AMP support to your WordPress site.
5
  * Plugin URI: https://github.com/automattic/amp-wp
6
- * Author: Automattic
7
- * Author URI: https://automattic.com
8
- * Version: 0.6.2
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
@@ -13,9 +13,26 @@
13
  * @package AMP
14
  */
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  define( 'AMP__FILE__', __FILE__ );
17
  define( 'AMP__DIR__', dirname( __FILE__ ) );
18
- define( 'AMP__VERSION', '0.6.2' );
19
 
20
  require_once AMP__DIR__ . '/includes/class-amp-autoloader.php';
21
  AMP_Autoloader::register();
@@ -38,7 +55,7 @@ function amp_deactivate() {
38
  // We need to manually remove the amp endpoint
39
  global $wp_rewrite;
40
  foreach ( $wp_rewrite->endpoints as $index => $endpoint ) {
41
- if ( AMP_QUERY_VAR === $endpoint[1] ) {
42
  unset( $wp_rewrite->endpoints[ $index ] );
43
  break;
44
  }
@@ -47,6 +64,15 @@ function amp_deactivate() {
47
  flush_rewrite_rules();
48
  }
49
 
 
 
 
 
 
 
 
 
 
50
  /**
51
  * Set up AMP.
52
  *
@@ -56,27 +82,13 @@ function amp_deactivate() {
56
  * @since 0.6
57
  */
58
  function amp_after_setup_theme() {
 
 
59
  if ( false === apply_filters( 'amp_is_enabled', true ) ) {
60
  return;
61
  }
62
 
63
- if ( ! defined( 'AMP_QUERY_VAR' ) ) {
64
- /**
65
- * Filter the AMP query variable.
66
- *
67
- * @since 0.3.2
68
- * @param string $query_var The AMP query variable.
69
- */
70
- define( 'AMP_QUERY_VAR', apply_filters( 'amp_query_var', 'amp' ) );
71
- }
72
-
73
- add_action( 'init', 'amp_init' );
74
- add_action( 'admin_init', 'AMP_Options_Manager::register_settings' );
75
- add_filter( 'amp_post_template_analytics', 'amp_add_custom_analytics' );
76
- add_action( 'wp_loaded', 'amp_post_meta_box' );
77
- add_action( 'wp_loaded', 'amp_add_options_menu' );
78
- add_action( 'parse_query', 'amp_correct_query_when_is_front_page' );
79
- AMP_Post_Type_Support::add_post_type_support();
80
  }
81
  add_action( 'after_setup_theme', 'amp_after_setup_theme', 5 );
82
 
@@ -96,28 +108,83 @@ function amp_init() {
96
 
97
  load_plugin_textdomain( 'amp', false, plugin_basename( AMP__DIR__ ) . '/languages' );
98
 
99
- add_rewrite_endpoint( AMP_QUERY_VAR, EP_PERMALINK );
100
 
 
 
 
101
  add_filter( 'request', 'amp_force_query_var_value' );
102
- add_action( 'wp', 'amp_maybe_add_actions' );
 
 
 
103
 
104
  // Redirect the old url of amp page to the updated url.
105
  add_filter( 'old_slug_redirect_url', 'amp_redirect_old_slug_to_new_url' );
106
 
107
  if ( class_exists( 'Jetpack' ) && ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
108
- require_once( AMP__DIR__ . '/jetpack-helper.php' );
109
  }
 
 
 
110
  }
111
 
112
  // Make sure the `amp` query var has an explicit value.
113
  // Avoids issues when filtering the deprecated `query_string` hook.
114
  function amp_force_query_var_value( $query_vars ) {
115
- if ( isset( $query_vars[ AMP_QUERY_VAR ] ) && '' === $query_vars[ AMP_QUERY_VAR ] ) {
116
- $query_vars[ AMP_QUERY_VAR ] = 1;
117
  }
118
  return $query_vars;
119
  }
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  /**
122
  * Fix up WP_Query for front page when amp query var is present.
123
  *
@@ -136,7 +203,7 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) {
136
  $query->is_home()
137
  &&
138
  // Is AMP endpoint.
139
- false !== $query->get( AMP_QUERY_VAR, false )
140
  &&
141
  // Is query not yet fixed uo up to be front page.
142
  ! $query->is_front_page()
@@ -148,7 +215,7 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) {
148
  get_option( 'page_on_front' )
149
  &&
150
  // See line in WP_Query::parse_query() at <https://github.com/WordPress/wordpress-develop/blob/0baa8ae/src/wp-includes/class-wp-query.php#L961>.
151
- 0 === count( array_diff( array_keys( wp_parse_args( $query->query ) ), array( AMP_QUERY_VAR, 'preview', 'page', 'paged', 'cpage' ) ) )
152
  );
153
  if ( $is_front_page_query ) {
154
  $query->is_home = false;
@@ -159,38 +226,28 @@ function amp_correct_query_when_is_front_page( WP_Query $query ) {
159
  }
160
 
161
  /**
162
- * Add AMP actions when the request can be served as AMP.
163
  *
164
- * Actions will only be added if the request is for a singular post (including front page and page for posts), excluding feeds.
 
 
 
 
165
  *
166
- * @since 0.2
167
  */
168
- function amp_maybe_add_actions() {
169
- global $wp_query;
170
- if ( ! ( is_singular() || $wp_query->is_posts_page ) || is_feed() ) {
171
- return;
172
  }
173
- $is_amp_endpoint = is_amp_endpoint();
174
-
175
- /**
176
- * Queried post object.
177
- *
178
- * @var WP_Post $post
179
- */
180
- $post = get_queried_object();
181
- if ( ! post_supports_amp( $post ) ) {
182
- if ( $is_amp_endpoint ) {
183
- wp_safe_redirect( get_permalink( $post->ID ), 302 ); // Temporary redirect because AMP may be supported in future.
184
- exit;
185
  }
186
- return;
187
- }
188
-
189
- if ( $is_amp_endpoint ) {
190
- amp_prepare_render();
191
- } else {
192
- amp_add_frontend_actions();
193
  }
 
194
  }
195
 
196
  function amp_load_classes() {
@@ -248,9 +305,14 @@ function amp_render_post( $post ) {
248
  * which is not ideal for any code that expects to run in an AMP context.
249
  * Let's force the value to be true while we render AMP.
250
  */
251
- $was_set = isset( $wp_query->query_vars[ AMP_QUERY_VAR ] );
252
  if ( ! $was_set ) {
253
- $wp_query->query_vars[ AMP_QUERY_VAR ] = true;
 
 
 
 
 
254
  }
255
 
256
  /**
@@ -267,17 +329,23 @@ function amp_render_post( $post ) {
267
  $template->load();
268
 
269
  if ( ! $was_set ) {
270
- unset( $wp_query->query_vars[ AMP_QUERY_VAR ] );
271
  }
272
  }
273
 
274
  /**
275
  * Bootstraps the AMP customizer.
276
  *
 
 
 
 
 
 
277
  * @since 0.4
278
  */
279
  function _amp_bootstrap_customizer() {
280
- add_action( 'after_setup_theme', 'amp_init_customizer' );
281
  }
282
  add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 ); // Should be hooked before priority 10 on 'plugins_loaded' to properly unhook core panels.
283
 
@@ -292,7 +360,7 @@ add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 ); // Should be hoo
292
  function amp_redirect_old_slug_to_new_url( $link ) {
293
 
294
  if ( is_amp_endpoint() ) {
295
- $link = trailingslashit( trailingslashit( $link ) . AMP_QUERY_VAR );
296
  }
297
 
298
  return $link;
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.0
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
13
  * @package AMP
14
  */
15
 
16
+ /**
17
+ * Print admin notice regarding having an old version of PHP.
18
+ *
19
+ * @since 0.7
20
+ */
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.0' );
36
 
37
  require_once AMP__DIR__ . '/includes/class-amp-autoloader.php';
38
  AMP_Autoloader::register();
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] ) {
59
  unset( $wp_rewrite->endpoints[ $index ] );
60
  break;
61
  }
64
  flush_rewrite_rules();
65
  }
66
 
67
+ /*
68
+ * Register AMP scripts regardless of whether AMP is enabled or it is the AMP endpoint
69
+ * for the sake of being able to use AMP components on non-AMP documents ("dirty AMP").
70
+ */
71
+ add_action( 'wp_default_scripts', 'amp_register_default_scripts' );
72
+
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
  *
82
  * @since 0.6
83
  */
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
  }
90
 
91
+ add_action( 'init', 'amp_init', 0 ); // Must be 0 because widgets_init happens at init priority 1.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
  add_action( 'after_setup_theme', 'amp_after_setup_theme', 5 );
94
 
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_Validation_Utils::init();
114
+ AMP_Theme_Support::init();
115
+ AMP_Post_Type_Support::add_post_type_support();
116
  add_filter( 'request', 'amp_force_query_var_value' );
117
+ add_action( 'admin_init', 'AMP_Options_Manager::register_settings' );
118
+ add_action( 'wp_loaded', 'amp_post_meta_box' );
119
+ add_action( 'wp_loaded', 'amp_add_options_menu' );
120
+ add_action( 'parse_query', 'amp_correct_query_when_is_front_page' );
121
 
122
  // Redirect the old url of amp page to the updated url.
123
  add_filter( 'old_slug_redirect_url', 'amp_redirect_old_slug_to_new_url' );
124
 
125
  if ( class_exists( 'Jetpack' ) && ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
126
+ require_once AMP__DIR__ . '/jetpack-helper.php';
127
  }
128
+
129
+ // Add actions for legacy post templates.
130
+ add_action( 'wp', 'amp_maybe_add_actions' );
131
  }
132
 
133
  // Make sure the `amp` query var has an explicit value.
134
  // Avoids issues when filtering the deprecated `query_string` hook.
135
  function amp_force_query_var_value( $query_vars ) {
136
+ if ( isset( $query_vars[ amp_get_slug() ] ) && '' === $query_vars[ amp_get_slug() ] ) {
137
+ $query_vars[ amp_get_slug() ] = 1;
138
  }
139
  return $query_vars;
140
  }
141
 
142
+ /**
143
+ * Conditionally add AMP actions or render the 'paired mode' template(s).
144
+ *
145
+ * If the request is for an AMP page and this is in 'canonical mode,' redirect to the non-AMP page.
146
+ * It won't need this plugin's template system, nor the frontend actions like the 'rel' link.
147
+ *
148
+ * @global WP_Query $wp_query
149
+ * @since 0.2
150
+ * @return void
151
+ */
152
+ function amp_maybe_add_actions() {
153
+
154
+ // Short-circuit when theme supports AMP, as everything is handled by AMP_Theme_Support.
155
+ if ( current_theme_supports( 'amp' ) ) {
156
+ return;
157
+ }
158
+
159
+ // The remaining logic here is for paired mode running in themes that don't support AMP, the template system in AMP<=0.6.
160
+ global $wp_query;
161
+ if ( ! ( is_singular() || $wp_query->is_posts_page ) || is_feed() ) {
162
+ return;
163
+ }
164
+
165
+ $is_amp_endpoint = is_amp_endpoint();
166
+
167
+ /**
168
+ * Queried post object.
169
+ *
170
+ * @var WP_Post $post
171
+ */
172
+ $post = get_queried_object();
173
+ if ( ! post_supports_amp( $post ) ) {
174
+ if ( $is_amp_endpoint ) {
175
+ wp_safe_redirect( get_permalink( $post->ID ), 302 ); // Temporary redirect because AMP may be supported in future.
176
+ exit;
177
+ }
178
+ return;
179
+ }
180
+
181
+ if ( $is_amp_endpoint ) {
182
+ amp_prepare_render();
183
+ } else {
184
+ amp_add_frontend_actions();
185
+ }
186
+ }
187
+
188
  /**
189
  * Fix up WP_Query for front page when amp query var is present.
190
  *
203
  $query->is_home()
204
  &&
205
  // Is AMP endpoint.
206
+ false !== $query->get( amp_get_slug(), false )
207
  &&
208
  // Is query not yet fixed uo up to be front page.
209
  ! $query->is_front_page()
215
  get_option( 'page_on_front' )
216
  &&
217
  // See line in WP_Query::parse_query() at <https://github.com/WordPress/wordpress-develop/blob/0baa8ae/src/wp-includes/class-wp-query.php#L961>.
218
+ 0 === count( array_diff( array_keys( wp_parse_args( $query->query ) ), array( amp_get_slug(), 'preview', 'page', 'paged', 'cpage' ) ) )
219
  );
220
  if ( $is_front_page_query ) {
221
  $query->is_home = false;
226
  }
227
 
228
  /**
229
+ * Whether this is in 'canonical mode.'
230
  *
231
+ * Themes can register support for this with `add_theme_support( 'amp' )`.
232
+ * Then, this will change the plugin from 'paired mode,' and it won't use its own templates.
233
+ * Nor output frontend markup like the 'rel' link. If the theme registers support for AMP with:
234
+ * `add_theme_support( 'amp', array( 'template_dir' => 'my-amp-templates' ) )`
235
+ * it will retain 'paired mode.
236
  *
237
+ * @return boolean Whether this is in AMP 'canonical mode'.
238
  */
239
+ function amp_is_canonical() {
240
+ $support = get_theme_support( 'amp' );
241
+ if ( true === $support ) {
242
+ return true;
243
  }
244
+ if ( is_array( $support ) ) {
245
+ $args = array_shift( $support );
246
+ if ( empty( $args['template_dir'] ) ) {
247
+ return true;
 
 
 
 
 
 
 
 
248
  }
 
 
 
 
 
 
 
249
  }
250
+ return false;
251
  }
252
 
253
  function amp_load_classes() {
305
  * which is not ideal for any code that expects to run in an AMP context.
306
  * Let's force the value to be true while we render AMP.
307
  */
308
+ $was_set = isset( $wp_query->query_vars[ amp_get_slug() ] );
309
  if ( ! $was_set ) {
310
+ $wp_query->query_vars[ amp_get_slug() ] = true;
311
+ }
312
+
313
+ // Prevent New Relic from causing invalid AMP responses due the NREUM script it injects after the meta charset.
314
+ if ( extension_loaded( 'newrelic' ) ) {
315
+ newrelic_disable_autorum();
316
  }
317
 
318
  /**
329
  $template->load();
330
 
331
  if ( ! $was_set ) {
332
+ unset( $wp_query->query_vars[ amp_get_slug() ] );
333
  }
334
  }
335
 
336
  /**
337
  * Bootstraps the AMP customizer.
338
  *
339
+ * Uses the priority of 12 for the 'after_setup_theme' action.
340
+ * Many themes run `add_theme_support()` on the 'after_setup_theme' hook, at the default priority of 10.
341
+ * And that function's documentation suggests adding it to that action.
342
+ * So this enables themes to `add_theme_support( 'amp' )`.
343
+ * And `amp_init_customizer()` will be able to recognize theme support by calling `amp_is_canonical()`.
344
+ *
345
  * @since 0.4
346
  */
347
  function _amp_bootstrap_customizer() {
348
+ add_action( 'after_setup_theme', 'amp_init_customizer', 12 );
349
  }
350
  add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 ); // Should be hooked before priority 10 on 'plugins_loaded' to properly unhook core panels.
351
 
360
  function amp_redirect_old_slug_to_new_url( $link ) {
361
 
362
  if ( is_amp_endpoint() ) {
363
+ $link = trailingslashit( trailingslashit( $link ) . amp_get_slug() );
364
  }
365
 
366
  return $link;
assets/css/amp-default.css ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
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
+ }
assets/css/amp-playlist-shortcode.css ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * For the custom AMP implementation of the 'playlist' shortcode.
3
+ */
4
+ .wp-playlist .wp-playlist-current-item img {
5
+ margin-right: 0;
6
+ }
7
+
8
+ .wp-playlist .wp-playlist-current-item amp-img {
9
+ float: left;
10
+ margin-right: 10px;
11
+ }
12
+
13
+ .wp-playlist audio {
14
+ display: block;
15
+ }
16
+
17
+ .wp-playlist .amp-carousel-button {
18
+ visibility: hidden;
19
+ }
assets/js/amp-post-meta-box.js CHANGED
@@ -18,6 +18,7 @@ var ampPostMetaBox = ( function( $ ) {
18
  * @since 0.6
19
  */
20
  data: {
 
21
  previewLink: '',
22
  enabled: true, // Overridden by post_supports_amp( $post ).
23
  canSupport: true, // Overridden by count( AMP_Post_Type_Support::get_support_errors( $post ) ) === 0.
@@ -60,7 +61,7 @@ var ampPostMetaBox = ( function( $ ) {
60
  component.data = data;
61
  $( document ).ready( function() {
62
  component.statusRadioInputs = $( '[name="' + component.data.statusInputName + '"]' );
63
- if ( component.data.enabled ) {
64
  component.addPreviewButton();
65
  }
66
  component.listen();
@@ -160,6 +161,8 @@ var ampPostMetaBox = ( function( $ ) {
160
  editAmpStatus.fadeToggle( component.toggleSpeed, function() {
161
  if ( editAmpStatus.is( ':visible' ) ) {
162
  editAmpStatus.focus();
 
 
163
  }
164
  } );
165
  $container.slideToggle( component.toggleSpeed );
18
  * @since 0.6
19
  */
20
  data: {
21
+ canonical: false, // Overridden by amp_is_canonical().
22
  previewLink: '',
23
  enabled: true, // Overridden by post_supports_amp( $post ).
24
  canSupport: true, // Overridden by count( AMP_Post_Type_Support::get_support_errors( $post ) ) === 0.
61
  component.data = data;
62
  $( document ).ready( function() {
63
  component.statusRadioInputs = $( '[name="' + component.data.statusInputName + '"]' );
64
+ if ( component.data.enabled && ! component.data.canonical ) {
65
  component.addPreviewButton();
66
  }
67
  component.listen();
161
  editAmpStatus.fadeToggle( component.toggleSpeed, function() {
162
  if ( editAmpStatus.is( ':visible' ) ) {
163
  editAmpStatus.focus();
164
+ } else {
165
+ $container.find( 'input[type="radio"]' ).first().focus();
166
  }
167
  } );
168
  $container.slideToggle( component.toggleSpeed );
includes/admin/class-amp-customizer.php CHANGED
@@ -134,7 +134,7 @@ class AMP_Template_Customizer {
134
 
135
  wp_add_inline_script( 'amp-customize-controls', sprintf( 'ampCustomizeControls.boot( %s );',
136
  wp_json_encode( array(
137
- 'queryVar' => AMP_QUERY_VAR,
138
  'panelId' => self::PANEL_ID,
139
  'ampUrl' => amp_admin_get_preview_permalink(),
140
  'l10n' => array(
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(
includes/admin/class-amp-post-meta-box.php CHANGED
@@ -121,6 +121,8 @@ class AMP_Post_Meta_Box {
121
  isset( $screen->base )
122
  &&
123
  'post' === $screen->base
 
 
124
  );
125
  if ( ! $validate ) {
126
  return;
@@ -143,7 +145,8 @@ class AMP_Post_Meta_Box {
143
  );
144
  wp_add_inline_script( self::ASSETS_HANDLE, sprintf( 'ampPostMetaBox.boot( %s );',
145
  wp_json_encode( array(
146
- 'previewLink' => esc_url_raw( add_query_arg( AMP_QUERY_VAR, '', get_preview_post_link( $post ) ) ),
 
147
  'enabled' => post_supports_amp( $post ),
148
  'canSupport' => count( AMP_Post_Type_Support::get_support_errors( $post ) ) === 0,
149
  'statusInputName' => self::STATUS_INPUT_NAME,
@@ -164,7 +167,11 @@ class AMP_Post_Meta_Box {
164
  $verify = (
165
  isset( $post->ID )
166
  &&
 
 
167
  current_user_can( 'edit_post', $post->ID )
 
 
168
  );
169
 
170
  if ( true !== $verify ) {
@@ -230,7 +237,7 @@ class AMP_Post_Meta_Box {
230
  );
231
 
232
  if ( $is_amp ) {
233
- $link = add_query_arg( AMP_QUERY_VAR, true, $link );
234
  }
235
 
236
  return $link;
121
  isset( $screen->base )
122
  &&
123
  'post' === $screen->base
124
+ &&
125
+ is_post_type_viewable( $post->post_type )
126
  );
127
  if ( ! $validate ) {
128
  return;
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,
167
  $verify = (
168
  isset( $post->ID )
169
  &&
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 ) {
237
  );
238
 
239
  if ( $is_amp ) {
240
+ $link = add_query_arg( amp_get_slug(), true, $link );
241
  }
242
 
243
  return $link;
includes/admin/functions.php CHANGED
@@ -15,8 +15,16 @@ define( 'AMP_CUSTOMIZER_QUERY_VAR', 'customize_amp' );
15
 
16
  /**
17
  * Sets up the AMP template editor for the Customizer.
 
 
 
 
18
  */
19
  function amp_init_customizer() {
 
 
 
 
20
  // Fire up the AMP Customizer.
21
  add_action( 'customize_register', array( 'AMP_Template_Customizer', 'init' ), 500 );
22
 
@@ -40,7 +48,7 @@ function amp_admin_get_preview_permalink() {
40
  */
41
  $post_type = (string) apply_filters( 'amp_customizer_post_type', 'post' );
42
 
43
- if ( ! post_type_supports( $post_type, AMP_QUERY_VAR ) ) {
44
  return null;
45
  }
46
 
@@ -107,23 +115,29 @@ function amp_add_options_menu() {
107
  /**
108
  * Add custom analytics.
109
  *
 
 
 
 
 
110
  * @param array $analytics Analytics.
111
  * @return array Analytics.
112
  */
113
- function amp_add_custom_analytics( $analytics ) {
114
- $analytics_entries = AMP_Options_Manager::get_option( 'analytics', array() );
115
-
116
- if ( ! $analytics_entries ) {
117
- return $analytics;
118
- }
119
 
120
- foreach ( $analytics_entries as $entry_id => $entry ) {
121
- $analytics[ $entry_id ] = array(
122
- 'type' => $entry['type'],
123
- 'attributes' => array(),
124
- 'config_data' => json_decode( $entry['config'] ),
125
- );
126
- }
 
 
 
 
 
127
 
128
  return $analytics;
129
  }
15
 
16
  /**
17
  * Sets up the AMP template editor for the Customizer.
18
+ *
19
+ * If this is in AMP canonical mode, exit.
20
+ * There's no need for the 'AMP' Customizer panel,
21
+ * And this does not need to toggle between the AMP and normal display.
22
  */
23
  function amp_init_customizer() {
24
+ if ( amp_is_canonical() ) {
25
+ return;
26
+ }
27
+
28
  // Fire up the AMP Customizer.
29
  add_action( 'customize_register', array( 'AMP_Template_Customizer', 'init' ), 500 );
30
 
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
 
115
  /**
116
  * Add custom analytics.
117
  *
118
+ * This is currently only used for legacy AMP post templates.
119
+ *
120
+ * @since 0.5
121
+ * @see amp_get_analytics()
122
+ *
123
  * @param array $analytics Analytics.
124
  * @return array Analytics.
125
  */
126
+ function amp_add_custom_analytics( $analytics = array() ) {
127
+ $analytics = amp_get_analytics( $analytics );
 
 
 
 
128
 
129
+ /**
130
+ * Add amp-analytics tags.
131
+ *
132
+ * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting.
133
+ * This filter should be used to alter entries for legacy AMP templates.
134
+ *
135
+ * @since 0.4
136
+ *
137
+ * @param array $analytics An associative array of the analytics entries we want to output. Each array entry must have a unique key, and the value should be an array with the following keys: `type`, `attributes`, `script_data`. See readme for more details.
138
+ * @param WP_Post $post The current post.
139
+ */
140
+ $analytics = apply_filters( 'amp_post_template_analytics', $analytics, get_queried_object() );
141
 
142
  return $analytics;
143
  }
includes/amp-frontend-actions.php CHANGED
@@ -10,6 +10,8 @@ add_action( 'wp_head', 'amp_frontend_add_canonical' );
10
  /**
11
  * Add amphtml link to frontend.
12
  *
 
 
13
  * @since 0.2
14
  */
15
  function amp_frontend_add_canonical() {
@@ -17,12 +19,22 @@ function amp_frontend_add_canonical() {
17
  /**
18
  * Filters whether to show the amphtml link on the frontend.
19
  *
 
20
  * @since 0.2
21
  */
22
  if ( false === apply_filters( 'amp_frontend_show_canonical', true ) ) {
23
  return;
24
  }
25
 
26
- $amp_url = amp_get_permalink( get_queried_object_id() );
27
- printf( '<link rel="amphtml" href="%s">', esc_url( $amp_url ) );
 
 
 
 
 
 
 
 
 
28
  }
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() {
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
  }
includes/amp-helper-functions.php CHANGED
@@ -5,6 +5,36 @@
5
  * @package AMP
6
  */
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  /**
9
  * Retrieves the full AMP-specific permalink for the given post ID.
10
  *
@@ -32,12 +62,16 @@ function amp_get_permalink( $post_id ) {
32
  return $pre_url;
33
  }
34
 
35
- $parsed_url = wp_parse_url( get_permalink( $post_id ) );
36
- $structure = get_option( 'permalink_structure' );
37
- if ( empty( $structure ) || ! empty( $parsed_url['query'] ) || is_post_type_hierarchical( get_post_type( $post_id ) ) ) {
38
- $amp_url = add_query_arg( AMP_QUERY_VAR, '', get_permalink( $post_id ) );
39
  } else {
40
- $amp_url = trailingslashit( get_permalink( $post_id ) ) . user_trailingslashit( AMP_QUERY_VAR, 'single_amp' );
 
 
 
 
 
 
41
  }
42
 
43
  /**
@@ -51,6 +85,25 @@ function amp_get_permalink( $post_id ) {
51
  return apply_filters( 'amp_get_permalink', $amp_url, $post_id );
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  /**
55
  * Determine whether a given post supports AMP.
56
  *
@@ -63,6 +116,10 @@ function amp_get_permalink( $post_id ) {
63
  * @return bool Whether the post supports AMP.
64
  */
65
  function post_supports_amp( $post ) {
 
 
 
 
66
  $errors = AMP_Post_Type_Support::get_support_errors( $post );
67
 
68
  // Return false if an error is found.
@@ -114,11 +171,19 @@ function post_supports_amp( $post ) {
114
  * @return bool Whether it is the AMP endpoint.
115
  */
116
  function is_amp_endpoint() {
 
 
 
 
 
 
 
 
117
  if ( 0 === did_action( 'parse_query' ) ) {
118
  _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' );
119
  }
120
 
121
- return false !== get_query_var( AMP_QUERY_VAR, false );
122
  }
123
 
124
  /**
@@ -130,3 +195,474 @@ function is_amp_endpoint() {
130
  function amp_get_asset_url( $file ) {
131
  return plugins_url( sprintf( 'assets/%s', $file ), AMP__FILE__ );
132
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  * @package AMP
6
  */
7
 
8
+ /**
9
+ * Get the slug used in AMP for the query var, endpoint, and post type support.
10
+ *
11
+ * The return value can be overridden by previously defining a AMP_QUERY_VAR
12
+ * constant or by adding a 'amp_query_var' filter, but *warning* this ability
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() {
19
+ if ( defined( 'AMP_QUERY_VAR' ) ) {
20
+ return AMP_QUERY_VAR;
21
+ }
22
+
23
+ /**
24
+ * Filter the AMP query variable.
25
+ *
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' );
32
+
33
+ define( 'AMP_QUERY_VAR', $query_var );
34
+
35
+ return $query_var;
36
+ }
37
+
38
  /**
39
  * Retrieves the full AMP-specific permalink for the given post ID.
40
  *
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
 
77
  /**
85
  return apply_filters( 'amp_get_permalink', $amp_url, $post_id );
86
  }
87
 
88
+ /**
89
+ * Remove the AMP endpoint (and query var) from a given URL.
90
+ *
91
+ * @since 0.7
92
+ *
93
+ * @param string $url URL.
94
+ * @return string URL with AMP stripped.
95
+ */
96
+ function amp_remove_endpoint( $url ) {
97
+
98
+ // Strip endpoint.
99
+ $url = preg_replace( ':/' . preg_quote( amp_get_slug(), ':' ) . '(?=/?(\?|#|$)):', '', $url );
100
+
101
+ // Strip query var.
102
+ $url = remove_query_arg( amp_get_slug(), $url );
103
+
104
+ return $url;
105
+ }
106
+
107
  /**
108
  * Determine whether a given post supports AMP.
109
  *
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.
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
  /**
195
  function amp_get_asset_url( $file ) {
196
  return plugins_url( sprintf( 'assets/%s', $file ), AMP__FILE__ );
197
  }
198
+
199
+ /**
200
+ * Get AMP boilerplate code.
201
+ *
202
+ * @since 0.7
203
+ * @link https://www.ampproject.org/docs/reference/spec#boilerplate
204
+ *
205
+ * @return string Boilerplate code.
206
+ */
207
+ function amp_get_boilerplate_code() {
208
+ return '<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>'
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
+ *
215
+ * @param WP_Scripts $wp_scripts Scripts.
216
+ */
217
+ function amp_register_default_scripts( $wp_scripts ) {
218
+
219
+ // AMP Runtime.
220
+ $handle = 'amp-runtime';
221
+ $wp_scripts->add(
222
+ $handle,
223
+ 'https://cdn.ampproject.org/v0.js',
224
+ array(),
225
+ null
226
+ );
227
+ $wp_scripts->add_data( $handle, 'amp_script_attributes', array(
228
+ 'async' => true,
229
+ ) );
230
+
231
+ // Shadow AMP API.
232
+ $handle = 'amp-shadow';
233
+ $wp_scripts->add(
234
+ $handle,
235
+ 'https://cdn.ampproject.org/shadow-v0.js',
236
+ array(),
237
+ null
238
+ );
239
+ $wp_scripts->add_data( $handle, 'amp_script_attributes', array(
240
+ 'async' => true,
241
+ ) );
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(
265
+ $extension,
266
+ $src,
267
+ array( 'amp-runtime' ),
268
+ null
269
+ );
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Add AMP script attributes to enqueued scripts.
275
+ *
276
+ * @link https://core.trac.wordpress.org/ticket/12009
277
+ * @since 0.7
278
+ *
279
+ * @param string $tag The script tag.
280
+ * @param string $handle The script handle.
281
+ * @return string Script loader tag.
282
+ */
283
+ function amp_filter_script_loader_tag( $tag, $handle ) {
284
+ $prefix = 'https://cdn.ampproject.org/';
285
+ $src = wp_scripts()->registered[ $handle ]->src;
286
+ if ( 0 !== strpos( $src, $prefix ) ) {
287
+ return $tag;
288
+ }
289
+
290
+ /*
291
+ * All scripts from AMP CDN should be loaded async.
292
+ * See <https://www.ampproject.org/docs/integration/pwa-amp/amp-in-pwa#include-"shadow-amp"-in-your-progressive-web-app>.
293
+ */
294
+ $attributes = array(
295
+ 'async' => true,
296
+ );
297
+
298
+ // Add custom-template and custom-element attributes. All component scripts look like https://cdn.ampproject.org/v0/:name-:version.js.
299
+ if ( 'v0' === strtok( substr( $src, strlen( $prefix ) ), '/' ) ) {
300
+ /*
301
+ * Per the spec, "Most extensions are custom-elements." In fact, there is only one custom template. So we hard-code it here.
302
+ *
303
+ * @link https://github.com/ampproject/amphtml/blob/cd685d4e62153557519553ffa2183aedf8c93d62/validator/validator.proto#L326-L328
304
+ * @link https://github.com/ampproject/amphtml/blob/cd685d4e62153557519553ffa2183aedf8c93d62/extensions/amp-mustache/validator-amp-mustache.protoascii#L27
305
+ */
306
+ if ( 'amp-mustache' === $handle ) {
307
+ $attributes['custom-template'] = $handle;
308
+ } else {
309
+ $attributes['custom-element'] = $handle;
310
+ }
311
+ }
312
+
313
+ // Add each attribute (if it hasn't already been added).
314
+ foreach ( $attributes as $key => $value ) {
315
+ if ( ! preg_match( ":\s$key(=|>|\s):", $tag ) ) {
316
+ if ( true === $value ) {
317
+ $attribute_string = sprintf( ' %s', esc_attr( $key ) );
318
+ } else {
319
+ $attribute_string = sprintf( ' %s="%s"', esc_attr( $key ), esc_attr( $value ) );
320
+ }
321
+ $tag = preg_replace(
322
+ ':(?=></script>):',
323
+ $attribute_string,
324
+ $tag,
325
+ 1
326
+ );
327
+ }
328
+ }
329
+
330
+ return $tag;
331
+ }
332
+
333
+ /**
334
+ * Retrieve analytics data added in backend.
335
+ *
336
+ * @since 0.7
337
+ *
338
+ * @param array $analytics Analytics entries.
339
+ * @return array Analytics.
340
+ */
341
+ function amp_get_analytics( $analytics = array() ) {
342
+ $analytics_entries = AMP_Options_Manager::get_option( 'analytics', array() );
343
+
344
+ /**
345
+ * Add amp-analytics tags.
346
+ *
347
+ * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting.
348
+ * This filter should be used to alter entries for paired mode.
349
+ *
350
+ * @since 0.7
351
+ *
352
+ * @param array $analytics_entries An associative array of the analytics entries we want to output. Each array entry must have a unique key, and the value should be an array with the following keys: `type`, `attributes`, `script_data`. See readme for more details.
353
+ */
354
+ $analytics_entries = apply_filters( 'amp_analytics_entries', $analytics_entries );
355
+
356
+ if ( ! $analytics_entries ) {
357
+ return $analytics;
358
+ }
359
+
360
+ foreach ( $analytics_entries as $entry_id => $entry ) {
361
+ $analytics[ $entry_id ] = array(
362
+ 'type' => $entry['type'],
363
+ 'attributes' => array(),
364
+ 'config_data' => json_decode( $entry['config'] ),
365
+ );
366
+ }
367
+
368
+ return $analytics;
369
+ }
370
+
371
+ /**
372
+ * Print analytics data.
373
+ *
374
+ * @since 0.7
375
+ *
376
+ * @param array|string $analytics Analytics entries, or empty string when called via wp_footer action.
377
+ */
378
+ function amp_print_analytics( $analytics ) {
379
+ if ( '' === $analytics ) {
380
+ $analytics = array();
381
+ }
382
+ $analytics_entries = amp_get_analytics( $analytics );
383
+
384
+ if ( empty( $analytics_entries ) ) {
385
+ return;
386
+ }
387
+
388
+ // Can enter multiple configs within backend.
389
+ foreach ( $analytics_entries as $id => $analytics_entry ) {
390
+ if ( ! isset( $analytics_entry['type'], $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) {
391
+ /* translators: %1$s is analytics entry ID, %2$s is actual entry keys. */
392
+ _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' );
393
+ continue;
394
+ }
395
+ $script_element = AMP_HTML_Utils::build_tag( 'script', array(
396
+ 'type' => 'application/json',
397
+ ), wp_json_encode( $analytics_entry['config_data'] ) );
398
+
399
+ $amp_analytics_attr = array_merge( array(
400
+ 'id' => $id,
401
+ 'type' => $analytics_entry['type'],
402
+ ), $analytics_entry['attributes'] );
403
+
404
+ echo AMP_HTML_Utils::build_tag( 'amp-analytics', $amp_analytics_attr, $script_element ); // WPCS: XSS OK.
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Get content embed handlers.
410
+ *
411
+ * @since 0.7
412
+ *
413
+ * @param WP_Post $post Post that the content belongs to. Deprecated when theme supports AMP, as embeds may apply
414
+ * to non-post data (e.g. Text widget).
415
+ * @return array Embed handlers.
416
+ */
417
+ function amp_get_content_embed_handlers( $post = null ) {
418
+ if ( current_theme_supports( 'amp' ) && $post ) {
419
+ _deprecated_argument( __FUNCTION__, '0.7', esc_html__( 'The $post argument is deprecated when theme supports AMP.', 'amp' ) );
420
+ $post = null;
421
+ }
422
+
423
+ /**
424
+ * Filters the content embed handlers.
425
+ *
426
+ * @since 0.2
427
+ * @since 0.7 Deprecated $post parameter.
428
+ *
429
+ * @param array $handlers Handlers.
430
+ * @param WP_Post $post Post. Deprecated. It will be null when `amp_is_canonical()`.
431
+ */
432
+ return apply_filters( 'amp_content_embed_handlers',
433
+ array(
434
+ 'AMP_Twitter_Embed_Handler' => array(),
435
+ 'AMP_YouTube_Embed_Handler' => array(),
436
+ 'AMP_DailyMotion_Embed_Handler' => array(),
437
+ 'AMP_Vimeo_Embed_Handler' => array(),
438
+ 'AMP_SoundCloud_Embed_Handler' => array(),
439
+ 'AMP_Instagram_Embed_Handler' => array(),
440
+ 'AMP_Issuu_Embed_Handler' => array(),
441
+ 'AMP_Meetup_Embed_Handler' => array(),
442
+ 'AMP_Vine_Embed_Handler' => array(),
443
+ 'AMP_Facebook_Embed_Handler' => array(),
444
+ 'AMP_Pinterest_Embed_Handler' => array(),
445
+ 'AMP_Playlist_Embed_Handler' => array(),
446
+ 'AMP_Reddit_Embed_Handler' => array(),
447
+ 'AMP_Tumblr_Embed_Handler' => array(),
448
+ 'AMP_Gallery_Embed_Handler' => array(),
449
+ 'WPCOM_AMP_Polldaddy_Embed' => array(),
450
+ ),
451
+ $post
452
+ );
453
+ }
454
+
455
+ /**
456
+ * Get content sanitizers.
457
+ *
458
+ * @since 0.7
459
+ *
460
+ * @param WP_Post $post Post that the content belongs to. Deprecated when theme supports AMP, as sanitizers apply
461
+ * to non-post data (e.g. Text widget).
462
+ * @return array Embed handlers.
463
+ */
464
+ function amp_get_content_sanitizers( $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
+ }
469
+
470
+ /**
471
+ * Filters the content sanitizers.
472
+ *
473
+ * @since 0.2
474
+ * @since 0.7 Deprecated $post parameter. It will be null when `amp_is_canonical()`.
475
+ *
476
+ * @param array $handlers Handlers.
477
+ * @param WP_Post $post Post. Deprecated.
478
+ */
479
+ $sanitizers = apply_filters( 'amp_content_sanitizers',
480
+ array(
481
+ 'AMP_Img_Sanitizer' => array(),
482
+ 'AMP_Form_Sanitizer' => array(),
483
+ 'AMP_Comments_Sanitizer' => array(),
484
+ 'AMP_Video_Sanitizer' => array(),
485
+ 'AMP_Audio_Sanitizer' => array(),
486
+ 'AMP_Playbuzz_Sanitizer' => array(),
487
+ 'AMP_Iframe_Sanitizer' => array(
488
+ 'add_placeholder' => true,
489
+ ),
490
+ 'AMP_Style_Sanitizer' => array(),
491
+ '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.
492
+ ),
493
+ $post
494
+ );
495
+
496
+ // Force style sanitizer and whitelist sanitizer to be at end.
497
+ foreach ( array( 'AMP_Style_Sanitizer', 'AMP_Tag_And_Attribute_Sanitizer' ) as $class_name ) {
498
+ if ( isset( $sanitizers[ $class_name ] ) ) {
499
+ $sanitizer = $sanitizers[ $class_name ];
500
+ unset( $sanitizers[ $class_name ] );
501
+ $sanitizers[ $class_name ] = $sanitizer;
502
+ }
503
+ }
504
+
505
+ return $sanitizers;
506
+ }
507
+
508
+ /**
509
+ * Grabs featured image or the first attached image for the post.
510
+ *
511
+ * @since 0.7 This originally was located in the private method AMP_Post_Template::get_post_image_metadata().
512
+ *
513
+ * @param WP_Post|int $post Post or post ID.
514
+ * @return array|false $post_image_meta Post image metadata, or false if not found.
515
+ */
516
+ function amp_get_post_image_metadata( $post = null ) {
517
+ $post = get_post( $post );
518
+ if ( ! $post ) {
519
+ return false;
520
+ }
521
+
522
+ $post_image_meta = null;
523
+ $post_image_id = false;
524
+
525
+ if ( has_post_thumbnail( $post->ID ) ) {
526
+ $post_image_id = get_post_thumbnail_id( $post->ID );
527
+ } else {
528
+ $attached_image_ids = get_posts(
529
+ array(
530
+ 'post_parent' => $post->ID,
531
+ 'post_type' => 'attachment',
532
+ 'post_mime_type' => 'image',
533
+ 'posts_per_page' => 1,
534
+ 'orderby' => 'menu_order',
535
+ 'order' => 'ASC',
536
+ 'fields' => 'ids',
537
+ 'suppress_filters' => false,
538
+ )
539
+ );
540
+
541
+ if ( ! empty( $attached_image_ids ) ) {
542
+ $post_image_id = array_shift( $attached_image_ids );
543
+ }
544
+ }
545
+
546
+ if ( ! $post_image_id ) {
547
+ return false;
548
+ }
549
+
550
+ $post_image_src = wp_get_attachment_image_src( $post_image_id, 'full' );
551
+
552
+ if ( is_array( $post_image_src ) ) {
553
+ $post_image_meta = array(
554
+ '@type' => 'ImageObject',
555
+ 'url' => $post_image_src[0],
556
+ 'width' => $post_image_src[1],
557
+ 'height' => $post_image_src[2],
558
+ );
559
+ }
560
+
561
+ return $post_image_meta;
562
+ }
563
+
564
+ /**
565
+ * Get schema.org metadata for the current query.
566
+ *
567
+ * @since 0.7
568
+ * @see AMP_Post_Template::build_post_data() Where the logic in this function originally existed.
569
+ *
570
+ * @return array $metadata All schema.org metadata for the post.
571
+ */
572
+ function amp_get_schemaorg_metadata() {
573
+ $metadata = array(
574
+ '@context' => 'http://schema.org',
575
+ 'publisher' => array(
576
+ '@type' => 'Organization',
577
+ 'name' => get_bloginfo( 'name' ),
578
+ ),
579
+ );
580
+
581
+ /**
582
+ * Filters the site icon used in AMP responses.
583
+ *
584
+ * In general the `get_site_icon_url` filter should be used instead.
585
+ *
586
+ * @since 0.3
587
+ * @todo Why is the size set to 32px?
588
+ *
589
+ * @param string $site_icon_url
590
+ */
591
+ $site_icon_url = apply_filters( 'amp_site_icon_url', get_site_icon_url( AMP_Post_Template::SITE_ICON_SIZE ) );
592
+ if ( $site_icon_url ) {
593
+ $metadata['publisher']['logo'] = array(
594
+ '@type' => 'ImageObject',
595
+ 'url' => $site_icon_url,
596
+ 'height' => AMP_Post_Template::SITE_ICON_SIZE,
597
+ 'width' => AMP_Post_Template::SITE_ICON_SIZE,
598
+ );
599
+ }
600
+
601
+ $post = get_queried_object();
602
+ if ( $post instanceof WP_Post ) {
603
+ $metadata = array_merge(
604
+ $metadata,
605
+ array(
606
+ '@type' => is_page() ? 'WebPage' : 'BlogPosting',
607
+ 'mainEntityOfPage' => get_permalink(),
608
+ 'headline' => get_the_title(),
609
+ 'datePublished' => date( 'c', get_the_date( 'U', $post->ID ) ),
610
+ 'dateModified' => date( 'c', get_the_date( 'U', $post->ID ) ),
611
+ )
612
+ );
613
+
614
+ $post_author = get_userdata( $post->post_author );
615
+ if ( $post_author ) {
616
+ $metadata['author'] = array(
617
+ '@type' => 'Person',
618
+ 'name' => html_entity_decode( $post_author->display_name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
619
+ );
620
+ }
621
+
622
+ $image_metadata = amp_get_post_image_metadata( $post );
623
+ if ( $image_metadata ) {
624
+ $metadata['image'] = $image_metadata;
625
+ }
626
+
627
+ /**
628
+ * Filters Schema.org metadata for a post.
629
+ *
630
+ * The 'post_template' in the filter name here is due to this filter originally being introduced in `AMP_Post_Template`.
631
+ * In general the `amp_schemaorg_metadata` filter should be used instead.
632
+ *
633
+ * @since 0.3
634
+ *
635
+ * @param array $metadata Metadata.
636
+ * @param WP_Post $post Post.
637
+ */
638
+ $metadata = apply_filters( 'amp_post_template_metadata', $metadata, $post );
639
+ }
640
+
641
+ /**
642
+ * Filters Schema.org metadata for a query.
643
+ *
644
+ * Check the the main query for the context for which metadata should be added.
645
+ *
646
+ * @since 0.7
647
+ *
648
+ * @param array $metadata Metadata.
649
+ */
650
+ $metadata = apply_filters( 'amp_schemaorg_metadata', $metadata );
651
+
652
+ return $metadata;
653
+ }
654
+
655
+ /**
656
+ * Output schema.org metadata.
657
+ *
658
+ * @since 0.7
659
+ */
660
+ function amp_print_schemaorg_metadata() {
661
+ $metadata = amp_get_schemaorg_metadata();
662
+ if ( empty( $metadata ) ) {
663
+ return;
664
+ }
665
+ ?>
666
+ <script type="application/ld+json"><?php echo wp_json_encode( $metadata ); ?></script>
667
+ <?php
668
+ }
includes/amp-post-template-actions.php CHANGED
@@ -14,7 +14,7 @@ function amp_post_template_init_hooks() {
14
  add_action( 'amp_post_template_head', 'amp_post_template_add_scripts' );
15
  add_action( 'amp_post_template_head', 'amp_post_template_add_fonts' );
16
  add_action( 'amp_post_template_head', 'amp_post_template_add_boilerplate_css' );
17
- add_action( 'amp_post_template_head', 'amp_post_template_add_schemaorg_metadata' );
18
  add_action( 'amp_post_template_head', 'amp_add_generator_metadata' );
19
  add_action( 'amp_post_template_css', 'amp_post_template_add_styles', 99 );
20
  add_action( 'amp_post_template_data', 'amp_post_template_add_analytics_script' );
@@ -46,23 +46,27 @@ function amp_post_template_add_canonical( $amp_template ) {
46
  /**
47
  * Print scripts.
48
  *
 
 
49
  * @param AMP_Post_Template $amp_template Template.
50
  */
51
  function amp_post_template_add_scripts( $amp_template ) {
 
 
 
 
 
52
  $scripts = $amp_template->get( 'amp_component_scripts', array() );
53
- foreach ( $scripts as $element => $script ) {
54
- $custom_type = ( 'amp-mustache' === $element ) ? 'template' : 'element';
55
- printf(
56
- '<script custom-%s="%s" src="%s" async></script>', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
57
- esc_attr( $custom_type ),
58
- esc_attr( $element ),
59
- esc_url( $script )
60
- );
61
  }
62
- printf(
63
- '<script src="%s" async></script>', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
64
- esc_url( $amp_template->get( 'amp_runtime_script' ) )
65
- );
 
66
  }
67
 
68
  /**
@@ -79,26 +83,22 @@ function amp_post_template_add_fonts( $amp_template ) {
79
 
80
  /**
81
  * Print boilerplate CSS.
 
 
 
82
  */
83
  function amp_post_template_add_boilerplate_css() {
84
- ?>
85
- <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
86
- <?php
87
  }
88
 
89
  /**
90
  * Print Schema.org metadata.
91
  *
92
- * @param AMP_Post_Template $amp_template Template.
93
  */
94
- function amp_post_template_add_schemaorg_metadata( $amp_template ) {
95
- $metadata = $amp_template->get( 'metadata' );
96
- if ( empty( $metadata ) ) {
97
- return;
98
- }
99
- ?>
100
- <script type="application/ld+json"><?php echo wp_json_encode( $metadata ); ?></script>
101
- <?php
102
  }
103
 
104
  /**
@@ -133,31 +133,11 @@ function amp_post_template_add_analytics_script( $data ) {
133
  /**
134
  * Print analytics data.
135
  *
136
- * @param AMP_Post_Template $amp_template Template.
137
  */
138
- function amp_post_template_add_analytics_data( $amp_template ) {
139
- $analytics_entries = $amp_template->get( 'amp_analytics' );
140
- if ( empty( $analytics_entries ) ) {
141
- return;
142
- }
143
-
144
- foreach ( $analytics_entries as $id => $analytics_entry ) {
145
- if ( ! isset( $analytics_entry['type'], $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) {
146
- /* translators: %1$s is analytics entry ID, %2$s is actual entry keys. */
147
- _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' );
148
- continue;
149
- }
150
- $script_element = AMP_HTML_Utils::build_tag( 'script', array(
151
- 'type' => 'application/json',
152
- ), wp_json_encode( $analytics_entry['config_data'] ) );
153
-
154
- $amp_analytics_attr = array_merge( array(
155
- 'id' => $id,
156
- 'type' => $analytics_entry['type'],
157
- ), $analytics_entry['attributes'] );
158
-
159
- echo AMP_HTML_Utils::build_tag( 'amp-analytics', $amp_analytics_attr, $script_element ); // WPCS: XSS OK.
160
- }
161
  }
162
 
163
  /**
14
  add_action( 'amp_post_template_head', 'amp_post_template_add_scripts' );
15
  add_action( 'amp_post_template_head', 'amp_post_template_add_fonts' );
16
  add_action( 'amp_post_template_head', 'amp_post_template_add_boilerplate_css' );
17
+ add_action( 'amp_post_template_head', 'amp_print_schemaorg_metadata' );
18
  add_action( 'amp_post_template_head', 'amp_add_generator_metadata' );
19
  add_action( 'amp_post_template_css', 'amp_post_template_add_styles', 99 );
20
  add_action( 'amp_post_template_data', 'amp_post_template_add_analytics_script' );
46
  /**
47
  * Print scripts.
48
  *
49
+ * @see amp_register_default_scripts()
50
+ * @see amp_filter_script_loader_tag()
51
  * @param AMP_Post_Template $amp_template Template.
52
  */
53
  function amp_post_template_add_scripts( $amp_template ) {
54
+
55
+ // Just in case the runtime has been overridden by amp_post_template_data filter.
56
+ wp_scripts()->registered['amp-runtime']->src = $amp_template->get( 'amp_runtime_script' );
57
+
58
+ // Make sure any filtered extension script URLs get updated in registered scripts before printing.
59
  $scripts = $amp_template->get( 'amp_component_scripts', array() );
60
+ foreach ( $scripts as $handle => $value ) {
61
+ if ( is_string( $value ) && wp_script_is( $handle, 'registered' ) ) {
62
+ wp_scripts()->registered[ $handle ]->src = $value;
63
+ }
 
 
 
 
64
  }
65
+
66
+ wp_print_scripts( array_merge(
67
+ array( 'amp-runtime' ),
68
+ array_keys( $scripts )
69
+ ) );
70
  }
71
 
72
  /**
83
 
84
  /**
85
  * Print boilerplate CSS.
86
+ *
87
+ * @since 0.3
88
+ * @see amp_get_boilerplate_code()
89
  */
90
  function amp_post_template_add_boilerplate_css() {
91
+ echo amp_get_boilerplate_code(); // WPCS: xss ok.
 
 
92
  }
93
 
94
  /**
95
  * Print Schema.org metadata.
96
  *
97
+ * @deprecated Since 0.7
98
  */
99
+ function amp_post_template_add_schemaorg_metadata() {
100
+ _deprecated_function( __FUNCTION__, '0.7', 'amp_print_schemaorg_metadata' );
101
+ amp_print_schemaorg_metadata();
 
 
 
 
 
102
  }
103
 
104
  /**
133
  /**
134
  * Print analytics data.
135
  *
136
+ * @since 0.3.2
137
  */
138
+ function amp_post_template_add_analytics_data() {
139
+ $analytics = amp_add_custom_analytics();
140
+ amp_print_analytics( $analytics );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
 
143
  /**
includes/class-amp-autoloader.php CHANGED
@@ -29,6 +29,8 @@ class AMP_Autoloader {
29
  * @var string[]
30
  */
31
  private static $_classmap = array(
 
 
32
  'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer',
33
  'AMP_Post_Meta_Box' => 'includes/admin/class-amp-post-meta-box',
34
  'AMP_Post_Type_Support' => 'includes/class-amp-post-type-support',
@@ -37,8 +39,13 @@ class AMP_Autoloader {
37
  'AMP_Facebook_Embed_Handler' => 'includes/embeds/class-amp-facebook-embed',
38
  'AMP_Gallery_Embed_Handler' => 'includes/embeds/class-amp-gallery-embed',
39
  'AMP_Instagram_Embed_Handler' => 'includes/embeds/class-amp-instagram-embed',
 
 
40
  'AMP_Pinterest_Embed_Handler' => 'includes/embeds/class-amp-pinterest-embed',
 
 
41
  'AMP_SoundCloud_Embed_Handler' => 'includes/embeds/class-amp-soundcloud-embed',
 
42
  'AMP_Twitter_Embed_Handler' => 'includes/embeds/class-amp-twitter-embed',
43
  'AMP_Vimeo_Embed_Handler' => 'includes/embeds/class-amp-vimeo-embed',
44
  'AMP_Vine_Embed_Handler' => 'includes/embeds/class-amp-vine-embed',
@@ -63,6 +70,8 @@ class AMP_Autoloader {
63
  'AMP_Blacklist_Sanitizer' => 'includes/sanitizers/class-amp-blacklist-sanitizer',
64
  'AMP_Iframe_Sanitizer' => 'includes/sanitizers/class-amp-iframe-sanitizer',
65
  'AMP_Img_Sanitizer' => 'includes/sanitizers/class-amp-img-sanitizer',
 
 
66
  'AMP_Playbuzz_Sanitizer' => 'includes/sanitizers/class-amp-playbuzz-sanitizer',
67
  'AMP_Style_Sanitizer' => 'includes/sanitizers/class-amp-style-sanitizer',
68
  'AMP_Tag_And_Attribute_Sanitizer' => 'includes/sanitizers/class-amp-tag-and-attribute-sanitizer',
@@ -75,8 +84,14 @@ class AMP_Autoloader {
75
  'AMP_DOM_Utils' => 'includes/utils/class-amp-dom-utils',
76
  'AMP_HTML_Utils' => 'includes/utils/class-amp-html-utils',
77
  'AMP_Image_Dimension_Extractor' => 'includes/utils/class-amp-image-dimension-extractor',
 
78
  'AMP_String_Utils' => 'includes/utils/class-amp-string-utils',
79
  'AMP_WP_Utils' => 'includes/utils/class-amp-wp-utils',
 
 
 
 
 
80
  'WPCOM_AMP_Polldaddy_Embed' => 'wpcom/class-amp-polldaddy-embed',
81
  'AMP_Test_Stub_Sanitizer' => 'tests/stubs',
82
  'AMP_Test_World_Sanitizer' => 'tests/stubs',
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',
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',
44
  'AMP_Pinterest_Embed_Handler' => 'includes/embeds/class-amp-pinterest-embed',
45
+ 'AMP_Playlist_Embed_Handler' => 'includes/embeds/class-amp-playlist-embed-handler',
46
+ 'AMP_Reddit_Embed_Handler' => 'includes/embeds/class-amp-reddit-embed-handler',
47
  'AMP_SoundCloud_Embed_Handler' => 'includes/embeds/class-amp-soundcloud-embed',
48
+ 'AMP_Tumblr_Embed_Handler' => 'includes/embeds/class-amp-tumblr-embed-handler',
49
  'AMP_Twitter_Embed_Handler' => 'includes/embeds/class-amp-twitter-embed',
50
  'AMP_Vimeo_Embed_Handler' => 'includes/embeds/class-amp-vimeo-embed',
51
  'AMP_Vine_Embed_Handler' => 'includes/embeds/class-amp-vine-embed',
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',
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',
97
  'AMP_Test_World_Sanitizer' => 'tests/stubs',
includes/class-amp-comment-walker.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Comment_Walker
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Comment_Walker
10
+ *
11
+ * Walker to wrap comments in mustache tags for amp-template.
12
+ */
13
+ class AMP_Comment_Walker extends Walker_Comment {
14
+
15
+ /**
16
+ * The original comments arguments.
17
+ *
18
+ * @since 0.7
19
+ * @var array
20
+ */
21
+ public $args;
22
+
23
+ /**
24
+ * Holds the timestamp of the most reacent comment in a thread.
25
+ *
26
+ * @since 0.7
27
+ * @var array
28
+ */
29
+ private $comment_thread_age = array();
30
+
31
+ /**
32
+ * Starts the element output.
33
+ *
34
+ * @since 0.7.0
35
+ *
36
+ * @see Walker::start_el()
37
+ * @see wp_list_comments()
38
+ * @global int $comment_depth
39
+ * @global WP_Comment $comment
40
+ *
41
+ * @param string $output Used to append additional content. Passed by reference.
42
+ * @param WP_Comment $comment Comment data object.
43
+ * @param int $depth Optional. Depth of the current comment in reference to parents. Default 0.
44
+ * @param array $args Optional. An array of arguments. Default empty array.
45
+ * @param int $id Optional. ID of the current comment. Default 0 (unused).
46
+ */
47
+ public function start_el( &$output, $comment, $depth = 0, $args = array(), $id = 0 ) {
48
+
49
+ $new_out = '';
50
+ parent::start_el( $new_out, $comment, $depth, $args, $id );
51
+
52
+ if ( 'div' === $args['style'] ) {
53
+ $tag = '<div';
54
+ } else {
55
+ $tag = '<li';
56
+ }
57
+ $new_tag = $tag . ' data-sort-time="' . esc_attr( strtotime( $comment->comment_date ) ) . '"';
58
+
59
+ if ( ! empty( $this->comment_thread_age[ $comment->comment_ID ] ) ) {
60
+ $new_tag .= ' data-update-time="' . esc_attr( $this->comment_thread_age[ $comment->comment_ID ] ) . '"';
61
+ }
62
+
63
+ $output .= $new_tag . substr( ltrim( $new_out ), strlen( $tag ) );
64
+
65
+ }
66
+
67
+ /**
68
+ * Output amp-list template code and place holder for comments.
69
+ *
70
+ * @since 0.7
71
+ * @see Walker::paged_walk()
72
+ *
73
+ * @param WP_Comment[] $elements List of comment Elements.
74
+ * @param int $max_depth The maximum hierarchical depth.
75
+ * @param int $page_num The specific page number, beginning with 1.
76
+ * @param int $per_page Per page counter.
77
+ *
78
+ * @return string XHTML of the specified page of elements.
79
+ */
80
+ public function paged_walk( $elements, $max_depth, $page_num, $per_page ) {
81
+ if ( empty( $elements ) || $max_depth < -1 ) {
82
+ return '';
83
+ }
84
+
85
+ $this->build_thread_latest_date( $elements );
86
+
87
+ $args = array_slice( func_get_args(), 4 );
88
+
89
+ return parent::paged_walk( $elements, $max_depth, $page_num, $per_page, $args[0] );
90
+ }
91
+
92
+ /**
93
+ * Find the timestamp of the latest child comment of a thread to set the updated time.
94
+ *
95
+ * @since 0.7
96
+ *
97
+ * @param WP_Comment[] $elements The list of comments to get thread times for.
98
+ * @param int $time $the timestamp to check against.
99
+ * @param bool $is_child Flag used to set the the value or return the time.
100
+ * @return int Latest time.
101
+ */
102
+ protected function build_thread_latest_date( $elements, $time = 0, $is_child = false ) {
103
+
104
+ foreach ( $elements as $element ) {
105
+
106
+ $children = $element->get_children();
107
+ $this_time = strtotime( $element->comment_date );
108
+ if ( ! empty( $children ) ) {
109
+ $this_time = $this->build_thread_latest_date( $children, $this_time, true );
110
+ }
111
+ if ( $this_time > $time ) {
112
+ $time = $this_time;
113
+ }
114
+ if ( false === $is_child ) {
115
+ $this->comment_thread_age[ $element->comment_ID ] = $time;
116
+ }
117
+ }
118
+
119
+ return $time;
120
+ }
121
+ }
includes/class-amp-post-type-support.php CHANGED
@@ -54,7 +54,7 @@ class AMP_Post_Type_Support {
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_QUERY_VAR );
58
  }
59
  }
60
 
@@ -73,7 +73,7 @@ class AMP_Post_Type_Support {
73
  $errors = array();
74
 
75
  // Because `add_rewrite_endpoint` doesn't let us target specific post_types.
76
- if ( ! post_type_supports( $post->post_type, AMP_QUERY_VAR ) ) {
77
  $errors[] = 'post-type-support';
78
  }
79
 
@@ -90,7 +90,7 @@ class AMP_Post_Type_Support {
90
  * @param int $post_id Post ID.
91
  * @param WP_Post $post Post.
92
  */
93
- if ( true === apply_filters( 'amp_skip_post', false, $post->ID, $post ) ) {
94
  $errors[] = 'skip-post';
95
  }
96
 
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
 
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
 
90
  * @param int $post_id Post ID.
91
  * @param WP_Post $post Post.
92
  */
93
+ if ( isset( $post->ID ) && true === apply_filters( 'amp_skip_post', false, $post->ID, $post ) ) {
94
  $errors[] = 'skip-post';
95
  }
96
 
includes/class-amp-theme-support.php ADDED
@@ -0,0 +1,1201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Theme_Support
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Theme_Support
10
+ *
11
+ * Callbacks for adding AMP-related things when theme support is added.
12
+ */
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.
24
+ *
25
+ * @var array
26
+ */
27
+ protected static $sanitizer_classes = array();
28
+
29
+ /**
30
+ * Embed handlers.
31
+ *
32
+ * @var AMP_Base_Embed_Handler[]
33
+ */
34
+ protected static $embed_handlers = array();
35
+
36
+ /**
37
+ * Template types.
38
+ *
39
+ * @var array
40
+ */
41
+ protected static $template_types = array(
42
+ 'paged', // Deprecated.
43
+ 'index',
44
+ '404',
45
+ 'archive',
46
+ 'author',
47
+ 'category',
48
+ 'tag',
49
+ 'taxonomy',
50
+ 'date',
51
+ 'home',
52
+ 'front_page',
53
+ 'page',
54
+ 'search',
55
+ 'single',
56
+ 'embed',
57
+ 'singular',
58
+ 'attachment',
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
+ self::purge_amp_query_vars();
96
+ self::handle_xhr_request();
97
+
98
+ require_once AMP__DIR__ . '/includes/amp-post-template-actions.php';
99
+
100
+ // Validate theme support usage.
101
+ $support = get_theme_support( 'amp' );
102
+ if ( WP_DEBUG && is_array( $support ) ) {
103
+ $args = array_shift( $support );
104
+ if ( ! is_array( $args ) ) {
105
+ trigger_error( esc_html__( 'Expected AMP theme support arg to be array.', 'amp' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
106
+ } elseif ( count( array_diff( array_keys( $args ), array( 'template_dir', 'available_callback', 'comments_live_list' ) ) ) !== 0 ) {
107
+ 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
108
+ }
109
+ }
110
+
111
+ add_action( 'widgets_init', array( __CLASS__, 'register_widgets' ) );
112
+
113
+ /*
114
+ * Note that wp action is use instead of template_redirect because some themes/plugins output
115
+ * the response at this action and then short-circuit with exit. So this is why the the preceding
116
+ * action to template_redirect--the wp action--is used instead.
117
+ */
118
+ add_action( 'wp', array( __CLASS__, 'finish_init' ), PHP_INT_MAX );
119
+ }
120
+
121
+ /**
122
+ * Finish initialization once query vars are set.
123
+ *
124
+ * @since 0.7
125
+ */
126
+ public static function finish_init() {
127
+ if ( ! is_amp_endpoint() ) {
128
+ // Add amphtml link when paired mode is available.
129
+ if ( self::is_paired_available() ) {
130
+ amp_add_frontend_actions(); // @todo This function is poor in how it requires a file that then does add_action().
131
+ if ( ! has_action( 'wp_head', 'amp_frontend_add_canonical' ) ) {
132
+ add_action( 'wp_head', 'amp_frontend_add_canonical' );
133
+ }
134
+ }
135
+ return;
136
+ }
137
+
138
+ if ( amp_is_canonical() ) {
139
+ self::redirect_canonical_amp();
140
+ } else {
141
+ self::register_paired_hooks();
142
+ }
143
+
144
+ self::add_hooks();
145
+ self::$sanitizer_classes = amp_get_content_sanitizers();
146
+ self::$embed_handlers = self::register_content_embed_handlers();
147
+ }
148
+
149
+ /**
150
+ * Redirect to canonical URL if the AMP URL was loaded, since canonical is now AMP.
151
+ *
152
+ * @since 0.7
153
+ */
154
+ public static function redirect_canonical_amp() {
155
+ if ( false !== get_query_var( amp_get_slug(), false ) ) { // Because is_amp_endpoint() now returns true if amp_is_canonical().
156
+ $url = preg_replace( '#^(https?://.+?)(/.*)$#', '$1', home_url( '/' ) );
157
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
158
+ $url .= wp_unslash( $_SERVER['REQUEST_URI'] );
159
+ }
160
+
161
+ $url = amp_remove_endpoint( $url );
162
+
163
+ wp_safe_redirect( $url, 302 ); // Temporary redirect because canonical may change in future.
164
+ exit;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Determines whether paired mode is available.
170
+ *
171
+ * When 'amp' theme support has not been added or canonical mode is enabled, then this returns false.
172
+ * Returns true when there is a template_dir defined in theme support, and if a defined available_callback
173
+ * returns true.
174
+ *
175
+ * @return bool Whether available.
176
+ */
177
+ public static function is_paired_available() {
178
+ $support = get_theme_support( 'amp' );
179
+ if ( empty( $support ) || amp_is_canonical() ) {
180
+ return false;
181
+ }
182
+
183
+ if ( is_singular() && ! post_supports_amp( get_queried_object() ) ) {
184
+ return false;
185
+ }
186
+
187
+ $args = array_shift( $support );
188
+
189
+ if ( isset( $args['available_callback'] ) && is_callable( $args['available_callback'] ) ) {
190
+ return call_user_func( $args['available_callback'] );
191
+ }
192
+ return true;
193
+ }
194
+
195
+ /**
196
+ * Determine whether the user is in the Customizer preview iframe.
197
+ *
198
+ * @since 0.7
199
+ *
200
+ * @return bool Whether in Customizer preview iframe.
201
+ */
202
+ public static function is_customize_preview_iframe() {
203
+ global $wp_customize;
204
+ return is_customize_preview() && $wp_customize->get_messenger_channel();
205
+ }
206
+
207
+ /**
208
+ * Register hooks for paired mode.
209
+ */
210
+ public static function register_paired_hooks() {
211
+ foreach ( self::$template_types as $template_type ) {
212
+ add_filter( "{$template_type}_template_hierarchy", array( __CLASS__, 'filter_paired_template_hierarchy' ) );
213
+ }
214
+ add_filter( 'template_include', array( __CLASS__, 'filter_paired_template_include' ), 100 );
215
+ }
216
+
217
+ /**
218
+ * Register hooks.
219
+ */
220
+ public static function add_hooks() {
221
+
222
+ // Remove core actions which are invalid AMP.
223
+ remove_action( 'wp_head', 'wp_post_preview_js', 1 );
224
+ remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
225
+ remove_action( 'wp_print_styles', 'print_emoji_styles' );
226
+ remove_action( 'wp_head', 'wp_oembed_add_host_js' );
227
+
228
+ // Prevent MediaElement.js scripts/styles from being enqueued.
229
+ add_filter( 'wp_video_shortcode_library', function() {
230
+ return 'amp';
231
+ } );
232
+ add_filter( 'wp_audio_shortcode_library', function() {
233
+ return 'amp';
234
+ } );
235
+
236
+ /*
237
+ * Add additional markup required by AMP <https://www.ampproject.org/docs/reference/spec#required-markup>.
238
+ * Note that the meta[name=viewport] is not added here because a theme may want to define one with additional
239
+ * properties than included in the default configuration. If a theme doesn't include one, then the meta viewport
240
+ * will be added when output buffering is finished. Note that meta charset _is_ output here because the output
241
+ * buffer will need it to parse the document properly, and it must be exactly as is to be valid AMP. Nevertheless,
242
+ * in this case too we should defer to the theme as well to output the meta charset because it is possible the
243
+ * install is not on utf-8 and we may need to do a encoding conversion.
244
+ */
245
+ add_action( 'wp_print_styles', array( __CLASS__, 'print_amp_styles' ), 0 ); // Print boilerplate before theme and plugin stylesheets.
246
+ add_action( 'wp_head', 'amp_add_generator_metadata', 20 );
247
+
248
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
249
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'dequeue_customize_preview_scripts' ), 1000 );
250
+ add_filter( 'customize_partial_render', array( __CLASS__, 'filter_customize_partial_render' ) );
251
+
252
+ add_action( 'wp_footer', 'amp_print_analytics' );
253
+
254
+ /*
255
+ * Disable admin bar because admin-bar.css (28K) and Dashicons (48K) alone
256
+ * combine to surpass the 50K limit imposed for the amp-custom style.
257
+ */
258
+ add_filter( 'show_admin_bar', '__return_false', 100 );
259
+
260
+ /*
261
+ * Start output buffering at very low priority for sake of plugins and themes that use template_redirect
262
+ * instead of template_include.
263
+ */
264
+ $priority = defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX; // phpcs:ignore PHPCompatibility.PHP.NewConstants.php_int_minFound
265
+ add_action( 'template_redirect', array( __CLASS__, 'start_output_buffering' ), $priority );
266
+
267
+ // Add validation hooks *after* output buffering has started for the response.
268
+ if ( AMP_Validation_Utils::should_validate_response() ) {
269
+ AMP_Validation_Utils::add_validation_hooks();
270
+ }
271
+
272
+ // Commenting hooks.
273
+ add_filter( 'wp_list_comments_args', array( __CLASS__, 'set_comments_walker' ), PHP_INT_MAX );
274
+ add_filter( 'comment_form_defaults', array( __CLASS__, 'filter_comment_form_defaults' ) );
275
+ add_filter( 'comment_reply_link', array( __CLASS__, 'filter_comment_reply_link' ), 10, 4 );
276
+ add_filter( 'cancel_comment_reply_link', array( __CLASS__, 'filter_cancel_comment_reply_link' ), 10, 3 );
277
+ add_action( 'comment_form', array( __CLASS__, 'amend_comment_form' ), 100 );
278
+ remove_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' );
279
+ add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'whitelist_layout_in_wp_kses_allowed_html' ), 10 );
280
+
281
+ // @todo Add character conversion.
282
+ }
283
+
284
+ /**
285
+ * Remove query vars that come in requests such as for amp-live-list.
286
+ *
287
+ * WordPress should generally not respond differently to requests when these parameters
288
+ * are present. In some cases, when a query param such as __amp_source_origin is present
289
+ * then it would normally get included into pagination links generated by get_pagenum_link().
290
+ * The whitelist sanitizer empties out links that contain this string as it matches the
291
+ * blacklisted_value_regex. So by preemptively scrubbing any reference to these query vars
292
+ * we can ensure that WordPress won't end up referencing them in any way.
293
+ *
294
+ * @since 0.7
295
+ */
296
+ public static function purge_amp_query_vars() {
297
+ $query_vars = array(
298
+ '__amp_source_origin',
299
+ '_wp_amp_action_xhr_converted',
300
+ 'amp_latest_update_time',
301
+ 'amp_last_check_time',
302
+ );
303
+
304
+ // Scrub input vars.
305
+ foreach ( $query_vars as $query_var ) {
306
+ if ( ! isset( $_GET[ $query_var ] ) ) { // phpcs:ignore
307
+ continue;
308
+ }
309
+ self::$purged_amp_query_vars[ $query_var ] = wp_unslash( $_GET[ $query_var ] ); // phpcs:ignore
310
+ unset( $_REQUEST[ $query_var ], $_GET[ $query_var ] );
311
+ $scrubbed = true;
312
+ }
313
+
314
+ if ( isset( $scrubbed ) ) {
315
+ $build_query = function( $query ) use ( $query_vars ) {
316
+ $pattern = '/^(' . join( '|', $query_vars ) . ')(?==|$)/';
317
+ $pairs = array();
318
+ foreach ( explode( '&', $query ) as $pair ) {
319
+ if ( ! preg_match( $pattern, $pair ) ) {
320
+ $pairs[] = $pair;
321
+ }
322
+ }
323
+ return join( '&', $pairs );
324
+ };
325
+
326
+ // Scrub QUERY_STRING.
327
+ if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
328
+ $_SERVER['QUERY_STRING'] = $build_query( $_SERVER['QUERY_STRING'] );
329
+ }
330
+
331
+ // Scrub REQUEST_URI.
332
+ if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
333
+ list( $path, $query ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
334
+
335
+ $pairs = $build_query( $query );
336
+ $_SERVER['REQUEST_URI'] = $path;
337
+ if ( ! empty( $pairs ) ) {
338
+ $_SERVER['REQUEST_URI'] .= "?{$pairs}";
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Send an HTTP response header.
346
+ *
347
+ * This largely exists to facilitate unit testing but it also provides a better interface for sending headers.
348
+ *
349
+ * @since 0.7.0
350
+ *
351
+ * @param string $name Header name.
352
+ * @param string $value Header value.
353
+ * @param array $args {
354
+ * Args to header().
355
+ *
356
+ * @type bool $replace Whether to replace a header previously sent. Default true.
357
+ * @type int $status_code Status code to send with the sent header.
358
+ * }
359
+ * @return bool Whether the header was sent.
360
+ */
361
+ public static function send_header( $name, $value, $args = array() ) {
362
+ $args = array_merge(
363
+ array(
364
+ 'replace' => true,
365
+ 'status_code' => null,
366
+ ),
367
+ $args
368
+ );
369
+
370
+ self::$headers_sent[] = array_merge( compact( 'name', 'value' ), $args );
371
+ if ( headers_sent() ) {
372
+ return false;
373
+ }
374
+
375
+ header(
376
+ sprintf( '%s: %s', $name, $value ),
377
+ $args['replace'],
378
+ $args['status_code']
379
+ );
380
+ return true;
381
+ }
382
+
383
+ /**
384
+ * Hook into a POST form submissions, such as the comment form or some other form submission.
385
+ *
386
+ * @since 0.7.0
387
+ */
388
+ public static function handle_xhr_request() {
389
+ $is_amp_xhr = (
390
+ ! empty( self::$purged_amp_query_vars['_wp_amp_action_xhr_converted'] )
391
+ &&
392
+ ! empty( self::$purged_amp_query_vars['__amp_source_origin'] )
393
+ &&
394
+ ( ! empty( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] )
395
+ );
396
+ if ( ! $is_amp_xhr ) {
397
+ return;
398
+ }
399
+
400
+ // Send AMP response header.
401
+ $origin = wp_validate_redirect( wp_sanitize_redirect( esc_url_raw( self::$purged_amp_query_vars['__amp_source_origin'] ) ) );
402
+ if ( $origin ) {
403
+ self::send_header( 'AMP-Access-Control-Allow-Source-Origin', $origin, array( 'replace' => true ) );
404
+ }
405
+
406
+ // Intercept POST requests which redirect.
407
+ add_filter( 'wp_redirect', array( __CLASS__, 'intercept_post_request_redirect' ), PHP_INT_MAX );
408
+
409
+ // Add special handling for redirecting after comment submission.
410
+ add_filter( 'comment_post_redirect', array( __CLASS__, 'filter_comment_post_redirect' ), PHP_INT_MAX, 2 );
411
+
412
+ // Add die handler for AMP error display, most likely due to problem with comment.
413
+ add_filter( 'wp_die_handler', function() {
414
+ return array( __CLASS__, 'handle_wp_die' );
415
+ } );
416
+
417
+ }
418
+
419
+ /**
420
+ * Strip tags that are not allowed in amp-mustache.
421
+ *
422
+ * @since 0.7.0
423
+ *
424
+ * @param string $text Text to sanitize.
425
+ * @return string Sanitized text.
426
+ */
427
+ protected static function wp_kses_amp_mustache( $text ) {
428
+ $amp_mustache_allowed_html_tags = array( 'strong', 'b', 'em', 'i', 'u', 's', 'small', 'mark', 'del', 'ins', 'sup', 'sub' );
429
+ return wp_kses( $text, array_fill_keys( $amp_mustache_allowed_html_tags, array() ) );
430
+ }
431
+
432
+ /**
433
+ * 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.
434
+ *
435
+ * @since 0.7.0
436
+ *
437
+ * @param string $url Comment permalink to redirect to.
438
+ * @param WP_Comment $comment Posted comment.
439
+ * @return string URL.
440
+ */
441
+ public static function filter_comment_post_redirect( $url, $comment ) {
442
+ $theme_support = get_theme_support( 'amp' );
443
+
444
+ // Cause a page refresh if amp-live-list is not implemented for comments via add_theme_support( 'amp', array( 'comments_live_list' => true ) ).
445
+ if ( empty( $theme_support[0]['comments_live_list'] ) ) {
446
+ /*
447
+ * Add the comment ID to the URL to force AMP to refresh the page.
448
+ * This is ideally a temporary workaround to deal with https://github.com/ampproject/amphtml/issues/14170
449
+ */
450
+ $url = add_query_arg( 'comment', $comment->comment_ID, $url );
451
+
452
+ // Pass URL along to wp_redirect().
453
+ return $url;
454
+ }
455
+
456
+ // Create a success message to display to the user.
457
+ if ( '1' === (string) $comment->comment_approved ) {
458
+ $message = __( 'Your comment has been posted.', 'amp' );
459
+ } else {
460
+ $message = __( 'Your comment is awaiting moderation.', 'default' ); // Note core string re-use.
461
+ }
462
+
463
+ /**
464
+ * Filters the message when comment submitted success message when
465
+ *
466
+ * @since 0.7
467
+ */
468
+ $message = apply_filters( 'amp_comment_posted_message', $message, $comment );
469
+
470
+ // Message will be shown in template defined by AMP_Theme_Support::amend_comment_form().
471
+ wp_send_json( array(
472
+ 'message' => self::wp_kses_amp_mustache( $message ),
473
+ ) );
474
+ }
475
+
476
+ /**
477
+ * New error handler for AMP form submission.
478
+ *
479
+ * @since 0.7.0
480
+ * @see wp_die()
481
+ *
482
+ * @param WP_Error|string $error The error to handle.
483
+ * @param string|int $title Optional. Error title. If `$message` is a `WP_Error` object,
484
+ * error data with the key 'title' may be used to specify the title.
485
+ * If `$title` is an integer, then it is treated as the response
486
+ * code. Default empty.
487
+ * @param string|array|int $args {
488
+ * Optional. Arguments to control behavior. If `$args` is an integer, then it is treated
489
+ * as the response code. Default empty array.
490
+ *
491
+ * @type int $response The HTTP response code. Default 200 for Ajax requests, 500 otherwise.
492
+ * }
493
+ */
494
+ public static function handle_wp_die( $error, $title = '', $args = array() ) {
495
+ if ( is_int( $title ) ) {
496
+ $status_code = $title;
497
+ } elseif ( is_int( $args ) ) {
498
+ $status_code = $args;
499
+ } elseif ( is_array( $args ) && isset( $args['response'] ) ) {
500
+ $status_code = $args['response'];
501
+ } else {
502
+ $status_code = 500;
503
+ }
504
+ status_header( $status_code );
505
+
506
+ if ( is_wp_error( $error ) ) {
507
+ $error = $error->get_error_message();
508
+ }
509
+
510
+ // Message will be shown in template defined by AMP_Theme_Support::amend_comment_form().
511
+ wp_send_json( array(
512
+ 'error' => self::wp_kses_amp_mustache( $error ),
513
+ ) );
514
+ }
515
+
516
+ /**
517
+ * Intercept the response to a POST request.
518
+ *
519
+ * @since 0.7.0
520
+ * @see wp_redirect()
521
+ *
522
+ * @param string $location The location to redirect to.
523
+ */
524
+ public static function intercept_post_request_redirect( $location ) {
525
+
526
+ // Make sure relative redirects get made absolute.
527
+ $parsed_location = array_merge(
528
+ array(
529
+ 'scheme' => 'https',
530
+ 'host' => wp_parse_url( home_url(), PHP_URL_HOST ),
531
+ 'path' => isset( $_SERVER['REQUEST_URI'] ) ? strtok( wp_unslash( $_SERVER['REQUEST_URI'] ), '?' ) : '/',
532
+ ),
533
+ wp_parse_url( $location )
534
+ );
535
+
536
+ $absolute_location = '';
537
+ if ( 'https' === $parsed_location['scheme'] ) {
538
+ $absolute_location .= $parsed_location['scheme'] . ':';
539
+ }
540
+ $absolute_location .= '//' . $parsed_location['host'];
541
+ if ( isset( $parsed_location['port'] ) ) {
542
+ $absolute_location .= ':' . $parsed_location['port'];
543
+ }
544
+ $absolute_location .= $parsed_location['path'];
545
+ if ( isset( $parsed_location['query'] ) ) {
546
+ $absolute_location .= '?' . $parsed_location['query'];
547
+ }
548
+ if ( isset( $parsed_location['fragment'] ) ) {
549
+ $absolute_location .= '#' . $parsed_location['fragment'];
550
+ }
551
+
552
+ self::send_header( 'AMP-Redirect-To', $absolute_location );
553
+ self::send_header( 'Access-Control-Expose-Headers', 'AMP-Redirect-To' );
554
+
555
+ wp_send_json_success();
556
+ }
557
+
558
+ /**
559
+ * Register/override widgets.
560
+ *
561
+ * @global WP_Widget_Factory
562
+ * @return void
563
+ */
564
+ public static function register_widgets() {
565
+ global $wp_widget_factory;
566
+ foreach ( $wp_widget_factory->widgets as $registered_widget ) {
567
+ $registered_widget_class_name = get_class( $registered_widget );
568
+ if ( ! preg_match( '/^WP_Widget_(.+)$/', $registered_widget_class_name, $matches ) ) {
569
+ continue;
570
+ }
571
+ $amp_class_name = 'AMP_Widget_' . $matches[1];
572
+ if ( ! class_exists( $amp_class_name ) || is_a( $amp_class_name, $registered_widget_class_name ) ) {
573
+ continue;
574
+ }
575
+
576
+ unregister_widget( $registered_widget_class_name );
577
+ register_widget( $amp_class_name );
578
+ }
579
+ }
580
+
581
+ /**
582
+ * Register content embed handlers.
583
+ *
584
+ * This was copied from `AMP_Content::register_embed_handlers()` due to being a private method
585
+ * and due to `AMP_Content` not being well suited for use in AMP canonical.
586
+ *
587
+ * @see AMP_Content::register_embed_handlers()
588
+ * @global int $content_width
589
+ * @return AMP_Base_Embed_Handler[] Handlers.
590
+ */
591
+ public static function register_content_embed_handlers() {
592
+ global $content_width;
593
+
594
+ $embed_handlers = array();
595
+ foreach ( amp_get_content_embed_handlers() as $embed_handler_class => $args ) {
596
+
597
+ /**
598
+ * Embed handler.
599
+ *
600
+ * @type AMP_Base_Embed_Handler $embed_handler
601
+ */
602
+ $embed_handler = new $embed_handler_class( array_merge(
603
+ array(
604
+ 'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH, // Back-compat.
605
+ ),
606
+ $args
607
+ ) );
608
+
609
+ if ( ! is_subclass_of( $embed_handler, 'AMP_Base_Embed_Handler' ) ) {
610
+ /* translators: %s is embed handler */
611
+ _doing_it_wrong( __METHOD__, esc_html( sprintf( __( 'Embed Handler (%s) must extend `AMP_Embed_Handler`', 'amp' ), $embed_handler_class ) ), '0.1' );
612
+ continue;
613
+ }
614
+
615
+ $embed_handler->register_embed();
616
+ $embed_handlers[] = $embed_handler;
617
+ }
618
+
619
+ return $embed_handlers;
620
+ }
621
+
622
+ /**
623
+ * Add the comments template placeholder marker
624
+ *
625
+ * @param array $args the args for the comments list..
626
+ * @return array Args to return.
627
+ */
628
+ public static function set_comments_walker( $args ) {
629
+ $amp_walker = new AMP_Comment_Walker();
630
+ $args['walker'] = $amp_walker;
631
+ return $args;
632
+ }
633
+
634
+ /**
635
+ * Adds the form submit success and fail templates.
636
+ */
637
+ public static function amend_comment_form() {
638
+ ?>
639
+ <?php if ( is_singular() && ! amp_is_canonical() ) : ?>
640
+ <input type="hidden" name="redirect_to" value="<?php echo esc_url( amp_get_permalink( get_the_ID() ) ); ?>">
641
+ <?php endif; ?>
642
+
643
+ <div submit-success>
644
+ <template type="amp-mustache">
645
+ <p>{{{message}}}</p>
646
+ </template>
647
+ </div>
648
+ <div submit-error>
649
+ <template type="amp-mustache">
650
+ <p class="amp-comment-submit-error">{{{error}}}</p>
651
+ </template>
652
+ </div>
653
+ <?php
654
+ }
655
+
656
+ /**
657
+ * Prepends template hierarchy with template_dir for AMP paired mode templates.
658
+ *
659
+ * @see get_query_template()
660
+ *
661
+ * @param array $templates Template hierarchy.
662
+ * @returns array Templates.
663
+ */
664
+ public static function filter_paired_template_hierarchy( $templates ) {
665
+ $support = get_theme_support( 'amp' );
666
+ $args = array_shift( $support );
667
+ if ( isset( $args['template_dir'] ) ) {
668
+ $amp_templates = array();
669
+ foreach ( $templates as $template ) {
670
+ $amp_templates[] = $args['template_dir'] . '/' . $template;
671
+ }
672
+ $templates = $amp_templates;
673
+ }
674
+ return $templates;
675
+ }
676
+
677
+ /**
678
+ * Redirect to the non-canonical URL when the template to include is empty.
679
+ *
680
+ * This is a failsafe in case an index.php is not located in the AMP template_dir,
681
+ * and the available_callback fails to omit a given request from being available in AMP.
682
+ *
683
+ * @param string $template Template to include.
684
+ * @return string Template to include.
685
+ */
686
+ public static function filter_paired_template_include( $template ) {
687
+ if ( empty( $template ) || ! self::is_paired_available() ) {
688
+ wp_safe_redirect( self::get_current_canonical_url(), 302 ); // Temporary redirect because support may come later.
689
+ exit;
690
+ }
691
+ return $template;
692
+ }
693
+
694
+ /**
695
+ * Get canonical URL for current request.
696
+ *
697
+ * @see rel_canonical()
698
+ * @global WP $wp
699
+ * @global WP_Rewrite $wp_rewrite
700
+ * @link https://www.ampproject.org/docs/reference/spec#canon.
701
+ * @link https://core.trac.wordpress.org/ticket/18660
702
+ *
703
+ * @return string Canonical non-AMP URL.
704
+ */
705
+ public static function get_current_canonical_url() {
706
+ global $wp, $wp_rewrite;
707
+
708
+ $url = null;
709
+ if ( is_singular() ) {
710
+ $url = wp_get_canonical_url();
711
+ }
712
+
713
+ // For non-singular queries, make use of the request URI and public query vars to determine canonical URL.
714
+ if ( empty( $url ) ) {
715
+ $added_query_vars = $wp->query_vars;
716
+ if ( ! $wp_rewrite->permalink_structure || empty( $wp->request ) ) {
717
+ $url = home_url( '/' );
718
+ } else {
719
+ $url = home_url( user_trailingslashit( $wp->request ) );
720
+ parse_str( $wp->matched_query, $matched_query_vars );
721
+ foreach ( $wp->query_vars as $key => $value ) {
722
+
723
+ // Remove query vars that were matched in the rewrite rules for the request.
724
+ if ( isset( $matched_query_vars[ $key ] ) ) {
725
+ unset( $added_query_vars[ $key ] );
726
+ }
727
+ }
728
+ }
729
+ }
730
+
731
+ if ( ! empty( $added_query_vars ) ) {
732
+ $url = add_query_arg( $added_query_vars, $url );
733
+ }
734
+
735
+ return amp_remove_endpoint( $url );
736
+ }
737
+
738
+ /**
739
+ * Get the ID for the amp-state.
740
+ *
741
+ * @since 0.7
742
+ *
743
+ * @param int $post_id Post ID.
744
+ * @return string ID for amp-state.
745
+ */
746
+ public static function get_comment_form_state_id( $post_id ) {
747
+ return sprintf( 'commentform_post_%d', $post_id );
748
+ }
749
+
750
+ /**
751
+ * Filter comment form args to an element with [text] AMP binding wrap the title reply.
752
+ *
753
+ * @since 0.7
754
+ * @see comment_form()
755
+ *
756
+ * @param array $args Comment form args.
757
+ * @return array Filtered comment form args.
758
+ */
759
+ public static function filter_comment_form_defaults( $args ) {
760
+ $state_id = self::get_comment_form_state_id( get_the_ID() );
761
+
762
+ $text_binding = sprintf(
763
+ '%s.replyToName ? %s : %s',
764
+ $state_id,
765
+ str_replace(
766
+ '%s',
767
+ sprintf( '" + %s.replyToName + "', $state_id ),
768
+ wp_json_encode( $args['title_reply_to'] )
769
+ ),
770
+ wp_json_encode( $args['title_reply'] )
771
+ );
772
+
773
+ $args['title_reply_before'] .= sprintf(
774
+ '<span [text]="%s">',
775
+ esc_attr( $text_binding )
776
+ );
777
+ $args['cancel_reply_before'] = '</span>' . $args['cancel_reply_before'];
778
+ return $args;
779
+ }
780
+
781
+ /**
782
+ * Modify the comment reply link for AMP.
783
+ *
784
+ * @since 0.7
785
+ * @see get_comment_reply_link()
786
+ *
787
+ * @param string $link The HTML markup for the comment reply link.
788
+ * @param array $args An array of arguments overriding the defaults.
789
+ * @param WP_Comment $comment The object of the comment being replied.
790
+ * @return string Comment reply link.
791
+ */
792
+ public static function filter_comment_reply_link( $link, $args, $comment ) {
793
+
794
+ // Continue to show default link to wp-login when user is not logged-in.
795
+ if ( get_option( 'comment_registration' ) && ! is_user_logged_in() ) {
796
+ return $link;
797
+ }
798
+
799
+ $state_id = self::get_comment_form_state_id( get_the_ID() );
800
+ $tap_state = array(
801
+ $state_id => array(
802
+ 'replyToName' => $comment->comment_author,
803
+ 'values' => array(
804
+ 'comment_parent' => (string) $comment->comment_ID,
805
+ ),
806
+ ),
807
+ );
808
+
809
+ // @todo Figure out how to support add_below. Instead of moving the form, what about letting the form get a fixed position?
810
+ $link = sprintf(
811
+ '<a rel="nofollow" class="comment-reply-link" href="%s" on="%s" aria-label="%s">%s</a>',
812
+ esc_attr( '#' . $args['respond_id'] ),
813
+ esc_attr( sprintf( 'tap:AMP.setState( %s )', wp_json_encode( $tap_state ) ) ),
814
+ esc_attr( sprintf( $args['reply_to_text'], $comment->comment_author ) ),
815
+ $args['reply_text']
816
+ );
817
+ return $link;
818
+ }
819
+
820
+ /**
821
+ * Filters the cancel comment reply link HTML.
822
+ *
823
+ * @since 0.7
824
+ * @see get_cancel_comment_reply_link()
825
+ *
826
+ * @param string $formatted_link The HTML-formatted cancel comment reply link.
827
+ * @param string $link Cancel comment reply link URL.
828
+ * @param string $text Cancel comment reply link text.
829
+ * @return string Cancel reply link.
830
+ */
831
+ public static function filter_cancel_comment_reply_link( $formatted_link, $link, $text ) {
832
+ unset( $formatted_link, $link );
833
+ if ( empty( $text ) ) {
834
+ $text = __( 'Click here to cancel reply.', 'default' );
835
+ }
836
+
837
+ $state_id = self::get_comment_form_state_id( get_the_ID() );
838
+ $tap_state = array(
839
+ $state_id => array(
840
+ 'replyToName' => '',
841
+ 'values' => array(
842
+ 'comment_parent' => '0',
843
+ ),
844
+ ),
845
+ );
846
+
847
+ $respond_id = 'respond'; // Hard-coded in comment_form() and default value in get_comment_reply_link().
848
+ return sprintf(
849
+ '<a id="cancel-comment-reply-link" href="%s" %s [hidden]="%s" on="%s">%s</a>',
850
+ esc_url( remove_query_arg( 'replytocom' ) . '#' . $respond_id ),
851
+ isset( $_GET['replytocom'] ) ? '' : ' hidden', // phpcs:ignore
852
+ esc_attr( sprintf( '%s.values.comment_parent == "0"', self::get_comment_form_state_id( get_the_ID() ) ) ),
853
+ esc_attr( sprintf( 'tap:AMP.setState( %s )', wp_json_encode( $tap_state ) ) ),
854
+ esc_html( $text )
855
+ );
856
+ }
857
+
858
+ /**
859
+ * Print AMP boilerplate and custom styles.
860
+ */
861
+ public static function print_amp_styles() {
862
+ echo amp_get_boilerplate_code() . "\n"; // WPCS: XSS OK.
863
+ echo "<style amp-custom></style>\n"; // This will by populated by AMP_Style_Sanitizer.
864
+ }
865
+
866
+ /**
867
+ * Ensure markup required by AMP <https://www.ampproject.org/docs/reference/spec#required-markup>.
868
+ *
869
+ * Ensure meta[charset], meta[name=viewport], and link[rel=canonical]; a the whitelist sanitizer
870
+ * may have removed an illegal meta[http-equiv] or meta[name=viewport]. Core only outputs a
871
+ * canonical URL by default if a singular post.
872
+ *
873
+ * @since 0.7
874
+ * @todo All of this might be better placed inside of a sanitizer.
875
+ *
876
+ * @param DOMDocument $dom Doc.
877
+ */
878
+ public static function ensure_required_markup( DOMDocument $dom ) {
879
+ $head = $dom->getElementsByTagName( 'head' )->item( 0 );
880
+ if ( ! $head ) {
881
+ $head = $dom->createElement( 'head' );
882
+ $dom->documentElement->insertBefore( $head, $dom->documentElement->firstChild );
883
+ }
884
+ $meta_charset = null;
885
+ $meta_viewport = null;
886
+ foreach ( $head->getElementsByTagName( 'meta' ) as $meta ) {
887
+ /**
888
+ * Meta.
889
+ *
890
+ * @var DOMElement $meta
891
+ */
892
+ if ( $meta->hasAttribute( 'charset' ) && 'utf-8' === strtolower( $meta->getAttribute( 'charset' ) ) ) { // @todo Also look for meta[http-equiv="Content-Type"]?
893
+ $meta_charset = $meta;
894
+ } elseif ( 'viewport' === $meta->getAttribute( 'name' ) ) {
895
+ $meta_viewport = $meta;
896
+ }
897
+ }
898
+ if ( ! $meta_charset ) {
899
+ // Warning: This probably means the character encoding needs to be converted.
900
+ $meta_charset = AMP_DOM_Utils::create_node( $dom, 'meta', array(
901
+ 'charset' => 'utf-8',
902
+ ) );
903
+ $head->insertBefore( $meta_charset, $head->firstChild );
904
+ }
905
+ if ( ! $meta_viewport ) {
906
+ $meta_viewport = AMP_DOM_Utils::create_node( $dom, 'meta', array(
907
+ 'name' => 'viewport',
908
+ 'content' => 'width=device-width,minimum-scale=1',
909
+ ) );
910
+ $head->insertBefore( $meta_viewport, $meta_charset->nextSibling );
911
+ }
912
+ // Prevent schema.org duplicates.
913
+ $has_schema_org_metadata = false;
914
+ foreach ( $head->getElementsByTagName( 'script' ) as $script ) {
915
+ if ( 'application/ld+json' === $script->getAttribute( 'type' ) && false !== strpos( $script->nodeValue, 'schema.org' ) ) {
916
+ $has_schema_org_metadata = true;
917
+ break;
918
+ }
919
+ }
920
+ if ( ! $has_schema_org_metadata ) {
921
+ $script = $dom->createElement( 'script', wp_json_encode( amp_get_schemaorg_metadata() ) );
922
+ $script->setAttribute( 'type', 'application/ld+json' );
923
+ $head->appendChild( $script );
924
+ }
925
+ // Ensure rel=canonical link.
926
+ $rel_canonical = null;
927
+ foreach ( $head->getElementsByTagName( 'link' ) as $link ) {
928
+ if ( 'canonical' === $link->getAttribute( 'rel' ) ) {
929
+ $rel_canonical = $link;
930
+ break;
931
+ }
932
+ }
933
+ if ( ! $rel_canonical ) {
934
+ $rel_canonical = AMP_DOM_Utils::create_node( $dom, 'link', array(
935
+ 'rel' => 'canonical',
936
+ 'href' => self::get_current_canonical_url(),
937
+ ) );
938
+ $head->appendChild( $rel_canonical );
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Dequeue Customizer assets which are not necessary outside the preview iframe.
944
+ *
945
+ * Prevent enqueueing customize-preview styles if not in customizer preview iframe.
946
+ * These are only needed for when there is live editing of content, such as selective refresh.
947
+ *
948
+ * @since 0.7
949
+ */
950
+ public static function dequeue_customize_preview_scripts() {
951
+
952
+ // Dequeue styles unnecessary unless in customizer preview iframe when editing (such as for edit shortcuts).
953
+ if ( ! self::is_customize_preview_iframe() ) {
954
+ wp_dequeue_style( 'customize-preview' );
955
+ foreach ( wp_styles()->registered as $handle => $dependency ) {
956
+ if ( in_array( 'customize-preview', $dependency->deps, true ) ) {
957
+ wp_dequeue_style( $handle );
958
+ }
959
+ }
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Start output buffering.
965
+ *
966
+ * @since 0.7
967
+ * @see AMP_Theme_Support::finish_output_buffering()
968
+ */
969
+ public static function start_output_buffering() {
970
+ /*
971
+ * Disable the New Relic Browser agent on AMP responses.
972
+ * This prevents th New Relic from causing invalid AMP responses due the NREUM script it injects after the meta charset:
973
+ * https://docs.newrelic.com/docs/browser/new-relic-browser/troubleshooting/google-amp-validator-fails-due-3rd-party-script
974
+ * Sites with New Relic will need to specially configure New Relic for AMP:
975
+ * https://docs.newrelic.com/docs/browser/new-relic-browser/installation/monitor-amp-pages-new-relic-browser
976
+ */
977
+ if ( function_exists( 'newrelic_disable_autorum' ) ) {
978
+ newrelic_disable_autorum();
979
+ }
980
+
981
+ ob_start( array( __CLASS__, 'finish_output_buffering' ) );
982
+ self::$is_output_buffering = true;
983
+ }
984
+
985
+ /**
986
+ * Determine whether output buffering has started.
987
+ *
988
+ * @since 0.7
989
+ * @see AMP_Theme_Support::start_output_buffering()
990
+ * @see AMP_Theme_Support::finish_output_buffering()
991
+ *
992
+ * @return bool Whether output buffering has started.
993
+ */
994
+ public static function is_output_buffering() {
995
+ return self::$is_output_buffering;
996
+ }
997
+
998
+ /**
999
+ * Finish output buffering.
1000
+ *
1001
+ * @since 0.7
1002
+ * @see AMP_Theme_Support::start_output_buffering()
1003
+ *
1004
+ * @param string $response Buffered Response.
1005
+ * @return string Processed Response.
1006
+ */
1007
+ public static function finish_output_buffering( $response ) {
1008
+ self::$is_output_buffering = false;
1009
+ return self::prepare_response( $response );
1010
+ }
1011
+
1012
+ /**
1013
+ * Filter rendered partial to convert to AMP.
1014
+ *
1015
+ * @see WP_Customize_Partial::render()
1016
+ *
1017
+ * @param string|mixed $partial Rendered partial.
1018
+ * @return string|mixed Filtered partial.
1019
+ * @global int $content_width
1020
+ */
1021
+ public static function filter_customize_partial_render( $partial ) {
1022
+ global $content_width;
1023
+ if ( is_string( $partial ) && preg_match( '/<\w/', $partial ) ) {
1024
+ $dom = AMP_DOM_Utils::get_dom_from_content( $partial );
1025
+ $args = array(
1026
+ 'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH, // Back-compat.
1027
+ 'use_document_element' => false,
1028
+ 'allow_dirty_styles' => true,
1029
+ 'allow_dirty_scripts' => false,
1030
+ );
1031
+ AMP_Content_Sanitizer::sanitize_document( $dom, self::$sanitizer_classes, $args ); // @todo Include script assets in response?
1032
+ $partial = AMP_DOM_Utils::get_content_from_dom( $dom );
1033
+ }
1034
+ return $partial;
1035
+ }
1036
+
1037
+ /**
1038
+ * Process response to ensure AMP validity.
1039
+ *
1040
+ * @since 0.7
1041
+ *
1042
+ * @param string $response HTML document response. By default it expects a complete document.
1043
+ * @param array $args {
1044
+ * Args to send to the preprocessor/sanitizer.
1045
+ *
1046
+ * @type callable $remove_invalid_callback Function to call whenever a node is removed due to being invalid.
1047
+ * }
1048
+ * @return string AMP document response.
1049
+ * @global int $content_width
1050
+ */
1051
+ public static function prepare_response( $response, $args = array() ) {
1052
+ global $content_width;
1053
+
1054
+ /*
1055
+ * Check if the response starts with HTML markup.
1056
+ * Without this check, JSON responses will be erroneously corrupted,
1057
+ * being wrapped in HTML documents.
1058
+ */
1059
+ if ( '<' !== substr( ltrim( $response ), 0, 1 ) ) {
1060
+ return $response;
1061
+ }
1062
+
1063
+ $is_validation_debug_mode = ! empty( $_REQUEST[ AMP_Validation_Utils::DEBUG_QUERY_VAR ] ); // WPCS: csrf ok.
1064
+
1065
+ $args = array_merge(
1066
+ array(
1067
+ 'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH, // Back-compat.
1068
+ 'use_document_element' => true,
1069
+ 'allow_dirty_styles' => self::is_customize_preview_iframe(), // Dirty styles only needed when editing (e.g. for edit shortcodes).
1070
+ 'allow_dirty_scripts' => is_customize_preview(), // Scripts are always needed to inject changeset UUID.
1071
+ 'disable_invalid_removal' => $is_validation_debug_mode,
1072
+ ),
1073
+ $args
1074
+ );
1075
+
1076
+ /*
1077
+ * Make sure that <meta charset> is present in output prior to parsing.
1078
+ * Note that the meta charset is supposed to appear within the first 1024 bytes.
1079
+ * See <https://www.w3.org/International/questions/qa-html-encoding-declarations>.
1080
+ */
1081
+ if ( ! preg_match( '#<meta[^>]+charset=#i', substr( $response, 0, 1024 ) ) ) {
1082
+ $response = preg_replace(
1083
+ '/(<head[^>]*>)/i',
1084
+ '$1' . sprintf( '<meta charset="%s">', esc_attr( get_bloginfo( 'charset' ) ) ),
1085
+ $response,
1086
+ 1
1087
+ );
1088
+ }
1089
+ $dom = AMP_DOM_Utils::get_dom( $response );
1090
+
1091
+ $xpath = new DOMXPath( $dom );
1092
+
1093
+ $head = $dom->getElementsByTagName( 'head' )->item( 0 );
1094
+
1095
+ if ( isset( $head ) ) {
1096
+ // Make sure scripts from the body get moved to the head.
1097
+ foreach ( $xpath->query( '//body//script[ @custom-element or @custom-template ]' ) as $script ) {
1098
+ $head->appendChild( $script );
1099
+ }
1100
+ }
1101
+
1102
+ // Ensure the mandatory amp attribute is present on the html element, as otherwise it will be stripped entirely.
1103
+ if ( ! $dom->documentElement->hasAttribute( 'amp' ) && ! $dom->documentElement->hasAttribute( '⚡️' ) ) {
1104
+ $dom->documentElement->setAttribute( 'amp', '' );
1105
+ }
1106
+
1107
+ $assets = AMP_Content_Sanitizer::sanitize_document( $dom, self::$sanitizer_classes, $args );
1108
+
1109
+ self::ensure_required_markup( $dom );
1110
+
1111
+ // @todo If 'utf-8' is not the blog charset, then we'll need to do some character encoding conversation or "entityification".
1112
+ if ( 'utf-8' !== strtolower( get_bloginfo( 'charset' ) ) ) {
1113
+ /* translators: %s is the charset of the current site */
1114
+ 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
1115
+ }
1116
+
1117
+ if ( AMP_Validation_Utils::should_validate_response() ) {
1118
+ AMP_Validation_Utils::finalize_validation( $dom, array(
1119
+ 'remove_source_comments' => ! $is_validation_debug_mode,
1120
+ ) );
1121
+ }
1122
+
1123
+ $response = "<!DOCTYPE html>\n";
1124
+ $response .= AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement );
1125
+
1126
+ $amp_scripts = $assets['scripts'];
1127
+ foreach ( self::$embed_handlers as $embed_handler ) {
1128
+ $amp_scripts = array_merge(
1129
+ $amp_scripts,
1130
+ $embed_handler->get_scripts()
1131
+ );
1132
+ }
1133
+
1134
+ // Allow for embed handlers to override the default extension version by defining a different URL.
1135
+ foreach ( $amp_scripts as $handle => $value ) {
1136
+ if ( is_string( $value ) && wp_script_is( $handle, 'registered' ) ) {
1137
+ wp_scripts()->registered[ $handle ]->src = $value;
1138
+ }
1139
+ }
1140
+
1141
+ /*
1142
+ * Inject additional AMP component scripts which have been discovered by the sanitizers into the head.
1143
+ * This is adapted from wp_scripts()->do_items(), but it runs only the bare minimum required to output
1144
+ * the missing scripts, without allowing other filters to apply which may cause an invalid AMP response.
1145
+ */
1146
+ $script_tags = '';
1147
+ foreach ( array_diff( array_keys( $amp_scripts ), wp_scripts()->done ) as $handle ) {
1148
+ if ( ! wp_script_is( $handle, 'registered' ) ) {
1149
+ continue;
1150
+ }
1151
+ $script_dep = wp_scripts()->registered[ $handle ];
1152
+ $script_tags .= amp_filter_script_loader_tag(
1153
+ sprintf(
1154
+ "<script type='text/javascript' src='%s'></script>\n", // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
1155
+ esc_url( $script_dep->src )
1156
+ ),
1157
+ $handle
1158
+ );
1159
+ }
1160
+ if ( ! empty( $script_tags ) ) {
1161
+ $response = preg_replace(
1162
+ '#(?=</head>)#',
1163
+ $script_tags,
1164
+ $response,
1165
+ 1
1166
+ );
1167
+ }
1168
+
1169
+ return $response;
1170
+ }
1171
+
1172
+ /**
1173
+ * Adds 'data-amp-layout' to the allowed <img> attributes for wp_kses().
1174
+ *
1175
+ * @since 0.7
1176
+ *
1177
+ * @param array $context Allowed tags and their allowed attributes.
1178
+ * @return array $context Filtered allowed tags and attributes.
1179
+ */
1180
+ public static function whitelist_layout_in_wp_kses_allowed_html( $context ) {
1181
+ if ( ! empty( $context['img']['width'] ) && ! empty( $context['img']['height'] ) ) {
1182
+ $context['img']['data-amp-layout'] = true;
1183
+ }
1184
+
1185
+ return $context;
1186
+ }
1187
+
1188
+ /**
1189
+ * Enqueue AMP assets if this is an AMP endpoint.
1190
+ *
1191
+ * @since 0.7
1192
+ *
1193
+ * @return void
1194
+ */
1195
+ public static function enqueue_assets() {
1196
+ wp_enqueue_script( 'amp-runtime' );
1197
+
1198
+ // Enqueue default styles expected by sanitizer.
1199
+ wp_enqueue_style( 'amp-default', amp_get_asset_url( 'css/amp-default.css' ), array(), AMP__VERSION );
1200
+ }
1201
+ }
includes/embeds/class-amp-base-embed-handler.php CHANGED
@@ -27,6 +27,16 @@ abstract class AMP_Base_Embed_Handler {
27
  ) );
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
30
  public function get_scripts() {
31
  return array();
32
  }
27
  ) );
28
  }
29
 
30
+ /**
31
+ * Get mapping of AMP component names to AMP script URLs.
32
+ *
33
+ * This is normally no longer needed because the whitelist
34
+ * sanitizer will automatically detect the need for them via
35
+ * the spec.
36
+ *
37
+ * @see AMP_Tag_And_Attribute_Sanitizer::get_scripts()
38
+ * @return array Scripts.
39
+ */
40
  public function get_scripts() {
41
  return array();
42
  }
includes/embeds/class-amp-dailymotion-embed.php CHANGED
@@ -18,9 +18,6 @@ class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
18
  protected $DEFAULT_WIDTH = 600;
19
  protected $DEFAULT_HEIGHT = 338;
20
 
21
- private static $script_slug = 'amp-dailymotion';
22
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-dailymotion-0.1.js';
23
-
24
  function __construct( $args = array() ) {
25
  parent::__construct( $args );
26
 
@@ -41,14 +38,6 @@ class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
41
  remove_shortcode( 'dailymotion' );
42
  }
43
 
44
- public function get_scripts() {
45
- if ( ! $this->did_convert_elements ) {
46
- return array();
47
- }
48
-
49
- return array( self::$script_slug => self::$script_src );
50
- }
51
-
52
  public function shortcode( $attr ) {
53
  $video_id = false;
54
 
18
  protected $DEFAULT_WIDTH = 600;
19
  protected $DEFAULT_HEIGHT = 338;
20
 
 
 
 
21
  function __construct( $args = array() ) {
22
  parent::__construct( $args );
23
 
38
  remove_shortcode( 'dailymotion' );
39
  }
40
 
 
 
 
 
 
 
 
 
41
  public function shortcode( $attr ) {
42
  $video_id = false;
43
 
includes/embeds/class-amp-facebook-embed.php CHANGED
@@ -14,9 +14,6 @@ class AMP_Facebook_Embed_Handler extends AMP_Base_Embed_Handler {
14
  protected $DEFAULT_WIDTH = 600;
15
  protected $DEFAULT_HEIGHT = 400;
16
 
17
- private static $script_slug = 'amp-facebook';
18
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-facebook-0.1.js';
19
-
20
  public function register_embed() {
21
  wp_embed_register_handler( 'amp-facebook', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
22
  }
@@ -25,14 +22,6 @@ class AMP_Facebook_Embed_Handler extends AMP_Base_Embed_Handler {
25
  wp_embed_unregister_handler( 'amp-facebook', -1 );
26
  }
27
 
28
- public function get_scripts() {
29
- if ( ! $this->did_convert_elements ) {
30
- return array();
31
- }
32
-
33
- return array( self::$script_slug => self::$script_src );
34
- }
35
-
36
  public function oembed( $matches, $attr, $url, $rawattr ) {
37
  return $this->render( array( 'url' => $url ) );
38
  }
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
  }
22
  wp_embed_unregister_handler( 'amp-facebook', -1 );
23
  }
24
 
 
 
 
 
 
 
 
 
25
  public function oembed( $matches, $attr, $url, $rawattr ) {
26
  return $this->render( array( 'url' => $url ) );
27
  }
includes/embeds/class-amp-gallery-embed.php CHANGED
@@ -7,27 +7,29 @@
7
 
8
  /**
9
  * Class AMP_Gallery_Embed_Handler
 
 
10
  */
11
  class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
12
- private static $script_slug = 'amp-carousel';
13
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-carousel-0.1.js';
14
 
 
 
 
15
  public function register_embed() {
16
- add_shortcode( 'gallery', array( $this, 'shortcode' ) );
17
- }
18
-
19
- public function unregister_embed() {
20
- remove_shortcode( 'gallery' );
21
- }
22
-
23
- public function get_scripts() {
24
- if ( ! $this->did_convert_elements ) {
25
- return array();
26
- }
27
-
28
- return array( self::$script_slug => self::$script_src );
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
31
  public function shortcode( $attr ) {
32
  $post = get_post();
33
 
@@ -40,46 +42,47 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
40
  }
41
 
42
  $atts = shortcode_atts( array(
43
- 'order' => 'ASC',
44
- 'orderby' => 'menu_order ID',
45
- 'id' => $post ? $post->ID : 0,
46
- 'include' => '',
47
- 'exclude' => '',
48
- 'size' => array( $this->args['width'], $this->args['height'] ),
 
49
  ), $attr, 'gallery' );
50
 
51
  $id = intval( $atts['id'] );
52
 
53
  if ( ! empty( $atts['include'] ) ) {
54
  $attachments = get_posts( array(
55
- 'include' => $atts['include'],
56
- 'post_status' => 'inherit',
57
- 'post_type' => 'attachment',
58
  'post_mime_type' => 'image',
59
- 'order' => $atts['order'],
60
- 'orderby' => $atts['orderby'],
61
- 'fields' => 'ids',
62
  ) );
63
  } elseif ( ! empty( $atts['exclude'] ) ) {
64
  $attachments = get_children( array(
65
- 'post_parent' => $id,
66
- 'exclude' => $atts['exclude'],
67
- 'post_status' => 'inherit',
68
- 'post_type' => 'attachment',
69
  'post_mime_type' => 'image',
70
- 'order' => $atts['order'],
71
- 'orderby' => $atts['orderby'],
72
- 'fields' => 'ids',
73
  ) );
74
  } else {
75
  $attachments = get_children( array(
76
- 'post_parent' => $id,
77
- 'post_status' => 'inherit',
78
- 'post_type' => 'attachment',
79
  'post_mime_type' => 'image',
80
- 'order' => $atts['order'],
81
- 'orderby' => $atts['orderby'],
82
- 'fields' => 'ids',
83
  ) );
84
  }
85
 
@@ -95,9 +98,17 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
95
  continue;
96
  }
97
 
 
 
 
 
 
 
 
98
  $urls[] = array(
99
- 'url' => $url,
100
- 'width' => $width,
 
101
  'height' => $height,
102
  );
103
  }
@@ -107,6 +118,26 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
107
  ) );
108
  }
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  public function render( $args ) {
111
  $this->did_convert_elements = true;
112
 
@@ -119,24 +150,36 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
119
  }
120
 
121
  $images = array();
122
- foreach ( $args['images'] as $image ) {
123
- $images[] = AMP_HTML_Utils::build_tag(
124
  'amp-img',
125
  array(
126
- 'src' => $image['url'],
127
- 'width' => $image['width'],
128
- 'height' => $image['height'],
129
  'layout' => 'responsive',
130
  )
131
  );
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
  return AMP_HTML_Utils::build_tag(
135
  'amp-carousel',
136
  array(
137
- 'width' => $this->args['width'],
138
  'height' => $this->args['height'],
139
- 'type' => 'slides',
140
  'layout' => 'responsive',
141
  ),
142
  implode( PHP_EOL, $images )
7
 
8
  /**
9
  * Class AMP_Gallery_Embed_Handler
10
+ *
11
+ * @since 0.2
12
  */
13
  class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
 
 
14
 
15
+ /**
16
+ * Register embed.
17
+ */
18
  public function register_embed() {
19
+ add_filter( 'post_gallery', array( $this, 'override_gallery' ), 10, 2 );
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
22
+ /**
23
+ * Unregister embed.
24
+ */
25
+ public function unregister_embed() {}
26
+
27
+ /**
28
+ * Shortcode handler.
29
+ *
30
+ * @param array $attr Shortcode attributes.
31
+ * @return string Rendered gallery.
32
+ */
33
  public function shortcode( $attr ) {
34
  $post = get_post();
35
 
42
  }
43
 
44
  $atts = shortcode_atts( array(
45
+ 'order' => 'ASC',
46
+ 'orderby' => 'menu_order ID',
47
+ 'id' => $post ? $post->ID : 0,
48
+ 'include' => '',
49
+ 'exclude' => '',
50
+ 'size' => array( $this->args['width'], $this->args['height'] ),
51
+ 'link' => 'none',
52
  ), $attr, 'gallery' );
53
 
54
  $id = intval( $atts['id'] );
55
 
56
  if ( ! empty( $atts['include'] ) ) {
57
  $attachments = get_posts( array(
58
+ 'include' => $atts['include'],
59
+ 'post_status' => 'inherit',
60
+ 'post_type' => 'attachment',
61
  'post_mime_type' => 'image',
62
+ 'order' => $atts['order'],
63
+ 'orderby' => $atts['orderby'],
64
+ 'fields' => 'ids',
65
  ) );
66
  } elseif ( ! empty( $atts['exclude'] ) ) {
67
  $attachments = get_children( array(
68
+ 'post_parent' => $id,
69
+ 'exclude' => $atts['exclude'],
70
+ 'post_status' => 'inherit',
71
+ 'post_type' => 'attachment',
72
  'post_mime_type' => 'image',
73
+ 'order' => $atts['order'],
74
+ 'orderby' => $atts['orderby'],
75
+ 'fields' => 'ids',
76
  ) );
77
  } else {
78
  $attachments = get_children( array(
79
+ 'post_parent' => $id,
80
+ 'post_status' => 'inherit',
81
+ 'post_type' => 'attachment',
82
  'post_mime_type' => 'image',
83
+ 'order' => $atts['order'],
84
+ 'orderby' => $atts['orderby'],
85
+ 'fields' => 'ids',
86
  ) );
87
  }
88
 
98
  continue;
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(
109
+ 'href' => $href,
110
+ 'url' => $url,
111
+ 'width' => $width,
112
  'height' => $height,
113
  );
114
  }
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>.
126
+ *
127
+ * @param string $html Markup to filter, possibly ''.
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
+
135
+ /**
136
+ * Render.
137
+ *
138
+ * @param array $args Args.
139
+ * @return string Rendered.
140
+ */
141
  public function render( $args ) {
142
  $this->did_convert_elements = true;
143
 
150
  }
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'] ) ) {
165
+ $image = AMP_HTML_Utils::build_tag(
166
+ 'a',
167
+ array(
168
+ 'href' => $props['href'],
169
+ ),
170
+ $image
171
+ );
172
+ }
173
+
174
+ $images[] = $image;
175
  }
176
 
177
  return AMP_HTML_Utils::build_tag(
178
  'amp-carousel',
179
  array(
180
+ 'width' => $this->args['width'],
181
  'height' => $this->args['height'],
182
+ 'type' => 'slides',
183
  'layout' => 'responsive',
184
  ),
185
  implode( PHP_EOL, $images )
includes/embeds/class-amp-instagram-embed.php CHANGED
@@ -17,9 +17,6 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
17
  protected $DEFAULT_WIDTH = 600;
18
  protected $DEFAULT_HEIGHT = 600;
19
 
20
- private static $script_slug = 'amp-instagram';
21
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-instagram-0.1.js';
22
-
23
  public function register_embed() {
24
  wp_embed_register_handler( 'amp-instagram', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
25
  add_shortcode( 'instagram', array( $this, 'shortcode' ) );
@@ -30,14 +27,6 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
30
  remove_shortcode( 'instagram' );
31
  }
32
 
33
- public function get_scripts() {
34
- if ( ! $this->did_convert_elements ) {
35
- return array();
36
- }
37
-
38
- return array( self::$script_slug => self::$script_src );
39
- }
40
-
41
  public function shortcode( $attr ) {
42
  $url = false;
43
 
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' ) );
27
  remove_shortcode( 'instagram' );
28
  }
29
 
 
 
 
 
 
 
 
 
30
  public function shortcode( $attr ) {
31
  $url = false;
32
 
includes/embeds/class-amp-issuu-embed-handler.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Issuu_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 0.7
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Issuu_Embed_Handler
11
+ */
12
+ class AMP_Issuu_Embed_Handler extends AMP_Base_Embed_Handler {
13
+ /**
14
+ * Regex matched to produce output amp-iframe.
15
+ *
16
+ * @const string
17
+ */
18
+ const URL_PATTERN = '#https?://(www\.)?issuu\.com/.+/docs/.+#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 Meetup 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'], 'issuu.com' ) ) {
45
+ if ( preg_match( '/width\s*:\s*(\d+)px/', $return, $matches ) ) {
46
+ $attr['width'] = $matches[1];
47
+ }
48
+ if ( preg_match( '/height\s*:\s*(\d+)px/', $return, $matches ) ) {
49
+ $attr['height'] = $matches[1];
50
+ }
51
+
52
+ $return = AMP_HTML_Utils::build_tag(
53
+ 'amp-iframe',
54
+ array(
55
+ 'width' => $attr['width'],
56
+ 'height' => $attr['height'],
57
+ 'src' => $url,
58
+ 'sandbox' => 'allow-scripts allow-same-origin',
59
+ )
60
+ );
61
+ }
62
+ return $return;
63
+ }
64
+ }
65
+
includes/embeds/class-amp-meetup-embed-handler.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Meetup_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 0.7
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Meetup_Embed_Handler
11
+ */
12
+ class AMP_Meetup_Embed_Handler extends AMP_Base_Embed_Handler {
13
+
14
+ /**
15
+ * Register embed.
16
+ */
17
+ public function register_embed() {
18
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 2 );
19
+ }
20
+
21
+ /**
22
+ * Unregister embed.
23
+ */
24
+ public function unregister_embed() {
25
+ remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
26
+ }
27
+
28
+ /**
29
+ * Filter oEmbed HTML for Meetup to prepare it for AMP.
30
+ *
31
+ * @param string $cache Cache for oEmbed.
32
+ * @param string $url Embed URL.
33
+ * @return string Embed.
34
+ */
35
+ public function filter_embed_oembed_html( $cache, $url ) {
36
+ $parsed_url = wp_parse_url( $url );
37
+ if ( false !== strpos( $parsed_url['host'], 'meetup.com' ) ) {
38
+
39
+ // Supply the width/height so that we don't have to make requests to look them up later.
40
+ $cache = str_replace( '<img ', '<img width="50" height="50" ', $cache );
41
+ }
42
+ return $cache;
43
+ }
44
+ }
45
+
includes/embeds/class-amp-pinterest-embed.php CHANGED
@@ -15,9 +15,6 @@ class AMP_Pinterest_Embed_Handler extends AMP_Base_Embed_Handler {
15
  protected $DEFAULT_WIDTH = 450;
16
  protected $DEFAULT_HEIGHT = 750;
17
 
18
- private static $script_slug = 'amp-pinterest';
19
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-pinterest-0.1.js';
20
-
21
  public function register_embed() {
22
  wp_embed_register_handler(
23
  'amp-pinterest',
@@ -30,14 +27,6 @@ class AMP_Pinterest_Embed_Handler extends AMP_Base_Embed_Handler {
30
  wp_embed_unregister_handler('amp-pinterest', -1);
31
  }
32
 
33
- public function get_scripts() {
34
- if ( ! $this->did_convert_elements) {
35
- return array();
36
- }
37
-
38
- return array( self::$script_slug => self::$script_src);
39
- }
40
-
41
  public function oembed( $matches, $attr, $url, $rawattr ) {
42
  return $this->render( array( 'url' => $url ) );
43
  }
15
  protected $DEFAULT_WIDTH = 450;
16
  protected $DEFAULT_HEIGHT = 750;
17
 
 
 
 
18
  public function register_embed() {
19
  wp_embed_register_handler(
20
  'amp-pinterest',
27
  wp_embed_unregister_handler('amp-pinterest', -1);
28
  }
29
 
 
 
 
 
 
 
 
 
30
  public function oembed( $matches, $attr, $url, $rawattr ) {
31
  return $this->render( array( 'url' => $url ) );
32
  }
includes/embeds/class-amp-playlist-embed-handler.php ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Playlist_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 0.7
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Playlist_Embed_Handler
11
+ *
12
+ * Creates AMP-compatible markup for the WordPress 'playlist' shortcode.
13
+ *
14
+ * @package AMP
15
+ */
16
+ class AMP_Playlist_Embed_Handler extends AMP_Base_Embed_Handler {
17
+
18
+ /**
19
+ * The tag of the shortcode.
20
+ *
21
+ * @var string
22
+ */
23
+ const SHORTCODE = 'playlist';
24
+
25
+ /**
26
+ * The default height of the thumbnail image for 'audio' playlist tracks.
27
+ *
28
+ * @var int
29
+ */
30
+ const DEFAULT_THUMB_HEIGHT = 64;
31
+
32
+ /**
33
+ * The default width of the thumbnail image for 'audio' playlist tracks.
34
+ *
35
+ * @var int
36
+ */
37
+ const DEFAULT_THUMB_WIDTH = 48;
38
+
39
+ /**
40
+ * The max width of the audio thumbnail image.
41
+ *
42
+ * This corresponds to the max-width in wp-mediaelement.css:
43
+ * .wp-playlist .wp-playlist-current-item img
44
+ *
45
+ * @var int
46
+ */
47
+ const THUMB_MAX_WIDTH = 60;
48
+
49
+ /**
50
+ * The height of the carousel.
51
+ *
52
+ * @var int
53
+ */
54
+ const CAROUSEL_HEIGHT = 160;
55
+
56
+ /**
57
+ * The pattern to get the playlist data.
58
+ *
59
+ * @var string
60
+ */
61
+ const PLAYLIST_REGEX = ':<script type="application/json" class="wp-playlist-script">(.+?)</script>:s';
62
+
63
+ /**
64
+ * The ID of individual playlist.
65
+ *
66
+ * @var int
67
+ */
68
+ public static $playlist_id = 0;
69
+
70
+ /**
71
+ * The removed shortcode callback.
72
+ *
73
+ * @var callable
74
+ */
75
+ public $removed_shortcode_callback;
76
+
77
+ /**
78
+ * Registers the playlist shortcode.
79
+ *
80
+ * @global array $shortcode_tags
81
+ * @return void
82
+ */
83
+ public function register_embed() {
84
+ global $shortcode_tags;
85
+ if ( shortcode_exists( self::SHORTCODE ) ) {
86
+ $this->removed_shortcode_callback = $shortcode_tags[ self::SHORTCODE ];
87
+ }
88
+ add_shortcode( self::SHORTCODE, array( $this, 'shortcode' ) );
89
+ remove_action( 'wp_playlist_scripts', 'wp_playlist_scripts' );
90
+ }
91
+
92
+ /**
93
+ * Unregisters the playlist shortcode.
94
+ *
95
+ * @return void
96
+ */
97
+ public function unregister_embed() {
98
+ if ( $this->removed_shortcode_callback ) {
99
+ add_shortcode( self::SHORTCODE, $this->removed_shortcode_callback );
100
+ $this->removed_shortcode_callback = null;
101
+ }
102
+ add_action( 'wp_playlist_scripts', 'wp_playlist_scripts' );
103
+ }
104
+
105
+ /**
106
+ * Enqueues the playlist styling.
107
+ *
108
+ * @return void
109
+ */
110
+ public function enqueue_styles() {
111
+ wp_enqueue_style(
112
+ 'amp-playlist-shortcode',
113
+ amp_get_asset_url( 'css/amp-playlist-shortcode.css' ),
114
+ array( 'wp-mediaelement' ),
115
+ AMP__VERSION
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Gets AMP-compliant markup for the playlist shortcode.
121
+ *
122
+ * Uses the JSON that wp_playlist_shortcode() produces.
123
+ * Gets the markup, based on the type of playlist.
124
+ *
125
+ * @param array $attr The playlist attributes.
126
+ * @return string Playlist shortcode markup.
127
+ */
128
+ public function shortcode( $attr ) {
129
+ $data = $this->get_data( $attr );
130
+ if ( isset( $data['type'] ) && ( 'audio' === $data['type'] ) ) {
131
+ return $this->audio_playlist( $data );
132
+ } elseif ( isset( $data['type'] ) && ( 'video' === $data['type'] ) ) {
133
+ return $this->video_playlist( $data );
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Gets an AMP-compliant audio playlist.
139
+ *
140
+ * @param array $data Data.
141
+ * @return string Playlist shortcode markup, or an empty string.
142
+ */
143
+ public function audio_playlist( $data ) {
144
+ if ( ! isset( $data['tracks'] ) ) {
145
+ return '';
146
+ }
147
+ self::$playlist_id++;
148
+ $container_id = 'wpPlaylist' . self::$playlist_id . 'Carousel';
149
+ $state_id = 'wpPlaylist' . self::$playlist_id;
150
+ $amp_state = array(
151
+ 'selectedIndex' => 0,
152
+ );
153
+
154
+ $this->enqueue_styles();
155
+ ob_start();
156
+ ?>
157
+ <div class="wp-playlist wp-audio-playlist wp-playlist-light">
158
+ <amp-state id="<?php echo esc_attr( $state_id ); ?>">
159
+ <script type="application/json"><?php echo wp_json_encode( $amp_state ); ?></script>
160
+ </amp-state>
161
+ <amp-carousel id="<?php echo esc_attr( $container_id ); ?>" [slide]="<?php echo esc_attr( $state_id . '.selectedIndex' ); ?>" height="<?php echo esc_attr( self::CAROUSEL_HEIGHT ); ?>" width="auto" type="slides">
162
+ <?php
163
+ foreach ( $data['tracks'] as $track ) :
164
+ $title = $this->get_title( $track );
165
+ $image_url = isset( $track['thumb']['src'] ) ? $track['thumb']['src'] : '';
166
+ $dimensions = $this->get_thumb_dimensions( $track );
167
+ ?>
168
+ <div>
169
+ <div class="wp-playlist-current-item">
170
+ <?php if ( $image_url ) : ?>
171
+ <amp-img src="<?php echo esc_url( $image_url ); ?>" height="<?php echo esc_attr( $dimensions['height'] ); ?>" width="<?php echo esc_attr( $dimensions['width'] ); ?>"></amp-img>
172
+ <?php endif; ?>
173
+ <div class="wp-playlist-caption">
174
+ <span class="wp-playlist-item-meta wp-playlist-item-title"><?php echo esc_html( $title ); ?></span>
175
+ </div>
176
+ </div>
177
+ <amp-audio width="auto" height="50" src="<?php echo esc_url( $track['src'] ); ?>"></amp-audio>
178
+ </div>
179
+ <?php endforeach; ?>
180
+ </amp-carousel>
181
+ <?php $this->print_tracks( $state_id, $data['tracks'] ); ?>
182
+ </div>
183
+ <?php
184
+ return ob_get_clean();
185
+ }
186
+
187
+ /**
188
+ * Gets an AMP-compliant video playlist.
189
+ *
190
+ * This uses similar markup to the native playlist shortcode output.
191
+ * So the styles from wp-mediaelement.min.css will apply to it.
192
+ *
193
+ * @global int $content_width
194
+ * @param array $data Data.
195
+ * @return string $video_playlist Markup for the video playlist.
196
+ */
197
+ public function video_playlist( $data ) {
198
+ global $content_width;
199
+ if ( ! isset( $data['tracks'], $data['tracks'][0]['src'] ) ) {
200
+ return '';
201
+ }
202
+ self::$playlist_id++;
203
+ $state_id = 'wpPlaylist' . self::$playlist_id;
204
+ $amp_state = array(
205
+ 'selectedIndex' => 0,
206
+ );
207
+ foreach ( $data['tracks'] as $index => $track ) {
208
+ $amp_state[ $index ] = array(
209
+ 'videoUrl' => $track['src'],
210
+ 'thumb' => isset( $track['thumb']['src'] ) ? $track['thumb']['src'] : '',
211
+ );
212
+ }
213
+
214
+ $dimensions = isset( $data['tracks'][0]['dimensions']['resized'] ) ? $data['tracks'][0]['dimensions']['resized'] : null;
215
+ $width = isset( $dimensions['width'] ) ? $dimensions['width'] : $content_width;
216
+ $height = isset( $dimensions['height'] ) ? $dimensions['height'] : null;
217
+ $src_bound = sprintf( '%s[%s.selectedIndex].videoUrl', $state_id, $state_id );
218
+
219
+ $this->enqueue_styles();
220
+ ob_start();
221
+ ?>
222
+ <div class="wp-playlist wp-video-playlist wp-playlist-light">
223
+ <amp-state id="<?php echo esc_attr( $state_id ); ?>">
224
+ <script type="application/json"><?php echo wp_json_encode( $amp_state ); // WPCS: XSS ok. ?></script>
225
+ </amp-state>
226
+ <amp-video id="amp-video" src="<?php echo esc_url( $data['tracks'][0]['src'] ); ?>" [src]="<?php echo esc_attr( $src_bound ); ?>" width="<?php echo esc_attr( $width ); ?>" height="<?php echo esc_attr( $height ); ?>" controls></amp-video>
227
+ <?php $this->print_tracks( $state_id, $data['tracks'] ); ?>
228
+ </div>
229
+ <?php
230
+ return ob_get_clean(); // WPCS: XSS ok.
231
+ }
232
+
233
+ /**
234
+ * Gets the thumbnail image dimensions, including height and width.
235
+ *
236
+ * If the width is higher than the maximum width,
237
+ * reduces it to the maximum width.
238
+ * And it proportionally reduces the height.
239
+ *
240
+ * @param array $track The data for the track.
241
+ * @return array {
242
+ * Dimensions.
243
+ *
244
+ * @type int $height Image height.
245
+ * @type int $width Image width.
246
+ * }
247
+ */
248
+ public function get_thumb_dimensions( $track ) {
249
+ $original_height = isset( $track['thumb']['height'] ) ? intval( $track['thumb']['height'] ) : self::DEFAULT_THUMB_HEIGHT;
250
+ $original_width = isset( $track['thumb']['width'] ) ? intval( $track['thumb']['width'] ) : self::DEFAULT_THUMB_WIDTH;
251
+ if ( $original_width > self::THUMB_MAX_WIDTH ) {
252
+ $ratio = $original_width / self::THUMB_MAX_WIDTH;
253
+ $height = intval( $original_height / $ratio );
254
+ } else {
255
+ $height = $original_height;
256
+ }
257
+ $width = min( self::THUMB_MAX_WIDTH, $original_width );
258
+ return compact( 'height', 'width' );
259
+ }
260
+
261
+ /**
262
+ * Outputs the playlist tracks, based on the type of playlist.
263
+ *
264
+ * These typically appear below the player.
265
+ * Clicking a track triggers the player to appear with its src.
266
+ *
267
+ * @param string $state_id The ID of the container.
268
+ * @param array $tracks Tracks.
269
+ * @return void
270
+ */
271
+ public function print_tracks( $state_id, $tracks ) {
272
+ ?>
273
+ <div class="wp-playlist-tracks">
274
+ <?php foreach ( $tracks as $index => $track ) : ?>
275
+ <?php
276
+ $on = 'tap:AMP.setState(' . wp_json_encode( array( $state_id => array( 'selectedIndex' => $index ) ) ) . ')';
277
+ $initial_class = 0 === $index ? 'wp-playlist-item wp-playlist-playing' : 'wp-playlist-item';
278
+ $bound_class = sprintf( '%d == %s.selectedIndex ? "wp-playlist-item wp-playlist-playing" : "wp-playlist-item"', $index, $state_id );
279
+ ?>
280
+ <div class="<?php echo esc_attr( $initial_class ); ?>" [class]="<?php echo esc_attr( $bound_class ); ?>" >
281
+ <a class="wp-playlist-caption" on="<?php echo esc_attr( $on ); ?>">
282
+ <?php echo esc_html( strval( $index + 1 ) . '.' ); ?> <span class="wp-playlist-item-title"><?php echo esc_html( $this->get_title( $track ) ); ?></span>
283
+ </a>
284
+ <?php if ( isset( $track['meta']['length_formatted'] ) ) : ?>
285
+ <div class="wp-playlist-item-length"><?php echo esc_html( $track['meta']['length_formatted'] ); ?></div>
286
+ <?php endif; ?>
287
+ </div>
288
+ <?php endforeach; ?>
289
+ </div>
290
+ <?php
291
+ }
292
+
293
+ /**
294
+ * Gets the data for the playlist.
295
+ *
296
+ * @see wp_playlist_shortcode()
297
+ * @param array $attr The shortcode attributes.
298
+ * @return array $data The data for the playlist.
299
+ */
300
+ public function get_data( $attr ) {
301
+ $markup = wp_playlist_shortcode( $attr );
302
+ preg_match( self::PLAYLIST_REGEX, $markup, $matches );
303
+ if ( empty( $matches[1] ) ) {
304
+ return array();
305
+ }
306
+ return json_decode( $matches[1], true );
307
+ }
308
+
309
+ /**
310
+ * Gets the title for the track.
311
+ *
312
+ * @param array $track The track data.
313
+ * @return string $title The title of the track.
314
+ */
315
+ public function get_title( $track ) {
316
+ if ( ! empty( $track['caption'] ) ) {
317
+ return $track['caption'];
318
+ } elseif ( ! empty( $track['title'] ) ) {
319
+ return $track['title'];
320
+ }
321
+ return '';
322
+ }
323
+
324
+ }
includes/embeds/class-amp-reddit-embed-handler.php ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Reddit_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 0.7
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Reddit_Embed_Handler
11
+ */
12
+ class AMP_Reddit_Embed_Handler extends AMP_Base_Embed_Handler {
13
+ /**
14
+ * Regex matched to produce output amp-reddit.
15
+ *
16
+ * @const string
17
+ */
18
+ const URL_PATTERN = '#https?://(www\.)?reddit\.com/r/[^/]+/comments/.*#i';
19
+
20
+ /**
21
+ * Register embed.
22
+ */
23
+ public function register_embed() {
24
+ wp_embed_register_handler( 'amp-reddit', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
25
+ }
26
+
27
+ /**
28
+ * Unregister embed.
29
+ */
30
+ public function unregister_embed() {
31
+ wp_embed_unregister_handler( 'amp-reddit', -1 );
32
+ }
33
+
34
+ /**
35
+ * Embed found with matching URL callback.
36
+ *
37
+ * @param array $matches URL regex matches.
38
+ * @param array $attr Additional parameters.
39
+ * @param array $url URL.
40
+ * @return string Embed.
41
+ */
42
+ public function oembed( $matches, $attr, $url ) {
43
+ return $this->render( array( 'url' => $url ) );
44
+ }
45
+
46
+ /**
47
+ * Output the Reddit amp element.
48
+ *
49
+ * @param array $args parameters used for output.
50
+ * @return string Rendered content.
51
+ */
52
+ public function render( $args ) {
53
+ $args = wp_parse_args( $args, array(
54
+ 'url' => false,
55
+ ) );
56
+
57
+ if ( empty( $args['url'] ) ) {
58
+ return '';
59
+ }
60
+
61
+ // @todo Sizing is not yet correct. See <https://github.com/ampproject/amphtml/issues/11869>.
62
+ return AMP_HTML_Utils::build_tag(
63
+ 'amp-reddit',
64
+ array(
65
+ 'layout' => 'responsive',
66
+ 'data-embedtype' => 'post',
67
+ 'width' => '100',
68
+ 'height' => '100',
69
+ 'data-src' => $args['url'],
70
+ )
71
+ );
72
+ }
73
+ }
74
+
includes/embeds/class-amp-soundcloud-embed.php CHANGED
@@ -17,21 +17,7 @@ class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
17
  *
18
  * @var int
19
  */
20
- protected $DEFAULT_HEIGHT = 200;
21
-
22
- /**
23
- * Script slug.
24
- *
25
- * @var string
26
- */
27
- private static $script_slug = 'amp-soundcloud';
28
-
29
- /**
30
- * Script source.
31
- *
32
- * @var string
33
- */
34
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-soundcloud-0.1.js';
35
 
36
  /**
37
  * Register embed.
@@ -49,19 +35,6 @@ class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
49
  remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
50
  }
51
 
52
- /**
53
- * Get scripts needed by component.
54
- *
55
- * @return array Scripts.
56
- */
57
- public function get_scripts() {
58
- if ( ! $this->did_convert_elements ) {
59
- return array();
60
- }
61
-
62
- return array( self::$script_slug => self::$script_src );
63
- }
64
-
65
  /**
66
  * Render oEmbed.
67
  *
17
  *
18
  * @var int
19
  */
20
+ protected $DEFAULT_HEIGHT = 200; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.MemberNotSnakeCase
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  /**
23
  * Register embed.
35
  remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Render oEmbed.
40
  *
includes/embeds/class-amp-tumblr-embed-handler.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Tumblr_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ * @since 0.7
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Tumblr_Embed_Handler
11
+ */
12
+ class AMP_Tumblr_Embed_Handler extends AMP_Base_Embed_Handler {
13
+
14
+ /**
15
+ * Register embed.
16
+ */
17
+ public function register_embed() {
18
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 2 );
19
+ }
20
+
21
+ /**
22
+ * Unregister embed.
23
+ */
24
+ public function unregister_embed() {
25
+ remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
26
+ }
27
+
28
+ /**
29
+ * Filter oEmbed HTML for Tumblr to prepare it for AMP.
30
+ *
31
+ * @param string $cache Cache for oEmbed.
32
+ * @param string $url Embed URL.
33
+ * @return string Embed.
34
+ */
35
+ public function filter_embed_oembed_html( $cache, $url ) {
36
+ $parsed_url = wp_parse_url( $url );
37
+ if ( false === strpos( $parsed_url['host'], 'tumblr.com' ) ) {
38
+ return $cache;
39
+ }
40
+
41
+ // @todo The iframe will not get sized properly.
42
+ if ( preg_match( '#data-href="(?P<href>https://embed.tumblr.com/embed/post/\w+/\w+)"#', $cache, $matches ) ) {
43
+ $cache = AMP_HTML_Utils::build_tag(
44
+ 'amp-iframe',
45
+ array(
46
+ 'width' => $this->args['width'],
47
+ 'height' => $this->args['height'],
48
+ 'layout' => 'responsive',
49
+ 'sandbox' => 'allow-scripts allow-popups', // The allow-scripts is needed to allow the iframe to render; allow-popups needed to allow clicking.
50
+ 'src' => $matches['href'],
51
+ ),
52
+ sprintf( '<a placeholder href="%s">Tumblr</a>', $url )
53
+ );
54
+ }
55
+
56
+ return $cache;
57
+ }
58
+
59
+ }
60
+
includes/embeds/class-amp-twitter-embed.php CHANGED
@@ -13,9 +13,6 @@
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
- private static $script_slug = 'amp-twitter';
17
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-twitter-0.1.js';
18
-
19
  public function register_embed() {
20
  add_shortcode( 'tweet', array( $this, 'shortcode' ) );
21
  wp_embed_register_handler( 'amp-twitter', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
@@ -26,14 +23,6 @@ class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
26
  wp_embed_unregister_handler( 'amp-twitter', -1 );
27
  }
28
 
29
- public function get_scripts() {
30
- if ( ! $this->did_convert_elements ) {
31
- return array();
32
- }
33
-
34
- return array( self::$script_slug => self::$script_src );
35
- }
36
-
37
  function shortcode( $attr ) {
38
  $attr = wp_parse_args( $attr, array(
39
  'tweet' => false,
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 );
23
  wp_embed_unregister_handler( 'amp-twitter', -1 );
24
  }
25
 
 
 
 
 
 
 
 
 
26
  function shortcode( $attr ) {
27
  $attr = wp_parse_args( $attr, array(
28
  'tweet' => false,
includes/embeds/class-amp-vimeo-embed.php CHANGED
@@ -19,9 +19,6 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
19
  protected $DEFAULT_WIDTH = 600;
20
  protected $DEFAULT_HEIGHT = 338;
21
 
22
- private static $script_slug = 'amp-vimeo';
23
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-vimeo-0.1.js';
24
-
25
  function __construct( $args = array() ) {
26
  parent::__construct( $args );
27
 
@@ -35,6 +32,7 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
35
  function register_embed() {
36
  wp_embed_register_handler( 'amp-vimeo', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
37
  add_shortcode( 'vimeo', array( $this, 'shortcode' ) );
 
38
  }
39
 
40
  public function unregister_embed() {
@@ -42,14 +40,6 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
42
  remove_shortcode( 'vimeo' );
43
  }
44
 
45
- public function get_scripts() {
46
- if ( ! $this->did_convert_elements ) {
47
- return array();
48
- }
49
-
50
- return array( self::$script_slug => self::$script_src );
51
- }
52
-
53
  public function shortcode( $attr ) {
54
  $video_id = false;
55
 
@@ -118,4 +108,27 @@ class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
118
 
119
  return $video_id;
120
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  }
19
  protected $DEFAULT_WIDTH = 600;
20
  protected $DEFAULT_HEIGHT = 338;
21
 
 
 
 
22
  function __construct( $args = array() ) {
23
  parent::__construct( $args );
24
 
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() {
40
  remove_shortcode( 'vimeo' );
41
  }
42
 
 
 
 
 
 
 
 
 
43
  public function shortcode( $attr ) {
44
  $video_id = false;
45
 
108
 
109
  return $video_id;
110
  }
111
+
112
+ /**
113
+ * Override the output of Vimeo videos.
114
+ *
115
+ * This overrides the value in wp_video_shortcode().
116
+ * The pattern matching is copied from WP_Widget_Media_Video::render().
117
+ *
118
+ * @param string $html Empty variable to be replaced with shortcode markup.
119
+ * @param array $attr The shortcode attributes.
120
+ * @return string|null $markup The markup to output.
121
+ */
122
+ public function video_override( $html, $attr ) {
123
+ if ( ! isset( $attr['src'] ) ) {
124
+ return $html;
125
+ }
126
+ $src = $attr['src'];
127
+ $vimeo_pattern = '#^https?://(.+\.)?vimeo\.com/.*#';
128
+ if ( 1 === preg_match( $vimeo_pattern, $src ) ) {
129
+ return $this->shortcode( array( $src ) );
130
+ }
131
+ return $html;
132
+ }
133
+
134
  }
includes/embeds/class-amp-vine-embed.php CHANGED
@@ -14,9 +14,6 @@ class AMP_Vine_Embed_Handler extends AMP_Base_Embed_Handler {
14
  protected $DEFAULT_WIDTH = 400;
15
  protected $DEFAULT_HEIGHT = 400;
16
 
17
- private static $script_slug = 'amp-vine';
18
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-vine-0.1.js';
19
-
20
  public function register_embed() {
21
  wp_embed_register_handler( 'amp-vine', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
22
  }
@@ -25,14 +22,6 @@ class AMP_Vine_Embed_Handler extends AMP_Base_Embed_Handler {
25
  wp_embed_unregister_handler( 'amp-vine', -1 );
26
  }
27
 
28
- public function get_scripts() {
29
- if ( ! $this->did_convert_elements ) {
30
- return array();
31
- }
32
-
33
- return array( self::$script_slug => self::$script_src );
34
- }
35
-
36
  public function oembed( $matches, $attr, $url, $rawattr ) {
37
  return $this->render( array( 'url' => $url, 'vine_id' => end( $matches ) ) );
38
  }
14
  protected $DEFAULT_WIDTH = 400;
15
  protected $DEFAULT_HEIGHT = 400;
16
 
 
 
 
17
  public function register_embed() {
18
  wp_embed_register_handler( 'amp-vine', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
19
  }
22
  wp_embed_unregister_handler( 'amp-vine', -1 );
23
  }
24
 
 
 
 
 
 
 
 
 
25
  public function oembed( $matches, $attr, $url, $rawattr ) {
26
  return $this->render( array( 'url' => $url, 'vine_id' => end( $matches ) ) );
27
  }
includes/embeds/class-amp-youtube-embed.php CHANGED
@@ -19,15 +19,12 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
19
  protected $DEFAULT_WIDTH = 600;
20
  protected $DEFAULT_HEIGHT = 338;
21
 
22
- private static $script_slug = 'amp-youtube';
23
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-youtube-0.1.js';
24
-
25
  function __construct( $args = array() ) {
26
  parent::__construct( $args );
27
 
28
  if ( isset( $this->args['content_max_width'] ) ) {
29
  $max_width = $this->args['content_max_width'];
30
- $this->args['width'] = $max_width;
31
  $this->args['height'] = round( $max_width * self::RATIO );
32
  }
33
  }
@@ -35,6 +32,7 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
35
  function register_embed() {
36
  wp_embed_register_handler( 'amp-youtube', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
37
  add_shortcode( 'youtube', array( $this, 'shortcode' ) );
 
38
  }
39
 
40
  public function unregister_embed() {
@@ -42,14 +40,6 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
42
  remove_shortcode( 'youtube' );
43
  }
44
 
45
- public function get_scripts() {
46
- if ( ! $this->did_convert_elements ) {
47
- return array();
48
- }
49
-
50
- return array( self::$script_slug => self::$script_src );
51
- }
52
-
53
  public function shortcode( $attr ) {
54
  $url = false;
55
  $video_id = false;
@@ -136,4 +126,27 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
136
 
137
  return $value;
138
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  }
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
  }
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() {
40
  remove_shortcode( 'youtube' );
41
  }
42
 
 
 
 
 
 
 
 
 
43
  public function shortcode( $attr ) {
44
  $url = false;
45
  $video_id = false;
126
 
127
  return $value;
128
  }
129
+
130
+ /**
131
+ * Override the output of YouTube videos.
132
+ *
133
+ * This overrides the value in wp_video_shortcode().
134
+ * The pattern matching is copied from WP_Widget_Media_Video::render().
135
+ *
136
+ * @param string $html Empty variable to be replaced with shortcode markup.
137
+ * @param array $attr The shortcode attributes.
138
+ * @return string|null $markup The markup to output.
139
+ */
140
+ public function video_override( $html, $attr ) {
141
+ if ( ! isset( $attr['src'] ) ) {
142
+ return $html;
143
+ }
144
+ $src = $attr['src'];
145
+ $youtube_pattern = '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#';
146
+ if ( 1 === preg_match( $youtube_pattern, $src ) ) {
147
+ return $this->shortcode( array( $src ) );
148
+ }
149
+ return $html;
150
+ }
151
+
152
  }
includes/options/class-amp-options-manager.php CHANGED
@@ -47,7 +47,7 @@ class AMP_Options_Manager {
47
  sort( $old_post_types );
48
  sort( $new_post_types );
49
  if ( $old_post_types !== $new_post_types ) {
50
- flush_rewrite_rules();
51
  }
52
  }
53
 
@@ -171,7 +171,7 @@ class AMP_Options_Manager {
171
  continue;
172
  }
173
 
174
- $post_type_supported = post_type_supports( $post_type->name, AMP_QUERY_VAR );
175
  $is_support_elected = in_array( $post_type->name, $supported_types, true );
176
 
177
  $error = null;
47
  sort( $old_post_types );
48
  sort( $new_post_types );
49
  if ( $old_post_types !== $new_post_types ) {
50
+ flush_rewrite_rules( false );
51
  }
52
  }
53
 
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;
includes/options/class-amp-options-menu.php CHANGED
@@ -22,7 +22,7 @@ class AMP_Options_Menu {
22
  */
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' ) );
26
  }
27
 
28
  /**
@@ -84,7 +84,7 @@ class AMP_Options_Menu {
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 = 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 ); ?>">
@@ -94,7 +94,7 @@ class AMP_Options_Menu {
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, post_type_supports( $post_type->name, AMP_QUERY_VAR ) ); ?>
98
  <?php disabled( $is_builtin ); ?>
99
  >
100
  <label for="<?php echo esc_attr( $element_id ); ?>">
@@ -102,7 +102,15 @@ class AMP_Options_Menu {
102
  </label>
103
  <br>
104
  <?php endforeach; ?>
105
- <p class="description"><?php esc_html_e( 'Enable/disable AMP post type(s) support', 'amp' ); ?></p>
 
 
 
 
 
 
 
 
106
  </fieldset>
107
  <?php
108
  }
@@ -124,7 +132,9 @@ class AMP_Options_Menu {
124
  <?php
125
  settings_fields( AMP_Options_Manager::OPTION_NAME );
126
  do_settings_sections( AMP_Options_Manager::OPTION_NAME );
127
- submit_button();
 
 
128
  ?>
129
  </form>
130
  </div>
22
  */
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
  /**
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 ); ?>">
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 ); ?>">
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
  }
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
  ?>
139
  </form>
140
  </div>
includes/sanitizers/class-amp-allowed-tags-generated.php CHANGED
@@ -8,11 +8,13 @@
8
  * Note: This file only contains tags that are relevant to the `body` of
9
  * an AMP page. To include additional elements modify the variable
10
  * `mandatory_parent_blacklist` in the amp_wp_build.py script.
 
 
11
  */
12
  class AMP_Allowed_Tags_Generated {
13
 
14
- private static $spec_file_revision = 527;
15
- private static $minimum_validator_revision_required = 265;
16
 
17
  private static $allowed_tags = array(
18
  'a' => array(
@@ -23,31 +25,36 @@ class AMP_Allowed_Tags_Generated {
23
  'download' => array(),
24
  'href' => array(
25
  'blacklisted_value_regex' => '__amp_source_origin',
26
- 'allow_empty' => true,
27
- 'allow_relative' => true,
28
- 'allowed_protocol' => array(
29
- 'bbmi',
30
- 'fb-messenger',
31
- 'ftp',
32
- 'http',
33
- 'https',
34
- 'intent',
35
- 'line',
36
- 'mailto',
37
- 'skype',
38
- 'sms',
39
- 'snapchat',
40
- 'tel',
41
- 'tg',
42
- 'threema',
43
- 'twitter',
44
- 'viber',
45
- 'whatsapp',
 
 
 
 
46
  ),
47
  ),
48
  'hreflang' => array(),
49
  'media' => array(),
50
  'name' => array(),
 
51
  'rel' => array(
52
  'blacklisted_value_regex' => '(^|\\s)(components|dns-prefetch|import|manifest|preconnect|prefetch|preload|prerender|serviceworker|stylesheet|subresource|)(\\s|$)',
53
  ),
@@ -61,38 +68,26 @@ class AMP_Allowed_Tags_Generated {
61
  ),
62
  ),
63
  'tag_spec' => array(
64
- 'html_format' => array(
65
- 'amp',
66
- 'amp4ads',
67
- ),
68
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#links',
69
  ),
70
-
71
  ),
72
  ),
73
  'abbr' => array(
74
  array(
75
  'attr_spec_list' => array(),
76
  'tag_spec' => array(),
77
-
78
  ),
79
  ),
80
  'acronym' => array(
81
  array(
82
  'attr_spec_list' => array(),
83
- 'tag_spec' => array(
84
- 'html_format' => array(
85
- 'amp',
86
- ),
87
- ),
88
-
89
  ),
90
  ),
91
  'address' => array(
92
  array(
93
  'attr_spec_list' => array(),
94
  'tag_spec' => array(),
95
-
96
  ),
97
  ),
98
  'amp-3q-player' => array(
@@ -110,11 +105,10 @@ class AMP_Allowed_Tags_Generated {
110
  ),
111
  ),
112
  'tag_spec' => array(
113
- 'html_format' => array(
114
- 'amp',
115
  ),
116
  ),
117
-
118
  ),
119
  ),
120
  'amp-accordion' => array(
@@ -123,11 +117,16 @@ class AMP_Allowed_Tags_Generated {
123
  'disable-session-states' => array(
124
  'value' => '',
125
  ),
 
 
 
126
  ),
127
  'tag_spec' => array(
 
 
 
128
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-accordion',
129
  ),
130
-
131
  ),
132
  ),
133
  'amp-ad' => array(
@@ -139,44 +138,55 @@ class AMP_Allowed_Tags_Generated {
139
  'noloading' => array(
140
  'value' => '',
141
  ),
 
142
  'src' => array(
143
  'blacklisted_value_regex' => '__amp_source_origin',
144
- 'allow_relative' => true,
145
- 'allowed_protocol' => array(
146
- 'https',
 
 
147
  ),
148
  ),
 
149
  'type' => array(
150
  'mandatory' => true,
151
  ),
152
  ),
153
  'tag_spec' => array(
 
 
 
154
  'disallowed_ancestor' => array(
155
- 'amp-app-banner',
156
  ),
157
- 'html_format' => array(
158
- 'amp',
159
  ),
160
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
161
  ),
162
-
163
  ),
164
  array(
165
  'attr_spec_list' => array(
166
  'alt' => array(),
167
  'data-multi-size' => array(
 
168
  'mandatory' => true,
 
169
  ),
170
  'json' => array(),
171
  'media' => array(),
172
  'noloading' => array(
173
  'value' => '',
174
  ),
 
175
  'src' => array(
176
  'blacklisted_value_regex' => '__amp_source_origin',
177
- 'allow_relative' => true,
178
- 'allowed_protocol' => array(
179
- 'https',
 
 
180
  ),
181
  ),
182
  'type' => array(
@@ -184,40 +194,64 @@ class AMP_Allowed_Tags_Generated {
184
  ),
185
  ),
186
  'tag_spec' => array(
 
 
 
187
  'disallowed_ancestor' => array(
188
- 'amp-app-banner',
189
- 'amp-carousel',
190
- 'amp-fx-flying-carpet',
191
- 'amp-lightbox',
192
- 'amp-sticky-ad',
193
  ),
194
- 'html_format' => array(
195
- 'amp',
196
  ),
197
  'spec_name' => 'amp-ad with data-multi-size attribute',
198
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
199
  ),
200
-
201
  ),
202
- ),
203
- 'amp-ad-exit' => array(
204
  array(
205
  'attr_spec_list' => array(
206
- 'id' => array(
 
 
207
  'mandatory' => true,
 
208
  ),
 
209
  'media' => array(),
210
  'noloading' => array(
211
  'value' => '',
212
  ),
 
 
 
 
 
 
 
 
 
 
 
 
213
  ),
214
  'tag_spec' => array(
215
- 'html_format' => array(
216
- 'amp4ads',
 
 
 
 
 
 
 
 
217
  ),
218
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad-exit',
 
219
  ),
220
-
221
  ),
222
  ),
223
  'amp-analytics' => array(
@@ -225,22 +259,22 @@ class AMP_Allowed_Tags_Generated {
225
  'attr_spec_list' => array(
226
  'config' => array(
227
  'blacklisted_value_regex' => '__amp_source_origin',
228
- 'allow_empty' => true,
229
- 'allow_relative' => true,
230
- 'allowed_protocol' => array(
231
- 'https',
 
 
232
  ),
233
  ),
234
  'type' => array(),
235
  ),
236
  'tag_spec' => array(
237
- 'html_format' => array(
238
- 'amp',
239
- 'amp4ads',
240
  ),
241
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-analytics',
242
  ),
243
-
244
  ),
245
  ),
246
  'amp-anim' => array(
@@ -259,22 +293,22 @@ class AMP_Allowed_Tags_Generated {
259
  ),
260
  'blacklisted_value_regex' => '__amp_source_origin',
261
  'mandatory' => true,
262
- 'allow_relative' => true,
263
- 'allowed_protocol' => array(
264
- 'data',
265
- 'http',
266
- 'https',
 
 
267
  ),
268
  ),
269
  ),
270
  'tag_spec' => array(
271
- 'html_format' => array(
272
- 'amp',
273
- 'amp4ads',
274
  ),
275
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-anim',
276
  ),
277
-
278
  ),
279
  ),
280
  'amp-animation' => array(
@@ -289,22 +323,20 @@ class AMP_Allowed_Tags_Generated {
289
  ),
290
  ),
291
  'tag_spec' => array(
292
- 'html_format' => array(
293
- 'amp',
294
- 'amp4ads',
295
  ),
296
  ),
297
-
298
  ),
299
  ),
300
  'amp-apester-media' => array(
301
  array(
302
  'attr_spec_list' => array(
303
  'data-apester-channel-token' => array(
304
- 'value_regex' => '[0-9a-za-z]+',
305
  ),
306
  'data-apester-media-id' => array(
307
- 'value_regex' => '[0-9a-za-z]+',
308
  ),
309
  'media' => array(),
310
  'noloading' => array(
@@ -312,12 +344,11 @@ class AMP_Allowed_Tags_Generated {
312
  ),
313
  ),
314
  'tag_spec' => array(
315
- 'html_format' => array(
316
- 'amp',
317
  ),
318
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-apester-media',
319
  ),
320
-
321
  ),
322
  ),
323
  'amp-app-banner' => array(
@@ -332,14 +363,13 @@ class AMP_Allowed_Tags_Generated {
332
  ),
333
  ),
334
  'tag_spec' => array(
335
- 'html_format' => array(
336
- 'amp',
337
- ),
338
  'mandatory_parent' => 'body',
 
 
 
339
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-app-banner',
340
  'unique' => true,
341
  ),
342
-
343
  ),
344
  ),
345
  'amp-audio' => array(
@@ -363,24 +393,28 @@ class AMP_Allowed_Tags_Generated {
363
  'noloading' => array(
364
  'value' => '',
365
  ),
 
 
 
366
  'src' => array(
367
  'blacklisted_value_regex' => '__amp_source_origin',
368
- 'allow_relative' => true,
369
- 'allowed_protocol' => array(
370
- 'https',
 
 
371
  ),
372
  ),
373
  ),
374
  'tag_spec' => array(
375
  'disallowed_ancestor' => array(
376
- 'amp-story',
377
  ),
378
- 'html_format' => array(
379
- 'amp',
380
  ),
381
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
382
  ),
383
-
384
  ),
385
  array(
386
  'attr_spec_list' => array(
@@ -405,76 +439,114 @@ class AMP_Allowed_Tags_Generated {
405
  ),
406
  'src' => array(
407
  'blacklisted_value_regex' => '__amp_source_origin',
408
- 'allow_relative' => true,
409
- 'allowed_protocol' => array(
410
- 'https',
 
 
411
  ),
412
  ),
413
  ),
414
  'tag_spec' => array(
415
- 'html_format' => array(
416
- 'amp',
417
- ),
418
  'mandatory_ancestor' => 'amp-story',
 
 
 
419
  'spec_name' => 'amp-story >> amp-audio',
420
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
421
  ),
422
-
423
  ),
 
 
424
  array(
425
  'attr_spec_list' => array(
426
- 'album' => array(),
427
- 'artist' => array(),
428
- 'artwork' => array(),
429
- 'controls' => array(),
430
- 'controlslist' => array(),
431
- 'loop' => array(
432
- 'value' => '',
433
- ),
434
  'media' => array(),
435
- 'muted' => array(
436
- 'value' => '',
437
- ),
438
  'noloading' => array(
439
  'value' => '',
440
  ),
441
- 'src' => array(
442
- 'blacklisted_value_regex' => '__amp_source_origin',
443
- 'allow_relative' => true,
444
- 'allowed_protocol' => array(
445
- 'https',
446
- ),
447
  ),
448
  ),
449
  'tag_spec' => array(
450
- 'html_format' => array(
451
- 'amp4ads',
 
452
  ),
453
- 'spec_name' => 'amp-audio (a4a)',
454
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
455
  ),
456
-
457
  ),
458
  ),
459
- 'amp-auto-ads' => array(
460
  array(
461
  'attr_spec_list' => array(
 
 
 
 
 
 
 
 
 
 
 
462
  'media' => array(),
463
  'noloading' => array(
464
  'value' => '',
465
  ),
466
- 'type' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  'mandatory' => true,
468
  ),
469
  ),
470
  'tag_spec' => array(
471
- 'html_format' => array(
472
- 'amp',
473
  ),
474
- 'mandatory_parent' => 'body',
475
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-auto-ads',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  ),
477
-
478
  ),
479
  ),
480
  'amp-brid-player' => array(
@@ -504,12 +576,11 @@ class AMP_Allowed_Tags_Generated {
504
  ),
505
  ),
506
  'tag_spec' => array(
507
- 'html_format' => array(
508
- 'amp',
509
  ),
510
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-brid-player',
511
  ),
512
-
513
  ),
514
  ),
515
  'amp-brightcove' => array(
@@ -530,12 +601,32 @@ class AMP_Allowed_Tags_Generated {
530
  ),
531
  ),
532
  'tag_spec' => array(
533
- 'html_format' => array(
534
- 'amp',
535
  ),
536
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-brightcove',
537
  ),
538
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  ),
540
  ),
541
  'amp-call-tracking' => array(
@@ -544,9 +635,11 @@ class AMP_Allowed_Tags_Generated {
544
  'config' => array(
545
  'blacklisted_value_regex' => '__amp_source_origin',
546
  'mandatory' => true,
547
- 'allow_relative' => false,
548
- 'allowed_protocol' => array(
549
- 'https',
 
 
550
  ),
551
  ),
552
  'media' => array(),
@@ -555,12 +648,11 @@ class AMP_Allowed_Tags_Generated {
555
  ),
556
  ),
557
  'tag_spec' => array(
558
- 'html_format' => array(
559
- 'amp',
560
  ),
561
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-call-tracking',
562
  ),
563
-
564
  ),
565
  ),
566
  'amp-carousel' => array(
@@ -580,6 +672,13 @@ class AMP_Allowed_Tags_Generated {
580
  'dots' => array(
581
  'value' => '',
582
  ),
 
 
 
 
 
 
 
583
  'loop' => array(
584
  'value' => '',
585
  ),
@@ -592,9 +691,27 @@ class AMP_Allowed_Tags_Generated {
592
  ),
593
  ),
594
  'tag_spec' => array(
 
 
 
595
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-carousel',
596
  ),
597
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  ),
599
  ),
600
  'amp-dailymotion' => array(
@@ -632,116 +749,375 @@ class AMP_Allowed_Tags_Generated {
632
  ),
633
  ),
634
  'tag_spec' => array(
635
- 'html_format' => array(
636
- 'amp',
637
  ),
638
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-dailymotion',
639
  ),
640
-
641
  ),
642
  ),
643
- 'amp-embed' => array(
644
  array(
645
  'attr_spec_list' => array(
646
- 'alt' => array(),
647
- 'json' => array(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
  'media' => array(),
 
 
 
 
 
649
  'noloading' => array(
650
  'value' => '',
651
  ),
 
 
 
 
 
 
 
 
 
652
  'src' => array(
653
  'blacklisted_value_regex' => '__amp_source_origin',
654
- 'allow_relative' => true,
655
- 'allowed_protocol' => array(
656
- 'https',
 
 
657
  ),
658
  ),
659
  'type' => array(
660
- 'mandatory' => true,
661
  ),
 
662
  ),
663
  'tag_spec' => array(
664
- 'disallowed_ancestor' => array(
665
- 'amp-app-banner',
666
  ),
667
- 'html_format' => array(
668
- 'amp',
669
- ),
670
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
671
  ),
672
-
673
  ),
674
  array(
675
  'attr_spec_list' => array(
676
- 'alt' => array(),
677
- 'data-multi-size' => array(
678
- 'mandatory' => true,
679
  'value' => '',
680
  ),
681
- 'json' => array(),
 
 
 
 
 
 
 
 
 
 
 
682
  'media' => array(),
 
 
 
 
 
 
683
  'noloading' => array(
684
  'value' => '',
685
  ),
 
 
 
 
 
 
 
 
 
686
  'src' => array(
687
  'blacklisted_value_regex' => '__amp_source_origin',
688
- 'allow_relative' => true,
689
- 'allowed_protocol' => array(
690
- 'https',
 
 
691
  ),
692
  ),
693
  'type' => array(
694
- 'mandatory' => true,
695
  ),
 
696
  ),
697
  'tag_spec' => array(
698
- 'disallowed_ancestor' => array(
699
- 'amp-app-banner',
700
- 'amp-carousel',
701
- 'amp-fx-flying-carpet',
702
- 'amp-lightbox',
703
- 'amp-sticky-ad',
704
- ),
705
- 'html_format' => array(
706
- 'amp',
707
- ),
708
- 'spec_name' => 'amp-embed with data-multi-size attribute',
709
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
710
- ),
711
-
712
- ),
713
- ),
714
- 'amp-experiment' => array(
715
- array(
716
- 'attr_spec_list' => array(),
717
- 'tag_spec' => array(
718
- 'html_format' => array(
719
- 'amp',
720
  ),
721
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-experiment',
722
- 'unique' => true,
723
  ),
724
-
725
  ),
726
- ),
727
- 'amp-facebook' => array(
728
  array(
729
  'attr_spec_list' => array(
730
- 'data-href' => array(
731
- 'mandatory' => true,
 
 
 
 
 
 
 
 
 
 
 
 
732
  ),
 
 
 
733
  'media' => array(),
 
 
 
 
 
734
  'noloading' => array(
735
  'value' => '',
736
  ),
737
- ),
738
- 'tag_spec' => array(
739
- 'html_format' => array(
740
- 'amp',
741
  ),
742
- ),
743
-
744
- ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  ),
746
  'amp-facebook-comments' => array(
747
  array(
@@ -755,11 +1131,10 @@ class AMP_Allowed_Tags_Generated {
755
  ),
756
  ),
757
  'tag_spec' => array(
758
- 'html_format' => array(
759
- 'amp',
760
  ),
761
  ),
762
-
763
  ),
764
  ),
765
  'amp-facebook-like' => array(
@@ -767,10 +1142,12 @@ class AMP_Allowed_Tags_Generated {
767
  'attr_spec_list' => array(
768
  'data-href' => array(
769
  'mandatory' => true,
770
- 'allow_relative' => false,
771
- 'allowed_protocol' => array(
772
- 'http',
773
- 'https',
 
 
774
  ),
775
  ),
776
  'media' => array(),
@@ -779,11 +1156,35 @@ class AMP_Allowed_Tags_Generated {
779
  ),
780
  ),
781
  'tag_spec' => array(
782
- 'html_format' => array(
783
- 'amp',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
  ),
785
  ),
786
-
787
  ),
788
  ),
789
  'amp-fit-text' => array(
@@ -796,8 +1197,11 @@ class AMP_Allowed_Tags_Generated {
796
  'value' => '',
797
  ),
798
  ),
799
- 'tag_spec' => array(),
800
-
 
 
 
801
  ),
802
  ),
803
  'amp-font' => array(
@@ -822,12 +1226,10 @@ class AMP_Allowed_Tags_Generated {
822
  ),
823
  ),
824
  'tag_spec' => array(
825
- 'html_format' => array(
826
- 'amp',
827
- 'amp4ads',
828
  ),
829
  ),
830
-
831
  ),
832
  ),
833
  'amp-fx-flying-carpet' => array(
@@ -842,11 +1244,10 @@ class AMP_Allowed_Tags_Generated {
842
  ),
843
  ),
844
  'tag_spec' => array(
845
- 'html_format' => array(
846
- 'amp',
847
  ),
848
  ),
849
-
850
  ),
851
  ),
852
  'amp-gfycat' => array(
@@ -864,12 +1265,11 @@ class AMP_Allowed_Tags_Generated {
864
  ),
865
  ),
866
  'tag_spec' => array(
867
- 'html_format' => array(
868
- 'amp',
869
  ),
870
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-gfycat',
871
  ),
872
-
873
  ),
874
  ),
875
  'amp-gist' => array(
@@ -884,31 +1284,11 @@ class AMP_Allowed_Tags_Generated {
884
  ),
885
  ),
886
  'tag_spec' => array(
887
- 'html_format' => array(
888
- 'amp',
889
  ),
890
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-gist',
891
  ),
892
-
893
- ),
894
- ),
895
- 'amp-gwd-animation' => array(
896
- array(
897
- 'attr_spec_list' => array(
898
- 'media' => array(),
899
- 'noloading' => array(
900
- 'value' => '',
901
- ),
902
- 'timeline-event-prefix' => array(
903
- 'value' => '',
904
- ),
905
- ),
906
- 'tag_spec' => array(
907
- 'html_format' => array(
908
- 'amp4ads',
909
- ),
910
- ),
911
-
912
  ),
913
  ),
914
  'amp-hulu' => array(
@@ -923,21 +1303,18 @@ class AMP_Allowed_Tags_Generated {
923
  ),
924
  ),
925
  'tag_spec' => array(
926
- 'html_format' => array(
927
- 'amp',
928
  ),
929
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-hulu',
930
  ),
931
-
932
  ),
933
  ),
934
  'amp-iframe' => array(
935
  array(
936
  'attr_spec_list' => array(
937
  '[src]' => array(),
938
- 'allow' => array(
939
- 'value_regex' => 'geolocation|fullscreen|payment|transparency',
940
- ),
941
  'allowfullscreen' => array(
942
  'value' => '',
943
  ),
@@ -964,37 +1341,45 @@ class AMP_Allowed_Tags_Generated {
964
  ),
965
  'src' => array(
966
  'blacklisted_value_regex' => '__amp_source_origin',
967
- 'allow_relative' => true,
968
- 'allowed_protocol' => array(
969
- 'data',
970
- 'https',
 
 
971
  ),
972
  ),
973
  'srcdoc' => array(),
974
  ),
975
  'tag_spec' => array(
976
- 'html_format' => array(
977
- 'amp',
978
  ),
979
  ),
980
-
981
  ),
982
  ),
983
  'amp-ima-video' => array(
984
  array(
985
  'attr_spec_list' => array(
 
 
 
986
  'data-src' => array(
987
  'blacklisted_value_regex' => '__amp_source_origin',
988
- 'allow_relative' => true,
989
- 'allowed_protocol' => array(
990
- 'https',
 
 
991
  ),
992
  ),
993
  'data-tag' => array(
994
  'mandatory' => true,
995
- 'allow_relative' => true,
996
- 'allowed_protocol' => array(
997
- 'https',
 
 
998
  ),
999
  ),
1000
  'media' => array(),
@@ -1003,12 +1388,11 @@ class AMP_Allowed_Tags_Generated {
1003
  ),
1004
  ),
1005
  'tag_spec' => array(
1006
- 'html_format' => array(
1007
- 'amp',
1008
  ),
1009
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ima-video',
1010
  ),
1011
-
1012
  ),
1013
  ),
1014
  'amp-image-lightbox' => array(
@@ -1021,11 +1405,10 @@ class AMP_Allowed_Tags_Generated {
1021
  ),
1022
  ),
1023
  'tag_spec' => array(
1024
- 'html_format' => array(
1025
- 'amp',
1026
  ),
1027
  ),
1028
-
1029
  ),
1030
  ),
1031
  'amp-img' => array(
@@ -1037,6 +1420,13 @@ class AMP_Allowed_Tags_Generated {
1037
  '[srcset]' => array(),
1038
  'alt' => array(),
1039
  'attribution' => array(),
 
 
 
 
 
 
 
1040
  'media' => array(),
1041
  'noloading' => array(
1042
  'value' => '',
@@ -1048,22 +1438,19 @@ class AMP_Allowed_Tags_Generated {
1048
  ),
1049
  'blacklisted_value_regex' => '__amp_source_origin',
1050
  'mandatory' => true,
1051
- 'allow_relative' => true,
1052
- 'allowed_protocol' => array(
1053
- 'data',
1054
- 'http',
1055
- 'https',
 
 
1056
  ),
1057
  ),
1058
  ),
1059
  'tag_spec' => array(
1060
- 'html_format' => array(
1061
- 'amp',
1062
- 'amp4ads',
1063
- ),
1064
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
1065
  ),
1066
-
1067
  ),
1068
  ),
1069
  'amp-imgur' => array(
@@ -1078,11 +1465,10 @@ class AMP_Allowed_Tags_Generated {
1078
  ),
1079
  ),
1080
  'tag_spec' => array(
1081
- 'html_format' => array(
1082
- 'amp',
1083
  ),
1084
  ),
1085
-
1086
  ),
1087
  ),
1088
  'amp-instagram' => array(
@@ -1098,11 +1484,10 @@ class AMP_Allowed_Tags_Generated {
1098
  ),
1099
  ),
1100
  'tag_spec' => array(
1101
- 'html_format' => array(
1102
- 'amp',
1103
  ),
1104
  ),
1105
-
1106
  ),
1107
  ),
1108
  'amp-install-serviceworker' => array(
@@ -1110,26 +1495,29 @@ class AMP_Allowed_Tags_Generated {
1110
  'attr_spec_list' => array(
1111
  'data-iframe-src' => array(
1112
  'blacklisted_value_regex' => '__amp_source_origin',
1113
- 'allow_relative' => true,
1114
- 'allowed_protocol' => array(
1115
- 'https',
 
 
1116
  ),
1117
  ),
1118
  'src' => array(
1119
  'blacklisted_value_regex' => '__amp_source_origin',
1120
  'mandatory' => true,
1121
- 'allow_relative' => true,
1122
- 'allowed_protocol' => array(
1123
- 'https',
 
 
1124
  ),
1125
  ),
1126
  ),
1127
  'tag_spec' => array(
1128
- 'html_format' => array(
1129
- 'amp',
1130
  ),
1131
  ),
1132
-
1133
  ),
1134
  ),
1135
  'amp-izlesene' => array(
@@ -1145,11 +1533,10 @@ class AMP_Allowed_Tags_Generated {
1145
  ),
1146
  ),
1147
  'tag_spec' => array(
1148
- 'html_format' => array(
1149
- 'amp',
1150
  ),
1151
  ),
1152
-
1153
  ),
1154
  ),
1155
  'amp-jwplayer' => array(
@@ -1167,11 +1554,10 @@ class AMP_Allowed_Tags_Generated {
1167
  ),
1168
  ),
1169
  'tag_spec' => array(
1170
- 'html_format' => array(
1171
- 'amp',
1172
  ),
1173
  ),
1174
-
1175
  ),
1176
  ),
1177
  'amp-kaltura-player' => array(
@@ -1186,11 +1572,10 @@ class AMP_Allowed_Tags_Generated {
1186
  ),
1187
  ),
1188
  'tag_spec' => array(
1189
- 'html_format' => array(
1190
- 'amp',
1191
  ),
1192
  ),
1193
-
1194
  ),
1195
  ),
1196
  'amp-layout' => array(
@@ -1202,13 +1587,8 @@ class AMP_Allowed_Tags_Generated {
1202
  ),
1203
  ),
1204
  'tag_spec' => array(
1205
- 'html_format' => array(
1206
- 'amp',
1207
- 'amp4ads',
1208
- ),
1209
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-layout',
1210
  ),
1211
-
1212
  ),
1213
  ),
1214
  'amp-lightbox' => array(
@@ -1223,11 +1603,10 @@ class AMP_Allowed_Tags_Generated {
1223
  'scrollable' => array(),
1224
  ),
1225
  'tag_spec' => array(
1226
- 'html_format' => array(
1227
- 'amp',
1228
  ),
1229
  ),
1230
-
1231
  ),
1232
  ),
1233
  'amp-list' => array(
@@ -1242,23 +1621,58 @@ class AMP_Allowed_Tags_Generated {
1242
  'noloading' => array(
1243
  'value' => '',
1244
  ),
 
 
 
1245
  'single-item' => array(),
1246
  'src' => array(
1247
  'blacklisted_value_regex' => '__amp_source_origin',
1248
  'mandatory' => true,
1249
- 'allow_relative' => true,
1250
- 'allowed_protocol' => array(
1251
- 'https',
 
 
1252
  ),
1253
  ),
1254
  'template' => array(),
1255
  ),
1256
  'tag_spec' => array(
1257
- 'html_format' => array(
1258
- 'amp',
1259
  ),
1260
  ),
1261
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1262
  ),
1263
  ),
1264
  'amp-live-list' => array(
@@ -1277,22 +1691,43 @@ class AMP_Allowed_Tags_Generated {
1277
  'id' => array(
1278
  'mandatory' => true,
1279
  ),
 
 
 
1280
  ),
1281
  'tag_spec' => array(
1282
- 'html_format' => array(
1283
- 'amp',
1284
  ),
1285
  ),
1286
-
1287
  ),
1288
  ),
1289
- 'amp-nexxtv-player' => array(
1290
  array(
1291
  'attr_spec_list' => array(
1292
- 'data-client' => array(
1293
  'mandatory' => true,
1294
  ),
1295
- 'data-mediaid' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1296
  'mandatory' => true,
1297
  'value_regex' => '[^=/?:]+',
1298
  ),
@@ -1300,13 +1735,14 @@ class AMP_Allowed_Tags_Generated {
1300
  'value_regex' => 'api|static',
1301
  ),
1302
  'data-origin' => array(
1303
- 'allow_empty' => true,
1304
- 'allowed_protocol' => array(
1305
- 'http',
1306
- 'https',
 
 
1307
  ),
1308
  ),
1309
- 'data-seek-to' => array(),
1310
  'data-streamtype' => array(
1311
  'value_regex' => 'album|audio|live|playlist|playlist-marked|video',
1312
  ),
@@ -1316,11 +1752,10 @@ class AMP_Allowed_Tags_Generated {
1316
  ),
1317
  ),
1318
  'tag_spec' => array(
1319
- 'html_format' => array(
1320
- 'amp',
1321
  ),
1322
  ),
1323
-
1324
  ),
1325
  ),
1326
  'amp-o2-player' => array(
@@ -1338,11 +1773,10 @@ class AMP_Allowed_Tags_Generated {
1338
  ),
1339
  ),
1340
  'tag_spec' => array(
1341
- 'html_format' => array(
1342
- 'amp',
1343
  ),
1344
  ),
1345
-
1346
  ),
1347
  ),
1348
  'amp-ooyala-player' => array(
@@ -1363,16 +1797,16 @@ class AMP_Allowed_Tags_Generated {
1363
  ),
1364
  ),
1365
  'tag_spec' => array(
1366
- 'html_format' => array(
1367
- 'amp',
1368
  ),
1369
  ),
1370
-
1371
  ),
1372
  ),
1373
  'amp-pinterest' => array(
1374
  array(
1375
  'attr_spec_list' => array(
 
1376
  'data-do' => array(
1377
  'mandatory' => true,
1378
  ),
@@ -1382,17 +1816,17 @@ class AMP_Allowed_Tags_Generated {
1382
  ),
1383
  ),
1384
  'tag_spec' => array(
1385
- 'html_format' => array(
1386
- 'amp',
1387
  ),
1388
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-pinterest',
1389
  ),
1390
-
1391
  ),
1392
  ),
1393
  'amp-pixel' => array(
1394
  array(
1395
  'attr_spec_list' => array(
 
1396
  'media' => array(),
1397
  'noloading' => array(
1398
  'value' => '',
@@ -1403,21 +1837,18 @@ class AMP_Allowed_Tags_Generated {
1403
  'src' => array(
1404
  'blacklisted_value_regex' => '__amp_source_origin',
1405
  'mandatory' => true,
1406
- 'allow_empty' => true,
1407
- 'allow_relative' => true,
1408
- 'allowed_protocol' => array(
1409
- 'https',
 
 
1410
  ),
1411
  ),
1412
  ),
1413
  'tag_spec' => array(
1414
- 'html_format' => array(
1415
- 'amp',
1416
- 'amp4ads',
1417
- ),
1418
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-pixel',
1419
  ),
1420
-
1421
  ),
1422
  ),
1423
  'amp-playbuzz' => array(
@@ -1440,11 +1871,10 @@ class AMP_Allowed_Tags_Generated {
1440
  'src' => array(),
1441
  ),
1442
  'tag_spec' => array(
1443
- 'html_format' => array(
1444
- 'amp',
1445
  ),
1446
  ),
1447
-
1448
  ),
1449
  ),
1450
  'amp-position-observer' => array(
@@ -1463,12 +1893,10 @@ class AMP_Allowed_Tags_Generated {
1463
  ),
1464
  ),
1465
  'tag_spec' => array(
1466
- 'html_format' => array(
1467
- 'amp',
1468
- 'amp4ads',
1469
  ),
1470
  ),
1471
-
1472
  ),
1473
  ),
1474
  'amp-reach-player' => array(
@@ -1484,11 +1912,10 @@ class AMP_Allowed_Tags_Generated {
1484
  ),
1485
  ),
1486
  'tag_spec' => array(
1487
- 'html_format' => array(
1488
- 'amp',
1489
  ),
1490
  ),
1491
-
1492
  ),
1493
  ),
1494
  'amp-reddit' => array(
@@ -1513,16 +1940,36 @@ class AMP_Allowed_Tags_Generated {
1513
  ),
1514
  ),
1515
  'tag_spec' => array(
1516
- 'html_format' => array(
1517
- 'amp',
1518
  ),
1519
  ),
1520
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1521
  ),
1522
  ),
1523
  'amp-selector' => array(
1524
  array(
1525
  'attr_spec_list' => array(
 
1526
  '[selected]' => array(),
1527
  'disabled' => array(
1528
  'value' => '',
@@ -1536,7 +1983,7 @@ class AMP_Allowed_Tags_Generated {
1536
  'value' => '',
1537
  ),
1538
  'name' => array(
1539
- '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|$)',
1540
  ),
1541
  'noloading' => array(
1542
  'value' => '',
@@ -1544,13 +1991,12 @@ class AMP_Allowed_Tags_Generated {
1544
  ),
1545
  'tag_spec' => array(
1546
  'disallowed_ancestor' => array(
1547
- 'amp-selector',
1548
  ),
1549
- 'html_format' => array(
1550
- 'amp',
1551
  ),
1552
  ),
1553
-
1554
  ),
1555
  ),
1556
  'amp-sidebar' => array(
@@ -1565,12 +2011,11 @@ class AMP_Allowed_Tags_Generated {
1565
  ),
1566
  ),
1567
  'tag_spec' => array(
1568
- 'html_format' => array(
1569
- 'amp',
1570
- ),
1571
  'mandatory_parent' => 'body',
 
 
 
1572
  ),
1573
-
1574
  ),
1575
  ),
1576
  'amp-social-share' => array(
@@ -1578,24 +2023,26 @@ class AMP_Allowed_Tags_Generated {
1578
  'attr_spec_list' => array(
1579
  'data-share-endpoint' => array(
1580
  'blacklisted_value_regex' => '__amp_source_origin',
1581
- 'allow_relative' => false,
1582
- 'allowed_protocol' => array(
1583
- 'bbmi',
1584
- 'fb-messenger',
1585
- 'ftp',
1586
- 'http',
1587
- 'https',
1588
- 'intent',
1589
- 'line',
1590
- 'mailto',
1591
- 'skype',
1592
- 'sms',
1593
- 'snapchat',
1594
- 'tel',
1595
- 'tg',
1596
- 'threema',
1597
- 'viber',
1598
- 'whatsapp',
 
 
1599
  ),
1600
  ),
1601
  'media' => array(),
@@ -1607,12 +2054,10 @@ class AMP_Allowed_Tags_Generated {
1607
  ),
1608
  ),
1609
  'tag_spec' => array(
1610
- 'html_format' => array(
1611
- 'amp',
1612
- 'amp4ads',
1613
  ),
1614
  ),
1615
-
1616
  ),
1617
  ),
1618
  'amp-soundcloud' => array(
@@ -1625,7 +2070,7 @@ class AMP_Allowed_Tags_Generated {
1625
  'value_regex' => '[0-9]+',
1626
  ),
1627
  'data-secret-token' => array(
1628
- 'value_regex' => '[a-za-z0-9_-]+',
1629
  ),
1630
  'data-trackid' => array(
1631
  'value_regex' => '[0-9]+',
@@ -1639,11 +2084,10 @@ class AMP_Allowed_Tags_Generated {
1639
  ),
1640
  ),
1641
  'tag_spec' => array(
1642
- 'html_format' => array(
1643
- 'amp',
1644
  ),
1645
  ),
1646
-
1647
  ),
1648
  ),
1649
  'amp-springboard-player' => array(
@@ -1676,11 +2120,10 @@ class AMP_Allowed_Tags_Generated {
1676
  ),
1677
  ),
1678
  'tag_spec' => array(
1679
- 'html_format' => array(
1680
- 'amp',
1681
  ),
1682
  ),
1683
-
1684
  ),
1685
  ),
1686
  'amp-state' => array(
@@ -1691,22 +2134,24 @@ class AMP_Allowed_Tags_Generated {
1691
  'id' => array(
1692
  'mandatory' => true,
1693
  ),
 
1694
  'src' => array(
1695
  'blacklisted_value_regex' => '__amp_source_origin',
1696
- 'allow_relative' => true,
1697
- 'allowed_protocol' => array(
1698
- 'https',
 
 
1699
  ),
1700
  ),
1701
  ),
1702
  'tag_spec' => array(
1703
- 'html_format' => array(
1704
- 'amp',
1705
  ),
1706
  'spec_name' => 'amp-state',
1707
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-bind',
1708
  ),
1709
-
1710
  ),
1711
  ),
1712
  'amp-sticky-ad' => array(
@@ -1719,29 +2164,32 @@ class AMP_Allowed_Tags_Generated {
1719
  ),
1720
  'tag_spec' => array(
1721
  'disallowed_ancestor' => array(
1722
- 'amp-app-banner',
1723
  ),
1724
- 'html_format' => array(
1725
- 'amp',
1726
  ),
1727
  'unique' => true,
1728
  ),
1729
-
1730
  ),
1731
  ),
1732
  'amp-story' => array(
1733
  array(
1734
  'attr_spec_list' => array(
1735
  'background-audio' => array(
1736
- 'allowed_protocol' => array(
1737
- 'http',
1738
- 'https',
 
 
1739
  ),
1740
  ),
1741
  'bookend-config-src' => array(
1742
- 'allowed_protocol' => array(
1743
- 'http',
1744
- 'https',
 
 
1745
  ),
1746
  ),
1747
  'standalone' => array(
@@ -1750,12 +2198,32 @@ class AMP_Allowed_Tags_Generated {
1750
  ),
1751
  ),
1752
  'tag_spec' => array(
1753
- 'html_format' => array(
1754
- 'amp',
1755
- ),
1756
  'mandatory_parent' => 'body',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1757
  ),
1758
-
1759
  ),
1760
  ),
1761
  'amp-story-grid-layer' => array(
@@ -1767,12 +2235,8 @@ class AMP_Allowed_Tags_Generated {
1767
  ),
1768
  ),
1769
  'tag_spec' => array(
1770
- 'html_format' => array(
1771
- 'amp',
1772
- ),
1773
  'mandatory_ancestor' => 'amp-story-page',
1774
  ),
1775
-
1776
  ),
1777
  ),
1778
  'amp-story-page' => array(
@@ -1780,9 +2244,11 @@ class AMP_Allowed_Tags_Generated {
1780
  'attr_spec_list' => array(
1781
  'auto-advance-after' => array(),
1782
  'background-audio' => array(
1783
- 'allowed_protocol' => array(
1784
- 'http',
1785
- 'https',
 
 
1786
  ),
1787
  ),
1788
  'id' => array(
@@ -1790,12 +2256,11 @@ class AMP_Allowed_Tags_Generated {
1790
  ),
1791
  ),
1792
  'tag_spec' => array(
1793
- 'html_format' => array(
1794
- 'amp',
1795
- ),
1796
  'mandatory_parent' => 'amp-story',
 
 
 
1797
  ),
1798
-
1799
  ),
1800
  ),
1801
  'amp-timeago' => array(
@@ -1806,7 +2271,7 @@ class AMP_Allowed_Tags_Generated {
1806
  ),
1807
  'datetime' => array(
1808
  'mandatory' => true,
1809
- '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])',
1810
  ),
1811
  'locale' => array(),
1812
  'media' => array(),
@@ -1815,12 +2280,11 @@ class AMP_Allowed_Tags_Generated {
1815
  ),
1816
  ),
1817
  'tag_spec' => array(
1818
- 'html_format' => array(
1819
- 'amp',
1820
  ),
1821
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-timeago',
1822
  ),
1823
-
1824
  ),
1825
  ),
1826
  'amp-twitter' => array(
@@ -1835,28 +2299,31 @@ class AMP_Allowed_Tags_Generated {
1835
  ),
1836
  ),
1837
  'tag_spec' => array(
1838
- 'html_format' => array(
1839
- 'amp',
1840
  ),
1841
  ),
1842
-
1843
  ),
1844
  ),
1845
  'amp-user-notification' => array(
1846
  array(
1847
  'attr_spec_list' => array(
1848
  'data-dismiss-href' => array(
1849
- 'allow_empty' => false,
1850
- 'allow_relative' => false,
1851
- 'allowed_protocol' => array(
1852
- 'https',
 
 
1853
  ),
1854
  ),
1855
  'data-show-if-href' => array(
1856
- 'allow_empty' => false,
1857
- 'allow_relative' => false,
1858
- 'allowed_protocol' => array(
1859
- 'https',
 
 
1860
  ),
1861
  ),
1862
  'enctype' => array(
@@ -1868,11 +2335,10 @@ class AMP_Allowed_Tags_Generated {
1868
  ),
1869
  ),
1870
  'tag_spec' => array(
1871
- 'html_format' => array(
1872
- 'amp',
1873
  ),
1874
  ),
1875
-
1876
  ),
1877
  ),
1878
  'amp-video' => array(
@@ -1906,6 +2372,13 @@ class AMP_Allowed_Tags_Generated {
1906
  'disableremoteplayback' => array(
1907
  'value' => '',
1908
  ),
 
 
 
 
 
 
 
1909
  'loop' => array(
1910
  'value' => '',
1911
  ),
@@ -1923,23 +2396,23 @@ class AMP_Allowed_Tags_Generated {
1923
  ),
1924
  'src' => array(
1925
  'blacklisted_value_regex' => '__amp_source_origin',
1926
- 'allow_relative' => true,
1927
- 'allowed_protocol' => array(
1928
- 'https',
 
 
1929
  ),
1930
  ),
1931
  ),
1932
  'tag_spec' => array(
1933
- 'disallowed_ancestor' => array(
1934
- 'amp-story',
1935
  ),
1936
- 'html_format' => array(
1937
- 'amp',
1938
- 'amp4ads',
1939
  ),
1940
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
1941
  ),
1942
-
1943
  ),
1944
  array(
1945
  'attr_spec_list' => array(
@@ -1990,22 +2463,22 @@ class AMP_Allowed_Tags_Generated {
1990
  ),
1991
  'src' => array(
1992
  'blacklisted_value_regex' => '__amp_source_origin',
1993
- 'allow_relative' => true,
1994
- 'allowed_protocol' => array(
1995
- 'https',
 
 
1996
  ),
1997
  ),
1998
  ),
1999
  'tag_spec' => array(
2000
- 'html_format' => array(
2001
- 'amp',
2002
- 'amp4ads',
2003
- ),
2004
  'mandatory_ancestor' => 'amp-story',
 
 
 
2005
  'spec_name' => 'amp-story >> amp-video',
2006
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
2007
  ),
2008
-
2009
  ),
2010
  ),
2011
  'amp-vimeo' => array(
@@ -2021,11 +2494,10 @@ class AMP_Allowed_Tags_Generated {
2021
  ),
2022
  ),
2023
  'tag_spec' => array(
2024
- 'html_format' => array(
2025
- 'amp',
2026
  ),
2027
  ),
2028
-
2029
  ),
2030
  ),
2031
  'amp-vine' => array(
@@ -2040,11 +2512,10 @@ class AMP_Allowed_Tags_Generated {
2040
  ),
2041
  ),
2042
  'tag_spec' => array(
2043
- 'html_format' => array(
2044
- 'amp',
2045
  ),
2046
  ),
2047
-
2048
  ),
2049
  ),
2050
  'amp-vk' => array(
@@ -2059,11 +2530,10 @@ class AMP_Allowed_Tags_Generated {
2059
  ),
2060
  ),
2061
  'tag_spec' => array(
2062
- 'html_format' => array(
2063
- 'amp',
2064
  ),
2065
  ),
2066
-
2067
  ),
2068
  ),
2069
  'amp-web-push' => array(
@@ -2071,9 +2541,11 @@ class AMP_Allowed_Tags_Generated {
2071
  'attr_spec_list' => array(
2072
  'helper-iframe-url' => array(
2073
  'mandatory' => true,
2074
- 'allow_relative' => false,
2075
- 'allowed_protocol' => array(
2076
- 'https',
 
 
2077
  ),
2078
  ),
2079
  'id' => array(
@@ -2086,26 +2558,29 @@ class AMP_Allowed_Tags_Generated {
2086
  ),
2087
  'permission-dialog-url' => array(
2088
  'mandatory' => true,
2089
- 'allow_relative' => false,
2090
- 'allowed_protocol' => array(
2091
- 'https',
 
 
2092
  ),
2093
  ),
2094
  'service-worker-url' => array(
2095
  'mandatory' => true,
2096
- 'allow_relative' => false,
2097
- 'allowed_protocol' => array(
2098
- 'https',
 
 
2099
  ),
2100
  ),
2101
  ),
2102
  'tag_spec' => array(
2103
- 'html_format' => array(
2104
- 'amp',
2105
  ),
2106
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-web-push',
2107
  ),
2108
-
2109
  ),
2110
  ),
2111
  'amp-web-push-widget' => array(
@@ -2121,12 +2596,30 @@ class AMP_Allowed_Tags_Generated {
2121
  ),
2122
  ),
2123
  'tag_spec' => array(
2124
- 'html_format' => array(
2125
- 'amp',
2126
  ),
2127
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-web-push',
2128
  ),
2129
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2130
  ),
2131
  ),
2132
  'amp-youtube' => array(
@@ -2137,31 +2630,41 @@ class AMP_Allowed_Tags_Generated {
2137
  'credentials' => array(
2138
  'value_regex_casei' => '(include|omit)',
2139
  ),
 
 
 
2140
  'data-videoid' => array(
2141
- 'mandatory' => true,
2142
  'value_regex' => '[^=/?:]+',
2143
  ),
 
 
 
 
 
 
 
2144
  'media' => array(),
2145
  'noloading' => array(
2146
  'value' => '',
2147
  ),
2148
  ),
2149
- 'tag_spec' => array(),
2150
-
 
 
 
2151
  ),
2152
  ),
2153
  'article' => array(
2154
  array(
2155
  'attr_spec_list' => array(),
2156
  'tag_spec' => array(),
2157
-
2158
  ),
2159
  ),
2160
  'aside' => array(
2161
  array(
2162
  'attr_spec_list' => array(),
2163
  'tag_spec' => array(),
2164
-
2165
  ),
2166
  ),
2167
  'audio' => array(
@@ -2174,36 +2677,48 @@ class AMP_Allowed_Tags_Generated {
2174
  'preload' => array(),
2175
  'src' => array(
2176
  'blacklisted_value_regex' => '__amp_source_origin',
2177
- 'allow_relative' => false,
2178
- 'allowed_protocol' => array(
2179
- 'data',
2180
- 'https',
 
 
2181
  ),
2182
  ),
2183
  ),
2184
  'tag_spec' => array(
2185
- 'html_format' => array(
2186
- 'amp',
2187
- ),
2188
  'mandatory_ancestor' => 'noscript',
2189
  'mandatory_ancestor_suggested_alternative' => 'amp-audio',
2190
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
2191
  ),
2192
-
2193
  ),
2194
  ),
2195
  'b' => array(
2196
  array(
2197
  'attr_spec_list' => array(),
2198
  'tag_spec' => array(),
2199
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2200
  ),
2201
  ),
2202
  'bdi' => array(
2203
  array(
2204
  'attr_spec_list' => array(),
2205
  'tag_spec' => array(),
2206
-
2207
  ),
2208
  ),
2209
  'bdo' => array(
@@ -2212,18 +2727,12 @@ class AMP_Allowed_Tags_Generated {
2212
  'dir' => array(),
2213
  ),
2214
  'tag_spec' => array(),
2215
-
2216
  ),
2217
  ),
2218
  'big' => array(
2219
  array(
2220
  'attr_spec_list' => array(),
2221
- 'tag_spec' => array(
2222
- 'html_format' => array(
2223
- 'amp',
2224
- ),
2225
- ),
2226
-
2227
  ),
2228
  ),
2229
  'blockquote' => array(
@@ -2232,16 +2741,17 @@ class AMP_Allowed_Tags_Generated {
2232
  'align' => array(),
2233
  'cite' => array(
2234
  'blacklisted_value_regex' => '__amp_source_origin',
2235
- 'allow_empty' => true,
2236
- 'allow_relative' => true,
2237
- 'allowed_protocol' => array(
2238
- 'http',
2239
- 'https',
 
 
2240
  ),
2241
  ),
2242
  ),
2243
  'tag_spec' => array(),
2244
-
2245
  ),
2246
  ),
2247
  'body' => array(
@@ -2253,14 +2763,12 @@ class AMP_Allowed_Tags_Generated {
2253
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
2254
  'unique' => true,
2255
  ),
2256
-
2257
  ),
2258
  ),
2259
  'br' => array(
2260
  array(
2261
  'attr_spec_list' => array(),
2262
  'tag_spec' => array(),
2263
-
2264
  ),
2265
  ),
2266
  'button' => array(
@@ -2273,7 +2781,7 @@ class AMP_Allowed_Tags_Generated {
2273
  'value' => '',
2274
  ),
2275
  'name' => array(
2276
- '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|$)',
2277
  ),
2278
  'role' => array(),
2279
  'tabindex' => array(),
@@ -2281,12 +2789,11 @@ class AMP_Allowed_Tags_Generated {
2281
  'value' => array(),
2282
  ),
2283
  'tag_spec' => array(),
2284
-
2285
  ),
2286
  array(
2287
  'attr_spec_list' => array(
2288
  'name' => array(
2289
- '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|$)',
2290
  ),
2291
  'open-button' => array(
2292
  'value' => '',
@@ -2297,32 +2804,21 @@ class AMP_Allowed_Tags_Generated {
2297
  'value' => array(),
2298
  ),
2299
  'tag_spec' => array(
2300
- 'html_format' => array(
2301
- 'amp',
2302
- 'amp4ads',
2303
- ),
2304
  'mandatory_ancestor' => 'amp-app-banner',
2305
  'spec_name' => 'amp-app-banner button[open-button]',
2306
  ),
2307
-
2308
  ),
2309
  ),
2310
  'caption' => array(
2311
  array(
2312
  'attr_spec_list' => array(),
2313
  'tag_spec' => array(),
2314
-
2315
  ),
2316
  ),
2317
  'center' => array(
2318
  array(
2319
  'attr_spec_list' => array(),
2320
- 'tag_spec' => array(
2321
- 'html_format' => array(
2322
- 'amp',
2323
- ),
2324
- ),
2325
-
2326
  ),
2327
  ),
2328
  'circle' => array(
@@ -2406,21 +2902,15 @@ class AMP_Allowed_Tags_Generated {
2406
  'xmlns:xlink' => array(),
2407
  ),
2408
  'tag_spec' => array(
2409
- 'html_format' => array(
2410
- 'amp',
2411
- 'amp4ads',
2412
- ),
2413
  'mandatory_ancestor' => 'svg',
2414
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2415
  ),
2416
-
2417
  ),
2418
  ),
2419
  'cite' => array(
2420
  array(
2421
  'attr_spec_list' => array(),
2422
  'tag_spec' => array(),
2423
-
2424
  ),
2425
  ),
2426
  'clippath' => array(
@@ -2501,21 +2991,15 @@ class AMP_Allowed_Tags_Generated {
2501
  'xmlns:xlink' => array(),
2502
  ),
2503
  'tag_spec' => array(
2504
- 'html_format' => array(
2505
- 'amp',
2506
- 'amp4ads',
2507
- ),
2508
  'mandatory_ancestor' => 'svg',
2509
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2510
  ),
2511
-
2512
  ),
2513
  ),
2514
  'code' => array(
2515
  array(
2516
  'attr_spec_list' => array(),
2517
  'tag_spec' => array(),
2518
-
2519
  ),
2520
  ),
2521
  'col' => array(
@@ -2524,7 +3008,6 @@ class AMP_Allowed_Tags_Generated {
2524
  'span' => array(),
2525
  ),
2526
  'tag_spec' => array(),
2527
-
2528
  ),
2529
  ),
2530
  'colgroup' => array(
@@ -2533,14 +3016,12 @@ class AMP_Allowed_Tags_Generated {
2533
  'span' => array(),
2534
  ),
2535
  'tag_spec' => array(),
2536
-
2537
  ),
2538
  ),
2539
  'data' => array(
2540
  array(
2541
  'attr_spec_list' => array(),
2542
  'tag_spec' => array(),
2543
-
2544
  ),
2545
  ),
2546
  'datalist' => array(
@@ -2549,14 +3030,12 @@ class AMP_Allowed_Tags_Generated {
2549
  'tag_spec' => array(
2550
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
2551
  ),
2552
-
2553
  ),
2554
  ),
2555
  'dd' => array(
2556
  array(
2557
  'attr_spec_list' => array(),
2558
  'tag_spec' => array(),
2559
-
2560
  ),
2561
  ),
2562
  'defs' => array(
@@ -2636,14 +3115,9 @@ class AMP_Allowed_Tags_Generated {
2636
  'xmlns:xlink' => array(),
2637
  ),
2638
  'tag_spec' => array(
2639
- 'html_format' => array(
2640
- 'amp',
2641
- 'amp4ads',
2642
- ),
2643
  'mandatory_ancestor' => 'svg',
2644
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2645
  ),
2646
-
2647
  ),
2648
  ),
2649
  'del' => array(
@@ -2651,17 +3125,18 @@ class AMP_Allowed_Tags_Generated {
2651
  'attr_spec_list' => array(
2652
  'cite' => array(
2653
  'blacklisted_value_regex' => '__amp_source_origin',
2654
- 'allow_empty' => true,
2655
- 'allow_relative' => true,
2656
- 'allowed_protocol' => array(
2657
- 'http',
2658
- 'https',
 
 
2659
  ),
2660
  ),
2661
  'datetime' => array(),
2662
  ),
2663
  'tag_spec' => array(),
2664
-
2665
  ),
2666
  ),
2667
  'desc' => array(
@@ -2676,32 +3151,21 @@ class AMP_Allowed_Tags_Generated {
2676
  'xmlns:xlink' => array(),
2677
  ),
2678
  'tag_spec' => array(
2679
- 'html_format' => array(
2680
- 'amp',
2681
- 'amp4ads',
2682
- ),
2683
  'mandatory_ancestor' => 'svg',
2684
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2685
  ),
2686
-
2687
  ),
2688
  ),
2689
  'dfn' => array(
2690
  array(
2691
  'attr_spec_list' => array(),
2692
  'tag_spec' => array(),
2693
-
2694
  ),
2695
  ),
2696
  'dir' => array(
2697
  array(
2698
  'attr_spec_list' => array(),
2699
- 'tag_spec' => array(
2700
- 'html_format' => array(
2701
- 'amp',
2702
- ),
2703
- ),
2704
-
2705
  ),
2706
  ),
2707
  'div' => array(
@@ -2710,20 +3174,19 @@ class AMP_Allowed_Tags_Generated {
2710
  'align' => array(),
2711
  ),
2712
  'tag_spec' => array(),
2713
-
2714
  ),
2715
  array(
2716
  'attr_spec_list' => array(
2717
  'align' => array(),
2718
  'submitting' => array(
 
2719
  'mandatory' => true,
2720
  ),
2721
  ),
2722
  'tag_spec' => array(
2723
  'mandatory_parent' => 'form',
2724
- 'spec_name' => 'form > div [submitting]',
2725
  ),
2726
-
2727
  ),
2728
  array(
2729
  'attr_spec_list' => array(
@@ -2734,9 +3197,23 @@ class AMP_Allowed_Tags_Generated {
2734
  ),
2735
  'tag_spec' => array(
2736
  'mandatory_parent' => 'form',
2737
- 'spec_name' => 'form > div [submit-success]',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2738
  ),
2739
-
2740
  ),
2741
  array(
2742
  'attr_spec_list' => array(
@@ -2747,23 +3224,35 @@ class AMP_Allowed_Tags_Generated {
2747
  ),
2748
  'tag_spec' => array(
2749
  'mandatory_parent' => 'form',
2750
- 'spec_name' => 'form > div [submit-error]',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2751
  ),
2752
-
2753
  ),
2754
  ),
2755
  'dl' => array(
2756
  array(
2757
  'attr_spec_list' => array(),
2758
  'tag_spec' => array(),
2759
-
2760
  ),
2761
  ),
2762
  'dt' => array(
2763
  array(
2764
  'attr_spec_list' => array(),
2765
  'tag_spec' => array(),
2766
-
2767
  ),
2768
  ),
2769
  'ellipse' => array(
@@ -2848,21 +3337,15 @@ class AMP_Allowed_Tags_Generated {
2848
  'xmlns:xlink' => array(),
2849
  ),
2850
  'tag_spec' => array(
2851
- 'html_format' => array(
2852
- 'amp',
2853
- 'amp4ads',
2854
- ),
2855
  'mandatory_ancestor' => 'svg',
2856
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2857
  ),
2858
-
2859
  ),
2860
  ),
2861
  'em' => array(
2862
  array(
2863
  'attr_spec_list' => array(),
2864
  'tag_spec' => array(),
2865
-
2866
  ),
2867
  ),
2868
  'fecolormatrix' => array(
@@ -2945,14 +3428,9 @@ class AMP_Allowed_Tags_Generated {
2945
  'y' => array(),
2946
  ),
2947
  'tag_spec' => array(
2948
- 'html_format' => array(
2949
- 'amp',
2950
- 'amp4ads',
2951
- ),
2952
  'mandatory_ancestor' => 'svg',
2953
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2954
  ),
2955
-
2956
  ),
2957
  ),
2958
  'fecomposite' => array(
@@ -3039,14 +3517,9 @@ class AMP_Allowed_Tags_Generated {
3039
  'y' => array(),
3040
  ),
3041
  'tag_spec' => array(
3042
- 'html_format' => array(
3043
- 'amp',
3044
- 'amp4ads',
3045
- ),
3046
  'mandatory_ancestor' => 'svg',
3047
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3048
  ),
3049
-
3050
  ),
3051
  ),
3052
  'feflood' => array(
@@ -3126,14 +3599,9 @@ class AMP_Allowed_Tags_Generated {
3126
  'y' => array(),
3127
  ),
3128
  'tag_spec' => array(
3129
- 'html_format' => array(
3130
- 'amp',
3131
- 'amp4ads',
3132
- ),
3133
  'mandatory_ancestor' => 'svg',
3134
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3135
  ),
3136
-
3137
  ),
3138
  ),
3139
  'fegaussianblur' => array(
@@ -3216,14 +3684,9 @@ class AMP_Allowed_Tags_Generated {
3216
  'y' => array(),
3217
  ),
3218
  'tag_spec' => array(
3219
- 'html_format' => array(
3220
- 'amp',
3221
- 'amp4ads',
3222
- ),
3223
  'mandatory_ancestor' => 'svg',
3224
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3225
  ),
3226
-
3227
  ),
3228
  ),
3229
  'femerge' => array(
@@ -3303,14 +3766,9 @@ class AMP_Allowed_Tags_Generated {
3303
  'y' => array(),
3304
  ),
3305
  'tag_spec' => array(
3306
- 'html_format' => array(
3307
- 'amp',
3308
- 'amp4ads',
3309
- ),
3310
  'mandatory_ancestor' => 'svg',
3311
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3312
  ),
3313
-
3314
  ),
3315
  ),
3316
  'femergenode' => array(
@@ -3326,14 +3784,9 @@ class AMP_Allowed_Tags_Generated {
3326
  'xmlns:xlink' => array(),
3327
  ),
3328
  'tag_spec' => array(
3329
- 'html_format' => array(
3330
- 'amp',
3331
- 'amp4ads',
3332
- ),
3333
  'mandatory_ancestor' => 'svg',
3334
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3335
  ),
3336
-
3337
  ),
3338
  ),
3339
  'feoffset' => array(
@@ -3416,14 +3869,9 @@ class AMP_Allowed_Tags_Generated {
3416
  'y' => array(),
3417
  ),
3418
  'tag_spec' => array(
3419
- 'html_format' => array(
3420
- 'amp',
3421
- 'amp4ads',
3422
- ),
3423
  'mandatory_ancestor' => 'svg',
3424
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3425
  ),
3426
-
3427
  ),
3428
  ),
3429
  'fieldset' => array(
@@ -3432,25 +3880,22 @@ class AMP_Allowed_Tags_Generated {
3432
  '[disabled]' => array(),
3433
  'disabled' => array(),
3434
  'name' => array(
3435
- '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|$)',
3436
  ),
3437
  ),
3438
  'tag_spec' => array(),
3439
-
3440
  ),
3441
  ),
3442
  'figcaption' => array(
3443
  array(
3444
  'attr_spec_list' => array(),
3445
  'tag_spec' => array(),
3446
-
3447
  ),
3448
  ),
3449
  'figure' => array(
3450
  array(
3451
  'attr_spec_list' => array(),
3452
  'tag_spec' => array(),
3453
-
3454
  ),
3455
  ),
3456
  'filter' => array(
@@ -3532,11 +3977,13 @@ class AMP_Allowed_Tags_Generated {
3532
  'alternative_names' => array(
3533
  'href',
3534
  ),
3535
- 'allow_empty' => false,
3536
- 'allow_relative' => true,
3537
- 'allowed_protocol' => array(
3538
- 'http',
3539
- 'https',
 
 
3540
  ),
3541
  ),
3542
  'xlink:role' => array(),
@@ -3550,21 +3997,15 @@ class AMP_Allowed_Tags_Generated {
3550
  'y' => array(),
3551
  ),
3552
  'tag_spec' => array(
3553
- 'html_format' => array(
3554
- 'amp',
3555
- 'amp4ads',
3556
- ),
3557
  'mandatory_ancestor' => 'svg',
3558
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3559
  ),
3560
-
3561
  ),
3562
  ),
3563
  'footer' => array(
3564
  array(
3565
  'attr_spec_list' => array(),
3566
  'tag_spec' => array(),
3567
-
3568
  ),
3569
  ),
3570
  'foreignobject' => array(
@@ -3648,14 +4089,9 @@ class AMP_Allowed_Tags_Generated {
3648
  'y' => array(),
3649
  ),
3650
  'tag_spec' => array(
3651
- 'html_format' => array(
3652
- 'amp',
3653
- 'amp4ads',
3654
- ),
3655
  'mandatory_ancestor' => 'svg',
3656
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3657
  ),
3658
-
3659
  ),
3660
  ),
3661
  'form' => array(
@@ -3666,16 +4102,20 @@ class AMP_Allowed_Tags_Generated {
3666
  'action' => array(
3667
  'blacklisted_value_regex' => '__amp_source_origin',
3668
  'mandatory' => true,
3669
- 'allow_relative' => true,
3670
- 'allowed_protocol' => array(
3671
- 'https',
 
 
3672
  ),
3673
  ),
3674
  'action-xhr' => array(
3675
  'blacklisted_value_regex' => '__amp_source_origin',
3676
- 'allow_relative' => true,
3677
- 'allowed_protocol' => array(
3678
- 'https',
 
 
3679
  ),
3680
  ),
3681
  'autocomplete' => array(),
@@ -3694,23 +4134,23 @@ class AMP_Allowed_Tags_Generated {
3694
  ),
3695
  'verify-xhr' => array(
3696
  'blacklisted_value_regex' => '__amp_source_origin',
3697
- 'allow_relative' => true,
3698
- 'allowed_protocol' => array(
3699
- 'https',
 
 
3700
  ),
3701
  ),
3702
  ),
3703
  'tag_spec' => array(
3704
  'disallowed_ancestor' => array(
3705
- 'amp-app-banner',
3706
  ),
3707
- 'html_format' => array(
3708
- 'amp',
3709
- 'amp4ads',
3710
  ),
3711
- 'spec_name' => 'form [method=get]',
3712
  ),
3713
-
3714
  ),
3715
  array(
3716
  'attr_spec_list' => array(
@@ -3719,9 +4159,11 @@ class AMP_Allowed_Tags_Generated {
3719
  'action-xhr' => array(
3720
  'blacklisted_value_regex' => '__amp_source_origin',
3721
  'mandatory' => true,
3722
- 'allow_relative' => true,
3723
- 'allowed_protocol' => array(
3724
- 'https',
 
 
3725
  ),
3726
  ),
3727
  'autocomplete' => array(),
@@ -3730,6 +4172,7 @@ class AMP_Allowed_Tags_Generated {
3730
  ),
3731
  'enctype' => array(),
3732
  'method' => array(
 
3733
  'mandatory' => true,
3734
  'value_casei' => 'post',
3735
  ),
@@ -3741,23 +4184,23 @@ class AMP_Allowed_Tags_Generated {
3741
  ),
3742
  'verify-xhr' => array(
3743
  'blacklisted_value_regex' => '__amp_source_origin',
3744
- 'allow_relative' => true,
3745
- 'allowed_protocol' => array(
3746
- 'https',
 
 
3747
  ),
3748
  ),
3749
  ),
3750
  'tag_spec' => array(
3751
  'disallowed_ancestor' => array(
3752
- 'amp-app-banner',
3753
  ),
3754
- 'html_format' => array(
3755
- 'amp',
3756
- 'amp4ads',
3757
  ),
3758
- 'spec_name' => 'form [method=post]',
3759
  ),
3760
-
3761
  ),
3762
  ),
3763
  'g' => array(
@@ -3837,14 +4280,9 @@ class AMP_Allowed_Tags_Generated {
3837
  'xmlns:xlink' => array(),
3838
  ),
3839
  'tag_spec' => array(
3840
- 'html_format' => array(
3841
- 'amp',
3842
- 'amp4ads',
3843
- ),
3844
  'mandatory_ancestor' => 'svg',
3845
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3846
  ),
3847
-
3848
  ),
3849
  ),
3850
  'glyph' => array(
@@ -3928,14 +4366,9 @@ class AMP_Allowed_Tags_Generated {
3928
  'xmlns:xlink' => array(),
3929
  ),
3930
  'tag_spec' => array(
3931
- 'html_format' => array(
3932
- 'amp',
3933
- 'amp4ads',
3934
- ),
3935
  'mandatory_ancestor' => 'svg',
3936
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3937
  ),
3938
-
3939
  ),
3940
  ),
3941
  'glyphref' => array(
@@ -4015,11 +4448,13 @@ class AMP_Allowed_Tags_Generated {
4015
  'alternative_names' => array(
4016
  'href',
4017
  ),
4018
- 'allow_empty' => false,
4019
- 'allow_relative' => true,
4020
- 'allowed_protocol' => array(
4021
- 'http',
4022
- 'https',
 
 
4023
  ),
4024
  ),
4025
  'xlink:role' => array(),
@@ -4033,14 +4468,9 @@ class AMP_Allowed_Tags_Generated {
4033
  'y' => array(),
4034
  ),
4035
  'tag_spec' => array(
4036
- 'html_format' => array(
4037
- 'amp',
4038
- 'amp4ads',
4039
- ),
4040
  'mandatory_ancestor' => 'svg',
4041
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4042
  ),
4043
-
4044
  ),
4045
  ),
4046
  'h1' => array(
@@ -4049,7 +4479,6 @@ class AMP_Allowed_Tags_Generated {
4049
  'align' => array(),
4050
  ),
4051
  'tag_spec' => array(),
4052
-
4053
  ),
4054
  ),
4055
  'h2' => array(
@@ -4058,7 +4487,6 @@ class AMP_Allowed_Tags_Generated {
4058
  'align' => array(),
4059
  ),
4060
  'tag_spec' => array(),
4061
-
4062
  ),
4063
  ),
4064
  'h3' => array(
@@ -4067,7 +4495,6 @@ class AMP_Allowed_Tags_Generated {
4067
  'align' => array(),
4068
  ),
4069
  'tag_spec' => array(),
4070
-
4071
  ),
4072
  ),
4073
  'h4' => array(
@@ -4076,7 +4503,6 @@ class AMP_Allowed_Tags_Generated {
4076
  'align' => array(),
4077
  ),
4078
  'tag_spec' => array(),
4079
-
4080
  ),
4081
  ),
4082
  'h5' => array(
@@ -4085,7 +4511,6 @@ class AMP_Allowed_Tags_Generated {
4085
  'align' => array(),
4086
  ),
4087
  'tag_spec' => array(),
4088
-
4089
  ),
4090
  ),
4091
  'h6' => array(
@@ -4094,25 +4519,29 @@ class AMP_Allowed_Tags_Generated {
4094
  'align' => array(),
4095
  ),
4096
  'tag_spec' => array(),
4097
-
 
 
 
 
 
 
 
 
 
 
4098
  ),
4099
  ),
4100
  'header' => array(
4101
  array(
4102
  'attr_spec_list' => array(),
4103
  'tag_spec' => array(),
4104
-
4105
  ),
4106
  ),
4107
  'hgroup' => array(
4108
  array(
4109
  'attr_spec_list' => array(),
4110
- 'tag_spec' => array(
4111
- 'html_format' => array(
4112
- 'amp',
4113
- ),
4114
- ),
4115
-
4116
  ),
4117
  ),
4118
  'hkern' => array(
@@ -4132,28 +4561,41 @@ class AMP_Allowed_Tags_Generated {
4132
  'xmlns:xlink' => array(),
4133
  ),
4134
  'tag_spec' => array(
4135
- 'html_format' => array(
4136
- 'amp',
4137
- 'amp4ads',
4138
- ),
4139
  'mandatory_ancestor' => 'svg',
4140
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4141
  ),
4142
-
4143
  ),
4144
  ),
4145
  'hr' => array(
4146
  array(
4147
  'attr_spec_list' => array(),
4148
  'tag_spec' => array(),
4149
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4150
  ),
4151
  ),
4152
  'i' => array(
4153
  array(
4154
  'attr_spec_list' => array(),
4155
  'tag_spec' => array(),
4156
-
4157
  ),
4158
  ),
4159
  'iframe' => array(
@@ -4174,24 +4616,22 @@ class AMP_Allowed_Tags_Generated {
4174
  ),
4175
  'src' => array(
4176
  'blacklisted_value_regex' => '__amp_source_origin',
4177
- 'allow_relative' => false,
4178
- 'allowed_protocol' => array(
4179
- 'data',
4180
- 'https',
 
 
4181
  ),
4182
  ),
4183
  'srcdoc' => array(),
4184
  'width' => array(),
4185
  ),
4186
  'tag_spec' => array(
4187
- 'html_format' => array(
4188
- 'amp',
4189
- ),
4190
  'mandatory_ancestor' => 'noscript',
4191
  'mandatory_ancestor_suggested_alternative' => 'amp-iframe',
4192
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-iframe',
4193
  ),
4194
-
4195
  ),
4196
  ),
4197
  'image' => array(
@@ -4276,12 +4716,14 @@ class AMP_Allowed_Tags_Generated {
4276
  'href',
4277
  ),
4278
  'blacklisted_value_regex' => '(^|\\s)data:image\\/svg\\+xml',
4279
- 'allow_empty' => false,
4280
- 'allow_relative' => true,
4281
- 'allowed_protocol' => array(
4282
- 'data',
4283
- 'http',
4284
- 'https',
 
 
4285
  ),
4286
  ),
4287
  'xlink:role' => array(),
@@ -4295,14 +4737,9 @@ class AMP_Allowed_Tags_Generated {
4295
  'y' => array(),
4296
  ),
4297
  'tag_spec' => array(
4298
- 'html_format' => array(
4299
- 'amp',
4300
- 'amp4ads',
4301
- ),
4302
  'mandatory_ancestor' => 'svg',
4303
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4304
  ),
4305
-
4306
  ),
4307
  ),
4308
  'img' => array(
@@ -4314,10 +4751,12 @@ class AMP_Allowed_Tags_Generated {
4314
  'ismap' => array(),
4315
  'longdesc' => array(
4316
  'blacklisted_value_regex' => '__amp_source_origin',
4317
- 'allow_relative' => true,
4318
- 'allowed_protocol' => array(
4319
- 'http',
4320
- 'https',
 
 
4321
  ),
4322
  ),
4323
  'src' => array(
@@ -4326,23 +4765,21 @@ class AMP_Allowed_Tags_Generated {
4326
  ),
4327
  'blacklisted_value_regex' => '__amp_source_origin',
4328
  'mandatory' => true,
4329
- 'allow_relative' => true,
4330
- 'allowed_protocol' => array(
4331
- 'data',
4332
- 'https',
 
 
4333
  ),
4334
  ),
4335
  'width' => array(),
4336
  ),
4337
  'tag_spec' => array(
4338
- 'html_format' => array(
4339
- 'amp',
4340
- ),
4341
  'mandatory_ancestor' => 'noscript',
4342
  'mandatory_ancestor_suggested_alternative' => 'amp-img',
4343
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
4344
  ),
4345
-
4346
  ),
4347
  ),
4348
  'input' => array(
@@ -4386,7 +4823,7 @@ class AMP_Allowed_Tags_Generated {
4386
  'minlength' => array(),
4387
  'multiple' => array(),
4388
  'name' => array(
4389
- '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|$)',
4390
  ),
4391
  'pattern' => array(),
4392
  'placeholder' => array(),
@@ -4406,7 +4843,6 @@ class AMP_Allowed_Tags_Generated {
4406
  'tag_spec' => array(
4407
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4408
  ),
4409
-
4410
  ),
4411
  ),
4412
  'ins' => array(
@@ -4414,24 +4850,24 @@ class AMP_Allowed_Tags_Generated {
4414
  'attr_spec_list' => array(
4415
  'cite' => array(
4416
  'blacklisted_value_regex' => '__amp_source_origin',
4417
- 'allow_empty' => true,
4418
- 'allow_relative' => true,
4419
- 'allowed_protocol' => array(
4420
- 'http',
4421
- 'https',
 
 
4422
  ),
4423
  ),
4424
  'datetime' => array(),
4425
  ),
4426
  'tag_spec' => array(),
4427
-
4428
  ),
4429
  ),
4430
  'kbd' => array(
4431
  array(
4432
  'attr_spec_list' => array(),
4433
  'tag_spec' => array(),
4434
-
4435
  ),
4436
  ),
4437
  'label' => array(
@@ -4442,14 +4878,12 @@ class AMP_Allowed_Tags_Generated {
4442
  'tag_spec' => array(
4443
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4444
  ),
4445
-
4446
  ),
4447
  ),
4448
  'legend' => array(
4449
  array(
4450
  'attr_spec_list' => array(),
4451
  'tag_spec' => array(),
4452
-
4453
  ),
4454
  ),
4455
  'li' => array(
@@ -4460,7 +4894,6 @@ class AMP_Allowed_Tags_Generated {
4460
  ),
4461
  ),
4462
  'tag_spec' => array(),
4463
-
4464
  ),
4465
  ),
4466
  'line' => array(
@@ -4545,14 +4978,9 @@ class AMP_Allowed_Tags_Generated {
4545
  'y2' => array(),
4546
  ),
4547
  'tag_spec' => array(
4548
- 'html_format' => array(
4549
- 'amp',
4550
- 'amp4ads',
4551
- ),
4552
  'mandatory_ancestor' => 'svg',
4553
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4554
  ),
4555
-
4556
  ),
4557
  ),
4558
  'lineargradient' => array(
@@ -4633,11 +5061,13 @@ class AMP_Allowed_Tags_Generated {
4633
  'alternative_names' => array(
4634
  'href',
4635
  ),
4636
- 'allow_empty' => false,
4637
- 'allow_relative' => true,
4638
- 'allowed_protocol' => array(
4639
- 'http',
4640
- 'https',
 
 
4641
  ),
4642
  ),
4643
  'xlink:role' => array(),
@@ -4652,14 +5082,9 @@ class AMP_Allowed_Tags_Generated {
4652
  'y2' => array(),
4653
  ),
4654
  'tag_spec' => array(
4655
- 'html_format' => array(
4656
- 'amp',
4657
- 'amp4ads',
4658
- ),
4659
  'mandatory_ancestor' => 'svg',
4660
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4661
  ),
4662
-
4663
  ),
4664
  ),
4665
  'link' => array(
@@ -4669,11 +5094,12 @@ class AMP_Allowed_Tags_Generated {
4669
  'value_casei' => 'utf-8',
4670
  ),
4671
  'color' => array(),
 
4672
  'href' => array(),
4673
  'hreflang' => array(),
4674
  'media' => array(),
4675
  'rel' => array(
4676
- 'blacklisted_value_regex' => '(^|\\s)(canonical|components|import|manifest|serviceworker|stylesheet|subresource|)(\\s|$)',
4677
  'mandatory' => true,
4678
  ),
4679
  'sizes' => array(),
@@ -4682,16 +5108,11 @@ class AMP_Allowed_Tags_Generated {
4682
  ),
4683
  'tag_spec' => array(
4684
  'disallowed_ancestor' => array(
4685
- 'template',
4686
- ),
4687
- 'html_format' => array(
4688
- 'amp',
4689
- 'amp4ads',
4690
  ),
4691
  'spec_name' => 'link rel=',
4692
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
4693
  ),
4694
-
4695
  ),
4696
  array(
4697
  'attr_spec_list' => array(
@@ -4699,28 +5120,123 @@ class AMP_Allowed_Tags_Generated {
4699
  'value_casei' => 'utf-8',
4700
  ),
4701
  'color' => array(),
 
4702
  'href' => array(
 
4703
  'mandatory' => true,
 
 
 
 
 
 
 
4704
  ),
4705
  'hreflang' => array(),
4706
- 'itemprop' => array(
 
 
4707
  'mandatory' => true,
4708
- 'value_casei' => 'sameas',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4709
  ),
 
4710
  'media' => array(),
 
 
 
 
 
4711
  'sizes' => array(),
4712
  'target' => array(),
4713
  'type' => array(),
4714
  ),
4715
  'tag_spec' => array(
4716
- 'html_format' => array(
4717
- 'amp',
4718
- 'amp4ads',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4719
  ),
4720
- 'spec_name' => 'link itemprop=sameas',
4721
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
4722
  ),
4723
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4724
  ),
4725
  array(
4726
  'attr_spec_list' => array(
@@ -4728,12 +5244,15 @@ class AMP_Allowed_Tags_Generated {
4728
  'value_casei' => 'utf-8',
4729
  ),
4730
  'color' => array(),
 
4731
  'href' => array(
4732
  'mandatory' => true,
4733
  ),
4734
  'hreflang' => array(),
4735
  'itemprop' => array(
 
4736
  'mandatory' => true,
 
4737
  ),
4738
  'media' => array(),
4739
  'sizes' => array(),
@@ -4741,14 +5260,33 @@ class AMP_Allowed_Tags_Generated {
4741
  'type' => array(),
4742
  ),
4743
  'tag_spec' => array(
4744
- 'html_format' => array(
4745
- 'amp',
4746
- 'amp4ads',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4747
  ),
 
 
 
 
 
 
4748
  'spec_name' => 'link itemprop=',
4749
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
4750
  ),
4751
-
4752
  ),
4753
  array(
4754
  'attr_spec_list' => array(
@@ -4756,6 +5294,7 @@ class AMP_Allowed_Tags_Generated {
4756
  'value_casei' => 'utf-8',
4757
  ),
4758
  'color' => array(),
 
4759
  'href' => array(
4760
  'mandatory' => true,
4761
  ),
@@ -4769,39 +5308,27 @@ class AMP_Allowed_Tags_Generated {
4769
  'type' => array(),
4770
  ),
4771
  'tag_spec' => array(
4772
- 'html_format' => array(
4773
- 'amp',
4774
- 'amp4ads',
4775
- ),
4776
  'spec_name' => 'link property=',
4777
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
4778
  ),
4779
-
4780
  ),
4781
  ),
4782
  'listing' => array(
4783
  array(
4784
  'attr_spec_list' => array(),
4785
- 'tag_spec' => array(
4786
- 'html_format' => array(
4787
- 'amp',
4788
- ),
4789
- ),
4790
-
4791
  ),
4792
  ),
4793
  'main' => array(
4794
  array(
4795
  'attr_spec_list' => array(),
4796
  'tag_spec' => array(),
4797
-
4798
  ),
4799
  ),
4800
  'mark' => array(
4801
  array(
4802
  'attr_spec_list' => array(),
4803
  'tag_spec' => array(),
4804
-
4805
  ),
4806
  ),
4807
  'marker' => array(
@@ -4886,14 +5413,9 @@ class AMP_Allowed_Tags_Generated {
4886
  'xmlns:xlink' => array(),
4887
  ),
4888
  'tag_spec' => array(
4889
- 'html_format' => array(
4890
- 'amp',
4891
- 'amp4ads',
4892
- ),
4893
  'mandatory_ancestor' => 'svg',
4894
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4895
  ),
4896
-
4897
  ),
4898
  ),
4899
  'mask' => array(
@@ -4978,61 +5500,218 @@ class AMP_Allowed_Tags_Generated {
4978
  'y' => array(),
4979
  ),
4980
  'tag_spec' => array(
4981
- 'html_format' => array(
4982
- 'amp',
4983
- 'amp4ads',
4984
- ),
4985
  'mandatory_ancestor' => 'svg',
4986
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4987
  ),
4988
-
4989
  ),
4990
  ),
4991
  'meta' => array(
4992
  array(
4993
  'attr_spec_list' => array(
4994
- 'content' => array(
4995
- 'mandatory' => true,
4996
- 'chrome' => array(
4997
- 'value',
4998
- ),
4999
- 'ie' => array(
5000
- 'value',
5001
- ),
5002
- ),
5003
- 'http-equiv' => array(
5004
  'mandatory' => true,
5005
- 'value_casei' => 'x-ua-compatible',
5006
  ),
5007
  ),
5008
  'tag_spec' => array(
5009
- 'html_format' => array(
5010
- 'amp',
5011
- 'amp4ads',
5012
- ),
5013
- 'mandatory_ancestor' => 'head',
5014
- 'spec_name' => 'meta http-equiv=x-ua-compatible',
5015
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5016
  ),
5017
-
5018
  ),
5019
  array(
5020
  'attr_spec_list' => array(
5021
- 'content' => array(),
5022
- 'itemprop' => array(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5023
  'name' => array(
5024
- 'blacklisted_value_regex' => '(^|\\s)(amp-.*|amp4ads-.*|apple-itunes-app|content-disposition|revisit-after|viewport)(\\s|$)',
 
 
5025
  ),
5026
- 'property' => array(),
5027
  ),
5028
  'tag_spec' => array(
5029
- 'html_format' => array(
5030
- 'amp',
5031
- 'amp4ads',
5032
- ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5033
  'spec_name' => 'meta name= and content=',
5034
  ),
5035
-
5036
  ),
5037
  array(
5038
  'attr_spec_list' => array(
@@ -5041,20 +5720,16 @@ class AMP_Allowed_Tags_Generated {
5041
  'value_casei' => 'text/html; charset=utf-8',
5042
  ),
5043
  'http-equiv' => array(
 
5044
  'mandatory' => true,
5045
  'value_casei' => 'content-type',
5046
  ),
5047
  ),
5048
  'tag_spec' => array(
5049
- 'html_format' => array(
5050
- 'amp',
5051
- 'amp4ads',
5052
- ),
5053
  'mandatory_ancestor' => 'head',
5054
- 'spec_name' => 'meta http-equiv=content-type',
5055
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5056
  ),
5057
-
5058
  ),
5059
  array(
5060
  'attr_spec_list' => array(
@@ -5062,20 +5737,16 @@ class AMP_Allowed_Tags_Generated {
5062
  'mandatory' => true,
5063
  ),
5064
  'http-equiv' => array(
 
5065
  'mandatory' => true,
5066
  'value_casei' => 'content-language',
5067
  ),
5068
  ),
5069
  'tag_spec' => array(
5070
- 'html_format' => array(
5071
- 'amp',
5072
- 'amp4ads',
5073
- ),
5074
  'mandatory_ancestor' => 'head',
5075
  'spec_name' => 'meta http-equiv=content-language',
5076
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5077
  ),
5078
-
5079
  ),
5080
  array(
5081
  'attr_spec_list' => array(
@@ -5083,20 +5754,16 @@ class AMP_Allowed_Tags_Generated {
5083
  'mandatory' => true,
5084
  ),
5085
  'http-equiv' => array(
 
5086
  'mandatory' => true,
5087
  'value_casei' => 'pics-label',
5088
  ),
5089
  ),
5090
  'tag_spec' => array(
5091
- 'html_format' => array(
5092
- 'amp',
5093
- 'amp4ads',
5094
- ),
5095
  'mandatory_ancestor' => 'head',
5096
  'spec_name' => 'meta http-equiv=pics-label',
5097
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5098
  ),
5099
-
5100
  ),
5101
  array(
5102
  'attr_spec_list' => array(
@@ -5104,20 +5771,16 @@ class AMP_Allowed_Tags_Generated {
5104
  'mandatory' => true,
5105
  ),
5106
  'http-equiv' => array(
 
5107
  'mandatory' => true,
5108
  'value_casei' => 'imagetoolbar',
5109
  ),
5110
  ),
5111
  'tag_spec' => array(
5112
- 'html_format' => array(
5113
- 'amp',
5114
- 'amp4ads',
5115
- ),
5116
  'mandatory_ancestor' => 'head',
5117
  'spec_name' => 'meta http-equiv=imagetoolbar',
5118
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5119
  ),
5120
-
5121
  ),
5122
  array(
5123
  'attr_spec_list' => array(
@@ -5126,20 +5789,16 @@ class AMP_Allowed_Tags_Generated {
5126
  'value_casei' => 'text/css',
5127
  ),
5128
  'http-equiv' => array(
 
5129
  'mandatory' => true,
5130
  'value_casei' => 'content-style-type',
5131
  ),
5132
  ),
5133
  'tag_spec' => array(
5134
- 'html_format' => array(
5135
- 'amp',
5136
- 'amp4ads',
5137
- ),
5138
  'mandatory_ancestor' => 'head',
5139
- 'spec_name' => 'meta http-equiv=content-style-type',
5140
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5141
  ),
5142
-
5143
  ),
5144
  array(
5145
  'attr_spec_list' => array(
@@ -5148,20 +5807,16 @@ class AMP_Allowed_Tags_Generated {
5148
  'value_casei' => 'text/javascript',
5149
  ),
5150
  'http-equiv' => array(
 
5151
  'mandatory' => true,
5152
  'value_casei' => 'content-script-type',
5153
  ),
5154
  ),
5155
  'tag_spec' => array(
5156
- 'html_format' => array(
5157
- 'amp',
5158
- 'amp4ads',
5159
- ),
5160
  'mandatory_ancestor' => 'head',
5161
- 'spec_name' => 'meta http-equiv=content-script-type',
5162
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5163
  ),
5164
-
5165
  ),
5166
  array(
5167
  'attr_spec_list' => array(
@@ -5169,20 +5824,16 @@ class AMP_Allowed_Tags_Generated {
5169
  'mandatory' => true,
5170
  ),
5171
  'http-equiv' => array(
 
5172
  'mandatory' => true,
5173
  'value_casei' => 'origin-trial',
5174
  ),
5175
  ),
5176
  'tag_spec' => array(
5177
- 'html_format' => array(
5178
- 'amp',
5179
- 'amp4ads',
5180
- ),
5181
  'mandatory_ancestor' => 'head',
5182
  'spec_name' => 'meta http-equiv=origin-trial',
5183
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5184
  ),
5185
-
5186
  ),
5187
  array(
5188
  'attr_spec_list' => array(
@@ -5190,20 +5841,67 @@ class AMP_Allowed_Tags_Generated {
5190
  'mandatory' => true,
5191
  ),
5192
  'http-equiv' => array(
 
5193
  'mandatory' => true,
5194
  'value_casei' => 'resource-type',
5195
  ),
5196
  ),
5197
  'tag_spec' => array(
5198
- 'html_format' => array(
5199
- 'amp',
5200
- 'amp4ads',
5201
- ),
5202
  'mandatory_ancestor' => 'head',
5203
  'spec_name' => 'meta http-equiv=resource-type',
5204
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5205
  ),
5206
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5207
  ),
5208
  ),
5209
  'metadata' => array(
@@ -5218,14 +5916,9 @@ class AMP_Allowed_Tags_Generated {
5218
  'xmlns:xlink' => array(),
5219
  ),
5220
  'tag_spec' => array(
5221
- 'html_format' => array(
5222
- 'amp',
5223
- 'amp4ads',
5224
- ),
5225
  'mandatory_ancestor' => 'svg',
5226
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5227
  ),
5228
-
5229
  ),
5230
  ),
5231
  'meter' => array(
@@ -5239,29 +5932,23 @@ class AMP_Allowed_Tags_Generated {
5239
  'value' => array(),
5240
  ),
5241
  'tag_spec' => array(),
5242
-
5243
  ),
5244
  ),
5245
  'multicol' => array(
5246
  array(
5247
  'attr_spec_list' => array(),
5248
- 'tag_spec' => array(
5249
- 'html_format' => array(
5250
- 'amp',
5251
- ),
5252
- ),
5253
-
5254
  ),
5255
  ),
5256
  'nav' => array(
5257
  array(
5258
  'attr_spec_list' => array(),
5259
  'tag_spec' => array(),
5260
-
5261
  ),
5262
  array(
5263
  'attr_spec_list' => array(
5264
  'toolbar' => array(
 
5265
  'mandatory' => true,
5266
  ),
5267
  'toolbar-target' => array(
@@ -5269,61 +5956,48 @@ class AMP_Allowed_Tags_Generated {
5269
  ),
5270
  ),
5271
  'tag_spec' => array(
5272
- 'html_format' => array(
5273
- 'amp',
5274
- ),
5275
  'mandatory_parent' => 'amp-sidebar',
5276
  'spec_name' => 'amp-sidebar > nav',
5277
  ),
5278
-
5279
  ),
5280
  ),
5281
  'nextid' => array(
5282
  array(
5283
  'attr_spec_list' => array(),
5284
- 'tag_spec' => array(
5285
- 'html_format' => array(
5286
- 'amp',
5287
- ),
5288
- ),
5289
-
5290
  ),
5291
  ),
5292
  'nobr' => array(
5293
  array(
5294
  'attr_spec_list' => array(),
5295
- 'tag_spec' => array(
5296
- 'html_format' => array(
5297
- 'amp',
5298
- ),
5299
- ),
5300
-
5301
  ),
5302
  ),
5303
  'noscript' => array(
 
 
 
 
 
 
 
 
 
 
5304
  array(
5305
  'attr_spec_list' => array(),
5306
  'tag_spec' => array(
5307
  'disallowed_ancestor' => array(
5308
- 'noscript',
5309
- ),
5310
- 'html_format' => array(
5311
- 'amp',
5312
  ),
5313
  'mandatory_ancestor' => 'body',
5314
  ),
5315
-
5316
  ),
5317
  ),
5318
  'o:p' => array(
5319
  array(
5320
  'attr_spec_list' => array(),
5321
- 'tag_spec' => array(
5322
- 'html_format' => array(
5323
- 'amp',
5324
- ),
5325
- ),
5326
-
5327
  ),
5328
  ),
5329
  'ol' => array(
@@ -5336,11 +6010,10 @@ class AMP_Allowed_Tags_Generated {
5336
  'value_regex' => '[0-9]*',
5337
  ),
5338
  'type' => array(
5339
- 'value_regex' => '[1aaii]',
5340
  ),
5341
  ),
5342
  'tag_spec' => array(),
5343
-
5344
  ),
5345
  ),
5346
  'optgroup' => array(
@@ -5355,7 +6028,6 @@ class AMP_Allowed_Tags_Generated {
5355
  'mandatory_parent' => 'select',
5356
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
5357
  ),
5358
-
5359
  ),
5360
  ),
5361
  'option' => array(
@@ -5373,7 +6045,6 @@ class AMP_Allowed_Tags_Generated {
5373
  'tag_spec' => array(
5374
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
5375
  ),
5376
-
5377
  ),
5378
  ),
5379
  'output' => array(
@@ -5382,11 +6053,10 @@ class AMP_Allowed_Tags_Generated {
5382
  'for' => array(),
5383
  'form' => array(),
5384
  'name' => array(
5385
- '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|$)',
5386
  ),
5387
  ),
5388
  'tag_spec' => array(),
5389
-
5390
  ),
5391
  ),
5392
  'p' => array(
@@ -5395,7 +6065,6 @@ class AMP_Allowed_Tags_Generated {
5395
  'align' => array(),
5396
  ),
5397
  'tag_spec' => array(),
5398
-
5399
  ),
5400
  ),
5401
  'path' => array(
@@ -5478,14 +6147,9 @@ class AMP_Allowed_Tags_Generated {
5478
  'xmlns:xlink' => array(),
5479
  ),
5480
  'tag_spec' => array(
5481
- 'html_format' => array(
5482
- 'amp',
5483
- 'amp4ads',
5484
- ),
5485
  'mandatory_ancestor' => 'svg',
5486
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5487
  ),
5488
-
5489
  ),
5490
  ),
5491
  'pattern' => array(
@@ -5572,11 +6236,13 @@ class AMP_Allowed_Tags_Generated {
5572
  'alternative_names' => array(
5573
  'href',
5574
  ),
5575
- 'allow_empty' => false,
5576
- 'allow_relative' => true,
5577
- 'allowed_protocol' => array(
5578
- 'http',
5579
- 'https',
 
 
5580
  ),
5581
  ),
5582
  'xlink:role' => array(),
@@ -5590,14 +6256,9 @@ class AMP_Allowed_Tags_Generated {
5590
  'y' => array(),
5591
  ),
5592
  'tag_spec' => array(
5593
- 'html_format' => array(
5594
- 'amp',
5595
- 'amp4ads',
5596
- ),
5597
  'mandatory_ancestor' => 'svg',
5598
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5599
  ),
5600
-
5601
  ),
5602
  ),
5603
  'polygon' => array(
@@ -5679,14 +6340,9 @@ class AMP_Allowed_Tags_Generated {
5679
  'xmlns:xlink' => array(),
5680
  ),
5681
  'tag_spec' => array(
5682
- 'html_format' => array(
5683
- 'amp',
5684
- 'amp4ads',
5685
- ),
5686
  'mandatory_ancestor' => 'svg',
5687
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5688
  ),
5689
-
5690
  ),
5691
  ),
5692
  'polyline' => array(
@@ -5768,21 +6424,15 @@ class AMP_Allowed_Tags_Generated {
5768
  'xmlns:xlink' => array(),
5769
  ),
5770
  'tag_spec' => array(
5771
- 'html_format' => array(
5772
- 'amp',
5773
- 'amp4ads',
5774
- ),
5775
  'mandatory_ancestor' => 'svg',
5776
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5777
  ),
5778
-
5779
  ),
5780
  ),
5781
  'pre' => array(
5782
  array(
5783
  'attr_spec_list' => array(),
5784
  'tag_spec' => array(),
5785
-
5786
  ),
5787
  ),
5788
  'progress' => array(
@@ -5792,7 +6442,6 @@ class AMP_Allowed_Tags_Generated {
5792
  'value' => array(),
5793
  ),
5794
  'tag_spec' => array(),
5795
-
5796
  ),
5797
  ),
5798
  'q' => array(
@@ -5800,16 +6449,17 @@ class AMP_Allowed_Tags_Generated {
5800
  'attr_spec_list' => array(
5801
  'cite' => array(
5802
  'blacklisted_value_regex' => '__amp_source_origin',
5803
- 'allow_empty' => true,
5804
- 'allow_relative' => true,
5805
- 'allowed_protocol' => array(
5806
- 'http',
5807
- 'https',
 
 
5808
  ),
5809
  ),
5810
  ),
5811
  'tag_spec' => array(),
5812
-
5813
  ),
5814
  ),
5815
  'radialgradient' => array(
@@ -5894,11 +6544,13 @@ class AMP_Allowed_Tags_Generated {
5894
  'alternative_names' => array(
5895
  'href',
5896
  ),
5897
- 'allow_empty' => false,
5898
- 'allow_relative' => true,
5899
- 'allowed_protocol' => array(
5900
- 'http',
5901
- 'https',
 
 
5902
  ),
5903
  ),
5904
  'xlink:role' => array(),
@@ -5911,21 +6563,15 @@ class AMP_Allowed_Tags_Generated {
5911
  'xmlns:xlink' => array(),
5912
  ),
5913
  'tag_spec' => array(
5914
- 'html_format' => array(
5915
- 'amp',
5916
- 'amp4ads',
5917
- ),
5918
  'mandatory_ancestor' => 'svg',
5919
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5920
  ),
5921
-
5922
  ),
5923
  ),
5924
  'rb' => array(
5925
  array(
5926
  'attr_spec_list' => array(),
5927
  'tag_spec' => array(),
5928
-
5929
  ),
5930
  ),
5931
  'rect' => array(
@@ -6012,174 +6658,2201 @@ class AMP_Allowed_Tags_Generated {
6012
  'y' => array(),
6013
  ),
6014
  'tag_spec' => array(
6015
- 'html_format' => array(
6016
- 'amp',
6017
- 'amp4ads',
6018
- ),
6019
  'mandatory_ancestor' => 'svg',
6020
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6021
  ),
6022
-
6023
  ),
6024
  ),
6025
  'rp' => array(
6026
  array(
6027
  'attr_spec_list' => array(),
6028
  'tag_spec' => array(),
6029
-
6030
  ),
6031
  ),
6032
  'rt' => array(
6033
  array(
6034
  'attr_spec_list' => array(),
6035
  'tag_spec' => array(),
6036
-
6037
  ),
6038
  ),
6039
  'rtc' => array(
6040
  array(
6041
  'attr_spec_list' => array(),
6042
  'tag_spec' => array(),
6043
-
6044
  ),
6045
  ),
6046
  'ruby' => array(
6047
  array(
6048
  'attr_spec_list' => array(),
6049
  'tag_spec' => array(),
6050
-
6051
  ),
6052
  ),
6053
  's' => array(
6054
  array(
6055
  'attr_spec_list' => array(),
6056
  'tag_spec' => array(),
6057
-
6058
  ),
6059
  ),
6060
  'samp' => array(
6061
  array(
6062
  'attr_spec_list' => array(),
6063
  'tag_spec' => array(),
6064
-
6065
  ),
6066
  ),
6067
  'script' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6068
  array(
6069
  'attr_spec_list' => array(
6070
  'nonce' => array(),
6071
  'type' => array(
 
6072
  'mandatory' => true,
6073
  'value_casei' => 'application/ld+json',
6074
  ),
6075
  ),
 
 
 
 
 
 
6076
  'tag_spec' => array(
6077
  'spec_name' => 'script type=application/ld+json',
6078
  ),
6079
-
6080
  ),
6081
  array(
6082
  'attr_spec_list' => array(
 
 
 
 
 
 
6083
  'type' => array(
6084
  'mandatory' => true,
6085
  'value_casei' => 'application/json',
6086
  ),
6087
  ),
 
 
 
 
 
 
6088
  'tag_spec' => array(
6089
- 'html_format' => array(
6090
- 'amp',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6091
  ),
6092
- 'mandatory_parent' => 'amp-ima-video',
6093
- 'spec_name' => 'amp-ima-video > script[type=application/json]',
6094
  ),
6095
-
6096
  ),
6097
  array(
6098
  'attr_spec_list' => array(
 
 
 
 
6099
  'nonce' => array(),
6100
  'type' => array(
6101
- 'mandatory' => true,
6102
- 'value' => 'application/json',
6103
  ),
6104
  ),
6105
  'tag_spec' => array(
6106
- 'html_format' => array(
6107
- 'amp4ads',
 
 
 
 
 
 
6108
  ),
6109
- 'mandatory_parent' => 'amp-ad-exit',
6110
- 'spec_name' => 'amp-ad-exit configuration json',
6111
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad-exit',
6112
  ),
6113
-
6114
  ),
6115
  array(
6116
  'attr_spec_list' => array(
 
 
 
 
6117
  'nonce' => array(),
6118
  'type' => array(
6119
- 'mandatory' => true,
6120
- 'value_casei' => 'application/json',
6121
  ),
6122
  ),
6123
  'tag_spec' => array(
6124
- 'html_format' => array(
6125
- 'amp',
6126
- 'amp4ads',
 
 
 
6127
  ),
6128
- 'mandatory_parent' => 'amp-analytics',
6129
- 'spec_name' => 'amp-analytics extension .json script',
6130
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-analytics',
6131
  ),
6132
-
6133
  ),
6134
  array(
6135
  'attr_spec_list' => array(
 
 
 
 
6136
  'nonce' => array(),
6137
  'type' => array(
6138
- 'mandatory' => true,
6139
- 'value_casei' => 'application/json',
6140
  ),
6141
  ),
6142
  'tag_spec' => array(
6143
- 'mandatory_parent' => 'amp-animation',
6144
- 'spec_name' => 'amp-animation extension .json script',
 
 
 
 
 
6145
  ),
6146
-
6147
  ),
6148
  array(
6149
  'attr_spec_list' => array(
 
 
 
 
6150
  'nonce' => array(),
6151
  'type' => array(
6152
- 'mandatory' => true,
6153
- 'value_casei' => 'application/json',
6154
  ),
6155
  ),
6156
  'tag_spec' => array(
6157
- 'html_format' => array(
6158
- 'amp',
 
 
 
 
6159
  ),
6160
- 'mandatory_parent' => 'amp-state',
6161
- 'spec_name' => 'amp-bind extension .json script',
6162
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-bind',
6163
  ),
6164
-
6165
  ),
6166
  array(
6167
  'attr_spec_list' => array(
 
 
 
 
6168
  'nonce' => array(),
6169
  'type' => array(
6170
- 'mandatory' => true,
6171
- 'value_casei' => 'application/json',
6172
  ),
6173
  ),
6174
  'tag_spec' => array(
6175
- 'html_format' => array(
6176
- 'amp',
 
 
 
 
 
 
6177
  ),
6178
- 'mandatory_parent' => 'amp-experiment',
6179
- 'spec_name' => 'amp-experiment extension .json script',
6180
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-experiment',
6181
  ),
6182
-
6183
  ),
6184
  ),
6185
  'section' => array(
@@ -6187,10 +8860,9 @@ class AMP_Allowed_Tags_Generated {
6187
  'attr_spec_list' => array(),
6188
  'tag_spec' => array(
6189
  'disallowed_ancestor' => array(
6190
- 'amp-accordion',
6191
  ),
6192
  ),
6193
-
6194
  ),
6195
  array(
6196
  'attr_spec_list' => array(
@@ -6202,7 +8874,6 @@ class AMP_Allowed_Tags_Generated {
6202
  'mandatory_parent' => 'amp-accordion',
6203
  'spec_name' => 'amp-accordion > section',
6204
  ),
6205
-
6206
  ),
6207
  ),
6208
  'select' => array(
@@ -6217,7 +8888,7 @@ class AMP_Allowed_Tags_Generated {
6217
  'disabled' => array(),
6218
  'multiple' => array(),
6219
  'name' => array(
6220
- '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|$)',
6221
  ),
6222
  'required' => array(),
6223
  'size' => array(),
@@ -6225,7 +8896,6 @@ class AMP_Allowed_Tags_Generated {
6225
  'tag_spec' => array(
6226
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
6227
  ),
6228
-
6229
  ),
6230
  ),
6231
  'slot' => array(
@@ -6233,19 +8903,13 @@ class AMP_Allowed_Tags_Generated {
6233
  'attr_spec_list' => array(
6234
  'name' => array(),
6235
  ),
6236
- 'tag_spec' => array(
6237
- 'html_format' => array(
6238
- 'amp',
6239
- ),
6240
- ),
6241
-
6242
  ),
6243
  ),
6244
  'small' => array(
6245
  array(
6246
  'attr_spec_list' => array(),
6247
  'tag_spec' => array(),
6248
-
6249
  ),
6250
  ),
6251
  'solidcolor' => array(
@@ -6322,14 +8986,9 @@ class AMP_Allowed_Tags_Generated {
6322
  'xmlns:xlink' => array(),
6323
  ),
6324
  'tag_spec' => array(
6325
- 'html_format' => array(
6326
- 'amp',
6327
- 'amp4ads',
6328
- ),
6329
  'mandatory_ancestor' => 'svg',
6330
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6331
  ),
6332
-
6333
  ),
6334
  ),
6335
  'source' => array(
@@ -6340,23 +8999,20 @@ class AMP_Allowed_Tags_Generated {
6340
  'media' => array(),
6341
  'src' => array(
6342
  'blacklisted_value_regex' => '__amp_source_origin',
6343
- 'allow_relative' => true,
6344
- 'allowed_protocol' => array(
6345
- 'https',
 
 
6346
  ),
6347
  ),
6348
  'type' => array(),
6349
  ),
6350
  'tag_spec' => array(
6351
- 'html_format' => array(
6352
- 'amp',
6353
- 'amp4ads',
6354
- ),
6355
  'mandatory_parent' => 'amp-video',
6356
  'spec_name' => 'amp-video > source',
6357
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
6358
  ),
6359
-
6360
  ),
6361
  array(
6362
  'attr_spec_list' => array(
@@ -6365,23 +9021,20 @@ class AMP_Allowed_Tags_Generated {
6365
  'media' => array(),
6366
  'src' => array(
6367
  'blacklisted_value_regex' => '__amp_source_origin',
6368
- 'allow_relative' => true,
6369
- 'allowed_protocol' => array(
6370
- 'https',
 
 
6371
  ),
6372
  ),
6373
  'type' => array(),
6374
  ),
6375
  'tag_spec' => array(
6376
- 'html_format' => array(
6377
- 'amp',
6378
- 'amp4ads',
6379
- ),
6380
  'mandatory_parent' => 'amp-audio',
6381
  'spec_name' => 'amp-audio > source',
6382
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
6383
  ),
6384
-
6385
  ),
6386
  array(
6387
  'attr_spec_list' => array(
@@ -6389,9 +9042,11 @@ class AMP_Allowed_Tags_Generated {
6389
  'src' => array(
6390
  'blacklisted_value_regex' => '__amp_source_origin',
6391
  'mandatory' => true,
6392
- 'allow_relative' => true,
6393
- 'allowed_protocol' => array(
6394
- 'https',
 
 
6395
  ),
6396
  ),
6397
  'type' => array(
@@ -6399,15 +9054,10 @@ class AMP_Allowed_Tags_Generated {
6399
  ),
6400
  ),
6401
  'tag_spec' => array(
6402
- 'html_format' => array(
6403
- 'amp',
6404
- 'amp4ads',
6405
- ),
6406
  'mandatory_parent' => 'audio',
6407
  'spec_name' => 'audio > source',
6408
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
6409
  ),
6410
-
6411
  ),
6412
  array(
6413
  'attr_spec_list' => array(
@@ -6415,9 +9065,11 @@ class AMP_Allowed_Tags_Generated {
6415
  'src' => array(
6416
  'blacklisted_value_regex' => '__amp_source_origin',
6417
  'mandatory' => true,
6418
- 'allow_relative' => true,
6419
- 'allowed_protocol' => array(
6420
- 'https',
 
 
6421
  ),
6422
  ),
6423
  'type' => array(
@@ -6425,15 +9077,10 @@ class AMP_Allowed_Tags_Generated {
6425
  ),
6426
  ),
6427
  'tag_spec' => array(
6428
- 'html_format' => array(
6429
- 'amp',
6430
- 'amp4ads',
6431
- ),
6432
  'mandatory_parent' => 'video',
6433
  'spec_name' => 'video > source',
6434
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
6435
  ),
6436
-
6437
  ),
6438
  array(
6439
  'attr_spec_list' => array(
@@ -6442,40 +9089,34 @@ class AMP_Allowed_Tags_Generated {
6442
  'media' => array(),
6443
  'src' => array(
6444
  'blacklisted_value_regex' => '__amp_source_origin',
6445
- 'allow_relative' => true,
6446
- 'allowed_protocol' => array(
6447
- 'https',
 
 
6448
  ),
6449
  ),
6450
  'type' => array(),
6451
  ),
6452
  'tag_spec' => array(
6453
- 'html_format' => array(
6454
- 'amp',
6455
- 'amp4ads',
6456
- ),
6457
  'mandatory_parent' => 'amp-ima-video',
 
 
 
6458
  'spec_name' => 'amp-ima-video > source',
6459
  ),
6460
-
6461
  ),
6462
  ),
6463
  'spacer' => array(
6464
  array(
6465
  'attr_spec_list' => array(),
6466
- 'tag_spec' => array(
6467
- 'html_format' => array(
6468
- 'amp',
6469
- ),
6470
- ),
6471
-
6472
  ),
6473
  ),
6474
  'span' => array(
6475
  array(
6476
  'attr_spec_list' => array(),
6477
  'tag_spec' => array(),
6478
-
6479
  ),
6480
  ),
6481
  'stop' => array(
@@ -6489,15 +9130,10 @@ class AMP_Allowed_Tags_Generated {
6489
  ),
6490
  ),
6491
  'tag_spec' => array(
6492
- 'html_format' => array(
6493
- 'amp',
6494
- 'amp4ads',
6495
- ),
6496
  'mandatory_ancestor' => 'lineargradient',
6497
  'spec_name' => 'lineargradient > stop',
6498
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6499
  ),
6500
-
6501
  ),
6502
  array(
6503
  'attr_spec_list' => array(
@@ -6509,48 +9145,84 @@ class AMP_Allowed_Tags_Generated {
6509
  ),
6510
  ),
6511
  'tag_spec' => array(
6512
- 'html_format' => array(
6513
- 'amp',
6514
- 'amp4ads',
6515
- ),
6516
  'mandatory_ancestor' => 'radialgradient',
6517
  'spec_name' => 'radialgradient > stop',
6518
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6519
  ),
6520
-
6521
  ),
6522
  ),
6523
  'strike' => array(
6524
  array(
6525
  'attr_spec_list' => array(),
6526
- 'tag_spec' => array(
6527
- 'html_format' => array(
6528
- 'amp',
6529
- ),
6530
- ),
6531
-
6532
  ),
6533
  ),
6534
  'strong' => array(
6535
  array(
6536
  'attr_spec_list' => array(),
6537
  'tag_spec' => array(),
6538
-
6539
  ),
6540
  ),
6541
  'style' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6542
  array(
6543
  'attr_spec_list' => array(
6544
  'amp-boilerplate' => array(
 
6545
  'mandatory' => true,
6546
  'value' => '',
6547
  ),
6548
  'nonce' => array(),
6549
  ),
 
 
 
6550
  'tag_spec' => array(
6551
- 'html_format' => array(
6552
- 'amp',
 
 
 
 
 
 
 
 
 
 
 
6553
  ),
 
 
 
 
 
 
6554
  'mandatory_alternatives' => 'noscript > style[amp-boilerplate]',
6555
  'mandatory_ancestor' => 'head',
6556
  'mandatory_parent' => 'noscript',
@@ -6558,39 +9230,36 @@ class AMP_Allowed_Tags_Generated {
6558
  'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
6559
  'unique' => true,
6560
  ),
6561
-
6562
  ),
6563
  array(
6564
  'attr_spec_list' => array(
6565
  'amp-keyframes' => array(
 
6566
  'mandatory' => true,
6567
  'value' => '',
6568
  ),
6569
  ),
 
 
 
 
6570
  'tag_spec' => array(
6571
- 'html_format' => array(
6572
- 'amp',
6573
- 'amp4ads',
6574
- ),
6575
  'mandatory_parent' => 'body',
6576
  'spec_name' => 'style[amp-keyframes]',
6577
  'unique' => true,
6578
  ),
6579
-
6580
  ),
6581
  ),
6582
  'sub' => array(
6583
  array(
6584
  'attr_spec_list' => array(),
6585
  'tag_spec' => array(),
6586
-
6587
  ),
6588
  ),
6589
  'sup' => array(
6590
  array(
6591
  'attr_spec_list' => array(),
6592
  'tag_spec' => array(),
6593
-
6594
  ),
6595
  ),
6596
  'svg' => array(
@@ -6678,13 +9347,8 @@ class AMP_Allowed_Tags_Generated {
6678
  'zoomandpan' => array(),
6679
  ),
6680
  'tag_spec' => array(
6681
- 'html_format' => array(
6682
- 'amp',
6683
- 'amp4ads',
6684
- ),
6685
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6686
  ),
6687
-
6688
  ),
6689
  ),
6690
  'switch' => array(
@@ -6762,14 +9426,9 @@ class AMP_Allowed_Tags_Generated {
6762
  'xmlns:xlink' => array(),
6763
  ),
6764
  'tag_spec' => array(
6765
- 'html_format' => array(
6766
- 'amp',
6767
- 'amp4ads',
6768
- ),
6769
  'mandatory_ancestor' => 'svg',
6770
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6771
  ),
6772
-
6773
  ),
6774
  ),
6775
  'symbol' => array(
@@ -6847,14 +9506,9 @@ class AMP_Allowed_Tags_Generated {
6847
  'xmlns:xlink' => array(),
6848
  ),
6849
  'tag_spec' => array(
6850
- 'html_format' => array(
6851
- 'amp',
6852
- 'amp4ads',
6853
- ),
6854
  'mandatory_ancestor' => 'svg',
6855
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6856
  ),
6857
-
6858
  ),
6859
  ),
6860
  'table' => array(
@@ -6871,14 +9525,12 @@ class AMP_Allowed_Tags_Generated {
6871
  'width' => array(),
6872
  ),
6873
  'tag_spec' => array(),
6874
-
6875
  ),
6876
  ),
6877
  'tbody' => array(
6878
  array(
6879
  'attr_spec_list' => array(),
6880
  'tag_spec' => array(),
6881
-
6882
  ),
6883
  ),
6884
  'td' => array(
@@ -6894,7 +9546,6 @@ class AMP_Allowed_Tags_Generated {
6894
  'width' => array(),
6895
  ),
6896
  'tag_spec' => array(),
6897
-
6898
  ),
6899
  ),
6900
  'template' => array(
@@ -6907,10 +9558,30 @@ class AMP_Allowed_Tags_Generated {
6907
  ),
6908
  'tag_spec' => array(
6909
  'disallowed_ancestor' => array(
6910
- 'template',
 
 
 
 
 
 
6911
  ),
6912
  ),
6913
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6914
  ),
6915
  ),
6916
  'text' => array(
@@ -6997,14 +9668,9 @@ class AMP_Allowed_Tags_Generated {
6997
  'y' => array(),
6998
  ),
6999
  'tag_spec' => array(
7000
- 'html_format' => array(
7001
- 'amp',
7002
- 'amp4ads',
7003
- ),
7004
  'mandatory_ancestor' => 'svg',
7005
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7006
  ),
7007
-
7008
  ),
7009
  ),
7010
  'textarea' => array(
@@ -7032,7 +9698,7 @@ class AMP_Allowed_Tags_Generated {
7032
  'maxlength' => array(),
7033
  'minlength' => array(),
7034
  'name' => array(
7035
- '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|$)',
7036
  ),
7037
  'placeholder' => array(),
7038
  'readonly' => array(),
@@ -7047,7 +9713,6 @@ class AMP_Allowed_Tags_Generated {
7047
  'tag_spec' => array(
7048
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
7049
  ),
7050
-
7051
  ),
7052
  ),
7053
  'textpath' => array(
@@ -7129,11 +9794,13 @@ class AMP_Allowed_Tags_Generated {
7129
  'alternative_names' => array(
7130
  'href',
7131
  ),
7132
- 'allow_empty' => false,
7133
- 'allow_relative' => true,
7134
- 'allowed_protocol' => array(
7135
- 'http',
7136
- 'https',
 
 
7137
  ),
7138
  ),
7139
  'xlink:role' => array(),
@@ -7146,21 +9813,15 @@ class AMP_Allowed_Tags_Generated {
7146
  'xmlns:xlink' => array(),
7147
  ),
7148
  'tag_spec' => array(
7149
- 'html_format' => array(
7150
- 'amp',
7151
- 'amp4ads',
7152
- ),
7153
  'mandatory_ancestor' => 'svg',
7154
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7155
  ),
7156
-
7157
  ),
7158
  ),
7159
  'tfoot' => array(
7160
  array(
7161
  'attr_spec_list' => array(),
7162
  'tag_spec' => array(),
7163
-
7164
  ),
7165
  ),
7166
  'th' => array(
@@ -7179,14 +9840,12 @@ class AMP_Allowed_Tags_Generated {
7179
  'width' => array(),
7180
  ),
7181
  'tag_spec' => array(),
7182
-
7183
  ),
7184
  ),
7185
  'thead' => array(
7186
  array(
7187
  'attr_spec_list' => array(),
7188
  'tag_spec' => array(),
7189
-
7190
  ),
7191
  ),
7192
  'time' => array(
@@ -7195,7 +9854,6 @@ class AMP_Allowed_Tags_Generated {
7195
  'datetime' => array(),
7196
  ),
7197
  'tag_spec' => array(),
7198
-
7199
  ),
7200
  ),
7201
  'title' => array(
@@ -7204,7 +9862,6 @@ class AMP_Allowed_Tags_Generated {
7204
  'tag_spec' => array(
7205
  'spec_name' => 'title',
7206
  ),
7207
-
7208
  ),
7209
  array(
7210
  'attr_spec_list' => array(
@@ -7217,15 +9874,10 @@ class AMP_Allowed_Tags_Generated {
7217
  'xmlns:xlink' => array(),
7218
  ),
7219
  'tag_spec' => array(
7220
- 'html_format' => array(
7221
- 'amp',
7222
- 'amp4ads',
7223
- ),
7224
  'mandatory_ancestor' => 'svg',
7225
  'spec_name' => 'svg title',
7226
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7227
  ),
7228
-
7229
  ),
7230
  ),
7231
  'tr' => array(
@@ -7237,7 +9889,6 @@ class AMP_Allowed_Tags_Generated {
7237
  'valign' => array(),
7238
  ),
7239
  'tag_spec' => array(),
7240
-
7241
  ),
7242
  ),
7243
  'track' => array(
@@ -7253,22 +9904,19 @@ class AMP_Allowed_Tags_Generated {
7253
  'src' => array(
7254
  'blacklisted_value_regex' => '__amp_source_origin',
7255
  'mandatory' => true,
7256
- 'allow_relative' => false,
7257
- 'allowed_protocol' => array(
7258
- 'https',
 
 
7259
  ),
7260
  ),
7261
  'srclang' => array(),
7262
  ),
7263
  'tag_spec' => array(
7264
- 'html_format' => array(
7265
- 'amp',
7266
- 'amp4ads',
7267
- ),
7268
  'mandatory_parent' => 'audio',
7269
  'spec_name' => 'audio > track',
7270
  ),
7271
-
7272
  ),
7273
  array(
7274
  'attr_spec_list' => array(
@@ -7283,9 +9931,11 @@ class AMP_Allowed_Tags_Generated {
7283
  'src' => array(
7284
  'blacklisted_value_regex' => '__amp_source_origin',
7285
  'mandatory' => true,
7286
- 'allow_relative' => false,
7287
- 'allowed_protocol' => array(
7288
- 'https',
 
 
7289
  ),
7290
  ),
7291
  'srclang' => array(
@@ -7293,14 +9943,9 @@ class AMP_Allowed_Tags_Generated {
7293
  ),
7294
  ),
7295
  'tag_spec' => array(
7296
- 'html_format' => array(
7297
- 'amp',
7298
- 'amp4ads',
7299
- ),
7300
  'mandatory_parent' => 'audio',
7301
  'spec_name' => 'audio > track[kind=subtitles]',
7302
  ),
7303
-
7304
  ),
7305
  array(
7306
  'attr_spec_list' => array(
@@ -7314,22 +9959,19 @@ class AMP_Allowed_Tags_Generated {
7314
  'src' => array(
7315
  'blacklisted_value_regex' => '__amp_source_origin',
7316
  'mandatory' => true,
7317
- 'allow_relative' => false,
7318
- 'allowed_protocol' => array(
7319
- 'https',
 
 
7320
  ),
7321
  ),
7322
  'srclang' => array(),
7323
  ),
7324
  'tag_spec' => array(
7325
- 'html_format' => array(
7326
- 'amp',
7327
- 'amp4ads',
7328
- ),
7329
  'mandatory_parent' => 'video',
7330
  'spec_name' => 'video > track',
7331
  ),
7332
-
7333
  ),
7334
  array(
7335
  'attr_spec_list' => array(
@@ -7344,9 +9986,11 @@ class AMP_Allowed_Tags_Generated {
7344
  'src' => array(
7345
  'blacklisted_value_regex' => '__amp_source_origin',
7346
  'mandatory' => true,
7347
- 'allow_relative' => false,
7348
- 'allowed_protocol' => array(
7349
- 'https',
 
 
7350
  ),
7351
  ),
7352
  'srclang' => array(
@@ -7354,14 +9998,9 @@ class AMP_Allowed_Tags_Generated {
7354
  ),
7355
  ),
7356
  'tag_spec' => array(
7357
- 'html_format' => array(
7358
- 'amp',
7359
- 'amp4ads',
7360
- ),
7361
  'mandatory_parent' => 'video',
7362
  'spec_name' => 'video > track[kind=subtitles]',
7363
  ),
7364
-
7365
  ),
7366
  array(
7367
  'attr_spec_list' => array(
@@ -7378,22 +10017,19 @@ class AMP_Allowed_Tags_Generated {
7378
  'src' => array(
7379
  'blacklisted_value_regex' => '__amp_source_origin',
7380
  'mandatory' => true,
7381
- 'allow_relative' => false,
7382
- 'allowed_protocol' => array(
7383
- 'https',
 
 
7384
  ),
7385
  ),
7386
  'srclang' => array(),
7387
  ),
7388
  'tag_spec' => array(
7389
- 'html_format' => array(
7390
- 'amp',
7391
- 'amp4ads',
7392
- ),
7393
  'mandatory_parent' => 'amp-audio',
7394
  'spec_name' => 'amp-audio > track',
7395
  ),
7396
-
7397
  ),
7398
  array(
7399
  'attr_spec_list' => array(
@@ -7411,9 +10047,11 @@ class AMP_Allowed_Tags_Generated {
7411
  'src' => array(
7412
  'blacklisted_value_regex' => '__amp_source_origin',
7413
  'mandatory' => true,
7414
- 'allow_relative' => false,
7415
- 'allowed_protocol' => array(
7416
- 'https',
 
 
7417
  ),
7418
  ),
7419
  'srclang' => array(
@@ -7421,14 +10059,9 @@ class AMP_Allowed_Tags_Generated {
7421
  ),
7422
  ),
7423
  'tag_spec' => array(
7424
- 'html_format' => array(
7425
- 'amp',
7426
- 'amp4ads',
7427
- ),
7428
  'mandatory_parent' => 'amp-audio',
7429
  'spec_name' => 'amp-audio > track[kind=subtitles]',
7430
  ),
7431
-
7432
  ),
7433
  array(
7434
  'attr_spec_list' => array(
@@ -7445,22 +10078,19 @@ class AMP_Allowed_Tags_Generated {
7445
  'src' => array(
7446
  'blacklisted_value_regex' => '__amp_source_origin',
7447
  'mandatory' => true,
7448
- 'allow_relative' => false,
7449
- 'allowed_protocol' => array(
7450
- 'https',
 
 
7451
  ),
7452
  ),
7453
  'srclang' => array(),
7454
  ),
7455
  'tag_spec' => array(
7456
- 'html_format' => array(
7457
- 'amp',
7458
- 'amp4ads',
7459
- ),
7460
  'mandatory_parent' => 'amp-video',
7461
  'spec_name' => 'amp-video > track',
7462
  ),
7463
-
7464
  ),
7465
  array(
7466
  'attr_spec_list' => array(
@@ -7478,9 +10108,11 @@ class AMP_Allowed_Tags_Generated {
7478
  'src' => array(
7479
  'blacklisted_value_regex' => '__amp_source_origin',
7480
  'mandatory' => true,
7481
- 'allow_relative' => false,
7482
- 'allowed_protocol' => array(
7483
- 'https',
 
 
7484
  ),
7485
  ),
7486
  'srclang' => array(
@@ -7488,14 +10120,9 @@ class AMP_Allowed_Tags_Generated {
7488
  ),
7489
  ),
7490
  'tag_spec' => array(
7491
- 'html_format' => array(
7492
- 'amp',
7493
- 'amp4ads',
7494
- ),
7495
  'mandatory_parent' => 'amp-video',
7496
  'spec_name' => 'amp-video > track[kind=subtitles]',
7497
  ),
7498
-
7499
  ),
7500
  array(
7501
  'attr_spec_list' => array(
@@ -7513,9 +10140,11 @@ class AMP_Allowed_Tags_Generated {
7513
  'src' => array(
7514
  'blacklisted_value_regex' => '__amp_source_origin',
7515
  'mandatory' => true,
7516
- 'allow_relative' => false,
7517
- 'allowed_protocol' => array(
7518
- 'https',
 
 
7519
  ),
7520
  ),
7521
  'srclang' => array(
@@ -7523,15 +10152,10 @@ class AMP_Allowed_Tags_Generated {
7523
  ),
7524
  ),
7525
  'tag_spec' => array(
7526
- 'html_format' => array(
7527
- 'amp',
7528
- 'amp4ads',
7529
- ),
7530
  'mandatory_parent' => 'amp-ima-video',
7531
  'spec_name' => 'amp-ima-video > track[kind=subtitles]',
7532
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ima-video',
7533
  ),
7534
-
7535
  ),
7536
  ),
7537
  'tref' => array(
@@ -7610,11 +10234,13 @@ class AMP_Allowed_Tags_Generated {
7610
  'alternative_names' => array(
7611
  'href',
7612
  ),
7613
- 'allow_empty' => false,
7614
- 'allow_relative' => true,
7615
- 'allowed_protocol' => array(
7616
- 'http',
7617
- 'https',
 
 
7618
  ),
7619
  ),
7620
  'xlink:role' => array(),
@@ -7627,14 +10253,9 @@ class AMP_Allowed_Tags_Generated {
7627
  'xmlns:xlink' => array(),
7628
  ),
7629
  'tag_spec' => array(
7630
- 'html_format' => array(
7631
- 'amp',
7632
- 'amp4ads',
7633
- ),
7634
  'mandatory_ancestor' => 'svg',
7635
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7636
  ),
7637
-
7638
  ),
7639
  ),
7640
  'tspan' => array(
@@ -7720,39 +10341,27 @@ class AMP_Allowed_Tags_Generated {
7720
  'y' => array(),
7721
  ),
7722
  'tag_spec' => array(
7723
- 'html_format' => array(
7724
- 'amp',
7725
- 'amp4ads',
7726
- ),
7727
  'mandatory_ancestor' => 'svg',
7728
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7729
  ),
7730
-
7731
  ),
7732
  ),
7733
  'tt' => array(
7734
  array(
7735
  'attr_spec_list' => array(),
7736
- 'tag_spec' => array(
7737
- 'html_format' => array(
7738
- 'amp',
7739
- ),
7740
- ),
7741
-
7742
  ),
7743
  ),
7744
  'u' => array(
7745
  array(
7746
  'attr_spec_list' => array(),
7747
  'tag_spec' => array(),
7748
-
7749
  ),
7750
  ),
7751
  'ul' => array(
7752
  array(
7753
  'attr_spec_list' => array(),
7754
  'tag_spec' => array(),
7755
-
7756
  ),
7757
  ),
7758
  'use' => array(
@@ -7835,11 +10444,13 @@ class AMP_Allowed_Tags_Generated {
7835
  'alternative_names' => array(
7836
  'href',
7837
  ),
7838
- 'allow_empty' => false,
7839
- 'allow_relative' => true,
7840
- 'allowed_protocol' => array(
7841
- 'http',
7842
- 'https',
 
 
7843
  ),
7844
  ),
7845
  'xlink:role' => array(),
@@ -7853,21 +10464,15 @@ class AMP_Allowed_Tags_Generated {
7853
  'y' => array(),
7854
  ),
7855
  'tag_spec' => array(
7856
- 'html_format' => array(
7857
- 'amp',
7858
- 'amp4ads',
7859
- ),
7860
  'mandatory_ancestor' => 'svg',
7861
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7862
  ),
7863
-
7864
  ),
7865
  ),
7866
  'var' => array(
7867
  array(
7868
  'attr_spec_list' => array(),
7869
  'tag_spec' => array(),
7870
-
7871
  ),
7872
  ),
7873
  'video' => array(
@@ -7883,23 +10488,21 @@ class AMP_Allowed_Tags_Generated {
7883
  'preload' => array(),
7884
  'src' => array(
7885
  'blacklisted_value_regex' => '__amp_source_origin',
7886
- 'allow_relative' => false,
7887
- 'allowed_protocol' => array(
7888
- 'data',
7889
- 'https',
 
 
7890
  ),
7891
  ),
7892
  'width' => array(),
7893
  ),
7894
  'tag_spec' => array(
7895
- 'html_format' => array(
7896
- 'amp',
7897
- ),
7898
  'mandatory_ancestor' => 'noscript',
7899
  'mandatory_ancestor_suggested_alternative' => 'amp-video',
7900
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
7901
  ),
7902
-
7903
  ),
7904
  ),
7905
  'view' => array(
@@ -7919,14 +10522,9 @@ class AMP_Allowed_Tags_Generated {
7919
  'zoomandpan' => array(),
7920
  ),
7921
  'tag_spec' => array(
7922
- 'html_format' => array(
7923
- 'amp',
7924
- 'amp4ads',
7925
- ),
7926
  'mandatory_ancestor' => 'svg',
7927
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7928
  ),
7929
-
7930
  ),
7931
  ),
7932
  'vkern' => array(
@@ -7946,32 +10544,21 @@ class AMP_Allowed_Tags_Generated {
7946
  'xmlns:xlink' => array(),
7947
  ),
7948
  'tag_spec' => array(
7949
- 'html_format' => array(
7950
- 'amp',
7951
- 'amp4ads',
7952
- ),
7953
  'mandatory_ancestor' => 'svg',
7954
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
7955
  ),
7956
-
7957
  ),
7958
  ),
7959
  'wbr' => array(
7960
  array(
7961
  'attr_spec_list' => array(),
7962
  'tag_spec' => array(),
7963
-
7964
  ),
7965
  ),
7966
  'xmp' => array(
7967
  array(
7968
  'attr_spec_list' => array(),
7969
- 'tag_spec' => array(
7970
- 'html_format' => array(
7971
- 'amp',
7972
- ),
7973
- ),
7974
-
7975
  ),
7976
  ),
7977
  );
@@ -7984,7 +10571,6 @@ class AMP_Allowed_Tags_Generated {
7984
  'layout' => array(),
7985
  'sizes' => array(),
7986
  'width' => array(),
7987
-
7988
  );
7989
 
7990
 
@@ -8040,6 +10626,9 @@ class AMP_Allowed_Tags_Generated {
8040
  'amp-access-show' => array(),
8041
  'amp-access-style' => array(),
8042
  'amp-access-template' => array(),
 
 
 
8043
  'aria-activedescendant' => array(),
8044
  'aria-atomic' => array(),
8045
  'aria-autocomplete' => array(),
@@ -8077,21 +10666,21 @@ class AMP_Allowed_Tags_Generated {
8077
  'aria-valuenow' => array(),
8078
  'aria-valuetext' => array(),
8079
  'class' => array(
8080
- 'blacklisted_value_regex' => '(^|\\w)i-amphtml-',
8081
  ),
8082
  'content' => array(),
8083
  'datatype' => array(),
8084
  'dir' => array(),
8085
  'draggable' => array(),
8086
  'fallback' => array(
8087
- 'value' => '',
8088
  ),
8089
  'hidden' => array(
8090
- 'value' => '',
8091
  ),
8092
  'i-amp-access-id' => array(),
8093
  'id' => array(
8094
- 'blacklisted_value_regex' => '(^|\\s)(__amp_\\s*|__count__|__definegetter__|__definesetter__|__lookupgetter__|__lookupsetter__|__nosuchmethod__|__parent__|__proto__|__amp_\\s*|\\$p|\\$proxy|acceptcharset|addeventlistener|appendchild|assignedslot|attachshadow|amp|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|i-amphtml-\\s*|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|$)',
8095
  ),
8096
  'inlist' => array(),
8097
  'itemid' => array(),
@@ -8104,40 +10693,84 @@ class AMP_Allowed_Tags_Generated {
8104
  'on' => array(),
8105
  'overflow' => array(),
8106
  'placeholder' => array(
8107
- 'value' => '',
8108
  ),
8109
  'prefix' => array(),
8110
  'property' => array(),
8111
  'rel' => array(
8112
- 'blacklisted_value_regex' => '(^|\\s)(canonical|components|dns-prefetch|import|manifest|preconnect|preload|prerender|serviceworker|stylesheet|subresource)(\\s|$)',
8113
  ),
8114
  'resource' => array(),
8115
  'rev' => array(),
8116
  'role' => array(),
 
 
 
 
 
 
 
 
 
 
 
 
8117
  'tabindex' => array(),
8118
  'title' => array(),
8119
  'translate' => array(),
8120
  'typeof' => array(),
8121
  'validation-for' => array(),
8122
  'visible-when-invalid' => array(
8123
- 'value_regex' => '(badinput|customerror|patternmismatch|rangeoverflow|rangeunderflow|stepmismatch|toolong|typemismatch|valuemissing)',
8124
  ),
8125
  'vocab' => array(),
8126
-
8127
  );
8128
 
 
 
 
 
 
 
 
8129
  public static function get_allowed_tags() {
8130
  return self::$allowed_tags;
8131
  }
8132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8133
  public static function get_allowed_attributes() {
8134
  return self::$globally_allowed_attrs;
8135
  }
8136
 
 
 
 
 
 
 
8137
  public static function get_layout_attributes() {
8138
  return self::$layout_allowed_attrs;
8139
  }
8140
 
8141
  }
8142
-
8143
- ?>
8
  * Note: This file only contains tags that are relevant to the `body` of
9
  * an AMP page. To include additional elements modify the variable
10
  * `mandatory_parent_blacklist` in the amp_wp_build.py script.
11
+ *
12
+ * phpcs:ignoreFile
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(
25
  'download' => array(),
26
  'href' => array(
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',
41
+ 'line',
42
+ 'skype',
43
+ 'sms',
44
+ 'snapchat',
45
+ 'tel',
46
+ 'tg',
47
+ 'threema',
48
+ 'twitter',
49
+ 'viber',
50
+ 'whatsapp',
51
+ ),
52
  ),
53
  ),
54
  'hreflang' => array(),
55
  'media' => array(),
56
  'name' => array(),
57
+ 'referrerpolicy' => array(),
58
  'rel' => array(
59
  'blacklisted_value_regex' => '(^|\\s)(components|dns-prefetch|import|manifest|preconnect|prefetch|preload|prerender|serviceworker|stylesheet|subresource|)(\\s|$)',
60
  ),
68
  ),
69
  ),
70
  'tag_spec' => array(
 
 
 
 
71
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#links',
72
  ),
 
73
  ),
74
  ),
75
  'abbr' => array(
76
  array(
77
  'attr_spec_list' => array(),
78
  'tag_spec' => array(),
 
79
  ),
80
  ),
81
  'acronym' => array(
82
  array(
83
  'attr_spec_list' => array(),
84
+ 'tag_spec' => array(),
 
 
 
 
 
85
  ),
86
  ),
87
  'address' => array(
88
  array(
89
  'attr_spec_list' => array(),
90
  'tag_spec' => array(),
 
91
  ),
92
  ),
93
  'amp-3q-player' => array(
105
  ),
106
  ),
107
  'tag_spec' => array(
108
+ 'requires_extension' => array(
109
+ 'amp-3q-player',
110
  ),
111
  ),
 
112
  ),
113
  ),
114
  'amp-accordion' => 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
+ ),
128
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-accordion',
129
  ),
 
130
  ),
131
  ),
132
  'amp-ad' => 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
  ),
150
  ),
151
+ 'template' => array(),
152
  'type' => array(
153
  'mandatory' => true,
154
  ),
155
  ),
156
  'tag_spec' => array(
157
+ 'also_requires_tag_warning' => array(
158
+ 'amp-ad extension .js script',
159
+ ),
160
  'disallowed_ancestor' => array(
161
+ 'amp-app-banner',
162
  ),
163
+ 'requires_extension' => array(
164
+ 'amp-ad',
165
  ),
166
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
167
  ),
 
168
  ),
169
  array(
170
  'attr_spec_list' => array(
171
  'alt' => array(),
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
  ),
191
  ),
192
  'type' => array(
194
  ),
195
  ),
196
  'tag_spec' => array(
197
+ 'also_requires_tag_warning' => array(
198
+ 'amp-ad extension .js script',
199
+ ),
200
  'disallowed_ancestor' => array(
201
+ 'amp-app-banner',
202
+ 'amp-carousel',
203
+ 'amp-fx-flying-carpet',
204
+ 'amp-lightbox',
205
+ 'amp-sticky-ad',
206
  ),
207
+ 'requires_extension' => array(
208
+ 'amp-ad',
209
  ),
210
  'spec_name' => 'amp-ad with data-multi-size attribute',
211
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
212
  ),
 
213
  ),
 
 
214
  array(
215
  'attr_spec_list' => array(
216
+ 'alt' => array(),
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
+ ),
235
+ ),
236
+ 'type' => array(
237
+ 'mandatory' => true,
238
+ ),
239
  ),
240
  'tag_spec' => array(
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',
247
+ 'amp-lightbox',
248
+ ),
249
+ 'requires_extension' => array(
250
+ 'amp-ad',
251
  ),
252
+ 'spec_name' => 'amp-ad with data-enable-refresh attribute',
253
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
254
  ),
 
255
  ),
256
  ),
257
  'amp-analytics' => array(
259
  'attr_spec_list' => array(
260
  'config' => array(
261
  'blacklisted_value_regex' => '__amp_source_origin',
262
+ 'value_url' => array(
263
+ 'allow_empty' => true,
264
+ 'allow_relative' => true,
265
+ 'allowed_protocol' => array(
266
+ 'https',
267
+ ),
268
  ),
269
  ),
270
  'type' => array(),
271
  ),
272
  'tag_spec' => array(
273
+ 'requires_extension' => array(
274
+ 'amp-analytics',
 
275
  ),
276
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-analytics',
277
  ),
 
278
  ),
279
  ),
280
  'amp-anim' => array(
293
  ),
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',
302
+ ),
303
  ),
304
  ),
305
  ),
306
  'tag_spec' => array(
307
+ 'requires_extension' => array(
308
+ 'amp-anim',
 
309
  ),
310
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-anim',
311
  ),
 
312
  ),
313
  ),
314
  'amp-animation' => array(
323
  ),
324
  ),
325
  'tag_spec' => array(
326
+ 'requires_extension' => array(
327
+ 'amp-animation',
 
328
  ),
329
  ),
 
330
  ),
331
  ),
332
  'amp-apester-media' => array(
333
  array(
334
  'attr_spec_list' => array(
335
  'data-apester-channel-token' => array(
336
+ 'value_regex' => '[0-9a-zA-Z]+',
337
  ),
338
  'data-apester-media-id' => array(
339
+ 'value_regex' => '[0-9a-zA-Z]+',
340
  ),
341
  'media' => array(),
342
  'noloading' => array(
344
  ),
345
  ),
346
  'tag_spec' => array(
347
+ 'requires_extension' => array(
348
+ 'amp-apester-media',
349
  ),
350
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-apester-media',
351
  ),
 
352
  ),
353
  ),
354
  'amp-app-banner' => array(
363
  ),
364
  ),
365
  'tag_spec' => array(
 
 
 
366
  'mandatory_parent' => 'body',
367
+ 'requires_extension' => array(
368
+ 'amp-app-banner',
369
+ ),
370
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-app-banner',
371
  'unique' => true,
372
  ),
 
373
  ),
374
  ),
375
  'amp-audio' => array(
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
  ),
413
+ 'requires_extension' => array(
414
+ 'amp-audio',
415
  ),
416
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
417
  ),
 
418
  ),
419
  array(
420
  'attr_spec_list' => array(
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',
454
+ ),
455
  'spec_name' => 'amp-story >> amp-audio',
456
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
457
  ),
 
458
  ),
459
+ ),
460
+ 'amp-auto-ads' => array(
461
  array(
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
  ),
476
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-auto-ads',
 
477
  ),
 
478
  ),
479
  ),
480
+ 'amp-beopinion' => array(
481
  array(
482
  'attr_spec_list' => array(
483
+ 'data-account' => array(
484
+ 'mandatory' => true,
485
+ 'value_regex_casei' => '[0-9a-f]{24}',
486
+ ),
487
+ 'data-content' => array(
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
+ ),
503
+ ),
504
+ ),
505
+ ),
506
+ 'amp-bind-macro' => array(
507
+ array(
508
+ 'attr_spec_list' => array(
509
+ 'arguments' => array(),
510
+ 'expression' => array(
511
+ 'mandatory' => true,
512
+ ),
513
+ 'id' => array(
514
  'mandatory' => true,
515
  ),
516
  ),
517
  'tag_spec' => array(
518
+ 'requires_extension' => array(
519
+ 'amp-bind',
520
  ),
521
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-bind',
522
+ ),
523
+ ),
524
+ ),
525
+ 'amp-bodymovin-animation' => array(
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
+ ),
548
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-bodymovin-animation',
549
  ),
 
550
  ),
551
  ),
552
  'amp-brid-player' => array(
576
  ),
577
  ),
578
  'tag_spec' => array(
579
+ 'requires_extension' => array(
580
+ 'amp-brid-player',
581
  ),
582
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-brid-player',
583
  ),
 
584
  ),
585
  ),
586
  'amp-brightcove' => array(
601
  ),
602
  ),
603
  'tag_spec' => array(
604
+ 'requires_extension' => array(
605
+ 'amp-brightcove',
606
  ),
607
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-brightcove',
608
  ),
609
+ ),
610
+ ),
611
+ 'amp-byside-content' => array(
612
+ array(
613
+ 'attr_spec_list' => array(
614
+ 'data-label' => array(
615
+ 'mandatory' => true,
616
+ ),
617
+ 'data-webcare-id' => array(
618
+ 'mandatory' => true,
619
+ ),
620
+ 'media' => array(),
621
+ 'noloading' => array(
622
+ 'value' => '',
623
+ ),
624
+ ),
625
+ 'tag_spec' => array(
626
+ 'requires_extension' => array(
627
+ 'amp-byside-content',
628
+ ),
629
+ ),
630
  ),
631
  ),
632
  'amp-call-tracking' => array(
635
  'config' => array(
636
  'blacklisted_value_regex' => '__amp_source_origin',
637
  'mandatory' => true,
638
+ 'value_url' => array(
639
+ 'allow_relative' => false,
640
+ 'allowed_protocol' => array(
641
+ 'https',
642
+ ),
643
  ),
644
  ),
645
  'media' => array(),
648
  ),
649
  ),
650
  'tag_spec' => array(
651
+ 'requires_extension' => array(
652
+ 'amp-call-tracking',
653
  ),
654
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-call-tracking',
655
  ),
 
656
  ),
657
  ),
658
  'amp-carousel' => array(
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
  ),
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
+ ),
700
+ ),
701
+ 'amp-consent' => array(
702
+ array(
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
+ ),
713
+ 'unique' => true,
714
+ ),
715
  ),
716
  ),
717
  'amp-dailymotion' => array(
749
  ),
750
  ),
751
  'tag_spec' => array(
752
+ 'requires_extension' => array(
753
+ 'amp-dailymotion',
754
  ),
755
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-dailymotion',
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
+ ),
769
+ 'first-day-of-week' => array(
770
+ 'value_regex' => '[0-6]',
771
+ ),
772
+ 'format' => array(),
773
+ 'fullscreen' => array(
774
+ 'value' => '',
775
+ ),
776
+ 'highlighted' => array(),
777
+ 'input-selector' => array(),
778
+ 'locale' => array(),
779
+ 'max' => array(),
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
  ),
816
+ 'spec_name' => 'amp-date-picker[type=single][mode=static]',
 
 
 
817
  ),
 
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
+ ),
828
+ 'first-day-of-week' => array(
829
+ 'value_regex' => '[0-6]',
830
+ ),
831
+ 'format' => array(),
832
+ 'highlighted' => array(),
833
+ 'input-selector' => array(),
834
+ 'locale' => array(),
835
+ 'max' => array(),
836
  'media' => array(),
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
  ),
873
+ 'spec_name' => 'amp-date-picker[type=single][mode=overlay]',
 
874
  ),
 
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
+ ),
934
+ 'spec_name' => 'amp-date-picker[type=range][mode=static]',
935
+ ),
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]',
949
+ ),
950
+ 'format' => array(),
951
+ 'highlighted' => array(),
952
+ 'locale' => array(),
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
+ ),
993
+ 'spec_name' => 'amp-date-picker[type=range][mode=overlay]',
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
+ ),
1009
+ 'amp-embed' => array(
1010
+ array(
1011
+ 'attr_spec_list' => array(
1012
+ 'alt' => array(),
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
+ ),
1027
+ ),
1028
+ 'template' => array(),
1029
+ 'type' => array(
1030
+ 'mandatory' => true,
1031
+ ),
1032
+ ),
1033
+ 'tag_spec' => array(
1034
+ 'also_requires_tag_warning' => array(
1035
+ 'amp-ad extension .js script',
1036
+ ),
1037
+ 'disallowed_ancestor' => array(
1038
+ 'amp-app-banner',
1039
+ ),
1040
+ 'requires_extension' => array(
1041
+ 'amp-ad',
1042
+ ),
1043
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
1044
+ ),
1045
+ ),
1046
+ array(
1047
+ 'attr_spec_list' => array(
1048
+ 'alt' => array(),
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
+ ),
1068
+ ),
1069
+ 'type' => array(
1070
+ 'mandatory' => true,
1071
+ ),
1072
+ ),
1073
+ 'tag_spec' => array(
1074
+ 'also_requires_tag_warning' => array(
1075
+ 'amp-ad extension .js script',
1076
+ ),
1077
+ 'disallowed_ancestor' => array(
1078
+ 'amp-app-banner',
1079
+ 'amp-carousel',
1080
+ 'amp-fx-flying-carpet',
1081
+ 'amp-lightbox',
1082
+ 'amp-sticky-ad',
1083
+ ),
1084
+ 'requires_extension' => array(
1085
+ 'amp-ad',
1086
+ ),
1087
+ 'spec_name' => 'amp-embed with data-multi-size attribute',
1088
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
1089
+ ),
1090
+ ),
1091
+ ),
1092
+ 'amp-experiment' => array(
1093
+ array(
1094
+ 'attr_spec_list' => array(),
1095
+ 'tag_spec' => array(
1096
+ 'requires_extension' => array(
1097
+ 'amp-experiment',
1098
+ ),
1099
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-experiment',
1100
+ 'unique' => true,
1101
+ ),
1102
+ ),
1103
+ ),
1104
+ 'amp-facebook' => array(
1105
+ array(
1106
+ 'attr_spec_list' => array(
1107
+ 'data-href' => array(
1108
+ 'mandatory' => true,
1109
+ ),
1110
+ 'media' => array(),
1111
+ 'noloading' => array(
1112
+ 'value' => '',
1113
+ ),
1114
+ ),
1115
+ 'tag_spec' => array(
1116
+ 'requires_extension' => array(
1117
+ 'amp-facebook',
1118
+ ),
1119
+ ),
1120
+ ),
1121
  ),
1122
  'amp-facebook-comments' => array(
1123
  array(
1131
  ),
1132
  ),
1133
  'tag_spec' => array(
1134
+ 'requires_extension' => array(
1135
+ 'amp-facebook-comments',
1136
  ),
1137
  ),
 
1138
  ),
1139
  ),
1140
  'amp-facebook-like' => array(
1142
  'attr_spec_list' => array(
1143
  'data-href' => array(
1144
  'mandatory' => true,
1145
+ 'value_url' => array(
1146
+ 'allow_relative' => false,
1147
+ 'allowed_protocol' => array(
1148
+ 'http',
1149
+ 'https',
1150
+ ),
1151
  ),
1152
  ),
1153
  'media' => array(),
1156
  ),
1157
  ),
1158
  'tag_spec' => array(
1159
+ 'requires_extension' => array(
1160
+ 'amp-facebook-like',
1161
+ ),
1162
+ ),
1163
+ ),
1164
+ ),
1165
+ 'amp-facebook-page' => array(
1166
+ array(
1167
+ 'attr_spec_list' => array(
1168
+ 'data-href' => array(
1169
+ 'mandatory' => true,
1170
+ 'value_url' => array(
1171
+ 'allow_relative' => false,
1172
+ 'allowed_protocol' => array(
1173
+ 'http',
1174
+ 'https',
1175
+ ),
1176
+ ),
1177
+ ),
1178
+ 'media' => array(),
1179
+ 'noloading' => array(
1180
+ 'value' => '',
1181
+ ),
1182
+ ),
1183
+ 'tag_spec' => array(
1184
+ 'requires_extension' => array(
1185
+ 'amp-facebook-page',
1186
  ),
1187
  ),
 
1188
  ),
1189
  ),
1190
  'amp-fit-text' => array(
1197
  'value' => '',
1198
  ),
1199
  ),
1200
+ 'tag_spec' => array(
1201
+ 'requires_extension' => array(
1202
+ 'amp-fit-text',
1203
+ ),
1204
+ ),
1205
  ),
1206
  ),
1207
  'amp-font' => array(
1226
  ),
1227
  ),
1228
  'tag_spec' => array(
1229
+ 'requires_extension' => array(
1230
+ 'amp-font',
 
1231
  ),
1232
  ),
 
1233
  ),
1234
  ),
1235
  'amp-fx-flying-carpet' => array(
1244
  ),
1245
  ),
1246
  'tag_spec' => array(
1247
+ 'requires_extension' => array(
1248
+ 'amp-fx-flying-carpet',
1249
  ),
1250
  ),
 
1251
  ),
1252
  ),
1253
  'amp-gfycat' => array(
1265
  ),
1266
  ),
1267
  'tag_spec' => array(
1268
+ 'requires_extension' => array(
1269
+ 'amp-gfycat',
1270
  ),
1271
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-gfycat',
1272
  ),
 
1273
  ),
1274
  ),
1275
  'amp-gist' => array(
1284
  ),
1285
  ),
1286
  'tag_spec' => array(
1287
+ 'requires_extension' => array(
1288
+ 'amp-gist',
1289
  ),
1290
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-gist',
1291
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1292
  ),
1293
  ),
1294
  'amp-hulu' => array(
1303
  ),
1304
  ),
1305
  'tag_spec' => array(
1306
+ 'requires_extension' => array(
1307
+ 'amp-hulu',
1308
  ),
1309
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-hulu',
1310
  ),
 
1311
  ),
1312
  ),
1313
  'amp-iframe' => array(
1314
  array(
1315
  'attr_spec_list' => array(
1316
  '[src]' => array(),
1317
+ 'allow' => array(),
 
 
1318
  'allowfullscreen' => array(
1319
  'value' => '',
1320
  ),
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
+ ),
1350
  ),
1351
  ),
1352
  'srcdoc' => array(),
1353
  ),
1354
  'tag_spec' => array(
1355
+ 'requires_extension' => array(
1356
+ 'amp-iframe',
1357
  ),
1358
  ),
 
1359
  ),
1360
  ),
1361
  'amp-ima-video' => array(
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
  ),
1375
  ),
1376
  'data-tag' => array(
1377
  'mandatory' => true,
1378
+ 'value_url' => array(
1379
+ 'allow_relative' => true,
1380
+ 'allowed_protocol' => array(
1381
+ 'https',
1382
+ ),
1383
  ),
1384
  ),
1385
  'media' => array(),
1388
  ),
1389
  ),
1390
  'tag_spec' => array(
1391
+ 'requires_extension' => array(
1392
+ 'amp-ima-video',
1393
  ),
1394
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ima-video',
1395
  ),
 
1396
  ),
1397
  ),
1398
  'amp-image-lightbox' => array(
1405
  ),
1406
  ),
1407
  'tag_spec' => array(
1408
+ 'requires_extension' => array(
1409
+ 'amp-image-lightbox',
1410
  ),
1411
  ),
 
1412
  ),
1413
  ),
1414
  'amp-img' => array(
1420
  '[srcset]' => array(),
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' => '',
1438
  ),
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',
1447
+ ),
1448
  ),
1449
  ),
1450
  ),
1451
  'tag_spec' => array(
 
 
 
 
1452
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
1453
  ),
 
1454
  ),
1455
  ),
1456
  'amp-imgur' => array(
1465
  ),
1466
  ),
1467
  'tag_spec' => array(
1468
+ 'requires_extension' => array(
1469
+ 'amp-imgur',
1470
  ),
1471
  ),
 
1472
  ),
1473
  ),
1474
  'amp-instagram' => array(
1484
  ),
1485
  ),
1486
  'tag_spec' => array(
1487
+ 'requires_extension' => array(
1488
+ 'amp-instagram',
1489
  ),
1490
  ),
 
1491
  ),
1492
  ),
1493
  'amp-install-serviceworker' => array(
1495
  'attr_spec_list' => array(
1496
  'data-iframe-src' => array(
1497
  'blacklisted_value_regex' => '__amp_source_origin',
1498
+ 'value_url' => array(
1499
+ 'allow_relative' => true,
1500
+ 'allowed_protocol' => array(
1501
+ 'https',
1502
+ ),
1503
  ),
1504
  ),
1505
  'src' => array(
1506
  'blacklisted_value_regex' => '__amp_source_origin',
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
  ),
1520
  ),
 
1521
  ),
1522
  ),
1523
  'amp-izlesene' => array(
1533
  ),
1534
  ),
1535
  'tag_spec' => array(
1536
+ 'requires_extension' => array(
1537
+ 'amp-izlesene',
1538
  ),
1539
  ),
 
1540
  ),
1541
  ),
1542
  'amp-jwplayer' => array(
1554
  ),
1555
  ),
1556
  'tag_spec' => array(
1557
+ 'requires_extension' => array(
1558
+ 'amp-jwplayer',
1559
  ),
1560
  ),
 
1561
  ),
1562
  ),
1563
  'amp-kaltura-player' => array(
1572
  ),
1573
  ),
1574
  'tag_spec' => array(
1575
+ 'requires_extension' => array(
1576
+ 'amp-kaltura-player',
1577
  ),
1578
  ),
 
1579
  ),
1580
  ),
1581
  'amp-layout' => array(
1587
  ),
1588
  ),
1589
  'tag_spec' => array(
 
 
 
 
1590
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-layout',
1591
  ),
 
1592
  ),
1593
  ),
1594
  'amp-lightbox' => array(
1603
  'scrollable' => array(),
1604
  ),
1605
  'tag_spec' => array(
1606
+ 'requires_extension' => array(
1607
+ 'amp-lightbox',
1608
  ),
1609
  ),
 
1610
  ),
1611
  ),
1612
  'amp-list' => 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
  ),
1637
  ),
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(
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
+ ),
1724
+ 'amp-nexxtv-player' => array(
1725
+ array(
1726
+ 'attr_spec_list' => array(
1727
+ 'data-client' => array(
1728
+ 'mandatory' => true,
1729
+ ),
1730
+ 'data-mediaid' => array(
1731
  'mandatory' => true,
1732
  'value_regex' => '[^=/?:]+',
1733
  ),
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
  ),
1752
  ),
1753
  ),
1754
  'tag_spec' => array(
1755
+ 'requires_extension' => array(
1756
+ 'amp-nexxtv-player',
1757
  ),
1758
  ),
 
1759
  ),
1760
  ),
1761
  'amp-o2-player' => array(
1773
  ),
1774
  ),
1775
  'tag_spec' => array(
1776
+ 'requires_extension' => array(
1777
+ 'amp-o2-player',
1778
  ),
1779
  ),
 
1780
  ),
1781
  ),
1782
  'amp-ooyala-player' => array(
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(
1809
+ 'alt' => array(),
1810
  'data-do' => array(
1811
  'mandatory' => true,
1812
  ),
1816
  ),
1817
  ),
1818
  'tag_spec' => array(
1819
+ 'requires_extension' => array(
1820
+ 'amp-pinterest',
1821
  ),
1822
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-pinterest',
1823
  ),
 
1824
  ),
1825
  ),
1826
  'amp-pixel' => array(
1827
  array(
1828
  'attr_spec_list' => array(
1829
+ 'allow-ssr-img' => array(),
1830
  'media' => array(),
1831
  'noloading' => array(
1832
  'value' => '',
1837
  'src' => array(
1838
  'blacklisted_value_regex' => '__amp_source_origin',
1839
  'mandatory' => true,
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
  ),
1853
  ),
1854
  'amp-playbuzz' => array(
1871
  'src' => array(),
1872
  ),
1873
  'tag_spec' => array(
1874
+ 'requires_extension' => array(
1875
+ 'amp-playbuzz',
1876
  ),
1877
  ),
 
1878
  ),
1879
  ),
1880
  'amp-position-observer' => array(
1893
  ),
1894
  ),
1895
  'tag_spec' => array(
1896
+ 'requires_extension' => array(
1897
+ 'amp-position-observer',
 
1898
  ),
1899
  ),
 
1900
  ),
1901
  ),
1902
  'amp-reach-player' => array(
1912
  ),
1913
  ),
1914
  'tag_spec' => array(
1915
+ 'requires_extension' => array(
1916
+ 'amp-reach-player',
1917
  ),
1918
  ),
 
1919
  ),
1920
  ),
1921
  'amp-reddit' => array(
1940
  ),
1941
  ),
1942
  'tag_spec' => array(
1943
+ 'requires_extension' => array(
1944
+ 'amp-reddit',
1945
  ),
1946
  ),
1947
+ ),
1948
+ ),
1949
+ 'amp-riddle-quiz' => array(
1950
+ array(
1951
+ 'attr_spec_list' => array(
1952
+ 'data-riddle-id' => array(
1953
+ 'mandatory' => true,
1954
+ 'value_regex' => '[0-9]+',
1955
+ ),
1956
+ 'media' => array(),
1957
+ 'noloading' => array(
1958
+ 'value' => '',
1959
+ ),
1960
+ ),
1961
+ 'tag_spec' => array(
1962
+ 'requires_extension' => array(
1963
+ 'amp-riddle-quiz',
1964
+ ),
1965
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-riddle-quiz',
1966
+ ),
1967
  ),
1968
  ),
1969
  'amp-selector' => array(
1970
  array(
1971
  'attr_spec_list' => array(
1972
+ '[disabled]' => array(),
1973
  '[selected]' => array(),
1974
  'disabled' => array(
1975
  'value' => '',
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' => '',
1991
  ),
1992
  'tag_spec' => array(
1993
  'disallowed_ancestor' => array(
1994
+ 'amp-selector',
1995
  ),
1996
+ 'requires_extension' => array(
1997
+ 'amp-selector',
1998
  ),
1999
  ),
 
2000
  ),
2001
  ),
2002
  'amp-sidebar' => array(
2011
  ),
2012
  ),
2013
  'tag_spec' => array(
 
 
 
2014
  'mandatory_parent' => 'body',
2015
+ 'requires_extension' => array(
2016
+ 'amp-sidebar',
2017
+ ),
2018
  ),
 
2019
  ),
2020
  ),
2021
  'amp-social-share' => array(
2023
  'attr_spec_list' => array(
2024
  'data-share-endpoint' => array(
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',
2032
+ 'mailto',
2033
+ 'bbmi',
2034
+ 'fb-messenger',
2035
+ 'intent',
2036
+ 'line',
2037
+ 'skype',
2038
+ 'sms',
2039
+ 'snapchat',
2040
+ 'tel',
2041
+ 'tg',
2042
+ 'threema',
2043
+ 'viber',
2044
+ 'whatsapp',
2045
+ ),
2046
  ),
2047
  ),
2048
  'media' => array(),
2054
  ),
2055
  ),
2056
  'tag_spec' => array(
2057
+ 'requires_extension' => array(
2058
+ 'amp-social-share',
 
2059
  ),
2060
  ),
 
2061
  ),
2062
  ),
2063
  'amp-soundcloud' => array(
2070
  'value_regex' => '[0-9]+',
2071
  ),
2072
  'data-secret-token' => array(
2073
+ 'value_regex' => '[A-Za-z0-9_-]+',
2074
  ),
2075
  'data-trackid' => array(
2076
  'value_regex' => '[0-9]+',
2084
  ),
2085
  ),
2086
  'tag_spec' => array(
2087
+ 'requires_extension' => array(
2088
+ 'amp-soundcloud',
2089
  ),
2090
  ),
 
2091
  ),
2092
  ),
2093
  'amp-springboard-player' => array(
2120
  ),
2121
  ),
2122
  'tag_spec' => array(
2123
+ 'requires_extension' => array(
2124
+ 'amp-springboard-player',
2125
  ),
2126
  ),
 
2127
  ),
2128
  ),
2129
  'amp-state' => array(
2134
  'id' => array(
2135
  'mandatory' => true,
2136
  ),
2137
+ 'overridable' => array(),
2138
  'src' => array(
2139
  'blacklisted_value_regex' => '__amp_source_origin',
2140
+ 'value_url' => array(
2141
+ 'allow_relative' => true,
2142
+ 'allowed_protocol' => array(
2143
+ 'https',
2144
+ ),
2145
  ),
2146
  ),
2147
  ),
2148
  'tag_spec' => array(
2149
+ 'requires_extension' => array(
2150
+ 'amp-bind',
2151
  ),
2152
  'spec_name' => 'amp-state',
2153
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-bind',
2154
  ),
 
2155
  ),
2156
  ),
2157
  'amp-sticky-ad' => array(
2164
  ),
2165
  'tag_spec' => array(
2166
  'disallowed_ancestor' => array(
2167
+ 'amp-app-banner',
2168
  ),
2169
+ 'requires_extension' => array(
2170
+ 'amp-sticky-ad',
2171
  ),
2172
  'unique' => true,
2173
  ),
 
2174
  ),
2175
  ),
2176
  'amp-story' => array(
2177
  array(
2178
  'attr_spec_list' => array(
2179
  'background-audio' => array(
2180
+ 'value_url' => array(
2181
+ 'allowed_protocol' => array(
2182
+ 'http',
2183
+ 'https',
2184
+ ),
2185
  ),
2186
  ),
2187
  'bookend-config-src' => array(
2188
+ 'value_url' => array(
2189
+ 'allowed_protocol' => array(
2190
+ 'http',
2191
+ 'https',
2192
+ ),
2193
  ),
2194
  ),
2195
  'standalone' => array(
2198
  ),
2199
  ),
2200
  'tag_spec' => array(
 
 
 
2201
  'mandatory_parent' => 'body',
2202
+ 'requires_extension' => array(
2203
+ 'amp-story',
2204
+ ),
2205
+ ),
2206
+ ),
2207
+ ),
2208
+ 'amp-story-auto-ads' => array(
2209
+ array(
2210
+ 'attr_spec_list' => array(),
2211
+ 'tag_spec' => array(
2212
+ 'mandatory_parent' => 'amp-story',
2213
+ 'requires_extension' => array(
2214
+ 'amp-story-auto-ads',
2215
+ ),
2216
+ 'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/extensions/amp-story/amp-story-auto-ads.md',
2217
+ 'unique' => true,
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
  ),
2229
  'amp-story-grid-layer' => array(
2235
  ),
2236
  ),
2237
  'tag_spec' => array(
 
 
 
2238
  'mandatory_ancestor' => 'amp-story-page',
2239
  ),
 
2240
  ),
2241
  ),
2242
  'amp-story-page' => array(
2244
  'attr_spec_list' => array(
2245
  'auto-advance-after' => array(),
2246
  'background-audio' => array(
2247
+ 'value_url' => array(
2248
+ 'allowed_protocol' => array(
2249
+ 'http',
2250
+ 'https',
2251
+ ),
2252
  ),
2253
  ),
2254
  'id' => array(
2256
  ),
2257
  ),
2258
  'tag_spec' => array(
 
 
 
2259
  'mandatory_parent' => 'amp-story',
2260
+ 'requires_extension' => array(
2261
+ 'amp-story',
2262
+ ),
2263
  ),
 
2264
  ),
2265
  ),
2266
  'amp-timeago' => array(
2271
  ),
2272
  'datetime' => array(
2273
  'mandatory' => true,
2274
+ '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])',
2275
  ),
2276
  'locale' => array(),
2277
  'media' => array(),
2280
  ),
2281
  ),
2282
  'tag_spec' => array(
2283
+ 'requires_extension' => array(
2284
+ 'amp-timeago',
2285
  ),
2286
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-timeago',
2287
  ),
 
2288
  ),
2289
  ),
2290
  'amp-twitter' => array(
2299
  ),
2300
  ),
2301
  'tag_spec' => array(
2302
+ 'requires_extension' => array(
2303
+ 'amp-twitter',
2304
  ),
2305
  ),
 
2306
  ),
2307
  ),
2308
  'amp-user-notification' => array(
2309
  array(
2310
  'attr_spec_list' => array(
2311
  'data-dismiss-href' => array(
2312
+ 'value_url' => array(
2313
+ 'allow_empty' => false,
2314
+ 'allow_relative' => false,
2315
+ 'allowed_protocol' => array(
2316
+ 'https',
2317
+ ),
2318
  ),
2319
  ),
2320
  'data-show-if-href' => array(
2321
+ 'value_url' => array(
2322
+ 'allow_empty' => false,
2323
+ 'allow_relative' => false,
2324
+ 'allowed_protocol' => array(
2325
+ 'https',
2326
+ ),
2327
  ),
2328
  ),
2329
  'enctype' => array(
2335
  ),
2336
  ),
2337
  'tag_spec' => array(
2338
+ 'requires_extension' => array(
2339
+ 'amp-user-notification',
2340
  ),
2341
  ),
 
2342
  ),
2343
  ),
2344
  'amp-video' => 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
  ),
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
  ),
2405
  ),
2406
  ),
2407
  'tag_spec' => array(
2408
+ 'also_requires_tag_warning' => array(
2409
+ 'amp-video extension .js script',
2410
  ),
2411
+ 'disallowed_ancestor' => array(
2412
+ 'amp-story',
 
2413
  ),
2414
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
2415
  ),
 
2416
  ),
2417
  array(
2418
  'attr_spec_list' => array(
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',
2478
+ ),
2479
  'spec_name' => 'amp-story >> amp-video',
2480
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
2481
  ),
 
2482
  ),
2483
  ),
2484
  'amp-vimeo' => array(
2494
  ),
2495
  ),
2496
  'tag_spec' => array(
2497
+ 'requires_extension' => array(
2498
+ 'amp-vimeo',
2499
  ),
2500
  ),
 
2501
  ),
2502
  ),
2503
  'amp-vine' => array(
2512
  ),
2513
  ),
2514
  'tag_spec' => array(
2515
+ 'requires_extension' => array(
2516
+ 'amp-vine',
2517
  ),
2518
  ),
 
2519
  ),
2520
  ),
2521
  'amp-vk' => array(
2530
  ),
2531
  ),
2532
  'tag_spec' => array(
2533
+ 'requires_extension' => array(
2534
+ 'amp-vk',
2535
  ),
2536
  ),
 
2537
  ),
2538
  ),
2539
  'amp-web-push' => array(
2541
  'attr_spec_list' => array(
2542
  'helper-iframe-url' => array(
2543
  'mandatory' => true,
2544
+ 'value_url' => array(
2545
+ 'allow_relative' => false,
2546
+ 'allowed_protocol' => array(
2547
+ 'https',
2548
+ ),
2549
  ),
2550
  ),
2551
  'id' => array(
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
  ),
2567
  ),
2568
  'service-worker-url' => array(
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
  ),
2582
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-web-push',
2583
  ),
 
2584
  ),
2585
  ),
2586
  'amp-web-push-widget' => array(
2596
  ),
2597
  ),
2598
  'tag_spec' => array(
2599
+ 'requires_extension' => array(
2600
+ 'amp-web-push',
2601
  ),
2602
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-web-push',
2603
  ),
2604
+ ),
2605
+ ),
2606
+ 'amp-wistia-player' => array(
2607
+ array(
2608
+ 'attr_spec_list' => array(
2609
+ 'data-media-hashed-id' => array(
2610
+ 'mandatory' => true,
2611
+ 'value_regex' => '[0-9a-zA-Z]+',
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(
2630
  'credentials' => array(
2631
  'value_regex_casei' => '(include|omit)',
2632
  ),
2633
+ 'data-live-channelid' => array(
2634
+ 'value_regex' => '[^=/?:]+',
2635
+ ),
2636
  'data-videoid' => array(
 
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
+ ),
2655
+ ),
2656
  ),
2657
  ),
2658
  'article' => array(
2659
  array(
2660
  'attr_spec_list' => array(),
2661
  'tag_spec' => array(),
 
2662
  ),
2663
  ),
2664
  'aside' => array(
2665
  array(
2666
  'attr_spec_list' => array(),
2667
  'tag_spec' => array(),
 
2668
  ),
2669
  ),
2670
  'audio' => array(
2677
  'preload' => array(),
2678
  'src' => array(
2679
  'blacklisted_value_regex' => '__amp_source_origin',
2680
+ 'value_url' => array(
2681
+ 'allow_relative' => false,
2682
+ 'allowed_protocol' => array(
2683
+ 'data',
2684
+ 'https',
2685
+ ),
2686
  ),
2687
  ),
2688
  ),
2689
  'tag_spec' => array(
 
 
 
2690
  'mandatory_ancestor' => 'noscript',
2691
  'mandatory_ancestor_suggested_alternative' => 'amp-audio',
2692
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
2693
  ),
 
2694
  ),
2695
  ),
2696
  'b' => array(
2697
  array(
2698
  'attr_spec_list' => array(),
2699
  'tag_spec' => array(),
2700
+ ),
2701
+ ),
2702
+ 'base' => array(
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(
2713
+ 'mandatory_parent' => 'head',
2714
+ 'unique' => true,
2715
+ ),
2716
  ),
2717
  ),
2718
  'bdi' => array(
2719
  array(
2720
  'attr_spec_list' => array(),
2721
  'tag_spec' => array(),
 
2722
  ),
2723
  ),
2724
  'bdo' => array(
2727
  'dir' => array(),
2728
  ),
2729
  'tag_spec' => array(),
 
2730
  ),
2731
  ),
2732
  'big' => array(
2733
  array(
2734
  'attr_spec_list' => array(),
2735
+ 'tag_spec' => array(),
 
 
 
 
 
2736
  ),
2737
  ),
2738
  'blockquote' => array(
2741
  'align' => array(),
2742
  'cite' => array(
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
+ ),
2751
  ),
2752
  ),
2753
  ),
2754
  'tag_spec' => array(),
 
2755
  ),
2756
  ),
2757
  'body' => array(
2763
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
2764
  'unique' => true,
2765
  ),
 
2766
  ),
2767
  ),
2768
  'br' => array(
2769
  array(
2770
  'attr_spec_list' => array(),
2771
  'tag_spec' => array(),
 
2772
  ),
2773
  ),
2774
  'button' => 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|$)',
2785
  ),
2786
  'role' => array(),
2787
  'tabindex' => array(),
2789
  'value' => array(),
2790
  ),
2791
  'tag_spec' => array(),
 
2792
  ),
2793
  array(
2794
  'attr_spec_list' => array(
2795
  'name' => array(
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' => '',
2804
  'value' => array(),
2805
  ),
2806
  'tag_spec' => array(
 
 
 
 
2807
  'mandatory_ancestor' => 'amp-app-banner',
2808
  'spec_name' => 'amp-app-banner button[open-button]',
2809
  ),
 
2810
  ),
2811
  ),
2812
  'caption' => array(
2813
  array(
2814
  'attr_spec_list' => array(),
2815
  'tag_spec' => array(),
 
2816
  ),
2817
  ),
2818
  'center' => array(
2819
  array(
2820
  'attr_spec_list' => array(),
2821
+ 'tag_spec' => array(),
 
 
 
 
 
2822
  ),
2823
  ),
2824
  'circle' => array(
2902
  'xmlns:xlink' => array(),
2903
  ),
2904
  'tag_spec' => array(
 
 
 
 
2905
  'mandatory_ancestor' => 'svg',
2906
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2907
  ),
 
2908
  ),
2909
  ),
2910
  'cite' => array(
2911
  array(
2912
  'attr_spec_list' => array(),
2913
  'tag_spec' => array(),
 
2914
  ),
2915
  ),
2916
  'clippath' => array(
2991
  'xmlns:xlink' => array(),
2992
  ),
2993
  'tag_spec' => array(
 
 
 
 
2994
  'mandatory_ancestor' => 'svg',
2995
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
2996
  ),
 
2997
  ),
2998
  ),
2999
  'code' => array(
3000
  array(
3001
  'attr_spec_list' => array(),
3002
  'tag_spec' => array(),
 
3003
  ),
3004
  ),
3005
  'col' => array(
3008
  'span' => array(),
3009
  ),
3010
  'tag_spec' => array(),
 
3011
  ),
3012
  ),
3013
  'colgroup' => array(
3016
  'span' => array(),
3017
  ),
3018
  'tag_spec' => array(),
 
3019
  ),
3020
  ),
3021
  'data' => array(
3022
  array(
3023
  'attr_spec_list' => array(),
3024
  'tag_spec' => array(),
 
3025
  ),
3026
  ),
3027
  'datalist' => array(
3030
  'tag_spec' => array(
3031
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
3032
  ),
 
3033
  ),
3034
  ),
3035
  'dd' => array(
3036
  array(
3037
  'attr_spec_list' => array(),
3038
  'tag_spec' => array(),
 
3039
  ),
3040
  ),
3041
  'defs' => array(
3115
  'xmlns:xlink' => array(),
3116
  ),
3117
  'tag_spec' => array(
 
 
 
 
3118
  'mandatory_ancestor' => 'svg',
3119
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3120
  ),
 
3121
  ),
3122
  ),
3123
  'del' => array(
3125
  'attr_spec_list' => array(
3126
  'cite' => array(
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
+ ),
3135
  ),
3136
  ),
3137
  'datetime' => array(),
3138
  ),
3139
  'tag_spec' => array(),
 
3140
  ),
3141
  ),
3142
  'desc' => array(
3151
  'xmlns:xlink' => array(),
3152
  ),
3153
  'tag_spec' => array(
 
 
 
 
3154
  'mandatory_ancestor' => 'svg',
3155
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3156
  ),
 
3157
  ),
3158
  ),
3159
  'dfn' => array(
3160
  array(
3161
  'attr_spec_list' => array(),
3162
  'tag_spec' => array(),
 
3163
  ),
3164
  ),
3165
  'dir' => array(
3166
  array(
3167
  'attr_spec_list' => array(),
3168
+ 'tag_spec' => array(),
 
 
 
 
 
3169
  ),
3170
  ),
3171
  'div' => array(
3174
  'align' => array(),
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(
3192
  'attr_spec_list' => array(
3197
  ),
3198
  'tag_spec' => array(
3199
  'mandatory_parent' => 'form',
3200
+ 'spec_name' => 'FORM > DIV [submit-success]',
3201
+ ),
3202
+ ),
3203
+ array(
3204
+ 'attr_spec_list' => array(
3205
+ 'align' => array(),
3206
+ 'submit-success' => array(
3207
+ 'mandatory' => true,
3208
+ ),
3209
+ 'template' => array(
3210
+ 'mandatory' => true,
3211
+ ),
3212
+ ),
3213
+ 'tag_spec' => array(
3214
+ 'mandatory_parent' => 'form',
3215
+ 'spec_name' => 'FORM > DIV [submit-success][template]',
3216
  ),
 
3217
  ),
3218
  array(
3219
  'attr_spec_list' => array(
3224
  ),
3225
  'tag_spec' => array(
3226
  'mandatory_parent' => 'form',
3227
+ 'spec_name' => 'FORM > DIV [submit-error]',
3228
+ ),
3229
+ ),
3230
+ array(
3231
+ 'attr_spec_list' => array(
3232
+ 'align' => array(),
3233
+ 'submit-error' => array(
3234
+ 'mandatory' => true,
3235
+ ),
3236
+ 'template' => array(
3237
+ 'mandatory' => true,
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(),
 
3250
  ),
3251
  ),
3252
  'dt' => array(
3253
  array(
3254
  'attr_spec_list' => array(),
3255
  'tag_spec' => array(),
 
3256
  ),
3257
  ),
3258
  'ellipse' => array(
3337
  'xmlns:xlink' => array(),
3338
  ),
3339
  'tag_spec' => array(
 
 
 
 
3340
  'mandatory_ancestor' => 'svg',
3341
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3342
  ),
 
3343
  ),
3344
  ),
3345
  'em' => array(
3346
  array(
3347
  'attr_spec_list' => array(),
3348
  'tag_spec' => array(),
 
3349
  ),
3350
  ),
3351
  'fecolormatrix' => array(
3428
  'y' => array(),
3429
  ),
3430
  'tag_spec' => array(
 
 
 
 
3431
  'mandatory_ancestor' => 'svg',
3432
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3433
  ),
 
3434
  ),
3435
  ),
3436
  'fecomposite' => array(
3517
  'y' => array(),
3518
  ),
3519
  'tag_spec' => array(
 
 
 
 
3520
  'mandatory_ancestor' => 'svg',
3521
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3522
  ),
 
3523
  ),
3524
  ),
3525
  'feflood' => array(
3599
  'y' => array(),
3600
  ),
3601
  'tag_spec' => array(
 
 
 
 
3602
  'mandatory_ancestor' => 'svg',
3603
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3604
  ),
 
3605
  ),
3606
  ),
3607
  'fegaussianblur' => array(
3684
  'y' => array(),
3685
  ),
3686
  'tag_spec' => array(
 
 
 
 
3687
  'mandatory_ancestor' => 'svg',
3688
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3689
  ),
 
3690
  ),
3691
  ),
3692
  'femerge' => array(
3766
  'y' => array(),
3767
  ),
3768
  'tag_spec' => array(
 
 
 
 
3769
  'mandatory_ancestor' => 'svg',
3770
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3771
  ),
 
3772
  ),
3773
  ),
3774
  'femergenode' => array(
3784
  'xmlns:xlink' => array(),
3785
  ),
3786
  'tag_spec' => array(
 
 
 
 
3787
  'mandatory_ancestor' => 'svg',
3788
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3789
  ),
 
3790
  ),
3791
  ),
3792
  'feoffset' => array(
3869
  'y' => array(),
3870
  ),
3871
  'tag_spec' => array(
 
 
 
 
3872
  'mandatory_ancestor' => 'svg',
3873
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
3874
  ),
 
3875
  ),
3876
  ),
3877
  'fieldset' => array(
3880
  '[disabled]' => array(),
3881
  'disabled' => array(),
3882
  'name' => array(
3883
+ '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|$)',
3884
  ),
3885
  ),
3886
  'tag_spec' => array(),
 
3887
  ),
3888
  ),
3889
  'figcaption' => array(
3890
  array(
3891
  'attr_spec_list' => array(),
3892
  'tag_spec' => array(),
 
3893
  ),
3894
  ),
3895
  'figure' => array(
3896
  array(
3897
  'attr_spec_list' => array(),
3898
  'tag_spec' => array(),
 
3899
  ),
3900
  ),
3901
  'filter' => array(
3977
  'alternative_names' => array(
3978
  'href',
3979
  ),
3980
+ 'value_url' => array(
3981
+ 'allow_empty' => false,
3982
+ 'allow_relative' => true,
3983
+ 'allowed_protocol' => array(
3984
+ 'http',
3985
+ 'https',
3986
+ ),
3987
  ),
3988
  ),
3989
  'xlink:role' => array(),
3997
  'y' => array(),
3998
  ),
3999
  'tag_spec' => array(
 
 
 
 
4000
  'mandatory_ancestor' => 'svg',
4001
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4002
  ),
 
4003
  ),
4004
  ),
4005
  'footer' => array(
4006
  array(
4007
  'attr_spec_list' => array(),
4008
  'tag_spec' => array(),
 
4009
  ),
4010
  ),
4011
  'foreignobject' => 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(
4102
  'action' => array(
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
  ),
4111
  ),
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(),
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
  ),
4143
  ),
4144
  ),
4145
  'tag_spec' => array(
4146
  'disallowed_ancestor' => array(
4147
+ 'amp-app-banner',
4148
  ),
4149
+ 'requires_extension' => array(
4150
+ 'amp-form',
 
4151
  ),
4152
+ 'spec_name' => 'FORM [method=GET]',
4153
  ),
 
4154
  ),
4155
  array(
4156
  'attr_spec_list' => array(
4159
  'action-xhr' => array(
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(),
4172
  ),
4173
  'enctype' => array(),
4174
  'method' => array(
4175
+ 'dispatch_key' => 2,
4176
  'mandatory' => true,
4177
  'value_casei' => 'post',
4178
  ),
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
  ),
4193
  ),
4194
  ),
4195
  'tag_spec' => array(
4196
  'disallowed_ancestor' => array(
4197
+ 'amp-app-banner',
4198
  ),
4199
+ 'requires_extension' => array(
4200
+ 'amp-form',
 
4201
  ),
4202
+ 'spec_name' => 'FORM [method=POST]',
4203
  ),
 
4204
  ),
4205
  ),
4206
  'g' => array(
4280
  'xmlns:xlink' => array(),
4281
  ),
4282
  'tag_spec' => array(
 
 
 
 
4283
  'mandatory_ancestor' => 'svg',
4284
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4285
  ),
 
4286
  ),
4287
  ),
4288
  'glyph' => array(
4366
  'xmlns:xlink' => array(),
4367
  ),
4368
  'tag_spec' => array(
 
 
 
 
4369
  'mandatory_ancestor' => 'svg',
4370
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4371
  ),
 
4372
  ),
4373
  ),
4374
  'glyphref' => array(
4448
  'alternative_names' => array(
4449
  'href',
4450
  ),
4451
+ 'value_url' => array(
4452
+ 'allow_empty' => false,
4453
+ 'allow_relative' => true,
4454
+ 'allowed_protocol' => array(
4455
+ 'http',
4456
+ 'https',
4457
+ ),
4458
  ),
4459
  ),
4460
  'xlink:role' => array(),
4468
  'y' => array(),
4469
  ),
4470
  'tag_spec' => array(
 
 
 
 
4471
  'mandatory_ancestor' => 'svg',
4472
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4473
  ),
 
4474
  ),
4475
  ),
4476
  'h1' => array(
4479
  'align' => array(),
4480
  ),
4481
  'tag_spec' => array(),
 
4482
  ),
4483
  ),
4484
  'h2' => array(
4487
  'align' => array(),
4488
  ),
4489
  'tag_spec' => array(),
 
4490
  ),
4491
  ),
4492
  'h3' => array(
4495
  'align' => array(),
4496
  ),
4497
  'tag_spec' => array(),
 
4498
  ),
4499
  ),
4500
  'h4' => array(
4503
  'align' => array(),
4504
  ),
4505
  'tag_spec' => array(),
 
4506
  ),
4507
  ),
4508
  'h5' => array(
4511
  'align' => array(),
4512
  ),
4513
  'tag_spec' => array(),
 
4514
  ),
4515
  ),
4516
  'h6' => array(
4519
  'align' => array(),
4520
  ),
4521
  'tag_spec' => array(),
4522
+ ),
4523
+ ),
4524
+ 'head' => array(
4525
+ array(
4526
+ 'attr_spec_list' => array(),
4527
+ 'tag_spec' => array(
4528
+ 'mandatory' => true,
4529
+ 'mandatory_parent' => 'html',
4530
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
4531
+ 'unique' => true,
4532
+ ),
4533
  ),
4534
  ),
4535
  'header' => array(
4536
  array(
4537
  'attr_spec_list' => array(),
4538
  'tag_spec' => array(),
 
4539
  ),
4540
  ),
4541
  'hgroup' => array(
4542
  array(
4543
  'attr_spec_list' => array(),
4544
+ 'tag_spec' => array(),
 
 
 
 
 
4545
  ),
4546
  ),
4547
  'hkern' => array(
4561
  'xmlns:xlink' => array(),
4562
  ),
4563
  'tag_spec' => array(
 
 
 
 
4564
  'mandatory_ancestor' => 'svg',
4565
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4566
  ),
 
4567
  ),
4568
  ),
4569
  'hr' => array(
4570
  array(
4571
  'attr_spec_list' => array(),
4572
  'tag_spec' => array(),
4573
+ ),
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
+ ),
4593
  ),
4594
  ),
4595
  'i' => array(
4596
  array(
4597
  'attr_spec_list' => array(),
4598
  'tag_spec' => array(),
 
4599
  ),
4600
  ),
4601
  'iframe' => array(
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
+ ),
4625
  ),
4626
  ),
4627
  'srcdoc' => array(),
4628
  'width' => array(),
4629
  ),
4630
  'tag_spec' => array(
 
 
 
4631
  'mandatory_ancestor' => 'noscript',
4632
  'mandatory_ancestor_suggested_alternative' => 'amp-iframe',
4633
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-iframe',
4634
  ),
 
4635
  ),
4636
  ),
4637
  'image' => array(
4716
  'href',
4717
  ),
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',
4726
+ ),
4727
  ),
4728
  ),
4729
  'xlink:role' => array(),
4737
  'y' => array(),
4738
  ),
4739
  'tag_spec' => array(
 
 
 
 
4740
  'mandatory_ancestor' => 'svg',
4741
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4742
  ),
 
4743
  ),
4744
  ),
4745
  'img' => 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(
4765
  ),
4766
  'blacklisted_value_regex' => '__amp_source_origin',
4767
  'mandatory' => true,
4768
+ 'value_url' => array(
4769
+ 'allow_relative' => true,
4770
+ 'allowed_protocol' => array(
4771
+ 'data',
4772
+ 'https',
4773
+ ),
4774
  ),
4775
  ),
4776
  'width' => array(),
4777
  ),
4778
  'tag_spec' => array(
 
 
 
4779
  'mandatory_ancestor' => 'noscript',
4780
  'mandatory_ancestor_suggested_alternative' => 'amp-img',
4781
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-img',
4782
  ),
 
4783
  ),
4784
  ),
4785
  'input' => array(
4823
  'minlength' => array(),
4824
  'multiple' => array(),
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(),
4843
  'tag_spec' => array(
4844
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4845
  ),
 
4846
  ),
4847
  ),
4848
  'ins' => 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(
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(
4894
  ),
4895
  ),
4896
  'tag_spec' => array(),
 
4897
  ),
4898
  ),
4899
  'line' => array(
4978
  'y2' => array(),
4979
  ),
4980
  'tag_spec' => array(
 
 
 
 
4981
  'mandatory_ancestor' => 'svg',
4982
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
4983
  ),
 
4984
  ),
4985
  ),
4986
  'lineargradient' => array(
5061
  'alternative_names' => array(
5062
  'href',
5063
  ),
5064
+ 'value_url' => array(
5065
+ 'allow_empty' => false,
5066
+ 'allow_relative' => true,
5067
+ 'allowed_protocol' => array(
5068
+ 'http',
5069
+ 'https',
5070
+ ),
5071
  ),
5072
  ),
5073
  'xlink:role' => array(),
5082
  'y2' => array(),
5083
  ),
5084
  'tag_spec' => array(
 
 
 
 
5085
  'mandatory_ancestor' => 'svg',
5086
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5087
  ),
 
5088
  ),
5089
  ),
5090
  'link' => array(
5094
  'value_casei' => 'utf-8',
5095
  ),
5096
  'color' => array(),
5097
+ 'crossorigin' => array(),
5098
  'href' => array(),
5099
  'hreflang' => array(),
5100
  'media' => array(),
5101
  'rel' => array(
5102
+ 'blacklisted_value_regex' => '(^|\\s)(canonical|components|import|manifest|preload|serviceworker|stylesheet|subresource|)(\\s|$)',
5103
  'mandatory' => true,
5104
  ),
5105
  'sizes' => array(),
5108
  ),
5109
  'tag_spec' => array(
5110
  'disallowed_ancestor' => array(
5111
+ 'template',
 
 
 
 
5112
  ),
5113
  'spec_name' => 'link rel=',
5114
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5115
  ),
 
5116
  ),
5117
  array(
5118
  'attr_spec_list' => array(
5120
  'value_casei' => 'utf-8',
5121
  ),
5122
  'color' => array(),
5123
+ 'crossorigin' => array(),
5124
  'href' => array(
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
+ ),
5133
+ ),
5134
  ),
5135
  'hreflang' => array(),
5136
+ 'media' => array(),
5137
+ 'rel' => array(
5138
+ 'dispatch_key' => 2,
5139
  'mandatory' => true,
5140
+ 'value_casei' => 'canonical',
5141
+ ),
5142
+ 'sizes' => array(),
5143
+ 'target' => array(),
5144
+ 'type' => array(),
5145
+ ),
5146
+ 'tag_spec' => array(
5147
+ 'mandatory' => true,
5148
+ 'mandatory_parent' => 'head',
5149
+ 'spec_name' => 'link rel=canonical',
5150
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
5151
+ 'unique' => true,
5152
+ ),
5153
+ ),
5154
+ array(
5155
+ 'attr_spec_list' => array(
5156
+ 'charset' => array(
5157
+ 'value_casei' => 'utf-8',
5158
+ ),
5159
+ 'color' => array(),
5160
+ 'crossorigin' => array(),
5161
+ 'href' => array(
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
+ ),
5170
  ),
5171
+ 'hreflang' => array(),
5172
  'media' => array(),
5173
+ 'rel' => array(
5174
+ 'dispatch_key' => 2,
5175
+ 'mandatory' => true,
5176
+ 'value_casei' => 'manifest',
5177
+ ),
5178
  'sizes' => array(),
5179
  'target' => array(),
5180
  'type' => array(),
5181
  ),
5182
  'tag_spec' => array(
5183
+ 'mandatory_parent' => 'head',
5184
+ 'spec_name' => 'link rel=manifest',
5185
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5186
+ ),
5187
+ ),
5188
+ array(
5189
+ 'attr_spec_list' => array(
5190
+ 'as' => array(),
5191
+ 'charset' => array(
5192
+ 'value_casei' => 'utf-8',
5193
+ ),
5194
+ 'color' => array(),
5195
+ 'crossorigin' => array(),
5196
+ 'href' => array(),
5197
+ 'hreflang' => array(),
5198
+ 'media' => array(),
5199
+ 'rel' => array(
5200
+ 'dispatch_key' => 2,
5201
+ 'mandatory' => true,
5202
+ 'value_casei' => 'preload',
5203
+ ),
5204
+ 'sizes' => array(),
5205
+ 'target' => array(),
5206
+ 'type' => array(),
5207
+ ),
5208
+ 'tag_spec' => array(
5209
+ 'disallowed_ancestor' => array(
5210
+ 'template',
5211
  ),
5212
+ 'spec_name' => 'link rel=preload',
5213
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5214
  ),
5215
+ ),
5216
+ array(
5217
+ 'attr_spec_list' => array(
5218
+ 'async' => array(),
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(
5236
+ 'mandatory_parent' => 'head',
5237
+ 'spec_name' => 'link rel=stylesheet for fonts',
5238
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#custom-fonts',
5239
+ ),
5240
  ),
5241
  array(
5242
  'attr_spec_list' => array(
5244
  'value_casei' => 'utf-8',
5245
  ),
5246
  'color' => array(),
5247
+ 'crossorigin' => array(),
5248
  'href' => array(
5249
  'mandatory' => true,
5250
  ),
5251
  'hreflang' => array(),
5252
  'itemprop' => array(
5253
+ 'dispatch_key' => 2,
5254
  'mandatory' => true,
5255
+ 'value_casei' => 'sameas',
5256
  ),
5257
  'media' => array(),
5258
  'sizes' => array(),
5260
  'type' => array(),
5261
  ),
5262
  'tag_spec' => array(
5263
+ 'spec_name' => 'link itemprop=sameAs',
5264
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5265
+ ),
5266
+ ),
5267
+ array(
5268
+ 'attr_spec_list' => array(
5269
+ 'charset' => array(
5270
+ 'value_casei' => 'utf-8',
5271
+ ),
5272
+ 'color' => array(),
5273
+ 'crossorigin' => array(),
5274
+ 'href' => array(
5275
+ 'mandatory' => true,
5276
+ ),
5277
+ 'hreflang' => array(),
5278
+ 'itemprop' => array(
5279
+ 'mandatory' => true,
5280
  ),
5281
+ 'media' => array(),
5282
+ 'sizes' => array(),
5283
+ 'target' => array(),
5284
+ 'type' => array(),
5285
+ ),
5286
+ 'tag_spec' => array(
5287
  'spec_name' => 'link itemprop=',
5288
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5289
  ),
 
5290
  ),
5291
  array(
5292
  'attr_spec_list' => array(
5294
  'value_casei' => 'utf-8',
5295
  ),
5296
  'color' => array(),
5297
+ 'crossorigin' => array(),
5298
  'href' => array(
5299
  'mandatory' => true,
5300
  ),
5308
  'type' => array(),
5309
  ),
5310
  'tag_spec' => array(
 
 
 
 
5311
  'spec_name' => 'link property=',
5312
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5313
  ),
 
5314
  ),
5315
  ),
5316
  'listing' => array(
5317
  array(
5318
  'attr_spec_list' => array(),
5319
+ 'tag_spec' => array(),
 
 
 
 
 
5320
  ),
5321
  ),
5322
  'main' => array(
5323
  array(
5324
  'attr_spec_list' => array(),
5325
  'tag_spec' => array(),
 
5326
  ),
5327
  ),
5328
  'mark' => array(
5329
  array(
5330
  'attr_spec_list' => array(),
5331
  'tag_spec' => array(),
 
5332
  ),
5333
  ),
5334
  'marker' => array(
5413
  'xmlns:xlink' => array(),
5414
  ),
5415
  'tag_spec' => array(
 
 
 
 
5416
  'mandatory_ancestor' => 'svg',
5417
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5418
  ),
 
5419
  ),
5420
  ),
5421
  'mask' => array(
5500
  'y' => array(),
5501
  ),
5502
  'tag_spec' => array(
 
 
 
 
5503
  'mandatory_ancestor' => 'svg',
5504
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5505
  ),
 
5506
  ),
5507
  ),
5508
  'meta' => array(
5509
  array(
5510
  'attr_spec_list' => array(
5511
+ 'charset' => array(
5512
+ 'dispatch_key' => 1,
 
 
 
 
 
 
 
 
5513
  'mandatory' => true,
5514
+ 'value_casei' => 'utf-8',
5515
  ),
5516
  ),
5517
  'tag_spec' => array(
5518
+ 'mandatory' => true,
5519
+ 'mandatory_parent' => 'head',
5520
+ 'spec_name' => 'meta charset=utf-8',
5521
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
5522
+ 'unique' => true,
 
 
5523
  ),
 
5524
  ),
5525
  array(
5526
  'attr_spec_list' => array(
5527
+ 'content' => array(
5528
+ 'mandatory' => true,
5529
+ 'value_properties' => array(
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(),
5540
+ 'width' => array(
5541
+ 'mandatory' => true,
5542
+ 'value' => 'device-width',
5543
+ ),
5544
+ ),
5545
+ ),
5546
  'name' => array(
5547
+ 'dispatch_key' => 2,
5548
+ 'mandatory' => true,
5549
+ 'value' => 'viewport',
5550
  ),
 
5551
  ),
5552
  'tag_spec' => array(
5553
+ 'mandatory' => true,
5554
+ 'mandatory_parent' => 'head',
5555
+ 'spec_name' => 'meta name=viewport',
5556
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
5557
+ 'unique' => true,
5558
+ ),
5559
+ ),
5560
+ array(
5561
+ 'attr_spec_list' => array(
5562
+ 'content' => array(
5563
+ 'mandatory' => true,
5564
+ 'value_properties' => array(
5565
+ 'chrome' => array(
5566
+ 'value' => '1',
5567
+ ),
5568
+ 'ie' => array(
5569
+ 'value' => 'edge',
5570
+ ),
5571
+ ),
5572
+ ),
5573
+ 'http-equiv' => array(
5574
+ 'dispatch_key' => 2,
5575
+ 'mandatory' => true,
5576
+ 'value_casei' => 'x-ua-compatible',
5577
+ ),
5578
+ ),
5579
+ 'tag_spec' => array(
5580
+ 'mandatory_ancestor' => 'head',
5581
+ 'spec_name' => 'meta http-equiv=X-UA-Compatible',
5582
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5583
+ ),
5584
+ ),
5585
+ array(
5586
+ 'attr_spec_list' => array(
5587
+ 'content' => array(
5588
+ 'mandatory' => true,
5589
+ 'value_regex' => '.*app-id=.*',
5590
+ ),
5591
+ 'name' => array(
5592
+ 'dispatch_key' => 2,
5593
+ 'mandatory' => true,
5594
+ 'value_casei' => 'apple-itunes-app',
5595
+ ),
5596
+ ),
5597
+ 'tag_spec' => array(
5598
+ 'mandatory_parent' => 'head',
5599
+ 'spec_name' => 'meta name=apple-itunes-app',
5600
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5601
+ ),
5602
+ ),
5603
+ array(
5604
+ 'attr_spec_list' => array(
5605
+ 'content' => array(
5606
+ 'mandatory' => true,
5607
+ ),
5608
+ 'name' => array(
5609
+ 'dispatch_key' => 2,
5610
+ 'mandatory' => true,
5611
+ 'value_casei' => 'amp-experiments-opt-in',
5612
+ ),
5613
+ ),
5614
+ 'tag_spec' => array(
5615
+ 'mandatory_parent' => 'head',
5616
+ 'spec_name' => 'meta name=amp-experiments-opt-in',
5617
+ ),
5618
+ ),
5619
+ array(
5620
+ 'attr_spec_list' => array(
5621
+ 'content' => array(
5622
+ 'mandatory' => true,
5623
+ 'value_url' => array(
5624
+ 'allowed_protocol' => array(
5625
+ 'https',
5626
+ ),
5627
+ ),
5628
+ ),
5629
+ 'name' => array(
5630
+ 'dispatch_key' => 2,
5631
+ 'mandatory' => true,
5632
+ 'value_casei' => 'amp-3p-iframe-src',
5633
+ ),
5634
+ ),
5635
+ 'tag_spec' => array(
5636
+ 'mandatory_parent' => 'head',
5637
+ 'spec_name' => 'meta name=amp-3p-iframe-src',
5638
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ad',
5639
+ ),
5640
+ ),
5641
+ array(
5642
+ 'attr_spec_list' => array(
5643
+ 'content' => array(
5644
+ 'mandatory' => true,
5645
+ ),
5646
+ 'name' => array(
5647
+ 'dispatch_key' => 2,
5648
+ 'mandatory' => true,
5649
+ 'value_casei' => 'amp-experiment-token',
5650
+ ),
5651
+ ),
5652
+ 'tag_spec' => array(
5653
+ 'mandatory_parent' => 'head',
5654
+ 'spec_name' => 'meta name=amp-experiment-token',
5655
+ ),
5656
+ ),
5657
+ array(
5658
+ 'attr_spec_list' => array(
5659
+ 'content' => array(
5660
+ 'mandatory' => true,
5661
+ ),
5662
+ 'name' => array(
5663
+ 'dispatch_key' => 2,
5664
+ 'mandatory' => true,
5665
+ 'value_casei' => 'amp-link-variable-allowed-origin',
5666
+ ),
5667
+ ),
5668
+ 'tag_spec' => array(
5669
+ 'mandatory_parent' => 'head',
5670
+ 'spec_name' => 'meta name=amp-link-variable-allowed-origin',
5671
+ ),
5672
+ ),
5673
+ array(
5674
+ 'attr_spec_list' => array(
5675
+ 'content' => array(
5676
+ 'mandatory' => true,
5677
+ ),
5678
+ 'name' => array(
5679
+ 'dispatch_key' => 2,
5680
+ 'mandatory' => true,
5681
+ 'value_casei' => 'amp-google-client-id-api',
5682
+ ),
5683
+ ),
5684
+ 'tag_spec' => array(
5685
+ 'mandatory_parent' => 'head',
5686
+ 'spec_name' => 'meta name=amp-google-clientid-id-api',
5687
+ ),
5688
+ ),
5689
+ array(
5690
+ 'attr_spec_list' => array(
5691
+ 'name' => array(
5692
+ 'dispatch_key' => 2,
5693
+ 'mandatory' => true,
5694
+ 'value_casei' => 'amp-ad-doubleclick-sra',
5695
+ ),
5696
+ ),
5697
+ 'tag_spec' => array(
5698
+ 'mandatory_parent' => 'head',
5699
+ 'spec_name' => 'meta name=amp-ad-doubleclick-sra',
5700
+ ),
5701
+ ),
5702
+ array(
5703
+ 'attr_spec_list' => array(
5704
+ 'content' => array(),
5705
+ 'itemprop' => array(),
5706
+ 'name' => array(
5707
+ 'blacklisted_value_regex' => '(^|\\s)(amp-.*|amp4ads-.*|apple-itunes-app|content-disposition|revisit-after|viewport)(\\s|$)',
5708
+ ),
5709
+ 'property' => array(),
5710
+ 'scheme' => array(),
5711
+ ),
5712
+ 'tag_spec' => array(
5713
  'spec_name' => 'meta name= and content=',
5714
  ),
 
5715
  ),
5716
  array(
5717
  'attr_spec_list' => array(
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(
 
 
 
 
5729
  'mandatory_ancestor' => 'head',
5730
+ 'spec_name' => 'meta http-equiv=Content-Type',
5731
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5732
  ),
 
5733
  ),
5734
  array(
5735
  'attr_spec_list' => array(
5737
  'mandatory' => true,
5738
  ),
5739
  'http-equiv' => array(
5740
+ 'dispatch_key' => 2,
5741
  'mandatory' => true,
5742
  'value_casei' => 'content-language',
5743
  ),
5744
  ),
5745
  'tag_spec' => array(
 
 
 
 
5746
  'mandatory_ancestor' => 'head',
5747
  'spec_name' => 'meta http-equiv=content-language',
5748
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5749
  ),
 
5750
  ),
5751
  array(
5752
  'attr_spec_list' => array(
5754
  'mandatory' => true,
5755
  ),
5756
  'http-equiv' => array(
5757
+ 'dispatch_key' => 2,
5758
  'mandatory' => true,
5759
  'value_casei' => 'pics-label',
5760
  ),
5761
  ),
5762
  'tag_spec' => array(
 
 
 
 
5763
  'mandatory_ancestor' => 'head',
5764
  'spec_name' => 'meta http-equiv=pics-label',
5765
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5766
  ),
 
5767
  ),
5768
  array(
5769
  'attr_spec_list' => array(
5771
  'mandatory' => true,
5772
  ),
5773
  'http-equiv' => array(
5774
+ 'dispatch_key' => 2,
5775
  'mandatory' => true,
5776
  'value_casei' => 'imagetoolbar',
5777
  ),
5778
  ),
5779
  'tag_spec' => array(
 
 
 
 
5780
  'mandatory_ancestor' => 'head',
5781
  'spec_name' => 'meta http-equiv=imagetoolbar',
5782
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5783
  ),
 
5784
  ),
5785
  array(
5786
  'attr_spec_list' => array(
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(
 
 
 
 
5798
  'mandatory_ancestor' => 'head',
5799
+ 'spec_name' => 'meta http-equiv=Content-Style-Type',
5800
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5801
  ),
 
5802
  ),
5803
  array(
5804
  'attr_spec_list' => array(
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(
 
 
 
 
5816
  'mandatory_ancestor' => 'head',
5817
+ 'spec_name' => 'meta http-equiv=Content-Script-Type',
5818
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5819
  ),
 
5820
  ),
5821
  array(
5822
  'attr_spec_list' => array(
5824
  'mandatory' => true,
5825
  ),
5826
  'http-equiv' => array(
5827
+ 'dispatch_key' => 2,
5828
  'mandatory' => true,
5829
  'value_casei' => 'origin-trial',
5830
  ),
5831
  ),
5832
  'tag_spec' => array(
 
 
 
 
5833
  'mandatory_ancestor' => 'head',
5834
  'spec_name' => 'meta http-equiv=origin-trial',
5835
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5836
  ),
 
5837
  ),
5838
  array(
5839
  'attr_spec_list' => array(
5841
  'mandatory' => true,
5842
  ),
5843
  'http-equiv' => array(
5844
+ 'dispatch_key' => 2,
5845
  'mandatory' => true,
5846
  'value_casei' => 'resource-type',
5847
  ),
5848
  ),
5849
  'tag_spec' => array(
 
 
 
 
5850
  'mandatory_ancestor' => 'head',
5851
  'spec_name' => 'meta http-equiv=resource-type',
5852
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5853
  ),
5854
+ ),
5855
+ array(
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(
5868
+ 'mandatory_ancestor' => 'head',
5869
+ 'spec_name' => 'meta http-equiv=x-dns-prefetch-control',
5870
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#html-tags',
5871
+ ),
5872
+ ),
5873
+ array(
5874
+ 'attr_spec_list' => array(
5875
+ 'content' => array(
5876
+ 'mandatory' => true,
5877
+ ),
5878
+ 'name' => array(
5879
+ 'dispatch_key' => 2,
5880
+ 'mandatory' => true,
5881
+ 'value_casei' => 'amp-ad-enable-refresh',
5882
+ ),
5883
+ ),
5884
+ 'tag_spec' => array(
5885
+ 'mandatory_ancestor' => 'head',
5886
+ 'spec_name' => 'meta name=amp-ad-enable-refresh',
5887
+ ),
5888
+ ),
5889
+ array(
5890
+ 'attr_spec_list' => array(
5891
+ 'content' => array(
5892
+ 'mandatory' => true,
5893
+ ),
5894
+ 'name' => array(
5895
+ 'dispatch_key' => 2,
5896
+ 'mandatory' => true,
5897
+ 'value_casei' => 'amp-to-amp-navigation',
5898
+ ),
5899
+ ),
5900
+ 'tag_spec' => array(
5901
+ 'mandatory_parent' => 'head',
5902
+ 'spec_name' => 'meta name=amp-to-amp-navigation',
5903
+ 'unique' => true,
5904
+ ),
5905
  ),
5906
  ),
5907
  'metadata' => array(
5916
  'xmlns:xlink' => array(),
5917
  ),
5918
  'tag_spec' => array(
 
 
 
 
5919
  'mandatory_ancestor' => 'svg',
5920
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
5921
  ),
 
5922
  ),
5923
  ),
5924
  'meter' => array(
5932
  'value' => array(),
5933
  ),
5934
  'tag_spec' => array(),
 
5935
  ),
5936
  ),
5937
  'multicol' => array(
5938
  array(
5939
  'attr_spec_list' => array(),
5940
+ 'tag_spec' => array(),
 
 
 
 
 
5941
  ),
5942
  ),
5943
  'nav' => array(
5944
  array(
5945
  'attr_spec_list' => array(),
5946
  'tag_spec' => array(),
 
5947
  ),
5948
  array(
5949
  'attr_spec_list' => array(
5950
  'toolbar' => array(
5951
+ 'dispatch_key' => 1,
5952
  'mandatory' => true,
5953
  ),
5954
  'toolbar-target' => array(
5956
  ),
5957
  ),
5958
  'tag_spec' => array(
 
 
 
5959
  'mandatory_parent' => 'amp-sidebar',
5960
  'spec_name' => 'amp-sidebar > nav',
5961
  ),
 
5962
  ),
5963
  ),
5964
  'nextid' => array(
5965
  array(
5966
  'attr_spec_list' => array(),
5967
+ 'tag_spec' => array(),
 
 
 
 
 
5968
  ),
5969
  ),
5970
  'nobr' => array(
5971
  array(
5972
  'attr_spec_list' => array(),
5973
+ 'tag_spec' => array(),
 
 
 
 
 
5974
  ),
5975
  ),
5976
  'noscript' => array(
5977
+ array(
5978
+ 'attr_spec_list' => array(),
5979
+ 'tag_spec' => array(
5980
+ 'mandatory' => true,
5981
+ 'mandatory_parent' => 'head',
5982
+ 'spec_name' => 'noscript enclosure for boilerplate',
5983
+ 'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
5984
+ 'unique' => true,
5985
+ ),
5986
+ ),
5987
  array(
5988
  'attr_spec_list' => array(),
5989
  'tag_spec' => array(
5990
  'disallowed_ancestor' => array(
5991
+ 'noscript',
 
 
 
5992
  ),
5993
  'mandatory_ancestor' => 'body',
5994
  ),
 
5995
  ),
5996
  ),
5997
  'o:p' => array(
5998
  array(
5999
  'attr_spec_list' => array(),
6000
+ 'tag_spec' => array(),
 
 
 
 
 
6001
  ),
6002
  ),
6003
  'ol' => array(
6010
  'value_regex' => '[0-9]*',
6011
  ),
6012
  'type' => array(
6013
+ 'value_regex' => '[1AaIi]',
6014
  ),
6015
  ),
6016
  'tag_spec' => array(),
 
6017
  ),
6018
  ),
6019
  'optgroup' => array(
6028
  'mandatory_parent' => 'select',
6029
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
6030
  ),
 
6031
  ),
6032
  ),
6033
  'option' => array(
6045
  'tag_spec' => array(
6046
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
6047
  ),
 
6048
  ),
6049
  ),
6050
  'output' => array(
6053
  'for' => array(),
6054
  'form' => array(),
6055
  'name' => array(
6056
+ '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|$)',
6057
  ),
6058
  ),
6059
  'tag_spec' => array(),
 
6060
  ),
6061
  ),
6062
  'p' => array(
6065
  'align' => array(),
6066
  ),
6067
  'tag_spec' => array(),
 
6068
  ),
6069
  ),
6070
  'path' => array(
6147
  'xmlns:xlink' => array(),
6148
  ),
6149
  'tag_spec' => array(
 
 
 
 
6150
  'mandatory_ancestor' => 'svg',
6151
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6152
  ),
 
6153
  ),
6154
  ),
6155
  'pattern' => array(
6236
  'alternative_names' => array(
6237
  'href',
6238
  ),
6239
+ 'value_url' => array(
6240
+ 'allow_empty' => false,
6241
+ 'allow_relative' => true,
6242
+ 'allowed_protocol' => array(
6243
+ 'http',
6244
+ 'https',
6245
+ ),
6246
  ),
6247
  ),
6248
  'xlink:role' => array(),
6256
  'y' => array(),
6257
  ),
6258
  'tag_spec' => array(
 
 
 
 
6259
  'mandatory_ancestor' => 'svg',
6260
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6261
  ),
 
6262
  ),
6263
  ),
6264
  'polygon' => array(
6340
  'xmlns:xlink' => array(),
6341
  ),
6342
  'tag_spec' => array(
 
 
 
 
6343
  'mandatory_ancestor' => 'svg',
6344
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6345
  ),
 
6346
  ),
6347
  ),
6348
  'polyline' => array(
6424
  'xmlns:xlink' => array(),
6425
  ),
6426
  'tag_spec' => array(
 
 
 
 
6427
  'mandatory_ancestor' => 'svg',
6428
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6429
  ),
 
6430
  ),
6431
  ),
6432
  'pre' => array(
6433
  array(
6434
  'attr_spec_list' => array(),
6435
  'tag_spec' => array(),
 
6436
  ),
6437
  ),
6438
  'progress' => array(
6442
  'value' => array(),
6443
  ),
6444
  'tag_spec' => array(),
 
6445
  ),
6446
  ),
6447
  'q' => array(
6449
  'attr_spec_list' => array(
6450
  'cite' => array(
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
+ ),
6459
  ),
6460
  ),
6461
  ),
6462
  'tag_spec' => array(),
 
6463
  ),
6464
  ),
6465
  'radialgradient' => array(
6544
  'alternative_names' => array(
6545
  'href',
6546
  ),
6547
+ 'value_url' => array(
6548
+ 'allow_empty' => false,
6549
+ 'allow_relative' => true,
6550
+ 'allowed_protocol' => array(
6551
+ 'http',
6552
+ 'https',
6553
+ ),
6554
  ),
6555
  ),
6556
  'xlink:role' => array(),
6563
  'xmlns:xlink' => array(),
6564
  ),
6565
  'tag_spec' => array(
 
 
 
 
6566
  'mandatory_ancestor' => 'svg',
6567
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6568
  ),
 
6569
  ),
6570
  ),
6571
  'rb' => array(
6572
  array(
6573
  'attr_spec_list' => array(),
6574
  'tag_spec' => array(),
 
6575
  ),
6576
  ),
6577
  'rect' => array(
6658
  'y' => array(),
6659
  ),
6660
  'tag_spec' => array(
 
 
 
 
6661
  'mandatory_ancestor' => 'svg',
6662
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
6663
  ),
 
6664
  ),
6665
  ),
6666
  'rp' => array(
6667
  array(
6668
  'attr_spec_list' => array(),
6669
  'tag_spec' => array(),
 
6670
  ),
6671
  ),
6672
  'rt' => array(
6673
  array(
6674
  'attr_spec_list' => array(),
6675
  'tag_spec' => array(),
 
6676
  ),
6677
  ),
6678
  'rtc' => array(
6679
  array(
6680
  'attr_spec_list' => array(),
6681
  'tag_spec' => array(),
 
6682
  ),
6683
  ),
6684
  'ruby' => array(
6685
  array(
6686
  'attr_spec_list' => array(),
6687
  'tag_spec' => array(),
 
6688
  ),
6689
  ),
6690
  's' => array(
6691
  array(
6692
  'attr_spec_list' => array(),
6693
  'tag_spec' => array(),
 
6694
  ),
6695
  ),
6696
  'samp' => array(
6697
  array(
6698
  'attr_spec_list' => array(),
6699
  'tag_spec' => array(),
 
6700
  ),
6701
  ),
6702
  'script' => array(
6703
+ array(
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(
6720
+ 'blacklisted_cdata_regex' => array(
6721
+ 'error_message' => 'contents',
6722
+ 'regex' => '.',
6723
+ ),
6724
+ ),
6725
+ 'tag_spec' => array(
6726
+ 'mandatory' => true,
6727
+ 'mandatory_parent' => 'head',
6728
+ 'spec_name' => 'amphtml engine v0.js script',
6729
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
6730
+ 'unique' => true,
6731
+ ),
6732
+ ),
6733
  array(
6734
  'attr_spec_list' => array(
6735
  'nonce' => array(),
6736
  'type' => array(
6737
+ 'dispatch_key' => 2,
6738
  'mandatory' => true,
6739
  'value_casei' => 'application/ld+json',
6740
  ),
6741
  ),
6742
+ 'cdata' => array(
6743
+ 'blacklisted_cdata_regex' => array(
6744
+ 'error_message' => 'html comments',
6745
+ 'regex' => '<!--',
6746
+ ),
6747
+ ),
6748
  'tag_spec' => array(
6749
  'spec_name' => 'script type=application/ld+json',
6750
  ),
 
6751
  ),
6752
  array(
6753
  'attr_spec_list' => array(
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(
6766
+ 'blacklisted_cdata_regex' => array(
6767
+ 'error_message' => 'html comments',
6768
+ 'regex' => '<!--',
6769
+ ),
6770
+ ),
6771
  'tag_spec' => array(
6772
+ 'mandatory_parent' => 'head',
6773
+ 'spec_name' => 'script id=amp-rtc',
6774
+ 'unique' => true,
6775
+ ),
6776
+ ),
6777
+ array(
6778
+ 'attr_spec_list' => array(
6779
+ 'type' => array(
6780
+ 'dispatch_key' => 3,
6781
+ 'mandatory' => true,
6782
+ 'value_casei' => 'application/json',
6783
+ ),
6784
+ ),
6785
+ 'cdata' => array(
6786
+ 'blacklisted_cdata_regex' => array(
6787
+ 'error_message' => 'html comments',
6788
+ 'regex' => '<!--',
6789
+ ),
6790
+ ),
6791
+ 'tag_spec' => array(
6792
+ 'mandatory_parent' => 'amp-ima-video',
6793
+ 'spec_name' => 'amp-ima-video > script[type=application/json]',
6794
+ ),
6795
+ ),
6796
+ array(
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
+ ),
6817
+ array(
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',
6839
+ ),
6840
+ ),
6841
+ ),
6842
+ array(
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',
6864
+ ),
6865
+ ),
6866
+ ),
6867
+ array(
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
+ ),
6890
+ array(
6891
+ 'attr_spec_list' => array(
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(
6904
+ 'blacklisted_cdata_regex' => array(
6905
+ 'error_message' => 'html comments',
6906
+ 'regex' => '<!--',
6907
+ ),
6908
+ ),
6909
+ 'tag_spec' => array(
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,
6917
+ ),
6918
+ ),
6919
+ array(
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
+ ),
6942
+ array(
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
+ ),
6965
+ ),
6966
+ array(
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
+ ),
6989
+ array(
6990
+ 'attr_spec_list' => array(
6991
+ 'nonce' => array(),
6992
+ 'type' => array(
6993
+ 'dispatch_key' => 3,
6994
+ 'mandatory' => true,
6995
+ 'value_casei' => 'application/json',
6996
+ ),
6997
+ ),
6998
+ 'cdata' => array(
6999
+ 'blacklisted_cdata_regex' => array(
7000
+ 'error_message' => 'html comments',
7001
+ 'regex' => '<!--',
7002
+ ),
7003
+ ),
7004
+ 'tag_spec' => array(
7005
+ 'mandatory_parent' => 'amp-analytics',
7006
+ 'requires_extension' => array(
7007
+ 'amp-analytics',
7008
+ ),
7009
+ 'spec_name' => 'amp-analytics extension .json script',
7010
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-analytics',
7011
+ ),
7012
+ ),
7013
+ array(
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
+ ),
7036
+ array(
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
+ ),
7057
+ array(
7058
+ 'attr_spec_list' => array(
7059
+ 'nonce' => array(),
7060
+ 'type' => array(
7061
+ 'dispatch_key' => 3,
7062
+ 'mandatory' => true,
7063
+ 'value_casei' => 'application/json',
7064
+ ),
7065
+ ),
7066
+ 'cdata' => array(
7067
+ 'blacklisted_cdata_regex' => array(
7068
+ 'error_message' => 'html comments',
7069
+ 'regex' => '<!--',
7070
+ ),
7071
+ ),
7072
+ 'tag_spec' => array(
7073
+ 'mandatory_parent' => 'amp-animation',
7074
+ 'requires_extension' => array(
7075
+ 'amp-animation',
7076
+ ),
7077
+ 'spec_name' => 'amp-animation extension .json script',
7078
+ ),
7079
+ ),
7080
+ array(
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
+ ),
7103
+ array(
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
+ ),
7125
+ array(
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
+ ),
7148
+ array(
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
+ ),
7169
+ array(
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
+ ),
7190
+ array(
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
+ ),
7212
+ array(
7213
+ 'attr_spec_list' => array(
7214
+ 'nonce' => array(),
7215
+ 'type' => array(
7216
+ 'dispatch_key' => 3,
7217
+ 'mandatory' => true,
7218
+ 'value_casei' => 'application/json',
7219
+ ),
7220
+ ),
7221
+ 'cdata' => array(
7222
+ 'blacklisted_cdata_regex' => array(
7223
+ 'error_message' => 'html comments',
7224
+ 'regex' => '<!--',
7225
+ ),
7226
+ ),
7227
+ 'tag_spec' => array(
7228
+ 'mandatory_parent' => 'amp-state',
7229
+ 'requires_extension' => array(
7230
+ 'amp-bind',
7231
+ ),
7232
+ 'spec_name' => 'amp-bind extension .json script',
7233
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-bind',
7234
+ ),
7235
+ ),
7236
+ array(
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
+ ),
7257
+ array(
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
+ ),
7280
+ array(
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
+ ),
7303
+ array(
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
+ ),
7324
+ array(
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
+ ),
7346
+ array(
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
+ ),
7369
+ array(
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
+ ),
7437
+ array(
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
+ ),
7510
+ array(
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
+ ),
7533
+ array(
7534
+ 'attr_spec_list' => array(
7535
+ 'nonce' => array(),
7536
+ 'type' => array(
7537
+ 'dispatch_key' => 3,
7538
+ 'mandatory' => true,
7539
+ 'value_casei' => 'application/json',
7540
+ ),
7541
+ ),
7542
+ 'cdata' => array(
7543
+ 'blacklisted_cdata_regex' => array(
7544
+ 'error_message' => 'html comments',
7545
+ 'regex' => '<!--',
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
+ ),
7575
+ array(
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
+ ),
7596
+ array(
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
+ ),
7617
+ array(
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
+ ),
7640
+ array(
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
+ ),
7663
+ array(
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
+ ),
7686
+ array(
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
+ ),
7709
+ array(
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
+ ),
7731
+ array(
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
+ ),
7754
+ array(
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
+ ),
7777
+ array(
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
+ ),
7798
+ array(
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
+ ),
7819
+ array(
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
+ ),
7842
+ array(
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
+ ),
7863
+ array(
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
+ ),
7886
+ array(
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
+ ),
7907
+ array(
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
+ ),
7930
+ array(
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
+ ),
7953
+ array(
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
+ ),
7998
+ array(
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
+ ),
8021
+ array(
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
+ ),
8043
+ array(
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
+ ),
8089
+ array(
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
+ ),
8134
+ array(
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
+ ),
8158
+ array(
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
+ ),
8179
+ array(
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
+ ),
8202
+ array(
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
+ ),
8223
+ array(
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
+ ),
8246
+ array(
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
+ ),
8267
+ array(
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
+ ),
8288
+ array(
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
+ ),
8311
+ array(
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
+ ),
8333
+ array(
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
+ ),
8354
+ array(
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
+ ),
8376
+ array(
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
+ ),
8399
+ array(
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
+ ),
8422
+ array(
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
+ ),
8445
+ array(
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
+ ),
8468
+ array(
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
+ ),
8494
+ array(
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
+ ),
8515
+ array(
8516
+ 'attr_spec_list' => array(
8517
+ 'nonce' => array(),
8518
+ 'type' => array(
8519
+ 'dispatch_key' => 3,
8520
+ 'mandatory' => true,
8521
+ 'value_casei' => 'application/json',
8522
+ ),
8523
+ ),
8524
+ 'cdata' => array(
8525
+ 'blacklisted_cdata_regex' => array(
8526
+ 'error_message' => 'html comments',
8527
+ 'regex' => '<!--',
8528
+ ),
8529
+ ),
8530
+ 'tag_spec' => array(
8531
+ 'mandatory_parent' => 'amp-story-auto-ads',
8532
+ 'requires_extension' => array(
8533
+ 'amp-story-auto-ads',
8534
+ ),
8535
+ 'spec_name' => 'amp-story-auto-ads config script',
8536
+ 'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/extensions/amp-story/amp-story-auto-ads.md',
8537
+ ),
8538
+ ),
8539
+ array(
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
+ ),
8582
+ array(
8583
+ 'attr_spec_list' => array(
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(
8596
+ 'blacklisted_cdata_regex' => array(
8597
+ 'error_message' => 'html comments',
8598
+ 'regex' => '<!--',
8599
+ ),
8600
+ ),
8601
+ 'tag_spec' => array(
8602
+ 'mandatory_parent' => 'head',
8603
+ 'requires_extension' => array(
8604
+ 'amp-subscriptions',
8605
+ ),
8606
+ 'spec_name' => 'amp-subscriptions extension .json script',
8607
+ 'unique' => true,
8608
+ ),
8609
+ ),
8610
+ array(
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',
8632
+ ),
8633
+ ),
8634
+ ),
8635
+ array(
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
+ ),
8656
+ array(
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
+ ),
8679
+ array(
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
+ ),
8702
+ array(
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
  ),
8748
  array(
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
  ),
8771
  array(
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
  ),
8792
  array(
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
  ),
8813
  array(
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
  ),
8857
  ),
8858
  'section' => array(
8860
  'attr_spec_list' => array(),
8861
  'tag_spec' => array(
8862
  'disallowed_ancestor' => array(
8863
+ 'amp-accordion',
8864
  ),
8865
  ),
 
8866
  ),
8867
  array(
8868
  'attr_spec_list' => array(
8874
  'mandatory_parent' => 'amp-accordion',
8875
  'spec_name' => 'amp-accordion > section',
8876
  ),
 
8877
  ),
8878
  ),
8879
  'select' => array(
8888
  'disabled' => array(),
8889
  'multiple' => array(),
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(),
8896
  'tag_spec' => array(
8897
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
8898
  ),
 
8899
  ),
8900
  ),
8901
  'slot' => array(
8903
  'attr_spec_list' => array(
8904
  'name' => array(),
8905
  ),
8906
+ 'tag_spec' => array(),
 
 
 
 
 
8907
  ),
8908
  ),
8909
  'small' => array(
8910
  array(
8911
  'attr_spec_list' => array(),
8912
  'tag_spec' => array(),
 
8913
  ),
8914
  ),
8915
  'solidcolor' => array(
8986
  'xmlns:xlink' => array(),
8987
  ),
8988
  'tag_spec' => array(
 
 
 
 
8989
  'mandatory_ancestor' => 'svg',
8990
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
8991
  ),
 
8992
  ),
8993
  ),
8994
  'source' => array(
8999
  'media' => array(),
9000
  'src' => array(
9001
  'blacklisted_value_regex' => '__amp_source_origin',
9002
+ 'value_url' => array(
9003
+ 'allow_relative' => true,
9004
+ 'allowed_protocol' => array(
9005
+ 'https',
9006
+ ),
9007
  ),
9008
  ),
9009
  'type' => array(),
9010
  ),
9011
  'tag_spec' => array(
 
 
 
 
9012
  'mandatory_parent' => 'amp-video',
9013
  'spec_name' => 'amp-video > source',
9014
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
9015
  ),
 
9016
  ),
9017
  array(
9018
  'attr_spec_list' => array(
9021
  'media' => array(),
9022
  'src' => array(
9023
  'blacklisted_value_regex' => '__amp_source_origin',
9024
+ 'value_url' => array(
9025
+ 'allow_relative' => true,
9026
+ 'allowed_protocol' => array(
9027
+ 'https',
9028
+ ),
9029
  ),
9030
  ),
9031
  'type' => array(),
9032
  ),
9033
  'tag_spec' => array(
 
 
 
 
9034
  'mandatory_parent' => 'amp-audio',
9035
  'spec_name' => 'amp-audio > source',
9036
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
9037
  ),
 
9038
  ),
9039
  array(
9040
  'attr_spec_list' => array(
9042
  'src' => array(
9043
  'blacklisted_value_regex' => '__amp_source_origin',
9044
  'mandatory' => true,
9045
+ 'value_url' => array(
9046
+ 'allow_relative' => true,
9047
+ 'allowed_protocol' => array(
9048
+ 'https',
9049
+ ),
9050
  ),
9051
  ),
9052
  'type' => array(
9054
  ),
9055
  ),
9056
  'tag_spec' => array(
 
 
 
 
9057
  'mandatory_parent' => 'audio',
9058
  'spec_name' => 'audio > source',
9059
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-audio',
9060
  ),
 
9061
  ),
9062
  array(
9063
  'attr_spec_list' => array(
9065
  'src' => array(
9066
  'blacklisted_value_regex' => '__amp_source_origin',
9067
  'mandatory' => true,
9068
+ 'value_url' => array(
9069
+ 'allow_relative' => true,
9070
+ 'allowed_protocol' => array(
9071
+ 'https',
9072
+ ),
9073
  ),
9074
  ),
9075
  'type' => array(
9077
  ),
9078
  ),
9079
  'tag_spec' => array(
 
 
 
 
9080
  'mandatory_parent' => 'video',
9081
  'spec_name' => 'video > source',
9082
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
9083
  ),
 
9084
  ),
9085
  array(
9086
  'attr_spec_list' => array(
9089
  'media' => array(),
9090
  'src' => array(
9091
  'blacklisted_value_regex' => '__amp_source_origin',
9092
+ 'value_url' => array(
9093
+ 'allow_relative' => true,
9094
+ 'allowed_protocol' => array(
9095
+ 'https',
9096
+ ),
9097
  ),
9098
  ),
9099
  'type' => array(),
9100
  ),
9101
  'tag_spec' => array(
 
 
 
 
9102
  'mandatory_parent' => 'amp-ima-video',
9103
+ 'requires_extension' => array(
9104
+ 'amp-ima-video',
9105
+ ),
9106
  'spec_name' => 'amp-ima-video > source',
9107
  ),
 
9108
  ),
9109
  ),
9110
  'spacer' => array(
9111
  array(
9112
  'attr_spec_list' => array(),
9113
+ 'tag_spec' => array(),
 
 
 
 
 
9114
  ),
9115
  ),
9116
  'span' => array(
9117
  array(
9118
  'attr_spec_list' => array(),
9119
  'tag_spec' => array(),
 
9120
  ),
9121
  ),
9122
  'stop' => array(
9130
  ),
9131
  ),
9132
  'tag_spec' => array(
 
 
 
 
9133
  'mandatory_ancestor' => 'lineargradient',
9134
  'spec_name' => 'lineargradient > stop',
9135
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9136
  ),
 
9137
  ),
9138
  array(
9139
  'attr_spec_list' => array(
9145
  ),
9146
  ),
9147
  'tag_spec' => array(
 
 
 
 
9148
  'mandatory_ancestor' => 'radialgradient',
9149
  'spec_name' => 'radialgradient > stop',
9150
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9151
  ),
 
9152
  ),
9153
  ),
9154
  'strike' => array(
9155
  array(
9156
  'attr_spec_list' => array(),
9157
+ 'tag_spec' => array(),
 
 
 
 
 
9158
  ),
9159
  ),
9160
  'strong' => array(
9161
  array(
9162
  'attr_spec_list' => array(),
9163
  'tag_spec' => array(),
 
9164
  ),
9165
  ),
9166
  'style' => array(
9167
+ array(
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(
9179
+ 'blacklisted_cdata_regex' => array(
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
+ ),
9186
+ 'tag_spec' => array(
9187
+ 'mandatory_parent' => 'head',
9188
+ 'spec_name' => 'style amp-custom',
9189
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#stylesheets',
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]',
9207
+ 'mandatory_parent' => 'head',
9208
+ 'spec_name' => 'head > style[amp-boilerplate]',
9209
+ 'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
9210
+ 'unique' => true,
9211
+ ),
9212
+ ),
9213
+ array(
9214
+ 'attr_spec_list' => array(
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]',
9227
  'mandatory_ancestor' => 'head',
9228
  'mandatory_parent' => 'noscript',
9230
  'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
9231
  'unique' => true,
9232
  ),
 
9233
  ),
9234
  array(
9235
  'attr_spec_list' => array(
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
+ ),
9246
  'tag_spec' => array(
 
 
 
 
9247
  'mandatory_parent' => 'body',
9248
  'spec_name' => 'style[amp-keyframes]',
9249
  'unique' => true,
9250
  ),
 
9251
  ),
9252
  ),
9253
  'sub' => array(
9254
  array(
9255
  'attr_spec_list' => array(),
9256
  'tag_spec' => array(),
 
9257
  ),
9258
  ),
9259
  'sup' => array(
9260
  array(
9261
  'attr_spec_list' => array(),
9262
  'tag_spec' => array(),
 
9263
  ),
9264
  ),
9265
  'svg' => array(
9347
  'zoomandpan' => array(),
9348
  ),
9349
  'tag_spec' => array(
 
 
 
 
9350
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9351
  ),
 
9352
  ),
9353
  ),
9354
  'switch' => array(
9426
  'xmlns:xlink' => array(),
9427
  ),
9428
  'tag_spec' => array(
 
 
 
 
9429
  'mandatory_ancestor' => 'svg',
9430
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9431
  ),
 
9432
  ),
9433
  ),
9434
  'symbol' => array(
9506
  'xmlns:xlink' => array(),
9507
  ),
9508
  'tag_spec' => array(
 
 
 
 
9509
  'mandatory_ancestor' => 'svg',
9510
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9511
  ),
 
9512
  ),
9513
  ),
9514
  'table' => array(
9525
  'width' => array(),
9526
  ),
9527
  'tag_spec' => array(),
 
9528
  ),
9529
  ),
9530
  'tbody' => array(
9531
  array(
9532
  'attr_spec_list' => array(),
9533
  'tag_spec' => array(),
 
9534
  ),
9535
  ),
9536
  'td' => array(
9546
  'width' => array(),
9547
  ),
9548
  'tag_spec' => array(),
 
9549
  ),
9550
  ),
9551
  'template' => array(
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',
9568
  ),
9569
  ),
9570
+ ),
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
+ ),
9583
+ 'spec_name' => 'amp-story-auto-ads > template',
9584
+ ),
9585
  ),
9586
  ),
9587
  'text' => array(
9668
  'y' => array(),
9669
  ),
9670
  'tag_spec' => array(
 
 
 
 
9671
  'mandatory_ancestor' => 'svg',
9672
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9673
  ),
 
9674
  ),
9675
  ),
9676
  'textarea' => array(
9698
  'maxlength' => array(),
9699
  'minlength' => array(),
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(),
9713
  'tag_spec' => array(
9714
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
9715
  ),
 
9716
  ),
9717
  ),
9718
  'textpath' => array(
9794
  'alternative_names' => array(
9795
  'href',
9796
  ),
9797
+ 'value_url' => array(
9798
+ 'allow_empty' => false,
9799
+ 'allow_relative' => true,
9800
+ 'allowed_protocol' => array(
9801
+ 'http',
9802
+ 'https',
9803
+ ),
9804
  ),
9805
  ),
9806
  'xlink:role' => array(),
9813
  'xmlns:xlink' => array(),
9814
  ),
9815
  'tag_spec' => array(
 
 
 
 
9816
  'mandatory_ancestor' => 'svg',
9817
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9818
  ),
 
9819
  ),
9820
  ),
9821
  'tfoot' => array(
9822
  array(
9823
  'attr_spec_list' => array(),
9824
  'tag_spec' => array(),
 
9825
  ),
9826
  ),
9827
  'th' => array(
9840
  'width' => array(),
9841
  ),
9842
  'tag_spec' => array(),
 
9843
  ),
9844
  ),
9845
  'thead' => array(
9846
  array(
9847
  'attr_spec_list' => array(),
9848
  'tag_spec' => array(),
 
9849
  ),
9850
  ),
9851
  'time' => array(
9854
  'datetime' => array(),
9855
  ),
9856
  'tag_spec' => array(),
 
9857
  ),
9858
  ),
9859
  'title' => array(
9862
  'tag_spec' => array(
9863
  'spec_name' => 'title',
9864
  ),
 
9865
  ),
9866
  array(
9867
  'attr_spec_list' => array(
9874
  'xmlns:xlink' => array(),
9875
  ),
9876
  'tag_spec' => array(
 
 
 
 
9877
  'mandatory_ancestor' => 'svg',
9878
  'spec_name' => 'svg title',
9879
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
9880
  ),
 
9881
  ),
9882
  ),
9883
  'tr' => array(
9889
  'valign' => array(),
9890
  ),
9891
  'tag_spec' => array(),
 
9892
  ),
9893
  ),
9894
  'track' => array(
9904
  'src' => array(
9905
  'blacklisted_value_regex' => '__amp_source_origin',
9906
  'mandatory' => true,
9907
+ 'value_url' => array(
9908
+ 'allow_relative' => false,
9909
+ 'allowed_protocol' => array(
9910
+ 'https',
9911
+ ),
9912
  ),
9913
  ),
9914
  'srclang' => array(),
9915
  ),
9916
  'tag_spec' => array(
 
 
 
 
9917
  'mandatory_parent' => 'audio',
9918
  'spec_name' => 'audio > track',
9919
  ),
 
9920
  ),
9921
  array(
9922
  'attr_spec_list' => array(
9931
  'src' => array(
9932
  'blacklisted_value_regex' => '__amp_source_origin',
9933
  'mandatory' => true,
9934
+ 'value_url' => array(
9935
+ 'allow_relative' => false,
9936
+ 'allowed_protocol' => array(
9937
+ 'https',
9938
+ ),
9939
  ),
9940
  ),
9941
  'srclang' => array(
9943
  ),
9944
  ),
9945
  'tag_spec' => array(
 
 
 
 
9946
  'mandatory_parent' => 'audio',
9947
  'spec_name' => 'audio > track[kind=subtitles]',
9948
  ),
 
9949
  ),
9950
  array(
9951
  'attr_spec_list' => array(
9959
  'src' => array(
9960
  'blacklisted_value_regex' => '__amp_source_origin',
9961
  'mandatory' => true,
9962
+ 'value_url' => array(
9963
+ 'allow_relative' => false,
9964
+ 'allowed_protocol' => array(
9965
+ 'https',
9966
+ ),
9967
  ),
9968
  ),
9969
  'srclang' => array(),
9970
  ),
9971
  'tag_spec' => array(
 
 
 
 
9972
  'mandatory_parent' => 'video',
9973
  'spec_name' => 'video > track',
9974
  ),
 
9975
  ),
9976
  array(
9977
  'attr_spec_list' => array(
9986
  'src' => array(
9987
  'blacklisted_value_regex' => '__amp_source_origin',
9988
  'mandatory' => true,
9989
+ 'value_url' => array(
9990
+ 'allow_relative' => false,
9991
+ 'allowed_protocol' => array(
9992
+ 'https',
9993
+ ),
9994
  ),
9995
  ),
9996
  'srclang' => array(
9998
  ),
9999
  ),
10000
  'tag_spec' => array(
 
 
 
 
10001
  'mandatory_parent' => 'video',
10002
  'spec_name' => 'video > track[kind=subtitles]',
10003
  ),
 
10004
  ),
10005
  array(
10006
  'attr_spec_list' => array(
10017
  'src' => array(
10018
  'blacklisted_value_regex' => '__amp_source_origin',
10019
  'mandatory' => true,
10020
+ 'value_url' => array(
10021
+ 'allow_relative' => false,
10022
+ 'allowed_protocol' => array(
10023
+ 'https',
10024
+ ),
10025
  ),
10026
  ),
10027
  'srclang' => array(),
10028
  ),
10029
  'tag_spec' => array(
 
 
 
 
10030
  'mandatory_parent' => 'amp-audio',
10031
  'spec_name' => 'amp-audio > track',
10032
  ),
 
10033
  ),
10034
  array(
10035
  'attr_spec_list' => array(
10047
  'src' => array(
10048
  'blacklisted_value_regex' => '__amp_source_origin',
10049
  'mandatory' => true,
10050
+ 'value_url' => array(
10051
+ 'allow_relative' => false,
10052
+ 'allowed_protocol' => array(
10053
+ 'https',
10054
+ ),
10055
  ),
10056
  ),
10057
  'srclang' => array(
10059
  ),
10060
  ),
10061
  'tag_spec' => array(
 
 
 
 
10062
  'mandatory_parent' => 'amp-audio',
10063
  'spec_name' => 'amp-audio > track[kind=subtitles]',
10064
  ),
 
10065
  ),
10066
  array(
10067
  'attr_spec_list' => array(
10078
  'src' => array(
10079
  'blacklisted_value_regex' => '__amp_source_origin',
10080
  'mandatory' => true,
10081
+ 'value_url' => array(
10082
+ 'allow_relative' => false,
10083
+ 'allowed_protocol' => array(
10084
+ 'https',
10085
+ ),
10086
  ),
10087
  ),
10088
  'srclang' => array(),
10089
  ),
10090
  'tag_spec' => array(
 
 
 
 
10091
  'mandatory_parent' => 'amp-video',
10092
  'spec_name' => 'amp-video > track',
10093
  ),
 
10094
  ),
10095
  array(
10096
  'attr_spec_list' => array(
10108
  'src' => array(
10109
  'blacklisted_value_regex' => '__amp_source_origin',
10110
  'mandatory' => true,
10111
+ 'value_url' => array(
10112
+ 'allow_relative' => false,
10113
+ 'allowed_protocol' => array(
10114
+ 'https',
10115
+ ),
10116
  ),
10117
  ),
10118
  'srclang' => array(
10120
  ),
10121
  ),
10122
  'tag_spec' => array(
 
 
 
 
10123
  'mandatory_parent' => 'amp-video',
10124
  'spec_name' => 'amp-video > track[kind=subtitles]',
10125
  ),
 
10126
  ),
10127
  array(
10128
  'attr_spec_list' => array(
10140
  'src' => array(
10141
  'blacklisted_value_regex' => '__amp_source_origin',
10142
  'mandatory' => true,
10143
+ 'value_url' => array(
10144
+ 'allow_relative' => false,
10145
+ 'allowed_protocol' => array(
10146
+ 'https',
10147
+ ),
10148
  ),
10149
  ),
10150
  'srclang' => array(
10152
  ),
10153
  ),
10154
  'tag_spec' => array(
 
 
 
 
10155
  'mandatory_parent' => 'amp-ima-video',
10156
  'spec_name' => 'amp-ima-video > track[kind=subtitles]',
10157
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-ima-video',
10158
  ),
 
10159
  ),
10160
  ),
10161
  'tref' => array(
10234
  'alternative_names' => array(
10235
  'href',
10236
  ),
10237
+ 'value_url' => array(
10238
+ 'allow_empty' => false,
10239
+ 'allow_relative' => true,
10240
+ 'allowed_protocol' => array(
10241
+ 'http',
10242
+ 'https',
10243
+ ),
10244
  ),
10245
  ),
10246
  'xlink:role' => array(),
10253
  'xmlns:xlink' => array(),
10254
  ),
10255
  'tag_spec' => array(
 
 
 
 
10256
  'mandatory_ancestor' => 'svg',
10257
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
10258
  ),
 
10259
  ),
10260
  ),
10261
  'tspan' => array(
10341
  'y' => array(),
10342
  ),
10343
  'tag_spec' => array(
 
 
 
 
10344
  'mandatory_ancestor' => 'svg',
10345
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
10346
  ),
 
10347
  ),
10348
  ),
10349
  'tt' => array(
10350
  array(
10351
  'attr_spec_list' => array(),
10352
+ 'tag_spec' => array(),
 
 
 
 
 
10353
  ),
10354
  ),
10355
  'u' => array(
10356
  array(
10357
  'attr_spec_list' => array(),
10358
  'tag_spec' => array(),
 
10359
  ),
10360
  ),
10361
  'ul' => array(
10362
  array(
10363
  'attr_spec_list' => array(),
10364
  'tag_spec' => array(),
 
10365
  ),
10366
  ),
10367
  'use' => array(
10444
  'alternative_names' => array(
10445
  'href',
10446
  ),
10447
+ 'value_url' => array(
10448
+ 'allow_empty' => false,
10449
+ 'allow_relative' => true,
10450
+ 'allowed_protocol' => array(
10451
+ 'http',
10452
+ 'https',
10453
+ ),
10454
  ),
10455
  ),
10456
  'xlink:role' => array(),
10464
  'y' => array(),
10465
  ),
10466
  'tag_spec' => array(
 
 
 
 
10467
  'mandatory_ancestor' => 'svg',
10468
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
10469
  ),
 
10470
  ),
10471
  ),
10472
  'var' => array(
10473
  array(
10474
  'attr_spec_list' => array(),
10475
  'tag_spec' => array(),
 
10476
  ),
10477
  ),
10478
  'video' => array(
10488
  'preload' => array(),
10489
  'src' => array(
10490
  'blacklisted_value_regex' => '__amp_source_origin',
10491
+ 'value_url' => array(
10492
+ 'allow_relative' => false,
10493
+ 'allowed_protocol' => array(
10494
+ 'data',
10495
+ 'https',
10496
+ ),
10497
  ),
10498
  ),
10499
  'width' => array(),
10500
  ),
10501
  'tag_spec' => array(
 
 
 
10502
  'mandatory_ancestor' => 'noscript',
10503
  'mandatory_ancestor_suggested_alternative' => 'amp-video',
10504
  'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-video',
10505
  ),
 
10506
  ),
10507
  ),
10508
  'view' => array(
10522
  'zoomandpan' => array(),
10523
  ),
10524
  'tag_spec' => array(
 
 
 
 
10525
  'mandatory_ancestor' => 'svg',
10526
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
10527
  ),
 
10528
  ),
10529
  ),
10530
  'vkern' => array(
10544
  'xmlns:xlink' => array(),
10545
  ),
10546
  'tag_spec' => array(
 
 
 
 
10547
  'mandatory_ancestor' => 'svg',
10548
  'spec_url' => 'https://www.ampproject.org/docs/reference/spec#svg',
10549
  ),
 
10550
  ),
10551
  ),
10552
  'wbr' => array(
10553
  array(
10554
  'attr_spec_list' => array(),
10555
  'tag_spec' => array(),
 
10556
  ),
10557
  ),
10558
  'xmp' => array(
10559
  array(
10560
  'attr_spec_list' => array(),
10561
+ 'tag_spec' => array(),
 
 
 
 
 
10562
  ),
10563
  ),
10564
  );
10571
  'layout' => array(),
10572
  'sizes' => array(),
10573
  'width' => array(),
 
10574
  );
10575
 
10576
 
10626
  'amp-access-show' => array(),
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(),
10634
  'aria-autocomplete' => array(),
10666
  'aria-valuenow' => array(),
10667
  'aria-valuetext' => array(),
10668
  'class' => array(
10669
+ 'blacklisted_value_regex' => '(^|\\W)i-amphtml-',
10670
  ),
10671
  'content' => array(),
10672
  'datatype' => array(),
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(
10683
+ 'blacklisted_value_regex' => '(^|\\s)(__amp_\\S*|__count__|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|__noSuchMethod__|__parent__|__proto__|__AMP_\\S*|\\$p|\\$proxy|acceptCharset|addEventListener|appendChild|assignedSlot|attachShadow|AMP|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|i-amphtml-\\S*|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|$)',
10684
  ),
10685
  'inlist' => array(),
10686
  'itemid' => array(),
10693
  'on' => array(),
10694
  'overflow' => array(),
10695
  'placeholder' => array(
10696
+ 'value' => '',
10697
  ),
10698
  'prefix' => array(),
10699
  'property' => array(),
10700
  'rel' => array(
10701
+ 'blacklisted_value_regex' => '(^|\\s)(canonical|components|dns-prefetch|import|manifest|preconnect|preload|prerender|serviceworker|stylesheet|subresource)(\\s|$)',
10702
  ),
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(),
10719
  'title' => array(),
10720
  'translate' => array(),
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
+ *
10733
+ * @since 0.5
10734
+ * @return array Allowed tags.
10735
+ */
10736
  public static function get_allowed_tags() {
10737
  return self::$allowed_tags;
10738
  }
10739
 
10740
+ /**
10741
+ * Get allowed tag.
10742
+ *
10743
+ * Get the rules for a single tag so that the entire data structure needn't be passed around.
10744
+ *
10745
+ * @since 0.7
10746
+ * @param string $node_name Tag name.
10747
+ * @return array|null Allowed tag, or null if the tag does not exist.
10748
+ */
10749
+ public static function get_allowed_tag( $node_name ) {
10750
+ if ( isset( self::$allowed_tags[ $node_name ] ) ) {
10751
+ return self::$allowed_tags[ $node_name ];
10752
+ }
10753
+ return null;
10754
+ }
10755
+
10756
+ /**
10757
+ * Get list of globally-allowed attributes.
10758
+ *
10759
+ * @since 0.5
10760
+ * @return array Allowed tag.
10761
+ */
10762
  public static function get_allowed_attributes() {
10763
  return self::$globally_allowed_attrs;
10764
  }
10765
 
10766
+ /**
10767
+ * Get layout attributes.
10768
+ *
10769
+ * @since 0.5
10770
+ * @return array Allowed tag.
10771
+ */
10772
  public static function get_layout_attributes() {
10773
  return self::$layout_allowed_attrs;
10774
  }
10775
 
10776
  }
 
 
includes/sanitizers/class-amp-audio-sanitizer.php CHANGED
@@ -20,42 +20,6 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
20
  */
21
  public static $tag = 'audio';
22
 
23
- /**
24
- * Script slug.
25
- *
26
- * @var string AMP HTML audio tag to use in place of HTML's 'audio' tag.
27
- *
28
- * @since 0.2
29
- */
30
- private static $script_slug = 'amp-audio';
31
-
32
- /**
33
- * Script src.
34
- *
35
- * @var string URL to AMP Project's Audio element javascript file found at cdn.ampproject.org
36
- *
37
- * @since 0.2
38
- */
39
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-audio-0.1.js';
40
-
41
- /**
42
- * Return one element array containing AMP HTML audio tag and respective Javascript URL
43
- *
44
- * HTML tags and Javascript URLs found at cdn.ampproject.org
45
- *
46
- * @since 0.2
47
- *
48
- * @return string[] Returns AMP HTML audio tag as array key and Javascript URL as array value,
49
- * respectively. Will return an empty array if sanitization has yet to be run
50
- * or if it did not find any HTML audio elements to convert to AMP equivalents.
51
- */
52
- public function get_scripts() {
53
- if ( ! $this->did_convert_elements ) {
54
- return array();
55
- }
56
- return array( self::$script_slug => self::$script_src );
57
- }
58
-
59
  /**
60
  * Sanitize the <audio> elements from the HTML contained in this instance's DOMDocument.
61
  *
@@ -117,7 +81,7 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
117
  * @see: https://github.com/ampproject/amphtml/issues/2261
118
  */
119
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
120
- $node->parentNode->removeChild( $node );
121
  } else {
122
  $node->parentNode->replaceChild( $new_node, $node );
123
  }
20
  */
21
  public static $tag = 'audio';
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  /**
24
  * Sanitize the <audio> elements from the HTML contained in this instance's DOMDocument.
25
  *
81
  * @see: https://github.com/ampproject/amphtml/issues/2261
82
  */
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
  }
includes/sanitizers/class-amp-base-sanitizer.php CHANGED
@@ -43,17 +43,26 @@ abstract class AMP_Base_Sanitizer {
43
  * @var array {
44
  * @type int $content_max_width
45
  * @type bool $add_placeholder
 
46
  * @type bool $require_https_src
47
  * @type string[] $amp_allowed_tags
48
  * @type string[] $amp_globally_allowed_attributes
49
  * @type string[] $amp_layout_allowed_attributes
 
 
 
 
 
 
 
 
50
  * }
51
  */
52
  protected $args;
53
 
54
  /**
55
  * Flag to be set in child class' sanitize() method indicating if the
56
- * HTML contained in the DOMDocument has been santized yet or not.
57
  *
58
  * @since 0.2
59
  *
@@ -61,13 +70,20 @@ abstract class AMP_Base_Sanitizer {
61
  */
62
  protected $did_convert_elements = false;
63
 
 
 
 
 
 
 
 
64
  /**
65
  * AMP_Base_Sanitizer constructor.
66
  *
67
  * @since 0.2
68
  *
69
  * @param DOMDocument $dom Represents the HTML document to sanitize.
70
- * @param array $args array {
71
  * Args.
72
  *
73
  * @type int $content_max_width
@@ -81,6 +97,12 @@ abstract class AMP_Base_Sanitizer {
81
  public function __construct( $dom, $args = array() ) {
82
  $this->dom = $dom;
83
  $this->args = array_merge( $this->DEFAULT_ARGS, $args );
 
 
 
 
 
 
84
  }
85
 
86
  /**
@@ -96,7 +118,9 @@ abstract class AMP_Base_Sanitizer {
96
  *
97
  * @since 0.2
98
  *
99
- * @return string[] This are empty in this the base class.
 
 
100
  */
101
  public function get_scripts() {
102
  return array();
@@ -107,19 +131,39 @@ abstract class AMP_Base_Sanitizer {
107
  *
108
  * @since 0.4
109
  *
110
- * @return string[] This are empty in this the base class.
111
  */
112
  public function get_styles() {
113
  return array();
114
  }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  /**
117
  * Get HTML body as DOMElement from DOMDocument received by the constructor.
118
  *
119
- * @return DOMElement
 
120
  */
121
  protected function get_body_node() {
122
- return $this->dom->getElementsByTagName( 'body' )->item( 0 );
 
123
  }
124
 
125
  /**
@@ -131,12 +175,15 @@ abstract class AMP_Base_Sanitizer {
131
  * @return float|int|string Returns a numeric dimension value, or an empty string.
132
  */
133
  public function sanitize_dimension( $value, $dimension ) {
134
- if ( empty( $value ) ) {
 
 
135
  return '';
136
  }
137
 
138
- if ( false !== filter_var( $value, FILTER_VALIDATE_INT ) ) {
139
- return absint( $value );
 
140
  }
141
 
142
  if ( AMP_String_Utils::endswith( $value, 'px' ) ) {
@@ -154,7 +201,7 @@ abstract class AMP_Base_Sanitizer {
154
  }
155
 
156
  /**
157
- * Enforce fixed height.
158
  *
159
  * @param string[] $attributes {
160
  * Attributes.
@@ -167,12 +214,11 @@ abstract class AMP_Base_Sanitizer {
167
  * }
168
  * @return string[]
169
  */
170
- public function enforce_fixed_height( $attributes ) {
171
  if ( empty( $attributes['height'] ) ) {
172
  unset( $attributes['width'] );
173
  $attributes['height'] = self::FALLBACK_HEIGHT;
174
  }
175
-
176
  if ( empty( $attributes['width'] ) ) {
177
  $attributes['layout'] = 'fixed-height';
178
  }
@@ -180,42 +226,6 @@ abstract class AMP_Base_Sanitizer {
180
  return $attributes;
181
  }
182
 
183
- /**
184
- * This is our workaround to enforce max sizing with layout=responsive.
185
- *
186
- * We want elements to not grow beyond their width and shrink to fill the screen on viewports smaller than their width.
187
- *
188
- * See https://github.com/ampproject/amphtml/issues/1280#issuecomment-171533526
189
- * See https://github.com/Automattic/amp-wp/issues/101
190
- *
191
- * @param string[] $attributes {
192
- * Attributes.
193
- *
194
- * @type int $height
195
- * @type int $width
196
- * @type string $sizes
197
- * @type string $class
198
- * @type string $layout
199
- * }
200
- * @return string[]
201
- */
202
- public function enforce_sizes_attribute( $attributes ) {
203
- if ( ! isset( $attributes['width'], $attributes['height'] ) ) {
204
- return $attributes;
205
- }
206
-
207
- $max_width = $attributes['width'];
208
- if ( isset( $this->args['content_max_width'] ) && $max_width >= $this->args['content_max_width'] ) {
209
- $max_width = $this->args['content_max_width'];
210
- }
211
-
212
- $attributes['sizes'] = sprintf( '(min-width: %1$dpx) %1$dpx, 100vw', absint( $max_width ) );
213
-
214
- $this->add_or_append_attribute( $attributes, 'class', 'amp-wp-enforced-sizes' );
215
-
216
- return $attributes;
217
- }
218
-
219
  /**
220
  * Adds or appends key and value to list of attributes
221
  *
@@ -268,4 +278,68 @@ abstract class AMP_Base_Sanitizer {
268
 
269
  return $src;
270
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
43
  * @var array {
44
  * @type int $content_max_width
45
  * @type bool $add_placeholder
46
+ * @type bool $use_document_element
47
  * @type bool $require_https_src
48
  * @type string[] $amp_allowed_tags
49
  * @type string[] $amp_globally_allowed_attributes
50
  * @type string[] $amp_layout_allowed_attributes
51
+ * @type array $amp_allowed_tags
52
+ * @type array $amp_globally_allowed_attributes
53
+ * @type array $amp_layout_allowed_attributes
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;
62
 
63
  /**
64
  * Flag to be set in child class' sanitize() method indicating if the
65
+ * HTML contained in the DOMDocument has been sanitized yet or not.
66
  *
67
  * @since 0.2
68
  *
70
  */
71
  protected $did_convert_elements = false;
72
 
73
+ /**
74
+ * The root element used for sanitization. Either html or body.
75
+ *
76
+ * @var DOMElement
77
+ */
78
+ protected $root_element;
79
+
80
  /**
81
  * AMP_Base_Sanitizer constructor.
82
  *
83
  * @since 0.2
84
  *
85
  * @param DOMDocument $dom Represents the HTML document to sanitize.
86
+ * @param array $args {
87
  * Args.
88
  *
89
  * @type int $content_max_width
97
  public function __construct( $dom, $args = array() ) {
98
  $this->dom = $dom;
99
  $this->args = array_merge( $this->DEFAULT_ARGS, $args );
100
+
101
+ if ( ! empty( $this->args['use_document_element'] ) ) {
102
+ $this->root_element = $this->dom->documentElement;
103
+ } else {
104
+ $this->root_element = $this->dom->getElementsByTagName( 'body' )->item( 0 );
105
+ }
106
  }
107
 
108
  /**
118
  *
119
  * @since 0.2
120
  *
121
+ * @return string[] Returns component name as array key and JavaScript URL as array value,
122
+ * respectively. Will return an empty array if sanitization has yet to be run
123
+ * or if it did not find any HTML elements to convert to AMP equivalents.
124
  */
125
  public function get_scripts() {
126
  return array();
131
  *
132
  * @since 0.4
133
  *
134
+ * @return array[][] Mapping of CSS selectors to arrays of properties.
135
  */
136
  public function get_styles() {
137
  return array();
138
  }
139
 
140
+ /**
141
+ * Get stylesheets.
142
+ *
143
+ * @since 0.7
144
+ * @returns array Values are the CSS stylesheets. Keys are MD5 hashes of the stylesheets.
145
+ */
146
+ public function get_stylesheets() {
147
+ $stylesheets = array();
148
+
149
+ foreach ( $this->get_styles() as $selector => $properties ) {
150
+ $stylesheet = sprintf( '%s { %s }', $selector, join( '; ', $properties ) . ';' );
151
+
152
+ $stylesheets[ md5( $stylesheet ) ] = $stylesheet;
153
+ }
154
+
155
+ return $stylesheets;
156
+ }
157
+
158
  /**
159
  * Get HTML body as DOMElement from DOMDocument received by the constructor.
160
  *
161
+ * @deprecated Just reference $root_element instead.
162
+ * @return DOMElement The body or html element.
163
  */
164
  protected function get_body_node() {
165
+ _deprecated_function( __METHOD__, 'AMP_Base_Sanitizer::$root_element', '0.7' );
166
+ return $this->root_element;
167
  }
168
 
169
  /**
175
  * @return float|int|string Returns a numeric dimension value, or an empty string.
176
  */
177
  public function sanitize_dimension( $value, $dimension ) {
178
+
179
+ // Allows 0 to be used as valid dimension.
180
+ if ( null === $value ) {
181
  return '';
182
  }
183
 
184
+ // Accepts both integers and floats & prevents negative values.
185
+ if ( is_numeric( $value ) ) {
186
+ return max( 0, floatval( $value ) );
187
  }
188
 
189
  if ( AMP_String_Utils::endswith( $value, 'px' ) ) {
201
  }
202
 
203
  /**
204
+ * Sets the layout, and possibly the 'height' and 'width' attributes.
205
  *
206
  * @param string[] $attributes {
207
  * Attributes.
214
  * }
215
  * @return string[]
216
  */
217
+ public function set_layout( $attributes ) {
218
  if ( empty( $attributes['height'] ) ) {
219
  unset( $attributes['width'] );
220
  $attributes['height'] = self::FALLBACK_HEIGHT;
221
  }
 
222
  if ( empty( $attributes['width'] ) ) {
223
  $attributes['layout'] = 'fixed-height';
224
  }
226
  return $attributes;
227
  }
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  /**
230
  * Adds or appends key and value to list of attributes
231
  *
278
 
279
  return $src;
280
  }
281
+
282
+ /**
283
+ * Removes an invalid child of a node.
284
+ *
285
+ * Also, calls the mutation callback for it.
286
+ * This tracks all the nodes that were removed.
287
+ *
288
+ * @since 0.7
289
+ *
290
+ * @param DOMNode|DOMElement $node The node to remove.
291
+ * @param array $args Additional args to pass to validation error callback.
292
+ *
293
+ * @return void
294
+ */
295
+ public function remove_invalid_child( $node, $args = array() ) {
296
+ if ( isset( $this->args['validation_error_callback'] ) ) {
297
+ call_user_func( $this->args['validation_error_callback'],
298
+ array_merge( compact( 'node' ), $args )
299
+ );
300
+ }
301
+ if ( empty( $this->args['disable_invalid_removal'] ) ) {
302
+ $node->parentNode->removeChild( $node );
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Removes an invalid attribute of a node.
308
+ *
309
+ * Also, calls the mutation callback for it.
310
+ * This tracks all the attributes that were removed.
311
+ *
312
+ * @since 0.7
313
+ *
314
+ * @param DOMElement $element The node for which to remove the attribute.
315
+ * @param DOMAttr|string $attribute The attribute to remove from the element.
316
+ * @param array $args Additional args to pass to validation error callback.
317
+ * @return void
318
+ */
319
+ public function remove_invalid_attribute( $element, $attribute, $args = array() ) {
320
+ if ( isset( $this->args['validation_error_callback'] ) ) {
321
+ if ( is_string( $attribute ) ) {
322
+ $attribute = $element->getAttributeNode( $attribute );
323
+ }
324
+ if ( $attribute ) {
325
+ call_user_func( $this->args['validation_error_callback'],
326
+ array_merge(
327
+ array(
328
+ 'node' => $attribute,
329
+ ),
330
+ $args
331
+ )
332
+ );
333
+ if ( empty( $this->args['disable_invalid_removal'] ) ) {
334
+ $element->removeAttributeNode( $attribute );
335
+ }
336
+ }
337
+ } elseif ( empty( $this->args['disable_invalid_removal'] ) ) {
338
+ if ( is_string( $attribute ) ) {
339
+ $element->removeAttribute( $attribute );
340
+ } else {
341
+ $element->removeAttributeNode( $attribute );
342
+ }
343
+ }
344
+ }
345
  }
includes/sanitizers/class-amp-blacklist-sanitizer.php CHANGED
@@ -32,11 +32,13 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
32
  * Sanitize.
33
  */
34
  public function sanitize() {
 
 
35
  $blacklisted_tags = $this->get_blacklisted_tags();
36
  $blacklisted_attributes = $this->get_blacklisted_attributes();
37
  $blacklisted_protocols = $this->get_blacklisted_protocols();
38
 
39
- $body = $this->get_body_node();
40
  $this->strip_tags( $body, $blacklisted_tags );
41
  $this->strip_attributes_recursive( $body, $blacklisted_attributes, $blacklisted_protocols );
42
  }
@@ -71,13 +73,13 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
71
  $attribute = $node->attributes->item( $i );
72
  $attribute_name = strtolower( $attribute->name );
73
  if ( in_array( $attribute_name, $bad_attributes, true ) ) {
74
- $node->removeAttribute( $attribute_name );
75
  continue;
76
  }
77
 
78
  // The on* attributes (like onclick) are a special case.
79
  if ( 0 === stripos( $attribute_name, 'on' ) && 'on' !== $attribute_name ) {
80
- $node->removeAttribute( $attribute_name );
81
  continue;
82
  } elseif ( 'a' === $node_name ) {
83
  $this->sanitize_a_attribute( $node, $attribute );
@@ -110,10 +112,10 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
110
  for ( $i = $length - 1; $i >= 0; $i-- ) {
111
  $element = $elements->item( $i );
112
  $parent_node = $element->parentNode;
113
- $parent_node->removeChild( $element );
114
 
115
  if ( 'body' !== $parent_node->nodeName && AMP_DOM_Utils::is_node_empty( $parent_node ) ) {
116
- $parent_node->parentNode->removeChild( $parent_node );
117
  }
118
  }
119
  }
@@ -132,13 +134,13 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
132
  $old_value = $attribute->value;
133
  $new_value = trim( preg_replace( self::PATTERN_REL_WP_ATTACHMENT, '', $old_value ) );
134
  if ( empty( $new_value ) ) {
135
- $node->removeAttribute( $attribute_name );
136
  } elseif ( $old_value !== $new_value ) {
137
  $node->setAttribute( $attribute_name, $new_value );
138
  }
139
  } elseif ( 'rev' === $attribute_name ) {
140
  // rev removed from HTML5 spec, which was used by Jetpack Markdown.
141
- $node->removeAttribute( $attribute_name );
142
  } elseif ( 'target' === $attribute_name ) {
143
  // _blank is the only allowed value and it must be lowercase.
144
  // replace _new with _blank and others should simply be removed.
@@ -148,7 +150,7 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
148
  $node->setAttribute( $attribute_name, '_blank' );
149
  } else {
150
  // Only _blank is allowed.
151
- $node->removeAttribute( $attribute_name );
152
  }
153
  }
154
  }
@@ -217,7 +219,7 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
217
 
218
  // Remove the node from the parent, if defined.
219
  if ( $node->parentNode ) {
220
- $node->parentNode->removeChild( $node );
221
  }
222
  }
223
 
32
  * Sanitize.
33
  */
34
  public function sanitize() {
35
+ _deprecated_function( __METHOD__, '0.7', 'AMP_Tag_And_Attribute_Sanitizer::sanitize' );
36
+
37
  $blacklisted_tags = $this->get_blacklisted_tags();
38
  $blacklisted_attributes = $this->get_blacklisted_attributes();
39
  $blacklisted_protocols = $this->get_blacklisted_protocols();
40
 
41
+ $body = $this->root_element;
42
  $this->strip_tags( $body, $blacklisted_tags );
43
  $this->strip_attributes_recursive( $body, $blacklisted_attributes, $blacklisted_protocols );
44
  }
73
  $attribute = $node->attributes->item( $i );
74
  $attribute_name = strtolower( $attribute->name );
75
  if ( in_array( $attribute_name, $bad_attributes, true ) ) {
76
+ $this->remove_invalid_attribute( $node, $attribute_name );
77
  continue;
78
  }
79
 
80
  // The on* attributes (like onclick) are a special case.
81
  if ( 0 === stripos( $attribute_name, 'on' ) && 'on' !== $attribute_name ) {
82
+ $this->remove_invalid_attribute( $node, $attribute_name );
83
  continue;
84
  } elseif ( 'a' === $node_name ) {
85
  $this->sanitize_a_attribute( $node, $attribute );
112
  for ( $i = $length - 1; $i >= 0; $i-- ) {
113
  $element = $elements->item( $i );
114
  $parent_node = $element->parentNode;
115
+ $this->remove_invalid_child( $element );
116
 
117
  if ( 'body' !== $parent_node->nodeName && AMP_DOM_Utils::is_node_empty( $parent_node ) ) {
118
+ $this->remove_invalid_child( $parent_node );
119
  }
120
  }
121
  }
134
  $old_value = $attribute->value;
135
  $new_value = trim( preg_replace( self::PATTERN_REL_WP_ATTACHMENT, '', $old_value ) );
136
  if ( empty( $new_value ) ) {
137
+ $this->remove_invalid_attribute( $node, $attribute_name );
138
  } elseif ( $old_value !== $new_value ) {
139
  $node->setAttribute( $attribute_name, $new_value );
140
  }
141
  } elseif ( 'rev' === $attribute_name ) {
142
  // rev removed from HTML5 spec, which was used by Jetpack Markdown.
143
+ $this->remove_invalid_attribute( $node, $attribute_name );
144
  } elseif ( 'target' === $attribute_name ) {
145
  // _blank is the only allowed value and it must be lowercase.
146
  // replace _new with _blank and others should simply be removed.
150
  $node->setAttribute( $attribute_name, '_blank' );
151
  } else {
152
  // Only _blank is allowed.
153
+ $this->remove_invalid_attribute( $node, $attribute_name );
154
  }
155
  }
156
  }
219
 
220
  // Remove the node from the parent, if defined.
221
  if ( $node->parentNode ) {
222
+ $this->remove_invalid_child( $node );
223
  }
224
  }
225
 
includes/sanitizers/class-amp-comments-sanitizer.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Comments_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Comments_Sanitizer
10
+ *
11
+ * Strips and corrects attributes in forms.
12
+ */
13
+ class AMP_Comments_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Pre-process the comment form and comment list for AMP.
17
+ *
18
+ * @since 0.7
19
+ */
20
+ public function sanitize() {
21
+
22
+ foreach ( $this->dom->getElementsByTagName( 'form' ) as $comment_form ) {
23
+ /**
24
+ * Comment form.
25
+ *
26
+ * @var DOMElement $comment_form
27
+ */
28
+ $action = $comment_form->getAttribute( 'action-xhr' );
29
+ if ( ! $action ) {
30
+ $action = $comment_form->getAttribute( 'action' );
31
+ }
32
+ $action_path = wp_parse_url( $action, PHP_URL_PATH );
33
+ if ( preg_match( '#/wp-comments-post\.php$#', $action_path ) ) {
34
+ $this->process_comment_form( $comment_form );
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Comment form.
41
+ *
42
+ * @param DOMElement $comment_form Comment form.
43
+ */
44
+ protected function process_comment_form( $comment_form ) {
45
+ /**
46
+ * Element.
47
+ *
48
+ * @var DOMElement $element
49
+ */
50
+
51
+ /**
52
+ * Named input elements.
53
+ *
54
+ * @var DOMElement[][] $form_fields
55
+ */
56
+ $form_fields = array();
57
+ foreach ( $comment_form->getElementsByTagName( 'input' ) as $element ) {
58
+ $name = $element->getAttribute( 'name' );
59
+ if ( $name ) {
60
+ $form_fields[ $name ][] = $element;
61
+ }
62
+ }
63
+ foreach ( $comment_form->getElementsByTagName( 'textarea' ) as $element ) {
64
+ $name = $element->getAttribute( 'name' );
65
+ if ( $name ) {
66
+ $form_fields[ $name ][] = $element;
67
+ }
68
+ }
69
+
70
+ if ( empty( $form_fields['comment_post_ID'] ) ) {
71
+ return;
72
+ }
73
+ $post_id = (int) $form_fields['comment_post_ID'][0]->getAttribute( 'value' );
74
+ $state_id = AMP_Theme_Support::get_comment_form_state_id( $post_id );
75
+
76
+ $form_state = array(
77
+ 'values' => array(),
78
+ 'submitting' => false,
79
+ 'replyToName' => '',
80
+ );
81
+
82
+ if ( ! empty( $form_fields['comment_parent'] ) ) {
83
+ $comment_id = (int) $form_fields['comment_parent'][0]->getAttribute( 'value' );
84
+ if ( $comment_id ) {
85
+ $reply_comment = get_comment( $comment_id );
86
+ if ( $reply_comment ) {
87
+ $form_state['replyToName'] = $reply_comment->comment_author;
88
+ }
89
+ }
90
+ }
91
+
92
+ $amp_bind_attr_format = AMP_DOM_Utils::get_amp_bind_placeholder_prefix() . '%s';
93
+ foreach ( $form_fields as $name => $form_field ) {
94
+ foreach ( $form_field as $element ) {
95
+
96
+ // @todo Radio and checkbox inputs are not supported yet.
97
+ if ( in_array( strtolower( $element->getAttribute( 'type' ) ), array( 'checkbox', 'radio' ), true ) ) {
98
+ continue;
99
+ }
100
+
101
+ $element->setAttribute( sprintf( $amp_bind_attr_format, 'disabled' ), "$state_id.submitting" );
102
+
103
+ if ( 'textarea' === strtolower( $element->nodeName ) ) {
104
+ $form_state['values'][ $name ] = $element->textContent;
105
+ $element->setAttribute( sprintf( $amp_bind_attr_format, 'text' ), "$state_id.values.$name" );
106
+ } else {
107
+ $form_state['values'][ $name ] = $element->hasAttribute( 'value' ) ? $element->getAttribute( 'value' ) : '';
108
+ $element->setAttribute( sprintf( $amp_bind_attr_format, 'value' ), "$state_id.values.$name" );
109
+ }
110
+
111
+ // Update the state in response to changing the input.
112
+ $element->setAttribute( 'on', sprintf(
113
+ 'change:AMP.setState( { %s: { values: { %s: event.value } } } )',
114
+ $state_id,
115
+ wp_json_encode( $name )
116
+ ) );
117
+ }
118
+ }
119
+
120
+ // Add amp-state to the document.
121
+ $amp_state = $this->dom->createElement( 'amp-state' );
122
+ $amp_state->setAttribute( 'id', $state_id );
123
+ $script = $this->dom->createElement( 'script' );
124
+ $script->setAttribute( 'type', 'application/json' );
125
+ $amp_state->appendChild( $script );
126
+ $script->appendChild( $this->dom->createTextNode( wp_json_encode( $form_state ) ) );
127
+ $comment_form->insertBefore( $amp_state, $comment_form->firstChild );
128
+
129
+ // Update state when submitting form.
130
+ $form_reset_state = $form_state;
131
+ unset(
132
+ $form_reset_state['values']['author'],
133
+ $form_reset_state['values']['email'],
134
+ $form_reset_state['values']['url']
135
+ );
136
+ $on = array(
137
+ // Disable the form when submitting.
138
+ sprintf(
139
+ 'submit:AMP.setState( { %s: { submitting: true } } )',
140
+ wp_json_encode( $state_id )
141
+ ),
142
+ // Re-enable the form fields when the submission fails.
143
+ sprintf(
144
+ 'submit-error:AMP.setState( { %s: { submitting: false } } )',
145
+ wp_json_encode( $state_id )
146
+ ),
147
+ // Reset the form to its initial state (with enabled form fields), except for the author, email, and url.
148
+ sprintf(
149
+ 'submit-success:AMP.setState( { %s: %s } )',
150
+ $state_id,
151
+ wp_json_encode( $form_reset_state )
152
+ ),
153
+ );
154
+ $comment_form->setAttribute( 'on', implode( ';', $on ) );
155
+ }
156
+ }
includes/sanitizers/class-amp-form-sanitizer.php ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Form_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ * @since 0.7
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Form_Sanitizer
11
+ *
12
+ * Strips and corrects attributes in forms.
13
+ *
14
+ * @since 0.7
15
+ */
16
+ class AMP_Form_Sanitizer extends AMP_Base_Sanitizer {
17
+
18
+ /**
19
+ * Tag.
20
+ *
21
+ * @var string HTML <form> tag to identify and process.
22
+ *
23
+ * @since 0.7
24
+ */
25
+ public static $tag = 'form';
26
+
27
+ /**
28
+ * Sanitize the <form> elements from the HTML contained in this instance's DOMDocument.
29
+ *
30
+ * @link https://www.ampproject.org/docs/reference/components/amp-form
31
+ * @since 0.7
32
+ */
33
+ public function sanitize() {
34
+
35
+ /**
36
+ * Node list.
37
+ *
38
+ * @var DOMNodeList $node
39
+ */
40
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
41
+ $num_nodes = $nodes->length;
42
+
43
+ if ( 0 === $num_nodes ) {
44
+ return;
45
+ }
46
+
47
+ for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
48
+ $node = $nodes->item( $i );
49
+ if ( ! $node instanceof DOMElement ) {
50
+ continue;
51
+ }
52
+
53
+ // In HTML, the default method is 'get'.
54
+ $method = 'get';
55
+ if ( $node->getAttribute( 'method' ) ) {
56
+ $method = strtolower( $node->getAttribute( 'method' ) );
57
+ } else {
58
+ $node->setAttribute( 'method', $method );
59
+ }
60
+
61
+ /*
62
+ * In HTML, the default action is just the current URL that the page is served from.
63
+ * The action "specifies a server endpoint to handle the form input. The value must be an
64
+ * https URL and must not be a link to a CDN".
65
+ */
66
+ if ( ! $node->getAttribute( 'action' ) ) {
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
+
73
+ // Make HTTP URLs protocol-less, since HTTPS is required for forms.
74
+ if ( 'http://' === strtolower( substr( $action_url, 0, 7 ) ) ) {
75
+ $action_url = substr( $action_url, 5 );
76
+ }
77
+
78
+ /*
79
+ * "For GET submissions, provide at least one of action or action-xhr".
80
+ * "This attribute is required for method=GET. For method=POST, the
81
+ * action attribute is invalid, use action-xhr instead".
82
+ */
83
+ if ( 'get' === $method ) {
84
+ if ( $action_url !== $node->getAttribute( 'action' ) ) {
85
+ $node->setAttribute( 'action', $action_url );
86
+ }
87
+ } elseif ( 'post' === $method ) {
88
+ $node->removeAttribute( 'action' );
89
+ if ( ! $xhr_action ) {
90
+ // record that action was converted tp action-xhr.
91
+ $action_url = add_query_arg( '_wp_amp_action_xhr_converted', 1, $action_url );
92
+ $node->setAttribute( 'action-xhr', $action_url );
93
+ // Append error handler if not found.
94
+ $this->ensure_submit_error_element( $node );
95
+ } elseif ( 'http://' === substr( $xhr_action, 0, 7 ) ) {
96
+ $node->setAttribute( 'action-xhr', substr( $xhr_action, 5 ) );
97
+ }
98
+ }
99
+
100
+ /*
101
+ * The target "indicates where to display the form response after submitting the form.
102
+ * The value must be _blank or _top". The _self and _parent values are treated
103
+ * as synonymous with _top, and anything else is treated like _blank.
104
+ */
105
+ $target = $node->getAttribute( 'target' );
106
+ if ( '_top' !== $target ) {
107
+ if ( ! $target || in_array( $target, array( '_self', '_parent' ), true ) ) {
108
+ $node->setAttribute( 'target', '_top' );
109
+ } elseif ( '_blank' !== $target ) {
110
+ $node->setAttribute( 'target', '_blank' );
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Checks if the form has an error handler else create one if not.
118
+ *
119
+ * @link https://www.ampproject.org/docs/reference/components/amp-form#success/error-response-rendering
120
+ * @since 0.7
121
+ *
122
+ * @param DOMElement $form The form node to check.
123
+ */
124
+ public function ensure_submit_error_element( $form ) {
125
+ $templates = $form->getElementsByTagName( 'template' );
126
+ for ( $i = $templates->length - 1; $i >= 0; $i-- ) {
127
+ if ( $templates->item( $i )->parentNode->hasAttribute( 'submit-error' ) ) {
128
+ return; // Found error template, do nothing.
129
+ }
130
+ }
131
+
132
+ $div = $this->dom->createElement( 'div' );
133
+ $template = $this->dom->createElement( 'template' );
134
+ $mustache = $this->dom->createTextNode( '{{{error}}}' );
135
+ $div->setAttribute( 'submit-error', '' );
136
+ $template->setAttribute( 'type', 'amp-mustache' );
137
+ $template->appendChild( $mustache );
138
+ $div->appendChild( $template );
139
+ $form->appendChild( $div );
140
+ }
141
+ }
includes/sanitizers/class-amp-iframe-sanitizer.php CHANGED
@@ -39,24 +39,6 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
39
  */
40
  public static $tag = 'iframe';
41
 
42
- /**
43
- * Script slug.
44
- *
45
- * @var string AMP HTML tag to use in place of HTML's <iframe> tag.
46
- *
47
- * @since 0.2
48
- */
49
- private static $script_slug = 'amp-iframe';
50
-
51
- /**
52
- * Script src.
53
- *
54
- * @var string URL to AMP Project's IFrame element's JavaScript file found at cdn.ampproject.org
55
- *
56
- * @since 0.2
57
- */
58
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-iframe-0.1.js';
59
-
60
  /**
61
  * Default args.
62
  *
@@ -66,24 +48,6 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
66
  'add_placeholder' => false,
67
  );
68
 
69
- /**
70
- * Return one element array containing AMP HTML iframe tag and respective Javascript URL
71
- *
72
- * HTML tags and Javascript URLs found at cdn.ampproject.org
73
- *
74
- * @since 0.2
75
- *
76
- * @return string[] Returns AMP HTML iframe tag as array key and Javascript URL as array value,
77
- * respectively. Will return an empty array if sanitization has yet to be run
78
- * or if it did not find any HTML iframe elements to convert to AMP equivalents.
79
- */
80
- public function get_scripts() {
81
- if ( ! $this->did_convert_elements ) {
82
- return array();
83
- }
84
- return array( self::$script_slug => self::$script_src );
85
- }
86
-
87
  /**
88
  * Sanitize the <iframe> elements from the HTML contained in this instance's DOMDocument.
89
  *
@@ -110,14 +74,16 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
110
  * @see: https://github.com/ampproject/amphtml/issues/2261
111
  */
112
  if ( empty( $new_attributes['src'] ) ) {
113
- $node->parentNode->removeChild( $node );
114
  continue;
115
  }
116
 
117
  $this->did_convert_elements = true;
118
-
119
- $new_attributes = $this->enforce_fixed_height( $new_attributes );
120
- $new_attributes = $this->enforce_sizes_attribute( $new_attributes );
 
 
121
 
122
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-iframe', $new_attributes );
123
 
@@ -228,4 +194,5 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
228
 
229
  return $placeholder_node;
230
  }
 
231
  }
39
  */
40
  public static $tag = 'iframe';
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  /**
43
  * Default args.
44
  *
48
  'add_placeholder' => false,
49
  );
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  /**
52
  * Sanitize the <iframe> elements from the HTML contained in this instance's DOMDocument.
53
  *
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
 
194
 
195
  return $placeholder_node;
196
  }
197
+
198
  }
includes/sanitizers/class-amp-img-sanitizer.php CHANGED
@@ -46,43 +46,6 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
46
  */
47
  private static $anim_extension = '.gif';
48
 
49
- /**
50
- * Script slug.
51
- *
52
- * @var string AMP HTML tag to use in place of HTML's <img> tag.
53
- *
54
- * @since 0.2
55
- */
56
- private static $script_slug = 'amp-anim';
57
-
58
- /**
59
- * Script src.
60
- *
61
- * @var string URL to AMP Project's Image element's JavaScript file found at cdn.ampproject.org
62
- *
63
- * @since 0.2
64
- */
65
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-anim-0.1.js';
66
-
67
- /**
68
- * Return one element array containing AMP HTML image tag and respective Javascript URL
69
- *
70
- * HTML tags and Javascript URLs found at cdn.ampproject.org
71
- *
72
- * @since 0.2
73
- *
74
- * @return string[] Returns AMP HTML image tag as array key and Javascript URL
75
- * as array value, respectively. Will return an empty array
76
- * if sanitization has yet to be run or if it did not find any
77
- * HTML image elements to convert to AMP equivalents.
78
- */
79
- public function get_scripts() {
80
- if ( ! $this->did_convert_elements ) {
81
- return array();
82
- }
83
- return array( self::$script_slug => self::$script_src );
84
- }
85
-
86
  /**
87
  * Sanitize the <img> elements from the HTML contained in this instance's DOMDocument.
88
  *
@@ -110,13 +73,13 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
110
  continue;
111
  }
112
 
113
- if ( ! $node->hasAttribute( 'src' ) || '' === $node->getAttribute( 'src' ) ) {
114
- $node->parentNode->removeChild( $node );
115
  continue;
116
  }
117
 
118
  // Determine which images need their dimensions determined/extracted.
119
- if ( '' === $node->getAttribute( 'width' ) || '' === $node->getAttribute( 'height' ) ) {
120
  $need_dimensions[ $node->getAttribute( 'src' ) ][] = $node;
121
  } else {
122
  $this->adjust_and_replace_node( $node );
@@ -156,7 +119,6 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
156
  case 'alt':
157
  case 'class':
158
  case 'srcset':
159
- case 'sizes':
160
  case 'on':
161
  case 'attribution':
162
  $out[ $name ] = $value;
@@ -167,6 +129,10 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
167
  $out[ $name ] = $this->sanitize_dimension( $value, $name );
168
  break;
169
 
 
 
 
 
170
  default:
171
  break;
172
  }
@@ -191,18 +157,30 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
191
  if ( ! $node instanceof DOMElement ) {
192
  continue;
193
  }
194
-
195
- // Provide default dimensions for images whose dimensions we couldn't fetch.
196
- if ( false !== $dimensions ) {
197
- $node->setAttribute( 'width', $dimensions['width'] );
198
- $node->setAttribute( 'height', $dimensions['height'] );
199
- } else {
200
- $width = isset( $this->args['content_max_width'] ) ? $this->args['content_max_width'] : self::FALLBACK_WIDTH;
201
  $height = self::FALLBACK_HEIGHT;
 
202
  $node->setAttribute( 'width', $width );
203
  $node->setAttribute( 'height', $height );
204
  $class = $node->hasAttribute( 'class' ) ? $node->getAttribute( 'class' ) . ' amp-wp-unknown-size' : 'amp-wp-unknown-size';
205
  $node->setAttribute( 'class', $class );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  }
207
  }
208
  }
@@ -229,7 +207,10 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
229
  private function adjust_and_replace_node( $node ) {
230
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
231
  $new_attributes = $this->filter_attributes( $old_attributes );
232
- $new_attributes = $this->enforce_sizes_attribute( $new_attributes );
 
 
 
233
  if ( $this->is_gif_url( $new_attributes['src'] ) ) {
234
  $this->did_convert_elements = true;
235
 
@@ -238,6 +219,7 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
238
  $new_tag = 'amp-img';
239
  }
240
  $new_node = AMP_DOM_Utils::create_node( $this->dom, $new_tag, $new_attributes );
 
241
  $node->parentNode->replaceChild( $new_node, $node );
242
  }
243
 
@@ -255,4 +237,43 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
255
  $path = AMP_WP_Utils::parse_url( $url, PHP_URL_PATH );
256
  return substr( $path, -strlen( $ext ) ) === $ext;
257
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  }
46
  */
47
  private static $anim_extension = '.gif';
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  /**
50
  * Sanitize the <img> elements from the HTML contained in this instance's DOMDocument.
51
  *
73
  continue;
74
  }
75
 
76
+ if ( ! $node->hasAttribute( 'src' ) || '' === trim( $node->getAttribute( 'src' ) ) ) {
77
+ $this->remove_invalid_child( $node );
78
  continue;
79
  }
80
 
81
  // Determine which images need their dimensions determined/extracted.
82
+ if ( ! is_numeric( $node->getAttribute( 'width' ) ) || ! is_numeric( $node->getAttribute( 'height' ) ) ) {
83
  $need_dimensions[ $node->getAttribute( 'src' ) ][] = $node;
84
  } else {
85
  $this->adjust_and_replace_node( $node );
119
  case 'alt':
120
  case 'class':
121
  case 'srcset':
 
122
  case 'on':
123
  case 'attribution':
124
  $out[ $name ] = $value;
129
  $out[ $name ] = $this->sanitize_dimension( $value, $name );
130
  break;
131
 
132
+ case 'data-amp-layout':
133
+ $out['layout'] = $value;
134
+ break;
135
+
136
  default:
137
  break;
138
  }
157
  if ( ! $node instanceof DOMElement ) {
158
  continue;
159
  }
160
+ if (
161
+ ! is_numeric( $node->getAttribute( 'width' ) ) &&
162
+ ! is_numeric( $node->getAttribute( 'height' ) )
163
+ ) {
 
 
 
164
  $height = self::FALLBACK_HEIGHT;
165
+ $width = self::FALLBACK_WIDTH;
166
  $node->setAttribute( 'width', $width );
167
  $node->setAttribute( 'height', $height );
168
  $class = $node->hasAttribute( 'class' ) ? $node->getAttribute( 'class' ) . ' amp-wp-unknown-size' : 'amp-wp-unknown-size';
169
  $node->setAttribute( 'class', $class );
170
+ } elseif (
171
+ ! is_numeric( $node->getAttribute( 'height' ) )
172
+ ) {
173
+ $height = self::FALLBACK_HEIGHT;
174
+ $node->setAttribute( 'height', $height );
175
+ $class = $node->hasAttribute( 'class' ) ? $node->getAttribute( 'class' ) . ' amp-wp-unknown-size amp-wp-unknown-height' : 'amp-wp-unknown-size amp-wp-unknown-height';
176
+ $node->setAttribute( 'class', $class );
177
+ } elseif (
178
+ ! is_numeric( $node->getAttribute( 'width' ) )
179
+ ) {
180
+ $width = self::FALLBACK_WIDTH;
181
+ $node->setAttribute( 'width', $width );
182
+ $class = $node->hasAttribute( 'class' ) ? $node->getAttribute( 'class' ) . ' amp-wp-unknown-size amp-wp-unknown-width' : 'amp-wp-unknown-size amp-wp-unknown-width';
183
+ $node->setAttribute( 'class', $class );
184
  }
185
  }
186
  }
207
  private function adjust_and_replace_node( $node ) {
208
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
209
  $new_attributes = $this->filter_attributes( $old_attributes );
210
+ $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-enforced-sizes' );
211
+ if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['height'] ) && ! empty( $new_attributes['width'] ) ) {
212
+ $new_attributes['layout'] = 'intrinsic';
213
+ }
214
  if ( $this->is_gif_url( $new_attributes['src'] ) ) {
215
  $this->did_convert_elements = true;
216
 
219
  $new_tag = 'amp-img';
220
  }
221
  $new_node = AMP_DOM_Utils::create_node( $this->dom, $new_tag, $new_attributes );
222
+ $new_node = $this->handle_centering( $new_node );
223
  $node->parentNode->replaceChild( $new_node, $node );
224
  }
225
 
237
  $path = AMP_WP_Utils::parse_url( $url, PHP_URL_PATH );
238
  return substr( $path, -strlen( $ext ) ) === $ext;
239
  }
240
+
241
+ /**
242
+ * Handles an issue with the aligncenter class.
243
+ *
244
+ * If the <amp-img> has the class aligncenter, this strips the class and wraps it in a <figure> to center the image.
245
+ * There was an issue where the aligncenter class overrode the "display: inline-block" rule of AMP's layout="intrinsic" attribute.
246
+ * So this strips that class, and instead wraps the image in a <figure> to center it.
247
+ *
248
+ * @since 0.7
249
+ * @see https://github.com/Automattic/amp-wp/issues/1104
250
+ *
251
+ * @param DOMElement $node The <amp-img> node.
252
+ * @return DOMElement $node The <amp-img> node, possibly wrapped in a <figure>.
253
+ */
254
+ public function handle_centering( $node ) {
255
+ $align_class = 'aligncenter';
256
+ $classes = $node->getAttribute( 'class' );
257
+ $width = $node->getAttribute( 'width' );
258
+
259
+ // If this doesn't have a width attribute, centering it in the <figure> wrapper won't work.
260
+ if ( empty( $width ) || ! in_array( $align_class, preg_split( '/\s+/', trim( $classes ) ), true ) ) {
261
+ return $node;
262
+ }
263
+
264
+ // Strip the class, and wrap the <amp-img> in a <figure>.
265
+ $classes = trim( str_replace( $align_class, '', $classes ) );
266
+ $node->setAttribute( 'class', $classes );
267
+ $figure = AMP_DOM_Utils::create_node(
268
+ $this->dom,
269
+ 'figure',
270
+ array(
271
+ 'class' => $align_class,
272
+ 'style' => "max-width: {$width}px;",
273
+ )
274
+ );
275
+ $figure->appendChild( $node );
276
+
277
+ return $figure;
278
+ }
279
  }
includes/sanitizers/class-amp-playbuzz-sanitizer.php CHANGED
@@ -31,24 +31,6 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
31
  */
32
  public static $pb_class = 'pb_feed';
33
 
34
- /**
35
- * Script slug.
36
- *
37
- * @var string AMP HTML audio tag to use in place of HTML's 'audio' tag.
38
- *
39
- * @since 0.2
40
- */
41
- private static $script_slug = 'amp-playbuzz';
42
-
43
- /**
44
- * Script src.
45
- *
46
- * @var string URL to AMP Project's Playbuzz element javascript file found at cdn.ampproject.org
47
- *
48
- * @since 0.2
49
- */
50
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-playbuzz-0.1.js';
51
-
52
  /**
53
  * Hardcoded height to set for Playbuzz elements.
54
  *
@@ -58,25 +40,6 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
58
  */
59
  private static $height = '500';
60
 
61
- /**
62
- * Return one element array containing AMP HTML audio tag and respective Javascript URL
63
- *
64
- * HTML tags and Javascript URLs found at cdn.ampproject.org
65
- *
66
- * @since 0.2
67
- *
68
- * @return string[] Returns AMP Playbuzz tag as array key and Javascript URL as array value,
69
- * respectively. Will return an empty array if sanitization has yet to be run
70
- * or if it did not find any HTML Playbuzz elements to convert to AMP equivalents.
71
- */
72
- public function get_scripts() {
73
- if ( ! $this->did_convert_elements ) {
74
- return array();
75
- }
76
- return array( self::$script_slug => self::$script_src );
77
- }
78
-
79
-
80
  /**
81
  * Sanitize the Playbuzz elements from the HTML contained in this instance's DOMDocument.
82
  *
@@ -106,7 +69,7 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
106
  continue;
107
  }
108
 
109
- $new_node = AMP_DOM_Utils::create_node( $this->dom, self::$script_slug, $new_attributes );
110
 
111
  $node->parentNode->replaceChild( $new_node, $node );
112
 
31
  */
32
  public static $pb_class = 'pb_feed';
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  /**
35
  * Hardcoded height to set for Playbuzz elements.
36
  *
40
  */
41
  private static $height = '500';
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  /**
44
  * Sanitize the Playbuzz elements from the HTML contained in this instance's DOMDocument.
45
  *
69
  continue;
70
  }
71
 
72
+ $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-playbuzz', $new_attributes );
73
 
74
  $node->parentNode->replaceChild( $new_node, $node );
75
 
includes/sanitizers/class-amp-rule-spec.php CHANGED
@@ -17,13 +17,16 @@ abstract class AMP_Rule_Spec {
17
  */
18
  const ATTR_SPEC_LIST = 'attr_spec_list';
19
  const TAG_SPEC = 'tag_spec';
 
20
 
21
  /**
22
- * AMP attr_spec value check results
 
 
23
  */
24
- const PASS = 'pass';
25
- const FAIL = 'fail';
26
- const NOT_APPLICABLE = 'not_applicable';
27
 
28
  /**
29
  * HTML Element Tag rule names
@@ -46,6 +49,8 @@ abstract class AMP_Rule_Spec {
46
  const VALUE_CASEI = 'value_casei';
47
  const VALUE_REGEX = 'value_regex';
48
  const VALUE_REGEX_CASEI = 'value_regex_casei';
 
 
49
 
50
  /**
51
  * If a node type listed here is invalid, it and it's subtree will be
@@ -77,7 +82,60 @@ abstract class AMP_Rule_Spec {
77
  */
78
  public static $whitelisted_attr_regex = array(
79
  '@^data-[a-zA-Z][\\w:.-]*$@uis',
80
- '(update|item|pagination)', // Allowed for live reference points.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  );
82
 
83
  /**
@@ -92,16 +150,5 @@ abstract class AMP_Rule_Spec {
92
  'attr_spec_list' => array(),
93
  'tag_spec' => array(),
94
  ),
95
-
96
- // Needed for some tags such as analytics.
97
- 'script' => array(
98
- 'attr_spec_list' => array(
99
- 'type' => array(
100
- 'mandatory' => true,
101
- 'value_casei' => 'application/json',
102
- ),
103
- ),
104
- 'tag_spec' => array(),
105
- ),
106
  );
107
  }
17
  */
18
  const ATTR_SPEC_LIST = 'attr_spec_list';
19
  const TAG_SPEC = 'tag_spec';
20
+ const CDATA = 'cdata';
21
 
22
  /**
23
+ * AMP attr_spec value check results.
24
+ *
25
+ * In 0.7 these changed from strings to integers to speed up comparisons.
26
  */
27
+ const PASS = 1;
28
+ const FAIL = 0;
29
+ const NOT_APPLICABLE = -1;
30
 
31
  /**
32
  * HTML Element Tag rule names
49
  const VALUE_CASEI = 'value_casei';
50
  const VALUE_REGEX = 'value_regex';
51
  const VALUE_REGEX_CASEI = 'value_regex_casei';
52
+ const VALUE_PROPERTIES = 'value_properties';
53
+ const VALUE_URL = 'value_url';
54
 
55
  /**
56
  * If a node type listed here is invalid, it and it's subtree will be
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
+ /**
89
+ * List of boolean attributes.
90
+ *
91
+ * @since 0.7
92
+ * @var array
93
+ */
94
+ public static $boolean_attributes = array(
95
+ 'allowfullscreen',
96
+ 'async',
97
+ 'autofocus',
98
+ 'autoplay',
99
+ 'checked',
100
+ 'compact',
101
+ 'controls',
102
+ 'declare',
103
+ 'default',
104
+ 'defaultchecked',
105
+ 'defaultmuted',
106
+ 'defaultselected',
107
+ 'defer',
108
+ 'disabled',
109
+ 'draggable',
110
+ 'enabled',
111
+ 'formnovalidate',
112
+ 'hidden',
113
+ 'indeterminate',
114
+ 'inert',
115
+ 'ismap',
116
+ 'itemscope',
117
+ 'loop',
118
+ 'multiple',
119
+ 'muted',
120
+ 'nohref',
121
+ 'noresize',
122
+ 'noshade',
123
+ 'novalidate',
124
+ 'nowrap',
125
+ 'open',
126
+ 'pauseonexit',
127
+ 'readonly',
128
+ 'required',
129
+ 'reversed',
130
+ 'scoped',
131
+ 'seamless',
132
+ 'selected',
133
+ 'sortable',
134
+ 'spellcheck',
135
+ 'translate',
136
+ 'truespeed',
137
+ 'typemustmatch',
138
+ 'visible',
139
  );
140
 
141
  /**
150
  'attr_spec_list' => array(),
151
  'tag_spec' => array(),
152
  ),
 
 
 
 
 
 
 
 
 
 
 
153
  );
154
  }
includes/sanitizers/class-amp-style-sanitizer.php CHANGED
@@ -15,18 +15,127 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
15
  /**
16
  * Styles.
17
  *
18
- * @var string[] List of CSS styles in HTML content of DOMDocument ($this->dom).
19
  *
20
  * @since 0.4
 
21
  */
22
  private $styles = array();
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  /**
25
  * Get list of CSS styles in HTML content of DOMDocument ($this->dom).
26
  *
27
  * @since 0.4
28
  *
29
- * @return string[]
30
  */
31
  public function get_styles() {
32
  if ( ! $this->did_convert_elements ) {
@@ -35,57 +144,338 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
35
  return $this->styles;
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Sanitize CSS styles within the HTML contained in this instance's DOMDocument.
40
  *
41
  * @since 0.4
42
  */
43
  public function sanitize() {
44
- $body = $this->get_body_node();
45
- $this->collect_styles_recursive( $body );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  $this->did_convert_elements = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
 
49
  /**
50
- * Collect and store all CSS styles.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  *
52
  * Collects the CSS styles from within the HTML contained in this instance's DOMDocument.
53
  *
54
  * @see Retrieve array of styles using $this->get_styles() after calling this method.
55
  *
56
  * @since 0.4
 
57
  *
58
  * @note Uses recursion to traverse down the tree of DOMDocument nodes.
59
  *
60
- * @param DOMNode $node Node.
61
  */
62
- private function collect_styles_recursive( $node ) {
63
- if ( XML_ELEMENT_NODE !== $node->nodeType ) {
 
64
  return;
65
  }
 
66
 
67
- if ( $node->hasAttributes() && $node instanceof DOMElement ) {
68
- $style = $node->getAttribute( 'style' );
69
- $class = $node->getAttribute( 'class' );
70
 
71
- if ( $style ) {
72
- $style = $this->process_style( $style );
73
- if ( ! empty( $style ) ) {
74
- $class_name = $this->generate_class_name( $style );
75
- $new_class = trim( $class . ' ' . $class_name );
76
 
77
- $node->setAttribute( 'class', $new_class );
78
- $this->styles[ '.' . $class_name ] = $style;
79
- }
80
- $node->removeAttribute( 'style' );
 
 
 
 
81
  }
82
- }
83
 
84
- $length = $node->childNodes->length;
85
- for ( $i = $length - 1; $i >= 0; $i -- ) {
86
- $child_node = $node->childNodes->item( $i );
87
- $this->collect_styles_recursive( $child_node );
88
  }
 
89
  }
90
 
91
  /**
@@ -93,26 +483,20 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
93
  *
94
  * @since 0.4
95
  *
96
- * @param string $string Style string.
97
- * @return array
98
  */
99
- private function process_style( $string ) {
100
 
101
- /**
102
- * Filter properties
103
- */
104
- $string = safecss_filter_attr( esc_html( $string ) );
105
-
106
- if ( ! $string ) {
107
- return array();
108
- }
109
 
110
  /*
111
- * safecss returns a string but we want individual rules.
112
  * Use preg_split to break up rules by `;` but only if the
113
  * semi-colon is not inside parens (like a data-encoded image).
114
  */
115
- $styles = array_map( 'trim', preg_split( '/;(?![^(]*\))/', $string ) );
 
116
 
117
  // Normalize the order of the styles.
118
  sort( $styles );
@@ -121,12 +505,12 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
121
 
122
  // Normalize whitespace and filter rules.
123
  foreach ( $styles as $index => $rule ) {
124
- $arr2 = array_map( 'trim', explode( ':', $rule, 2 ) );
125
- if ( 2 !== count( $arr2 ) ) {
126
  continue;
127
  }
128
 
129
- list( $property, $value ) = $this->filter_style( $arr2[0], $arr2[1] );
130
  if ( empty( $property ) || empty( $value ) ) {
131
  continue;
132
  }
@@ -151,13 +535,13 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
151
  * @return array
152
  */
153
  private function filter_style( $property, $value ) {
154
-
155
- /**
156
  * Remove overflow if value is `auto` or `scroll`; not allowed in AMP
157
  *
 
158
  * @see https://www.ampproject.org/docs/reference/spec.html#properties
159
  */
160
- if ( preg_match( '#^overflow#i', $property ) && preg_match( '#^(auto|scroll)$#i', $value ) ) {
161
  return array( false, false );
162
  }
163
 
@@ -165,8 +549,10 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
165
  $property = 'max-width';
166
  }
167
 
168
- /**
169
  * Remove `!important`; not allowed in AMP
 
 
170
  */
171
  if ( false !== strpos( $value, 'important' ) ) {
172
  $value = preg_replace( '/\s*\!\s*important$/', '', $value );
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.
62
+ *
63
+ * @var DOMElement
64
+ */
65
+ private $amp_custom_style_element;
66
+
67
+ /**
68
+ * Regex for allowed font stylesheet URL.
69
+ *
70
+ * @var string
71
+ */
72
+ private $allowed_font_src_regex;
73
+
74
+ /**
75
+ * Base URL for styles.
76
+ *
77
+ * Full URL with trailing slash.
78
+ *
79
+ * @var string
80
+ */
81
+ private $base_url;
82
+
83
+ /**
84
+ * URL of the content directory.
85
+ *
86
+ * @var string
87
+ */
88
+ private $content_url;
89
+
90
+ /**
91
+ * AMP_Base_Sanitizer constructor.
92
+ *
93
+ * @since 0.7
94
+ *
95
+ * @param DOMDocument $dom Represents the HTML document to sanitize.
96
+ * @param array $args Args.
97
+ */
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
+
117
+ $spec_name = 'link rel=stylesheet for fonts'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
118
+ foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'link' ) as $spec_rule ) {
119
+ if ( isset( $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) && $spec_name === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) {
120
+ $this->allowed_font_src_regex = '@^(' . $spec_rule[ AMP_Rule_Spec::ATTR_SPEC_LIST ]['href']['value_regex'] . ')$@';
121
+ break;
122
+ }
123
+ }
124
+
125
+ $guessurl = site_url();
126
+ if ( ! $guessurl ) {
127
+ $guessurl = wp_guess_url();
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 ) {
144
  return $this->styles;
145
  }
146
 
147
+ /**
148
+ * Get stylesheets.
149
+ *
150
+ * @since 0.7
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
  /**
158
  * Sanitize CSS styles within the HTML contained in this instance's DOMDocument.
159
  *
160
  * @since 0.4
161
  */
162
  public function sanitize() {
163
+ $elements = array();
164
+
165
+ // Do nothing if inline styles are allowed.
166
+ if ( ! empty( $this->args['allow_dirty_styles'] ) ) {
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(
178
+ sprintf( '( self::style and not( @amp-boilerplate ) and ( not( @type ) or %s = "text/css" ) )', sprintf( $lower_case, '@type' ) ),
179
+ sprintf( '( self::link and @href and %s = "stylesheet" )', sprintf( $lower_case, '@rel' ) ),
180
+ );
181
+
182
+ foreach ( $xpath->query( '//*[ ' . implode( ' or ', $predicates ) . ' ]' ) as $element ) {
183
+ $elements[] = $element;
184
+ }
185
+
186
+ /**
187
+ * Element.
188
+ *
189
+ * @var DOMElement $element
190
+ */
191
+ foreach ( $elements as $element ) {
192
+ $node_name = strtolower( $element->nodeName );
193
+ if ( 'style' === $node_name ) {
194
+ $this->process_style_element( $element );
195
+ } elseif ( 'link' === $node_name ) {
196
+ $this->process_link_element( $element );
197
+ }
198
+ }
199
+
200
+ $elements = array();
201
+ foreach ( $xpath->query( '//*[ @style ]' ) as $element ) {
202
+ $elements[] = $element;
203
+ }
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
  /**
287
+ * Process style element.
288
+ *
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 ) {
319
+ $this->amp_custom_style_element = $element;
320
+ } else {
321
+ $element->parentNode->removeChild( $element ); // There can only be one. #highlander.
322
+ }
323
+ } else {
324
+
325
+ // Remove from DOM since we'll be adding it to amp-custom.
326
+ $element->parentNode->removeChild( $element );
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Process link element.
332
+ *
333
+ * @param DOMElement $element Link element.
334
+ */
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
  /**
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 );
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
  }
535
  * @return array
536
  */
537
  private function filter_style( $property, $value ) {
538
+ /*
 
539
  * Remove overflow if value is `auto` or `scroll`; not allowed in AMP
540
  *
541
+ * @todo This removal needs to be reported.
542
  * @see https://www.ampproject.org/docs/reference/spec.html#properties
543
  */
544
+ if ( preg_match( '#^overflow(-[xy])?$#i', $property ) && preg_match( '#^(auto|scroll)$#i', $value ) ) {
545
  return array( false, false );
546
  }
547
 
549
  $property = 'max-width';
550
  }
551
 
552
+ /*
553
  * Remove `!important`; not allowed in AMP
554
+ *
555
+ * @todo This removal needs to be reported.
556
  */
557
  if ( false !== strpos( $value, 'important' ) ) {
558
  $value = preg_replace( '/\s*\!\s*important$/', '', $value );
includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php CHANGED
@@ -17,7 +17,6 @@
17
  * @todo Need to check the following items that are not yet checked by this sanitizer:
18
  *
19
  * - `also_requires_attr` - if one attribute is present, this requires another.
20
- * - `CdataSpec` - CDATA is not validated or sanitized.
21
  * - `ChildTagSpec` - Places restrictions on the number and type of child tags.
22
  * - `if_value_regex` - if one attribute value matches, this places a restriction
23
  * on another attribute/value.
@@ -53,6 +52,14 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
53
  */
54
  protected $layout_allowed_attributes;
55
 
 
 
 
 
 
 
 
 
56
  /**
57
  * Stack.
58
  *
@@ -71,6 +78,13 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
71
  */
72
  protected $DEFAULT_ARGS = array();
73
 
 
 
 
 
 
 
 
74
  /**
75
  * AMP_Tag_And_Attribute_Sanitizer constructor.
76
  *
@@ -81,40 +95,152 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
81
  */
82
  public function __construct( $dom, $args = array() ) {
83
  $this->DEFAULT_ARGS = array(
84
- 'amp_allowed_tags' => AMP_Allowed_Tags_Generated::get_allowed_tags(),
85
  'amp_globally_allowed_attributes' => AMP_Allowed_Tags_Generated::get_allowed_attributes(),
86
- 'amp_layout_allowed_attributes' => AMP_Allowed_Tags_Generated::get_layout_attributes(),
 
87
  );
88
 
89
  parent::__construct( $dom, $args );
90
 
91
- /**
92
- * Prepare whitelists
93
- */
94
- $this->allowed_tags = $this->args['amp_allowed_tags'];
95
- $this->globally_allowed_attributes = $this->args['amp_globally_allowed_attributes'];
96
- $this->layout_allowed_attributes = $this->args['amp_layout_allowed_attributes'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
 
99
  /**
100
- * Sanitize the <video> elements from the HTML contained in this instance's DOMDocument.
101
  *
102
- * @since 0.5
 
 
 
 
 
 
 
 
 
103
  */
104
- public function sanitize() {
 
 
105
 
106
- foreach ( AMP_Rule_Spec::$additional_allowed_tags as $tag_name => $tag_rule_spec ) {
107
- $this->allowed_tags[ $tag_name ][] = $tag_rule_spec;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
 
 
109
 
110
- /**
111
- * Add root of content to the stack.
112
- *
113
- * @var DOMElement $body
114
- */
115
- $body = $this->get_body_node();
116
 
117
- $this->stack[] = $body;
 
118
 
119
  /**
120
  * This loop traverses through the DOM tree iteratively.
@@ -165,6 +291,12 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
165
  return;
166
  }
167
 
 
 
 
 
 
 
168
  /*
169
  * Compile a list of rule_specs to validate for this node
170
  * based on tag name of the node.
@@ -176,6 +308,27 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
176
  }
177
  foreach ( $rule_spec_list as $id => $rule_spec ) {
178
  if ( $this->validate_tag_spec_for_node( $node, $rule_spec[ AMP_Rule_Spec::TAG_SPEC ] ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  $rule_spec_list_to_validate[ $id ] = $rule_spec;
180
  }
181
  }
@@ -188,6 +341,8 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
188
 
189
  // The remaining validations all have to do with attributes.
190
  $attr_spec_list = array();
 
 
191
 
192
  /*
193
  * If we have exactly one rule_spec, use it's attr_spec_list
@@ -196,7 +351,10 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
196
  if ( 1 === count( $rule_spec_list_to_validate ) ) {
197
  $rule_spec = array_pop( $rule_spec_list_to_validate );
198
  $attr_spec_list = $rule_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ];
199
-
 
 
 
200
  } else {
201
  /*
202
  * If there is more than one valid rule_spec for this node,
@@ -213,12 +371,25 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
213
  $attr_spec_scores[ $spec_id ] = $this->validate_attr_spec_list_for_node( $node, $rule_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] );
214
  }
215
 
 
 
 
 
 
 
 
 
 
216
  // Get the key(s) to the highest score(s).
217
  $spec_ids_sorted = array_keys( $attr_spec_scores, max( $attr_spec_scores ), true );
218
 
219
  // If there is exactly one attr_spec with a max score, use that one.
220
  if ( 1 === count( $spec_ids_sorted ) ) {
221
  $attr_spec_list = $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][ AMP_Rule_Spec::ATTR_SPEC_LIST ];
 
 
 
 
222
  } else {
223
  // This should not happen very often, but...
224
  // If we're here, then we're not sure which spec should
@@ -227,6 +398,13 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
227
  $spec_list = isset( $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::ATTR_SPEC_LIST ] ) ? $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::ATTR_SPEC_LIST ] : null;
228
  if ( ! $this->is_missing_mandatory_attribute( $spec_list, $node ) ) {
229
  $attr_spec_list = array_merge( $attr_spec_list, $spec_list );
 
 
 
 
 
 
 
230
  }
231
  }
232
  $first_spec = reset( $rule_spec_list_to_validate );
@@ -241,18 +419,68 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
241
  return;
242
  }
243
 
244
- // Remove any remaining disallowed attributes.
245
- $this->sanitize_disallowed_attributes_in_node( $node, $attr_spec_list );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
- // Remove values that don't conform to the attr_spec.
248
- $this->sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list );
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  }
250
 
251
  /**
252
  * Whether a node is missing a mandatory attribute.
253
  *
254
- * @param array $attr_spec The attribute specification.
255
- * @param object $node The DOMElement of the node to check.
256
  * @return boolean $is_missing boolean Whether a required attribute is missing.
257
  */
258
  public function is_missing_mandatory_attribute( $attr_spec, $node ) {
@@ -260,8 +488,22 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
260
  return false;
261
  }
262
  foreach ( $attr_spec as $attr_name => $attr_spec_rule_value ) {
 
 
 
263
  $is_mandatory = isset( $attr_spec_rule_value[ AMP_Rule_Spec::MANDATORY ] ) ? ( true === $attr_spec_rule_value[ AMP_Rule_Spec::MANDATORY ] ) : false;
264
- $attribute_exists = method_exists( $node, 'hasAttribute' ) && $node->hasAttribute( $attr_name );
 
 
 
 
 
 
 
 
 
 
 
265
  if ( $is_mandatory && ! $attribute_exists ) {
266
  return true;
267
  }
@@ -269,6 +511,24 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
269
  return false;
270
  }
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  /**
273
  * Determines is a node is currently valid per its tag specification.
274
  *
@@ -279,15 +539,21 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
279
  *
280
  * @since 0.5
281
  *
282
- * @param object $node The node to validate.
283
- * @param array $tag_spec The sepecification.
284
  * @return boolean $valid Whether the node's placement is valid.
285
  */
286
  private function validate_tag_spec_for_node( $node, $tag_spec ) {
 
287
  if ( ! empty( $tag_spec[ AMP_Rule_Spec::MANDATORY_PARENT ] ) && ! $this->has_parent( $node, $tag_spec[ AMP_Rule_Spec::MANDATORY_PARENT ] ) ) {
288
  return false;
289
  }
290
 
 
 
 
 
 
291
  if ( ! empty( $tag_spec[ AMP_Rule_Spec::DISALLOWED_ANCESTOR ] ) ) {
292
  foreach ( $tag_spec[ AMP_Rule_Spec::DISALLOWED_ANCESTOR ] as $disallowed_ancestor_node_name ) {
293
  if ( $this->has_ancestor( $node, $disallowed_ancestor_node_name ) ) {
@@ -313,14 +579,27 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
313
  * @param DOMNode $node Node.
314
  * @param array[] $attr_spec_list Attribute Spec list.
315
  *
316
- * @return int Validity.
317
  */
318
  private function validate_attr_spec_list_for_node( $node, $attr_spec_list ) {
319
-
320
- /**
321
- * If node has no attributes there is no point in continuing.
322
  */
323
  if ( ! $node->hasAttributes() ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  return 0;
325
  }
326
 
@@ -333,16 +612,15 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
333
  }
334
  }
335
 
336
- if ( ! $node instanceof DOMElement ) {
337
- /*
338
- * A DOMNode is not valid for checks so might
339
- * as well bail here is not an DOMElement.
340
- */
341
- return 0;
342
- }
343
-
344
  $score = 0;
345
 
 
 
 
 
 
 
 
346
  /*
347
  * Iterate through each attribute rule in this attr spec list and run
348
  * the series of tests. Each filter is given a `$node`, an `$attr_name`,
@@ -351,10 +629,20 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
351
  */
352
  foreach ( $attr_spec_list as $attr_name => $attr_spec_rule ) {
353
 
 
 
 
 
 
 
354
  // If a mandatory attribute is required, and attribute exists, pass.
355
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] ) ) {
356
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule ) ) {
 
 
357
  $score++;
 
 
358
  }
359
  }
360
 
@@ -363,8 +651,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
363
  * Given attribute's value must exactly equal value of the rule to pass.
364
  */
365
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) ) {
366
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) ) {
 
367
  $score++;
 
 
368
  }
369
  }
370
 
@@ -374,8 +665,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
374
  * specified by the value of rule to pass.
375
  */
376
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX ] ) ) {
377
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
 
378
  $score++;
 
 
379
  }
380
  }
381
 
@@ -385,8 +679,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
385
  * the rule to pass.
386
  */
387
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_CASEI ] ) ) {
388
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
 
389
  $score++;
 
 
390
  }
391
  }
392
 
@@ -396,8 +693,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
396
  * pattern specified by the value of the rule to pass.
397
  */
398
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX_CASEI ] ) ) {
399
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
 
400
  $score++;
 
 
401
  }
402
  }
403
 
@@ -405,9 +705,25 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
405
  * If given attribute's value is a URL with a protocol, the protocol must
406
  * be in the array specified by the rule's value to pass.
407
  */
408
- if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOWED_PROTOCOL ] ) ) {
409
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  $score++;
 
 
411
  }
412
  }
413
 
@@ -415,9 +731,12 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
415
  * If the given attribute's value is *not* a relative path, and the rule's
416
  * value is `false`, then pass.
417
  */
418
- if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_RELATIVE ] ) ) {
419
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
 
420
  $score++;
 
 
421
  }
422
  }
423
 
@@ -425,9 +744,12 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
425
  * If the given attribute's value exists, is non-empty and the rule's value
426
  * is false, then pass.
427
  */
428
- if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_EMPTY ] ) ) {
429
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
 
430
  $score++;
 
 
431
  }
432
  }
433
 
@@ -436,8 +758,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
436
  * of domains in the value of the rule, then pass.
437
  */
438
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::DISALLOWED_DOMAIN ] ) ) {
439
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) ) {
 
440
  $score++;
 
 
441
  }
442
  }
443
 
@@ -446,10 +771,28 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
446
  * by the rule's value, then pass.
447
  */
448
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX ] ) ) {
449
- if ( AMP_Rule_Spec::PASS === $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
 
450
  $score++;
 
 
451
  }
452
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  }
454
 
455
  return $score;
@@ -460,15 +803,16 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
460
  *
461
  * @param DOMNode $node Node.
462
  * @param array[] $attr_spec_list Attribute spec list.
 
463
  */
464
- private function sanitize_disallowed_attributes_in_node( $node, $attr_spec_list ) {
465
 
466
  if ( ! $node instanceof DOMElement ) {
467
  /**
468
  * If $node is only a DOMNode and not a DOMElement we can't
469
  * remove an attribute from it anyway. So bail out now.
470
  */
471
- return;
472
  }
473
 
474
  /*
@@ -479,34 +823,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
479
  $attrs_to_remove = array();
480
  foreach ( $node->attributes as $attr_name => $attr_node ) {
481
  if ( ! $this->is_amp_allowed_attribute( $attr_name, $attr_spec_list ) ) {
482
- /**
483
- * This attribute is not allowed for this node; plan to remove it.
484
- */
485
- $attrs_to_remove[] = $attr_name;
486
  }
487
  }
488
 
489
- if ( ! empty( $attrs_to_remove ) ) {
490
- /*
491
- * Ensure we are not removing attributes listed as an alternate
492
- * or allowed attributes, e.g. 'srcset' is an alternate for 'src'.
493
- */
494
- foreach ( $attr_spec_list as $attr_name => $attr_spec_rule_value ) {
495
- if ( isset( $attr_spec_rule_value[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
496
- foreach ( $attr_spec_rule_value[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
497
- $alt_name_keys = array_keys( $attrs_to_remove, $alternative_name, true );
498
- if ( ! empty( $alt_name_keys ) ) {
499
- unset( $attrs_to_remove[ $alt_name_keys[0] ] );
500
- }
501
- }
502
- }
503
- }
504
-
505
- // Remove the disallowed attributes.
506
- foreach ( $attrs_to_remove as $attr_name ) {
507
- $node->removeAttribute( $attr_name );
508
- }
509
- }
510
  }
511
 
512
  /**
@@ -514,23 +835,29 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
514
  *
515
  * Allowed values are found $this->globally_allowed_attributes and in parameter $attr_spec_list
516
  *
517
- * @param DOMNode $node Node.
518
- * @param array[][] $attr_spec_list Attribute spec list.
 
 
519
  */
520
- private function sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ) {
521
 
522
  if ( ! $node instanceof DOMElement ) {
523
  /*
524
  * If $node is only a DOMNode and not a DOMElement we can't
525
  * remove an attribute from it anyway. So bail out now.
526
  */
527
- return;
528
  }
529
 
530
- $this->delegated_sanitize_disallowed_attribute_values_in_node( $node, $this->globally_allowed_attributes );
531
- if ( ! empty( $attr_spec_list ) ) {
532
- $this->delegated_sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list );
533
- }
 
 
 
 
534
  }
535
 
536
  /**
@@ -538,10 +865,12 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
538
  *
539
  * @see $this->sanitize_disallowed_attribute_values_in_node() which delegates to this method
540
  *
541
- * @param DOMElement $node Node.
542
- * @param array[][] $attr_spec_list Attribute spec list.
 
 
543
  */
544
- private function delegated_sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ) {
545
  $attrs_to_remove = array();
546
 
547
  foreach ( $attr_spec_list as $attr_name => $attr_val ) {
@@ -554,7 +883,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
554
 
555
  foreach ( $node->attributes as $attr_name => $attr_node ) {
556
 
557
- if ( ! isset( $attr_spec_list[ $attr_name ] ) ) {
558
  continue;
559
  }
560
 
@@ -568,18 +897,21 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
568
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
569
  $should_remove_node = true;
570
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX ] ) &&
571
- AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
572
  $should_remove_node = true;
573
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX_CASEI ] ) &&
574
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
575
  $should_remove_node = true;
576
- } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOWED_PROTOCOL ] ) &&
577
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
578
  $should_remove_node = true;
579
- } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_RELATIVE ] ) &&
 
 
 
580
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
581
  $should_remove_node = true;
582
- } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_EMPTY ] ) &&
583
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
584
  $should_remove_node = true;
585
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::DISALLOWED_DOMAIN ] ) &&
@@ -598,22 +930,24 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
598
 
599
  if ( $is_mandatory ) {
600
  $this->remove_node( $node );
601
- return;
602
  }
603
 
604
- $attrs_to_remove[] = $attr_name;
605
  }
606
  }
607
 
608
  // Remove the disallowed values.
609
- foreach ( $attrs_to_remove as $attr_name ) {
610
- if ( isset( $attr_spec_list[ $attr_name ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) &&
611
- ( true === $attr_spec_list[ $attr_name ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) ) {
612
- $node->setAttribute( $attr_name, '' );
613
  } else {
614
- $node->removeAttribute( $attr_name );
615
  }
616
  }
 
 
617
  }
618
 
619
  /**
@@ -656,7 +990,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
656
  *
657
  * @param DOMElement $node Node.
658
  * @param string $attr_name Attribute name.
659
- * @param array[] $attr_spec_rule Attribute spec rule.
660
  *
661
  * @return string:
662
  * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
@@ -667,7 +1001,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
667
  private function check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) {
668
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) ) {
669
  if ( $node->hasAttribute( $attr_name ) ) {
670
- if ( $node->getAttribute( $attr_name ) === $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) {
671
  return AMP_Rule_Spec::PASS;
672
  } else {
673
  return AMP_Rule_Spec::FAIL;
@@ -675,7 +1009,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
675
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
676
  foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
677
  if ( $node->hasAttribute( $alternative_name ) ) {
678
- if ( $node->getAttribute( $alternative_name ) === $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) {
679
  return AMP_Rule_Spec::PASS;
680
  } else {
681
  return AMP_Rule_Spec::FAIL;
@@ -687,6 +1021,33 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
687
  return AMP_Rule_Spec::NOT_APPLICABLE;
688
  }
689
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690
  /**
691
  * Check if attribute has a value rule determine if its value matches ignoring case.
692
  *
@@ -792,6 +1153,47 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
792
  return AMP_Rule_Spec::NOT_APPLICABLE;
793
  }
794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  /**
796
  * Check if attribute has a protocol value rule determine if it matches.
797
  *
@@ -806,11 +1208,9 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
806
  * is no rule for this attribute.
807
  */
808
  private function check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) {
809
- if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOWED_PROTOCOL ] ) ) {
810
  if ( $node->hasAttribute( $attr_name ) ) {
811
- $attr_value = $node->getAttribute( $attr_name );
812
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
813
- $urls_to_test = explode( ',', $attr_value );
814
  foreach ( $urls_to_test as $url ) {
815
  /*
816
  * This seems to be an acceptable check since the AMP validator
@@ -818,7 +1218,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
818
  */
819
  $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME );
820
  if ( $url_scheme ) {
821
- if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[ AMP_Rule_Spec::ALLOWED_PROTOCOL ], true ) ) {
822
  return AMP_Rule_Spec::FAIL;
823
  }
824
  }
@@ -827,9 +1227,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
827
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
828
  foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
829
  if ( $node->hasAttribute( $alternative_name ) ) {
830
- $attr_value = $node->getAttribute( $alternative_name );
831
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
832
- $urls_to_test = explode( ',', $attr_value );
833
  foreach ( $urls_to_test as $url ) {
834
  /*
835
  * This seems to be an acceptable check since the AMP validator
@@ -837,7 +1235,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
837
  */
838
  $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME );
839
  if ( $url_scheme ) {
840
- if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[ AMP_Rule_Spec::ALLOWED_PROTOCOL ], true ) ) {
841
  return AMP_Rule_Spec::FAIL;
842
  }
843
  }
@@ -864,11 +1262,9 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
864
  * is no rule for this attribute.
865
  */
866
  private function check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) {
867
- if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_RELATIVE ] ) && ! ( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_RELATIVE ] ) ) {
868
  if ( $node->hasAttribute( $attr_name ) ) {
869
- $attr_value = $node->getAttribute( $attr_name );
870
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
871
- $urls_to_test = explode( ',', $attr_value );
872
  foreach ( $urls_to_test as $url ) {
873
  $parsed_url = AMP_WP_Utils::parse_url( $url );
874
 
@@ -887,9 +1283,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
887
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
888
  foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
889
  if ( $node->hasAttribute( $alternative_name ) ) {
890
- $attr_value = $node->getAttribute( $alternative_name );
891
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
892
- $urls_to_test = explode( ',', $attr_value );
893
  foreach ( $urls_to_test as $url ) {
894
  $parsed_url = AMP_WP_Utils::parse_url( $url );
895
  if ( empty( $parsed_url['scheme'] ) ) {
@@ -918,7 +1312,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
918
  * is no rule for this attribute.
919
  */
920
  private function check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) {
921
- if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_EMPTY ] ) && ! ( $attr_spec_rule[ AMP_Rule_Spec::ALLOW_EMPTY ] ) && $node->hasAttribute( $attr_name ) ) {
922
  $attr_value = $node->getAttribute( $attr_name );
923
  if ( empty( $attr_value ) ) {
924
  return AMP_Rule_Spec::FAIL;
@@ -1000,6 +1394,67 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
1000
  return AMP_Rule_Spec::NOT_APPLICABLE;
1001
  }
1002
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1003
  /**
1004
  * Determine if the supplied attribute name is allowed for AMP.
1005
  *
@@ -1019,6 +1474,16 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
1019
  }
1020
  }
1021
  }
 
 
 
 
 
 
 
 
 
 
1022
  return false;
1023
  }
1024
 
@@ -1066,6 +1531,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
1066
  *
1067
  * @since 0.5
1068
  *
 
1069
  * @param DOMNode $node Node.
1070
  * @param string $ancestor_tag_name Ancestor tag name.
1071
  * @return bool Return true if given node has any ancestor with the give name, false otherwise.
@@ -1151,9 +1617,9 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
1151
  */
1152
  $parent = $node->parentNode;
1153
  if ( $node && $parent ) {
1154
- $parent->removeChild( $node );
1155
  }
1156
- while ( $parent && ! $parent->hasChildNodes() && 'body' !== $parent->nodeName ) {
1157
  $node = $parent;
1158
  $parent = $parent->parentNode;
1159
  if ( $parent ) {
@@ -1162,4 +1628,3 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
1162
  }
1163
  }
1164
  }
1165
-
17
  * @todo Need to check the following items that are not yet checked by this sanitizer:
18
  *
19
  * - `also_requires_attr` - if one attribute is present, this requires another.
 
20
  * - `ChildTagSpec` - Places restrictions on the number and type of child tags.
21
  * - `if_value_regex` - if one attribute value matches, this places a restriction
22
  * on another attribute/value.
52
  */
53
  protected $layout_allowed_attributes;
54
 
55
+ /**
56
+ * Mapping of alternative names back to their primary names.
57
+ *
58
+ * @since 0.7
59
+ * @var array
60
+ */
61
+ protected $rev_alternate_attr_name_lookup = array();
62
+
63
  /**
64
  * Stack.
65
  *
78
  */
79
  protected $DEFAULT_ARGS = array();
80
 
81
+ /**
82
+ * AMP script components that are discovered being required through sanitization.
83
+ *
84
+ * @var string[]
85
+ */
86
+ protected $script_components = array();
87
+
88
  /**
89
  * AMP_Tag_And_Attribute_Sanitizer constructor.
90
  *
95
  */
96
  public function __construct( $dom, $args = array() ) {
97
  $this->DEFAULT_ARGS = array(
98
+ 'amp_allowed_tags' => AMP_Allowed_Tags_Generated::get_allowed_tags(),
99
  'amp_globally_allowed_attributes' => AMP_Allowed_Tags_Generated::get_allowed_attributes(),
100
+ 'amp_layout_allowed_attributes' => AMP_Allowed_Tags_Generated::get_layout_attributes(),
101
+ 'amp_bind_placeholder_prefix' => AMP_DOM_Utils::get_amp_bind_placeholder_prefix(),
102
  );
103
 
104
  parent::__construct( $dom, $args );
105
 
106
+ if ( ! empty( $this->args['allow_dirty_styles'] ) ) {
107
+
108
+ // Allow style attribute on all elements.
109
+ $this->args['amp_globally_allowed_attributes']['style'] = array();
110
+
111
+ // Allow style elements.
112
+ $this->args['amp_allowed_tags']['style'][] = array(
113
+ 'attr_spec_list' => array(
114
+ 'type' => array(
115
+ 'value_casei' => 'text/css',
116
+ ),
117
+ ),
118
+ 'cdata' => array(),
119
+ 'tag_spec' => array(
120
+ 'spec_name' => 'style for Customizer preview',
121
+ ),
122
+ );
123
+
124
+ // Allow stylesheet links.
125
+ $this->args['amp_allowed_tags']['link'][] = array(
126
+ 'attr_spec_list' => array(
127
+ 'async' => array(),
128
+ 'crossorigin' => array(),
129
+ 'href' => array(
130
+ 'mandatory' => true,
131
+ ),
132
+ 'integrity' => array(),
133
+ 'media' => array(),
134
+ 'rel' => array(
135
+ 'dispatch_key' => 2,
136
+ 'mandatory' => true,
137
+ 'value_casei' => 'stylesheet',
138
+ ),
139
+ 'type' => array(
140
+ 'value_casei' => 'text/css',
141
+ ),
142
+ ),
143
+ 'tag_spec' => array(
144
+ 'spec_name' => 'link rel=stylesheet for Customizer preview', // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
145
+ ),
146
+ );
147
+ }
148
+
149
+ // Allow scripts if requested.
150
+ if ( ! empty( $this->args['allow_dirty_scripts'] ) ) {
151
+ $this->args['amp_allowed_tags']['script'][] = array(
152
+ 'attr_spec_list' => array(
153
+ 'type' => array(),
154
+ 'src' => array(),
155
+ 'async' => array(),
156
+ 'defer' => array(),
157
+ ),
158
+ 'cdata' => array(),
159
+ 'tag_spec' => array(
160
+ 'spec_name' => 'scripts for Customizer preview',
161
+ ),
162
+ );
163
+ }
164
+
165
+ // Prepare whitelists.
166
+ $this->allowed_tags = $this->args['amp_allowed_tags'];
167
+ foreach ( AMP_Rule_Spec::$additional_allowed_tags as $tag_name => $tag_rule_spec ) {
168
+ $this->allowed_tags[ $tag_name ][] = $tag_rule_spec;
169
+ }
170
+
171
+ // @todo Do the same for body when !use_document_element?
172
+ if ( ! empty( $this->args['use_document_element'] ) ) {
173
+ foreach ( $this->allowed_tags['html'] as &$rule_spec ) {
174
+ unset( $rule_spec[ AMP_Rule_Spec::TAG_SPEC ][ AMP_Rule_Spec::MANDATORY_PARENT ] );
175
+ }
176
+ }
177
+
178
+ foreach ( $this->allowed_tags as &$tag_specs ) {
179
+ foreach ( $tag_specs as &$tag_spec ) {
180
+ if ( isset( $tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] ) ) {
181
+ $tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] = $this->process_alternate_names( $tag_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] );
182
+ }
183
+ }
184
+ }
185
+ $this->globally_allowed_attributes = $this->process_alternate_names( $this->args['amp_globally_allowed_attributes'] );
186
+ $this->layout_allowed_attributes = $this->process_alternate_names( $this->args['amp_layout_allowed_attributes'] );
187
  }
188
 
189
  /**
190
+ * Return array of values that would be valid as an HTML `script` element.
191
  *
192
+ * Array keys are AMP element names and array values are their respective
193
+ * Javascript URLs from https://cdn.ampproject.org
194
+ *
195
+ * @since 0.7
196
+ * @see amp_register_default_scripts()
197
+ *
198
+ * @return array() Returns component name as array key and true as value (or JavaScript URL string),
199
+ * respectively. When true then the default component script URL will be used.
200
+ * Will return an empty array if sanitization has yet to be run
201
+ * or if it did not find any HTML elements to convert to AMP equivalents.
202
  */
203
+ public function get_scripts() {
204
+ return array_fill_keys( $this->script_components, true );
205
+ }
206
 
207
+ /**
208
+ * Process alternative names in attribute spec list.
209
+ *
210
+ * @since 0.7
211
+ *
212
+ * @param array $attr_spec_list Attribute spec list.
213
+ * @return array Modified attribute spec list.
214
+ */
215
+ private function process_alternate_names( $attr_spec_list ) {
216
+ foreach ( $attr_spec_list as $attr_name => &$attr_spec ) {
217
+ if ( '[' === $attr_name[0] ) {
218
+ $placeholder_attr_name = $this->args['amp_bind_placeholder_prefix'] . trim( $attr_name, '[]' );
219
+ if ( ! isset( $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
220
+ $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] = array();
221
+ }
222
+ $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ][] = $placeholder_attr_name;
223
+ }
224
+
225
+ // Save all alternative names in lookup to improve performance.
226
+ if ( isset( $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
227
+ foreach ( $attr_spec[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
228
+ $this->rev_alternate_attr_name_lookup[ $alternative_name ] = $attr_name;
229
+ }
230
+ }
231
  }
232
+ return $attr_spec_list;
233
+ }
234
 
235
+ /**
236
+ * Sanitize the <video> elements from the HTML contained in this instance's DOMDocument.
237
+ *
238
+ * @since 0.5
239
+ */
240
+ public function sanitize() {
241
 
242
+ // Add root of content to the stack.
243
+ $this->stack[] = $this->root_element;
244
 
245
  /**
246
  * This loop traverses through the DOM tree iteratively.
291
  return;
292
  }
293
 
294
+ /**
295
+ * Node is now an element.
296
+ *
297
+ * @var DOMElement $node
298
+ */
299
+
300
  /*
301
  * Compile a list of rule_specs to validate for this node
302
  * based on tag name of the node.
308
  }
309
  foreach ( $rule_spec_list as $id => $rule_spec ) {
310
  if ( $this->validate_tag_spec_for_node( $node, $rule_spec[ AMP_Rule_Spec::TAG_SPEC ] ) ) {
311
+
312
+ // Expand extension_spec into a set of attr_spec_list.
313
+ if ( isset( $rule_spec[ AMP_Rule_Spec::TAG_SPEC ]['extension_spec'] ) ) {
314
+ $extension_spec = $rule_spec[ AMP_Rule_Spec::TAG_SPEC ]['extension_spec'];
315
+ $custom_attr = 'amp-mustache' === $extension_spec['name'] ? 'custom-template' : 'custom-element';
316
+
317
+ $rule_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ][ $custom_attr ] = array(
318
+ AMP_Rule_Spec::VALUE => $extension_spec['name'],
319
+ AMP_Rule_Spec::MANDATORY => true,
320
+ );
321
+
322
+ $rule_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ]['src'] = array(
323
+ AMP_Rule_Spec::VALUE_REGEX => implode( '', array(
324
+ '^',
325
+ preg_quote( 'https://cdn.ampproject.org/v0/' . $extension_spec['name'] . '-' ),
326
+ '(' . implode( '|', $extension_spec['allowed_versions'] ) . ')',
327
+ '\.js$',
328
+ ) ),
329
+ );
330
+ }
331
+
332
  $rule_spec_list_to_validate[ $id ] = $rule_spec;
333
  }
334
  }
341
 
342
  // The remaining validations all have to do with attributes.
343
  $attr_spec_list = array();
344
+ $tag_spec = array();
345
+ $cdata = array();
346
 
347
  /*
348
  * If we have exactly one rule_spec, use it's attr_spec_list
351
  if ( 1 === count( $rule_spec_list_to_validate ) ) {
352
  $rule_spec = array_pop( $rule_spec_list_to_validate );
353
  $attr_spec_list = $rule_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ];
354
+ $tag_spec = $rule_spec[ AMP_Rule_Spec::TAG_SPEC ];
355
+ if ( isset( $rule_spec[ AMP_Rule_Spec::CDATA ] ) ) {
356
+ $cdata = $rule_spec[ AMP_Rule_Spec::CDATA ];
357
+ }
358
  } else {
359
  /*
360
  * If there is more than one valid rule_spec for this node,
371
  $attr_spec_scores[ $spec_id ] = $this->validate_attr_spec_list_for_node( $node, $rule_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] );
372
  }
373
 
374
+ // Remove all spec lists that didn't match.
375
+ $attr_spec_scores = array_filter( $attr_spec_scores );
376
+
377
+ // If no attribute spec lists match, then the element must be removed.
378
+ if ( empty( $attr_spec_scores ) ) {
379
+ $this->remove_node( $node );
380
+ return;
381
+ }
382
+
383
  // Get the key(s) to the highest score(s).
384
  $spec_ids_sorted = array_keys( $attr_spec_scores, max( $attr_spec_scores ), true );
385
 
386
  // If there is exactly one attr_spec with a max score, use that one.
387
  if ( 1 === count( $spec_ids_sorted ) ) {
388
  $attr_spec_list = $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][ AMP_Rule_Spec::ATTR_SPEC_LIST ];
389
+ $tag_spec = $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][ AMP_Rule_Spec::TAG_SPEC ];
390
+ if ( isset( $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][ AMP_Rule_Spec::CDATA ] ) ) {
391
+ $cdata = $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][ AMP_Rule_Spec::CDATA ];
392
+ }
393
  } else {
394
  // This should not happen very often, but...
395
  // If we're here, then we're not sure which spec should
398
  $spec_list = isset( $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::ATTR_SPEC_LIST ] ) ? $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::ATTR_SPEC_LIST ] : null;
399
  if ( ! $this->is_missing_mandatory_attribute( $spec_list, $node ) ) {
400
  $attr_spec_list = array_merge( $attr_spec_list, $spec_list );
401
+ $tag_spec = array_merge(
402
+ $tag_spec,
403
+ $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::TAG_SPEC ]
404
+ );
405
+ if ( isset( $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::CDATA ] ) ) {
406
+ $cdata = array_merge( $cdata, $rule_spec_list_to_validate[ $id ][ AMP_Rule_Spec::CDATA ] );
407
+ }
408
  }
409
  }
410
  $first_spec = reset( $rule_spec_list_to_validate );
419
  return;
420
  }
421
 
422
+ // Remove element if it has illegal CDATA.
423
+ if ( ! empty( $cdata ) && $node instanceof DOMElement ) {
424
+ $validity = $this->validate_cdata_for_node( $node, $cdata );
425
+ if ( is_wp_error( $validity ) ) {
426
+ $this->remove_node( $node );
427
+ return;
428
+ }
429
+ }
430
+
431
+ $merged_attr_spec_list = array_merge(
432
+ $this->globally_allowed_attributes,
433
+ $attr_spec_list
434
+ );
435
+
436
+ // Identify any remaining disallowed attributes.
437
+ $disallowed_attributes = $this->get_disallowed_attributes_in_node( $node, $merged_attr_spec_list );
438
+
439
+ // Identify attribute values that don't conform to the attr_spec.
440
+ $disallowed_attributes = $this->sanitize_disallowed_attribute_values_in_node( $node, $merged_attr_spec_list, $disallowed_attributes );
441
+
442
+ // If $disallowed_attributes is false then the entire element should be removed.
443
+ if ( false === $disallowed_attributes ) {
444
+ $this->remove_node( $node );
445
+ return;
446
+ }
447
+
448
+ // Remove all invalid attributes.
449
+ foreach ( $disallowed_attributes as $disallowed_attribute ) {
450
+ $this->remove_invalid_attribute( $node, $disallowed_attribute );
451
+ }
452
+
453
+ // Add required AMP component scripts if the element is still in the document.
454
+ if ( $node->parentNode ) {
455
+ if ( ! empty( $tag_spec['also_requires_tag_warning'] ) ) {
456
+ $this->script_components[] = strtok( $tag_spec['also_requires_tag_warning'][0], ' ' );
457
+ }
458
+ if ( ! empty( $tag_spec['requires_extension'] ) ) {
459
+ $this->script_components = array_merge( $this->script_components, $tag_spec['requires_extension'] );
460
+ }
461
 
462
+ // Check if element needs amp-bind component.
463
+ if ( $node instanceof DOMElement && ! in_array( 'amp-bind', $this->script_components, true ) ) {
464
+ foreach ( $node->attributes as $name => $value ) {
465
+ $is_bind_attribute = (
466
+ '[' === $name[0]
467
+ ||
468
+ ( isset( $this->rev_alternate_attr_name_lookup[ $name ] ) && '[' === $this->rev_alternate_attr_name_lookup[ $name ][0] )
469
+ );
470
+ if ( $is_bind_attribute ) {
471
+ $this->script_components[] = 'amp-bind';
472
+ break;
473
+ }
474
+ }
475
+ }
476
+ }
477
  }
478
 
479
  /**
480
  * Whether a node is missing a mandatory attribute.
481
  *
482
+ * @param array $attr_spec The attribute specification.
483
+ * @param DOMElement $node The DOMElement of the node to check.
484
  * @return boolean $is_missing boolean Whether a required attribute is missing.
485
  */
486
  public function is_missing_mandatory_attribute( $attr_spec, $node ) {
488
  return false;
489
  }
490
  foreach ( $attr_spec as $attr_name => $attr_spec_rule_value ) {
491
+ if ( '\u' === substr( $attr_name, 0, 2 ) ) {
492
+ $attr_name = html_entity_decode( '&#x' . substr( $attr_name, 2 ) . ';' ); // Probably ⚡.
493
+ }
494
  $is_mandatory = isset( $attr_spec_rule_value[ AMP_Rule_Spec::MANDATORY ] ) ? ( true === $attr_spec_rule_value[ AMP_Rule_Spec::MANDATORY ] ) : false;
495
+ $attribute_exists = false;
496
+ if ( method_exists( $node, 'hasAttribute' ) ) {
497
+ $attribute_exists = $node->hasAttribute( $attr_name );
498
+ if ( ! $attribute_exists && ! empty( $attr_spec_rule_value[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
499
+ foreach ( $attr_spec_rule_value[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_attr_name ) {
500
+ if ( $node->hasAttribute( $alternative_attr_name ) ) {
501
+ $attribute_exists = true;
502
+ break;
503
+ }
504
+ }
505
+ }
506
+ }
507
  if ( $is_mandatory && ! $attribute_exists ) {
508
  return true;
509
  }
511
  return false;
512
  }
513
 
514
+ /**
515
+ * Validate element for its CDATA.
516
+ *
517
+ * @since 0.7
518
+ *
519
+ * @param DOMElement $element Element.
520
+ * @param array $cdata_spec CDATA.
521
+ * @return true|WP_Error True when valid or error when invalid.
522
+ */
523
+ private function validate_cdata_for_node( $element, $cdata_spec ) {
524
+ if ( isset( $cdata_spec['blacklisted_cdata_regex'] ) ) {
525
+ if ( preg_match( '@' . $cdata_spec['blacklisted_cdata_regex']['regex'] . '@u', $element->textContent ) ) {
526
+ return new WP_Error( $cdata_spec['blacklisted_cdata_regex']['error_message'] );
527
+ }
528
+ }
529
+ return true;
530
+ }
531
+
532
  /**
533
  * Determines is a node is currently valid per its tag specification.
534
  *
539
  *
540
  * @since 0.5
541
  *
542
+ * @param DOMNode $node The node to validate.
543
+ * @param array $tag_spec The specification.
544
  * @return boolean $valid Whether the node's placement is valid.
545
  */
546
  private function validate_tag_spec_for_node( $node, $tag_spec ) {
547
+
548
  if ( ! empty( $tag_spec[ AMP_Rule_Spec::MANDATORY_PARENT ] ) && ! $this->has_parent( $node, $tag_spec[ AMP_Rule_Spec::MANDATORY_PARENT ] ) ) {
549
  return false;
550
  }
551
 
552
+ // Extension scripts must be in the head.
553
+ if ( isset( $tag_spec['extension_spec'] ) && ! $this->has_parent( $node, 'head' ) ) {
554
+ return false;
555
+ }
556
+
557
  if ( ! empty( $tag_spec[ AMP_Rule_Spec::DISALLOWED_ANCESTOR ] ) ) {
558
  foreach ( $tag_spec[ AMP_Rule_Spec::DISALLOWED_ANCESTOR ] as $disallowed_ancestor_node_name ) {
559
  if ( $this->has_ancestor( $node, $disallowed_ancestor_node_name ) ) {
579
  * @param DOMNode $node Node.
580
  * @param array[] $attr_spec_list Attribute Spec list.
581
  *
582
+ * @return float Number of times the attribute spec list matched. If there was a mismatch, then 0 is returned. 0.5 is returned if there is an implicit match.
583
  */
584
  private function validate_attr_spec_list_for_node( $node, $attr_spec_list ) {
585
+ /*
586
+ * If node has no attributes there is no point in continuing, but if none of attributes
587
+ * in the spec list are mandatory, then we give this a score.
588
  */
589
  if ( ! $node->hasAttributes() ) {
590
+ foreach ( $attr_spec_list as $attr_name => $attr_spec_rule ) {
591
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] ) ) {
592
+ return 0;
593
+ }
594
+ }
595
+ return 0.5;
596
+ }
597
+
598
+ if ( ! $node instanceof DOMElement ) {
599
+ /*
600
+ * A DOMNode is not valid for checks so might
601
+ * as well bail here is not an DOMElement.
602
+ */
603
  return 0;
604
  }
605
 
612
  }
613
  }
614
 
 
 
 
 
 
 
 
 
615
  $score = 0;
616
 
617
+ /*
618
+ * Keep track of how many of the attribute spec rules are mandatory,
619
+ * because if none are mandatory, then we will let this rule have a
620
+ * score since all the invalid attributes can just be removed.
621
+ */
622
+ $mandatory_count = 0;
623
+
624
  /*
625
  * Iterate through each attribute rule in this attr spec list and run
626
  * the series of tests. Each filter is given a `$node`, an `$attr_name`,
629
  */
630
  foreach ( $attr_spec_list as $attr_name => $attr_spec_rule ) {
631
 
632
+ // If attr spec rule is empty, then it allows anything.
633
+ if ( empty( $attr_spec_rule ) && $node->hasAttribute( $attr_name ) ) {
634
+ $score++;
635
+ continue;
636
+ }
637
+
638
  // If a mandatory attribute is required, and attribute exists, pass.
639
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] ) ) {
640
+ $mandatory_count++;
641
+ $result = $this->check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule );
642
+ if ( AMP_Rule_Spec::PASS === $result ) {
643
  $score++;
644
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
645
+ return 0;
646
  }
647
  }
648
 
651
  * Given attribute's value must exactly equal value of the rule to pass.
652
  */
653
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) ) {
654
+ $result = $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule );
655
+ if ( AMP_Rule_Spec::PASS === $result ) {
656
  $score++;
657
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
658
+ return 0;
659
  }
660
  }
661
 
665
  * specified by the value of rule to pass.
666
  */
667
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX ] ) ) {
668
+ $result = $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule );
669
+ if ( AMP_Rule_Spec::PASS === $result ) {
670
  $score++;
671
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
672
+ return 0;
673
  }
674
  }
675
 
679
  * the rule to pass.
680
  */
681
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_CASEI ] ) ) {
682
+ $result = $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule );
683
+ if ( AMP_Rule_Spec::PASS === $result ) {
684
  $score++;
685
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
686
+ return 0;
687
  }
688
  }
689
 
693
  * pattern specified by the value of the rule to pass.
694
  */
695
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX_CASEI ] ) ) {
696
+ $result = $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule );
697
+ if ( AMP_Rule_Spec::PASS === $result ) {
698
  $score++;
699
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
700
+ return 0;
701
  }
702
  }
703
 
705
  * If given attribute's value is a URL with a protocol, the protocol must
706
  * be in the array specified by the rule's value to pass.
707
  */
708
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOWED_PROTOCOL ] ) ) {
709
+ $result = $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule );
710
+ if ( AMP_Rule_Spec::PASS === $result ) {
711
+ $score++;
712
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
713
+ return 0;
714
+ }
715
+ }
716
+
717
+ /*
718
+ * If given attribute's value is a URL with a host, the host must
719
+ * be valid
720
+ */
721
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ] ) ) {
722
+ $result = $this->check_attr_spec_rule_valid_url( $node, $attr_name, $attr_spec_rule );
723
+ if ( AMP_Rule_Spec::PASS === $result ) {
724
  $score++;
725
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
726
+ return 0;
727
  }
728
  }
729
 
731
  * If the given attribute's value is *not* a relative path, and the rule's
732
  * value is `false`, then pass.
733
  */
734
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_RELATIVE ] ) ) {
735
+ $result = $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule );
736
+ if ( AMP_Rule_Spec::PASS === $result ) {
737
  $score++;
738
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
739
+ return 0;
740
  }
741
  }
742
 
744
  * If the given attribute's value exists, is non-empty and the rule's value
745
  * is false, then pass.
746
  */
747
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) ) {
748
+ $result = $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule );
749
+ if ( AMP_Rule_Spec::PASS === $result ) {
750
  $score++;
751
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
752
+ return 0;
753
  }
754
  }
755
 
758
  * of domains in the value of the rule, then pass.
759
  */
760
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::DISALLOWED_DOMAIN ] ) ) {
761
+ $result = $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule );
762
+ if ( AMP_Rule_Spec::PASS === $result ) {
763
  $score++;
764
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
765
+ return 0;
766
  }
767
  }
768
 
771
  * by the rule's value, then pass.
772
  */
773
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX ] ) ) {
774
+ $result = $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule );
775
+ if ( AMP_Rule_Spec::PASS === $result ) {
776
  $score++;
777
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
778
+ return 0;
779
  }
780
  }
781
+
782
+ // If the attribute's value exists and it matches the value properties spec.
783
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_PROPERTIES ] ) && $node->hasAttribute( $attr_name ) ) {
784
+ $result = $this->check_attr_spec_rule_value_properties( $node, $attr_name, $attr_spec_rule );
785
+ if ( AMP_Rule_Spec::PASS === $result ) {
786
+ $score++;
787
+ } elseif ( AMP_Rule_Spec::FAIL === $result ) {
788
+ return 0;
789
+ }
790
+ }
791
+ }
792
+
793
+ // Give the spec a score if it doesn't have any mandatory attributes.
794
+ if ( 0 === $mandatory_count && 0 === $score ) {
795
+ $score = 0.5;
796
  }
797
 
798
  return $score;
803
  *
804
  * @param DOMNode $node Node.
805
  * @param array[] $attr_spec_list Attribute spec list.
806
+ * @return DOMAttr[] Attributes to remove.
807
  */
808
+ private function get_disallowed_attributes_in_node( $node, $attr_spec_list ) {
809
 
810
  if ( ! $node instanceof DOMElement ) {
811
  /**
812
  * If $node is only a DOMNode and not a DOMElement we can't
813
  * remove an attribute from it anyway. So bail out now.
814
  */
815
+ return array();
816
  }
817
 
818
  /*
823
  $attrs_to_remove = array();
824
  foreach ( $node->attributes as $attr_name => $attr_node ) {
825
  if ( ! $this->is_amp_allowed_attribute( $attr_name, $attr_spec_list ) ) {
826
+ $attrs_to_remove[] = $attr_node;
 
 
 
827
  }
828
  }
829
 
830
+ return $attrs_to_remove;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
831
  }
832
 
833
  /**
835
  *
836
  * Allowed values are found $this->globally_allowed_attributes and in parameter $attr_spec_list
837
  *
838
+ * @param DOMNode $node Node.
839
+ * @param array[][] $attr_spec_list Attribute spec list.
840
+ * @param DOMAttr[] $attributes_pending_removal Attributes pending removal.
841
+ * @return DOMAttr[]|false Attributes to remove, or false if the element itself should be removed.
842
  */
843
+ private function sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list, $attributes_pending_removal ) {
844
 
845
  if ( ! $node instanceof DOMElement ) {
846
  /*
847
  * If $node is only a DOMNode and not a DOMElement we can't
848
  * remove an attribute from it anyway. So bail out now.
849
  */
850
+ return $attributes_pending_removal;
851
  }
852
 
853
+ return $this->delegated_sanitize_disallowed_attribute_values_in_node(
854
+ $node,
855
+ array_merge(
856
+ $this->globally_allowed_attributes,
857
+ $attr_spec_list
858
+ ),
859
+ $attributes_pending_removal
860
+ );
861
  }
862
 
863
  /**
865
  *
866
  * @see $this->sanitize_disallowed_attribute_values_in_node() which delegates to this method
867
  *
868
+ * @param DOMElement $node Node.
869
+ * @param array[][] $attr_spec_list Attribute spec list.
870
+ * @param DOMAttr[] $attributes_pending_removal Attributes pending removal.
871
+ * @return DOMAttr[]|false Attributes to remove, or false if the element itself should be removed.
872
  */
873
+ private function delegated_sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list, $attributes_pending_removal ) {
874
  $attrs_to_remove = array();
875
 
876
  foreach ( $attr_spec_list as $attr_name => $attr_val ) {
883
 
884
  foreach ( $node->attributes as $attr_name => $attr_node ) {
885
 
886
+ if ( ! isset( $attr_spec_list[ $attr_name ] ) || in_array( $attr_node, $attributes_pending_removal, true ) ) {
887
  continue;
888
  }
889
 
897
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
898
  $should_remove_node = true;
899
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX ] ) &&
900
+ AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
901
  $should_remove_node = true;
902
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX_CASEI ] ) &&
903
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
904
  $should_remove_node = true;
905
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOWED_PROTOCOL ] ) &&
906
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
907
  $should_remove_node = true;
908
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ] ) &&
909
+ AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_valid_url( $node, $attr_name, $attr_spec_rule ) ) {
910
+ $should_remove_node = true;
911
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_RELATIVE ] ) &&
912
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
913
  $should_remove_node = true;
914
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) &&
915
  AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
916
  $should_remove_node = true;
917
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::DISALLOWED_DOMAIN ] ) &&
930
 
931
  if ( $is_mandatory ) {
932
  $this->remove_node( $node );
933
+ return false;
934
  }
935
 
936
+ $attrs_to_remove[] = $attr_node;
937
  }
938
  }
939
 
940
  // Remove the disallowed values.
941
+ foreach ( $attrs_to_remove as $attr_node ) {
942
+ if ( isset( $attr_spec_list[ $attr_node->nodeName ][ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) &&
943
+ ( true === $attr_spec_list[ $attr_node->nodeName ][ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) ) {
944
+ $attr_node->nodeValue = '';
945
  } else {
946
+ $attributes_pending_removal[] = $attr_node;
947
  }
948
  }
949
+
950
+ return $attributes_pending_removal;
951
  }
952
 
953
  /**
990
  *
991
  * @param DOMElement $node Node.
992
  * @param string $attr_name Attribute name.
993
+ * @param array $attr_spec_rule Attribute spec rule.
994
  *
995
  * @return string:
996
  * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
1001
  private function check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) {
1002
  if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) ) {
1003
  if ( $node->hasAttribute( $attr_name ) ) {
1004
+ if ( $this->check_matching_attribute_value( $attr_name, $node->getAttribute( $attr_name ), $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) ) {
1005
  return AMP_Rule_Spec::PASS;
1006
  } else {
1007
  return AMP_Rule_Spec::FAIL;
1009
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
1010
  foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
1011
  if ( $node->hasAttribute( $alternative_name ) ) {
1012
+ if ( $this->check_matching_attribute_value( $attr_name, $node->getAttribute( $alternative_name ), $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) ) {
1013
  return AMP_Rule_Spec::PASS;
1014
  } else {
1015
  return AMP_Rule_Spec::FAIL;
1021
  return AMP_Rule_Spec::NOT_APPLICABLE;
1022
  }
1023
 
1024
+ /**
1025
+ * Check that an attribute's value matches is given spec value.
1026
+ *
1027
+ * This takes into account boolean attributes where value can match name (e.g. selected="selected").
1028
+ *
1029
+ * @since 0.7.0
1030
+ *
1031
+ * @param string $attr_name Attribute name.
1032
+ * @param string $attr_value Attribute value.
1033
+ * @param string $spec_value Attribute spec value.
1034
+ * @return bool Is value valid.
1035
+ */
1036
+ private function check_matching_attribute_value( $attr_name, $attr_value, $spec_value ) {
1037
+ if ( $spec_value === $attr_value ) {
1038
+ return true;
1039
+ }
1040
+
1041
+ // Check for boolean attribute.
1042
+ return (
1043
+ '' === $spec_value
1044
+ &&
1045
+ in_array( $attr_name, AMP_Rule_Spec::$boolean_attributes, true )
1046
+ &&
1047
+ strtolower( $attr_value ) === strtolower( $attr_name )
1048
+ );
1049
+ }
1050
+
1051
  /**
1052
  * Check if attribute has a value rule determine if its value matches ignoring case.
1053
  *
1153
  return AMP_Rule_Spec::NOT_APPLICABLE;
1154
  }
1155
 
1156
+ /**
1157
+ * Check if attribute has a valid host value
1158
+ *
1159
+ * @since 0.7
1160
+ *
1161
+ * @param DOMElement $node Node.
1162
+ * @param string $attr_name Attribute name.
1163
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
1164
+ *
1165
+ * @return string:
1166
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
1167
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
1168
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
1169
+ * is no rule for this attribute.
1170
+ */
1171
+ private function check_attr_spec_rule_valid_url( $node, $attr_name, $attr_spec_rule ) {
1172
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ] ) ) {
1173
+ if ( $node->hasAttribute( $attr_name ) ) {
1174
+ $urls_to_test = preg_split( '/\s*,\s*/', $node->getAttribute( $attr_name ) );
1175
+ foreach ( $urls_to_test as $url ) {
1176
+ $url = urldecode( $url );
1177
+ // Check if the host contains invalid chars.
1178
+ $url_host = wp_parse_url( $url, PHP_URL_HOST );
1179
+ if ( $url_host && preg_match( '/[!"#$%&\'()*+,\/:;<=>?@[\]^`{|}~\s]/i', $url_host ) ) {
1180
+ return AMP_Rule_Spec::FAIL;
1181
+ }
1182
+
1183
+ // Check if the protocol contains invalid chars.
1184
+ $dots_pos = strpos( $url, ':' );
1185
+ if ( false !== $dots_pos && preg_match( '/[!"#$%&\'()*+,\/:;<=>?@[\]^`{|}~\s]/i', substr( $url, 0, $dots_pos ) ) ) {
1186
+ return AMP_Rule_Spec::FAIL;
1187
+ }
1188
+ }
1189
+
1190
+ return AMP_Rule_Spec::PASS;
1191
+ }
1192
+ }
1193
+
1194
+ return AMP_Rule_Spec::NOT_APPLICABLE;
1195
+ }
1196
+
1197
  /**
1198
  * Check if attribute has a protocol value rule determine if it matches.
1199
  *
1208
  * is no rule for this attribute.
1209
  */
1210
  private function check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) {
1211
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOWED_PROTOCOL ] ) ) {
1212
  if ( $node->hasAttribute( $attr_name ) ) {
1213
+ $urls_to_test = preg_split( '/\s*,\s*/', $node->getAttribute( $attr_name ) );
 
 
1214
  foreach ( $urls_to_test as $url ) {
1215
  /*
1216
  * This seems to be an acceptable check since the AMP validator
1218
  */
1219
  $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME );
1220
  if ( $url_scheme ) {
1221
+ if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOWED_PROTOCOL ], true ) ) {
1222
  return AMP_Rule_Spec::FAIL;
1223
  }
1224
  }
1227
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
1228
  foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
1229
  if ( $node->hasAttribute( $alternative_name ) ) {
1230
+ $urls_to_test = preg_split( '/\s*,\s*/', $node->getAttribute( $alternative_name ) );
 
 
1231
  foreach ( $urls_to_test as $url ) {
1232
  /*
1233
  * This seems to be an acceptable check since the AMP validator
1235
  */
1236
  $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME );
1237
  if ( $url_scheme ) {
1238
+ if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOWED_PROTOCOL ], true ) ) {
1239
  return AMP_Rule_Spec::FAIL;
1240
  }
1241
  }
1262
  * is no rule for this attribute.
1263
  */
1264
  private function check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) {
1265
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_RELATIVE ] ) && ! ( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_RELATIVE ] ) ) {
1266
  if ( $node->hasAttribute( $attr_name ) ) {
1267
+ $urls_to_test = preg_split( '/\s*,\s*/', $node->getAttribute( $attr_name ) );
 
 
1268
  foreach ( $urls_to_test as $url ) {
1269
  $parsed_url = AMP_WP_Utils::parse_url( $url );
1270
 
1283
  } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
1284
  foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
1285
  if ( $node->hasAttribute( $alternative_name ) ) {
1286
+ $urls_to_test = preg_split( '/\s*,\s*/', $node->getAttribute( $alternative_name ) );
 
 
1287
  foreach ( $urls_to_test as $url ) {
1288
  $parsed_url = AMP_WP_Utils::parse_url( $url );
1289
  if ( empty( $parsed_url['scheme'] ) ) {
1312
  * is no rule for this attribute.
1313
  */
1314
  private function check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) {
1315
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) && ! ( $attr_spec_rule[ AMP_Rule_Spec::VALUE_URL ][ AMP_Rule_Spec::ALLOW_EMPTY ] ) && $node->hasAttribute( $attr_name ) ) {
1316
  $attr_value = $node->getAttribute( $attr_name );
1317
  if ( empty( $attr_value ) ) {
1318
  return AMP_Rule_Spec::FAIL;
1394
  return AMP_Rule_Spec::NOT_APPLICABLE;
1395
  }
1396
 
1397
+ /**
1398
+ * Check if attribute has valid properties.
1399
+ *
1400
+ * @since 0.7
1401
+ *
1402
+ * @param DOMElement $node Node.
1403
+ * @param string $attr_name Attribute name.
1404
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
1405
+ *
1406
+ * @return string:
1407
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
1408
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
1409
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
1410
+ * is no rule for this attribute.
1411
+ */
1412
+ private function check_attr_spec_rule_value_properties( $node, $attr_name, $attr_spec_rule ) {
1413
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_PROPERTIES ] ) && $node->hasAttribute( $attr_name ) ) {
1414
+ $properties = array();
1415
+ foreach ( explode( ',', $node->getAttribute( $attr_name ) ) as $pair ) {
1416
+ $pair_parts = explode( '=', $pair, 2 );
1417
+ if ( 2 !== count( $pair_parts ) ) {
1418
+ return 0;
1419
+ }
1420
+ $properties[ strtolower( $pair_parts[0] ) ] = $pair_parts[1];
1421
+ }
1422
+
1423
+ // Fail if there are unrecognized properties.
1424
+ if ( count( array_diff( array_keys( $properties ), array_keys( $attr_spec_rule[ AMP_Rule_Spec::VALUE_PROPERTIES ] ) ) ) > 0 ) {
1425
+ return AMP_Rule_Spec::FAIL;
1426
+ }
1427
+
1428
+ foreach ( $attr_spec_rule[ AMP_Rule_Spec::VALUE_PROPERTIES ] as $prop_name => $property_spec ) {
1429
+
1430
+ // Mandatory property is missing.
1431
+ if ( ! empty( $property_spec['mandatory'] ) && ! isset( $properties[ $prop_name ] ) ) {
1432
+ return AMP_Rule_Spec::FAIL;
1433
+ }
1434
+
1435
+ if ( ! isset( $properties[ $prop_name ] ) ) {
1436
+ continue;
1437
+ }
1438
+
1439
+ $prop_value = $properties[ $prop_name ];
1440
+
1441
+ // Required value is absent, so fail.
1442
+ $required_value = null;
1443
+ if ( isset( $property_spec['value'] ) ) {
1444
+ $required_value = $property_spec['value'];
1445
+ } elseif ( isset( $property_spec['value_double'] ) ) {
1446
+ $required_value = $property_spec['value_double'];
1447
+ $prop_value = (double) $prop_value;
1448
+ }
1449
+ if ( isset( $required_value ) && $prop_value !== $required_value ) {
1450
+ return AMP_Rule_Spec::FAIL;
1451
+ }
1452
+ }
1453
+ return AMP_Rule_Spec::PASS;
1454
+ }
1455
+ return AMP_Rule_Spec::NOT_APPLICABLE;
1456
+ }
1457
+
1458
  /**
1459
  * Determine if the supplied attribute name is allowed for AMP.
1460
  *
1474
  }
1475
  }
1476
  }
1477
+
1478
+ $is_allowed_alt_name_attr = (
1479
+ isset( $this->rev_alternate_attr_name_lookup[ $attr_name ] )
1480
+ &&
1481
+ isset( $attr_spec_list[ $this->rev_alternate_attr_name_lookup[ $attr_name ] ] )
1482
+ );
1483
+ if ( $is_allowed_alt_name_attr ) {
1484
+ return true;
1485
+ }
1486
+
1487
  return false;
1488
  }
1489
 
1531
  *
1532
  * @since 0.5
1533
  *
1534
+ * @todo The $ancestor_tag_name here is not sufficient as it is not just a tag name but an entire selector that is used.
1535
  * @param DOMNode $node Node.
1536
  * @param string $ancestor_tag_name Ancestor tag name.
1537
  * @return bool Return true if given node has any ancestor with the give name, false otherwise.
1617
  */
1618
  $parent = $node->parentNode;
1619
  if ( $node && $parent ) {
1620
+ $this->remove_invalid_child( $node );
1621
  }
1622
+ while ( $parent && ! $parent->hasChildNodes() && $this->root_element !== $parent ) {
1623
  $node = $parent;
1624
  $parent = $parent->parentNode;
1625
  if ( $parent ) {
1628
  }
1629
  }
1630
  }
 
includes/sanitizers/class-amp-video-sanitizer.php CHANGED
@@ -32,42 +32,6 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
32
  */
33
  public static $tag = 'video';
34
 
35
- /**
36
- * Script tag.
37
- *
38
- * @var string AMP HTML tag to use in place of HTML's <video> tag.
39
- *
40
- * @since 0.2
41
- */
42
- private static $script_slug = 'amp-video';
43
-
44
- /**
45
- * Script src.
46
- *
47
- * @var string URL to AMP Project's Video element's JavaScript file found at cdn.ampproject.org
48
- *
49
- * @since 0.2
50
- */
51
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-video-0.1.js';
52
-
53
- /**
54
- * Return one element array containing AMP HTML video tag and respective Javascript URL
55
- *
56
- * HTML tags and Javascript URLs found at cdn.ampproject.org
57
- *
58
- * @since 0.2
59
- *
60
- * @return string[] Returns AMP HTML video tag as array key and Javascript URL as array value,
61
- * respectively. Will return an empty array if sanitization has yet to be run
62
- * or if it did not find any HTML video elements to convert to AMP equivalents.
63
- */
64
- public function get_scripts() {
65
- if ( ! $this->did_convert_elements ) {
66
- return array();
67
- }
68
- return array( self::$script_slug => self::$script_src );
69
- }
70
-
71
  /**
72
  * Sanitize the <video> elements from the HTML contained in this instance's DOMDocument.
73
  *
@@ -85,9 +49,10 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
85
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
86
 
87
  $new_attributes = $this->filter_attributes( $old_attributes );
88
-
89
- $new_attributes = $this->enforce_fixed_height( $new_attributes );
90
- $new_attributes = $this->enforce_sizes_attribute( $new_attributes );
 
91
 
92
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-video', $new_attributes );
93
 
@@ -129,7 +94,7 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
129
  * See: https://github.com/ampproject/amphtml/issues/2261
130
  */
131
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
132
- $node->parentNode->removeChild( $node );
133
  } else {
134
  $node->parentNode->replaceChild( $new_node, $node );
135
  }
32
  */
33
  public static $tag = 'video';
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  /**
36
  * Sanitize the <video> elements from the HTML contained in this instance's DOMDocument.
37
  *
49
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
50
 
51
  $new_attributes = $this->filter_attributes( $old_attributes );
52
+ $new_attributes = $this->set_layout( $new_attributes );
53
+ if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['width'] ) && ! empty( $new_attributes['height'] ) ) {
54
+ $new_attributes['layout'] = 'responsive';
55
+ }
56
 
57
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-video', $new_attributes );
58
 
94
  * See: https://github.com/ampproject/amphtml/issues/2261
95
  */
96
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
97
+ $this->remove_invalid_child( $node );
98
  } else {
99
  $node->parentNode->replaceChild( $new_node, $node );
100
  }
includes/templates/class-amp-content-sanitizer.php CHANGED
@@ -7,31 +7,74 @@
7
 
8
  /**
9
  * Class AMP_Content_Sanitizer
 
 
10
  */
11
  class AMP_Content_Sanitizer {
12
 
13
  /**
14
- * Sanitize.
 
 
 
15
  *
16
- * @param string $content Content.
17
  * @param string[] $sanitizer_classes Sanitizer classes.
18
  * @param array $global_args Global args.
19
- *
20
- * @return array
21
  */
22
  public static function sanitize( $content, array $sanitizer_classes, $global_args = array() ) {
23
- $scripts = array();
24
- $styles = array();
25
- $dom = AMP_DOM_Utils::get_dom_from_content( $content );
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- foreach ( $sanitizer_classes as $sanitizer_class => $args ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  if ( ! class_exists( $sanitizer_class ) ) {
29
  /* translators: %s is sanitizer class */
30
  _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) class does not exist', 'amp' ), esc_html( $sanitizer_class ) ), '0.4.1' );
31
  continue;
32
  }
33
 
34
- $sanitizer = new $sanitizer_class( $dom, array_merge( $global_args, $args ) );
 
 
 
 
 
35
 
36
  if ( ! is_subclass_of( $sanitizer, 'AMP_Base_Sanitizer' ) ) {
37
  /* translators: %s is sanitizer class */
@@ -42,12 +85,14 @@ class AMP_Content_Sanitizer {
42
  $sanitizer->sanitize();
43
 
44
  $scripts = array_merge( $scripts, $sanitizer->get_scripts() );
45
- $styles = array_merge( $styles, $sanitizer->get_styles() );
 
 
 
 
46
  }
47
 
48
- $sanitized_content = AMP_DOM_Utils::get_content_from_dom( $dom );
49
-
50
- return array( $sanitized_content, $scripts, $styles );
51
  }
52
  }
53
 
7
 
8
  /**
9
  * Class AMP_Content_Sanitizer
10
+ *
11
+ * @since 0.4.1
12
  */
13
  class AMP_Content_Sanitizer {
14
 
15
  /**
16
+ * Sanitize _content_.
17
+ *
18
+ * @since 0.4.1
19
+ * @since 0.7 Passing return_styles=false in $global_args causes stylesheets to be returned instead of styles.
20
  *
21
+ * @param string $content HTML content string or DOM document.
22
  * @param string[] $sanitizer_classes Sanitizer classes.
23
  * @param array $global_args Global args.
24
+ * @return array Tuple containing sanitized HTML, scripts array, and styles array (or stylesheets, if return_styles=false is passed in $global_args).
 
25
  */
26
  public static function sanitize( $content, array $sanitizer_classes, $global_args = array() ) {
27
+ $dom = AMP_DOM_Utils::get_dom_from_content( $content );
28
+
29
+ // For back-compat.
30
+ if ( ! isset( $global_args['return_styles'] ) ) {
31
+ $global_args['return_styles'] = true;
32
+ }
33
+
34
+ $results = self::sanitize_document( $dom, $sanitizer_classes, $global_args );
35
+ return array(
36
+ AMP_DOM_Utils::get_content_from_dom( $dom ),
37
+ $results['scripts'],
38
+ empty( $global_args['return_styles'] ) ? $results['stylesheets'] : $results['styles'],
39
+ );
40
+ }
41
 
42
+ /**
43
+ * Sanitize document.
44
+ *
45
+ * @since 0.7
46
+ *
47
+ * @param DOMDocument $dom HTML document.
48
+ * @param string[] $sanitizer_classes Sanitizer classes.
49
+ * @param array $args Global args passed into sanitizers.
50
+ * @return array {
51
+ * Scripts and stylesheets needed by sanitizers.
52
+ *
53
+ * @type array $scripts Scripts.
54
+ * @type array $stylesheets Stylesheets. If $args['return_styles'] is empty.
55
+ * @type array $styles Styles. If $args['return_styles'] is not empty. For legacy purposes.
56
+ * }
57
+ */
58
+ public static function sanitize_document( &$dom, $sanitizer_classes, $args ) {
59
+ $scripts = array();
60
+ $stylesheets = array();
61
+ $styles = array();
62
+
63
+ $return_styles = ! empty( $args['return_styles'] );
64
+ unset( $args['return_styles'] );
65
+ foreach ( $sanitizer_classes as $sanitizer_class => $sanitizer_args ) {
66
  if ( ! class_exists( $sanitizer_class ) ) {
67
  /* translators: %s is sanitizer class */
68
  _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) class does not exist', 'amp' ), esc_html( $sanitizer_class ) ), '0.4.1' );
69
  continue;
70
  }
71
 
72
+ /**
73
+ * Sanitizer.
74
+ *
75
+ * @type AMP_Base_Sanitizer $sanitizer
76
+ */
77
+ $sanitizer = new $sanitizer_class( $dom, array_merge( $args, $sanitizer_args ) );
78
 
79
  if ( ! is_subclass_of( $sanitizer, 'AMP_Base_Sanitizer' ) ) {
80
  /* translators: %s is sanitizer class */
85
  $sanitizer->sanitize();
86
 
87
  $scripts = array_merge( $scripts, $sanitizer->get_scripts() );
88
+ if ( $return_styles ) {
89
+ $styles = array_merge( $styles, $sanitizer->get_styles() );
90
+ } else {
91
+ $stylesheets = array_merge( $stylesheets, $sanitizer->get_stylesheets() );
92
+ }
93
  }
94
 
95
+ return compact( 'scripts', 'styles', 'stylesheets' );
 
 
96
  }
97
  }
98
 
includes/templates/class-amp-post-template.php CHANGED
@@ -137,17 +137,7 @@ class AMP_Post_Template {
137
 
138
  'post_amp_styles' => array(),
139
 
140
- /**
141
- * Add amp-analytics tags.
142
- *
143
- * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting.
144
- *
145
- * @since 0.4
146
- *
147
- * @param array $analytics An associative array of the analytics entries we want to output. Each array entry must have a unique key, and the value should be an array with the following keys: `type`, `attributes`, `script_data`. See readme for more details.
148
- * @param WP_Post $post The current post.
149
- */
150
- 'amp_analytics' => apply_filters( 'amp_post_template_analytics', array(), $this->post ),
151
  );
152
 
153
  $this->build_post_content();
@@ -287,42 +277,6 @@ class AMP_Post_Template {
287
  )
288
  );
289
 
290
- $metadata = array(
291
- '@context' => 'http://schema.org',
292
- '@type' => is_page() ? 'WebPage' : 'BlogPosting',
293
- 'mainEntityOfPage' => $this->get( 'canonical_url' ),
294
- 'publisher' => array(
295
- '@type' => 'Organization',
296
- 'name' => $this->get( 'blog_name' ),
297
- ),
298
- 'headline' => $post_title,
299
- 'datePublished' => date( 'c', $post_publish_timestamp ),
300
- 'dateModified' => date( 'c', $post_modified_timestamp ),
301
- );
302
- if ( $post_author ) {
303
- $metadata['author'] = array(
304
- '@type' => 'Person',
305
- 'name' => html_entity_decode( $post_author->display_name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
306
- );
307
- }
308
-
309
- $site_icon_url = $this->get( 'site_icon_url' );
310
- if ( $site_icon_url ) {
311
- $metadata['publisher']['logo'] = array(
312
- '@type' => 'ImageObject',
313
- 'url' => $site_icon_url,
314
- 'height' => self::SITE_ICON_SIZE,
315
- 'width' => self::SITE_ICON_SIZE,
316
- );
317
- }
318
-
319
- $image_metadata = $this->get_post_image_metadata();
320
- if ( $image_metadata ) {
321
- $metadata['image'] = $image_metadata;
322
- }
323
-
324
- $this->add_data_by_key( 'metadata', apply_filters( 'amp_post_template_metadata', $metadata, $this->post ) );
325
-
326
  $this->build_post_featured_image();
327
  $this->build_post_commments_data();
328
  }
@@ -362,34 +316,8 @@ class AMP_Post_Template {
362
  private function build_post_content() {
363
  $amp_content = new AMP_Content(
364
  $this->post->post_content,
365
- apply_filters(
366
- 'amp_content_embed_handlers', array(
367
- 'AMP_Twitter_Embed_Handler' => array(),
368
- 'AMP_YouTube_Embed_Handler' => array(),
369
- 'AMP_DailyMotion_Embed_Handler' => array(),
370
- 'AMP_Vimeo_Embed_Handler' => array(),
371
- 'AMP_SoundCloud_Embed_Handler' => array(),
372
- 'AMP_Instagram_Embed_Handler' => array(),
373
- 'AMP_Vine_Embed_Handler' => array(),
374
- 'AMP_Facebook_Embed_Handler' => array(),
375
- 'AMP_Pinterest_Embed_Handler' => array(),
376
- 'AMP_Gallery_Embed_Handler' => array(),
377
- 'WPCOM_AMP_Polldaddy_Embed' => array(),
378
- ), $this->post
379
- ),
380
- apply_filters(
381
- 'amp_content_sanitizers', array(
382
- 'AMP_Style_Sanitizer' => array(),
383
- 'AMP_Img_Sanitizer' => array(),
384
- 'AMP_Video_Sanitizer' => array(),
385
- 'AMP_Audio_Sanitizer' => array(),
386
- 'AMP_Playbuzz_Sanitizer' => array(),
387
- 'AMP_Iframe_Sanitizer' => array(
388
- 'add_placeholder' => true,
389
- ),
390
- '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.
391
- ), $this->post
392
- ),
393
  array(
394
  'content_max_width' => $this->get( 'content_max_width' ),
395
  )
@@ -427,7 +355,7 @@ class AMP_Post_Template {
427
 
428
  list( $sanitized_html, $featured_scripts, $featured_styles ) = AMP_Content_Sanitizer::sanitize(
429
  $featured_html,
430
- array( 'AMP_Img_Sanitizer' => array() ),
431
  array(
432
  'content_max_width' => $this->get( 'content_max_width' ),
433
  )
@@ -472,54 +400,6 @@ class AMP_Post_Template {
472
  $this->add_data_by_key( 'customizer_settings', apply_filters( 'amp_post_template_customizer_settings', $settings, $this->post ) );
473
  }
474
 
475
- /**
476
- * Grabs featured image or the first attached image for the post
477
- *
478
- * TODO: move to a utils class?
479
- */
480
- private function get_post_image_metadata() {
481
- $post_image_meta = null;
482
- $post_image_id = false;
483
-
484
- if ( has_post_thumbnail( $this->ID ) ) {
485
- $post_image_id = get_post_thumbnail_id( $this->ID );
486
- } else {
487
- $attached_image_ids = get_posts(
488
- array(
489
- 'post_parent' => $this->ID,
490
- 'post_type' => 'attachment',
491
- 'post_mime_type' => 'image',
492
- 'posts_per_page' => 1,
493
- 'orderby' => 'menu_order',
494
- 'order' => 'ASC',
495
- 'fields' => 'ids',
496
- 'suppress_filters' => false,
497
- )
498
- );
499
-
500
- if ( ! empty( $attached_image_ids ) ) {
501
- $post_image_id = array_shift( $attached_image_ids );
502
- }
503
- }
504
-
505
- if ( ! $post_image_id ) {
506
- return false;
507
- }
508
-
509
- $post_image_src = wp_get_attachment_image_src( $post_image_id, 'full' );
510
-
511
- if ( is_array( $post_image_src ) ) {
512
- $post_image_meta = array(
513
- '@type' => 'ImageObject',
514
- 'url' => $post_image_src[0],
515
- 'width' => $post_image_src[1],
516
- 'height' => $post_image_src[2],
517
- );
518
- }
519
-
520
- return $post_image_meta;
521
- }
522
-
523
  /**
524
  * Build HTML tag attributes.
525
  */
137
 
138
  'post_amp_styles' => array(),
139
 
140
+ 'amp_analytics' => amp_add_custom_analytics(),
 
 
 
 
 
 
 
 
 
 
141
  );
142
 
143
  $this->build_post_content();
277
  )
278
  );
279
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  $this->build_post_featured_image();
281
  $this->build_post_commments_data();
282
  }
316
  private function build_post_content() {
317
  $amp_content = new AMP_Content(
318
  $this->post->post_content,
319
+ amp_get_content_embed_handlers( $this->post ),
320
+ amp_get_content_sanitizers( $this->post ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  array(
322
  'content_max_width' => $this->get( 'content_max_width' ),
323
  )
355
 
356
  list( $sanitized_html, $featured_scripts, $featured_styles ) = AMP_Content_Sanitizer::sanitize(
357
  $featured_html,
358
+ amp_get_content_sanitizers( $this->post ),
359
  array(
360
  'content_max_width' => $this->get( 'content_max_width' ),
361
  )
400
  $this->add_data_by_key( 'customizer_settings', apply_filters( 'amp_post_template_customizer_settings', $settings, $this->post ) );
401
  }
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  /**
404
  * Build HTML tag attributes.
405
  */
includes/utils/class-amp-dom-utils.php CHANGED
@@ -13,29 +13,125 @@
13
  class AMP_DOM_Utils {
14
 
15
  /**
16
- * Return a valid DOMDocument representing arbitrary HTML content passed as a parameter.
17
  *
18
- * @see Reciprocal function get_content_from_dom()
19
  *
20
- * @since 0.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  *
22
- * @param string $content Valid HTML content to be represented by a DOMDocument.
 
 
 
 
 
 
23
  *
 
 
 
 
24
  * @return DOMDocument|false Returns DOMDocument, or false if conversion failed.
25
  */
26
- public static function get_dom_from_content( $content ) {
27
  $libxml_previous_state = libxml_use_internal_errors( true );
28
 
29
  $dom = new DOMDocument();
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  /*
32
  * Wrap in dummy tags, since XML needs one parent node.
33
  * It also makes it easier to loop through nodes.
34
  * We can later use this to extract our nodes.
35
- * Add utf-8 charset so loadHTML does not have problems parsing it.
36
- * See: http://php.net/manual/en/domdocument.loadhtml.php#78243
37
  */
38
- $result = $dom->loadHTML( '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $content . '</body></html>' );
39
 
40
  libxml_clear_errors();
41
  libxml_use_internal_errors( $libxml_previous_state );
@@ -44,24 +140,207 @@ class AMP_DOM_Utils {
44
  return false;
45
  }
46
 
 
 
 
 
 
 
 
 
47
  return $dom;
48
  }
49
 
50
  /**
51
- * Return valid HTML content extracted from the DOMDocument passed as a parameter.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  *
53
- * @see Reciprocal function get_dom_from_content()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  *
55
  * @since 0.2
56
  *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  * @param DOMDocument $dom Represents an HTML document from which to extract HTML content.
58
  *
59
- * @return string Returns the HTML content represented in the DOMDocument
60
  */
61
  public static function get_content_from_dom( $dom ) {
62
 
63
  /**
64
  * We only want children of the body tag, since we have a subset of HTML.
 
 
65
  */
66
  $body = $dom->getElementsByTagName( 'body' )->item( 0 );
67
 
@@ -85,12 +364,13 @@ class AMP_DOM_Utils {
85
  /**
86
  * Return valid HTML content extracted from the DOMNode passed as a parameter.
87
  *
88
- * @see Called by function get_content_from_dom()
89
- *
90
  * @since 0.6
 
 
 
91
  *
92
  * @param DOMDocument $dom Represents an HTML document.
93
- * @param DOMNode $node Represents an HTML element of the $dom from which to extract HTML content.
94
  * @return string Returns the HTML content represented in the DOMNode
95
  */
96
  public static function get_content_from_dom_node( $dom, $node ) {
@@ -102,39 +382,49 @@ class AMP_DOM_Utils {
102
  */
103
  static $self_closing_tags_regex;
104
 
105
- /*
106
- * Most AMP elements need closing tags. To force them, we cannot use
107
- * saveHTML (node support is 5.3+) and LIBXML_NOEMPTYTAG results in
108
- * issues with self-closing tags like `br` and `hr`. So, we're manually
109
- * forcing closing tags.
110
- */
111
- self::recursive_force_closing_tags( $dom, $node );
112
-
113
  /*
114
  * Cache this regex so we don't have to recreate it every call.
115
  */
116
  if ( ! isset( $self_closing_tags_regex ) ) {
117
- $self_closing_tags = implode( '|', self::get_self_closing_tags() );
118
- $self_closing_tags_regex = "#></({$self_closing_tags})>#i";
119
  }
120
 
121
- $html = $dom->saveXML( $node );
122
 
123
  // Whitespace just causes unit tests to fail... so whitespace begone.
124
  if ( '' === trim( $html ) ) {
125
  return '';
126
  }
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  /*
129
  * Travis w/PHP 7.1 generates <br></br> and <hr></hr> vs. <br/> and <hr/>, respectively.
130
  * Travis w/PHP 7.x generates <source ...></source> vs. <source ... />. Etc.
131
  * Seems like LIBXML_NOEMPTYTAG was passed, but as you can see it was not.
132
  * This does not happen in my (@mikeschinkel) local testing, btw.
133
  */
134
- $html = preg_replace( $self_closing_tags_regex, '/>', $html );
135
 
136
  return $html;
137
-
138
  }
139
 
140
  /**
@@ -210,12 +500,14 @@ class AMP_DOM_Utils {
210
  * Forces HTML element closing tags given a DOMDocument and optional DOMElement
211
  *
212
  * @since 0.2
 
213
  *
214
  * @param DOMDocument $dom Represents HTML document on which to force closing tags.
215
  * @param DOMElement $node Represents HTML element to start closing tags on.
216
  * If not passed, defaults to first child of body.
217
  */
218
  public static function recursive_force_closing_tags( $dom, $node = null ) {
 
219
 
220
  if ( is_null( $node ) ) {
221
  $node = $dom->getElementsByTagName( 'body' )->item( 0 );
@@ -257,48 +549,6 @@ class AMP_DOM_Utils {
257
  * @return bool Returns true if a valid self-closing tag, false if not.
258
  */
259
  private static function is_self_closing_tag( $tag ) {
260
- return in_array( $tag, self::get_self_closing_tags(), true );
261
- }
262
-
263
- /**
264
- * Returns array of self closing tags
265
- *
266
- * @since 0.6
267
- *
268
- * @return string[]
269
- */
270
- private static function get_self_closing_tags() {
271
- /*
272
- * As this function is called a lot the static var
273
- * prevents having to re-create the array every time.
274
- */
275
- static $self_closing_tags;
276
- if ( ! isset( $self_closing_tags ) ) {
277
- /*
278
- * https://www.w3.org/TR/html5/syntax.html#serializing-html-fragments
279
- * Not all are valid AMP, but we include them for completeness.
280
- */
281
- $self_closing_tags = array(
282
- 'area',
283
- 'base',
284
- 'basefont',
285
- 'bgsound',
286
- 'br',
287
- 'col',
288
- 'embed',
289
- 'frame',
290
- 'hr',
291
- 'img',
292
- 'input',
293
- 'keygen',
294
- 'link',
295
- 'meta',
296
- 'param',
297
- 'source',
298
- 'track',
299
- 'wbr',
300
- );
301
- }
302
- return $self_closing_tags;
303
  }
304
  }
13
  class AMP_DOM_Utils {
14
 
15
  /**
16
+ * HTML elements that are self-closing.
17
  *
18
+ * Not all are valid AMP, but we include them for completeness.
19
  *
20
+ * @since 0.7
21
+ * @link https://www.w3.org/TR/html5/syntax.html#serializing-html-fragments
22
+ * @var array
23
+ */
24
+ private static $self_closing_tags = array(
25
+ 'area',
26
+ 'base',
27
+ 'basefont',
28
+ 'bgsound',
29
+ 'br',
30
+ 'col',
31
+ 'embed',
32
+ 'frame',
33
+ 'hr',
34
+ 'img',
35
+ 'input',
36
+ 'keygen',
37
+ 'link',
38
+ 'meta',
39
+ 'param',
40
+ 'source',
41
+ 'track',
42
+ 'wbr',
43
+ );
44
+
45
+ /**
46
+ * Stored noscript/comment replacements for libxml<2.8.
47
  *
48
+ * @since 0.7
49
+ * @var array
50
+ */
51
+ public static $noscript_placeholder_comments = array();
52
+
53
+ /**
54
+ * Return a valid DOMDocument representing HTML document passed as a parameter.
55
  *
56
+ * @since 0.7
57
+ * @see AMP_DOM_Utils::get_content_from_dom_node()
58
+ *
59
+ * @param string $document Valid HTML document to be represented by a DOMDocument.
60
  * @return DOMDocument|false Returns DOMDocument, or false if conversion failed.
61
  */
62
+ public static function get_dom( $document ) {
63
  $libxml_previous_state = libxml_use_internal_errors( true );
64
 
65
  $dom = new DOMDocument();
66
 
67
+ // @todo In the future consider an AMP_DOMDocument subclass that does this automatically. See <https://github.com/Automattic/amp-wp/pull/895/files#r163825513>.
68
+ $document = self::convert_amp_bind_attributes( $document );
69
+
70
+ /*
71
+ * Prevent amp-mustache syntax from getting URL-encoded in attributes when saveHTML is done.
72
+ * While this is applying to the entire document, it only really matters inside of <template>
73
+ * elements, since URL-encoding of curly braces in href attributes would not normally matter.
74
+ * But when this is done inside of a <template> then it breaks Mustache. Since Mustache
75
+ * is logic-less and curly braces are not unsafe for HTML, we can do a global replacement.
76
+ * The replacement is done on the entire HTML document instead of just inside of the <template>
77
+ * elements since it is faster and wouldn't change the outcome.
78
+ */
79
+ $placeholders = self::get_mustache_tag_placeholders();
80
+ $document = str_replace(
81
+ array_keys( $placeholders ),
82
+ array_values( $placeholders ),
83
+ $document
84
+ );
85
+
86
+ // Force all self-closing tags to have closing tags since DOMDocument isn't fully aware.
87
+ $document = preg_replace(
88
+ '#<(' . implode( '|', self::$self_closing_tags ) . ')[^>]*>(?!</\1>)#',
89
+ '$0</$1>',
90
+ $document
91
+ );
92
+
93
+ // Deal with bugs in older versions of libxml.
94
+ $added_back_compat_meta_content_type = false;
95
+ if ( version_compare( LIBXML_DOTTED_VERSION, '2.8', '<' ) ) {
96
+ /*
97
+ * Replace noscript elements with placeholders since libxml<2.8 can parse them incorrectly.
98
+ * When appearing in the head element, a noscript can cause the head to close prematurely
99
+ * and the noscript gets moved to the body and anything after it which was in the head.
100
+ * See <https://stackoverflow.com/questions/39013102/why-does-noscript-move-into-body-tag-instead-of-head-tag>.
101
+ */
102
+ $document = preg_replace_callback(
103
+ '#<noscript[^>]*>.*?</noscript>#si',
104
+ function( $matches ) {
105
+ $placeholder = sprintf( '<!--noscript:%s-->', (string) wp_rand() );
106
+ AMP_DOM_Utils::$noscript_placeholder_comments[ $placeholder ] = $matches[0];
107
+ return $placeholder;
108
+ },
109
+ $document
110
+ );
111
+
112
+ /*
113
+ * Add a pre-HTML5-style declaration of the encoding since libxml<2.8 doesn't recognize
114
+ * HTML5's meta charset. See <https://bugzilla.gnome.org/show_bug.cgi?id=655218>.
115
+ */
116
+ $document = preg_replace(
117
+ '#(?=<meta\s+charset=["\']?([a-z0-9_-]+))#i',
118
+ '<meta http-equiv="Content-Type" content="text/html; charset=$1" id="meta-http-equiv-content-type">',
119
+ $document,
120
+ 1,
121
+ $count
122
+ );
123
+ if ( 1 === $count ) {
124
+ $added_back_compat_meta_content_type = true;
125
+ }
126
+ }
127
+
128
  /*
129
  * Wrap in dummy tags, since XML needs one parent node.
130
  * It also makes it easier to loop through nodes.
131
  * We can later use this to extract our nodes.
132
+ * Add charset so loadHTML does not have problems parsing it.
 
133
  */
134
+ $result = $dom->loadHTML( $document );
135
 
136
  libxml_clear_errors();
137
  libxml_use_internal_errors( $libxml_previous_state );
140
  return false;
141
  }
142
 
143
+ // Remove pre-HTML5-style encoding declaration if added above.
144
+ if ( $added_back_compat_meta_content_type ) {
145
+ $meta_http_equiv_element = $dom->getElementById( 'meta-http-equiv-content-type' );
146
+ if ( $meta_http_equiv_element ) {
147
+ $meta_http_equiv_element->parentNode->removeChild( $meta_http_equiv_element );
148
+ }
149
+ }
150
+
151
  return $dom;
152
  }
153
 
154
  /**
155
+ * Get attribute prefix for converted amp-bind attributes.
156
+ *
157
+ * This contains a random string to prevent HTML content containing this data- attribute
158
+ * originally from being mutated to contain an amp-bind attribute when attributes are restored.
159
+ *
160
+ * @since 0.7
161
+ * @see \AMP_DOM_Utils::convert_amp_bind_attributes()
162
+ * @see \AMP_DOM_Utils::restore_amp_bind_attributes()
163
+ * @link https://www.ampproject.org/docs/reference/components/amp-bind
164
+ *
165
+ * @return string HTML5 data-* attribute name prefix for AMP binding attributes.
166
+ */
167
+ public static function get_amp_bind_placeholder_prefix() {
168
+ static $attribute_prefix;
169
+ if ( ! isset( $attribute_prefix ) ) {
170
+ $attribute_prefix = sprintf( 'amp-binding-%s-', md5( wp_rand() ) );
171
+ }
172
+ return $attribute_prefix;
173
+ }
174
+
175
+ /**
176
+ * Get amp-mustache tag/placeholder mappings.
177
+ *
178
+ * @since 0.7
179
+ * @see \wpdb::placeholder_escape()
180
+ *
181
+ * @return array Mapping of mustache tag token to its placeholder.
182
+ */
183
+ private static function get_mustache_tag_placeholders() {
184
+ static $placeholders;
185
+ if ( ! isset( $placeholders ) ) {
186
+ $salt = wp_rand();
187
+
188
+ // Note: The order of these tokens is important, as it determines the order of the order of the replacements.
189
+ $tokens = array(
190
+ '{{{',
191
+ '}}}',
192
+ '{{#',
193
+ '{{^',
194
+ '{{/',
195
+ '{{/',
196
+ '{{',
197
+ '}}',
198
+ );
199
+ $placeholders = array();
200
+ foreach ( $tokens as $token ) {
201
+ $placeholders[ $token ] = '_amp_mustache_' . md5( $salt . $token );
202
+ }
203
+ }
204
+ return $placeholders;
205
+ }
206
+
207
+ /**
208
+ * Replace AMP binding attributes with something that libxml can parse (as HTML5 data-* attributes).
209
+ *
210
+ * This is necessary because attributes in square brackets are not understood in PHP and
211
+ * get dropped with an error raised:
212
+ * > Warning: DOMDocument::loadHTML(): error parsing attribute name
213
+ * This is a reciprocal function of AMP_DOM_Utils::restore_amp_bind_attributes().
214
+ *
215
+ * @since 0.7
216
+ * @see \AMP_DOM_Utils::convert_amp_bind_attributes()
217
+ * @link https://www.ampproject.org/docs/reference/components/amp-bind
218
+ *
219
+ * @param string $html HTML containing amp-bind attributes.
220
+ * @return string HTML with AMP binding attributes replaced with HTML5 data-* attributes.
221
+ */
222
+ public static function convert_amp_bind_attributes( $html ) {
223
+ $amp_bind_attr_prefix = self::get_amp_bind_placeholder_prefix();
224
+
225
+ // Pattern for HTML attribute accounting for binding attr name, boolean attribute, single/double-quoted attribute value, and unquoted attribute values.
226
+ $attr_regex = '#^\s+(?P<name>\[?[a-zA-Z0-9_\-]+\]?)(?P<value>=(?:"[^"]*"|\'[^\']*\'|[^\'"\s]+))?#';
227
+
228
+ /**
229
+ * Replace callback.
230
+ *
231
+ * @param array $tag_matches Tag matches.
232
+ * @return string Replacement.
233
+ */
234
+ $replace_callback = function( $tag_matches ) use ( $amp_bind_attr_prefix, $attr_regex ) {
235
+ $old_attrs = rtrim( $tag_matches['attrs'] );
236
+ $new_attrs = '';
237
+ $offset = 0;
238
+ while ( preg_match( $attr_regex, substr( $old_attrs, $offset ), $attr_matches ) ) {
239
+ $offset += strlen( $attr_matches[0] );
240
+
241
+ if ( '[' === $attr_matches['name'][0] ) {
242
+ $new_attrs .= ' ' . $amp_bind_attr_prefix . trim( $attr_matches['name'], '[]' );
243
+ if ( isset( $attr_matches['value'] ) ) {
244
+ $new_attrs .= $attr_matches['value'];
245
+ }
246
+ } else {
247
+ $new_attrs .= $attr_matches[0];
248
+ }
249
+ }
250
+
251
+ // Bail on parse error which occurs when the regex isn't able to consume the entire $new_attrs string.
252
+ if ( strlen( $old_attrs ) !== $offset ) {
253
+ return $tag_matches[0];
254
+ }
255
+
256
+ return '<' . $tag_matches['name'] . $new_attrs . '>';
257
+ };
258
+
259
+ $converted = preg_replace_callback(
260
+ // Match all start tags that probably contain a binding attribute.
261
+ '#<(?P<name>[a-zA-Z0-9_\-]+)(?P<attrs>\s[^>]+\]=[^>]+)>#',
262
+ $replace_callback,
263
+ $html
264
+ );
265
+
266
+ /**
267
+ * If the regex engine incurred an error during processing, for example exceeding the backtrack
268
+ * limit, $converted will be null. In this case we return the originally passed document to allow
269
+ * DOMDocument to attempt to load it. If the AMP HTML doesn't make use of amp-bind or similar
270
+ * attributes, then everything should still work.
271
+ *
272
+ * See https://github.com/Automattic/amp-wp/issues/993 for additional context on this issue.
273
+ * See http://php.net/manual/en/pcre.constants.php for additional info on PCRE errors.
274
+ */
275
+ return ( ! is_null( $converted ) ) ? $converted : $html;
276
+ }
277
+
278
+ /**
279
+ * Convert AMP bind-attributes back to their original syntax.
280
+ *
281
+ * This is a reciprocal function of AMP_DOM_Utils::convert_amp_bind_attributes().
282
  *
283
+ * @since 0.7
284
+ * @see \AMP_DOM_Utils::convert_amp_bind_attributes()
285
+ * @link https://www.ampproject.org/docs/reference/components/amp-bind
286
+ *
287
+ * @param string $html HTML with amp-bind attributes converted.
288
+ * @return string HTML with amp-bind attributes restored.
289
+ */
290
+ public static function restore_amp_bind_attributes( $html ) {
291
+ $html = preg_replace(
292
+ '#\s' . self::get_amp_bind_placeholder_prefix() . '([a-zA-Z0-9_\-]+)#',
293
+ ' [$1]',
294
+ $html
295
+ );
296
+ return $html;
297
+ }
298
+
299
+ /**
300
+ * Return a valid DOMDocument representing arbitrary HTML content passed as a parameter.
301
+ *
302
+ * @see Reciprocal function get_content_from_dom()
303
  *
304
  * @since 0.2
305
  *
306
+ * @param string $content Valid HTML content to be represented by a DOMDocument.
307
+ *
308
+ * @return DOMDocument|false Returns DOMDocument, or false if conversion failed.
309
+ */
310
+ public static function get_dom_from_content( $content ) {
311
+ /*
312
+ * Wrap in dummy tags, since XML needs one parent node.
313
+ * It also makes it easier to loop through nodes.
314
+ * We can later use this to extract our nodes.
315
+ * Add utf-8 charset so loadHTML does not have problems parsing it.
316
+ * See: http://php.net/manual/en/domdocument.loadhtml.php#78243
317
+ */
318
+ $document = sprintf(
319
+ '<html><head><meta http-equiv="content-type" content="text/html; charset=%s"></head><body>%s</body></html>',
320
+ get_bloginfo( 'charset' ),
321
+ $content
322
+ );
323
+
324
+ return self::get_dom( $document );
325
+
326
+ }
327
+
328
+ /**
329
+ * Return valid HTML *body* content extracted from the DOMDocument passed as a parameter.
330
+ *
331
+ * @since 0.2
332
+ * @see AMP_DOM_Utils::get_content_from_dom_node() Reciprocal function.
333
+ *
334
  * @param DOMDocument $dom Represents an HTML document from which to extract HTML content.
335
  *
336
+ * @return string Returns the HTML content of the body element represented in the DOMDocument.
337
  */
338
  public static function get_content_from_dom( $dom ) {
339
 
340
  /**
341
  * We only want children of the body tag, since we have a subset of HTML.
342
+ *
343
+ * @todo We will want to get the full HTML eventually.
344
  */
345
  $body = $dom->getElementsByTagName( 'body' )->item( 0 );
346
 
364
  /**
365
  * Return valid HTML content extracted from the DOMNode passed as a parameter.
366
  *
 
 
367
  * @since 0.6
368
+ * @see AMP_DOM_Utils::get_dom() Where the operations in this method are mirrored.
369
+ * @see AMP_DOM_Utils::get_content_from_dom() Reciprocal function.
370
+ * @todo In the future consider an AMP_DOMDocument subclass that does this automatically at saveHTML(). See <https://github.com/Automattic/amp-wp/pull/895/files#r163825513>.
371
  *
372
  * @param DOMDocument $dom Represents an HTML document.
373
+ * @param DOMElement $node Represents an HTML element of the $dom from which to extract HTML content.
374
  * @return string Returns the HTML content represented in the DOMNode
375
  */
376
  public static function get_content_from_dom_node( $dom, $node ) {
382
  */
383
  static $self_closing_tags_regex;
384
 
 
 
 
 
 
 
 
 
385
  /*
386
  * Cache this regex so we don't have to recreate it every call.
387
  */
388
  if ( ! isset( $self_closing_tags_regex ) ) {
389
+ $self_closing_tags = implode( '|', self::$self_closing_tags );
390
+ $self_closing_tags_regex = "#</({$self_closing_tags})>#i";
391
  }
392
 
393
+ $html = $dom->saveHTML( $node );
394
 
395
  // Whitespace just causes unit tests to fail... so whitespace begone.
396
  if ( '' === trim( $html ) ) {
397
  return '';
398
  }
399
 
400
+ // Restore noscript elements which were temporarily removed to prevent libxml<2.8 parsing problems.
401
+ if ( version_compare( LIBXML_DOTTED_VERSION, '2.8', '<' ) ) {
402
+ $html = str_replace(
403
+ array_keys( self::$noscript_placeholder_comments ),
404
+ array_values( self::$noscript_placeholder_comments ),
405
+ $html
406
+ );
407
+ }
408
+
409
+ $html = self::restore_amp_bind_attributes( $html );
410
+
411
+ // Restore amp-mustache placeholders which were replaced to prevent URL-encoded corruption by saveHTML.
412
+ $placeholders = self::get_mustache_tag_placeholders();
413
+ $html = str_replace(
414
+ array_values( $placeholders ),
415
+ array_keys( $placeholders ),
416
+ $html
417
+ );
418
+
419
  /*
420
  * Travis w/PHP 7.1 generates <br></br> and <hr></hr> vs. <br/> and <hr/>, respectively.
421
  * Travis w/PHP 7.x generates <source ...></source> vs. <source ... />. Etc.
422
  * Seems like LIBXML_NOEMPTYTAG was passed, but as you can see it was not.
423
  * This does not happen in my (@mikeschinkel) local testing, btw.
424
  */
425
+ $html = preg_replace( $self_closing_tags_regex, '', $html );
426
 
427
  return $html;
 
428
  }
429
 
430
  /**
500
  * Forces HTML element closing tags given a DOMDocument and optional DOMElement
501
  *
502
  * @since 0.2
503
+ * @deprecated
504
  *
505
  * @param DOMDocument $dom Represents HTML document on which to force closing tags.
506
  * @param DOMElement $node Represents HTML element to start closing tags on.
507
  * If not passed, defaults to first child of body.
508
  */
509
  public static function recursive_force_closing_tags( $dom, $node = null ) {
510
+ _deprecated_function( __METHOD__, '0.7' );
511
 
512
  if ( is_null( $node ) ) {
513
  $node = $dom->getElementsByTagName( 'body' )->item( 0 );
549
  * @return bool Returns true if a valid self-closing tag, false if not.
550
  */
551
  private static function is_self_closing_tag( $tag ) {
552
+ return in_array( strtolower( $tag ), self::$self_closing_tags, true );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  }
554
  }
includes/utils/class-amp-validation-utils.php ADDED
@@ -0,0 +1,1848 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Validation_Utils
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Validation_Utils
10
+ *
11
+ * @since 0.7
12
+ */
13
+ class AMP_Validation_Utils {
14
+
15
+ /**
16
+ * Query var that triggers validation.
17
+ *
18
+ * @var string
19
+ */
20
+ const VALIDATE_QUERY_VAR = 'amp_validate';
21
+
22
+ /**
23
+ * Query var that enables validation debug mode, to disable removal of invalid elements/attributes.
24
+ *
25
+ * @var string
26
+ */
27
+ const DEBUG_QUERY_VAR = 'amp_debug';
28
+
29
+ /**
30
+ * Query var for cache-busting.
31
+ *
32
+ * @var string
33
+ */
34
+ const CACHE_BUST_QUERY_VAR = 'amp_cache_bust';
35
+
36
+ /**
37
+ * The slug of the post type to store AMP errors.
38
+ *
39
+ * @var string
40
+ */
41
+ const POST_TYPE_SLUG = 'amp_validation_error';
42
+
43
+ /**
44
+ * The key in the response for the sources that have invalid output.
45
+ *
46
+ * @var string
47
+ */
48
+ const SOURCES_INVALID_OUTPUT = 'sources_with_invalid_output';
49
+
50
+ /**
51
+ * Validation code for an invalid element.
52
+ *
53
+ * @var string
54
+ */
55
+ const INVALID_ELEMENT_CODE = 'invalid_element';
56
+
57
+ /**
58
+ * Validation code for an invalid attribute.
59
+ *
60
+ * @var string
61
+ */
62
+ const INVALID_ATTRIBUTE_CODE = 'invalid_attribute';
63
+
64
+ /**
65
+ * Validation code for when script is enqueued (which is not allowed).
66
+ *
67
+ * @var string
68
+ */
69
+ const ENQUEUED_SCRIPT_CODE = 'enqueued_script';
70
+
71
+ /**
72
+ * The meta key for the AMP URL where the error occurred.
73
+ *
74
+ * @var string
75
+ */
76
+ const AMP_URL_META = 'amp_url';
77
+
78
+ /**
79
+ * The key for removed elements.
80
+ *
81
+ * @var string
82
+ */
83
+ const REMOVED_ELEMENTS = 'removed_elements';
84
+
85
+ /**
86
+ * The key for removed attributes.
87
+ *
88
+ * @var string
89
+ */
90
+ const REMOVED_ATTRIBUTES = 'removed_attributes';
91
+
92
+ /**
93
+ * The key for removed sources.
94
+ *
95
+ * @var string
96
+ */
97
+ const REMOVED_SOURCES = 'removed_sources';
98
+
99
+ /**
100
+ * The action to recheck URLs for AMP validity.
101
+ *
102
+ * @var string
103
+ */
104
+ const RECHECK_ACTION = 'amp_recheck';
105
+
106
+ /**
107
+ * The query arg for whether there are remaining errors after rechecking URLs.
108
+ *
109
+ * @var string
110
+ */
111
+ const REMAINING_ERRORS = 'amp_remaining_errors';
112
+
113
+ /**
114
+ * The query arg for the number of URLs tested.
115
+ *
116
+ * @var string
117
+ */
118
+ const URLS_TESTED = 'amp_urls_tested';
119
+
120
+ /**
121
+ * The nonce action for rechecking a URL.
122
+ *
123
+ * @var string
124
+ */
125
+ const NONCE_ACTION = 'amp_recheck_';
126
+
127
+ /**
128
+ * Transient key to store validation errors when activating a plugin.
129
+ *
130
+ * @var string
131
+ */
132
+ const PLUGIN_ACTIVATION_VALIDATION_ERRORS_TRANSIENT_KEY = 'amp_plugin_activation_validation_errors';
133
+
134
+ /**
135
+ * The name of the side meta box on the CPT post.php page.
136
+ *
137
+ * @var string
138
+ */
139
+ const STATUS_META_BOX = 'amp_validation_status';
140
+
141
+ /**
142
+ * The name of the side meta box on the CPT post.php page.
143
+ *
144
+ * @var string
145
+ */
146
+ const VALIDATION_ERRORS_META_BOX = 'amp_validation_errors';
147
+
148
+ /**
149
+ * The errors encountered when validating.
150
+ *
151
+ * @var array[][] {
152
+ * @type string $code Error code.
153
+ * @type string $node_name Name of removed node.
154
+ * @type string $parent_name Name of parent node.
155
+ * }
156
+ */
157
+ public static $validation_errors = array();
158
+
159
+ /**
160
+ * Sources that enqueue each script.
161
+ *
162
+ * @var array
163
+ */
164
+ public static $enqueued_script_sources = array();
165
+
166
+ /**
167
+ * Sources that enqueue each style.
168
+ *
169
+ * @var array
170
+ */
171
+ public static $enqueued_style_sources = array();
172
+
173
+ /**
174
+ * Post IDs for posts that have been updated which need to be re-validated.
175
+ *
176
+ * @var int[]
177
+ */
178
+ public static $posts_pending_frontend_validation = array();
179
+
180
+ /**
181
+ * Current sources gathered for a given hook currently being run.
182
+ *
183
+ * @see AMP_Validation_Utils::wrap_hook_callbacks()
184
+ * @see AMP_Validation_Utils::decorate_filter_source()
185
+ * @var array[]
186
+ */
187
+ protected static $current_hook_source_stack = array();
188
+
189
+ /**
190
+ * Hook source stack.
191
+ *
192
+ * This has to be public for the sake of PHP 5.3.
193
+ *
194
+ * @since 0.7
195
+ * @var array[]
196
+ */
197
+ public static $hook_source_stack = array();
198
+
199
+ /**
200
+ * Add the actions.
201
+ *
202
+ * @return void
203
+ */
204
+ public static function init() {
205
+ if ( current_theme_supports( 'amp' ) ) {
206
+ add_action( 'init', array( __CLASS__, 'register_post_type' ) );
207
+ add_filter( 'dashboard_glance_items', array( __CLASS__, 'filter_dashboard_glance_items' ) );
208
+ add_action( 'rightnow_end', array( __CLASS__, 'print_dashboard_glance_styles' ) );
209
+ add_action( 'save_post', array( __CLASS__, 'handle_save_post_prompting_validation' ), 10, 2 );
210
+ }
211
+
212
+ add_action( 'edit_form_top', array( __CLASS__, 'print_edit_form_validation_status' ), 10, 2 );
213
+ add_action( 'all_admin_notices', array( __CLASS__, 'plugin_notice' ) );
214
+ add_filter( 'manage_' . self::POST_TYPE_SLUG . '_posts_columns', array( __CLASS__, 'add_post_columns' ) );
215
+ add_action( 'manage_posts_custom_column', array( __CLASS__, 'output_custom_column' ), 10, 2 );
216
+ add_filter( 'post_row_actions', array( __CLASS__, 'filter_row_actions' ), 10, 2 );
217
+ add_filter( 'bulk_actions-edit-' . self::POST_TYPE_SLUG, array( __CLASS__, 'add_bulk_action' ), 10, 2 );
218
+ add_filter( 'handle_bulk_actions-edit-' . self::POST_TYPE_SLUG, array( __CLASS__, 'handle_bulk_action' ), 10, 3 );
219
+ add_action( 'admin_notices', array( __CLASS__, 'remaining_error_notice' ) );
220
+ add_action( 'post_action_' . self::RECHECK_ACTION, array( __CLASS__, 'handle_inline_recheck' ) );
221
+ add_action( 'admin_menu', array( __CLASS__, 'remove_publish_meta_box' ) );
222
+ add_action( 'admin_menu', array( __CLASS__, 'add_admin_menu_validation_status_count' ) );
223
+ add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
224
+
225
+ // Actions and filters involved in validation.
226
+ add_action( 'activate_plugin', function() {
227
+ if ( ! has_action( 'shutdown', array( __CLASS__, 'validate_after_plugin_activation' ) ) ) {
228
+ add_action( 'shutdown', array( __CLASS__, 'validate_after_plugin_activation' ) ); // Shutdown so all plugins will have been activated.
229
+ }
230
+ } );
231
+ }
232
+
233
+ /**
234
+ * Add count of how many validation error posts there are to the admin menu.
235
+ */
236
+ public static function add_admin_menu_validation_status_count() {
237
+ global $submenu;
238
+ if ( ! isset( $submenu[ AMP_Options_Manager::OPTION_NAME ] ) ) {
239
+ return;
240
+ }
241
+ $count = wp_count_posts( self::POST_TYPE_SLUG );
242
+ if ( empty( $count->publish ) ) {
243
+ return;
244
+ }
245
+ foreach ( $submenu[ AMP_Options_Manager::OPTION_NAME ] as &$submenu_item ) {
246
+ if ( 'edit.php?post_type=' . self::POST_TYPE_SLUG === $submenu_item[2] ) {
247
+ $submenu_item[0] .= ' <span class="awaiting-mod"><span class="pending-count">' . esc_html( $count->publish ) . '</span></span>';
248
+ break;
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Filter At a Glance items add AMP Validation Errors.
255
+ *
256
+ * @param array $items At a glance items.
257
+ * @return array Items.
258
+ */
259
+ public static function filter_dashboard_glance_items( $items ) {
260
+ $counts = wp_count_posts( self::POST_TYPE_SLUG );
261
+ if ( ! empty( $counts->publish ) ) {
262
+ $items[] = sprintf(
263
+ '<a class="amp-validation-errors" href="%s">%s</a>',
264
+ esc_url( admin_url( 'edit.php?post_type=' . self::POST_TYPE_SLUG ) ),
265
+ esc_html( sprintf(
266
+ /* translators: %s is the validation error count */
267
+ _n( '%s AMP Validation Error', '%s AMP Validation Errors', $counts->publish, 'amp' ),
268
+ $counts->publish
269
+ ) )
270
+ );
271
+ }
272
+ return $items;
273
+ }
274
+
275
+ /**
276
+ * Print styles for the At a Glance widget.
277
+ */
278
+ public static function print_dashboard_glance_styles() {
279
+ ?>
280
+ <style>
281
+ #dashboard_right_now .amp-validation-errors {
282
+ color: #a00;
283
+ }
284
+ #dashboard_right_now .amp-validation-errors:before {
285
+ content: "\f534";
286
+ }
287
+ #dashboard_right_now .amp-validation-errors:hover {
288
+ color: #dc3232;
289
+ border: none;
290
+ }
291
+ </style>
292
+ <?php
293
+ }
294
+
295
+ /**
296
+ * Add hooks for doing validation during preprocessing/sanitizing.
297
+ */
298
+ public static function add_validation_hooks() {
299
+ self::wrap_widget_callbacks();
300
+
301
+ add_action( 'all', array( __CLASS__, 'wrap_hook_callbacks' ) );
302
+ $wrapped_filters = array( 'the_content', 'the_excerpt' );
303
+ foreach ( $wrapped_filters as $wrapped_filter ) {
304
+ add_filter( $wrapped_filter, array( __CLASS__, 'decorate_filter_source' ), PHP_INT_MAX );
305
+ }
306
+
307
+ add_filter( 'do_shortcode_tag', array( __CLASS__, 'decorate_shortcode_source' ), -1, 2 );
308
+ add_filter( 'amp_content_sanitizers', array( __CLASS__, 'add_validation_callback' ) );
309
+ }
310
+
311
+ /**
312
+ * Handle save_post action to queue re-validation of the post on the frontend.
313
+ *
314
+ * @see AMP_Validation_Utils::validate_queued_posts_on_frontend()
315
+ * @param int $post_id Post ID.
316
+ * @param WP_Post $post Post.
317
+ */
318
+ public static function handle_save_post_prompting_validation( $post_id, $post ) {
319
+ $should_validate_post = (
320
+ is_post_type_viewable( $post->post_type )
321
+ &&
322
+ ! wp_is_post_autosave( $post )
323
+ &&
324
+ ! wp_is_post_revision( $post )
325
+ );
326
+ if ( $should_validate_post ) {
327
+ self::$posts_pending_frontend_validation[] = $post_id;
328
+
329
+ // The reason for shutdown is to ensure that all postmeta changes have been saved, including whether AMP is enabled.
330
+ if ( ! has_action( 'shutdown', array( __CLASS__, 'validate_queued_posts_on_frontend' ) ) ) {
331
+ add_action( 'shutdown', array( __CLASS__, 'validate_queued_posts_on_frontend' ) );
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Validate the posts pending frontend validation.
338
+ *
339
+ * @see AMP_Validation_Utils::handle_save_post_prompting_validation()
340
+ */
341
+ public static function validate_queued_posts_on_frontend() {
342
+ $posts = array_filter(
343
+ array_map( 'get_post', self::$posts_pending_frontend_validation ),
344
+ function( $post ) {
345
+ return $post && post_supports_amp( $post ) && 'trash' !== $post->post_status;
346
+ }
347
+ );
348
+
349
+ // @todo Only validate the first and then queue the rest in WP Cron?
350
+ foreach ( $posts as $post ) {
351
+ $url = amp_get_permalink( $post->ID );
352
+ if ( ! $url ) {
353
+ continue;
354
+ }
355
+
356
+ $validation_errors = self::validate_url( $url );
357
+ if ( is_wp_error( $validation_errors ) ) {
358
+ continue;
359
+ }
360
+
361
+ self::store_validation_errors( $validation_errors, $url );
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Processes markup, to determine AMP validity.
367
+ *
368
+ * Passes $markup through the AMP sanitizers.
369
+ * Also passes a 'validation_error_callback' to keep track of stripped attributes and nodes.
370
+ *
371
+ * @param string $markup The markup to process.
372
+ * @return string Sanitized markup.
373
+ */
374
+ public static function process_markup( $markup ) {
375
+ AMP_Theme_Support::register_content_embed_handlers();
376
+
377
+ /** This filter is documented in wp-includes/post-template.php */
378
+ $markup = apply_filters( 'the_content', $markup );
379
+ $args = array(
380
+ 'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH,
381
+ 'validation_error_callback' => 'AMP_Validation_Utils::add_validation_error',
382
+ );
383
+
384
+ $results = AMP_Content_Sanitizer::sanitize( $markup, amp_get_content_sanitizers(), $args );
385
+ return $results[0];
386
+ }
387
+
388
+ /**
389
+ * Whether the user has the required capability.
390
+ *
391
+ * Checks for permissions before validating.
392
+ *
393
+ * @return boolean $has_cap Whether the current user has the capability.
394
+ */
395
+ public static function has_cap() {
396
+ return current_user_can( 'edit_posts' );
397
+ }
398
+
399
+ /**
400
+ * Add validation error.
401
+ *
402
+ * @param array $data {
403
+ * Data.
404
+ *
405
+ * @type string $code Error code.
406
+ * @type DOMElement|DOMNode $node The removed node.
407
+ * }
408
+ */
409
+ public static function add_validation_error( array $data ) {
410
+ $node = null;
411
+
412
+ if ( isset( $data['node'] ) && $data['node'] instanceof DOMNode ) {
413
+ $node = $data['node'];
414
+ unset( $data['node'] );
415
+ $data['node_name'] = $node->nodeName;
416
+ $data['sources'] = self::locate_sources( $node );
417
+ if ( $node->parentNode ) {
418
+ $data['parent_name'] = $node->parentNode->nodeName;
419
+ }
420
+ }
421
+
422
+ if ( $node instanceof DOMElement ) {
423
+ if ( ! isset( $data['code'] ) ) {
424
+ $data['code'] = self::INVALID_ELEMENT_CODE;
425
+ }
426
+ $data['node_attributes'] = array();
427
+ foreach ( $node->attributes as $attribute ) {
428
+ $data['node_attributes'][ $attribute->nodeName ] = $attribute->nodeValue;
429
+ }
430
+
431
+ $is_enqueued_link = (
432
+ 'link' === $node->nodeName
433
+ &&
434
+ preg_match( '/(?P<handle>.+)-css$/', (string) $node->getAttribute( 'id' ), $matches )
435
+ &&
436
+ isset( self::$enqueued_style_sources[ $matches['handle'] ] )
437
+ );
438
+ if ( $is_enqueued_link ) {
439
+ $data['sources'] = self::$enqueued_style_sources[ $matches['handle'] ];
440
+ }
441
+ } elseif ( $node instanceof DOMAttr ) {
442
+ if ( ! isset( $data['code'] ) ) {
443
+ $data['code'] = self::INVALID_ATTRIBUTE_CODE;
444
+ }
445
+ $data['element_attributes'] = array();
446
+ if ( $node->parentNode && $node->parentNode->hasAttributes() ) {
447
+ foreach ( $node->parentNode->attributes as $attribute ) {
448
+ $data['element_attributes'][ $attribute->nodeName ] = $attribute->nodeValue;
449
+ }
450
+ }
451
+ }
452
+
453
+ if ( ! isset( $data['code'] ) ) {
454
+ $data['code'] = 'unknown';
455
+ }
456
+
457
+ self::$validation_errors[] = $data;
458
+ }
459
+
460
+ /**
461
+ * Gets the AMP validation response.
462
+ *
463
+ * Returns the current validation errors the sanitizers found in rendering the page.
464
+ *
465
+ * @param array $validation_errors Validation errors.
466
+ * @return array The AMP validity of the markup.
467
+ */
468
+ public static function summarize_validation_errors( $validation_errors ) {
469
+ $results = array();
470
+ $removed_elements = array();
471
+ $removed_attributes = array();
472
+ $invalid_sources = array();
473
+ foreach ( $validation_errors as $validation_error ) {
474
+ $code = isset( $validation_error['code'] ) ? $validation_error['code'] : null;
475
+
476
+ if ( self::INVALID_ELEMENT_CODE === $code ) {
477
+ if ( ! isset( $removed_elements[ $validation_error['node_name'] ] ) ) {
478
+ $removed_elements[ $validation_error['node_name'] ] = 0;
479
+ }
480
+ $removed_elements[ $validation_error['node_name'] ] += 1;
481
+ } elseif ( self::INVALID_ATTRIBUTE_CODE === $code ) {
482
+ if ( ! isset( $removed_attributes[ $validation_error['node_name'] ] ) ) {
483
+ $removed_attributes[ $validation_error['node_name'] ] = 0;
484
+ }
485
+ $removed_attributes[ $validation_error['node_name'] ] += 1;
486
+ }
487
+
488
+ if ( ! empty( $validation_error['sources'] ) ) {
489
+ $source = array_pop( $validation_error['sources'] );
490
+
491
+ if ( isset( $source['type'], $source['name'] ) ) {
492
+ $invalid_sources[ $source['type'] ][] = $source['name'];
493
+ }
494
+ }
495
+ }
496
+
497
+ $results = array_merge(
498
+ array(
499
+ self::SOURCES_INVALID_OUTPUT => $invalid_sources,
500
+ ),
501
+ compact(
502
+ 'removed_elements',
503
+ 'removed_attributes'
504
+ ),
505
+ $results );
506
+
507
+ return $results;
508
+ }
509
+
510
+ /**
511
+ * Reset the stored removed nodes and attributes.
512
+ *
513
+ * After testing if the markup is valid,
514
+ * these static values will remain.
515
+ * So reset them in case another test is needed.
516
+ *
517
+ * @return void
518
+ */
519
+ public static function reset_validation_results() {
520
+ self::$validation_errors = array();
521
+ self::$enqueued_style_sources = array();
522
+ self::$enqueued_script_sources = array();
523
+ }
524
+
525
+ /**
526
+ * Checks the AMP validity of the post content.
527
+ *
528
+ * If it's not valid AMP, it displays an error message above the 'Classic' editor.
529
+ *
530
+ * @param WP_Post $post The updated post.
531
+ * @return void
532
+ */
533
+ public static function print_edit_form_validation_status( $post ) {
534
+ if ( ! post_supports_amp( $post ) || ! self::has_cap() ) {
535
+ return;
536
+ }
537
+
538
+ $url = null;
539
+ $validation_status_post = null;
540
+ $validation_errors = array();
541
+
542
+ // Incorporate frontend validation status if there is a known URL for the post.
543
+ if ( is_post_type_viewable( $post->post_type ) ) {
544
+ $url = amp_get_permalink( $post->ID );
545
+
546
+ $validation_status_post = self::get_validation_status_post( $url );
547
+ if ( $validation_status_post ) {
548
+ $data = json_decode( $validation_status_post->post_content, true );
549
+ if ( is_array( $data ) ) {
550
+ $validation_errors = array_merge( $validation_errors, $data );
551
+ }
552
+ }
553
+ }
554
+
555
+ // If no results from URL are available, validate post content outside frontend context.
556
+ if ( empty( $validation_errors ) && post_type_supports( $post->post_type, 'editor' ) ) {
557
+ self::process_markup( $post->post_content );
558
+ $validation_errors = array_merge(
559
+ $validation_errors,
560
+ self::$validation_errors
561
+ );
562
+ self::reset_validation_results();
563
+
564
+ // Make sure original post is restored after applying shortcodes which could change it.
565
+ $GLOBALS['post'] = $post; // WPCS: override ok.
566
+ setup_postdata( $post );
567
+ }
568
+
569
+ if ( empty( $validation_errors ) ) {
570
+ return;
571
+ }
572
+
573
+ echo '<div class="notice notice-warning">';
574
+ echo '<p>';
575
+ esc_html_e( 'Warning: There is content which fails AMP validation; it will be stripped when served as AMP.', 'amp' );
576
+ if ( $validation_status_post || $url ) {
577
+ if ( $validation_status_post ) {
578
+ echo sprintf(
579
+ ' <a href="%s" target="_blank">%s</a>',
580
+ esc_url( get_edit_post_link( $validation_status_post ) ),
581
+ esc_html__( 'Details', 'amp' )
582
+ );
583
+ }
584
+ if ( $url ) {
585
+ if ( $validation_status_post ) {
586
+ echo ' | ';
587
+ }
588
+ echo sprintf(
589
+ ' <a href="%s" aria-label="%s" target="_blank">%s</a>',
590
+ esc_url( self::get_debug_url( $url ) ),
591
+ esc_attr__( 'Validate URL on frontend but without invalid elements/attributes removed', 'amp' ),
592
+ esc_html__( 'Debug', 'amp' )
593
+ );
594
+ }
595
+ }
596
+ echo '</p>';
597
+
598
+ $results = self::summarize_validation_errors( array_unique( $validation_errors, SORT_REGULAR ) );
599
+ $removed_sets = array();
600
+ if ( ! empty( $results[ self::REMOVED_ELEMENTS ] ) && is_array( $results[ self::REMOVED_ELEMENTS ] ) ) {
601
+ $removed_sets[] = array(
602
+ 'label' => __( 'Invalid elements:', 'amp' ),
603
+ 'names' => array_map( 'sanitize_key', $results[ self::REMOVED_ELEMENTS ] ),
604
+ );
605
+ }
606
+ if ( ! empty( $results[ self::REMOVED_ATTRIBUTES ] ) && is_array( $results[ self::REMOVED_ATTRIBUTES ] ) ) {
607
+ $removed_sets[] = array(
608
+ 'label' => __( 'Invalid attributes:', 'amp' ),
609
+ 'names' => array_map( 'sanitize_key', $results[ self::REMOVED_ATTRIBUTES ] ),
610
+ );
611
+ }
612
+ // @todo There are other kinds of errors other than REMOVED_ELEMENTS and REMOVED_ATTRIBUTES.
613
+ foreach ( $removed_sets as $removed_set ) {
614
+ printf( '<p>%s ', esc_html( $removed_set['label'] ) );
615
+ self::output_removed_set( $removed_set['names'] );
616
+ echo '</p>';
617
+ }
618
+
619
+ echo '</div>';
620
+ }
621
+
622
+ /**
623
+ * Get source start comment.
624
+ *
625
+ * @param array $source Source data.
626
+ * @param bool $is_start Whether the comment is the start or end.
627
+ * @return string HTML Comment.
628
+ */
629
+ public static function get_source_comment( array $source, $is_start = true ) {
630
+ unset( $source['reflection'] );
631
+ return sprintf(
632
+ '<!--%samp-source-stack %s-->',
633
+ $is_start ? '' : '/',
634
+ str_replace( '--', '', wp_json_encode( $source ) )
635
+ );
636
+ }
637
+
638
+ /**
639
+ * Parse source comment.
640
+ *
641
+ * @param DOMComment $comment Comment.
642
+ * @return array|null Parsed source or null if not a source comment.
643
+ */
644
+ public static function parse_source_comment( DOMComment $comment ) {
645
+ if ( ! preg_match( '#^\s*(?P<closing>/)?amp-source-stack\s+(?P<args>{.+})\s*$#s', $comment->nodeValue, $matches ) ) {
646
+ return null;
647
+ }
648
+
649
+ $source = json_decode( $matches['args'], true );
650
+ $closing = ! empty( $matches['closing'] );
651
+
652
+ return compact( 'source', 'closing' );
653
+ }
654
+
655
+ /**
656
+ * Walk back tree to find the open sources.
657
+ *
658
+ * @param DOMNode $node Node to look for.
659
+ * @return array[][] {
660
+ * The data of the removed sources (theme, plugin, or mu-plugin).
661
+ *
662
+ * @type string $name The name of the source.
663
+ * @type string $type The type of the source.
664
+ * }
665
+ */
666
+ public static function locate_sources( DOMNode $node ) {
667
+ $xpath = new DOMXPath( $node->ownerDocument );
668
+ $comments = $xpath->query( 'preceding::comment()[ starts-with( ., "amp-source-stack" ) or starts-with( ., "/amp-source-stack" ) ]', $node );
669
+ $sources = array();
670
+ foreach ( $comments as $comment ) {
671
+ $parsed_comment = self::parse_source_comment( $comment );
672
+ if ( ! $parsed_comment ) {
673
+ continue;
674
+ }
675
+ if ( $parsed_comment['closing'] ) {
676
+ array_pop( $sources );
677
+ } else {
678
+ $sources[] = $parsed_comment['source'];
679
+ }
680
+ }
681
+ return $sources;
682
+ }
683
+
684
+ /**
685
+ * Remove source comments.
686
+ *
687
+ * @param DOMDocument $dom Document.
688
+ */
689
+ public static function remove_source_comments( $dom ) {
690
+ $xpath = new DOMXPath( $dom );
691
+ $comments = array();
692
+ foreach ( $xpath->query( '//comment()[ starts-with( ., "amp-source-stack" ) or starts-with( ., "/amp-source-stack" ) ]' ) as $comment ) {
693
+ if ( self::parse_source_comment( $comment ) ) {
694
+ $comments[] = $comment;
695
+ }
696
+ }
697
+ foreach ( $comments as $comment ) {
698
+ $comment->parentNode->removeChild( $comment );
699
+ }
700
+ }
701
+
702
+ /**
703
+ * Wrap callbacks for registered widgets to keep track of queued assets and the source for anything printed for validation.
704
+ *
705
+ * @global array $wp_filter
706
+ * @return void
707
+ */
708
+ public static function wrap_widget_callbacks() {
709
+ global $wp_registered_widgets;
710
+ foreach ( $wp_registered_widgets as $widget_id => &$registered_widget ) {
711
+ $source = self::get_source( $registered_widget['callback'] );
712
+ if ( ! $source ) {
713
+ continue;
714
+ }
715
+ $source['widget_id'] = $widget_id;
716
+
717
+ $function = $registered_widget['callback'];
718
+ $accepted_args = 2; // For the $instance and $args arguments.
719
+ $callback = compact( 'function', 'accepted_args', 'source' );
720
+
721
+ $registered_widget['callback'] = self::wrapped_callback( $callback );
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Wrap filter/action callback functions for a given hook.
727
+ *
728
+ * Wrapped callback functions are reset to their original functions after invocation.
729
+ * This runs at the 'all' action. The shutdown hook is excluded.
730
+ *
731
+ * @global WP_Hook[] $wp_filter
732
+ * @param string $hook Hook name for action or filter.
733
+ * @return void
734
+ */
735
+ public static function wrap_hook_callbacks( $hook ) {
736
+ global $wp_filter;
737
+
738
+ if ( ! isset( $wp_filter[ $hook ] ) || 'shutdown' === $hook ) {
739
+ return;
740
+ }
741
+
742
+ self::$current_hook_source_stack[ $hook ] = array();
743
+ foreach ( $wp_filter[ $hook ]->callbacks as $priority => &$callbacks ) {
744
+ foreach ( $callbacks as &$callback ) {
745
+ $source = self::get_source( $callback['function'] );
746
+ if ( ! $source ) {
747
+ continue;
748
+ }
749
+
750
+ $reflection = $source['reflection'];
751
+ unset( $source['reflection'] ); // Omit from stored source.
752
+
753
+ // Add hook to stack for decorate_filter_source to read from.
754
+ self::$current_hook_source_stack[ $hook ][] = $source;
755
+
756
+ /*
757
+ * A current limitation with wrapping callbacks is that the wrapped function cannot have
758
+ * any parameters passed by reference. Without this the result is:
759
+ *
760
+ * > PHP Warning: Parameter 1 to wp_default_styles() expected to be a reference, value given.
761
+ */
762
+ if ( self::has_parameters_passed_by_reference( $reflection ) ) {
763
+ continue;
764
+ }
765
+
766
+ $source['hook'] = $hook;
767
+ $original_function = $callback['function'];
768
+ $wrapped_callback = self::wrapped_callback( array_merge(
769
+ $callback,
770
+ compact( 'priority', 'source', 'hook' )
771
+ ) );
772
+
773
+ $callback['function'] = function() use ( &$callback, $wrapped_callback, $original_function ) {
774
+ $callback['function'] = $original_function; // Restore original.
775
+ return call_user_func_array( $wrapped_callback, func_get_args() );
776
+ };
777
+ }
778
+ }
779
+ }
780
+
781
+ /**
782
+ * Determine whether the given reflection method/function has params passed by reference.
783
+ *
784
+ * @since 0.7
785
+ * @param ReflectionFunction|ReflectionMethod $reflection Reflection.
786
+ * @return bool Whether there are parameters passed by reference.
787
+ */
788
+ protected static function has_parameters_passed_by_reference( $reflection ) {
789
+ foreach ( $reflection->getParameters() as $parameter ) {
790
+ if ( $parameter->isPassedByReference() ) {
791
+ return true;
792
+ }
793
+ }
794
+ return false;
795
+ }
796
+
797
+ /**
798
+ * Filters the output created by a shortcode callback.
799
+ *
800
+ * @since 0.7
801
+ *
802
+ * @param string $output Shortcode output.
803
+ * @param string $tag Shortcode name.
804
+ * @return string Output.
805
+ * @global array $shortcode_tags
806
+ */
807
+ public static function decorate_shortcode_source( $output, $tag ) {
808
+ global $shortcode_tags;
809
+ if ( ! isset( $shortcode_tags[ $tag ] ) ) {
810
+ return $output;
811
+ }
812
+ $source = self::get_source( $shortcode_tags[ $tag ] );
813
+ if ( empty( $source ) ) {
814
+ return $output;
815
+ }
816
+ $source['shortcode'] = $tag;
817
+
818
+ $output = implode( '', array(
819
+ self::get_source_comment( $source, true ),
820
+ $output,
821
+ self::get_source_comment( $source, false ),
822
+ ) );
823
+ return $output;
824
+ }
825
+
826
+ /**
827
+ * Wraps output of a filter to add source stack comments.
828
+ *
829
+ * @todo Duplicate with AMP_Validation_Utils::wrap_buffer_with_source_comments()?
830
+ * @param string $value Value.
831
+ * @return string Value wrapped in source comments.
832
+ */
833
+ public static function decorate_filter_source( $value ) {
834
+
835
+ // Abort if the output is not a string and it doesn't contain any HTML tags.
836
+ if ( ! is_string( $value ) || ! preg_match( '/<.+?>/s', $value ) ) {
837
+ return $value;
838
+ }
839
+
840
+ $post = get_post();
841
+ $source = array(
842
+ 'hook' => current_filter(),
843
+ 'filter' => true,
844
+ );
845
+ if ( $post ) {
846
+ $source['post_id'] = $post->ID;
847
+ $source['post_type'] = $post->post_type;
848
+ }
849
+ if ( isset( self::$current_hook_source_stack[ current_filter() ] ) ) {
850
+ $sources = self::$current_hook_source_stack[ current_filter() ];
851
+ array_pop( $sources ); // Remove self.
852
+ $source['sources'] = $sources;
853
+ }
854
+ return implode( '', array(
855
+ self::get_source_comment( $source, true ),
856
+ $value,
857
+ self::get_source_comment( $source, false ),
858
+ ) );
859
+ }
860
+
861
+ /**
862
+ * Gets the plugin or theme of the callback, if one exists.
863
+ *
864
+ * @param string|array $callback The callback for which to get the plugin.
865
+ * @return array|null {
866
+ * The source data.
867
+ *
868
+ * @type string $type Source type (core, plugin, mu-plugin, or theme).
869
+ * @type string $name Source name.
870
+ * @type string $function Normalized function name.
871
+ * @type ReflectionMethod|ReflectionFunction $reflection
872
+ * }
873
+ */
874
+ public static function get_source( $callback ) {
875
+ $reflection = null;
876
+ $class_name = null; // Because ReflectionMethod::getDeclaringClass() can return a parent class.
877
+ try {
878
+ if ( is_string( $callback ) && is_callable( $callback ) ) {
879
+ // The $callback is a function or static method.
880
+ $exploded_callback = explode( '::', $callback, 2 );
881
+ if ( 2 === count( $exploded_callback ) ) {
882
+ $class_name = $exploded_callback[0];
883
+ $reflection = new ReflectionMethod( $exploded_callback[0], $exploded_callback[1] );
884
+ } else {
885
+ $reflection = new ReflectionFunction( $callback );
886
+ }
887
+ } elseif ( is_array( $callback ) && isset( $callback[0], $callback[1] ) && method_exists( $callback[0], $callback[1] ) ) {
888
+ // The $callback is a method.
889
+ if ( is_string( $callback[0] ) ) {
890
+ $class_name = $callback[0];
891
+ } elseif ( is_object( $callback[0] ) ) {
892
+ $class_name = get_class( $callback[0] );
893
+ }
894
+ $reflection = new ReflectionMethod( $callback[0], $callback[1] );
895
+ } elseif ( is_object( $callback ) && ( 'Closure' === get_class( $callback ) ) ) {
896
+ $reflection = new ReflectionFunction( $callback );
897
+ }
898
+ } catch ( Exception $e ) {
899
+ return null;
900
+ }
901
+
902
+ if ( ! $reflection ) {
903
+ return null;
904
+ }
905
+
906
+ $source = compact( 'reflection' );
907
+
908
+ $file = $reflection->getFileName();
909
+ if ( $file ) {
910
+ $file = wp_normalize_path( $file );
911
+ $slug_pattern = '([^/]+)';
912
+ if ( preg_match( ':' . preg_quote( trailingslashit( wp_normalize_path( WP_PLUGIN_DIR ) ), ':' ) . $slug_pattern . ':s', $file, $matches ) ) {
913
+ $source['type'] = 'plugin';
914
+ $source['name'] = $matches[1];
915
+ } elseif ( preg_match( ':' . preg_quote( trailingslashit( wp_normalize_path( get_theme_root() ) ), ':' ) . $slug_pattern . ':s', $file, $matches ) ) {
916
+ $source['type'] = 'theme';
917
+ $source['name'] = $matches[1];
918
+ } elseif ( preg_match( ':' . preg_quote( trailingslashit( wp_normalize_path( WPMU_PLUGIN_DIR ) ), ':' ) . $slug_pattern . ':s', $file, $matches ) ) {
919
+ $source['type'] = 'mu-plugin';
920
+ $source['name'] = $matches[1];
921
+ } elseif ( preg_match( ':' . preg_quote( trailingslashit( wp_normalize_path( ABSPATH ) ), ':' ) . '(wp-admin|wp-includes)/:s', $file, $matches ) ) {
922
+ $source['type'] = 'core';
923
+ $source['name'] = $matches[1];
924
+ }
925
+ }
926
+
927
+ if ( $class_name ) {
928
+ $source['function'] = $class_name . '::' . $reflection->getName();
929
+ } else {
930
+ $source['function'] = $reflection->getName();
931
+ }
932
+
933
+ return $source;
934
+ }
935
+
936
+ /**
937
+ * Check whether or not output buffering is currently possible.
938
+ *
939
+ * This is to guard against a fatal error: "ob_start(): Cannot use output buffering in output buffering display handlers".
940
+ *
941
+ * @return bool Whether output buffering is allowed.
942
+ */
943
+ public static function can_output_buffer() {
944
+
945
+ // Output buffering for validation can only be done while overall output buffering is being done for the response.
946
+ if ( ! AMP_Theme_Support::is_output_buffering() ) {
947
+ return false;
948
+ }
949
+
950
+ // Abort when in shutdown since output has finished, when we're likely in the overall output buffering display handler.
951
+ if ( did_action( 'shutdown' ) ) {
952
+ return false;
953
+ }
954
+
955
+ // Check if any functions in call stack are output buffering display handlers.
956
+ $called_functions = array();
957
+ if ( defined( 'DEBUG_BACKTRACE_IGNORE_ARGS' ) ) {
958
+ $arg = DEBUG_BACKTRACE_IGNORE_ARGS; // phpcs:ignore PHPCompatibility.PHP.NewConstants.debug_backtrace_ignore_argsFound
959
+ } else {
960
+ $arg = false;
961
+ }
962
+ $backtrace = debug_backtrace( $arg ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Only way to find out if we are in a buffering display handler.
963
+ foreach ( $backtrace as $call_stack ) {
964
+ $called_functions[] = '{closure}' === $call_stack['function'] ? 'Closure::__invoke' : $call_stack['function'];
965
+ }
966
+ return 0 === count( array_intersect( ob_list_handlers(), $called_functions ) );
967
+ }
968
+
969
+ /**
970
+ * Wraps a callback in comments if it outputs markup.
971
+ *
972
+ * If the sanitizer removes markup,
973
+ * this indicates which plugin it was from.
974
+ * The call_user_func_array() logic is mainly copied from WP_Hook:apply_filters().
975
+ *
976
+ * @param array $callback {
977
+ * The callback data.
978
+ *
979
+ * @type callable $function
980
+ * @type int $accepted_args
981
+ * @type array $source
982
+ * }
983
+ * @return closure $wrapped_callback The callback, wrapped in comments.
984
+ */
985
+ public static function wrapped_callback( $callback ) {
986
+ return function() use ( $callback ) {
987
+ global $wp_styles, $wp_scripts;
988
+
989
+ $function = $callback['function'];
990
+ $accepted_args = $callback['accepted_args'];
991
+ $args = func_get_args();
992
+
993
+ $before_styles_enqueued = array();
994
+ if ( isset( $wp_styles ) && isset( $wp_styles->queue ) ) {
995
+ $before_styles_enqueued = $wp_styles->queue;
996
+ }
997
+ $before_scripts_enqueued = array();
998
+ if ( isset( $wp_scripts ) && isset( $wp_scripts->queue ) ) {
999
+ $before_scripts_enqueued = $wp_scripts->queue;
1000
+ }
1001
+
1002
+ // Wrap the markup output of (action) hooks in source comments.
1003
+ AMP_Validation_Utils::$hook_source_stack[] = $callback['source'];
1004
+ $has_buffer_started = false;
1005
+ if ( AMP_Validation_Utils::can_output_buffer() ) {
1006
+ $has_buffer_started = ob_start( array( __CLASS__, 'wrap_buffer_with_source_comments' ) );
1007
+ }
1008
+ $result = call_user_func_array( $function, array_slice( $args, 0, intval( $accepted_args ) ) );
1009
+ if ( $has_buffer_started ) {
1010
+ ob_end_flush();
1011
+ }
1012
+ array_pop( AMP_Validation_Utils::$hook_source_stack );
1013
+
1014
+ // Keep track of which source enqueued the styles.
1015
+ if ( isset( $wp_styles ) && isset( $wp_styles->queue ) ) {
1016
+ foreach ( array_diff( $wp_styles->queue, $before_styles_enqueued ) as $handle ) {
1017
+ AMP_Validation_Utils::$enqueued_style_sources[ $handle ][] = $callback['source'];
1018
+ }
1019
+ }
1020
+
1021
+ // Keep track of which source enqueued the scripts, and immediately report validity .
1022
+ if ( isset( $wp_scripts ) && isset( $wp_scripts->queue ) ) {
1023
+ foreach ( array_diff( $wp_scripts->queue, $before_scripts_enqueued ) as $handle ) {
1024
+ AMP_Validation_Utils::$enqueued_script_sources[ $handle ][] = $callback['source'];
1025
+
1026
+ // Flag all scripts not loaded from the AMP CDN as validation errors.
1027
+ if ( isset( $wp_scripts->registered[ $handle ] ) && 0 !== strpos( $wp_scripts->registered[ $handle ]->src, 'https://cdn.ampproject.org/' ) ) {
1028
+ self::add_validation_error( array(
1029
+ 'code' => self::ENQUEUED_SCRIPT_CODE,
1030
+ 'handle' => $handle,
1031
+ 'dependency' => $wp_scripts->registered[ $handle ],
1032
+ 'sources' => array(
1033
+ $callback['source'],
1034
+ ),
1035
+ ) );
1036
+ }
1037
+ }
1038
+ }
1039
+
1040
+ return $result;
1041
+ };
1042
+ }
1043
+
1044
+ /**
1045
+ * Wrap output buffer with source comments.
1046
+ *
1047
+ * A key reason for why this is a method and not a closure is so that
1048
+ * the can_output_buffer method will be able to identify it by name.
1049
+ *
1050
+ * @since 0.7
1051
+ * @todo Is duplicate of \AMP_Validation_Utils::decorate_filter_source()?
1052
+ *
1053
+ * @param string $output Output buffer.
1054
+ * @return string Output buffer conditionally wrapped with source comments.
1055
+ */
1056
+ public static function wrap_buffer_with_source_comments( $output ) {
1057
+ if ( empty( self::$hook_source_stack ) ) {
1058
+ return $output;
1059
+ }
1060
+
1061
+ $source = self::$hook_source_stack[ count( self::$hook_source_stack ) - 1 ];
1062
+
1063
+ // Wrap output that contains HTML tags (as opposed to actions that trigger in HTML attributes).
1064
+ if ( ! empty( $output ) && preg_match( '/<.+?>/s', $output ) ) {
1065
+ $output = implode( '', array(
1066
+ self::get_source_comment( $source, true ),
1067
+ $output,
1068
+ self::get_source_comment( $source, false ),
1069
+ ) );
1070
+ }
1071
+ return $output;
1072
+ }
1073
+
1074
+ /**
1075
+ * Output a removed set, each wrapped in <code></code>.
1076
+ *
1077
+ * @param array[][] $set {
1078
+ * The removed elements to output.
1079
+ *
1080
+ * @type string $name The name of the source.
1081
+ * @type string $count The number that were invalid.
1082
+ * }
1083
+ * @return void
1084
+ */
1085
+ protected static function output_removed_set( $set ) {
1086
+ $items = array();
1087
+ foreach ( $set as $name => $count ) {
1088
+ if ( 1 === intval( $count ) ) {
1089
+ $items[] = sprintf( '<code>%s</code>', esc_html( $name ) );
1090
+ } else {
1091
+ $items[] = sprintf( '<code>%s</code> (%d)', esc_html( $name ), $count );
1092
+ }
1093
+ }
1094
+ echo implode( ', ', $items ); // WPCS: XSS OK.
1095
+ }
1096
+
1097
+ /**
1098
+ * Whether to validate the front end response.
1099
+ *
1100
+ * Either the user has the capability and the query var is present.
1101
+ *
1102
+ * @return boolean Whether to validate.
1103
+ */
1104
+ public static function should_validate_response() {
1105
+ return self::has_cap() && isset( $_GET[ self::VALIDATE_QUERY_VAR ] ); // WPCS: CSRF ok.
1106
+ }
1107
+
1108
+ /**
1109
+ * Finalize validation.
1110
+ *
1111
+ * @param DOMDocument $dom Document.
1112
+ * @param array $args {
1113
+ * Args.
1114
+ *
1115
+ * @type bool $remove_source_comments Whether source comments should be removed. Defaults to true.
1116
+ * @type bool $append_validation_status_comment Whether the validation errors should be appended as an HTML comment. Defaults to true.
1117
+ * }
1118
+ */
1119
+ public static function finalize_validation( DOMDocument $dom, $args = array() ) {
1120
+ $args = array_merge(
1121
+ array(
1122
+ 'remove_source_comments' => true,
1123
+ 'append_validation_status_comment' => true,
1124
+ ),
1125
+ $args
1126
+ );
1127
+
1128
+ if ( $args['remove_source_comments'] ) {
1129
+ self::remove_source_comments( $dom );
1130
+ }
1131
+
1132
+ if ( $args['append_validation_status_comment'] ) {
1133
+ $encoded = wp_json_encode( self::$validation_errors, 128 /* JSON_PRETTY_PRINT */ );
1134
+ $encoded = str_replace( '--', '\u002d\u002d', $encoded ); // Prevent "--" in strings from breaking out of HTML comments.
1135
+ $comment = $dom->createComment( 'AMP_VALIDATION_ERRORS:' . $encoded . "\n" );
1136
+ $dom->documentElement->appendChild( $comment );
1137
+ }
1138
+ }
1139
+
1140
+ /**
1141
+ * Adds the validation callback if front-end validation is needed.
1142
+ *
1143
+ * @param array $sanitizers The AMP sanitizers.
1144
+ * @return array $sanitizers The filtered AMP sanitizers.
1145
+ */
1146
+ public static function add_validation_callback( $sanitizers ) {
1147
+ foreach ( $sanitizers as $sanitizer => $args ) {
1148
+ $sanitizers[ $sanitizer ] = array_merge(
1149
+ $args,
1150
+ array(
1151
+ 'validation_error_callback' => __CLASS__ . '::add_validation_error',
1152
+ )
1153
+ );
1154
+ }
1155
+ return $sanitizers;
1156
+ }
1157
+
1158
+ /**
1159
+ * Registers the post type to store the validation errors.
1160
+ *
1161
+ * @return void.
1162
+ */
1163
+ public static function register_post_type() {
1164
+ $post_type = register_post_type(
1165
+ self::POST_TYPE_SLUG,
1166
+ array(
1167
+ 'labels' => array(
1168
+ 'name' => _x( 'Validation Status', 'post type general name', 'amp' ),
1169
+ 'singular_name' => __( 'validation error', 'amp' ),
1170
+ 'not_found' => __( 'No validation errors found', 'amp' ),
1171
+ 'not_found_in_trash' => __( 'No validation errors found in trash', 'amp' ),
1172
+ 'search_items' => __( 'Search statuses', 'amp' ),
1173
+ 'edit_item' => __( 'Validation Status', 'amp' ),
1174
+ ),
1175
+ 'supports' => false,
1176
+ 'public' => false,
1177
+ 'show_ui' => true,
1178
+ 'show_in_menu' => AMP_Options_Manager::OPTION_NAME,
1179
+ )
1180
+ );
1181
+
1182
+ // Hide the add new post link.
1183
+ $post_type->cap->create_posts = 'do_not_allow';
1184
+ }
1185
+
1186
+ /**
1187
+ * Stores the validation errors.
1188
+ *
1189
+ * After the preprocessors run, this gets the validation response if the query var is present.
1190
+ * It then stores the response in a custom post type.
1191
+ * If there's already an error post for the URL, but there's no error anymore, it deletes it.
1192
+ *
1193
+ * @param array $validation_errors Validation errors.
1194
+ * @param string $url URL on which the validation errors occurred.
1195
+ * @return int|null $post_id The post ID of the custom post type used, or null.
1196
+ * @global WP $wp
1197
+ */
1198
+ public static function store_validation_errors( $validation_errors, $url ) {
1199
+ $post_for_this_url = self::get_validation_status_post( $url );
1200
+
1201
+ // Since there are no validation errors and there is an existing $existing_post_id, just delete the post.
1202
+ if ( empty( $validation_errors ) ) {
1203
+ if ( $post_for_this_url ) {
1204
+ wp_delete_post( $post_for_this_url->ID, true );
1205
+ }
1206
+ return null;
1207
+ }
1208
+
1209
+ $encoded_errors = wp_json_encode( $validation_errors );
1210
+ $post_name = md5( $encoded_errors );
1211
+
1212
+ // If the post name is unchanged then the errors are the same and there is nothing to do.
1213
+ if ( $post_for_this_url && $post_for_this_url->post_name === $post_name ) {
1214
+ return $post_for_this_url->ID;
1215
+ }
1216
+
1217
+ // If there already exists a post for the given validation errors, just amend the $url to the existing post.
1218
+ $post_for_other_url = get_page_by_path( $post_name, OBJECT, self::POST_TYPE_SLUG );
1219
+ if ( ! $post_for_other_url ) {
1220
+ $post_for_other_url = get_page_by_path( $post_name . '__trashed', OBJECT, self::POST_TYPE_SLUG );
1221
+ }
1222
+ if ( $post_for_other_url ) {
1223
+ if ( 'trash' === $post_for_other_url->post_status ) {
1224
+ wp_untrash_post( $post_for_other_url->ID );
1225
+ }
1226
+ if ( ! in_array( $url, get_post_meta( $post_for_other_url->ID, self::AMP_URL_META, false ), true ) ) {
1227
+ add_post_meta( $post_for_other_url->ID, self::AMP_URL_META, wp_slash( $url ), false );
1228
+ }
1229
+ return $post_for_other_url->ID;
1230
+ }
1231
+
1232
+ // Otherwise, create a new validation status post, or update the existing one.
1233
+ $post_id = wp_insert_post( wp_slash( array(
1234
+ 'ID' => $post_for_this_url ? $post_for_this_url->ID : null,
1235
+ 'post_type' => self::POST_TYPE_SLUG,
1236
+ 'post_title' => $url,
1237
+ 'post_name' => $post_name,
1238
+ 'post_content' => $encoded_errors,
1239
+ 'post_status' => 'publish',
1240
+ ) ) );
1241
+ if ( ! $post_id ) {
1242
+ return null;
1243
+ }
1244
+ if ( ! in_array( $url, get_post_meta( $post_id, self::AMP_URL_META, false ), true ) ) {
1245
+ add_post_meta( $post_id, self::AMP_URL_META, wp_slash( $url ), false );
1246
+ }
1247
+ return $post_id;
1248
+ }
1249
+
1250
+ /**
1251
+ * Gets the existing custom post that stores errors for the $url, if it exists.
1252
+ *
1253
+ * @param string $url The URL of the post.
1254
+ * @return WP_Post|null The post of the existing custom post, or null.
1255
+ */
1256
+ public static function get_validation_status_post( $url ) {
1257
+ if ( ! post_type_exists( self::POST_TYPE_SLUG ) ) {
1258
+ return null;
1259
+ }
1260
+ $query = new WP_Query( array(
1261
+ 'post_type' => self::POST_TYPE_SLUG,
1262
+ 'post_status' => 'publish',
1263
+ 'posts_per_page' => 1,
1264
+ 'meta_query' => array(
1265
+ array(
1266
+ 'key' => self::AMP_URL_META,
1267
+ 'value' => $url,
1268
+ ),
1269
+ ),
1270
+ ) );
1271
+ return array_shift( $query->posts );
1272
+ }
1273
+
1274
+ /**
1275
+ * Validates the latest published post.
1276
+ *
1277
+ * @return array|WP_Error The validation errors, or WP_Error.
1278
+ */
1279
+ public static function validate_after_plugin_activation() {
1280
+ $url = amp_admin_get_preview_permalink();
1281
+ if ( ! $url ) {
1282
+ return new WP_Error( 'no_published_post_url_available' );
1283
+ }
1284
+ $validation_errors = self::validate_url( $url );
1285
+ if ( is_array( $validation_errors ) && count( $validation_errors ) > 0 ) {
1286
+ self::store_validation_errors( $validation_errors, $url );
1287
+ set_transient( self::PLUGIN_ACTIVATION_VALIDATION_ERRORS_TRANSIENT_KEY, $validation_errors, 60 );
1288
+ } else {
1289
+ delete_transient( self::PLUGIN_ACTIVATION_VALIDATION_ERRORS_TRANSIENT_KEY );
1290
+ }
1291
+ return $validation_errors;
1292
+ }
1293
+
1294
+ /**
1295
+ * Validates a given URL.
1296
+ *
1297
+ * The validation errors will be stored in the validation status custom post type,
1298
+ * as well as in a transient.
1299
+ *
1300
+ * @param string $url The URL to validate.
1301
+ * @return array|WP_Error The validation errors, or WP_Error on error.
1302
+ */
1303
+ public static function validate_url( $url ) {
1304
+ $validation_url = add_query_arg(
1305
+ array(
1306
+ self::VALIDATE_QUERY_VAR => 1,
1307
+ self::CACHE_BUST_QUERY_VAR => wp_rand(),
1308
+ ),
1309
+ $url
1310
+ );
1311
+
1312
+ $r = wp_remote_get( $validation_url, array(
1313
+ 'cookies' => wp_unslash( $_COOKIE ),
1314
+ 'sslverify' => false,
1315
+ 'headers' => array(
1316
+ 'Cache-Control' => 'no-cache',
1317
+ ),
1318
+ ) );
1319
+ if ( is_wp_error( $r ) ) {
1320
+ return $r;
1321
+ }
1322
+ if ( wp_remote_retrieve_response_code( $r ) >= 400 ) {
1323
+ return new WP_Error(
1324
+ wp_remote_retrieve_response_code( $r ),
1325
+ wp_remote_retrieve_response_message( $r )
1326
+ );
1327
+ }
1328
+ $response = wp_remote_retrieve_body( $r );
1329
+ if ( ! preg_match( '#</body>.*?<!--\s*AMP_VALIDATION_ERRORS\s*:\s*(\[.*?\])\s*-->#s', $response, $matches ) ) {
1330
+ return new WP_Error( 'response_comment_absent' );
1331
+ }
1332
+ $validation_errors = json_decode( $matches[1], true );
1333
+ if ( ! is_array( $validation_errors ) ) {
1334
+ return new WP_Error( 'malformed_json_validation_errors' );
1335
+ }
1336
+
1337
+ return $validation_errors;
1338
+ }
1339
+
1340
+ /**
1341
+ * On activating a plugin, display a notice if a plugin causes an AMP validation error.
1342
+ *
1343
+ * @return void
1344
+ */
1345
+ public static function plugin_notice() {
1346
+ global $pagenow;
1347
+ if ( ( 'plugins.php' === $pagenow ) && ( ! empty( $_GET['activate'] ) || ! empty( $_GET['activate-multi'] ) ) ) { // WPCS: CSRF ok.
1348
+ $validation_errors = get_transient( self::PLUGIN_ACTIVATION_VALIDATION_ERRORS_TRANSIENT_KEY );
1349
+ if ( empty( $validation_errors ) || ! is_array( $validation_errors ) ) {
1350
+ return;
1351
+ }
1352
+ delete_transient( self::PLUGIN_ACTIVATION_VALIDATION_ERRORS_TRANSIENT_KEY );
1353
+ $errors = self::summarize_validation_errors( $validation_errors );
1354
+ $invalid_plugins = isset( $errors[ self::SOURCES_INVALID_OUTPUT ]['plugin'] ) ? array_unique( $errors[ self::SOURCES_INVALID_OUTPUT ]['plugin'] ) : null;
1355
+ if ( isset( $invalid_plugins ) ) {
1356
+ $reported_plugins = array();
1357
+ foreach ( $invalid_plugins as $plugin ) {
1358
+ $reported_plugins[] = sprintf( '<code>%s</code>', esc_html( $plugin ) );
1359
+ }
1360
+
1361
+ $more_details_link = sprintf(
1362
+ '<a href="%s">%s</a>',
1363
+ esc_url( add_query_arg(
1364
+ 'post_type',
1365
+ self::POST_TYPE_SLUG,
1366
+ admin_url( 'edit.php' )
1367
+ ) ),
1368
+ __( 'More details', 'amp' )
1369
+ );
1370
+ printf(
1371
+ '<div class="notice notice-warning is-dismissible"><p>%s %s %s</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">%s</span></button></div>',
1372
+ esc_html( _n( 'Warning: The following plugin may be incompatible with AMP:', 'Warning: The following plugins may be incompatible with AMP: ', count( $invalid_plugins ), 'amp' ) ),
1373
+ implode( ', ', $reported_plugins ),
1374
+ $more_details_link,
1375
+ esc_html__( 'Dismiss this notice.', 'amp' )
1376
+ ); // WPCS: XSS ok.
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ /**
1382
+ * Adds post columns to the UI for the validation errors.
1383
+ *
1384
+ * @param array $columns The post columns.
1385
+ * @return array $columns The new post columns.
1386
+ */
1387
+ public static function add_post_columns( $columns ) {
1388
+ $columns = array_merge(
1389
+ $columns,
1390
+ array(
1391
+ 'url_count' => esc_html__( 'Count', 'amp' ),
1392
+ self::REMOVED_ELEMENTS => esc_html__( 'Removed Elements', 'amp' ),
1393
+ self::REMOVED_ATTRIBUTES => esc_html__( 'Removed Attributes', 'amp' ),
1394
+ self::SOURCES_INVALID_OUTPUT => esc_html__( 'Incompatible Sources', 'amp' ),
1395
+ )
1396
+ );
1397
+
1398
+ // Move date to end.
1399
+ if ( isset( $columns['date'] ) ) {
1400
+ $date = $columns['date'];
1401
+ unset( $columns['date'] );
1402
+ $columns['date'] = $date;
1403
+ }
1404
+
1405
+ return $columns;
1406
+ }
1407
+
1408
+ /**
1409
+ * Outputs custom columns in the /wp-admin UI for the AMP validation errors.
1410
+ *
1411
+ * @param string $column_name The name of the column.
1412
+ * @param int $post_id The ID of the post for the column.
1413
+ * @return void
1414
+ */
1415
+ public static function output_custom_column( $column_name, $post_id ) {
1416
+ $post = get_post( $post_id );
1417
+ if ( self::POST_TYPE_SLUG !== $post->post_type ) {
1418
+ return;
1419
+ }
1420
+ $validation_errors = json_decode( $post->post_content, true );
1421
+ if ( ! is_array( $validation_errors ) ) {
1422
+ return;
1423
+ }
1424
+ $errors = self::summarize_validation_errors( $validation_errors );
1425
+ $urls = get_post_meta( $post_id, self::AMP_URL_META, false );
1426
+
1427
+ switch ( $column_name ) {
1428
+ case 'url_count':
1429
+ echo count( $urls );
1430
+ break;
1431
+ case self::REMOVED_ELEMENTS:
1432
+ if ( ! empty( $errors[ self::REMOVED_ELEMENTS ] ) ) {
1433
+ self::output_removed_set( $errors[ self::REMOVED_ELEMENTS ] );
1434
+ } else {
1435
+ esc_html_e( '--', 'amp' );
1436
+ }
1437
+ break;
1438
+ case self::REMOVED_ATTRIBUTES:
1439
+ if ( ! empty( $errors[ self::REMOVED_ATTRIBUTES ] ) ) {
1440
+ self::output_removed_set( $errors[ self::REMOVED_ATTRIBUTES ] );
1441
+ } else {
1442
+ esc_html_e( '--', 'amp' );
1443
+ }
1444
+ break;
1445
+ case self::SOURCES_INVALID_OUTPUT:
1446
+ if ( isset( $errors[ self::SOURCES_INVALID_OUTPUT ] ) ) {
1447
+ $sources = array();
1448
+ foreach ( $errors[ self::SOURCES_INVALID_OUTPUT ] as $type => $names ) {
1449
+ foreach ( array_unique( $names ) as $name ) {
1450
+ $sources[] = sprintf( '%s: <code>%s</code>', esc_html( $type ), esc_html( $name ) );
1451
+ }
1452
+ }
1453
+ echo implode( ', ', $sources ); // WPCS: XSS ok.
1454
+ }
1455
+ break;
1456
+ }
1457
+ }
1458
+
1459
+ /**
1460
+ * Adds a 'Recheck' link to the edit.php row actions.
1461
+ *
1462
+ * The logic to add the new action is mainly copied from WP_Posts_List_Table::handle_row_actions().
1463
+ *
1464
+ * @param array $actions The actions in the edit.php page.
1465
+ * @param WP_Post $post The post for the actions.
1466
+ * @return array $actions The filtered actions.
1467
+ */
1468
+ public static function filter_row_actions( $actions, $post ) {
1469
+ if ( self::POST_TYPE_SLUG !== $post->post_type ) {
1470
+ return $actions;
1471
+ }
1472
+
1473
+ $actions['edit'] = sprintf(
1474
+ '<a href="%s">%s</a>',
1475
+ esc_url( get_edit_post_link( $post ) ),
1476
+ esc_html__( 'Details', 'amp' )
1477
+ );
1478
+ unset( $actions['inline hide-if-no-js'] );
1479
+ $url = get_post_meta( $post->ID, self::AMP_URL_META, true );
1480
+
1481
+ if ( ! empty( $url ) ) {
1482
+ $actions[ self::RECHECK_ACTION ] = self::get_recheck_link( $post, get_edit_post_link( $post->ID, 'raw' ), $url );
1483
+ $actions[ self::DEBUG_QUERY_VAR ] = sprintf(
1484
+ '<a href="%s" aria-label="%s">%s</a>',
1485
+ esc_url( self::get_debug_url( $url ) ),
1486
+ esc_attr__( 'Validate URL on frontend but without invalid elements/attributes removed', 'amp' ),
1487
+ esc_html__( 'Debug', 'amp' )
1488
+ );
1489
+ }
1490
+
1491
+ return $actions;
1492
+ }
1493
+
1494
+ /**
1495
+ * Adds a 'Recheck' bulk action to the edit.php page.
1496
+ *
1497
+ * @param array $actions The bulk actions in the edit.php page.
1498
+ * @return array $actions The filtered bulk actions.
1499
+ */
1500
+ public static function add_bulk_action( $actions ) {
1501
+ unset( $actions['edit'] );
1502
+ $actions[ self::RECHECK_ACTION ] = esc_html__( 'Recheck', 'amp' );
1503
+ return $actions;
1504
+ }
1505
+
1506
+ /**
1507
+ * Handles the 'Recheck' bulk action on the edit.php page.
1508
+ *
1509
+ * @param string $redirect The URL of the redirect.
1510
+ * @param string $action The action.
1511
+ * @param array $items The items on which to take the action.
1512
+ * @return string $redirect The filtered URL of the redirect.
1513
+ */
1514
+ public static function handle_bulk_action( $redirect, $action, $items ) {
1515
+ if ( self::RECHECK_ACTION !== $action ) {
1516
+ return $redirect;
1517
+ }
1518
+ $remaining_invalid_urls = array();
1519
+ foreach ( $items as $item ) {
1520
+ $url = get_post_meta( $item, self::AMP_URL_META, true );
1521
+ if ( empty( $url ) ) {
1522
+ continue;
1523
+ }
1524
+
1525
+ $validation_errors = self::validate_url( $url );
1526
+ if ( ! is_array( $validation_errors ) ) {
1527
+ continue;
1528
+ }
1529
+
1530
+ self::store_validation_errors( $validation_errors, $url );
1531
+ if ( ! empty( $validation_errors ) ) {
1532
+ $remaining_invalid_urls[] = $url;
1533
+ }
1534
+ }
1535
+
1536
+ // Get the URLs that still have errors after rechecking.
1537
+ $args = array(
1538
+ self::URLS_TESTED => count( $items ),
1539
+ self::REMAINING_ERRORS => empty( $remaining_invalid_urls ) ? '0' : '1',
1540
+ );
1541
+
1542
+ return add_query_arg( $args, $redirect );
1543
+ }
1544
+
1545
+ /**
1546
+ * Outputs an admin notice after rechecking URL(s) on the custom post page.
1547
+ *
1548
+ * @return void
1549
+ */
1550
+ public static function remaining_error_notice() {
1551
+ if ( ! isset( $_GET[ self::REMAINING_ERRORS ] ) || self::POST_TYPE_SLUG !== get_current_screen()->post_type ) { // WPCS: CSRF ok.
1552
+ return;
1553
+ }
1554
+
1555
+ $count_urls_tested = isset( $_GET[ self::URLS_TESTED ] ) ? intval( $_GET[ self::URLS_TESTED ] ) : 1; // WPCS: CSRF ok.
1556
+ $errors_remain = ! empty( $_GET[ self::REMAINING_ERRORS ] ); // WPCS: CSRF ok.
1557
+ if ( $errors_remain ) {
1558
+ $class = 'notice-warning';
1559
+ $message = _n( 'The rechecked URL still has validation errors.', 'The rechecked URLs still have validation errors.', $count_urls_tested, 'amp' );
1560
+ } else {
1561
+ $message = _n( 'The rechecked URL has no validation errors.', 'The rechecked URLs have no validation errors.', $count_urls_tested, 'amp' );
1562
+ $class = 'updated';
1563
+ }
1564
+
1565
+ printf(
1566
+ '<div class="notice is-dismissible %s"><p>%s</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">%s</span></button></div>',
1567
+ esc_attr( $class ),
1568
+ esc_html( $message ),
1569
+ esc_html__( 'Dismiss this notice.', 'amp' )
1570
+ );
1571
+ }
1572
+
1573
+ /**
1574
+ * Handles clicking 'recheck' on the inline post actions.
1575
+ *
1576
+ * @param int $post_id The post ID of the recheck.
1577
+ * @return void
1578
+ */
1579
+ public static function handle_inline_recheck( $post_id ) {
1580
+ check_admin_referer( self::NONCE_ACTION . $post_id );
1581
+ $url = get_post_meta( $post_id, self::AMP_URL_META, true );
1582
+ if ( isset( $_GET['recheck_url'] ) ) {
1583
+ $url = wp_validate_redirect( wp_unslash( $_GET['recheck_url'] ) );
1584
+ }
1585
+ $validation_errors = self::validate_url( $url );
1586
+ $remaining_errors = true;
1587
+ if ( is_array( $validation_errors ) ) {
1588
+ self::store_validation_errors( $validation_errors, $url );
1589
+ $remaining_errors = ! empty( $validation_errors );
1590
+ }
1591
+
1592
+ $redirect = wp_get_referer();
1593
+ if ( ! $redirect || empty( $validation_errors ) ) {
1594
+ // If there are no remaining errors and the post was deleted, redirect to edit.php instead of post.php.
1595
+ $redirect = add_query_arg(
1596
+ 'post_type',
1597
+ self::POST_TYPE_SLUG,
1598
+ admin_url( 'edit.php' )
1599
+ );
1600
+ }
1601
+ $args = array(
1602
+ self::URLS_TESTED => '1',
1603
+ self::REMAINING_ERRORS => $remaining_errors ? '1' : '0',
1604
+ );
1605
+ wp_safe_redirect( add_query_arg( $args, $redirect ) );
1606
+ exit();
1607
+ }
1608
+
1609
+ /**
1610
+ * Removes the 'Publish' meta box from the CPT post.php page.
1611
+ *
1612
+ * @return void
1613
+ */
1614
+ public static function remove_publish_meta_box() {
1615
+ remove_meta_box( 'submitdiv', self::POST_TYPE_SLUG, 'side' );
1616
+ }
1617
+
1618
+ /**
1619
+ * Adds the meta boxes to the CPT post.php page.
1620
+ *
1621
+ * @return void
1622
+ */
1623
+ public static function add_meta_boxes() {
1624
+ add_meta_box( self::VALIDATION_ERRORS_META_BOX, __( 'Validation Errors', 'amp' ), array( __CLASS__, 'print_validation_errors_meta_box' ), self::POST_TYPE_SLUG, 'normal' );
1625
+ add_meta_box( self::STATUS_META_BOX, __( 'Status', 'amp' ), array( __CLASS__, 'print_status_meta_box' ), self::POST_TYPE_SLUG, 'side' );
1626
+ }
1627
+
1628
+ /**
1629
+ * Outputs the markup of the side meta box in the CPT post.php page.
1630
+ *
1631
+ * This is partially copied from meta-boxes.php.
1632
+ * Adds 'Published on,' and links to move to trash and recheck.
1633
+ *
1634
+ * @param WP_Post $post The post for which to output the box.
1635
+ * @return void
1636
+ */
1637
+ public static function print_status_meta_box( $post ) {
1638
+ $redirect_url = add_query_arg(
1639
+ 'post',
1640
+ $post->ID,
1641
+ admin_url( 'post.php' )
1642
+ );
1643
+
1644
+ echo '<div id="submitpost" class="submitbox">';
1645
+ /* translators: Meta box date format */
1646
+ $date_format = __( 'M j, Y @ H:i', 'default' );
1647
+ echo '<div class="curtime misc-pub-section"><span id="timestamp">';
1648
+ /* translators: %s: The date this was published */
1649
+ printf( __( 'Published on: <b>%s</b>', 'amp' ), esc_html( date_i18n( $date_format, strtotime( $post->post_date ) ) ) ); // WPCS: XSS ok.
1650
+ echo '</span></div>';
1651
+ printf( '<div class="misc-pub-section"><a class="submitdelete deletion" href="%s">%s</a></div>', esc_url( get_delete_post_link( $post->ID ) ), esc_html__( 'Move to Trash', 'default' ) );
1652
+
1653
+ echo '<div class="misc-pub-section">';
1654
+ echo self::get_recheck_link( $post, $redirect_url ); // WPCS: XSS ok.
1655
+ $url = get_post_meta( $post->ID, self::AMP_URL_META, true );
1656
+ if ( $url ) {
1657
+ printf(
1658
+ ' | <a href="%s" aria-label="%s">%s</a>',
1659
+ esc_url( self::get_debug_url( $url ) ),
1660
+ esc_attr__( 'Validate URL on frontend but without invalid elements/attributes removed', 'amp' ),
1661
+ esc_html__( 'Debug', 'amp' )
1662
+ ); // WPCS: XSS ok.
1663
+ }
1664
+ echo '</div>';
1665
+
1666
+ echo '</div><!-- /submitpost -->';
1667
+ }
1668
+
1669
+ /**
1670
+ * Outputs the full meta box on the CPT post.php page.
1671
+ *
1672
+ * This displays the errors stored in the post content.
1673
+ * These are output as stored, but using <details> elements.
1674
+ *
1675
+ * @param WP_Post $post The post for which to output the box.
1676
+ * @return void
1677
+ */
1678
+ public static function print_validation_errors_meta_box( $post ) {
1679
+ $errors = json_decode( $post->post_content, true );
1680
+ $urls = get_post_meta( $post->ID, self::AMP_URL_META, false );
1681
+ ?>
1682
+ <style>
1683
+ .amp-validation-errors .detailed {
1684
+ margin-left: 30px;
1685
+ }
1686
+ .amp-validation-errors .amp-recheck {
1687
+ float: right;
1688
+ }
1689
+ </style>
1690
+ <div class="amp-validation-errors">
1691
+ <ul>
1692
+ <?php foreach ( $errors as $error ) : ?>
1693
+ <?php
1694
+ $collasped_details = array();
1695
+ ?>
1696
+ <li>
1697
+ <details open>
1698
+ <summary><code><?php echo esc_html( $error['code'] ); ?></code></summary>
1699
+ <ul class="detailed">
1700
+ <?php if ( self::INVALID_ELEMENT_CODE === $error['code'] ) : ?>
1701
+ <li>
1702
+ <details open>
1703
+ <summary><?php esc_html_e( 'Removed:', 'amp' ); ?></summary>
1704
+ <code class="detailed">
1705
+ <?php
1706
+ if ( isset( $error['parent_name'] ) ) {
1707
+ echo esc_html( sprintf( '<%s …>', $error['parent_name'] ) );
1708
+ }
1709
+ ?>
1710
+ <mark>
1711
+ <?php
1712
+ echo esc_html( sprintf( '<%s', $error['node_name'] ) );
1713
+ if ( isset( $error['node_attributes'] ) ) {
1714
+ foreach ( $error['node_attributes'] as $key => $value ) {
1715
+ printf( ' %s="%s"', esc_html( $key ), esc_html( $value ) );
1716
+ }
1717
+ }
1718
+ echo esc_html( '>…' );
1719
+ ?>
1720
+ </mark>
1721
+ </code>
1722
+ </details>
1723
+ <?php
1724
+ $collasped_details[] = 'node_attributes';
1725
+ $collasped_details[] = 'node_name';
1726
+ $collasped_details[] = 'parent_name';
1727
+ ?>
1728
+ </li>
1729
+ <?php elseif ( self::INVALID_ATTRIBUTE_CODE === $error['code'] ) : ?>
1730
+ <li>
1731
+ <details open>
1732
+ <summary><?php esc_html_e( 'Removed:', 'amp' ); ?></summary>
1733
+ <code class="detailed">
1734
+ <?php
1735
+ if ( isset( $error['parent_name'] ) ) {
1736
+ echo esc_html( sprintf( '<%s', $error['parent_name'] ) );
1737
+ }
1738
+ foreach ( $error['element_attributes'] as $key => $value ) {
1739
+ if ( $key === $error['node_name'] ) {
1740
+ echo '<mark>';
1741
+ }
1742
+ printf( ' %s="%s"', esc_html( $key ), esc_html( $value ) );
1743
+ if ( $key === $error['node_name'] ) {
1744
+ echo '</mark>';
1745
+ }
1746
+ }
1747
+ echo esc_html( '>' );
1748
+ ?>
1749
+ </code>
1750
+ </details>
1751
+ <?php
1752
+ $collasped_details[] = 'parent_name';
1753
+ $collasped_details[] = 'element_attributes';
1754
+ $collasped_details[] = 'node_name';
1755
+ ?>
1756
+ </li>
1757
+ <?php endif; ?>
1758
+ <?php unset( $error['code'] ); ?>
1759
+ <?php foreach ( $error as $key => $value ) : ?>
1760
+ <li>
1761
+ <details <?php echo ! in_array( $key, $collasped_details, true ) ? 'open' : ''; ?>>
1762
+ <summary><code><?php echo esc_html( $key ); ?></code></summary>
1763
+ <div class="detailed">
1764
+ <?php if ( is_string( $value ) ) : ?>
1765
+ <?php echo esc_html( $value ); ?>
1766
+ <?php else : ?>
1767
+ <pre><?php echo esc_html( wp_json_encode( $value, 128 /* JSON_PRETTY_PRINT */ | 64 /* JSON_UNESCAPED_SLASHES */ ) ); ?></pre>
1768
+ <?php endif; ?>
1769
+ </div>
1770
+ </details>
1771
+ </li>
1772
+ <?php endforeach; ?>
1773
+ </ul>
1774
+ </details>
1775
+ </li>
1776
+ <?php endforeach; ?>
1777
+ </ul>
1778
+ <hr>
1779
+ <h3><?php esc_html_e( 'URLs', 'amp' ); ?></h3>
1780
+ <ul>
1781
+ <?php foreach ( $urls as $url ) : ?>
1782
+ <li>
1783
+ <a href="<?php echo esc_url( $url ); ?>"><?php echo esc_url( $url ); ?></a>
1784
+ <span class="amp-recheck">
1785
+ <?php echo self::get_recheck_link( $post, get_edit_post_link( $post->ID, 'raw' ), $url ); // WPCS: XSS ok. ?>
1786
+ |
1787
+ <?php
1788
+ printf(
1789
+ '<a href="%s" aria-label="%s">%s</a>',
1790
+ esc_url( self::get_debug_url( $url ) ),
1791
+ esc_attr__( 'Validate URL on frontend but without invalid elements/attributes removed', 'amp' ),
1792
+ esc_html__( 'Debug', 'amp' )
1793
+ )
1794
+ ?>
1795
+ </span>
1796
+ </li>
1797
+ <?php endforeach; ?>
1798
+ </ul>
1799
+ </div>
1800
+ <?php
1801
+ }
1802
+
1803
+ /**
1804
+ * Get validation debug UR:.
1805
+ *
1806
+ * @param string $url URL to to validate and debug.
1807
+ * @return string Debug URL.
1808
+ */
1809
+ public static function get_debug_url( $url ) {
1810
+ return add_query_arg(
1811
+ array(
1812
+ self::VALIDATE_QUERY_VAR => 1,
1813
+ self::DEBUG_QUERY_VAR => 1,
1814
+ ),
1815
+ $url
1816
+ ) . '#development=1';
1817
+ }
1818
+
1819
+ /**
1820
+ * Gets the link to recheck the post for AMP validity.
1821
+ *
1822
+ * Appends a query var to $redirect_url.
1823
+ * On clicking the link, it checks if errors still exist for $post.
1824
+ *
1825
+ * @param WP_Post $post The post storing the validation error.
1826
+ * @param string $redirect_url The URL of the redirect.
1827
+ * @param string $recheck_url The URL to check. Optional.
1828
+ * @return string $link The link to recheck the post.
1829
+ */
1830
+ public static function get_recheck_link( $post, $redirect_url, $recheck_url = null ) {
1831
+ return sprintf(
1832
+ '<a href="%s" aria-label="%s">%s</a>',
1833
+ wp_nonce_url(
1834
+ add_query_arg(
1835
+ array(
1836
+ 'action' => self::RECHECK_ACTION,
1837
+ 'recheck_url' => $recheck_url,
1838
+ ),
1839
+ $redirect_url
1840
+ ),
1841
+ self::NONCE_ACTION . $post->ID
1842
+ ),
1843
+ esc_html__( 'Recheck the URL for AMP validity', 'amp' ),
1844
+ esc_html__( 'Recheck', 'amp' )
1845
+ );
1846
+ }
1847
+
1848
+ }
includes/widgets/class-amp-widget-archives.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Widget_Archives
4
+ *
5
+ * @since 0.7.0
6
+ * @package AMP
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Widget_Archives
11
+ *
12
+ * @since 0.7.0
13
+ * @package AMP
14
+ */
15
+ class AMP_Widget_Archives extends WP_Widget_Archives {
16
+
17
+ /**
18
+ * Echoes the markup of the widget.
19
+ *
20
+ * Mainly copied from WP_Widget_Archives::widget()
21
+ * Changes include:
22
+ * An id for the <form>.
23
+ * More escaping.
24
+ * The dropdown is now filtered with 'wp_dropdown_cats.'
25
+ * This enables adding an 'on' attribute, with the id of the form.
26
+ * So changing the dropdown value will redirect to the category page, with valid AMP.
27
+ *
28
+ * @since 0.7.0
29
+ *
30
+ * @param array $args Widget display data.
31
+ * @param array $instance Data for widget.
32
+ * @return void.
33
+ */
34
+ public function widget( $args, $instance ) {
35
+ if ( ! is_amp_endpoint() ) {
36
+ parent::widget( $args, $instance );
37
+ return;
38
+ }
39
+
40
+ $c = ! empty( $instance['count'] ) ? '1' : '0';
41
+ $d = ! empty( $instance['dropdown'] ) ? '1' : '0';
42
+
43
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
44
+ $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? __( 'Archives', 'default' ) : $instance['title'], $instance, $this->id_base );
45
+ echo wp_kses_post( $args['before_widget'] );
46
+ if ( $title ) :
47
+ echo wp_kses_post( $args['before_title'] . $title . $args['after_title'] );
48
+ endif;
49
+
50
+ if ( $d ) :
51
+ $dropdown_id = "{$this->id_base}-dropdown-{$this->number}";
52
+ ?>
53
+ <form action="<?php echo esc_url( home_url() ); ?>" method="get" target="_top">
54
+ <label class="screen-reader-text" for="<?php echo esc_attr( $dropdown_id ); ?>"><?php echo esc_html( $title ); ?></label>
55
+ <select id="<?php echo esc_attr( $dropdown_id ); ?>" name="archive-dropdown" on="change:AMP.navigateTo(url=event.value)">
56
+ <?php
57
+
58
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-archives.php */
59
+ $dropdown_args = apply_filters( 'widget_archives_dropdown_args', array(
60
+ 'type' => 'monthly',
61
+ 'format' => 'option',
62
+ 'show_post_count' => $c,
63
+ ) );
64
+
65
+ switch ( $dropdown_args['type'] ) {
66
+ case 'yearly':
67
+ $label = __( 'Select Year', 'default' );
68
+ break;
69
+ case 'monthly':
70
+ $label = __( 'Select Month', 'default' );
71
+ break;
72
+ case 'daily':
73
+ $label = __( 'Select Day', 'default' );
74
+ break;
75
+ case 'weekly':
76
+ $label = __( 'Select Week', 'default' );
77
+ break;
78
+ default:
79
+ $label = __( 'Select Post', 'default' );
80
+ break;
81
+ }
82
+ ?>
83
+ <option value=""><?php echo esc_attr( $label ); ?></option>
84
+ <?php wp_get_archives( $dropdown_args ); ?>
85
+ </select>
86
+ </form>
87
+ <?php else : ?>
88
+ <ul>
89
+ <?php
90
+
91
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-archives.php */
92
+ wp_get_archives( apply_filters( 'widget_archives_args', array(
93
+ 'type' => 'monthly',
94
+ 'show_post_count' => $c,
95
+ ) ) );
96
+ ?>
97
+ </ul>
98
+ <?php
99
+ endif;
100
+ echo wp_kses_post( $args['after_widget'] );
101
+ }
102
+
103
+ }
includes/widgets/class-amp-widget-categories.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Widget_Categories
4
+ *
5
+ * @since 0.7.0
6
+ * @package AMP
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Widget_Categories
11
+ *
12
+ * @since 0.7.0
13
+ * @package AMP
14
+ */
15
+ class AMP_Widget_Categories extends WP_Widget_Categories {
16
+
17
+ /**
18
+ * Echoes the markup of the widget.
19
+ *
20
+ * Mainly copied from WP_Widget_Categories::widget()
21
+ * There's now an id for the <form>.
22
+ * And the dropdown is now filtered with 'wp_dropdown_cats.'
23
+ * This enables adding an 'on' attribute, with the id of the form.
24
+ * So changing the dropdown value will redirect to the category page, with valid AMP.
25
+ *
26
+ * @since 0.7.0
27
+ *
28
+ * @param array $args Widget display data.
29
+ * @param array $instance Data for widget.
30
+ * @return void
31
+ */
32
+ public function widget( $args, $instance ) {
33
+ if ( ! is_amp_endpoint() ) {
34
+ parent::widget( $args, $instance );
35
+ return;
36
+ }
37
+
38
+ static $first_dropdown = true;
39
+ $title = ! empty( $instance['title'] ) ? $instance['title'] : __( 'Categories', 'default' );
40
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
41
+ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
42
+ $c = ! empty( $instance['count'] ) ? '1' : '0';
43
+ $h = ! empty( $instance['hierarchical'] ) ? '1' : '0';
44
+ $d = ! empty( $instance['dropdown'] ) ? '1' : '0';
45
+ echo wp_kses_post( $args['before_widget'] );
46
+ if ( $title ) {
47
+ echo wp_kses_post( $args['before_title'] . $title . $args['after_title'] );
48
+ }
49
+ $cat_args = array(
50
+ 'orderby' => 'name',
51
+ 'show_count' => $c,
52
+ 'hierarchical' => $h,
53
+ );
54
+ if ( $d ) :
55
+ $form_id = sprintf( 'widget-categories-dropdown-%d', $this->number );
56
+ printf( '<form action="%s" method="get" target="_top" id="%s">', esc_url( home_url() ), esc_attr( $form_id ) );
57
+ $dropdown_id = ( $first_dropdown ) ? 'cat' : "{$this->id_base}-dropdown-{$this->number}";
58
+ $first_dropdown = false;
59
+ echo '<label class="screen-reader-text" for="' . esc_attr( $dropdown_id ) . '">' . esc_html( $title ) . '</label>';
60
+ $cat_args['show_option_none'] = __( 'Select Category', 'default' );
61
+ $cat_args['id'] = $dropdown_id;
62
+
63
+ $dropdown = wp_dropdown_categories( array_merge(
64
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-categories.php */
65
+ apply_filters( 'widget_categories_dropdown_args', $cat_args, $instance ),
66
+ array( 'echo' => false )
67
+ ) );
68
+ $dropdown = preg_replace(
69
+ '/(?<=<select\b)/',
70
+ sprintf( '<select on="change:%s.submit"', esc_attr( $form_id ) ),
71
+ $dropdown,
72
+ 1
73
+ );
74
+ echo $dropdown; // WPCS: XSS OK.
75
+ echo '</form>';
76
+ else :
77
+ ?>
78
+ <ul>
79
+ <?php
80
+ $cat_args['title_li'] = '';
81
+
82
+ /** This filter is documented in wp-includes/widgets/class-wp-widget-categories.php */
83
+ wp_list_categories( apply_filters( 'widget_categories_args', $cat_args, $instance ) );
84
+ ?>
85
+ </ul>
86
+ <?php
87
+ endif;
88
+ echo wp_kses_post( $args['after_widget'] );
89
+ }
90
+
91
+ }
includes/widgets/class-amp-widget-media-video.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Widget_Media_Video
4
+ *
5
+ * @since 0.7.0
6
+ * @package AMP
7
+ */
8
+
9
+ if ( class_exists( 'WP_Widget_Media_Video' ) ) {
10
+ /**
11
+ * Class AMP_Widget_Media_Video
12
+ *
13
+ * @since 0.7.0
14
+ * @package AMP
15
+ */
16
+ class AMP_Widget_Media_Video extends WP_Widget_Media_Video {
17
+
18
+ /**
19
+ * Overrides the parent callback that strips width and height values.
20
+ *
21
+ * @param string $html Video shortcode HTML output.
22
+ * @return string HTML Output.
23
+ */
24
+ public function inject_video_max_width_style( $html ) {
25
+ if ( is_amp_endpoint() ) {
26
+ return $html;
27
+ }
28
+ return parent::inject_video_max_width_style( $html );
29
+ }
30
+
31
+ }
32
+
33
+ }
includes/widgets/class-amp-widget-recent-comments.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Widget_Recent_Comments
4
+ *
5
+ * @since 0.7.0
6
+ * @package AMP
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Widget_Recent_Comments
11
+ *
12
+ * @since 0.7.0
13
+ * @package AMP
14
+ */
15
+ class AMP_Widget_Recent_Comments extends WP_Widget_Recent_Comments {
16
+
17
+ /**
18
+ * Instantiates the widget, and prevents inline styling.
19
+ *
20
+ * @since 0.7.0
21
+ */
22
+ public function __construct() {
23
+ parent::__construct();
24
+ add_filter( 'wp_head', array( $this, 'remove_head_style_in_amp' ), 0 );
25
+ }
26
+
27
+ /**
28
+ * Prevent recent comments widget style from printing in AMP,
29
+ *
30
+ * @since 0.7.0
31
+ */
32
+ public function remove_head_style_in_amp() {
33
+ if ( is_amp_endpoint() ) {
34
+ add_filter( 'show_recent_comments_widget_style', '__return_false' );
35
+ }
36
+ }
37
+
38
+ }
includes/widgets/class-amp-widget-text.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Widget_Text
4
+ *
5
+ * @since 0.7.0
6
+ * @package AMP
7
+ */
8
+
9
+ if ( class_exists( 'WP_Widget_Text' ) ) {
10
+ /**
11
+ * Class AMP_Widget_Text
12
+ *
13
+ * @since 0.7.0
14
+ * @package AMP
15
+ */
16
+ class AMP_Widget_Text extends WP_Widget_Text {
17
+
18
+ /**
19
+ * Overrides the parent callback that strips width and height attributes.
20
+ *
21
+ * @param array $matches The matches returned from preg_replace_callback().
22
+ * @return string $html The markup, unaltered.
23
+ */
24
+ public function inject_video_max_width_style( $matches ) {
25
+ if ( is_amp_endpoint() ) {
26
+ return $matches[0];
27
+ }
28
+ return parent::inject_video_max_width_style( $matches );
29
+ }
30
+
31
+ }
32
+
33
+ }
readme.txt CHANGED
@@ -3,22 +3,28 @@ Contributors: batmoo, joen, automattic, potatomaster, albertomedina, google, xwp
3
  Tags: amp, mobile
4
  Requires at least: 4.7
5
  Tested up to: 4.9
6
- Stable tag: 0.6.2
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
- Requires PHP: 5.2
10
 
11
  Enable Accelerated Mobile Pages (AMP) on your WordPress site.
12
 
13
  == Description ==
14
 
15
- This plugin adds support for the [Accelerated Mobile Pages](https://www.ampproject.org) (AMP) Project, which is an open source initiative that aims to provide mobile optimized content that can load instantly everywhere.
16
 
17
- With the plugin active, all posts on your site will have dynamically generated AMP-compatible versions, accessible by appending `/amp/` to the end your post URLs. For example, if your post URL is `http://example.com/2016/01/01/amp-on/`, you can access the AMP version at `http://example.com/2016/01/01/amp-on/amp/`. If you do not have [pretty permalinks](https://codex.wordpress.org/Using_Permalinks#mod_rewrite:_.22Pretty_Permalinks.22) enabled, you can do the same thing by appending `?amp=1`, i.e. `http://example.com/?p=123&amp=1`
18
 
19
- Note #1: homepage, the blog index, and archives are not currently supported.
20
 
21
- Note #2: this plugin only creates AMP content but does not automatically display it to your users when they visit from a mobile device. That is handled by AMP consumers such as Google Search. For more details, see the [AMP Project FAQ](https://www.ampproject.org/docs/support/faqs.html).
 
 
 
 
 
 
22
 
23
  Follow along with or [contribute](https://github.com/Automattic/amp-wp/blob/develop/contributing.md) to the development of this plugin [on GitHub](https://github.com/Automattic/amp-wp). For more information on the plugin, how the plugin works and how to configure and extend it, please see the [project wiki](https://github.com/Automattic/amp-wp/wiki).
24
 
@@ -38,14 +44,51 @@ Follow along with or [contribute](https://github.com/Automattic/amp-wp/blob/deve
38
 
39
  == Changelog ==
40
 
41
- = 0.6.2 (2018-02-12) =
42
-
43
- - Reduce frequency of flushing rewrite rules and harden, use escaped translation functions, and make minor changes to improve logic/style. See [#953](https://github.com/Automattic/amp-wp/pull/953). Props philipjohn, westonruter.
44
- - Fix AMP preview icon in Firefox. See [#920](https://github.com/Automattic/amp-wp/pull/920). Props zigasancin.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  = 0.6.1 (2018-02-09) =
47
 
48
- Bump version to re-release to ensure temporarily-broken 0.6.0 ZIP build is permanently fixed, without requiring a site to re-install the plugin.
49
 
50
  = 0.6.0 (2018-01-23) =
51
 
@@ -56,7 +99,7 @@ Bump version to re-release to ensure temporarily-broken 0.6.0 ZIP build is perma
56
  - Add generator meta tag for AMP. See [#810](https://github.com/Automattic/amp-wp/pull/810). Props vaporwavre.
57
  - Add code quality checking via phpcs, eslint, jscs, and jshint. See [#795](https://github.com/Automattic/amp-wp/pull/795). Props westonruter.
58
  - Add autoloader to reduce complexity. See [#828](https://github.com/Automattic/amp-wp/pull/828). Props mikeschinkel, westonruter, ThierryA.
59
- - Fix Polldaddy amd SoundCloud embeds. Add vanilla WordPress "embed" test page. A new `bin/create-embed-test-post.php` wp-cli script is introduced. See [#829](https://github.com/Automattic/amp-wp/pull/829). Props kienstra, westonruter, ThierryA.
60
  - Merge AMP Customizer into main Customizer. See [#819](https://github.com/Automattic/amp-wp/pull/819). Props kaitnyl, westonruter.
61
  - Update AMP HTML tags and attributes. A new `bin/amphtml-update.sh` bash script is introduced. Fixes Playbuzz. See [#823](https://github.com/Automattic/amp-wp/pull/823). Props kienstra, ThierryA, westonruter.
62
  - Remove erroneous hash from id on amp-wp-header. See [#853](https://github.com/Automattic/amp-wp/pull/853). Props eshannon3.
3
  Tags: amp, mobile
4
  Requires at least: 4.7
5
  Tested up to: 4.9
6
+ Stable tag: 0.7.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
+ Requires PHP: 5.3
10
 
11
  Enable Accelerated Mobile Pages (AMP) on your WordPress site.
12
 
13
  == Description ==
14
 
15
+ Bring the speed and features of the open source [AMP project](https://www.ampproject.org/) to your site, the WordPress way.
16
 
17
+ With the plugin active, all posts on your site will have AMP-compatible versions, accessible by appending `/amp/` to the end your post URLs. For example, if your post URL is `http://example.com/2016/01/01/amp-on/`, you can access the AMP version at `http://example.com/2016/01/01/amp-on/amp/`. If you do not have [pretty permalinks](https://codex.wordpress.org/Using_Permalinks#mod_rewrite:_.22Pretty_Permalinks.22) enabled, you can do the same thing by appending `?amp=1`, i.e. `http://example.com/?p=123&amp=1`
18
 
19
+ Also, your pages and custom post types can have AMP versions. Simply check their boxes on the 'AMP Settings' page in `/wp-admin`.
20
 
21
+ Your entire site can render as "Native AMP" if your theme calls `add_theme_support( 'amp' )`. There will only be one version of each URL: the AMP version. There won't be separate URLs with `/amp` or `?amp` appended. See this [wiki page](https://github.com/Automattic/amp-wp/wiki/Adding-Theme-Support#native-amp) for details and restrictions.
22
+
23
+ Your theme can also use [Paired Mode](https://github.com/Automattic/amp-wp/wiki/Adding-Theme-Support#paired-mode), with your own custom templates for the AMP URLs.
24
+
25
+ "Native AMP" and "Paired Mode" add full support for commenting and widgets.
26
+
27
+ If your theme doesn't support `'amp'`, this will use basic legacy post templates for AMP consumers like Google Search and Twitter. And when visiting the site, the AMP content won't normally appear without appending strings to the URL like `/amp` or `?amp`.
28
 
29
  Follow along with or [contribute](https://github.com/Automattic/amp-wp/blob/develop/contributing.md) to the development of this plugin [on GitHub](https://github.com/Automattic/amp-wp). For more information on the plugin, how the plugin works and how to configure and extend it, please see the [project wiki](https://github.com/Automattic/amp-wp/wiki).
30
 
44
 
45
  == Changelog ==
46
 
47
+ = 0.7.0 (2018-05-03) =
48
+
49
+ - Render an entire site as "Native AMP" if the theme calls `add_theme_support( 'amp' )`. See [#857](https://github.com/Automattic/amp-wp/pull/857), [#852](https://github.com/Automattic/amp-wp/pull/852), [#865](https://github.com/Automattic/amp-wp/pull/865), [#888](https://github.com/Automattic/amp-wp/pull/888). Props westonruter, kaitnyl, ThierryA.
50
+ - Use the AMP spec to automatically discover the required AMP component scripts to include on the page while post-processing. See [#882](https://github.com/Automattic/amp-wp/pull/882), [#885](https://github.com/Automattic/amp-wp/pull/885). Props westonruter.
51
+ - Automatically concatenate stylesheets from `style` tags with loaded stylesheets from `link` tags combined in one `style[amp-custom]`. See [#887](https://github.com/Automattic/amp-wp/pull/887), [#890](https://github.com/Automattic/amp-wp/pull/890), [#935](https://github.com/Automattic/amp-wp/pull/935). Props westonruter.
52
+ - Update serialization to use HTML instead of XML; update minimum version of PHP fro, 5.2 to 5.3. See [#891](https://github.com/Automattic/amp-wp/pull/891).
53
+ - Add support for widgets. See [#870](https://github.com/Automattic/amp-wp/pull/870). Props kienstra.
54
+ - Add support for forms. See [#907](https://github.com/Automattic/amp-wp/pull/907), [#923](https://github.com/Automattic/amp-wp/pull/923). Props DavidCramer.
55
+ - Use "Paired Mode" if the theme calls `add_theme_support( 'amp' )` and passes a `'template_dir'` value for the AMP templates. See [#856](https://github.com/Automattic/amp-wp/pull/856), [#877](https://github.com/Automattic/amp-wp/pull/877). Props westonruter, kaitnyl.
56
+ - Add AMP implementations of audio/video playlists. See [#954](https://github.com/Automattic/amp-wp/pull/954). Props kienstra.
57
+ - Allow full Customization when the theme supports `'amp'`. See [#952](https://github.com/Automattic/amp-wp/pull/952). Props westonruter.
58
+ - Add support for all default WordPress widgets. See [#921](https://github.com/Automattic/amp-wp/pull/921), [#917](https://github.com/Automattic/amp-wp/pull/917). Props kienstra, westonruter.
59
+ - Add support for more default embeds: Issuu, Post, Meetup, Reddit, Screencast, Tumblr, and WordPress Plugin Directory. See [#889](https://github.com/Automattic/amp-wp/pull/889). Props kaitnyl.
60
+ - Allow native WordPress commenting, in fully valid AMP. See [#1024](https://github.com/Automattic/amp-wp/pull/1024), [#1029](https://github.com/Automattic/amp-wp/pull/1029), [#871](https://github.com/Automattic/amp-wp/pull/871), [#909](https://github.com/Automattic/amp-wp/pull/909). Props DavidCramer, westonruter.
61
+ - Add a UI for displaying validation errors, including invalid tags and attributes, with tracing for the source for each error according to which theme/plugin's shortcode, widget, or other hook is responsible. Includes debug mode to suspend sanitizer. See [#971](https://github.com/Automattic/amp-wp/pull/971), [#1012](https://github.com/Automattic/amp-wp/pull/1012), [#1016](https://github.com/Automattic/amp-wp/pull/1016). Props westonruter, kienstra.
62
+ - On activating a plugin, validate a front-end page and display a notice if there were errors. See [#971](https://github.com/Automattic/amp-wp/pull/971). Props westonruter, kienstra.
63
+ - Creation of AMP-related notifications, on entering invalid content in the 'classic' editor. See [#912](https://github.com/Automattic/amp-wp/pull/912/). Props kienstra, westonruter, ThierryA.
64
+ - Optionally use `<amp-live-list>` to display comments, avoiding full-page refreshes on adding comments. And enable making requests for an `<amp-live-list>`, like for displaying posts. See [#1029](https://github.com/Automattic/amp-wp/pull/1029), [#915](https://github.com/Automattic/amp-wp/pull/915). Props DavidCramer, westonruter.
65
+ - Support `<amp-bind>`, enabling more dynamic elements. See [#895](https://github.com/Automattic/amp-wp/pull/895). Props westonruter.
66
+ - Add output buffering, ensuring the entire page is valid AMP. See [#929](https://github.com/Automattic/amp-wp/pull/929), [#857](https://github.com/Automattic/amp-wp/pull/857), [#931](https://github.com/Automattic/amp-wp/pull/931). Props westonruter, ThierryA.
67
+ - Add validation of host names in URLs. See [#983](https://github.com/Automattic/amp-wp/pull/983). Props rubengonzalezmrf.
68
+ - Add WP-CLI scripts to test AMP support of comments and widgets. See [#924](https://github.com/Automattic/amp-wp/pull/924), [#859](https://github.com/Automattic/amp-wp/pull/859). Props DavidCramer, kienstra.
69
+ - Improve test coverage, including for `AMP_Theme_Support`. See [#1034](https://github.com/Automattic/amp-wp/pull/1034). Props DavidCramer, kienstra.
70
+ - Update the generated sanitizer file to the AMP spec, and simplify the file that generates it. See [#929](https://github.com/Automattic/amp-wp/pull/929), [#926](https://github.com/Automattic/amp-wp/pull/926). Props westonruter.
71
+ - Several sanitizer updates, including for styles, and preventing valid tags from being removed. See [#935](https://github.com/Automattic/amp-wp/pull/935), [#944](https://github.com/Automattic/amp-wp/pull/944), [#952](https://github.com/Automattic/amp-wp/pull/952). Props westonruter, davisshaver.
72
+ - Improve sanitization of `<amp-img>`, `<amp-video>`, and `<amp-iframe>`. See [#937](https://github.com/Automattic/amp-wp/pull/937), [#1054](https://github.com/Automattic/amp-wp/pull/1054). Props kienstra, amedina.
73
+ - Fix an issue where the JSON inside `<script type="application/json">` was wrapped with CDATA. See [#891](https://github.com/Automattic/amp-wp/pull/891). Props westonruter.
74
+ - Allow use of AMP components outside of AMP documents, including in [PWA](https://developers.google.com/web/progressive-web-apps/). See [#1013](https://github.com/Automattic/amp-wp/pull/1013). Props westonruter.
75
+ - Access the AMP query var with `amp_get_slug()`, instead of `AMP_QUERY_VAR`. See [#986](https://github.com/Automattic/amp-wp/pull/986). Props westonruter, mjangda.
76
+ - Update build scripts, including PHP versions in `.travis.yml`. See [#1058](https://github.com/Automattic/amp-wp/pull/1058/), [#949](https://github.com/Automattic/amp-wp/pull/949). Props westonruter.
77
+ - Prevent New Relic script from being injected in AMP responses. See [#932](https://github.com/Automattic/amp-wp/pull/932). Props westonruter.
78
+ - Fix handling of 0 and empty height/width attributes. See [#979](https://github.com/Automattic/amp-wp/pull/979). Props davisshaver.
79
+
80
+ For a full list of the closed issues and merged pull requests in this release, see the [0.7 milestone](https://github.com/Automattic/amp-wp/milestone/6?closed=1).
81
+
82
+ Contributors in this release, including design, development, testing, and project management: Adam Silverstein (adamsilverstein), Alberto Medina (amedina), Christian Chung (christianc1), Claudio Sossi, David Cramer (DavidCramer), Davis Shaver (davisshaver), Douglas Paul (douglyuckling), Jason Johnston (jhnstn), Joshua Wold (jwold), Kaitlyn (kaitnyl), Leo Postovoit (postphotos), Mackenzie Hartung (MackenzieHartung), Maxim Siebert (MaximSiebert), Mike Crantea (mehigh), Mohammad Jangda (mjangda), Oscar Sanchez (oscarssanchez), Philip John (philipjohn), Piotr Delawski (delawski), Renato Alves (renatonascalves), Rubén (rubengonzalezmrf), Ryan Kienstra (kienstra), Thierry Muller (ThierryA), vortfu, Weston Ruter (westonruter), Ziga Sancin (zigasancin).
83
+
84
+ = 0.6.2 (2018-02-28) =
85
+
86
+ * Improve logic and use of escaping; limit flushing rewrite rules to only when supported_post_types change. See [#953](https://github.com/Automattic/amp-wp/pull/953). Props philipjohn, westonruter.
87
+ * Fix AMP preview icon in Firefox. See [#920](https://github.com/Automattic/amp-wp/pull/920). Props zigasancin.
88
 
89
  = 0.6.1 (2018-02-09) =
90
 
91
+ Version bump to re-release plugin in order to deal with missing file in 0.6.0 release package that caused fatal error.
92
 
93
  = 0.6.0 (2018-01-23) =
94
 
99
  - Add generator meta tag for AMP. See [#810](https://github.com/Automattic/amp-wp/pull/810). Props vaporwavre.
100
  - Add code quality checking via phpcs, eslint, jscs, and jshint. See [#795](https://github.com/Automattic/amp-wp/pull/795). Props westonruter.
101
  - Add autoloader to reduce complexity. See [#828](https://github.com/Automattic/amp-wp/pull/828). Props mikeschinkel, westonruter, ThierryA.
102
+ - Fix Polldaddy amd SoundCloud embeds. Add vanilla WordPress "embed" test page. A new `bin/create-embed-test-post.php` WP-CLI script is introduced. See [#829](https://github.com/Automattic/amp-wp/pull/829). Props kienstra, westonruter, ThierryA.
103
  - Merge AMP Customizer into main Customizer. See [#819](https://github.com/Automattic/amp-wp/pull/819). Props kaitnyl, westonruter.
104
  - Update AMP HTML tags and attributes. A new `bin/amphtml-update.sh` bash script is introduced. Fixes Playbuzz. See [#823](https://github.com/Automattic/amp-wp/pull/823). Props kienstra, ThierryA, westonruter.
105
  - Remove erroneous hash from id on amp-wp-header. See [#853](https://github.com/Automattic/amp-wp/pull/853). Props eshannon3.
wpcom-helper.php CHANGED
@@ -1,17 +1,22 @@
1
  <?php
 
 
 
 
 
 
2
 
3
- // WPCOM-specific things
4
  add_action( 'pre_amp_render_post', 'jetpack_amp_disable_the_content_filters' );
5
 
6
- // Disable admin menu
7
  add_filter( 'amp_options_menu_is_enabled', '__return_false', 9999 );
8
 
9
  /**
10
  * Disable the_content filters for Jetpack.
11
  *
12
- * @param int $post_id Post ID.
13
  */
14
- function jetpack_amp_disable_the_content_filters( $post_id ) {
15
  add_filter( 'post_flair_disable', '__return_true', 99 );
16
  add_filter( 'videopress_show_2015_player', '__return_true' );
17
  add_filter( 'protected_embeds_use_form_post', '__return_false' );
@@ -24,7 +29,12 @@ function jetpack_amp_disable_the_content_filters( $post_id ) {
24
 
25
  add_action( 'amp_post_template_head', 'jetpack_amp_add_og_tags' );
26
 
27
- function jetpack_amp_add_og_tags( $amp_template ) {
 
 
 
 
 
28
  if ( function_exists( 'jetpack_og_tags' ) ) {
29
  jetpack_og_tags();
30
  }
@@ -32,9 +42,18 @@ function jetpack_amp_add_og_tags( $amp_template ) {
32
 
33
  add_filter( 'amp_post_template_metadata', 'jetpack_amp_post_template_metadata', 10, 2 );
34
 
 
 
 
 
 
 
 
 
 
35
  function jetpack_amp_post_template_metadata( $metadata, $post ) {
36
  if ( isset( $metadata['publisher'] ) && ! isset( $metadata['publisher']['logo'] ) ) {
37
- $metadata = wpcom_amp_add_blavatar_to_metadata( $metadata, $post );
38
  }
39
 
40
  if ( ! isset( $metadata['image'] ) ) {
@@ -44,22 +63,40 @@ function jetpack_amp_post_template_metadata( $metadata, $post ) {
44
  return $metadata;
45
  }
46
 
47
- function wpcom_amp_add_blavatar_to_metadata( $metadata, $post ) {
 
 
 
 
 
 
 
 
48
  if ( ! function_exists( 'blavatar_domain' ) ) {
49
  return $metadata;
50
  }
51
 
52
  $size = 60;
 
53
  $metadata['publisher']['logo'] = array(
54
- '@type' => 'ImageObject',
55
- 'url' => blavatar_url( blavatar_domain( site_url() ), 'img', $size, staticize_subdomain( 'https://wordpress.com/i/favicons/apple-touch-icon-60x60.png' ) ),
56
- 'width' => $size,
57
  'height' => $size,
58
  );
59
 
60
  return $metadata;
61
  }
62
 
 
 
 
 
 
 
 
 
 
63
  function wpcom_amp_add_image_to_metadata( $metadata, $post ) {
64
  if ( ! class_exists( 'Jetpack_PostImages' ) ) {
65
  return wpcom_amp_add_fallback_image_to_metadata( $metadata );
@@ -67,10 +104,10 @@ function wpcom_amp_add_image_to_metadata( $metadata, $post ) {
67
 
68
  $image = Jetpack_PostImages::get_image( $post->ID, array(
69
  'fallback_to_avatars' => true,
70
- 'avatar_size' => 200,
71
- // AMP already attempts these
72
- 'from_thumbnail' => false,
73
- 'from_attachment' => false,
74
  ) );
75
 
76
  if ( empty( $image ) ) {
@@ -80,30 +117,37 @@ function wpcom_amp_add_image_to_metadata( $metadata, $post ) {
80
  if ( ! isset( $image['src_width'] ) ) {
81
  $dimensions = wpcom_amp_extract_image_dimensions_from_getimagesize( array(
82
  $image['src'] => false,
83
- )
84
- );
85
 
86
  if ( false !== $dimensions[ $image['src'] ] ) {
87
- $image['src_width'] = $dimensions['width'];
88
  $image['src_height'] = $dimensions['height'];
89
  }
90
  }
91
 
92
  $metadata['image'] = array(
93
- '@type' => 'ImageObject',
94
- 'url' => $image['src'],
95
- 'width' => $image['src_width'],
96
  'height' => $image['src_height'],
97
  );
98
 
99
  return $metadata;
100
  }
101
 
 
 
 
 
 
 
 
 
102
  function wpcom_amp_add_fallback_image_to_metadata( $metadata ) {
103
  $metadata['image'] = array(
104
- '@type' => 'ImageObject',
105
- 'url' => staticize_subdomain( 'https://wordpress.com/i/blank.jpg' ),
106
- 'width' => 200,
107
  'height' => 200,
108
  );
109
 
@@ -111,36 +155,50 @@ function wpcom_amp_add_fallback_image_to_metadata( $metadata ) {
111
  }
112
 
113
  add_action( 'amp_extract_image_dimensions_batch_callbacks_registered', 'wpcom_amp_extract_image_dimensions_batch_add_custom_callbacks' );
 
 
 
 
 
 
114
  function wpcom_amp_extract_image_dimensions_batch_add_custom_callbacks() {
115
  // If images are being served from Photon or WP.com files, try extracting the size using querystring.
116
  add_action( 'amp_extract_image_dimensions_batch', 'wpcom_amp_extract_image_dimensions_from_querystring', 9, 1 ); // Hook in before the default extractors.
117
 
118
  // Uses a special wpcom lib (wpcom_getimagesize) to extract dimensions as a last resort if we weren't able to figure them out.
119
- add_action( 'amp_extract_image_dimensions_batch', 'wpcom_amp_extract_image_dimensions_from_getimagesize', 99, 1 ); // Our last resort, so run late
120
 
121
  // The wpcom override obviates this one, so take it out.
122
- remove_filter( 'amp_extract_image_dimensions_batch', array( 'AMP_Image_Dimension_Extractor', 'extract_by_downloading_images' ), 999, 1 );
123
  }
124
 
 
 
 
 
 
 
 
 
125
  function wpcom_amp_extract_image_dimensions_from_querystring( $dimensions ) {
126
- foreach ( $dimensions as $url => $value ) {
127
 
128
  if ( is_array( $value ) ) {
129
  continue;
130
  }
131
 
132
- $host = parse_url( $url, PHP_URL_HOST );
133
- if ( ! wp_endswith( $host, '.wp.com' ) || ! wp_endswith( $host, '.files.wordpress.com' ) ) {
134
  continue;
135
  }
136
 
137
- parse_str( parse_url( $url, PHP_URL_QUERY ), $query );
138
  $w = isset( $query['w'] ) ? absint( $query['w'] ) : false;
139
  $h = isset( $query['h'] ) ? absint( $query['h'] ) : false;
140
 
141
  if ( false !== $w && false !== $h ) {
142
  $dimensions[ $url ] = array(
143
- 'width' => $w,
144
  'height' => $h,
145
  );
146
  }
@@ -148,6 +206,14 @@ function wpcom_amp_extract_image_dimensions_from_querystring( $dimensions ) {
148
  return $dimensions;
149
  }
150
 
 
 
 
 
 
 
 
 
151
  function wpcom_amp_extract_image_dimensions_from_getimagesize( $dimensions ) {
152
  if ( ! function_exists( 'require_lib' ) ) {
153
  return $dimensions;
@@ -161,7 +227,7 @@ function wpcom_amp_extract_image_dimensions_from_getimagesize( $dimensions ) {
161
  $result = wpcom_getimagesize( $url );
162
  if ( is_array( $result ) ) {
163
  $dimensions[ $url ] = array(
164
- 'width' => $result[0],
165
  'height' => $result[1],
166
  );
167
  }
1
  <?php
2
+ /**
3
+ * WPCOM-specific things.
4
+ *
5
+ * @todo Move this into Jetpack. See https://github.com/Automattic/amp-wp/issues/1021
6
+ * @package AMP
7
+ */
8
 
 
9
  add_action( 'pre_amp_render_post', 'jetpack_amp_disable_the_content_filters' );
10
 
11
+ // Disable admin menu.
12
  add_filter( 'amp_options_menu_is_enabled', '__return_false', 9999 );
13
 
14
  /**
15
  * Disable the_content filters for Jetpack.
16
  *
17
+ * @since 0.3
18
  */
19
+ function jetpack_amp_disable_the_content_filters() {
20
  add_filter( 'post_flair_disable', '__return_true', 99 );
21
  add_filter( 'videopress_show_2015_player', '__return_true' );
22
  add_filter( 'protected_embeds_use_form_post', '__return_false' );
29
 
30
  add_action( 'amp_post_template_head', 'jetpack_amp_add_og_tags' );
31
 
32
+ /**
33
+ * Add Open Graph tags.
34
+ *
35
+ * @since 0.3
36
+ */
37
+ function jetpack_amp_add_og_tags() {
38
  if ( function_exists( 'jetpack_og_tags' ) ) {
39
  jetpack_og_tags();
40
  }
42
 
43
  add_filter( 'amp_post_template_metadata', 'jetpack_amp_post_template_metadata', 10, 2 );
44
 
45
+ /**
46
+ * Add publisher and image metadata.
47
+ *
48
+ * @since 0.3
49
+ *
50
+ * @param array $metadata Metadata array.
51
+ * @param WP_Post $post Post.
52
+ * @return array Modified metadata array.
53
+ */
54
  function jetpack_amp_post_template_metadata( $metadata, $post ) {
55
  if ( isset( $metadata['publisher'] ) && ! isset( $metadata['publisher']['logo'] ) ) {
56
+ $metadata = wpcom_amp_add_blavatar_to_metadata( $metadata );
57
  }
58
 
59
  if ( ! isset( $metadata['image'] ) ) {
63
  return $metadata;
64
  }
65
 
66
+ /**
67
+ * Add blavatar to metadata.
68
+ *
69
+ * @since 0.3
70
+ *
71
+ * @param array $metadata Metadata.
72
+ * @return array Metadata.
73
+ */
74
+ function wpcom_amp_add_blavatar_to_metadata( $metadata ) {
75
  if ( ! function_exists( 'blavatar_domain' ) ) {
76
  return $metadata;
77
  }
78
 
79
  $size = 60;
80
+
81
  $metadata['publisher']['logo'] = array(
82
+ '@type' => 'ImageObject',
83
+ 'url' => blavatar_url( blavatar_domain( site_url() ), 'img', $size, staticize_subdomain( 'https://wordpress.com/i/favicons/apple-touch-icon-60x60.png' ) ),
84
+ 'width' => $size,
85
  'height' => $size,
86
  );
87
 
88
  return $metadata;
89
  }
90
 
91
+ /**
92
+ * Add image to metadata.
93
+ *
94
+ * @since 0.3.2
95
+ *
96
+ * @param array $metadata Metadata.
97
+ * @param WP_Post $post Post.
98
+ * @return array Metadata.
99
+ */
100
  function wpcom_amp_add_image_to_metadata( $metadata, $post ) {
101
  if ( ! class_exists( 'Jetpack_PostImages' ) ) {
102
  return wpcom_amp_add_fallback_image_to_metadata( $metadata );
104
 
105
  $image = Jetpack_PostImages::get_image( $post->ID, array(
106
  'fallback_to_avatars' => true,
107
+ 'avatar_size' => 200,
108
+ // AMP already attempts these.
109
+ 'from_thumbnail' => false,
110
+ 'from_attachment' => false,
111
  ) );
112
 
113
  if ( empty( $image ) ) {
117
  if ( ! isset( $image['src_width'] ) ) {
118
  $dimensions = wpcom_amp_extract_image_dimensions_from_getimagesize( array(
119
  $image['src'] => false,
120
+ ) );
 
121
 
122
  if ( false !== $dimensions[ $image['src'] ] ) {
123
+ $image['src_width'] = $dimensions['width'];
124
  $image['src_height'] = $dimensions['height'];
125
  }
126
  }
127
 
128
  $metadata['image'] = array(
129
+ '@type' => 'ImageObject',
130
+ 'url' => $image['src'],
131
+ 'width' => $image['src_width'],
132
  'height' => $image['src_height'],
133
  );
134
 
135
  return $metadata;
136
  }
137
 
138
+ /**
139
+ * Add fallback image to metadata.
140
+ *
141
+ * @since 0.3.2
142
+ *
143
+ * @param array $metadata Metadata.
144
+ * @return array Metadata.
145
+ */
146
  function wpcom_amp_add_fallback_image_to_metadata( $metadata ) {
147
  $metadata['image'] = array(
148
+ '@type' => 'ImageObject',
149
+ 'url' => staticize_subdomain( 'https://wordpress.com/i/blank.jpg' ),
150
+ 'width' => 200,
151
  'height' => 200,
152
  );
153
 
155
  }
156
 
157
  add_action( 'amp_extract_image_dimensions_batch_callbacks_registered', 'wpcom_amp_extract_image_dimensions_batch_add_custom_callbacks' );
158
+
159
+ /**
160
+ * Add hooks to extract image dimensions.
161
+ *
162
+ * @since 0.5
163
+ */
164
  function wpcom_amp_extract_image_dimensions_batch_add_custom_callbacks() {
165
  // If images are being served from Photon or WP.com files, try extracting the size using querystring.
166
  add_action( 'amp_extract_image_dimensions_batch', 'wpcom_amp_extract_image_dimensions_from_querystring', 9, 1 ); // Hook in before the default extractors.
167
 
168
  // Uses a special wpcom lib (wpcom_getimagesize) to extract dimensions as a last resort if we weren't able to figure them out.
169
+ add_action( 'amp_extract_image_dimensions_batch', 'wpcom_amp_extract_image_dimensions_from_getimagesize', 99, 1 ); // Our last resort, so run late.
170
 
171
  // The wpcom override obviates this one, so take it out.
172
+ remove_filter( 'amp_extract_image_dimensions_batch', array( 'AMP_Image_Dimension_Extractor', 'extract_by_downloading_images' ), 999 );
173
  }
174
 
175
+ /**
176
+ * Extract image dimensions from query string.
177
+ *
178
+ * @since 0.5
179
+ *
180
+ * @param array $dimensions Dimensions.
181
+ * @return array Dimensions.
182
+ */
183
  function wpcom_amp_extract_image_dimensions_from_querystring( $dimensions ) {
184
+ foreach ( $dimensions as $url => $value ) {
185
 
186
  if ( is_array( $value ) ) {
187
  continue;
188
  }
189
 
190
+ $host = wp_parse_url( $url, PHP_URL_HOST );
191
+ if ( ! wp_endswith( $host, '.wp.com' ) && ! wp_endswith( $host, '.files.wordpress.com' ) ) {
192
  continue;
193
  }
194
 
195
+ parse_str( wp_parse_url( $url, PHP_URL_QUERY ), $query );
196
  $w = isset( $query['w'] ) ? absint( $query['w'] ) : false;
197
  $h = isset( $query['h'] ) ? absint( $query['h'] ) : false;
198
 
199
  if ( false !== $w && false !== $h ) {
200
  $dimensions[ $url ] = array(
201
+ 'width' => $w,
202
  'height' => $h,
203
  );
204
  }
206
  return $dimensions;
207
  }
208
 
209
+ /**
210
+ * Extract image dimensions via wpcom/imagesize.
211
+ *
212
+ * @since 0.5
213
+ *
214
+ * @param array $dimensions Dimensions.
215
+ * @return array Dimensions.
216
+ */
217
  function wpcom_amp_extract_image_dimensions_from_getimagesize( $dimensions ) {
218
  if ( ! function_exists( 'require_lib' ) ) {
219
  return $dimensions;
227
  $result = wpcom_getimagesize( $url );
228
  if ( is_array( $result ) ) {
229
  $dimensions[ $url ] = array(
230
+ 'width' => $result[0],
231
  'height' => $result[1],
232
  );
233
  }