AMP for WordPress - Version 0.6.0

Version Description

(2018-01-23) =

  • Add support for the "page" post type. A new page.php is introduced with template parts factored out (html-start.php, header.php, footer.php, html-end.php) and re-used from single.php. Note that AMP URLs will end in ?amp instead of /amp/. See #825. Props technosailor, ThierryA, westonruter.
  • Add AMP post preview button alongside non-AMP preview button. See #813. Props ThierryA, westonruter.
  • Add ability to disable AMP on a per-post basis via toggle in publish metabox. See #813. Props ThierryA, westonruter.
  • Add AMP settings admin screen for managing which post types have AMP support, eliminating the requirement to add add_post_type_support() calls in theme or plugin. See #811. Props ThierryA, westonruter.
  • Add generator meta tag for AMP. See #810. Props vaporwavre.
  • Add code quality checking via phpcs, eslint, jscs, and jshint. See #795. Props westonruter.
  • Add autoloader to reduce complexity. See #828. Props mikeschinkel, westonruter, ThierryA.
  • 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. Props kienstra, westonruter, ThierryA.
  • Merge AMP Customizer into main Customizer. See #819. Props kaitnyl, westonruter.
  • Update AMP HTML tags and attributes. A new bin/amphtml-update.sh bash script is introduced. Fixes Playbuzz. See #823. Props kienstra, ThierryA, westonruter.
  • Remove erroneous hash from id on amp-wp-header. See #853. Props eshannon3.

See 0.6 milestone.

Download this release

Release Info

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

Code changes from version 0.5.1 to 0.6.0

Files changed (76) hide show
  1. amp.php +162 -52
  2. assets/css/amp-customizer.css +123 -0
  3. assets/css/amp-post-meta-box.css +61 -0
  4. assets/images/amp-icon.svg +7 -0
  5. assets/images/amp-white-icon.svg +9 -0
  6. assets/js/amp-customize-controls.js +351 -0
  7. assets/js/amp-customize-preview.js +26 -0
  8. assets/js/amp-customizer-design-preview.js +9 -0
  9. assets/js/amp-customizer-preview.js +0 -15
  10. assets/js/amp-post-meta-box.js +176 -0
  11. includes/admin/class-amp-customizer.php +197 -76
  12. includes/admin/class-amp-post-meta-box.php +239 -0
  13. includes/admin/functions.php +57 -27
  14. includes/amp-frontend-actions.php +17 -2
  15. includes/amp-helper-functions.php +92 -12
  16. includes/amp-post-template-actions.php +85 -16
  17. includes/class-amp-autoloader.php +134 -0
  18. includes/class-amp-post-type-support.php +99 -0
  19. includes/embeds/class-amp-base-embed-handler.php +10 -3
  20. includes/embeds/class-amp-dailymotion-embed.php +11 -4
  21. includes/embeds/class-amp-facebook-embed.php +9 -3
  22. includes/embeds/class-amp-gallery-embed.php +9 -3
  23. includes/embeds/class-amp-instagram-embed.php +11 -4
  24. includes/embeds/class-amp-pinterest-embed.php +8 -2
  25. includes/embeds/class-amp-soundcloud-embed.php +151 -32
  26. includes/embeds/class-amp-twitter-embed.php +11 -4
  27. includes/embeds/class-amp-vimeo-embed.php +11 -4
  28. includes/embeds/class-amp-vine-embed.php +9 -3
  29. includes/embeds/class-amp-youtube-embed.php +11 -4
  30. includes/lib/fasterimage/amp-fasterimage.php +19 -17
  31. includes/options/class-amp-analytics-options-submenu.php +13 -5
  32. includes/options/class-amp-options-manager.php +244 -0
  33. includes/options/class-amp-options-menu.php +103 -24
  34. includes/options/views/class-amp-analytics-options-submenu-page.php +29 -36
  35. includes/options/views/class-amp-options-manager.php +0 -84
  36. includes/options/views/class-amp-options-menu-page.php +0 -17
  37. includes/sanitizers/class-amp-allowed-tags-generated.php +2700 -767
  38. includes/sanitizers/class-amp-audio-sanitizer.php +102 -16
  39. includes/sanitizers/class-amp-base-sanitizer.php +154 -7
  40. includes/sanitizers/class-amp-blacklist-sanitizer.php +87 -26
  41. includes/sanitizers/class-amp-iframe-sanitizer.php +113 -16
  42. includes/sanitizers/class-amp-img-sanitizer.php +167 -59
  43. includes/sanitizers/class-amp-playbuzz-sanitizer.php +90 -20
  44. includes/sanitizers/class-amp-rule-spec.php +107 -0
  45. includes/sanitizers/class-amp-style-sanitizer.php +99 -18
  46. includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php +649 -461
  47. includes/sanitizers/class-amp-video-sanitizer.php +108 -15
  48. includes/settings/class-amp-customizer-design-settings.php +160 -15
  49. includes/templates/class-amp-content-sanitizer.php +53 -0
  50. includes/{class-amp-content.php → templates/class-amp-content.php} +114 -45
  51. includes/{class-amp-post-template.php → templates/class-amp-post-template.php} +294 -132
  52. includes/utils/class-amp-dom-utils.php +205 -22
  53. includes/utils/class-amp-image-dimension-extractor.php +2 -4
  54. readme-assets/amp-options-analytics.png +0 -0
  55. readme-assets/analytics-option-entries.png +0 -0
  56. readme-assets/invalid-input.png +0 -0
  57. readme-assets/options-saved.png +0 -0
  58. readme.md +0 -684
  59. readme.txt +33 -32
  60. screenshot-1.png +0 -0
  61. screenshot-2.png +0 -0
  62. screenshot-3.png +0 -0
  63. screenshot-4.png +0 -0
  64. screenshot-5.png +0 -0
  65. templates/admin/amp-status.php +71 -0
  66. templates/footer.php +20 -2
  67. templates/header-bar.php +14 -4
  68. templates/header.php +14 -0
  69. templates/html-end.php +18 -0
  70. templates/html-start.php +26 -0
  71. templates/page.php +34 -0
  72. templates/single.php +21 -23
  73. templates/style.php +14 -5
  74. wpcom-helper.php +171 -0
  75. wpcom/class-amp-polldaddy-embed.php +101 -0
  76. wpcom/shortcodes.php +20 -0
amp.php CHANGED
@@ -5,24 +5,28 @@
5
  * Plugin URI: https://github.com/automattic/amp-wp
6
  * Author: Automattic
7
  * Author URI: https://automattic.com
8
- * Version: 0.5.1
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
 
 
12
  */
13
 
14
  define( 'AMP__FILE__', __FILE__ );
15
  define( 'AMP__DIR__', dirname( __FILE__ ) );
16
- define( 'AMP__VERSION', '0.5.1' );
 
 
 
17
 
18
- require_once( AMP__DIR__ . '/back-compat/back-compat.php' );
19
- require_once( AMP__DIR__ . '/includes/amp-helper-functions.php' );
20
- require_once( AMP__DIR__ . '/includes/admin/functions.php' );
21
- require_once( AMP__DIR__ . '/includes/settings/class-amp-customizer-settings.php' );
22
- require_once( AMP__DIR__ . '/includes/settings/class-amp-customizer-design-settings.php' );
23
 
24
  register_activation_hook( __FILE__, 'amp_activate' );
25
  function amp_activate() {
 
26
  if ( ! did_action( 'amp_init' ) ) {
27
  amp_init();
28
  }
@@ -43,20 +47,56 @@ function amp_deactivate() {
43
  flush_rewrite_rules();
44
  }
45
 
46
- add_action( 'init', 'amp_init' );
47
- function amp_init() {
 
 
 
 
 
 
 
48
  if ( false === apply_filters( 'amp_is_enabled', true ) ) {
49
  return;
50
  }
51
 
52
- define( 'AMP_QUERY_VAR', apply_filters( 'amp_query_var', 'amp' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
 
 
 
 
 
54
  do_action( 'amp_init' );
55
 
56
  load_plugin_textdomain( 'amp', false, plugin_basename( AMP__DIR__ ) . '/languages' );
57
 
58
  add_rewrite_endpoint( AMP_QUERY_VAR, EP_PERMALINK );
59
- add_post_type_support( 'post', AMP_QUERY_VAR );
60
 
61
  add_filter( 'request', 'amp_force_query_var_value' );
62
  add_action( 'wp', 'amp_maybe_add_actions' );
@@ -78,22 +118,69 @@ function amp_force_query_var_value( $query_vars ) {
78
  return $query_vars;
79
  }
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  function amp_maybe_add_actions() {
82
- if ( ! is_singular() || is_feed() ) {
 
83
  return;
84
  }
85
-
86
  $is_amp_endpoint = is_amp_endpoint();
87
 
88
- // Cannot use `get_queried_object` before canonical redirect; see https://core.trac.wordpress.org/ticket/35344
89
- global $wp_query;
90
- $post = $wp_query->post;
91
-
92
- $supports = post_supports_amp( $post );
93
-
94
- if ( ! $supports ) {
95
  if ( $is_amp_endpoint ) {
96
- wp_safe_redirect( get_permalink( $post->ID ) );
97
  exit;
98
  }
99
  return;
@@ -107,16 +194,16 @@ function amp_maybe_add_actions() {
107
  }
108
 
109
  function amp_load_classes() {
110
- require_once( AMP__DIR__ . '/includes/class-amp-post-template.php' ); // this loads everything else
111
  }
112
 
113
  function amp_add_frontend_actions() {
114
- require_once( AMP__DIR__ . '/includes/amp-frontend-actions.php' );
115
  }
116
 
117
  function amp_add_post_template_actions() {
118
- require_once( AMP__DIR__ . '/includes/amp-post-template-actions.php' );
119
- require_once( AMP__DIR__ . '/includes/amp-post-template-functions.php' );
120
  amp_post_template_init_hooks();
121
  }
122
 
@@ -124,52 +211,75 @@ function amp_prepare_render() {
124
  add_action( 'template_redirect', 'amp_render' );
125
  }
126
 
 
 
 
 
 
127
  function amp_render() {
128
- $post_id = get_queried_object_id();
129
- amp_render_post( $post_id );
130
- exit;
 
 
 
131
  }
132
 
133
- function amp_render_post( $post_id ) {
134
- $post = get_post( $post_id );
135
- if ( ! $post ) {
136
- return;
 
 
 
 
 
 
 
 
 
 
 
137
  }
 
138
 
139
- amp_load_classes();
 
 
 
 
 
 
 
 
140
 
 
 
 
 
 
 
 
141
  do_action( 'pre_amp_render_post', $post_id );
142
 
143
  amp_add_post_template_actions();
144
- $template = new AMP_Post_Template( $post_id );
145
  $template->load();
 
 
 
 
146
  }
147
 
148
  /**
149
  * Bootstraps the AMP customizer.
150
  *
151
- * If the AMP customizer is enabled, initially drop the core widgets and menus panels. If the current
152
- * preview page isn't flagged as an AMP template, the core panels will be re-added and the AMP panel
153
- * hidden.
154
- *
155
- * @internal This callback must be hooked before priority 10 on 'plugins_loaded' to properly unhook
156
- * the core panels.
157
- *
158
  * @since 0.4
159
  */
160
  function _amp_bootstrap_customizer() {
161
- /**
162
- * Filter whether to enable the AMP template customizer functionality.
163
- *
164
- * @param bool $enable Whether to enable the AMP customizer. Default true.
165
- */
166
- $amp_customizer_enabled = apply_filters( 'amp_customizer_is_enabled', true );
167
-
168
- if ( true === $amp_customizer_enabled ) {
169
- amp_init_customizer();
170
- }
171
  }
172
- add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 );
173
 
174
  /**
175
  * Redirects the old AMP URL to the new AMP URL.
5
  * Plugin URI: https://github.com/automattic/amp-wp
6
  * Author: Automattic
7
  * Author URI: https://automattic.com
8
+ * Version: 0.6.0
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
12
+ *
13
+ * @package AMP
14
  */
15
 
16
  define( 'AMP__FILE__', __FILE__ );
17
  define( 'AMP__DIR__', dirname( __FILE__ ) );
18
+ define( 'AMP__VERSION', '0.6.0' );
19
+
20
+ require_once AMP__DIR__ . '/includes/class-amp-autoloader.php';
21
+ AMP_Autoloader::register();
22
 
23
+ require_once AMP__DIR__ . '/back-compat/back-compat.php';
24
+ require_once AMP__DIR__ . '/includes/amp-helper-functions.php';
25
+ require_once AMP__DIR__ . '/includes/admin/functions.php';
 
 
26
 
27
  register_activation_hook( __FILE__, 'amp_activate' );
28
  function amp_activate() {
29
+ amp_after_setup_theme();
30
  if ( ! did_action( 'amp_init' ) ) {
31
  amp_init();
32
  }
47
  flush_rewrite_rules();
48
  }
49
 
50
+ /**
51
+ * Set up AMP.
52
+ *
53
+ * This function must be invoked through the 'after_setup_theme' action to allow
54
+ * the AMP setting to declare the post types support earlier than plugins/theme.
55
+ *
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
+
83
+ /**
84
+ * Init AMP.
85
+ *
86
+ * @since 0.1
87
+ */
88
+ function amp_init() {
89
 
90
+ /**
91
+ * Triggers on init when AMP plugin is active.
92
+ *
93
+ * @since 0.3
94
+ */
95
  do_action( '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' );
118
  return $query_vars;
119
  }
120
 
121
+ /**
122
+ * Fix up WP_Query for front page when amp query var is present.
123
+ *
124
+ * Normally the front page would not get served if a query var is present other than preview, page, paged, and cpage.
125
+ *
126
+ * @since 0.6
127
+ * @see WP_Query::parse_query()
128
+ * @link https://github.com/WordPress/wordpress-develop/blob/0baa8ae85c670d338e78e408f8d6e301c6410c86/src/wp-includes/class-wp-query.php#L951-L971
129
+ *
130
+ * @param WP_Query $query Query.
131
+ */
132
+ function amp_correct_query_when_is_front_page( WP_Query $query ) {
133
+ $is_front_page_query = (
134
+ $query->is_main_query()
135
+ &&
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()
143
+ &&
144
+ // Is showing pages on front.
145
+ 'page' === get_option( 'show_on_front' )
146
+ &&
147
+ // Has page on front set.
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;
155
+ $query->is_page = true;
156
+ $query->is_singular = true;
157
+ $query->set( 'page_id', get_option( 'page_on_front' ) );
158
+ }
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;
194
  }
195
 
196
  function amp_load_classes() {
197
+ _deprecated_function( __FUNCTION__, '0.6' );
198
  }
199
 
200
  function amp_add_frontend_actions() {
201
+ require_once AMP__DIR__ . '/includes/amp-frontend-actions.php';
202
  }
203
 
204
  function amp_add_post_template_actions() {
205
+ require_once AMP__DIR__ . '/includes/amp-post-template-actions.php';
206
+ require_once AMP__DIR__ . '/includes/amp-post-template-functions.php';
207
  amp_post_template_init_hooks();
208
  }
209
 
211
  add_action( 'template_redirect', 'amp_render' );
212
  }
213
 
214
+ /**
215
+ * Render AMP for queried post.
216
+ *
217
+ * @since 0.1
218
+ */
219
  function amp_render() {
220
+ // Note that queried object is used instead of the ID so that the_preview for the queried post can apply.
221
+ $post = get_queried_object();
222
+ if ( $post instanceof WP_Post ) {
223
+ amp_render_post( $post );
224
+ exit;
225
+ }
226
  }
227
 
228
+ /**
229
+ * Render AMP post template.
230
+ *
231
+ * @since 0.5
232
+ * @param WP_Post|int $post Post.
233
+ * @global WP_Query $wp_query
234
+ */
235
+ function amp_render_post( $post ) {
236
+ global $wp_query;
237
+
238
+ if ( ! ( $post instanceof WP_Post ) ) {
239
+ $post = get_post( $post );
240
+ if ( ! $post ) {
241
+ return;
242
+ }
243
  }
244
+ $post_id = $post->ID;
245
 
246
+ /*
247
+ * If amp_render_post is called directly outside of the standard endpoint, is_amp_endpoint() will return false,
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
+ /**
257
+ * Fires before rendering a post in AMP.
258
+ *
259
+ * @since 0.2
260
+ *
261
+ * @param int $post_id Post ID.
262
+ */
263
  do_action( 'pre_amp_render_post', $post_id );
264
 
265
  amp_add_post_template_actions();
266
+ $template = new AMP_Post_Template( $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
 
284
  /**
285
  * Redirects the old AMP URL to the new AMP URL.
assets/css/amp-customizer.css ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .amp-toggle {
2
+ position: relative;
3
+ display: inline-block;
4
+ width: 30px;
5
+ height: 15px;
6
+ top: 15px;
7
+ left: 130px;
8
+ }
9
+
10
+ .amp-toggle input,
11
+ .amp-toggle input.disabled {
12
+ position: absolute;
13
+ top: 0;
14
+ left: 0;
15
+ width: 100%;
16
+ height: 100%;
17
+ opacity: 0;
18
+ margin: 0;
19
+ padding: 0;
20
+ z-index: 1;
21
+ }
22
+
23
+ .amp-toggle .slider {
24
+ position: absolute;
25
+ cursor: pointer;
26
+ top: 0;
27
+ left: 0;
28
+ right: 0;
29
+ bottom: 0;
30
+ border-radius: 34px;
31
+ background-color: #555D66;
32
+ -webkit-transition: .3s;
33
+ transition: .3s;
34
+ transition-property: background-color, transform, -webkit-transform, -ms-transform, opacity;
35
+ }
36
+ .amp-toggle input:focus,
37
+ .amp-toggle input:active {
38
+ outline: none;
39
+ }
40
+ .amp-toggle input:hover + .slider,
41
+ .amp-toggle input:focus + .slider,
42
+ .amp-toggle input:active + .slider {
43
+ box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
44
+ }
45
+
46
+ .amp-toggle .slider:before {
47
+ position: absolute;
48
+ content: "";
49
+ height: 13px;
50
+ width: 13px;
51
+ top: 1px;
52
+ left: 1px;
53
+ border-radius: 50%;
54
+ background-color: transparent;
55
+ background-image: url( '../images/amp-white-icon.svg' );
56
+ background-size: 13px 13px;
57
+ -webkit-transition: .3s;
58
+ transition: .3s;
59
+ }
60
+
61
+ .amp-toggle input:checked + .slider {
62
+ background-color: #0379C4;
63
+ }
64
+
65
+ .amp-toggle input:checked + .slider:before {
66
+ -webkit-transform: translateX( 15px );
67
+ -ms-transform: translateX( 15px );
68
+ transform: translateX( 15px );
69
+ }
70
+
71
+ .amp-toggle input.disabled + .slider {
72
+ opacity: 0.7;
73
+ }
74
+
75
+ .amp-toggle .tooltip {
76
+ position: absolute;
77
+ bottom: 25px;
78
+ left: -115px;
79
+ width: 230px;
80
+ font-size: 13px;
81
+ text-align: center;
82
+ color: #FFFFFF;
83
+ background: #191E23;
84
+ padding: 15px;
85
+ z-index: 1;
86
+ cursor: default;
87
+ display: none;
88
+ }
89
+ .amp-toggle .tooltip a {
90
+ color: #00a0d2;
91
+ }
92
+ .amp-toggle .tooltip a:hover,
93
+ .amp-toggle .tooltip a:focus,
94
+ .amp-toggle .tooltip a:active {
95
+ color: #54cbf1;
96
+ }
97
+
98
+ .amp-toggle .tooltip:before {
99
+ position: absolute;
100
+ bottom: -8px;
101
+ left: 120px;
102
+ content: "";
103
+ border: solid;
104
+ border-color: #191E23 transparent;
105
+ border-width: 8px 8px 0 8px;
106
+ }
107
+
108
+ .js .accordion-section-title:after {
109
+ z-index: 0;
110
+ }
111
+
112
+ #customize-footer-actions .collapse-sidebar-label {
113
+ font-size: 11px;
114
+ margin-left: -3px;
115
+ }
116
+
117
+ .devices-wrapper .preview-desktop {
118
+ border-left: 1px solid #DDDDDD !important;
119
+ }
120
+
121
+ .wp-full-overlay-footer .devices button:before {
122
+ vertical-align: initial;
123
+ }
assets/css/amp-post-meta-box.css ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 1.0 AMP preview.
3
+ *
4
+ * Submit box preview buttons.
5
+ */
6
+
7
+ /* Core preview button */
8
+ .wp-core-ui #preview-action.has-amp-preview #post-preview {
9
+ border-top-right-radius: 0;
10
+ border-bottom-right-radius: 0;
11
+ float: none;
12
+ }
13
+
14
+ /* AMP preview button */
15
+ .wp-core-ui #amp-post-preview.preview {
16
+ border-top-left-radius: 0;
17
+ border-bottom-left-radius: 0;
18
+ text-indent: -9999px;
19
+ padding-right: 7px;
20
+ padding-left: 7px;
21
+ }
22
+
23
+ .wp-core-ui #amp-post-preview.preview::after {
24
+ content: "icon";
25
+ width: 14px;
26
+ float: left;
27
+ background: no-repeat center url( '../images/amp-icon.svg' );
28
+ background-size: 14px !important;
29
+ }
30
+
31
+ .wp-core-ui #amp-post-preview.preview.disabled::after {
32
+ opacity: 0.6;
33
+ }
34
+
35
+ /* AMP status */
36
+ .misc-amp-status .amp-icon {
37
+ float: left;
38
+ background: transparent url( '../images/amp-icon.svg' ) no-repeat left;
39
+ background-size: 17px;
40
+ width: 17px;
41
+ height: 17px;
42
+ margin: 0 8px 0 1px;
43
+ }
44
+
45
+ #amp-status-select fieldset {
46
+ margin: 7px 0 0 1px;
47
+ }
48
+
49
+ #amp-status-select .notice {
50
+ margin: 10px 0 -5px 3px;
51
+ }
52
+
53
+ .amp-status-actions {
54
+ margin-top: 10px;
55
+ }
56
+
57
+ @media screen and ( max-width: 782px ) {
58
+ #amp-status-select {
59
+ line-height: 280%;
60
+ }
61
+ }
assets/images/amp-icon.svg ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg width="62px" height="62px" viewBox="0 0 62 62" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>AMP-Icon</title>
4
+ <g id="AMP-Icon" fill="#82878c">
5
+ <path d="M41.6288667,28.1614333 L28.6243667,49.8035667 L26.2683667,49.8035667 L28.5975,35.7016667 L21.3838,35.7109667 C21.3838,35.7109667 21.3156,35.7130333 21.2835667,35.7130333 C20.6336,35.7130333 20.1076333,35.1870667 20.1076333,34.5371 C20.1076333,34.2581 20.367,33.7858667 20.367,33.7858667 L33.3291333,12.1695667 L35.7244,12.1799 L33.3363667,26.3035 L40.5872667,26.2942 C40.5872667,26.2942 40.6647667,26.2931667 40.7019667,26.2931667 C41.3519333,26.2931667 41.8779,26.8191333 41.8779,27.4691 C41.8779,27.7326 41.7745667,27.9640667 41.6278333,28.1604 L41.6288667,28.1614333 Z M31,0 C13.8787,0 0,13.8797333 0,31 C0,48.1213 13.8787,62 31,62 C48.1202667,62 62,48.1213 62,31 C62,13.8797333 48.1202667,0 31,0 L31,0 Z" id="Fill-1"></path>
6
+ </g>
7
+ </svg>
assets/images/amp-white-icon.svg ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg width="62px" height="62px" viewBox="0 0 62 62" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>AMP-White-Icon</title>
4
+ <g id="amp-logo-internal-site" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <g id="AMP-Brand-White-Icon" fill="#FFFFFF">
6
+ <path d="M41.6288667,28.1614333 L28.6243667,49.8035667 L26.2683667,49.8035667 L28.5975,35.7016667 L21.3838,35.7109667 C21.3838,35.7109667 21.3156,35.7130333 21.2835667,35.7130333 C20.6336,35.7130333 20.1076333,35.1870667 20.1076333,34.5371 C20.1076333,34.2581 20.367,33.7858667 20.367,33.7858667 L33.3291333,12.1695667 L35.7244,12.1799 L33.3363667,26.3035 L40.5872667,26.2942 C40.5872667,26.2942 40.6647667,26.2931667 40.7019667,26.2931667 C41.3519333,26.2931667 41.8779,26.8191333 41.8779,27.4691 C41.8779,27.7326 41.7745667,27.9640667 41.6278333,28.1604 L41.6288667,28.1614333 Z M31,0 C13.8787,0 0,13.8797333 0,31 C0,48.1213 13.8787,62 31,62 C48.1202667,62 62,48.1213 62,31 C62,13.8797333 48.1202667,0 31,0 L31,0 Z" id="Fill-1"></path>
7
+ </g>
8
+ </g>
9
+ </svg>
assets/js/amp-customize-controls.js ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* exported ampCustomizeControls */
2
+ /* eslint no-magic-numbers: [ "error", { "ignore": [ 0, 1, 250] } ] */
3
+
4
+ var ampCustomizeControls = ( function( api, $ ) {
5
+ 'use strict';
6
+
7
+ var component = {
8
+ data: {
9
+ queryVar: 'amp',
10
+ panelId: '',
11
+ ampUrl: '',
12
+ l10n: {
13
+ unavailableMessage: '',
14
+ unavailableLinkText: ''
15
+ }
16
+ },
17
+ tooltipTimeout: 5000,
18
+ tooltipVisible: new api.Value( false ),
19
+ tooltipFocused: new api.Value( 0 )
20
+ };
21
+
22
+ /**
23
+ * Boot using data sent inline.
24
+ *
25
+ * @param {Object} data Object data.
26
+ * @return {void}
27
+ */
28
+ component.boot = function boot( data ) {
29
+ component.data = data;
30
+
31
+ function initPanel() {
32
+ api.panel( component.data.panelId, component.panelReady );
33
+ }
34
+
35
+ if ( api.state ) {
36
+ component.addState();
37
+ api.bind( 'ready', initPanel );
38
+ } else { // WP<4.9.
39
+ api.bind( 'ready', function() {
40
+ component.addState(); // Needed for WP<4.9.
41
+ initPanel();
42
+ } );
43
+ }
44
+ };
45
+
46
+ /**
47
+ * Add state for AMP.
48
+ *
49
+ * @returns {void}
50
+ */
51
+ component.addState = function addState() {
52
+ api.state.add( 'ampEnabled', new api.Value( false ) );
53
+ api.state.add( 'ampAvailable', new api.Value( false ) );
54
+ };
55
+
56
+ /**
57
+ * Check if the URL is AMPified.
58
+ *
59
+ * @param {string} url URL.
60
+ * @return {boolean} whether it is an AMP URL.
61
+ */
62
+ component.isAmpUrl = function isAmpUrl( url ) {
63
+ var urlParser = document.createElement( 'a' ),
64
+ regexEndpoint = new RegExp( '\\/' + component.data.queryVar + '\\/?$' );
65
+
66
+ urlParser.href = url;
67
+ if ( ! _.isUndefined( wp.customize.utils.parseQueryString( urlParser.search.substr( 1 ) )[ component.data.queryVar ] ) ) {
68
+ return true;
69
+ }
70
+ return regexEndpoint.test( urlParser.pathname );
71
+ };
72
+
73
+ /**
74
+ * Create an non-AMP version of a URL.
75
+ *
76
+ * @param {string} url URL.
77
+ * @return {string} non-AMPified URL.
78
+ */
79
+ component.unampifyUrl = function unampifyUrl( url ) {
80
+ var urlParser = document.createElement( 'a' ),
81
+ regexEndpoint = new RegExp( '\\/' + component.data.queryVar + '\\/?$' ),
82
+ params;
83
+
84
+ urlParser.href = url;
85
+ urlParser.pathname = urlParser.pathname.replace( regexEndpoint, '' );
86
+
87
+ if ( urlParser.search.length > 1 ) {
88
+ params = wp.customize.utils.parseQueryString( urlParser.search.substr( 1 ) );
89
+ delete params[ component.data.queryVar ];
90
+ urlParser.search = $.param( params );
91
+ }
92
+
93
+ return urlParser.href;
94
+ };
95
+
96
+ /**
97
+ * Create an AMP version of a URL.
98
+ *
99
+ * @param {string} url URL.
100
+ * @return {string} AMPified URL.
101
+ */
102
+ component.ampifyUrl = function ampifyUrl( url ) {
103
+ var urlParser = document.createElement( 'a' );
104
+ urlParser.href = component.unampifyUrl( url );
105
+ if ( urlParser.search.length ) {
106
+ urlParser.search += '&';
107
+ }
108
+ urlParser.search += component.data.queryVar + '=1';
109
+ return urlParser.href;
110
+ };
111
+
112
+ /**
113
+ * Try to close the tooltip after a given timeout.
114
+ *
115
+ * @returns {void}
116
+ */
117
+ component.tryToCloseTooltip = function tryToCloseTooltip() {
118
+ clearTimeout( component.tooltipTimeoutId );
119
+ component.tooltipTimeoutId = setTimeout( function() {
120
+ if ( ! component.tooltipVisible.get() ) {
121
+ return;
122
+ }
123
+ if ( component.tooltipFocused.get() > 0 ) {
124
+ component.tryToCloseTooltip();
125
+ } else {
126
+ component.tooltipVisible.set( false );
127
+ }
128
+ }, component.tooltipTimeout );
129
+ };
130
+
131
+ /**
132
+ * Make current URL AMPified if toggle is on.
133
+ *
134
+ * @param {string} url URL.
135
+ * @return {string} AMPified URL.
136
+ */
137
+ component.setCurrentAmpUrl = function setCurrentAmpUrl( url ) {
138
+ var enabled = api.state( 'ampEnabled' ).get();
139
+ if ( ! enabled && component.isAmpUrl( url ) ) {
140
+ return component.unampifyUrl( url );
141
+ } else if ( enabled && ! component.isAmpUrl( url ) ) {
142
+ return component.ampifyUrl( url );
143
+ }
144
+ return url;
145
+ };
146
+
147
+ /**
148
+ * Swap to AMP version of URL in preview.
149
+ *
150
+ * @return {void}
151
+ */
152
+ component.updatePreviewUrl = function updatePreviewUrl() {
153
+ api.previewer.previewUrl.set( component.setCurrentAmpUrl( api.previewer.previewUrl.get() ) );
154
+ };
155
+
156
+ /**
157
+ * Enable AMP and navigate to the given URL.
158
+ *
159
+ * @param {string} url - URL.
160
+ * @returns {void}
161
+ */
162
+ component.enableAndNavigateToUrl = function enableAndNavigateToUrl( url ) {
163
+ api.state( 'ampEnabled' ).set( true );
164
+ api.previewer.previewUrl.set( url );
165
+ };
166
+
167
+ /**
168
+ * Update panel notifications.
169
+ *
170
+ * @returns {void}
171
+ */
172
+ component.updatePanelNotifications = function updatePanelNotifications() {
173
+ var panel = api.panel( component.data.panelId ), containers;
174
+ containers = panel.sections().concat( [ panel ] );
175
+ if ( api.state( 'ampAvailable' ).get() ) {
176
+ _.each( containers, function( container ) {
177
+ container.notifications.remove( 'amp_unavailable' );
178
+ } );
179
+ } else {
180
+ _.each( containers, function( container ) {
181
+ container.notifications.add( new api.Notification( 'amp_unavailable', {
182
+ message: component.data.l10n.unavailableMessage,
183
+ type: 'info',
184
+ linkText: component.data.l10n.unavailableLinkText,
185
+ url: component.data.ampUrl,
186
+ templateId: 'customize-amp-unavailable-notification',
187
+ render: function() {
188
+ var li = api.Notification.prototype.render.call( this );
189
+ li.find( 'a' ).on( 'click', function( event ) {
190
+ event.preventDefault();
191
+ component.enableAndNavigateToUrl( this.href );
192
+ } );
193
+ return li;
194
+ }
195
+ } ) );
196
+ } );
197
+ }
198
+ };
199
+
200
+ /**
201
+ * Hook up all AMP preview interactions once panel is ready.
202
+ *
203
+ * @param {wp.customize.Panel} panel The AMP panel.
204
+ * @return {void}
205
+ */
206
+ component.panelReady = function panelReady( panel ) {
207
+ var ampToggleContainer, checkbox, tooltip, tooltipLink;
208
+
209
+ ampToggleContainer = $( wp.template( 'customize-amp-enabled-toggle' )( {
210
+ message: component.data.l10n.unavailableMessage,
211
+ linkText: component.data.l10n.unavailableLinkText,
212
+ url: component.data.ampUrl
213
+ } ) );
214
+ checkbox = ampToggleContainer.find( 'input[type=checkbox]' );
215
+ tooltip = ampToggleContainer.find( '.tooltip' );
216
+ tooltipLink = tooltip.find( 'a' );
217
+
218
+ // AMP panel triggers the input toggle for AMP preview.
219
+ panel.expanded.bind( function( expanded ) {
220
+ if ( ! expanded ) {
221
+ return;
222
+ }
223
+ if ( api.state( 'ampAvailable' ).get() ) {
224
+ api.state( 'ampEnabled' ).set( panel.expanded.get() );
225
+ } else if ( ! panel.notifications ) {
226
+
227
+ /*
228
+ * This is only done if panel notifications aren't supported.
229
+ * If they are (as of 4.9) then a notification will be shown
230
+ * in the panel and its sections when AMP is not available.
231
+ */
232
+ setTimeout( function() {
233
+ component.tooltipVisible.set( true );
234
+ }, 250 );
235
+ }
236
+ } );
237
+
238
+ if ( panel.notifications ) {
239
+ api.state( 'ampAvailable' ).bind( component.updatePanelNotifications );
240
+ component.updatePanelNotifications();
241
+ api.section.bind( 'add', component.updatePanelNotifications );
242
+ }
243
+
244
+ // Enable AMP toggle if available and mobile device selected.
245
+ api.previewedDevice.bind( function( device ) {
246
+ if ( api.state( 'ampAvailable' ).get() ) {
247
+ api.state( 'ampEnabled' ).set( 'mobile' === device );
248
+ }
249
+ } );
250
+
251
+ // Message coming from previewer.
252
+ api.previewer.bind( 'amp-status', function( data ) {
253
+ api.state( 'ampAvailable' ).set( data.available );
254
+ } );
255
+ function setInitialAmpEnabledState( data ) {
256
+ api.state( 'ampEnabled' ).set( data.enabled );
257
+ api.previewer.unbind( 'amp-status', setInitialAmpEnabledState );
258
+ }
259
+ api.previewer.bind( 'amp-status', setInitialAmpEnabledState );
260
+
261
+ /*
262
+ * Persist the presence or lack of the amp=1 param when navigating in the preview,
263
+ * even if current page is not yet supported.
264
+ */
265
+ api.previewer.previewUrl.validate = ( function( prevValidate ) {
266
+ return function( value ) {
267
+ var val = prevValidate.call( this, value );
268
+ if ( val ) {
269
+ val = component.setCurrentAmpUrl( val );
270
+ }
271
+ return val;
272
+ };
273
+ } )( api.previewer.previewUrl.validate );
274
+
275
+ // Listen for ampEnabled state changes.
276
+ api.state( 'ampEnabled' ).bind( function( enabled ) {
277
+ checkbox.prop( 'checked', enabled );
278
+ component.updatePreviewUrl();
279
+ } );
280
+
281
+ // Listen for ampAvailable state changes.
282
+ api.state( 'ampAvailable' ).bind( function( available ) {
283
+ checkbox.toggleClass( 'disabled', ! available );
284
+
285
+ // Show the unavailable tooltip if AMP is enabled.
286
+ if ( api.state( 'ampEnabled' ).get() ) {
287
+ component.tooltipVisible.set( ! available );
288
+ }
289
+ } );
290
+
291
+ // Adding checkbox toggle before device selection.
292
+ $( '.devices-wrapper' ).before( ampToggleContainer );
293
+
294
+ // User clicked link within tooltip, go to linked post in preview.
295
+ tooltipLink.on( 'click', function( event ) {
296
+ event.preventDefault();
297
+ component.enableAndNavigateToUrl( this.href );
298
+ } );
299
+
300
+ // Toggle visibility of tooltip based on tooltipVisible state.
301
+ component.tooltipVisible.bind( function( visible ) {
302
+ tooltip.attr( 'aria-hidden', visible ? 'false' : 'true' );
303
+ if ( visible ) {
304
+ $( document ).on( 'click.amp-toggle-outside', function( event ) {
305
+ if ( ! $.contains( ampToggleContainer[0], event.target ) ) {
306
+ component.tooltipVisible.set( false );
307
+ }
308
+ } );
309
+ tooltip.fadeIn();
310
+ component.tryToCloseTooltip();
311
+ } else {
312
+ tooltip.fadeOut();
313
+ component.tooltipFocused.set( 0 );
314
+ $( document ).off( 'click.amp-toggle-outside' );
315
+ }
316
+ } );
317
+
318
+ // Handle click on checkbox to either enable the AMP preview or show the tooltip.
319
+ checkbox.on( 'click', function() {
320
+ this.checked = ! this.checked; // Undo what we just did, since state is managed in ampAvailable change handler.
321
+ if ( api.state( 'ampAvailable' ).get() ) {
322
+ api.state( 'ampEnabled' ).set( ! api.state( 'ampEnabled' ).get() );
323
+ } else {
324
+ component.tooltipVisible.set( true );
325
+ }
326
+ } );
327
+
328
+ // Keep track of the user's state interacting with the tooltip.
329
+ tooltip.on( 'mouseenter', function() {
330
+ if ( ! api.state( 'ampAvailable' ).get() ) {
331
+ component.tooltipVisible.set( true );
332
+ }
333
+ component.tooltipFocused.set( component.tooltipFocused.get() + 1 );
334
+ } );
335
+ tooltip.on( 'mouseleave', function() {
336
+ component.tooltipFocused.set( component.tooltipFocused.get() - 1 );
337
+ } );
338
+ tooltipLink.on( 'focus', function() {
339
+ if ( ! api.state( 'ampAvailable' ).get() ) {
340
+ component.tooltipVisible.set( true );
341
+ }
342
+ component.tooltipFocused.set( component.tooltipFocused.get() + 1 );
343
+ } );
344
+ tooltipLink.on( 'blur', function() {
345
+ component.tooltipFocused.set( component.tooltipFocused.get() - 1 );
346
+ } );
347
+ };
348
+
349
+ return component;
350
+
351
+ } )( wp.customize, jQuery );
assets/js/amp-customize-preview.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* exported ampCustomizePreview */
2
+
3
+ var ampCustomizePreview = ( function( api ) {
4
+ 'use strict';
5
+
6
+ var component = {};
7
+
8
+ /**
9
+ * Boot using data sent inline.
10
+ *
11
+ * @param {Object} data - PHP exports.
12
+ * @param {boolean} data.available - Whether AMP is available.
13
+ * @param {boolean} data.enabled - Whether AMP is enabled.
14
+ * @return {void}
15
+ */
16
+ component.boot = function boot( data ) {
17
+ api.bind( 'preview-ready', function() {
18
+ api.preview.bind( 'active', function() {
19
+ api.preview.send( 'amp-status', data );
20
+ } );
21
+ } );
22
+ };
23
+
24
+ return component;
25
+
26
+ } )( wp.customize );
assets/js/amp-customizer-design-preview.js CHANGED
@@ -1,3 +1,5 @@
 
 
1
  ( function( $ ) {
2
  'use strict';
3
 
@@ -37,4 +39,11 @@
37
  } );
38
  } );
39
 
 
 
 
 
 
 
 
40
  } )( jQuery );
1
+ /* global amp_customizer_design, console */
2
+
3
  ( function( $ ) {
4
  'use strict';
5
 
39
  } );
40
  } );
41
 
42
+ // Site title.
43
+ wp.customize( 'blogname', function( setting ) {
44
+ setting.bind( function( title ) {
45
+ $( '.amp-wp-header .amp-site-title, .amp-wp-footer h2' ).text( title );
46
+ } );
47
+ } );
48
+
49
  } )( jQuery );
assets/js/amp-customizer-preview.js DELETED
@@ -1,15 +0,0 @@
1
- ( function( $ ) {
2
- 'use strict';
3
-
4
- // Don't allow navigation away from the AMP page in the preview.
5
- // The Customizer breaks pretty spectacularly when that happens as it's only meant to work for AMP pages.
6
- $( 'a' ).click( function( e ) {
7
- var href = this.getAttribute( 'href' );
8
- if ( href && href.indexOf( '#' ) === 0 ) {
9
- return true;
10
- }
11
-
12
- return false;
13
- } );
14
-
15
- } )( jQuery );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/js/amp-post-meta-box.js ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* exported ampPostMetaBox */
2
+
3
+ /**
4
+ * AMP Post Meta Box.
5
+ *
6
+ * @todo Rename this to be just the ampEditPostScreen?
7
+ *
8
+ * @since 0.6
9
+ */
10
+ var ampPostMetaBox = ( function( $ ) {
11
+ 'use strict';
12
+
13
+ var component = {
14
+
15
+ /**
16
+ * Holds data.
17
+ *
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.
24
+ statusInputName: '',
25
+ l10n: {
26
+ ampPreviewBtnLabel: ''
27
+ }
28
+ },
29
+
30
+ /**
31
+ * Toggle animation speed.
32
+ *
33
+ * @since 0.6
34
+ */
35
+ toggleSpeed: 200,
36
+
37
+ /**
38
+ * Core preview button selector.
39
+ *
40
+ * @since 0.6
41
+ */
42
+ previewBtnSelector: '#post-preview',
43
+
44
+ /**
45
+ * AMP preview button selector.
46
+ *
47
+ * @since 0.6
48
+ */
49
+ ampPreviewBtnSelector: '#amp-post-preview'
50
+ };
51
+
52
+ /**
53
+ * Boot plugin.
54
+ *
55
+ * @since 0.6
56
+ * @param {Object} data Object data.
57
+ * @return {void}
58
+ */
59
+ component.boot = function boot( data ) {
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();
67
+ } );
68
+ };
69
+
70
+ /**
71
+ * Events listener.
72
+ *
73
+ * @since 0.6
74
+ * @return {void}
75
+ */
76
+ component.listen = function listen() {
77
+ $( component.ampPreviewBtnSelector ).on( 'click.amp-post-preview', function( e ) {
78
+ e.preventDefault();
79
+ component.onAmpPreviewButtonClick();
80
+ } );
81
+
82
+ component.statusRadioInputs.prop( 'disabled', true ); // Prevent cementing setting default status as overridden status.
83
+ $( '.edit-amp-status, [href="#amp_status"]' ).click( function( e ) {
84
+ e.preventDefault();
85
+ component.statusRadioInputs.prop( 'disabled', false );
86
+ component.toggleAmpStatus( $( e.target ) );
87
+ } );
88
+
89
+ $( '#submitpost input[type="submit"]' ).on( 'click', function() {
90
+ $( component.ampPreviewBtnSelector ).addClass( 'disabled' );
91
+ } );
92
+ };
93
+
94
+ /**
95
+ * Add AMP Preview button.
96
+ *
97
+ * @since 0.6
98
+ * @return {void}
99
+ */
100
+ component.addPreviewButton = function addPreviewButton() {
101
+ var previewBtn = $( component.previewBtnSelector );
102
+ previewBtn
103
+ .clone()
104
+ .insertAfter( previewBtn )
105
+ .prop( {
106
+ 'href': component.data.previewLink,
107
+ 'id': component.ampPreviewBtnSelector.replace( '#', '' )
108
+ } )
109
+ .text( component.data.l10n.ampPreviewBtnLabel )
110
+ .parent()
111
+ .addClass( 'has-amp-preview' );
112
+ };
113
+
114
+ /**
115
+ * AMP Preview button click handler.
116
+ *
117
+ * We trigger the Core preview link for events propagation purposes.
118
+ *
119
+ * @since 0.6
120
+ * @return {void}
121
+ */
122
+ component.onAmpPreviewButtonClick = function onAmpPreviewButtonClick() {
123
+ var $input;
124
+
125
+ // Flag the AMP preview referer.
126
+ $input = $( '<input>' )
127
+ .prop( {
128
+ 'type': 'hidden',
129
+ 'name': 'amp-preview',
130
+ 'value': 'do-preview'
131
+ } )
132
+ .insertAfter( component.ampPreviewBtnSelector );
133
+
134
+ // Trigger Core preview button and remove AMP flag.
135
+ $( component.previewBtnSelector ).click();
136
+ $input.remove();
137
+ };
138
+
139
+ /**
140
+ * Add AMP status toggle.
141
+ *
142
+ * @since 0.6
143
+ * @param {Object} $target Event target.
144
+ * @return {void}
145
+ */
146
+ component.toggleAmpStatus = function toggleAmpStatus( $target ) {
147
+ var $container = $( '#amp-status-select' ),
148
+ status = $container.data( 'amp-status' ),
149
+ $checked,
150
+ editAmpStatus = $( '.edit-amp-status' );
151
+
152
+ // Don't modify status on cancel button click.
153
+ if ( ! $target.hasClass( 'button-cancel' ) ) {
154
+ status = component.statusRadioInputs.filter( ':checked' ).val();
155
+ }
156
+
157
+ $checked = $( '#amp-status-' + status );
158
+
159
+ // Toggle elements.
160
+ editAmpStatus.fadeToggle( component.toggleSpeed, function() {
161
+ if ( editAmpStatus.is( ':visible' ) ) {
162
+ editAmpStatus.focus();
163
+ }
164
+ } );
165
+ $container.slideToggle( component.toggleSpeed );
166
+
167
+ // Update status.
168
+ if ( component.data.canSupport ) {
169
+ $container.data( 'amp-status', status );
170
+ $checked.prop( 'checked', true );
171
+ $( '.amp-status-text' ).text( $checked.next().text() );
172
+ }
173
+ };
174
+
175
+ return component;
176
+ })( window.jQuery );
includes/admin/class-amp-customizer.php CHANGED
@@ -8,6 +8,7 @@
8
  * @since 0.4
9
  */
10
  class AMP_Template_Customizer {
 
11
  /**
12
  * AMP template editor panel ID.
13
  *
@@ -39,81 +40,63 @@ class AMP_Template_Customizer {
39
 
40
  $self->wp_customize = $wp_customize;
41
 
 
 
 
 
 
 
 
 
42
  do_action( 'amp_customizer_init', $self );
43
 
44
- // Settings need to be registered for regular customize requests as well (since save is handled there)
45
  $self->register_settings();
 
46
 
47
- // Our custom panels only need to go for AMP Customizer requests though
48
- if ( self::is_amp_customizer() ) {
49
- if ( empty( $_GET['url'] ) ) { // input var ok
50
- $wp_customize->set_preview_url( amp_admin_get_preview_permalink() );
51
- }
52
-
53
- $self->_unregister_core_ui();
54
- $self->register_ui();
55
- } elseif ( is_customize_preview() ) {
56
- // Delay preview-specific actions until we're sure we're rendering an AMP page, since it's too early for `is_amp_endpoint()` here.
57
- add_action( 'pre_amp_render_post', array( $self, 'init_preview' ) );
58
- }
59
  }
60
 
61
  /**
62
- * Filters the core components to unhook the nav_menus and widgets panels.
63
  *
64
  * @since 0.4
65
- * @access private
66
- *
67
- * @return array Array of core Customizer components to keep active.
68
  */
69
- public static function _unregister_core_panels( $panels ) {
70
- if ( self::is_amp_customizer() ) {
71
- $panels = array();
72
- }
73
- return $panels;
74
- }
75
-
76
- /**
77
- * Removes all non-AMP sections and panels.
78
- *
79
- * Provides a clean, standalone instance-like experience by removing all non-AMP registered panels and sections.
80
- *
81
- * @since 0.4
82
- * @access private
83
- */
84
- private function _unregister_core_ui() {
85
- $panels = $this->wp_customize->panels();
86
- $sections = $this->wp_customize->sections();
87
-
88
- foreach ( $panels as $panel_id => $object ) {
89
- $this->wp_customize->remove_panel( $panel_id );
90
- }
91
-
92
- foreach ( $sections as $section_id => $object ) {
93
- $this->wp_customize->remove_section( $section_id );
94
- }
95
- }
96
-
97
  public function init_preview() {
98
- // Preview needs controls registered too for postMessage communication.
99
- $this->register_ui();
 
100
 
101
- add_action( 'amp_post_template_footer', array( $this, 'add_preview_scripts' ) );
 
 
 
 
 
 
102
  }
103
 
104
  /**
105
  * Sets up the AMP Customizer preview.
106
  */
107
  public function register_ui() {
108
- add_action( 'customize_controls_enqueue_scripts', array( $this, 'add_customizer_scripts' ) );
109
- add_filter( 'customize_previewable_devices', array( $this, 'force_mobile_preview' ) );
110
-
111
  $this->wp_customize->add_panel( self::PANEL_ID, array(
112
- 'type' => 'amp',
113
- 'title' => __( 'AMP', 'amp' ),
 
114
  'description' => sprintf( __( '<a href="%s" target="_blank">The AMP Project</a> is a Google-led initiative that dramatically improves loading speeds on phones and tablets. You can use the Customizer to preview changes to your AMP template before publishing them.', 'amp' ), 'https://ampproject.org' ),
115
  ) );
116
 
 
 
 
 
 
 
 
 
117
  do_action( 'amp_customizer_register_ui', $this->wp_customize );
118
  }
119
 
@@ -121,50 +104,188 @@ class AMP_Template_Customizer {
121
  * Registers settings for customizing AMP templates.
122
  *
123
  * @since 0.4
124
- * @access public
125
  */
126
  public function register_settings() {
 
 
 
 
 
 
 
 
 
127
  do_action( 'amp_customizer_register_settings', $this->wp_customize );
128
  }
129
 
 
 
 
 
 
130
  public function add_customizer_scripts() {
131
- wp_enqueue_script( 'wp-util' ); // fix `wp.template is not a function`
132
- do_action( 'amp_customizer_enqueue_scripts' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
134
 
135
  /**
136
- * Enqueues scripts and fires the 'wp_footer' action so we can output customizer scripts.
137
- *
138
- * This breaks AMP validation in the customizer but is necessary for the live preview.
139
  *
140
- * @since 0.4
141
- * @access public
142
  */
143
- public function add_preview_scripts() {
 
 
 
 
 
 
144
  wp_enqueue_script(
145
- 'amp-customizer',
146
- amp_get_asset_url( 'js/amp-customizer-preview.js' ),
147
- array( 'jquery', 'customize-preview', 'wp-util' ),
148
- $version = false,
149
- $footer = true
150
  );
151
 
152
- do_action( 'amp_customizer_enqueue_preview_scripts', $this->wp_customize );
 
 
 
 
 
 
153
 
154
- /** This action is documented in wp-includes/general-template.php */
155
- do_action( 'wp_footer' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
 
158
- public function force_mobile_preview( $devices ) {
159
- if ( isset( $devices['mobile'] ) ) {
160
- $devices['mobile']['default'] = true;
161
- unset( $devices['desktop']['default'] );
 
 
 
 
 
 
 
 
162
  }
163
 
164
- return $devices;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  public static function is_amp_customizer() {
168
- return ! empty( $_REQUEST[ AMP_CUSTOMIZER_QUERY_VAR ] ); // input var ok
 
169
  }
170
  }
8
  * @since 0.4
9
  */
10
  class AMP_Template_Customizer {
11
+
12
  /**
13
  * AMP template editor panel ID.
14
  *
40
 
41
  $self->wp_customize = $wp_customize;
42
 
43
+ /**
44
+ * Fires when the AMP Template Customizer initializes.
45
+ *
46
+ * In practice the `customize_register` hook should be used instead.
47
+ *
48
+ * @since 0.4
49
+ * @param AMP_Template_Customizer $self Instance.
50
+ */
51
  do_action( 'amp_customizer_init', $self );
52
 
 
53
  $self->register_settings();
54
+ $self->register_ui();
55
 
56
+ add_action( 'customize_controls_enqueue_scripts', array( $self, 'add_customizer_scripts' ) );
57
+ add_action( 'customize_controls_print_footer_scripts', array( $self, 'print_controls_templates' ) );
58
+ add_action( 'customize_preview_init', array( $self, 'init_preview' ) );
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
  /**
62
+ * Init Customizer preview.
63
  *
64
  * @since 0.4
65
+ * @global WP_Customize_Manager $wp_customize
 
 
66
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  public function init_preview() {
68
+ add_action( 'amp_post_template_head', 'wp_no_robots' );
69
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) );
70
+ add_action( 'amp_customizer_enqueue_preview_scripts', array( $this, 'enqueue_preview_scripts' ) );
71
 
72
+ // Output scripts and styles which will break AMP validation only when preview is opened with controls for manipulation.
73
+ if ( $this->wp_customize->get_messenger_channel() ) {
74
+ add_action( 'amp_post_template_head', array( $this->wp_customize, 'customize_preview_loading_style' ) );
75
+ add_action( 'amp_post_template_css', array( $this, 'add_customize_preview_styles' ) );
76
+ add_action( 'amp_post_template_head', array( $this->wp_customize, 'remove_frameless_preview_messenger_channel' ) );
77
+ add_action( 'amp_post_template_footer', array( $this, 'add_preview_scripts' ) );
78
+ }
79
  }
80
 
81
  /**
82
  * Sets up the AMP Customizer preview.
83
  */
84
  public function register_ui() {
 
 
 
85
  $this->wp_customize->add_panel( self::PANEL_ID, array(
86
+ 'type' => 'amp',
87
+ 'title' => __( 'AMP', 'amp' ),
88
+ /* translators: placeholder is URL to AMP project. */
89
  'description' => sprintf( __( '<a href="%s" target="_blank">The AMP Project</a> is a Google-led initiative that dramatically improves loading speeds on phones and tablets. You can use the Customizer to preview changes to your AMP template before publishing them.', 'amp' ), 'https://ampproject.org' ),
90
  ) );
91
 
92
+ /**
93
+ * Fires after the AMP panel has been registered for plugins to add additional controls.
94
+ *
95
+ * In practice the `customize_register` hook should be used instead.
96
+ *
97
+ * @since 0.4
98
+ * @param WP_Customize_Manager $manager Manager.
99
+ */
100
  do_action( 'amp_customizer_register_ui', $this->wp_customize );
101
  }
102
 
104
  * Registers settings for customizing AMP templates.
105
  *
106
  * @since 0.4
 
107
  */
108
  public function register_settings() {
109
+
110
+ /**
111
+ * Fires when plugins should register settings for AMP.
112
+ *
113
+ * In practice the `customize_register` hook should be used instead.
114
+ *
115
+ * @since 0.4
116
+ * @param WP_Customize_Manager $manager Manager.
117
+ */
118
  do_action( 'amp_customizer_register_settings', $this->wp_customize );
119
  }
120
 
121
+ /**
122
+ * Load up AMP scripts needed for Customizer integrations.
123
+ *
124
+ * @since 0.6
125
+ */
126
  public function add_customizer_scripts() {
127
+ wp_enqueue_script(
128
+ 'amp-customize-controls',
129
+ amp_get_asset_url( 'js/amp-customize-controls.js' ),
130
+ array( 'jquery', 'customize-controls' ),
131
+ AMP__VERSION,
132
+ true
133
+ );
134
+
135
+ wp_add_inline_script( 'amp-customize-controls', sprintf( 'ampCustomizeControls.boot( %s );',
136
+ wp_json_encode( array(
137
+ 'queryVar' => AMP_QUERY_VAR,
138
+ 'panelId' => self::PANEL_ID,
139
+ 'ampUrl' => amp_admin_get_preview_permalink(),
140
+ 'l10n' => array(
141
+ 'unavailableMessage' => __( 'AMP is not available for the page currently being previewed.', 'amp' ),
142
+ 'unavailableLinkText' => __( 'Navigate to an AMP compatible page', 'amp' ),
143
+ ),
144
+ ) )
145
+ ) );
146
+
147
+ wp_enqueue_style(
148
+ 'amp-customizer',
149
+ amp_get_asset_url( 'css/amp-customizer.css' )
150
+ );
151
+
152
+ /**
153
+ * Fires when plugins should register settings for AMP.
154
+ *
155
+ * In practice the `customize_controls_enqueue_scripts` hook should be used instead.
156
+ *
157
+ * @since 0.4
158
+ * @param WP_Customize_Manager $manager Manager.
159
+ */
160
+ do_action( 'amp_customizer_enqueue_scripts', $this->wp_customize );
161
  }
162
 
163
  /**
164
+ * Enqueues scripts used in both the AMP and non-AMP Customizer preview.
 
 
165
  *
166
+ * @since 0.6
 
167
  */
168
+ public function enqueue_preview_scripts() {
169
+
170
+ // Bail if user can't customize anyway.
171
+ if ( ! current_user_can( 'customize' ) ) {
172
+ return;
173
+ }
174
+
175
  wp_enqueue_script(
176
+ 'amp-customize-preview',
177
+ amp_get_asset_url( 'js/amp-customize-preview.js' ),
178
+ array( 'jquery', 'customize-preview' ),
179
+ AMP__VERSION,
180
+ true
181
  );
182
 
183
+ wp_add_inline_script( 'amp-customize-preview', sprintf( 'ampCustomizePreview.boot( %s );',
184
+ wp_json_encode( array(
185
+ 'available' => (bool) is_singular() && post_supports_amp( get_queried_object() ),
186
+ 'enabled' => is_amp_endpoint(),
187
+ ) )
188
+ ) );
189
+ }
190
 
191
+ /**
192
+ * Add AMP Customizer preview styles.
193
+ */
194
+ public function add_customize_preview_styles() {
195
+ ?>
196
+ /* Text meant only for screen readers; this is needed for wp.a11y.speak() */
197
+ .screen-reader-text {
198
+ border: 0;
199
+ clip: rect(1px, 1px, 1px, 1px);
200
+ -webkit-clip-path: inset(50%);
201
+ clip-path: inset(50%);
202
+ height: 1px;
203
+ margin: -1px;
204
+ overflow: hidden;
205
+ padding: 0;
206
+ position: absolute;
207
+ width: 1px;
208
+ word-wrap: normal !important;
209
+ }
210
+ body.wp-customizer-unloading {
211
+ opacity: 0.25 !important; /* Because AMP sets body to opacity:1 once layout complete. */
212
+ }
213
+ <?php
214
  }
215
 
216
+ /**
217
+ * Enqueues scripts and does wp_print_footer_scripts() so we can output customizer scripts.
218
+ *
219
+ * This breaks AMP validation in the customizer but is necessary for the live preview.
220
+ *
221
+ * @since 0.6
222
+ */
223
+ public function add_preview_scripts() {
224
+
225
+ // Bail if user can't customize anyway.
226
+ if ( ! current_user_can( 'customize' ) ) {
227
+ return;
228
  }
229
 
230
+ wp_enqueue_script( 'customize-selective-refresh' );
231
+ wp_enqueue_script( 'amp-customize-preview' );
232
+
233
+ /**
234
+ * Fires when plugins should enqueue their own scripts for the AMP Customizer preview.
235
+ *
236
+ * @since 0.4
237
+ * @param WP_Customize_Manager $wp_customize Manager.
238
+ */
239
+ do_action( 'amp_customizer_enqueue_preview_scripts', $this->wp_customize );
240
+
241
+ $this->wp_customize->customize_preview_settings();
242
+ $this->wp_customize->selective_refresh->export_preview_data();
243
+
244
+ wp_print_footer_scripts();
245
  }
246
 
247
+ /**
248
+ * Print templates needed for AMP in Customizer.
249
+ *
250
+ * @since 0.6
251
+ */
252
+ public function print_controls_templates() {
253
+ ?>
254
+ <script type="text/html" id="tmpl-customize-amp-enabled-toggle">
255
+ <div class="amp-toggle">
256
+ <# var elementIdPrefix = _.uniqueId( 'customize-amp-enabled-toggle' ); #>
257
+ <div id="{{ elementIdPrefix }}tooltip" aria-hidden="true" class="tooltip" role="tooltip">
258
+ {{ data.message }}
259
+ <# if ( data.url ) { #>
260
+ <a href="{{ data.url }}">{{ data.linkText }}</a>
261
+ <# } #>
262
+ </div>
263
+ <input id="{{ elementIdPrefix }}checkbox" type="checkbox" class="disabled" aria-describedby="{{ elementIdPrefix }}tooltip">
264
+ <span class="slider"></span>
265
+ <label for="{{ elementIdPrefix }}checkbox" class="screen-reader-text"><?php esc_html_e( 'AMP preview enabled', 'amp' ); ?></label>
266
+ </div>
267
+ </script>
268
+ <script type="text/html" id="tmpl-customize-amp-unavailable-notification">
269
+ <li class="notice notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.containerClasses || '' }}" data-code="{{ data.code }}" data-type="{{ data.type }}">
270
+ <div class="notification-message">
271
+ {{ data.message }}
272
+ <# if ( data.url ) { #>
273
+ <a href="{{ data.url }}">{{ data.linkText }}</a>
274
+ <# } #>
275
+ </div>
276
+ </li>
277
+ </script>
278
+ <?php
279
+ }
280
+
281
+ /**
282
+ * Whether the Customizer is AMP. This is always true since the AMP Customizer has been merged with the main Customizer.
283
+ *
284
+ * @deprecated 0.6
285
+ * @return bool
286
+ */
287
  public static function is_amp_customizer() {
288
+ _deprecated_function( __METHOD__, '0.6' );
289
+ return true;
290
  }
291
  }
includes/admin/class-amp-post-meta-box.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * AMP meta box settings.
4
+ *
5
+ * @package AMP
6
+ * @since 0.6
7
+ */
8
+
9
+ /**
10
+ * Post meta box class.
11
+ *
12
+ * @since 0.6
13
+ */
14
+ class AMP_Post_Meta_Box {
15
+
16
+ /**
17
+ * Assets handle.
18
+ *
19
+ * @since 0.6
20
+ * @var string
21
+ */
22
+ const ASSETS_HANDLE = 'amp-post-meta-box';
23
+
24
+ /**
25
+ * The enabled status post meta value.
26
+ *
27
+ * @since 0.6
28
+ * @var string
29
+ */
30
+ const ENABLED_STATUS = 'enabled';
31
+
32
+ /**
33
+ * The disabled status post meta value.
34
+ *
35
+ * @since 0.6
36
+ * @var string
37
+ */
38
+ const DISABLED_STATUS = 'disabled';
39
+
40
+ /**
41
+ * The status post meta key.
42
+ *
43
+ * @since 0.6
44
+ * @var string
45
+ */
46
+ const STATUS_POST_META_KEY = 'amp_status';
47
+
48
+ /**
49
+ * The field name for the enabled/disabled radio buttons.
50
+ *
51
+ * @since 0.6
52
+ * @var string
53
+ */
54
+ const STATUS_INPUT_NAME = 'amp_status';
55
+
56
+ /**
57
+ * The nonce name.
58
+ *
59
+ * @since 0.6
60
+ * @var string
61
+ */
62
+ const NONCE_NAME = 'amp-status-nonce';
63
+
64
+ /**
65
+ * The nonce action.
66
+ *
67
+ * @since 0.6
68
+ * @var string
69
+ */
70
+ const NONCE_ACTION = 'amp-update-status';
71
+
72
+ /**
73
+ * Initialize.
74
+ *
75
+ * @since 0.6
76
+ */
77
+ public function init() {
78
+ register_meta( 'post', self::STATUS_POST_META_KEY, array(
79
+ 'sanitize_callback' => array( $this, 'sanitize_status' ),
80
+ 'type' => 'string',
81
+ 'description' => __( 'AMP status.', 'amp' ),
82
+ 'show_in_rest' => true,
83
+ 'single' => true,
84
+ ) );
85
+
86
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
87
+ add_action( 'post_submitbox_misc_actions', array( $this, 'render_status' ) );
88
+ add_action( 'save_post', array( $this, 'save_amp_status' ) );
89
+ add_filter( 'preview_post_link', array( $this, 'preview_post_link' ) );
90
+ }
91
+
92
+ /**
93
+ * Sanitize status.
94
+ *
95
+ * @param string $status Status.
96
+ * @return string Sanitized status. Empty string when invalid.
97
+ */
98
+ public function sanitize_status( $status ) {
99
+ $status = strtolower( trim( $status ) );
100
+ if ( ! in_array( $status, array( 'enabled', 'disabled' ), true ) ) {
101
+ /*
102
+ * In lieu of actual validation being available, clear the status entirely
103
+ * so that the underlying default status will be used instead.
104
+ * In the future it would be ideal if register_meta() accepted a
105
+ * validate_callback as well which the REST API could leverage.
106
+ */
107
+ $status = '';
108
+ }
109
+ return $status;
110
+ }
111
+
112
+ /**
113
+ * Enqueue admin assets.
114
+ *
115
+ * @since 0.6
116
+ */
117
+ public function enqueue_admin_assets() {
118
+ $post = get_post();
119
+ $screen = get_current_screen();
120
+ $validate = (
121
+ isset( $screen->base )
122
+ &&
123
+ 'post' === $screen->base
124
+ );
125
+ if ( ! $validate ) {
126
+ return;
127
+ }
128
+
129
+ // Styles.
130
+ wp_enqueue_style(
131
+ self::ASSETS_HANDLE,
132
+ amp_get_asset_url( 'css/amp-post-meta-box.css' ),
133
+ false,
134
+ AMP__VERSION
135
+ );
136
+
137
+ // Scripts.
138
+ wp_enqueue_script(
139
+ self::ASSETS_HANDLE,
140
+ amp_get_asset_url( 'js/amp-post-meta-box.js' ),
141
+ array( 'jquery' ),
142
+ AMP__VERSION
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,
150
+ 'l10n' => array(
151
+ 'ampPreviewBtnLabel' => __( 'Preview changes in AMP (opens in new window)', 'amp' ),
152
+ ),
153
+ ) )
154
+ ) );
155
+ }
156
+
157
+ /**
158
+ * Render AMP status.
159
+ *
160
+ * @since 0.6
161
+ * @param WP_Post $post Post.
162
+ */
163
+ public function render_status( $post ) {
164
+ $verify = (
165
+ isset( $post->ID )
166
+ &&
167
+ current_user_can( 'edit_post', $post->ID )
168
+ );
169
+
170
+ if ( true !== $verify ) {
171
+ return;
172
+ }
173
+
174
+ $errors = AMP_Post_Type_Support::get_support_errors( $post );
175
+ $status = post_supports_amp( $post ) ? self::ENABLED_STATUS : self::DISABLED_STATUS;
176
+ $labels = array(
177
+ 'enabled' => __( 'Enabled', 'amp' ),
178
+ 'disabled' => __( 'Disabled', 'amp' ),
179
+ );
180
+
181
+ // The preceding variables are used inside the following amp-status.php template.
182
+ include_once AMP__DIR__ . '/templates/admin/amp-status.php';
183
+ }
184
+
185
+ /**
186
+ * Save AMP Status.
187
+ *
188
+ * @since 0.6
189
+ * @param int $post_id The Post ID.
190
+ */
191
+ public function save_amp_status( $post_id ) {
192
+ $verify = (
193
+ isset( $_POST[ self::NONCE_NAME ] )
194
+ &&
195
+ isset( $_POST[ self::STATUS_INPUT_NAME ] )
196
+ &&
197
+ wp_verify_nonce( sanitize_key( wp_unslash( $_POST[ self::NONCE_NAME ] ) ), self::NONCE_ACTION )
198
+ &&
199
+ current_user_can( 'edit_post', $post_id )
200
+ &&
201
+ ! wp_is_post_revision( $post_id )
202
+ &&
203
+ ! wp_is_post_autosave( $post_id )
204
+ );
205
+
206
+ if ( true === $verify ) {
207
+ update_post_meta(
208
+ $post_id,
209
+ self::STATUS_POST_META_KEY,
210
+ $_POST[ self::STATUS_INPUT_NAME ] // Note: The sanitize_callback has been supplied in the register_meta() call above.
211
+ );
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Modify post preview link.
217
+ *
218
+ * Add the AMP query var is the amp-preview flag is set.
219
+ *
220
+ * @since 0.6
221
+ *
222
+ * @param string $link The post preview link.
223
+ * @return string Preview URL.
224
+ */
225
+ public function preview_post_link( $link ) {
226
+ $is_amp = (
227
+ isset( $_POST['amp-preview'] ) // WPCS: CSRF ok.
228
+ &&
229
+ 'do-preview' === sanitize_key( wp_unslash( $_POST['amp-preview'] ) ) // WPCS: CSRF ok.
230
+ );
231
+
232
+ if ( $is_amp ) {
233
+ $link = add_query_arg( AMP_QUERY_VAR, true, $link );
234
+ }
235
+
236
+ return $link;
237
+ }
238
+
239
+ }
includes/admin/functions.php CHANGED
@@ -1,30 +1,37 @@
1
  <?php
2
- // Callbacks for adding AMP-related things to the admin.
3
-
4
- require_once( AMP__DIR__ . '/includes/options/class-amp-options-menu.php' );
5
- require_once( AMP__DIR__ . '/includes/options/views/class-amp-options-manager.php' );
 
6
 
 
 
 
 
 
 
7
  define( 'AMP_CUSTOMIZER_QUERY_VAR', 'customize_amp' );
8
 
9
  /**
10
  * Sets up the AMP template editor for the Customizer.
11
  */
12
  function amp_init_customizer() {
13
- require_once( AMP__DIR__ . '/includes/admin/class-amp-customizer.php' );
14
-
15
- // Drop core panels (menus, widgets) from the AMP customizer
16
- add_filter( 'customize_loaded_components', array( 'AMP_Template_Customizer', '_unregister_core_panels' ) );
17
-
18
- // Fire up the AMP Customizer
19
  add_action( 'customize_register', array( 'AMP_Template_Customizer', 'init' ), 500 );
20
 
21
- // Add some basic design settings + controls to the Customizer
22
  add_action( 'amp_init', array( 'AMP_Customizer_Design_Settings', 'init' ) );
23
 
24
- // Add a link to the Customizer
25
  add_action( 'admin_menu', 'amp_add_customizer_link' );
26
  }
27
 
 
 
 
 
 
28
  function amp_admin_get_preview_permalink() {
29
  /**
30
  * Filter the post type to retrieve the latest for use in the AMP template customizer.
@@ -33,15 +40,16 @@ function amp_admin_get_preview_permalink() {
33
  */
34
  $post_type = (string) apply_filters( 'amp_customizer_post_type', 'post' );
35
 
36
- if ( ! post_type_supports( $post_type, 'amp' ) ) {
37
- return;
38
  }
39
 
40
  $post_ids = get_posts( array(
41
- 'post_status' => 'publish',
42
- 'post_type' => $post_type,
43
- 'posts_per_page' => 1,
44
- 'fields' => 'ids',
 
45
  ) );
46
 
47
  if ( empty( $post_ids ) ) {
@@ -57,11 +65,10 @@ function amp_admin_get_preview_permalink() {
57
  * Registers a submenu page to access the AMP template editor panel in the Customizer.
58
  */
59
  function amp_add_customizer_link() {
60
- // Teensy little hack on menu_slug, but it works. No redirect!
61
  $menu_slug = add_query_arg( array(
62
- 'autofocus[panel]' => AMP_Template_Customizer::PANEL_ID,
63
- 'return' => rawurlencode( admin_url() ),
64
- AMP_CUSTOMIZER_QUERY_VAR => true,
65
  ), 'customize.php' );
66
 
67
  // Add the theme page.
@@ -74,23 +81,35 @@ function amp_add_customizer_link() {
74
  }
75
 
76
  /**
77
- * Registers a top-level menu for AMP configuration options
78
  */
79
  function amp_add_options_menu() {
80
  if ( ! is_admin() ) {
81
  return;
82
  }
83
 
84
- $show_options_menu = apply_filters( 'amp_options_menu_is_enabled', true );
85
- if ( true !== $show_options_menu ) {
 
 
 
 
 
 
 
86
  return;
87
  }
88
 
89
  $amp_options = new AMP_Options_Menu();
90
  $amp_options->init();
91
  }
92
- add_action( 'wp_loaded', 'amp_add_options_menu' );
93
 
 
 
 
 
 
 
94
  function amp_add_custom_analytics( $analytics ) {
95
  $analytics_entries = AMP_Options_Manager::get_option( 'analytics', array() );
96
 
@@ -108,4 +127,15 @@ function amp_add_custom_analytics( $analytics ) {
108
 
109
  return $analytics;
110
  }
111
- add_filter( 'amp_post_template_analytics', 'amp_add_custom_analytics' );
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
+ /**
3
+ * Callbacks for adding AMP-related things to the admin.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Obsolete constant for flagging when Customizer is opened for AMP.
10
+ *
11
+ * @deprecated
12
+ * @var string
13
+ */
14
  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
 
23
+ // Add some basic design settings + controls to the Customizer.
24
  add_action( 'amp_init', array( 'AMP_Customizer_Design_Settings', 'init' ) );
25
 
26
+ // Add a link to the Customizer.
27
  add_action( 'admin_menu', 'amp_add_customizer_link' );
28
  }
29
 
30
+ /**
31
+ * Get permalink for the first AMP-eligible post.
32
+ *
33
+ * @return string|null
34
+ */
35
  function amp_admin_get_preview_permalink() {
36
  /**
37
  * Filter the post type to retrieve the latest for use in the AMP template customizer.
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
 
47
  $post_ids = get_posts( array(
48
+ 'post_status' => 'publish',
49
+ 'post_password' => '',
50
+ 'post_type' => $post_type,
51
+ 'posts_per_page' => 1,
52
+ 'fields' => 'ids',
53
  ) );
54
 
55
  if ( empty( $post_ids ) ) {
65
  * Registers a submenu page to access the AMP template editor panel in the Customizer.
66
  */
67
  function amp_add_customizer_link() {
 
68
  $menu_slug = add_query_arg( array(
69
+ 'autofocus[panel]' => AMP_Template_Customizer::PANEL_ID,
70
+ 'url' => rawurlencode( amp_admin_get_preview_permalink() ),
71
+ 'return' => rawurlencode( admin_url() ),
72
  ), 'customize.php' );
73
 
74
  // Add the theme page.
81
  }
82
 
83
  /**
84
+ * Registers AMP settings.
85
  */
86
  function amp_add_options_menu() {
87
  if ( ! is_admin() ) {
88
  return;
89
  }
90
 
91
+ /**
92
+ * Filter whether to enable the AMP settings.
93
+ *
94
+ * @since 0.5
95
+ * @param bool $enable Whether to enable the AMP settings. Default true.
96
+ */
97
+ $short_circuit = apply_filters( 'amp_options_menu_is_enabled', true );
98
+
99
+ if ( true !== $short_circuit ) {
100
  return;
101
  }
102
 
103
  $amp_options = new AMP_Options_Menu();
104
  $amp_options->init();
105
  }
 
106
 
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
 
127
 
128
  return $analytics;
129
  }
130
+
131
+ /**
132
+ * Bootstrap AMP post meta box.
133
+ *
134
+ * This function must be invoked only once through the 'wp_loaded' action.
135
+ *
136
+ * @since 0.6
137
+ */
138
+ function amp_post_meta_box() {
139
+ $post_meta_box = new AMP_Post_Meta_Box();
140
+ $post_meta_box->init();
141
+ }
includes/amp-frontend-actions.php CHANGED
@@ -1,13 +1,28 @@
1
  <?php
2
- // Callbacks for adding AMP-related things to the main theme
 
 
 
 
3
 
4
  add_action( 'wp_head', 'amp_frontend_add_canonical' );
5
 
 
 
 
 
 
6
  function amp_frontend_add_canonical() {
 
 
 
 
 
 
7
  if ( false === apply_filters( 'amp_frontend_show_canonical', true ) ) {
8
  return;
9
  }
10
 
11
  $amp_url = amp_get_permalink( get_queried_object_id() );
12
- printf( '<link rel="amphtml" href="%s" />', esc_url( $amp_url ) );
13
  }
1
  <?php
2
+ /**
3
+ * Callbacks for adding AMP-related things to the main theme.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  add_action( 'wp_head', 'amp_frontend_add_canonical' );
9
 
10
+ /**
11
+ * Add amphtml link to frontend.
12
+ *
13
+ * @since 0.2
14
+ */
15
  function amp_frontend_add_canonical() {
16
+
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
  }
includes/amp-helper-functions.php CHANGED
@@ -1,43 +1,117 @@
1
  <?php
 
 
 
 
 
2
 
 
 
 
 
 
 
 
 
 
3
  function amp_get_permalink( $post_id ) {
 
 
 
 
 
 
 
 
 
 
 
4
  $pre_url = apply_filters( 'amp_pre_get_permalink', false, $post_id );
5
 
6
  if ( false !== $pre_url ) {
7
  return $pre_url;
8
  }
9
 
10
- $structure = get_option( 'permalink_structure' );
11
- if ( empty( $structure ) ) {
12
- $amp_url = add_query_arg( AMP_QUERY_VAR, 1, get_permalink( $post_id ) );
 
13
  } else {
14
  $amp_url = trailingslashit( get_permalink( $post_id ) ) . user_trailingslashit( AMP_QUERY_VAR, 'single_amp' );
15
  }
16
 
 
 
 
 
 
 
 
 
17
  return apply_filters( 'amp_get_permalink', $amp_url, $post_id );
18
  }
19
 
 
 
 
 
 
 
 
 
 
 
 
20
  function post_supports_amp( $post ) {
21
- // Because `add_rewrite_endpoint` doesn't let us target specific post_types :(
22
- if ( ! post_type_supports( $post->post_type, AMP_QUERY_VAR ) ) {
23
- return false;
24
- }
25
 
26
- if ( post_password_required( $post ) ) {
 
27
  return false;
28
  }
29
 
30
- if ( true === apply_filters( 'amp_skip_post', false, $post->ID, $post ) ) {
31
- return false;
32
- }
 
 
 
33
 
34
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
 
37
  /**
38
  * Are we currently on an AMP URL?
39
  *
40
  * Note: will always return `false` if called before the `parse_query` hook.
 
 
41
  */
42
  function is_amp_endpoint() {
43
  if ( 0 === did_action( 'parse_query' ) ) {
@@ -47,6 +121,12 @@ function is_amp_endpoint() {
47
  return false !== get_query_var( AMP_QUERY_VAR, false );
48
  }
49
 
 
 
 
 
 
 
50
  function amp_get_asset_url( $file ) {
51
  return plugins_url( sprintf( 'assets/%s', $file ), AMP__FILE__ );
52
  }
1
  <?php
2
+ /**
3
+ * AMP Helper Functions
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Retrieves the full AMP-specific permalink for the given post ID.
10
+ *
11
+ * @since 0.1
12
+ *
13
+ * @param int $post_id Post ID.
14
+ *
15
+ * @return string AMP permalink.
16
+ */
17
  function amp_get_permalink( $post_id ) {
18
+
19
+ /**
20
+ * Filters the AMP permalink to short-circuit normal generation.
21
+ *
22
+ * Returning a non-false value in this filter will cause the `get_permalink()` to get called and the `amp_get_permalink` filter to not apply.
23
+ *
24
+ * @since 0.4
25
+ *
26
+ * @param false $url Short-circuited URL.
27
+ * @param int $post_id Post ID.
28
+ */
29
  $pre_url = apply_filters( 'amp_pre_get_permalink', false, $post_id );
30
 
31
  if ( false !== $pre_url ) {
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
+ /**
44
+ * Filters AMP permalink.
45
+ *
46
+ * @since 0.2
47
+ *
48
+ * @param false $amp_url AMP URL.
49
+ * @param int $post_id Post ID.
50
+ */
51
  return apply_filters( 'amp_get_permalink', $amp_url, $post_id );
52
  }
53
 
54
+ /**
55
+ * Determine whether a given post supports AMP.
56
+ *
57
+ * @since 0.1
58
+ * @since 0.6 Returns false when post has meta to disable AMP.
59
+ * @see AMP_Post_Type_Support::get_support_errors()
60
+ *
61
+ * @param WP_Post $post Post.
62
+ *
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.
69
+ if ( ! empty( $errors ) ) {
70
  return false;
71
  }
72
 
73
+ switch ( get_post_meta( $post->ID, AMP_Post_Meta_Box::STATUS_POST_META_KEY, true ) ) {
74
+ case AMP_Post_Meta_Box::ENABLED_STATUS:
75
+ return true;
76
+
77
+ case AMP_Post_Meta_Box::DISABLED_STATUS:
78
+ return false;
79
 
80
+ // Disabled by default for custom page templates, page on front and page for posts.
81
+ default:
82
+ $enabled = (
83
+ ! (bool) get_page_template_slug( $post )
84
+ &&
85
+ ! (
86
+ 'page' === $post->post_type
87
+ &&
88
+ 'page' === get_option( 'show_on_front' )
89
+ &&
90
+ in_array( (int) $post->ID, array(
91
+ (int) get_option( 'page_on_front' ),
92
+ (int) get_option( 'page_for_posts' ),
93
+ ), true )
94
+ )
95
+ );
96
+
97
+ /**
98
+ * Filters whether default AMP status should be enabled or not.
99
+ *
100
+ * @since 0.6
101
+ *
102
+ * @param string $status Status.
103
+ * @param WP_Post $post Post.
104
+ */
105
+ return apply_filters( 'amp_post_status_default_enabled', $enabled, $post );
106
+ }
107
  }
108
 
109
  /**
110
  * Are we currently on an AMP URL?
111
  *
112
  * Note: will always return `false` if called before the `parse_query` hook.
113
+ *
114
+ * @return bool Whether it is the AMP endpoint.
115
  */
116
  function is_amp_endpoint() {
117
  if ( 0 === did_action( 'parse_query' ) ) {
121
  return false !== get_query_var( AMP_QUERY_VAR, false );
122
  }
123
 
124
+ /**
125
+ * Get AMP asset URL.
126
+ *
127
+ * @param string $file Relative path to file in assets directory.
128
+ * @return string URL.
129
+ */
130
  function amp_get_asset_url( $file ) {
131
  return plugins_url( sprintf( 'assets/%s', $file ), AMP__FILE__ );
132
  }
includes/amp-post-template-actions.php CHANGED
@@ -1,6 +1,13 @@
1
  <?php
2
- // Callbacks for adding content to an AMP template
 
 
 
 
3
 
 
 
 
4
  function amp_post_template_init_hooks() {
5
  add_action( 'amp_post_template_head', 'amp_post_template_add_title' );
6
  add_action( 'amp_post_template_head', 'amp_post_template_add_canonical' );
@@ -8,46 +15,82 @@ function amp_post_template_init_hooks() {
8
  add_action( 'amp_post_template_head', 'amp_post_template_add_fonts' );
9
  add_action( 'amp_post_template_head', 'amp_post_template_add_boilerplate_css' );
10
  add_action( 'amp_post_template_head', 'amp_post_template_add_schemaorg_metadata' );
 
11
  add_action( 'amp_post_template_css', 'amp_post_template_add_styles', 99 );
12
  add_action( 'amp_post_template_data', 'amp_post_template_add_analytics_script' );
13
  add_action( 'amp_post_template_footer', 'amp_post_template_add_analytics_data' );
14
  }
15
 
 
 
 
 
 
16
  function amp_post_template_add_title( $amp_template ) {
17
  ?>
18
  <title><?php echo esc_html( $amp_template->get( 'document_title' ) ); ?></title>
19
  <?php
20
  }
21
 
 
 
 
 
 
22
  function amp_post_template_add_canonical( $amp_template ) {
23
  ?>
24
  <link rel="canonical" href="<?php echo esc_url( $amp_template->get( 'canonical_url' ) ); ?>" />
25
  <?php
26
  }
27
 
 
 
 
 
 
28
  function amp_post_template_add_scripts( $amp_template ) {
29
  $scripts = $amp_template->get( 'amp_component_scripts', array() );
30
- foreach ( $scripts as $element => $script ) :
31
- $custom_type = ($element == 'amp-mustache') ? 'template' : 'element'; ?>
32
- <script custom-<?php echo esc_attr( $custom_type ); ?>="<?php echo esc_attr( $element ); ?>" src="<?php echo esc_url( $script ); ?>" async></script>
33
- <?php endforeach; ?>
34
- <script src="<?php echo esc_url( $amp_template->get( 'amp_runtime_script' ) ); ?>" async></script>
35
- <?php
 
 
 
 
 
 
 
36
  }
37
 
 
 
 
 
 
38
  function amp_post_template_add_fonts( $amp_template ) {
39
  $font_urls = $amp_template->get( 'font_urls', array() );
40
- foreach ( $font_urls as $slug => $url ) : ?>
41
- <link rel="stylesheet" href="<?php echo esc_url( $url ); ?>">
42
- <?php endforeach;
43
  }
44
 
45
- function amp_post_template_add_boilerplate_css( $amp_template ) {
 
 
 
46
  ?>
47
  <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>
48
  <?php
49
  }
50
 
 
 
 
 
 
51
  function amp_post_template_add_schemaorg_metadata( $amp_template ) {
52
  $metadata = $amp_template->get( 'metadata' );
53
  if ( empty( $metadata ) ) {
@@ -58,17 +101,28 @@ function amp_post_template_add_schemaorg_metadata( $amp_template ) {
58
  <?php
59
  }
60
 
 
 
 
 
 
61
  function amp_post_template_add_styles( $amp_template ) {
62
  $styles = $amp_template->get( 'post_amp_styles' );
63
  if ( ! empty( $styles ) ) {
64
- echo '/* Inline styles */' . PHP_EOL;
65
  foreach ( $styles as $selector => $declarations ) {
66
  $declarations = implode( ';', $declarations ) . ';';
67
- printf( '%1$s{%2$s}', $selector, $declarations );
68
  }
69
  }
70
  }
71
 
 
 
 
 
 
 
72
  function amp_post_template_add_analytics_script( $data ) {
73
  if ( ! empty( $data['amp_analytics'] ) ) {
74
  $data['amp_component_scripts']['amp-analytics'] = 'https://cdn.ampproject.org/v0/amp-analytics-0.1.js';
@@ -76,6 +130,11 @@ function amp_post_template_add_analytics_script( $data ) {
76
  return $data;
77
  }
78
 
 
 
 
 
 
79
  function amp_post_template_add_analytics_data( $amp_template ) {
80
  $analytics_entries = $amp_template->get( 'amp_analytics' );
81
  if ( empty( $analytics_entries ) ) {
@@ -84,7 +143,8 @@ function amp_post_template_add_analytics_data( $amp_template ) {
84
 
85
  foreach ( $analytics_entries as $id => $analytics_entry ) {
86
  if ( ! isset( $analytics_entry['type'], $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) {
87
- _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'Analytics entry for %s is missing one of the following keys: `type`, `attributes`, or `config_data` (array keys: %s)', 'amp' ), esc_html( $id ), esc_html( implode( ', ', array_keys( $analytics_entry ) ) ) ), '0.3.2' );
 
88
  continue;
89
  }
90
  $script_element = AMP_HTML_Utils::build_tag( 'script', array(
@@ -92,10 +152,19 @@ function amp_post_template_add_analytics_data( $amp_template ) {
92
  ), wp_json_encode( $analytics_entry['config_data'] ) );
93
 
94
  $amp_analytics_attr = array_merge( array(
95
- 'id' => $id,
96
  'type' => $analytics_entry['type'],
97
  ), $analytics_entry['attributes'] );
98
 
99
- echo AMP_HTML_Utils::build_tag( 'amp-analytics', $amp_analytics_attr, $script_element );
100
  }
101
  }
 
 
 
 
 
 
 
 
 
1
  <?php
2
+ /**
3
+ * Callbacks for adding content to an AMP template.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Register hooks.
10
+ */
11
  function amp_post_template_init_hooks() {
12
  add_action( 'amp_post_template_head', 'amp_post_template_add_title' );
13
  add_action( 'amp_post_template_head', 'amp_post_template_add_canonical' );
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' );
21
  add_action( 'amp_post_template_footer', 'amp_post_template_add_analytics_data' );
22
  }
23
 
24
+ /**
25
+ * Add title.
26
+ *
27
+ * @param AMP_Post_Template $amp_template template.
28
+ */
29
  function amp_post_template_add_title( $amp_template ) {
30
  ?>
31
  <title><?php echo esc_html( $amp_template->get( 'document_title' ) ); ?></title>
32
  <?php
33
  }
34
 
35
+ /**
36
+ * Add canonical link.
37
+ *
38
+ * @param AMP_Post_Template $amp_template Template.
39
+ */
40
  function amp_post_template_add_canonical( $amp_template ) {
41
  ?>
42
  <link rel="canonical" href="<?php echo esc_url( $amp_template->get( 'canonical_url' ) ); ?>" />
43
  <?php
44
  }
45
 
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
+ /**
69
+ * Print fonts.
70
+ *
71
+ * @param AMP_Post_Template $amp_template Template.
72
+ */
73
  function amp_post_template_add_fonts( $amp_template ) {
74
  $font_urls = $amp_template->get( 'font_urls', array() );
75
+ foreach ( $font_urls as $slug => $url ) {
76
+ printf( '<link rel="stylesheet" href="%s">', esc_url( esc_url( $url ) ) ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
77
+ }
78
  }
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 ) ) {
101
  <?php
102
  }
103
 
104
+ /**
105
+ * Print styles.
106
+ *
107
+ * @param AMP_Post_Template $amp_template Template.
108
+ */
109
  function amp_post_template_add_styles( $amp_template ) {
110
  $styles = $amp_template->get( 'post_amp_styles' );
111
  if ( ! empty( $styles ) ) {
112
+ echo '/* Inline styles */' . PHP_EOL; // WPCS: XSS OK.
113
  foreach ( $styles as $selector => $declarations ) {
114
  $declarations = implode( ';', $declarations ) . ';';
115
+ printf( '%1$s{%2$s}', $selector, $declarations ); // WPCS: XSS OK.
116
  }
117
  }
118
  }
119
 
120
+ /**
121
+ * Add analytics scripts.
122
+ *
123
+ * @param array $data Data.
124
+ * @return array Data.
125
+ */
126
  function amp_post_template_add_analytics_script( $data ) {
127
  if ( ! empty( $data['amp_analytics'] ) ) {
128
  $data['amp_component_scripts']['amp-analytics'] = 'https://cdn.ampproject.org/v0/amp-analytics-0.1.js';
130
  return $data;
131
  }
132
 
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 ) ) {
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(
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
+ /**
164
+ * Add generator metadata.
165
+ *
166
+ * @since 6.0
167
+ */
168
+ function amp_add_generator_metadata() {
169
+ printf( '<meta name="generator" content="%s" />', esc_attr( 'AMP Plugin v' . AMP__VERSION ) );
170
+ }
includes/class-amp-autoloader.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Autoloader
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Autoload the classes used by the AMP plugin.
10
+ *
11
+ * Class AMP_Autoloader
12
+ */
13
+ class AMP_Autoloader {
14
+
15
+ /**
16
+ * Map of Classname to relative filepath sans extension.
17
+ *
18
+ * @note We omitted the leading slash and the .php extension from each
19
+ * relative filepath because they are redundant and to include
20
+ * them would take up unnecessary bytes of memory at runtime.
21
+ *
22
+ * @example Format (note no leading / and no .php extension):
23
+ *
24
+ * array(
25
+ * 'Class_Name1' => 'subdir-of-includes/filename1',
26
+ * 'Class_Name2' => '2nd-subdir-of-includes/filename2',
27
+ * );
28
+ *
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',
35
+ 'AMP_Base_Embed_Handler' => 'includes/embeds/class-amp-base-embed-handler',
36
+ 'AMP_DailyMotion_Embed_Handler' => 'includes/embeds/class-amp-dailymotion-embed',
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',
45
+ 'AMP_YouTube_Embed_Handler' => 'includes/embeds/class-amp-youtube-embed',
46
+ 'FastImage' => 'includes/lib/fastimage/class-fastimage',
47
+ 'WillWashburn\Stream\Exception\StreamBufferTooSmallException' => 'includes/lib/fasterimage/Stream/Exception/StreamBufferTooSmallException',
48
+ 'WillWashburn\Stream\StreamableInterface' => 'includes/lib/fasterimage/Stream/StreamableInterface',
49
+ 'WillWashburn\Stream\Stream' => 'includes/lib/fasterimage/Stream/Stream',
50
+ 'FasterImage\Exception\InvalidImageException' => 'includes/lib/fasterimage/Exception/InvalidImageException',
51
+ 'FasterImage\ExifParser' => 'includes/lib/fasterimage/ExifParser',
52
+ 'FasterImage\ImageParser' => 'includes/lib/fasterimage/ImageParser',
53
+ 'FasterImage\FasterImage' => 'includes/lib/fasterimage/FasterImage',
54
+ 'AMP_Analytics_Options_Submenu' => 'includes/options/class-amp-analytics-options-submenu',
55
+ 'AMP_Options_Menu' => 'includes/options/class-amp-options-menu',
56
+ 'AMP_Options_Manager' => 'includes/options/class-amp-options-manager',
57
+ 'AMP_Analytics_Options_Submenu_Page' => 'includes/options/views/class-amp-analytics-options-submenu-page',
58
+ 'AMP_Options_Menu_Page' => 'includes/options/views/class-amp-options-menu-page',
59
+ 'AMP_Rule_Spec' => 'includes/sanitizers/class-amp-rule-spec',
60
+ 'AMP_Allowed_Tags_Generated' => 'includes/sanitizers/class-amp-allowed-tags-generated',
61
+ 'AMP_Audio_Sanitizer' => 'includes/sanitizers/class-amp-audio-sanitizer',
62
+ 'AMP_Base_Sanitizer' => 'includes/sanitizers/class-amp-base-sanitizer',
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',
69
+ 'AMP_Video_Sanitizer' => 'includes/sanitizers/class-amp-video-sanitizer',
70
+ 'AMP_Customizer_Design_Settings' => 'includes/settings/class-amp-customizer-design-settings',
71
+ 'AMP_Customizer_Settings' => 'includes/settings/class-amp-customizer-settings',
72
+ 'AMP_Content' => 'includes/templates/class-amp-content',
73
+ 'AMP_Content_Sanitizer' => 'includes/templates/class-amp-content-sanitizer',
74
+ 'AMP_Post_Template' => 'includes/templates/class-amp-post-template',
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',
83
+ );
84
+
85
+ /**
86
+ * Is registered.
87
+ *
88
+ * @var bool
89
+ */
90
+ public static $is_registered = false;
91
+
92
+ /**
93
+ * Perform the autoload on demand when requested by PHP runtime.
94
+ *
95
+ * Design Goal: Execute as few lines of code as possible each call.
96
+ *
97
+ * @since 0.6
98
+ *
99
+ * @param string $class_name Class name.
100
+ */
101
+ protected static function autoload( $class_name ) {
102
+ if ( ! isset( self::$_classmap[ $class_name ] ) ) {
103
+ return;
104
+ }
105
+ $filepath = self::$_classmap[ $class_name ];
106
+ require AMP__DIR__ . "/{$filepath}.php";
107
+ }
108
+
109
+ /**
110
+ * Registers this autoloader to PHP.
111
+ *
112
+ * @since 0.6
113
+ *
114
+ * Called at the end of this file; calling a second time has no effect.
115
+ */
116
+ public static function register() {
117
+ if ( ! self::$is_registered ) {
118
+ spl_autoload_register( array( __CLASS__, 'autoload' ) );
119
+ self::$is_registered = true;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Allows an extensions plugin to register a class and its file for autoloading
125
+ *
126
+ * @since 0.6
127
+ *
128
+ * @param string $class_name Full classname (include namespace if applicable).
129
+ * @param string $filepath Absolute filepath to class file, including .php extension.
130
+ */
131
+ public static function register_autoload_class( $class_name, $filepath ) {
132
+ self::$_classmap[ $class_name ] = '!' . $filepath;
133
+ }
134
+ }
includes/class-amp-post-type-support.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * AMP Post type support.
4
+ *
5
+ * @package AMP
6
+ * @since 0.6
7
+ */
8
+
9
+ /**
10
+ * Class AMP_Post_Type_Support.
11
+ */
12
+ class AMP_Post_Type_Support {
13
+
14
+ /**
15
+ * Get post types that plugin supports out of the box (which cannot be disabled).
16
+ *
17
+ * @return string[] Post types.
18
+ */
19
+ public static function get_builtin_supported_post_types() {
20
+ return array_filter( array( 'post' ), 'post_type_exists' );
21
+ }
22
+
23
+ /**
24
+ * Get post types that are eligible for AMP support.
25
+ *
26
+ * @since 0.6
27
+ * @return string[] Post types eligible for AMP.
28
+ */
29
+ public static function get_eligible_post_types() {
30
+ return array_merge(
31
+ self::get_builtin_supported_post_types(),
32
+ array( 'page' ),
33
+ array_values( get_post_types(
34
+ array(
35
+ 'public' => true,
36
+ '_builtin' => false,
37
+ ),
38
+ 'names'
39
+ ) )
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Declare support for post types.
45
+ *
46
+ * This function should only be invoked through the 'after_setup_theme' action to
47
+ * allow plugins/theme to overwrite the post types support.
48
+ *
49
+ * @since 0.6
50
+ */
51
+ public static function add_post_type_support() {
52
+ $post_types = array_merge(
53
+ self::get_builtin_supported_post_types(),
54
+ AMP_Options_Manager::get_option( 'supported_post_types', array() )
55
+ );
56
+ foreach ( $post_types as $post_type ) {
57
+ add_post_type_support( $post_type, AMP_QUERY_VAR );
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Return error codes for why a given post does not have AMP support.
63
+ *
64
+ * @since 0.6
65
+ *
66
+ * @param WP_Post|int $post Post.
67
+ * @return array Error codes for why a given post does not have AMP support.
68
+ */
69
+ public static function get_support_errors( $post ) {
70
+ if ( ! ( $post instanceof WP_Post ) ) {
71
+ $post = get_post( $post );
72
+ }
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
+
80
+ if ( post_password_required( $post ) ) {
81
+ $errors[] = 'password-protected';
82
+ }
83
+
84
+ /**
85
+ * Filters whether to skip the post from AMP.
86
+ *
87
+ * @since 0.3
88
+ *
89
+ * @param bool $skipped Skipped.
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
+
97
+ return $errors;
98
+ }
99
+ }
includes/embeds/class-amp-base-embed-handler.php CHANGED
@@ -1,8 +1,15 @@
1
  <?php
 
 
 
 
 
 
 
2
 
3
- // Used by some children
4
- require_once( AMP__DIR__ . '/includes/utils/class-amp-html-utils.php' );
5
-
6
  abstract class AMP_Base_Embed_Handler {
7
  protected $DEFAULT_WIDTH = 600;
8
  protected $DEFAULT_HEIGHT = 480;
1
  <?php
2
+ /**
3
+ * Class AMP_Base_Embed_Handler
4
+ *
5
+ * Used by some children.
6
+ *
7
+ * @package AMP
8
+ */
9
 
10
+ /**
11
+ * Class AMP_Base_Embed_Handler
12
+ */
13
  abstract class AMP_Base_Embed_Handler {
14
  protected $DEFAULT_WIDTH = 600;
15
  protected $DEFAULT_HEIGHT = 480;
includes/embeds/class-amp-dailymotion-embed.php CHANGED
@@ -1,8 +1,15 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
5
- // Much of this class is borrowed from Jetpack embeds
 
 
 
 
 
 
 
6
  class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
7
 
8
  const URL_PATTERN = '#https?:\/\/(www\.)?dailymotion\.com\/video\/.*#i';
1
  <?php
2
+ /**
3
+ * Class AMP_DailyMotion_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_DailyMotion_Embed_Handler
10
+ *
11
+ * Much of this class is borrowed from Jetpack embeds
12
+ */
13
  class AMP_DailyMotion_Embed_Handler extends AMP_Base_Embed_Handler {
14
 
15
  const URL_PATTERN = '#https?:\/\/(www\.)?dailymotion\.com\/video\/.*#i';
includes/embeds/class-amp-facebook-embed.php CHANGED
@@ -1,7 +1,13 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
 
 
 
 
 
 
5
  class AMP_Facebook_Embed_Handler extends AMP_Base_Embed_Handler {
6
  const URL_PATTERN = '#https?://(www\.)?facebook\.com/.*#i';
7
 
1
  <?php
2
+ /**
3
+ * Class AMP_Facebook_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Facebook_Embed_Handler
10
+ */
11
  class AMP_Facebook_Embed_Handler extends AMP_Base_Embed_Handler {
12
  const URL_PATTERN = '#https?://(www\.)?facebook\.com/.*#i';
13
 
includes/embeds/class-amp-gallery-embed.php CHANGED
@@ -1,7 +1,13 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
 
 
 
 
 
 
5
  class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
6
  private static $script_slug = 'amp-carousel';
7
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-carousel-0.1.js';
1
  <?php
2
+ /**
3
+ * Class AMP_Gallery_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
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';
includes/embeds/class-amp-instagram-embed.php CHANGED
@@ -1,8 +1,15 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
5
- // Much of this class is borrowed from Jetpack embeds
 
 
 
 
 
 
 
6
  class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
7
  const SHORT_URL_HOST = 'instagr.am';
8
  const URL_PATTERN = '#http(s?)://(www\.)?instagr(\.am|am\.com)/p/([^/?]+)#i';
1
  <?php
2
+ /**
3
+ * Class AMP_Instagram_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Instagram_Embed_Handler
10
+ *
11
+ * Much of this class is borrowed from Jetpack embeds
12
+ */
13
  class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
14
  const SHORT_URL_HOST = 'instagr.am';
15
  const URL_PATTERN = '#http(s?)://(www\.)?instagr(\.am|am\.com)/p/([^/?]+)#i';
includes/embeds/class-amp-pinterest-embed.php CHANGED
@@ -1,7 +1,13 @@
1
  <?php
 
 
 
 
 
2
 
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php');
4
-
 
5
  class AMP_Pinterest_Embed_Handler extends AMP_Base_Embed_Handler {
6
 
7
  const URL_PATTERN = '#https?://(www\.)?pinterest\.com/pin/.*#i';
1
  <?php
2
+ /**
3
+ * Class AMP_Pinterest_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Class AMP_Pinterest_Embed_Handler
10
+ */
11
  class AMP_Pinterest_Embed_Handler extends AMP_Base_Embed_Handler {
12
 
13
  const URL_PATTERN = '#https?://(www\.)?pinterest\.com/pin/.*#i';
includes/embeds/class-amp-soundcloud-embed.php CHANGED
@@ -1,24 +1,59 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
 
 
 
 
 
 
 
 
5
  class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
6
- const URL_PATTERN = '#https?://api\.soundcloud\.com/tracks/.*#i';
 
 
 
 
 
7
  protected $DEFAULT_HEIGHT = 200;
8
 
 
 
 
 
 
9
  private static $script_slug = 'amp-soundcloud';
 
 
 
 
 
 
10
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-soundcloud-0.1.js';
11
 
 
 
 
12
  public function register_embed() {
13
- wp_embed_register_handler( 'amp-soundcloud', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
14
  add_shortcode( 'soundcloud', array( $this, 'shortcode' ) );
 
15
  }
16
 
 
 
 
17
  public function unregister_embed() {
18
- wp_embed_unregister_handler( 'amp-soundcloud', -1 );
19
  remove_shortcode( 'soundcloud' );
 
20
  }
21
 
 
 
 
 
 
22
  public function get_scripts() {
23
  if ( ! $this->did_convert_elements ) {
24
  return array();
@@ -27,21 +62,81 @@ class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
27
  return array( self::$script_slug => self::$script_src );
28
  }
29
 
30
- public function oembed( $matches, $attr, $url, $rawattr ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  $track_id = $this->get_track_id_from_url( $url );
32
- return $this->render( array(
33
- 'track_id' => $track_id,
34
- ) );
35
  }
36
 
37
- public function shortcode( $attr ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- $track_id = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- if ( isset( $attr['id'] ) ) {
42
- $track_id = $attr['id'];
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  } else {
44
- $url = false;
 
 
 
 
45
  if ( isset( $attr['url'] ) ) {
46
  $url = $attr['url'];
47
  } elseif ( isset( $attr[0] ) ) {
@@ -50,27 +145,28 @@ class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
50
  $url = shortcode_new_to_old_params( $attr );
51
  }
52
 
53
- if ( ! empty( $url ) ) {
54
- $track_id = $this->get_track_id_from_url( $url );
55
  }
56
  }
57
-
58
- if ( empty( $track_id ) ) {
59
- return '';
60
- }
61
-
62
- return $this->render( array(
63
- 'track_id' => $track_id,
64
- ) );
65
  }
66
 
 
 
 
 
 
 
 
67
  public function render( $args ) {
68
  $args = wp_parse_args( $args, array(
69
  'track_id' => false,
 
70
  ) );
71
 
72
  if ( empty( $args['track_id'] ) ) {
73
- return AMP_HTML_Utils::build_tag( 'a', array( 'href' => esc_url( $args['url'] ), 'class' => 'amp-wp-embed-fallback' ), esc_html( $args['url'] ) );
74
  }
75
 
76
  $this->did_convert_elements = true;
@@ -79,17 +175,40 @@ class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
79
  'amp-soundcloud',
80
  array(
81
  'data-trackid' => $args['track_id'],
82
- 'layout' => 'fixed-height',
83
- 'height' => $this->args['height'],
84
  )
85
  );
86
  }
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  private function get_track_id_from_url( $url ) {
89
  $parsed_url = AMP_WP_Utils::parse_url( $url );
90
- $tok = explode( '/', $parsed_url['path'] );
91
- $track_id = $tok[2];
92
-
93
- return $track_id;
94
  }
95
  }
1
  <?php
2
+ /**
3
+ * Class AMP_SoundCloud_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_SoundCloud_Embed_Handler
10
+ *
11
+ * @since 0.5
12
+ */
13
  class AMP_SoundCloud_Embed_Handler extends AMP_Base_Embed_Handler {
14
+
15
+ /**
16
+ * Default height.
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.
38
+ */
39
  public function register_embed() {
 
40
  add_shortcode( 'soundcloud', array( $this, 'shortcode' ) );
41
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 2 );
42
  }
43
 
44
+ /**
45
+ * Unregister embed.
46
+ */
47
  public function unregister_embed() {
 
48
  remove_shortcode( 'soundcloud' );
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();
62
  return array( self::$script_slug => self::$script_src );
63
  }
64
 
65
+ /**
66
+ * Render oEmbed.
67
+ *
68
+ * @see \WP_Embed::shortcode()
69
+ *
70
+ * @deprecated Core's oEmbed handler is now used instead, with embed_oembed_html filter used to convert to AMP.
71
+ * @param array $matches URL pattern matches.
72
+ * @param array $attr Shortcode attribues.
73
+ * @param string $url URL.
74
+ * @return string Rendered oEmbed.
75
+ */
76
+ public function oembed( $matches, $attr, $url ) {
77
+ _deprecated_function( __METHOD__, '0.6' );
78
+ unset( $matches, $attr );
79
  $track_id = $this->get_track_id_from_url( $url );
80
+ return $this->render( compact( 'track_id' ) );
 
 
81
  }
82
 
83
+ /**
84
+ * Filter oEmbed HTML for SoundCloud to convert to AMP.
85
+ *
86
+ * @param string $cache Cache for oEmbed.
87
+ * @param string $url Embed URL.
88
+ * @return string Embed.
89
+ */
90
+ public function filter_embed_oembed_html( $cache, $url ) {
91
+ $parsed_url = wp_parse_url( $url );
92
+ if ( false === strpos( $parsed_url['host'], 'soundcloud.com' ) ) {
93
+ return $cache;
94
+ }
95
+ return $this->parse_amp_component_from_iframe( $cache );
96
+ }
97
 
98
+ /**
99
+ * Parse AMP component from iframe.
100
+ *
101
+ * @param string $html HTML.
102
+ * @return string AMP component or empty if unable to determine SoundCloud ID.
103
+ */
104
+ private function parse_amp_component_from_iframe( $html ) {
105
+ $embed = '';
106
+ if ( preg_match( '#<iframe.+?src="(?P<url>.+?)".*>#', $html, $matches ) ) {
107
+ $src = html_entity_decode( $matches['url'], ENT_QUOTES );
108
+ $query = array();
109
+ parse_str( wp_parse_url( $src, PHP_URL_QUERY ), $query );
110
+ if ( ! empty( $query['url'] ) ) {
111
+ $embed = $this->render( array(
112
+ 'track_id' => $this->get_track_id_from_url( $query['url'] ),
113
+ ) );
114
+ }
115
+ }
116
+ return $embed;
117
+ }
118
 
119
+ /**
120
+ * Render shortcode.
121
+ *
122
+ * @param array $attr Shortcode attributes.
123
+ * @param string $content Shortcode content.
124
+ * @return string Rendered shortcode.
125
+ */
126
+ public function shortcode( $attr, $content = null ) {
127
+ $output = '';
128
+ if ( function_exists( 'soundcloud_shortcode' ) ) {
129
+ if ( empty( $attr['url'] ) && ! empty( $attr['id'] ) ) {
130
+ $attr['url'] = 'https://api.soundcloud.com/tracks/' . intval( $attr['id'] );
131
+ }
132
+ $output = soundcloud_shortcode( $attr, $content );
133
+ $output = $this->parse_amp_component_from_iframe( $output );
134
  } else {
135
+ $url = null;
136
+ if ( isset( $attr['id'] ) ) {
137
+ $url = 'https://w.soundcloud.com/player/?url=https%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F' . intval( $attr['id'] );
138
+ }
139
+
140
  if ( isset( $attr['url'] ) ) {
141
  $url = $attr['url'];
142
  } elseif ( isset( $attr[0] ) ) {
145
  $url = shortcode_new_to_old_params( $attr );
146
  }
147
 
148
+ if ( $url ) {
149
+ $output = $this->render_embed_fallback( $url );
150
  }
151
  }
152
+ return $output;
 
 
 
 
 
 
 
153
  }
154
 
155
+ /**
156
+ * Render embed.
157
+ *
158
+ * @param array $args Args.
159
+ * @return string Rendered embed.
160
+ * @global WP_Embed $wp_embed
161
+ */
162
  public function render( $args ) {
163
  $args = wp_parse_args( $args, array(
164
  'track_id' => false,
165
+ 'url' => null,
166
  ) );
167
 
168
  if ( empty( $args['track_id'] ) ) {
169
+ return $this->render_embed_fallback( $args['url'] );
170
  }
171
 
172
  $this->did_convert_elements = true;
175
  'amp-soundcloud',
176
  array(
177
  'data-trackid' => $args['track_id'],
178
+ 'layout' => 'fixed-height',
179
+ 'height' => $this->args['height'],
180
  )
181
  );
182
  }
183
 
184
+ /**
185
+ * Render embed fallback.
186
+ *
187
+ * @param string $url URL.
188
+ * @returns string
189
+ */
190
+ private function render_embed_fallback( $url ) {
191
+ return AMP_HTML_Utils::build_tag( 'a',
192
+ array(
193
+ 'href' => esc_url( $url ),
194
+ 'class' => 'amp-wp-embed-fallback',
195
+ ),
196
+ esc_html( $url )
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Get track_id from URL.
202
+ *
203
+ * @param string $url URL.
204
+ *
205
+ * @return string Track ID or empty string if none matched.
206
+ */
207
  private function get_track_id_from_url( $url ) {
208
  $parsed_url = AMP_WP_Utils::parse_url( $url );
209
+ if ( ! preg_match( '#tracks/(?P<track_id>[^/]+)#', $parsed_url['path'], $matches ) ) {
210
+ return '';
211
+ }
212
+ return $matches['track_id'];
213
  }
214
  }
includes/embeds/class-amp-twitter-embed.php CHANGED
@@ -1,8 +1,15 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
5
- // Much of this class is borrowed from Jetpack embeds
 
 
 
 
 
 
 
6
  class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
7
  const URL_PATTERN = '#http(s|):\/\/twitter\.com(\/\#\!\/|\/)([a-zA-Z0-9_]{1,20})\/status(es)*\/(\d+)#i';
8
 
1
  <?php
2
+ /**
3
+ * Class AMP_Twitter_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Twitter_Embed_Handler
10
+ *
11
+ * Much of this class is borrowed from Jetpack embeds
12
+ */
13
  class AMP_Twitter_Embed_Handler extends AMP_Base_Embed_Handler {
14
  const URL_PATTERN = '#http(s|):\/\/twitter\.com(\/\#\!\/|\/)([a-zA-Z0-9_]{1,20})\/status(es)*\/(\d+)#i';
15
 
includes/embeds/class-amp-vimeo-embed.php CHANGED
@@ -1,8 +1,15 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
5
- // Much of this class is borrowed from Jetpack embeds
 
 
 
 
 
 
 
6
  class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
7
 
8
  const URL_PATTERN = '#https?:\/\/(www\.)?vimeo\.com\/.*#i';
1
  <?php
2
+ /**
3
+ * Class AMP_Vimeo_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Vimeo_Embed_Handler
10
+ *
11
+ * Much of this class is borrowed from Jetpack embeds
12
+ */
13
  class AMP_Vimeo_Embed_Handler extends AMP_Base_Embed_Handler {
14
 
15
  const URL_PATTERN = '#https?:\/\/(www\.)?vimeo\.com\/.*#i';
includes/embeds/class-amp-vine-embed.php CHANGED
@@ -1,7 +1,13 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
 
 
 
 
 
 
5
  class AMP_Vine_Embed_Handler extends AMP_Base_Embed_Handler {
6
  const URL_PATTERN = '#https?://vine\.co/v/([^/?]+)#i';
7
 
1
  <?php
2
+ /**
3
+ * Class AMP_Vine_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Vine_Embed_Handler
10
+ */
11
  class AMP_Vine_Embed_Handler extends AMP_Base_Embed_Handler {
12
  const URL_PATTERN = '#https?://vine\.co/v/([^/?]+)#i';
13
 
includes/embeds/class-amp-youtube-embed.php CHANGED
@@ -1,8 +1,15 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
4
-
5
- // Much of this class is borrowed from Jetpack embeds
 
 
 
 
 
 
 
6
  class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
7
  const SHORT_URL_HOST = 'youtu.be';
8
  // Only handling single videos. Playlists are handled elsewhere.
1
  <?php
2
+ /**
3
+ * Class AMP_YouTube_Embed_Handler
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_YouTube_Embed_Handler
10
+ *
11
+ * Much of this class is borrowed from Jetpack embeds.
12
+ */
13
  class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
14
  const SHORT_URL_HOST = 'youtu.be';
15
  // Only handling single videos. Playlists are handled elsewhere.
includes/lib/fasterimage/amp-fasterimage.php CHANGED
@@ -1,25 +1,27 @@
1
  <?php
 
 
 
 
 
2
 
 
 
 
 
 
 
 
3
  function amp_load_fasterimage_classes() {
4
- // We're not using composer to pull in FasterImage so we need to load the files manually
5
- $fasterimage__DIR__ = dirname( __FILE__ );
6
-
7
- // Stream files
8
- require_once( $fasterimage__DIR__ . '/Stream/Exception/StreamBufferTooSmallException.php' );
9
- require_once( $fasterimage__DIR__ . '/Stream/StreamableInterface.php' );
10
- require_once( $fasterimage__DIR__ . '/Stream/Stream.php' );
11
-
12
- // FasterImage files
13
- require_once( $fasterimage__DIR__ . '/Exception/InvalidImageException.php' );
14
- require_once( $fasterimage__DIR__ . '/ExifParser.php' );
15
- require_once( $fasterimage__DIR__ . '/ImageParser.php' );
16
- require_once( $fasterimage__DIR__ . '/FasterImage.php' );
17
  }
18
 
 
 
 
 
 
 
19
  function amp_get_fasterimage_client( $user_agent ) {
20
- if ( ! class_exists( 'FasterImage\FasterImage' ) ) {
21
- amp_load_fasterimage_classes();
22
- }
23
-
24
  return new FasterImage\FasterImage( $user_agent );
25
  }
1
  <?php
2
+ /**
3
+ * Functions for FasterImage.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Load classes for FasterImage.
10
+ *
11
+ * This is obsolete now that there is an autoloader.
12
+ *
13
+ * @deprecated
14
+ */
15
  function amp_load_fasterimage_classes() {
16
+ _deprecated_function( __FUNCTION__, '0.6' );
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
+ /**
20
+ * Get FasterImage client for user agent.
21
+ *
22
+ * @param string $user_agent User Agent.
23
+ * @return \FasterImage\FasterImage Instance.
24
+ */
25
  function amp_get_fasterimage_client( $user_agent ) {
 
 
 
 
26
  return new FasterImage\FasterImage( $user_agent );
27
  }
includes/options/class-amp-analytics-options-submenu.php CHANGED
@@ -1,8 +1,13 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/options/views/class-amp-analytics-options-submenu-page.php' );
4
- require_once( AMP__DIR__ . '/includes/utils/class-amp-html-utils.php' );
5
-
 
 
 
 
 
6
  class AMP_Analytics_Options_Submenu {
7
 
8
  private $parent_menu_slug;
@@ -37,7 +42,10 @@ class AMP_Analytics_Options_Submenu {
37
  public function amp_options_styles() {
38
  ?>
39
  <style>
40
- .analytics-data-container #delete {
 
 
 
41
  background: red;
42
  border-color: red;
43
  text-shadow: 0 0 0;
1
  <?php
2
+ /**
3
+ * Class AMP_Analytics_Options_Submenu
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Analytics_Options_Submenu
10
+ */
11
  class AMP_Analytics_Options_Submenu {
12
 
13
  private $parent_menu_slug;
42
  public function amp_options_styles() {
43
  ?>
44
  <style>
45
+ .analytics-data-container .button.delete,
46
+ .analytics-data-container .button.delete:hover,
47
+ .analytics-data-container .button.delete:active,
48
+ .analytics-data-container .button.delete:focus {
49
  background: red;
50
  border-color: red;
51
  text-shadow: 0 0 0;
includes/options/class-amp-options-manager.php ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Options_Manager.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Options_Manager
10
+ */
11
+ class AMP_Options_Manager {
12
+
13
+ /**
14
+ * Option name.
15
+ *
16
+ * @var string
17
+ */
18
+ const OPTION_NAME = 'amp-options';
19
+
20
+ /**
21
+ * Register settings.
22
+ */
23
+ public static function register_settings() {
24
+ register_setting(
25
+ self::OPTION_NAME,
26
+ self::OPTION_NAME,
27
+ array(
28
+ 'type' => 'array',
29
+ 'sanitize_callback' => array( __CLASS__, 'validate_options' ),
30
+ )
31
+ );
32
+
33
+ add_action( 'update_option_' . self::OPTION_NAME, 'flush_rewrite_rules' );
34
+ }
35
+
36
+ /**
37
+ * Get plugin options.
38
+ *
39
+ * @return array Options.
40
+ */
41
+ public static function get_options() {
42
+ return get_option( self::OPTION_NAME, array() );
43
+ }
44
+
45
+ /**
46
+ * Get plugin option.
47
+ *
48
+ * @param string $option Plugin option name.
49
+ * @param bool $default Default value.
50
+ *
51
+ * @return mixed Option value.
52
+ */
53
+ public static function get_option( $option, $default = false ) {
54
+ $amp_options = self::get_options();
55
+
56
+ if ( ! isset( $amp_options[ $option ] ) ) {
57
+ return $default;
58
+ }
59
+
60
+ return $amp_options[ $option ];
61
+ }
62
+
63
+ /**
64
+ * Validate options.
65
+ *
66
+ * @param array $new_options Plugin options.
67
+ * @return array Options.
68
+ */
69
+ public static function validate_options( $new_options ) {
70
+ $defaults = array(
71
+ 'supported_post_types' => array(),
72
+ 'analytics' => array(),
73
+ );
74
+
75
+ $options = array_merge(
76
+ $defaults,
77
+ self::get_options()
78
+ );
79
+
80
+ // Validate post type support.
81
+ if ( isset( $new_options['supported_post_types'] ) ) {
82
+ $options['supported_post_types'] = array();
83
+ foreach ( $new_options['supported_post_types'] as $post_type ) {
84
+ if ( ! post_type_exists( $post_type ) ) {
85
+ add_settings_error( self::OPTION_NAME, 'unknown_post_type', __( 'Unrecognized post type.', 'amp' ) );
86
+ } else {
87
+ $options['supported_post_types'][] = $post_type;
88
+ }
89
+ }
90
+ }
91
+
92
+ // Validate analytics.
93
+ if ( isset( $new_options['analytics'] ) ) {
94
+ foreach ( $new_options['analytics'] as $id => $data ) {
95
+
96
+ // Check save/delete pre-conditions and proceed if correct.
97
+ if ( empty( $data['type'] ) || empty( $data['config'] ) ) {
98
+ add_settings_error( self::OPTION_NAME, 'missing_analytics_vendor_or_config', __( 'Missing vendor type or config.', 'amp' ) );
99
+ continue;
100
+ }
101
+
102
+ // Validate JSON configuration.
103
+ $is_valid_json = AMP_HTML_Utils::is_valid_json( $data['config'] );
104
+ if ( ! $is_valid_json ) {
105
+ add_settings_error( self::OPTION_NAME, 'invalid_analytics_config_json', __( 'Invalid analytics config JSON.', 'amp' ) );
106
+ continue;
107
+ }
108
+
109
+ $entry_vendor_type = sanitize_key( $data['type'] );
110
+ $entry_config = trim( $data['config'] );
111
+
112
+ if ( ! empty( $data['id'] ) && '__new__' !== $data['id'] ) {
113
+ $entry_id = sanitize_key( $data['id'] );
114
+ } else {
115
+
116
+ // Generate a hash string to uniquely identify this entry.
117
+ $entry_id = substr( md5( $entry_vendor_type . $entry_config ), 0, 12 );
118
+
119
+ // Avoid duplicates.
120
+ if ( isset( $options['analytics'][ $entry_id ] ) ) {
121
+ add_settings_error( self::OPTION_NAME, 'duplicate_analytics_entry', __( 'Duplicate analytics entry found.', 'amp' ) );
122
+ continue;
123
+ }
124
+ }
125
+
126
+ if ( isset( $data['delete'] ) ) {
127
+ unset( $options['analytics'][ $entry_id ] );
128
+ } else {
129
+ $options['analytics'][ $entry_id ] = array(
130
+ 'type' => $entry_vendor_type,
131
+ 'config' => $entry_config,
132
+ );
133
+ }
134
+ }
135
+ }
136
+
137
+ return $options;
138
+ }
139
+
140
+
141
+ /**
142
+ * Check for errors with updating the supported post types.
143
+ *
144
+ * @since 0.6
145
+ * @see add_settings_error()
146
+ */
147
+ public static function check_supported_post_type_update_errors() {
148
+ $builtin_support = AMP_Post_Type_Support::get_builtin_supported_post_types();
149
+ $supported_types = self::get_option( 'supported_post_types', array() );
150
+ foreach ( AMP_Post_Type_Support::get_eligible_post_types() as $name ) {
151
+ $post_type = get_post_type_object( $name );
152
+ if ( empty( $post_type ) || in_array( $post_type->name, $builtin_support, true ) ) {
153
+ continue;
154
+ }
155
+
156
+ $post_type_supported = post_type_supports( $post_type->name, AMP_QUERY_VAR );
157
+ $is_support_elected = in_array( $post_type->name, $supported_types, true );
158
+
159
+ $error = null;
160
+ $code = null;
161
+ if ( $is_support_elected && ! $post_type_supported ) {
162
+ /* translators: %s: Post type name. */
163
+ $error = __( '"%s" could not be activated because support is removed by a plugin or theme', 'amp' );
164
+ $code = sprintf( '%s_activation_error', $post_type->name );
165
+ } elseif ( ! $is_support_elected && $post_type_supported ) {
166
+ /* translators: %s: Post type name. */
167
+ $error = __( '"%s" could not be deactivated because support is added by a plugin or theme', 'amp' );
168
+ $code = sprintf( '%s_deactivation_error', $post_type->name );
169
+ }
170
+
171
+ if ( isset( $error, $code ) ) {
172
+ add_settings_error(
173
+ self::OPTION_NAME,
174
+ $code,
175
+ sprintf(
176
+ $error,
177
+ isset( $post_type->label ) ? $post_type->label : $post_type->name
178
+ )
179
+ );
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Update plugin option.
186
+ *
187
+ * @param string $option Plugin option name.
188
+ * @param mixed $value Plugin option value.
189
+ *
190
+ * @return bool Whether update succeeded.
191
+ */
192
+ public static function update_option( $option, $value ) {
193
+ $amp_options = self::get_options();
194
+
195
+ $amp_options[ $option ] = $value;
196
+
197
+ return update_option( self::OPTION_NAME, $amp_options, false );
198
+ }
199
+
200
+ /**
201
+ * Handle analytics submission.
202
+ */
203
+ public static function handle_analytics_submit() {
204
+
205
+ // Request must come from user with right capabilities.
206
+ if ( ! current_user_can( 'manage_options' ) ) {
207
+ wp_die( esc_html__( 'Sorry, you do not have the necessary permissions to perform this action', 'amp' ) );
208
+ }
209
+
210
+ // Ensure request is coming from analytics option form.
211
+ check_admin_referer( 'analytics-options', 'analytics-options' );
212
+
213
+ if ( isset( $_POST['amp-options']['analytics'] ) ) {
214
+ self::update_option( 'analytics', wp_unslash( $_POST['amp-options']['analytics'] ) );
215
+
216
+ $errors = get_settings_errors( self::OPTION_NAME );
217
+ if ( empty( $errors ) ) {
218
+ add_settings_error( self::OPTION_NAME, 'settings_updated', __( 'The analytics entry was successfully saved!', 'amp' ), 'updated' );
219
+ $errors = get_settings_errors( self::OPTION_NAME );
220
+ }
221
+ set_transient( 'settings_errors', $errors );
222
+ }
223
+
224
+ /*
225
+ * Redirect to keep the user in the analytics options page.
226
+ * Wrap in is_admin() to enable phpunit tests to exercise this code.
227
+ */
228
+ wp_safe_redirect( admin_url( 'admin.php?page=amp-analytics-options&settings-updated=1' ) );
229
+ exit;
230
+ }
231
+
232
+ /**
233
+ * Update analytics options.
234
+ *
235
+ * @codeCoverageIgnore
236
+ * @deprecated
237
+ * @param array $data Unsanitized unslashed data.
238
+ * @return bool Whether options were updated.
239
+ */
240
+ public static function update_analytics_options( $data ) {
241
+ _deprecated_function( __METHOD__, '0.6', __CLASS__ . '::update_option' );
242
+ return self::update_option( 'analytics', wp_unslash( $data ) );
243
+ }
244
+ }
includes/options/class-amp-options-menu.php CHANGED
@@ -1,54 +1,133 @@
1
  <?php
 
 
 
 
 
2
 
3
- require_once( AMP__DIR__ . '/includes/options/class-amp-analytics-options-submenu.php' );
4
- require_once( AMP__DIR__ . '/includes/options/views/class-amp-options-menu-page.php' );
5
- require_once( AMP__DIR__ . '/includes/options/views/class-amp-options-manager.php' );
6
-
7
  class AMP_Options_Menu {
8
- const ICON_BASE64_SVG = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iNjJweCIgaGVpZ2h0PSI2MnB4IiB2aWV3Qm94PSIwIDAgNjIgNjIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+ICAgICAgICA8dGl0bGU+QU1QLUJyYW5kLUJsYWNrLUljb248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iYW1wLWxvZ28taW50ZXJuYWwtc2l0ZSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICAgICAgICA8ZyBpZD0iQU1QLUJyYW5kLUJsYWNrLUljb24iIGZpbGw9IiMwMDAwMDAiPiAgICAgICAgICAgIDxwYXRoIGQ9Ik00MS42Mjg4NjY3LDI4LjE2MTQzMzMgTDI4LjYyNDM2NjcsNDkuODAzNTY2NyBMMjYuMjY4MzY2Nyw0OS44MDM1NjY3IEwyOC41OTc1LDM1LjcwMTY2NjcgTDIxLjM4MzgsMzUuNzEwOTY2NyBDMjEuMzgzOCwzNS43MTA5NjY3IDIxLjMxNTYsMzUuNzEzMDMzMyAyMS4yODM1NjY3LDM1LjcxMzAzMzMgQzIwLjYzMzYsMzUuNzEzMDMzMyAyMC4xMDc2MzMzLDM1LjE4NzA2NjcgMjAuMTA3NjMzMywzNC41MzcxIEMyMC4xMDc2MzMzLDM0LjI1ODEgMjAuMzY3LDMzLjc4NTg2NjcgMjAuMzY3LDMzLjc4NTg2NjcgTDMzLjMyOTEzMzMsMTIuMTY5NTY2NyBMMzUuNzI0NCwxMi4xNzk5IEwzMy4zMzYzNjY3LDI2LjMwMzUgTDQwLjU4NzI2NjcsMjYuMjk0MiBDNDAuNTg3MjY2NywyNi4yOTQyIDQwLjY2NDc2NjcsMjYuMjkzMTY2NyA0MC43MDE5NjY3LDI2LjI5MzE2NjcgQzQxLjM1MTkzMzMsMjYuMjkzMTY2NyA0MS44Nzc5LDI2LjgxOTEzMzMgNDEuODc3OSwyNy40NjkxIEM0MS44Nzc5LDI3LjczMjYgNDEuNzc0NTY2NywyNy45NjQwNjY3IDQxLjYyNzgzMzMsMjguMTYwNCBMNDEuNjI4ODY2NywyOC4xNjE0MzMzIFogTTMxLDAgQzEzLjg3ODcsMCAwLDEzLjg3OTczMzMgMCwzMSBDMCw0OC4xMjEzIDEzLjg3ODcsNjIgMzEsNjIgQzQ4LjEyMDI2NjcsNjIgNjIsNDguMTIxMyA2MiwzMSBDNjIsMTMuODc5NzMzMyA0OC4xMjAyNjY3LDAgMzEsMCBMMzEsMCBaIiBpZD0iRmlsbC0xIj48L3BhdGg+ICAgICAgICA8L2c+ICAgIDwvZz48L3N2Zz4=';
9
 
10
- private $menu_page;
11
- private $menu_slug;
12
-
13
- public function __construct() {
14
- $this->menu_page = new AMP_Options_Menu_Page();
15
- $this->menu_slug = 'amp-plugin-options';
16
- }
17
 
 
 
 
18
  public function init() {
19
  add_action( 'admin_post_amp_analytics_options', 'AMP_Options_Manager::handle_analytics_submit' );
20
-
21
  add_action( 'admin_menu', array( $this, 'add_menu_items' ) );
22
  }
23
 
 
 
 
24
  public function add_menu_items() {
 
25
  add_menu_page(
26
  __( 'AMP Options', 'amp' ),
27
  __( 'AMP', 'amp' ),
28
  'manage_options',
29
- $this->menu_slug,
30
- array( $this->menu_page, 'render' ),
31
  self::ICON_BASE64_SVG
32
  );
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  $submenus = array(
35
- new AMP_Analytics_Options_Submenu( $this->menu_slug ),
36
  );
37
 
38
  // Create submenu items and calls on the Submenu Page object to render the actual contents of the page.
39
  foreach ( $submenus as $submenu ) {
40
- $submenu->init( $this->menu_slug );
41
  }
 
42
 
43
- $this->remove_toplevel_menu_item();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  }
45
 
46
- // Helper function to avoid having the top-level menu as
47
- // the first menu item
48
- function remove_toplevel_menu_item() {
49
- global $submenu;
50
- if ( isset( $submenu['amp-plugin-options'][0] ) ) {
51
- unset( $submenu['amp-plugin-options'][0] );
 
 
52
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
54
  }
1
  <?php
2
+ /**
3
+ * AMP Options.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * AMP_Options_Menu class.
10
+ */
 
11
  class AMP_Options_Menu {
 
12
 
13
+ /**
14
+ * The AMP svg menu icon.
15
+ *
16
+ * @var string
17
+ */
18
+ const ICON_BASE64_SVG = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iNjJweCIgaGVpZ2h0PSI2MnB4IiB2aWV3Qm94PSIwIDAgNjIgNjIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+ICAgICAgICA8dGl0bGU+QU1QLUJyYW5kLUJsYWNrLUljb248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iYW1wLWxvZ28taW50ZXJuYWwtc2l0ZSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICAgICAgICA8ZyBpZD0iQU1QLUJyYW5kLUJsYWNrLUljb24iIGZpbGw9IiMwMDAwMDAiPiAgICAgICAgICAgIDxwYXRoIGQ9Ik00MS42Mjg4NjY3LDI4LjE2MTQzMzMgTDI4LjYyNDM2NjcsNDkuODAzNTY2NyBMMjYuMjY4MzY2Nyw0OS44MDM1NjY3IEwyOC41OTc1LDM1LjcwMTY2NjcgTDIxLjM4MzgsMzUuNzEwOTY2NyBDMjEuMzgzOCwzNS43MTA5NjY3IDIxLjMxNTYsMzUuNzEzMDMzMyAyMS4yODM1NjY3LDM1LjcxMzAzMzMgQzIwLjYzMzYsMzUuNzEzMDMzMyAyMC4xMDc2MzMzLDM1LjE4NzA2NjcgMjAuMTA3NjMzMywzNC41MzcxIEMyMC4xMDc2MzMzLDM0LjI1ODEgMjAuMzY3LDMzLjc4NTg2NjcgMjAuMzY3LDMzLjc4NTg2NjcgTDMzLjMyOTEzMzMsMTIuMTY5NTY2NyBMMzUuNzI0NCwxMi4xNzk5IEwzMy4zMzYzNjY3LDI2LjMwMzUgTDQwLjU4NzI2NjcsMjYuMjk0MiBDNDAuNTg3MjY2NywyNi4yOTQyIDQwLjY2NDc2NjcsMjYuMjkzMTY2NyA0MC43MDE5NjY3LDI2LjI5MzE2NjcgQzQxLjM1MTkzMzMsMjYuMjkzMTY2NyA0MS44Nzc5LDI2LjgxOTEzMzMgNDEuODc3OSwyNy40NjkxIEM0MS44Nzc5LDI3LjczMjYgNDEuNzc0NTY2NywyNy45NjQwNjY3IDQxLjYyNzgzMzMsMjguMTYwNCBMNDEuNjI4ODY2NywyOC4xNjE0MzMzIFogTTMxLDAgQzEzLjg3ODcsMCAwLDEzLjg3OTczMzMgMCwzMSBDMCw0OC4xMjEzIDEzLjg3ODcsNjIgMzEsNjIgQzQ4LjEyMDI2NjcsNjIgNjIsNDguMTIxMyA2MiwzMSBDNjIsMTMuODc5NzMzMyA0OC4xMjAyNjY3LDAgMzEsMCBMMzEsMCBaIiBpZD0iRmlsbC0xIj48L3BhdGg+ICAgICAgICA8L2c+ICAgIDwvZz48L3N2Zz4=';
 
19
 
20
+ /**
21
+ * Initialize.
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
+ /**
29
+ * Add menu.
30
+ */
31
  public function add_menu_items() {
32
+
33
  add_menu_page(
34
  __( 'AMP Options', 'amp' ),
35
  __( 'AMP', 'amp' ),
36
  'manage_options',
37
+ AMP_Options_Manager::OPTION_NAME,
38
+ array( $this, 'render_screen' ),
39
  self::ICON_BASE64_SVG
40
  );
41
 
42
+ add_submenu_page(
43
+ AMP_Options_Manager::OPTION_NAME,
44
+ __( 'AMP Settings', 'amp' ),
45
+ __( 'General', 'amp' ),
46
+ 'manage_options',
47
+ AMP_Options_Manager::OPTION_NAME
48
+ );
49
+
50
+ add_settings_section(
51
+ 'post_types',
52
+ false,
53
+ '__return_false',
54
+ AMP_Options_Manager::OPTION_NAME
55
+ );
56
+ add_settings_field(
57
+ 'supported_post_types',
58
+ __( 'Post Type Support', 'amp' ),
59
+ array( $this, 'render_post_types_support' ),
60
+ AMP_Options_Manager::OPTION_NAME,
61
+ 'post_types'
62
+ );
63
+
64
  $submenus = array(
65
+ new AMP_Analytics_Options_Submenu( AMP_Options_Manager::OPTION_NAME ),
66
  );
67
 
68
  // Create submenu items and calls on the Submenu Page object to render the actual contents of the page.
69
  foreach ( $submenus as $submenu ) {
70
+ $submenu->init();
71
  }
72
+ }
73
 
74
+ /**
75
+ * Post types support section renderer.
76
+ *
77
+ * @since 0.6
78
+ */
79
+ public function render_post_types_support() {
80
+ $builtin_support = AMP_Post_Type_Support::get_builtin_supported_post_types();
81
+ $element_name = AMP_Options_Manager::OPTION_NAME . '[supported_post_types][]';
82
+ ?>
83
+ <fieldset>
84
+ <?php foreach ( array_map( 'get_post_type_object', AMP_Post_Type_Support::get_eligible_post_types() ) as $post_type ) : ?>
85
+ <?php
86
+ $element_id = AMP_Options_Manager::OPTION_NAME . "-supported_post_types-{$post_type->name}";
87
+ $is_builtin = in_array( $post_type->name, $builtin_support, true );
88
+ ?>
89
+ <?php if ( $is_builtin ) : ?>
90
+ <input type="hidden" name="<?php echo esc_attr( $element_name ); ?>" value="<?php echo esc_attr( $post_type->name ); ?>">
91
+ <?php endif; ?>
92
+ <input
93
+ type="checkbox"
94
+ id="<?php echo esc_attr( $element_id ); ?>"
95
+ name="<?php echo esc_attr( $element_name ); ?>"
96
+ value="<?php echo esc_attr( $post_type->name ); ?>"
97
+ <?php checked( true, post_type_supports( $post_type->name, AMP_QUERY_VAR ) ); ?>
98
+ <?php disabled( $is_builtin ); ?>
99
+ >
100
+ <label for="<?php echo esc_attr( $element_id ); ?>">
101
+ <?php echo esc_html( $post_type->label ); ?>
102
+ </label>
103
+ <br>
104
+ <?php endforeach; ?>
105
+ <p class="description"><?php esc_html_e( 'Enable/disable AMP post type(s) support', 'amp' ); ?></p>
106
+ </fieldset>
107
+ <?php
108
  }
109
 
110
+ /**
111
+ * Display Settings.
112
+ *
113
+ * @since 0.6
114
+ */
115
+ public function render_screen() {
116
+ if ( ! empty( $_GET['settings-updated'] ) ) { // WPCS: CSRF ok.
117
+ AMP_Options_Manager::check_supported_post_type_update_errors();
118
  }
119
+ ?>
120
+ <div class="wrap">
121
+ <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
122
+ <?php settings_errors(); ?>
123
+ <form action="options.php" method="post">
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>
131
+ <?php
132
  }
133
  }
includes/options/views/class-amp-analytics-options-submenu-page.php CHANGED
@@ -1,19 +1,30 @@
1
  <?php
 
 
 
 
 
2
 
3
- require_once( AMP__DIR__ . '/includes/options/views/class-amp-options-manager.php' );
4
-
 
5
  class AMP_Analytics_Options_Submenu_Page {
6
 
7
  private function render_entry( $id = '', $type = '', $config = '' ) {
8
  $is_existing_entry = ! empty( $id );
9
 
10
- $analytics_title = false;
11
  if ( $is_existing_entry ) {
12
  $entry_slug = sprintf( '%s%s', ( $type ? $type . '-' : '' ), substr( $id, -6 ) );
13
  $analytics_title = sprintf( __( 'Analytics: %s', 'amp' ), $entry_slug );
14
  } else {
15
  $analytics_title = __( 'Add new entry:', 'amp' );
16
  }
 
 
 
 
 
 
17
  ?>
18
  <div class="analytics-data-container">
19
  <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
@@ -23,20 +34,20 @@ class AMP_Analytics_Options_Submenu_Page {
23
  <div class="options">
24
  <p>
25
  <label>
26
- <?php echo __( 'Type:', 'amp' ) ?>
27
- <input class="option-input" type="text" name=vendor-type value="<?php echo esc_attr( $type ); ?>" />
28
  </label>
29
  <label>
30
- <?php echo __( 'ID:', 'amp' ) ?>
31
- <input type="text" name=id value="<?php echo esc_attr( $id ); ?>" readonly />
32
  </label>
33
- <input type="hidden" name=id-value value="<?php echo esc_attr( $id ); ?>" />
34
  </p>
35
  <p>
36
  <label>
37
- <?php echo __( 'JSON Configuration:', 'amp' ) ?>
38
  <br />
39
- <textarea rows="10" cols="100" name="config"><?php echo esc_textarea( $config ); ?></textarea>
40
  </label>
41
  </p>
42
  <input type="hidden" name="action" value="amp_analytics_options">
@@ -46,7 +57,7 @@ class AMP_Analytics_Options_Submenu_Page {
46
  wp_nonce_field( 'analytics-options', 'analytics-options' );
47
  submit_button( __( 'Save', 'amp' ), 'primary', 'save', false );
48
  if ( $is_existing_entry ) {
49
- submit_button( __( 'Delete', 'amp' ), 'delete button-primary', 'delete', false );
50
  }
51
  ?>
52
  </p>
@@ -55,33 +66,14 @@ class AMP_Analytics_Options_Submenu_Page {
55
  <?php
56
  }
57
 
 
 
 
58
  public function render_title() {
59
- $admin_notice_text = false;
60
- $admin_notice_type = false;
61
- if ( isset( $_GET['valid'] ) ) {
62
- $is_valid = (bool) $_GET['valid'];
63
-
64
- if ( $is_valid ) {
65
- $admin_notice_text = __( 'The analytics entry was successfully saved!', 'amp' );
66
- $admin_notice_type = 'success';
67
- } else {
68
- $admin_notice_text = __( 'Failed to save the analytics entry. Please make sure that the JSON configuration is valid and unique.', 'amp' );
69
- $admin_notice_type = 'error';
70
- }
71
- }
72
  ?>
73
  <div class="wrap">
74
  <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
75
- <?php
76
- // If redirected from serializer, check if action succeeded
77
- if ( $admin_notice_text ) : ?>
78
- <div class="amp-analytics-options notice <?php echo esc_attr( 'notice-' . $admin_notice_type ); ?> is-dismissible">
79
- <p><?php echo esc_html( $admin_notice_text ); ?></p>
80
- <button type="button" class="notice-dismiss">
81
- <span class="screen-reader-text"><?php __( 'Dismiss this notice.', 'amp' ) ?></span>
82
- </button>
83
- </div>
84
- <?php endif; ?>
85
  </div><!-- .wrap -->
86
  <?php
87
  }
@@ -91,11 +83,12 @@ class AMP_Analytics_Options_Submenu_Page {
91
 
92
  $this->render_title();
93
 
94
- // Render entries stored in the DB
95
  foreach ( $analytics_entries as $entry_id => $entry ) {
96
  $this->render_entry( $entry_id, $entry['type'], $entry['config'] );
97
  }
98
- // Empty form for adding more entries
 
99
  $this->render_entry();
100
  }
101
  }
1
  <?php
2
+ /**
3
+ * Class AMP_Analytics_Options_Submenu_Page
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Class AMP_Analytics_Options_Submenu_Page
10
+ */
11
  class AMP_Analytics_Options_Submenu_Page {
12
 
13
  private function render_entry( $id = '', $type = '', $config = '' ) {
14
  $is_existing_entry = ! empty( $id );
15
 
 
16
  if ( $is_existing_entry ) {
17
  $entry_slug = sprintf( '%s%s', ( $type ? $type . '-' : '' ), substr( $id, -6 ) );
18
  $analytics_title = sprintf( __( 'Analytics: %s', 'amp' ), $entry_slug );
19
  } else {
20
  $analytics_title = __( 'Add new entry:', 'amp' );
21
  }
22
+
23
+ if ( ! $is_existing_entry ) {
24
+ $id = '__new__';
25
+ }
26
+
27
+ $id_base = sprintf( '%s[analytics][%s]', AMP_Options_Manager::OPTION_NAME, $id );
28
  ?>
29
  <div class="analytics-data-container">
30
  <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
34
  <div class="options">
35
  <p>
36
  <label>
37
+ <?php esc_html_e( 'Type:', 'amp' ); ?>
38
+ <input class="option-input" type="text" name="<?php echo esc_attr( $id_base . '[type]' ); ?>" value="<?php echo esc_attr( $type ); ?>" />
39
  </label>
40
  <label>
41
+ <?php esc_html_e( 'ID:', 'amp' ); ?>
42
+ <input type="text" value="<?php echo esc_attr( $is_existing_entry ? $id : '' ); ?>" readonly />
43
  </label>
44
+ <input type="hidden" name="<?php echo esc_attr( $id_base . '[id]' ); ?>" value="<?php echo esc_attr( $id ); ?>" />
45
  </p>
46
  <p>
47
  <label>
48
+ <?php esc_html_e( 'JSON Configuration:', 'amp' ); ?>
49
  <br />
50
+ <textarea rows="10" cols="100" name="<?php echo esc_attr( $id_base . '[config]' ); ?>"><?php echo esc_textarea( $config ); ?></textarea>
51
  </label>
52
  </p>
53
  <input type="hidden" name="action" value="amp_analytics_options">
57
  wp_nonce_field( 'analytics-options', 'analytics-options' );
58
  submit_button( __( 'Save', 'amp' ), 'primary', 'save', false );
59
  if ( $is_existing_entry ) {
60
+ submit_button( __( 'Delete', 'amp' ), 'delete button-primary', $id_base . '[delete]', false );
61
  }
62
  ?>
63
  </p>
66
  <?php
67
  }
68
 
69
+ /**
70
+ * Render title.
71
+ */
72
  public function render_title() {
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  ?>
74
  <div class="wrap">
75
  <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
76
+ <?php settings_errors(); ?>
 
 
 
 
 
 
 
 
 
77
  </div><!-- .wrap -->
78
  <?php
79
  }
83
 
84
  $this->render_title();
85
 
86
+ // Render entries stored in the DB.
87
  foreach ( $analytics_entries as $entry_id => $entry ) {
88
  $this->render_entry( $entry_id, $entry['type'], $entry['config'] );
89
  }
90
+
91
+ // Empty form for adding more entries.
92
  $this->render_entry();
93
  }
94
  }
includes/options/views/class-amp-options-manager.php DELETED
@@ -1,84 +0,0 @@
1
- <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/utils/class-amp-html-utils.php' );
4
-
5
- class AMP_Options_Manager {
6
- const OPTION_NAME = 'amp-options';
7
-
8
- public static function get_options() {
9
- return get_option( self::OPTION_NAME, array() );
10
- }
11
-
12
- public static function get_option( $option, $default = false ) {
13
- $amp_options = self::get_options();
14
-
15
- if ( ! isset( $amp_options[ $option ] ) ) {
16
- return $default;
17
- }
18
-
19
- return $amp_options[ $option ];
20
- }
21
-
22
- public static function update_option( $option, $value ) {
23
- $amp_options = self::get_options();
24
-
25
- $amp_options[ $option ] = $value;
26
-
27
- return update_option( self::OPTION_NAME, $amp_options, false );
28
- }
29
-
30
- public static function handle_analytics_submit() {
31
- // Request must come from user with right capabilities
32
- if ( ! current_user_can( 'manage_options' ) ) {
33
- wp_die( __( 'Sorry, you do not have the necessary permissions to perform this action', 'amp' ) );
34
- }
35
- // Ensure request is coming from analytics option form
36
- check_admin_referer( 'analytics-options', 'analytics-options' );
37
-
38
- $status = AMP_Options_Manager::update_analytics_options( $_POST );
39
-
40
- // Redirect to keep the user in the analytics options page
41
- // Wrap in is_admin() to enable phpunit tests to exercise this code
42
- wp_safe_redirect( admin_url( 'admin.php?page=amp-analytics-options&valid=' . $status ) );
43
- exit;
44
- }
45
-
46
- public static function update_analytics_options( $data ) {
47
- // Check save/delete pre-conditions and proceed if correct
48
- if ( empty( $data['vendor-type'] ) || empty( $data['config'] ) ) {
49
- return false;
50
- }
51
-
52
- // Validate JSON configuration
53
- $is_valid_json = AMP_HTML_Utils::is_valid_json( stripslashes( $data['config'] ) );
54
- if ( ! $is_valid_json ) {
55
- return false;
56
- }
57
- $amp_analytics = self::get_option( 'analytics', array() );
58
-
59
- $entry_vendor_type = sanitize_key( $data['vendor-type'] );
60
- $entry_config = stripslashes( trim( $data['config'] ) );
61
-
62
- if ( ! empty( $data['id-value'] ) ) {
63
- $entry_id = sanitize_key( $data['id-value'] );
64
- } else {
65
- // Generate a hash string to uniquely identify this entry
66
- $entry_id = substr( md5( $entry_vendor_type . $entry_config ), 0, 12 );
67
- // Avoid duplicates
68
- if ( isset( $amp_analytics[ $entry_id ] ) ) {
69
- return false;
70
- }
71
- }
72
-
73
- if ( isset( $data['delete'] ) ) {
74
- unset( $amp_analytics[ $entry_id ] );
75
- } else {
76
- $amp_analytics[ $entry_id ] = array(
77
- 'type' => $entry_vendor_type,
78
- 'config' => $entry_config,
79
- );
80
- }
81
-
82
- return self::update_option( 'analytics', $amp_analytics );
83
- }
84
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/options/views/class-amp-options-menu-page.php DELETED
@@ -1,17 +0,0 @@
1
- <?php
2
-
3
- class AMP_Options_Menu_Page {
4
- public function render() {
5
- ?>
6
- <div class="ampoptions-admin-page">
7
- <h1><?php echo __( 'AMP Plugin Options', 'amp' ) ?></h1>
8
- <p>
9
- <?php
10
- __( 'This admin panel menu contains configuration options for the AMP Plugin.',
11
- 'amp' );
12
- ?>
13
- </p>
14
- </div>
15
- <?php
16
- }
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/sanitizers/class-amp-allowed-tags-generated.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
  /**
3
- * Generated by amp_wp_build.py - do not edit.
4
  *
5
  * This is a list of HTML tags and attributes that are allowed by the
6
  * AMP specification. Note that tag names have been converted to lowercase.
@@ -11,17 +11,19 @@
11
  */
12
  class AMP_Allowed_Tags_Generated {
13
 
14
- private static $spec_file_revision = 325;
15
- private static $minimum_validator_revision_required = 189;
16
 
17
  private static $allowed_tags = array(
18
  'a' => array(
19
  array(
20
  'attr_spec_list' => array(
 
21
  'border' => array(),
22
  'download' => array(),
23
  'href' => array(
24
  'blacklisted_value_regex' => '__amp_source_origin',
 
25
  'allow_relative' => true,
26
  'allowed_protocol' => array(
27
  'bbmi',
@@ -59,7 +61,11 @@ class AMP_Allowed_Tags_Generated {
59
  ),
60
  ),
61
  'tag_spec' => array(
62
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#links',
 
 
 
 
63
  ),
64
 
65
  ),
@@ -89,6 +95,28 @@ class AMP_Allowed_Tags_Generated {
89
 
90
  ),
91
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  'amp-accordion' => array(
93
  array(
94
  'attr_spec_list' => array(
@@ -97,22 +125,48 @@ class AMP_Allowed_Tags_Generated {
97
  ),
98
  ),
99
  'tag_spec' => array(
100
- 'also_requires_tag' => array(
101
- 'amp-accordion extension .js script',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  ),
103
  'html_format' => array(
104
  'amp',
105
- 'amp4ads',
106
  ),
107
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-accordion.html',
108
  ),
109
 
110
  ),
111
- ),
112
- 'amp-ad' => array(
113
  array(
114
  'attr_spec_list' => array(
115
  'alt' => array(),
 
 
 
116
  'json' => array(),
117
  'media' => array(),
118
  'noloading' => array(
@@ -132,12 +186,36 @@ class AMP_Allowed_Tags_Generated {
132
  'tag_spec' => array(
133
  'disallowed_ancestor' => array(
134
  'amp-app-banner',
135
- 'amp-sidebar',
 
 
 
136
  ),
137
  'html_format' => array(
138
  'amp',
139
  ),
140
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-ad.html',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  ),
142
 
143
  ),
@@ -147,6 +225,7 @@ class AMP_Allowed_Tags_Generated {
147
  'attr_spec_list' => array(
148
  'config' => array(
149
  'blacklisted_value_regex' => '__amp_source_origin',
 
150
  'allow_relative' => true,
151
  'allowed_protocol' => array(
152
  'https',
@@ -155,17 +234,11 @@ class AMP_Allowed_Tags_Generated {
155
  'type' => array(),
156
  ),
157
  'tag_spec' => array(
158
- 'also_requires_tag' => array(
159
- 'amp-analytics extension .js script',
160
- ),
161
- 'disallowed_ancestor' => array(
162
- 'amp-sidebar',
163
- ),
164
  'html_format' => array(
165
  'amp',
166
  'amp4ads',
167
  ),
168
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-analytics.html',
169
  ),
170
 
171
  ),
@@ -175,9 +248,6 @@ class AMP_Allowed_Tags_Generated {
175
  'attr_spec_list' => array(
176
  'alt' => array(),
177
  'attribution' => array(),
178
- 'autoplay' => array(
179
- 'value' => '',
180
- ),
181
  'controls' => array(),
182
  'media' => array(),
183
  'noloading' => array(
@@ -198,17 +268,31 @@ class AMP_Allowed_Tags_Generated {
198
  ),
199
  ),
200
  'tag_spec' => array(
201
- 'also_requires_tag' => array(
202
- 'amp-anim extension .js script',
 
203
  ),
204
- 'disallowed_ancestor' => array(
205
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
206
  ),
 
 
207
  'html_format' => array(
208
  'amp',
209
  'amp4ads',
210
  ),
211
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-anim.html',
212
  ),
213
 
214
  ),
@@ -228,13 +312,10 @@ class AMP_Allowed_Tags_Generated {
228
  ),
229
  ),
230
  'tag_spec' => array(
231
- 'also_requires_tag' => array(
232
- 'amp-apester-media extension .js script',
233
- ),
234
  'html_format' => array(
235
  'amp',
236
  ),
237
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-apester-media.html',
238
  ),
239
 
240
  ),
@@ -251,16 +332,11 @@ class AMP_Allowed_Tags_Generated {
251
  ),
252
  ),
253
  'tag_spec' => array(
254
- 'also_requires_tag' => array(
255
- 'amp-app-banner button[open-button]',
256
- 'amp-app-banner extension .js script',
257
- ),
258
  'html_format' => array(
259
  'amp',
260
  ),
261
  'mandatory_parent' => 'body',
262
- 'spec_name' => 'amp-app-banner',
263
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-add-banner.html',
264
  'unique' => true,
265
  ),
266
 
@@ -269,10 +345,14 @@ class AMP_Allowed_Tags_Generated {
269
  'amp-audio' => array(
270
  array(
271
  'attr_spec_list' => array(
 
 
 
272
  'autoplay' => array(
273
- 'value_regex' => '^$|desktop|tablet|mobile|autoplay',
274
  ),
275
  'controls' => array(),
 
276
  'loop' => array(
277
  'value' => '',
278
  ),
@@ -292,22 +372,27 @@ class AMP_Allowed_Tags_Generated {
292
  ),
293
  ),
294
  'tag_spec' => array(
295
- 'also_requires_tag' => array(
296
- 'amp-audio extension .js script',
297
- ),
298
  'disallowed_ancestor' => array(
299
- 'amp-sidebar',
300
  ),
301
  'html_format' => array(
302
  'amp',
303
  ),
304
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-audio.html',
305
  ),
306
 
307
  ),
308
  array(
309
  'attr_spec_list' => array(
 
 
 
 
 
 
 
310
  'controls' => array(),
 
311
  'loop' => array(
312
  'value' => '',
313
  ),
@@ -327,17 +412,46 @@ class AMP_Allowed_Tags_Generated {
327
  ),
328
  ),
329
  'tag_spec' => array(
330
- 'also_requires_tag' => array(
331
- 'amp-audio extension .js script',
332
  ),
333
- 'disallowed_ancestor' => array(
334
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  ),
 
 
336
  'html_format' => array(
337
  'amp4ads',
338
  ),
339
  'spec_name' => 'amp-audio (a4a)',
340
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-audio.html',
341
  ),
342
 
343
  ),
@@ -357,6 +471,8 @@ class AMP_Allowed_Tags_Generated {
357
  'html_format' => array(
358
  'amp',
359
  ),
 
 
360
  ),
361
 
362
  ),
@@ -364,6 +480,10 @@ class AMP_Allowed_Tags_Generated {
364
  'amp-brid-player' => array(
365
  array(
366
  'attr_spec_list' => array(
 
 
 
 
367
  'data-partner' => array(
368
  'mandatory' => true,
369
  'value_regex' => '[0-9]+',
@@ -384,17 +504,10 @@ class AMP_Allowed_Tags_Generated {
384
  ),
385
  ),
386
  'tag_spec' => array(
387
- 'also_requires_tag' => array(
388
- 'amp-brid-player extension .js script',
389
- ),
390
- 'disallowed_ancestor' => array(
391
- 'amp-sidebar',
392
- ),
393
  'html_format' => array(
394
  'amp',
395
- 'amp4ads',
396
  ),
397
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-brid-player.html',
398
  ),
399
 
400
  ),
@@ -402,30 +515,50 @@ class AMP_Allowed_Tags_Generated {
402
  'amp-brightcove' => array(
403
  array(
404
  'attr_spec_list' => array(
 
 
 
 
 
 
405
  'data-account' => array(
406
  'mandatory' => true,
407
  ),
408
- 'data-embed' => array(),
409
- 'data-player' => array(),
410
- 'data-playlist-id' => array(),
411
- 'data-video-id' => array(),
412
  'media' => array(),
413
  'noloading' => array(
414
  'value' => '',
415
  ),
416
  ),
417
  'tag_spec' => array(
418
- 'also_requires_tag' => array(
419
- 'amp-brightcove extension .js script',
420
  ),
421
- 'disallowed_ancestor' => array(
422
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  ),
 
 
424
  'html_format' => array(
425
  'amp',
426
- 'amp4ads',
427
  ),
428
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-brightcove.html',
429
  ),
430
 
431
  ),
@@ -433,6 +566,7 @@ class AMP_Allowed_Tags_Generated {
433
  'amp-carousel' => array(
434
  array(
435
  'attr_spec_list' => array(
 
436
  'arrows' => array(
437
  'value' => '',
438
  ),
@@ -458,17 +592,7 @@ class AMP_Allowed_Tags_Generated {
458
  ),
459
  ),
460
  'tag_spec' => array(
461
- 'also_requires_tag' => array(
462
- 'amp-carousel extension .js script',
463
- ),
464
- 'disallowed_ancestor' => array(
465
- 'amp-sidebar',
466
- ),
467
- 'html_format' => array(
468
- 'amp',
469
- 'amp4ads',
470
- ),
471
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-carousel.html',
472
  ),
473
 
474
  ),
@@ -476,6 +600,7 @@ class AMP_Allowed_Tags_Generated {
476
  'amp-dailymotion' => array(
477
  array(
478
  'attr_spec_list' => array(
 
479
  'data-endscreen-enable' => array(
480
  'value_regex' => 'true|false',
481
  ),
@@ -507,25 +632,52 @@ class AMP_Allowed_Tags_Generated {
507
  ),
508
  ),
509
  'tag_spec' => array(
510
- 'also_requires_tag' => array(
511
- 'amp-dailymotion extension .js script',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  ),
 
 
 
 
 
513
  'disallowed_ancestor' => array(
514
- 'amp-sidebar',
515
  ),
516
  'html_format' => array(
517
  'amp',
518
- 'amp4ads',
519
  ),
520
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-dailymotion.html',
521
  ),
522
 
523
  ),
524
- ),
525
- 'amp-embed' => array(
526
  array(
527
  'attr_spec_list' => array(
528
  'alt' => array(),
 
 
 
 
529
  'json' => array(),
530
  'media' => array(),
531
  'noloading' => array(
@@ -544,12 +696,17 @@ class AMP_Allowed_Tags_Generated {
544
  ),
545
  'tag_spec' => array(
546
  'disallowed_ancestor' => array(
547
- 'amp-sidebar',
 
 
 
 
548
  ),
549
  'html_format' => array(
550
  'amp',
551
  ),
552
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-embed.html',
 
553
  ),
554
 
555
  ),
@@ -558,16 +715,10 @@ class AMP_Allowed_Tags_Generated {
558
  array(
559
  'attr_spec_list' => array(),
560
  'tag_spec' => array(
561
- 'also_requires_tag' => array(
562
- 'amp-experiment extension .js script',
563
- ),
564
- 'disallowed_ancestor' => array(
565
- 'amp-sidebar',
566
- ),
567
  'html_format' => array(
568
  'amp',
569
  ),
570
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-experiment.html',
571
  'unique' => true,
572
  ),
573
 
@@ -585,44 +736,70 @@ class AMP_Allowed_Tags_Generated {
585
  ),
586
  ),
587
  'tag_spec' => array(
588
- 'also_requires_tag' => array(
589
- 'amp-facebook extension .js script',
590
  ),
591
- 'disallowed_ancestor' => array(
592
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
593
  ),
 
 
594
  'html_format' => array(
595
  'amp',
596
- 'amp4ads',
597
  ),
598
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-facebook.html',
599
  ),
600
 
601
  ),
602
  ),
603
- 'amp-fit-text' => array(
604
  array(
605
  'attr_spec_list' => array(
606
- 'max-font-size' => array(),
 
 
 
 
 
 
 
607
  'media' => array(),
608
- 'min-font-size' => array(),
609
  'noloading' => array(
610
  'value' => '',
611
  ),
612
  ),
613
  'tag_spec' => array(
614
- 'also_requires_tag' => array(
615
- 'amp-fit-text extension .js script',
616
- ),
617
  'html_format' => array(
618
  'amp',
619
- 'amp4ads',
620
  ),
621
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-fit-text.html',
622
  ),
623
 
624
  ),
625
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
  'amp-font' => array(
627
  array(
628
  'attr_spec_list' => array(
@@ -645,17 +822,10 @@ class AMP_Allowed_Tags_Generated {
645
  ),
646
  ),
647
  'tag_spec' => array(
648
- 'also_requires_tag' => array(
649
- 'amp-font extension .js script',
650
- ),
651
- 'disallowed_ancestor' => array(
652
- 'amp-sidebar',
653
- ),
654
  'html_format' => array(
655
  'amp',
656
  'amp4ads',
657
  ),
658
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-font.html',
659
  ),
660
 
661
  ),
@@ -672,17 +842,9 @@ class AMP_Allowed_Tags_Generated {
672
  ),
673
  ),
674
  'tag_spec' => array(
675
- 'also_requires_tag' => array(
676
- 'amp-fx-flying-carpet extension .js script',
677
- ),
678
- 'disallowed_ancestor' => array(
679
- 'amp-sidebar',
680
- ),
681
  'html_format' => array(
682
  'amp',
683
- 'amp4ads',
684
  ),
685
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-fx-flying-carpet.html',
686
  ),
687
 
688
  ),
@@ -702,17 +864,49 @@ class AMP_Allowed_Tags_Generated {
702
  ),
703
  ),
704
  'tag_spec' => array(
705
- 'also_requires_tag' => array(
706
- 'amp-gfycat extension .js script',
707
  ),
708
- 'disallowed_ancestor' => array(
709
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
710
  ),
 
 
711
  'html_format' => array(
712
  'amp',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
  'amp4ads',
714
  ),
715
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-gfycat.html',
716
  ),
717
 
718
  ),
@@ -729,16 +923,10 @@ class AMP_Allowed_Tags_Generated {
729
  ),
730
  ),
731
  'tag_spec' => array(
732
- 'also_requires_tag' => array(
733
- 'amp-hulu extension .js script',
734
- ),
735
- 'disallowed_ancestor' => array(
736
- 'amp-sidebar',
737
- ),
738
  'html_format' => array(
739
  'amp',
740
- 'amp4ads',
741
  ),
 
742
  ),
743
 
744
  ),
@@ -746,9 +934,16 @@ class AMP_Allowed_Tags_Generated {
746
  'amp-iframe' => array(
747
  array(
748
  'attr_spec_list' => array(
 
 
 
 
749
  'allowfullscreen' => array(
750
  'value' => '',
751
  ),
 
 
 
752
  'allowtransparency' => array(
753
  'value' => '',
754
  ),
@@ -778,16 +973,40 @@ class AMP_Allowed_Tags_Generated {
778
  'srcdoc' => array(),
779
  ),
780
  'tag_spec' => array(
781
- 'also_requires_tag' => array(
782
- 'amp-iframe extension .js script',
783
  ),
784
- 'disallowed_ancestor' => array(
785
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
786
  ),
 
 
 
 
 
 
787
  'html_format' => array(
788
  'amp',
789
  ),
790
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-iframe.html',
791
  ),
792
 
793
  ),
@@ -802,17 +1021,9 @@ class AMP_Allowed_Tags_Generated {
802
  ),
803
  ),
804
  'tag_spec' => array(
805
- 'also_requires_tag' => array(
806
- 'amp-image-lightbox extension .js script',
807
- ),
808
- 'disallowed_ancestor' => array(
809
- 'amp-sidebar',
810
- ),
811
  'html_format' => array(
812
  'amp',
813
- 'amp4ads',
814
  ),
815
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-image-lightbox.html',
816
  ),
817
 
818
  ),
@@ -820,6 +1031,10 @@ class AMP_Allowed_Tags_Generated {
820
  'amp-img' => array(
821
  array(
822
  'attr_spec_list' => array(
 
 
 
 
823
  'alt' => array(),
824
  'attribution' => array(),
825
  'media' => array(),
@@ -846,7 +1061,26 @@ class AMP_Allowed_Tags_Generated {
846
  'amp',
847
  'amp4ads',
848
  ),
849
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-img.html',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
  ),
851
 
852
  ),
@@ -864,17 +1098,9 @@ class AMP_Allowed_Tags_Generated {
864
  ),
865
  ),
866
  'tag_spec' => array(
867
- 'also_requires_tag' => array(
868
- 'amp-instagram extension .js script',
869
- ),
870
- 'disallowed_ancestor' => array(
871
- 'amp-sidebar',
872
- ),
873
  'html_format' => array(
874
  'amp',
875
- 'amp4ads',
876
  ),
877
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-instagram.html',
878
  ),
879
 
880
  ),
@@ -882,6 +1108,13 @@ class AMP_Allowed_Tags_Generated {
882
  'amp-install-serviceworker' => array(
883
  array(
884
  'attr_spec_list' => array(
 
 
 
 
 
 
 
885
  'src' => array(
886
  'blacklisted_value_regex' => '__amp_source_origin',
887
  'mandatory' => true,
@@ -892,16 +1125,29 @@ class AMP_Allowed_Tags_Generated {
892
  ),
893
  ),
894
  'tag_spec' => array(
895
- 'also_requires_tag' => array(
896
- 'amp-install-serviceworker extension .js script',
897
  ),
898
- 'disallowed_ancestor' => array(
899
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
900
  ),
 
 
901
  'html_format' => array(
902
  'amp',
903
  ),
904
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-install-serviceworker.html',
905
  ),
906
 
907
  ),
@@ -921,17 +1167,9 @@ class AMP_Allowed_Tags_Generated {
921
  ),
922
  ),
923
  'tag_spec' => array(
924
- 'also_requires_tag' => array(
925
- 'amp-jwplayer extension .js script',
926
- ),
927
- 'disallowed_ancestor' => array(
928
- 'amp-sidebar',
929
- ),
930
  'html_format' => array(
931
  'amp',
932
- 'amp4ads',
933
  ),
934
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-jwplayer.html',
935
  ),
936
 
937
  ),
@@ -948,17 +1186,27 @@ class AMP_Allowed_Tags_Generated {
948
  ),
949
  ),
950
  'tag_spec' => array(
951
- 'also_requires_tag' => array(
952
- 'amp-kaltura-player extension .js script',
953
  ),
954
- 'disallowed_ancestor' => array(
955
- 'amp-sidebar',
 
 
 
 
 
 
 
 
956
  ),
 
 
957
  'html_format' => array(
958
  'amp',
959
  'amp4ads',
960
  ),
961
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-kaltura-player.html',
962
  ),
963
 
964
  ),
@@ -975,17 +1223,9 @@ class AMP_Allowed_Tags_Generated {
975
  'scrollable' => array(),
976
  ),
977
  'tag_spec' => array(
978
- 'also_requires_tag' => array(
979
- 'amp-lightbox extension .js script',
980
- ),
981
- 'disallowed_ancestor' => array(
982
- 'amp-sidebar',
983
- ),
984
  'html_format' => array(
985
  'amp',
986
- 'amp4ads',
987
  ),
988
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-lightbox.html',
989
  ),
990
 
991
  ),
@@ -993,11 +1233,16 @@ class AMP_Allowed_Tags_Generated {
993
  'amp-list' => array(
994
  array(
995
  'attr_spec_list' => array(
 
 
996
  'credentials' => array(),
 
 
997
  'media' => array(),
998
  'noloading' => array(
999
  'value' => '',
1000
  ),
 
1001
  'src' => array(
1002
  'blacklisted_value_regex' => '__amp_source_origin',
1003
  'mandatory' => true,
@@ -1009,14 +1254,9 @@ class AMP_Allowed_Tags_Generated {
1009
  'template' => array(),
1010
  ),
1011
  'tag_spec' => array(
1012
- 'also_requires_tag' => array(
1013
- 'amp-list extension .js script',
1014
- ),
1015
  'html_format' => array(
1016
  'amp',
1017
- 'amp4ads',
1018
  ),
1019
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-list.html',
1020
  ),
1021
 
1022
  ),
@@ -1039,14 +1279,46 @@ class AMP_Allowed_Tags_Generated {
1039
  ),
1040
  ),
1041
  'tag_spec' => array(
1042
- 'also_requires_tag' => array(
1043
- 'amp-live-list extension .js script',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  ),
 
 
1045
  'html_format' => array(
1046
  'amp',
1047
- 'amp4ads',
1048
  ),
1049
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-live-list.html',
1050
  ),
1051
 
1052
  ),
@@ -1057,28 +1329,18 @@ class AMP_Allowed_Tags_Generated {
1057
  'data-bcid' => array(
1058
  'mandatory' => true,
1059
  ),
1060
- 'data-bid' => array(),
1061
  'data-pid' => array(
1062
  'mandatory' => true,
1063
  ),
1064
- 'data-vid' => array(),
1065
  'media' => array(),
1066
  'noloading' => array(
1067
  'value' => '',
1068
  ),
1069
  ),
1070
  'tag_spec' => array(
1071
- 'also_requires_tag' => array(
1072
- 'amp-o2-player extension .js script',
1073
- ),
1074
- 'disallowed_ancestor' => array(
1075
- 'amp-sidebar',
1076
- ),
1077
  'html_format' => array(
1078
  'amp',
1079
- 'amp4ads',
1080
  ),
1081
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-o2-player.html',
1082
  ),
1083
 
1084
  ),
@@ -1086,7 +1348,6 @@ class AMP_Allowed_Tags_Generated {
1086
  'amp-ooyala-player' => array(
1087
  array(
1088
  'attr_spec_list' => array(
1089
- 'data-config' => array(),
1090
  'data-embedcode' => array(
1091
  'mandatory' => true,
1092
  ),
@@ -1096,21 +1357,15 @@ class AMP_Allowed_Tags_Generated {
1096
  'data-playerid' => array(
1097
  'mandatory' => true,
1098
  ),
1099
- 'data-playerversion' => array(),
1100
  'media' => array(),
1101
  'noloading' => array(
1102
  'value' => '',
1103
  ),
1104
  ),
1105
  'tag_spec' => array(
1106
- 'also_requires_tag' => array(
1107
- 'amp-ooyala-player extension .js script',
1108
- ),
1109
  'html_format' => array(
1110
  'amp',
1111
- 'amp4ads',
1112
  ),
1113
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-ooyala-player.html',
1114
  ),
1115
 
1116
  ),
@@ -1127,17 +1382,10 @@ class AMP_Allowed_Tags_Generated {
1127
  ),
1128
  ),
1129
  'tag_spec' => array(
1130
- 'also_requires_tag' => array(
1131
- 'amp-pinterest extension .js script',
1132
- ),
1133
- 'disallowed_ancestor' => array(
1134
- 'amp-sidebar',
1135
- ),
1136
  'html_format' => array(
1137
  'amp',
1138
- 'amp4ads',
1139
  ),
1140
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-pinterest.html',
1141
  ),
1142
 
1143
  ),
@@ -1149,9 +1397,13 @@ class AMP_Allowed_Tags_Generated {
1149
  'noloading' => array(
1150
  'value' => '',
1151
  ),
 
 
 
1152
  'src' => array(
1153
  'blacklisted_value_regex' => '__amp_source_origin',
1154
  'mandatory' => true,
 
1155
  'allow_relative' => true,
1156
  'allowed_protocol' => array(
1157
  'https',
@@ -1159,14 +1411,11 @@ class AMP_Allowed_Tags_Generated {
1159
  ),
1160
  ),
1161
  'tag_spec' => array(
1162
- 'disallowed_ancestor' => array(
1163
- 'amp-sidebar',
1164
- ),
1165
  'html_format' => array(
1166
  'amp',
1167
  'amp4ads',
1168
  ),
1169
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-pixel.html',
1170
  ),
1171
 
1172
  ),
@@ -1177,6 +1426,7 @@ class AMP_Allowed_Tags_Generated {
1177
  'data-comments' => array(
1178
  'value_regex_casei' => '(false|true)',
1179
  ),
 
1180
  'data-item-info' => array(
1181
  'value_regex_casei' => '(false|true)',
1182
  ),
@@ -1187,19 +1437,36 @@ class AMP_Allowed_Tags_Generated {
1187
  'noloading' => array(
1188
  'value' => '',
1189
  ),
1190
- 'src' => array(
1191
- 'mandatory' => true,
1192
- ),
1193
  ),
1194
  'tag_spec' => array(
1195
- 'also_requires_tag' => array(
1196
- 'amp-playbuzz extension .js script',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1197
  ),
 
 
 
 
 
 
1198
  'html_format' => array(
1199
  'amp',
1200
  'amp4ads',
1201
  ),
1202
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-playbuzz',
1203
  ),
1204
 
1205
  ),
@@ -1217,17 +1484,9 @@ class AMP_Allowed_Tags_Generated {
1217
  ),
1218
  ),
1219
  'tag_spec' => array(
1220
- 'also_requires_tag' => array(
1221
- 'amp-reach-player extension .js script',
1222
- ),
1223
- 'disallowed_ancestor' => array(
1224
- 'amp-sidebar',
1225
- ),
1226
  'html_format' => array(
1227
  'amp',
1228
- 'amp4ads',
1229
  ),
1230
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-reach-player.html',
1231
  ),
1232
 
1233
  ),
@@ -1254,17 +1513,9 @@ class AMP_Allowed_Tags_Generated {
1254
  ),
1255
  ),
1256
  'tag_spec' => array(
1257
- 'also_requires_tag' => array(
1258
- 'amp-reddit extension .js script',
1259
- ),
1260
- 'disallowed_ancestor' => array(
1261
- 'amp-sidebar',
1262
- ),
1263
  'html_format' => array(
1264
  'amp',
1265
- 'amp4ads',
1266
  ),
1267
- 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-reddit',
1268
  ),
1269
 
1270
  ),
@@ -1272,32 +1523,32 @@ class AMP_Allowed_Tags_Generated {
1272
  'amp-selector' => array(
1273
  array(
1274
  'attr_spec_list' => array(
 
1275
  'disabled' => array(
1276
  'value' => '',
1277
  ),
1278
  'form' => array(),
 
 
 
1279
  'media' => array(),
1280
  'multiple' => array(
1281
  'value' => '',
1282
  ),
1283
- 'name' => array(),
 
 
1284
  'noloading' => array(
1285
  'value' => '',
1286
  ),
1287
  ),
1288
  'tag_spec' => array(
1289
- 'also_requires_tag' => array(
1290
- 'amp-selector extension .js script',
1291
- ),
1292
  'disallowed_ancestor' => array(
1293
  'amp-selector',
1294
- 'amp-sidebar',
1295
  ),
1296
  'html_format' => array(
1297
  'amp',
1298
- 'amp4ads',
1299
  ),
1300
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-selector.html',
1301
  ),
1302
 
1303
  ),
@@ -1314,18 +1565,10 @@ class AMP_Allowed_Tags_Generated {
1314
  ),
1315
  ),
1316
  'tag_spec' => array(
1317
- 'also_requires_tag' => array(
1318
- 'amp-sidebar extension .js script',
1319
- ),
1320
- 'disallowed_ancestor' => array(
1321
- 'amp-sidebar',
1322
- ),
1323
  'html_format' => array(
1324
  'amp',
1325
  ),
1326
  'mandatory_parent' => 'body',
1327
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-sidebar.html',
1328
- 'unique' => true,
1329
  ),
1330
 
1331
  ),
@@ -1364,14 +1607,10 @@ class AMP_Allowed_Tags_Generated {
1364
  ),
1365
  ),
1366
  'tag_spec' => array(
1367
- 'also_requires_tag' => array(
1368
- 'amp-social-share extension .js script',
1369
- ),
1370
  'html_format' => array(
1371
  'amp',
1372
  'amp4ads',
1373
  ),
1374
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-social-share.html',
1375
  ),
1376
 
1377
  ),
@@ -1382,11 +1621,13 @@ class AMP_Allowed_Tags_Generated {
1382
  'data-color' => array(
1383
  'value_regex_casei' => '([0-9a-f]{3}){1,2}',
1384
  ),
 
 
 
1385
  'data-secret-token' => array(
1386
  'value_regex' => '[a-za-z0-9_-]+',
1387
  ),
1388
  'data-trackid' => array(
1389
- 'mandatory' => true,
1390
  'value_regex' => '[0-9]+',
1391
  ),
1392
  'data-visual' => array(
@@ -1398,17 +1639,9 @@ class AMP_Allowed_Tags_Generated {
1398
  ),
1399
  ),
1400
  'tag_spec' => array(
1401
- 'also_requires_tag' => array(
1402
- 'amp-soundcloud extension .js script',
1403
- ),
1404
- 'disallowed_ancestor' => array(
1405
- 'amp-sidebar',
1406
- ),
1407
  'html_format' => array(
1408
  'amp',
1409
- 'amp4ads',
1410
  ),
1411
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-soundcloud.html',
1412
  ),
1413
 
1414
  ),
@@ -1443,17 +1676,35 @@ class AMP_Allowed_Tags_Generated {
1443
  ),
1444
  ),
1445
  'tag_spec' => array(
1446
- 'also_requires_tag' => array(
1447
- 'amp-springboard-player extension .js script',
1448
  ),
1449
- 'disallowed_ancestor' => array(
1450
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1451
  ),
 
 
1452
  'html_format' => array(
1453
  'amp',
1454
- 'amp4ads',
1455
  ),
1456
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-springboard-player.html',
 
1457
  ),
1458
 
1459
  ),
@@ -1467,45 +1718,126 @@ class AMP_Allowed_Tags_Generated {
1467
  ),
1468
  ),
1469
  'tag_spec' => array(
1470
- 'also_requires_tag' => array(
1471
- 'amp-sticky-ad extension .js script',
1472
- ),
1473
  'disallowed_ancestor' => array(
1474
  'amp-app-banner',
1475
- 'amp-sidebar',
1476
  ),
1477
  'html_format' => array(
1478
  'amp',
1479
  ),
1480
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-sticky-ad.html',
1481
  'unique' => true,
1482
  ),
1483
 
1484
  ),
1485
  ),
1486
- 'amp-twitter' => array(
1487
  array(
1488
  'attr_spec_list' => array(
1489
- 'data-tweetid' => array(
1490
- 'mandatory' => true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1491
  ),
 
1492
  'media' => array(),
1493
  'noloading' => array(
1494
  'value' => '',
1495
  ),
1496
  ),
1497
  'tag_spec' => array(
1498
- 'also_requires_tag' => array(
1499
- 'amp-twitter extension .js script',
1500
  ),
1501
- 'disallowed_ancestor' => array(
1502
- 'amp-sidebar',
 
 
 
 
 
 
 
 
1503
  ),
 
 
 
 
 
 
1504
  'html_format' => array(
1505
  'amp',
1506
- 'amp4ads',
1507
  ),
1508
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-twitter.html',
1509
  ),
1510
 
1511
  ),
@@ -1527,23 +1859,18 @@ class AMP_Allowed_Tags_Generated {
1527
  'https',
1528
  ),
1529
  ),
 
 
 
1530
  'media' => array(),
1531
  'noloading' => array(
1532
  'value' => '',
1533
  ),
1534
  ),
1535
  'tag_spec' => array(
1536
- 'also_requires_tag' => array(
1537
- 'amp-analytics extension .js script',
1538
- 'amp-user-notification extension .js script',
1539
- ),
1540
- 'disallowed_ancestor' => array(
1541
- 'amp-sidebar',
1542
- ),
1543
  'html_format' => array(
1544
  'amp',
1545
  ),
1546
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-user-notification.html',
1547
  ),
1548
 
1549
  ),
@@ -1551,7 +1878,22 @@ class AMP_Allowed_Tags_Generated {
1551
  'amp-video' => array(
1552
  array(
1553
  'attr_spec_list' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
1554
  'alt' => array(),
 
 
1555
  'attribution' => array(),
1556
  'autoplay' => array(
1557
  'value' => '',
@@ -1559,6 +1901,11 @@ class AMP_Allowed_Tags_Generated {
1559
  'controls' => array(
1560
  'value' => '',
1561
  ),
 
 
 
 
 
1562
  'loop' => array(
1563
  'value' => '',
1564
  ),
@@ -1584,13 +1931,79 @@ class AMP_Allowed_Tags_Generated {
1584
  ),
1585
  'tag_spec' => array(
1586
  'disallowed_ancestor' => array(
1587
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1588
  ),
 
 
 
 
 
 
 
 
 
1589
  'html_format' => array(
1590
  'amp',
1591
  'amp4ads',
1592
  ),
1593
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-video.html',
 
 
1594
  ),
1595
 
1596
  ),
@@ -1608,17 +2021,9 @@ class AMP_Allowed_Tags_Generated {
1608
  ),
1609
  ),
1610
  'tag_spec' => array(
1611
- 'also_requires_tag' => array(
1612
- 'amp-vimeo extension .js script',
1613
- ),
1614
- 'disallowed_ancestor' => array(
1615
- 'amp-sidebar',
1616
- ),
1617
  'html_format' => array(
1618
  'amp',
1619
- 'amp4ads',
1620
  ),
1621
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-vimeo.html',
1622
  ),
1623
 
1624
  ),
@@ -1635,47 +2040,113 @@ class AMP_Allowed_Tags_Generated {
1635
  ),
1636
  ),
1637
  'tag_spec' => array(
1638
- 'also_requires_tag' => array(
1639
- 'amp-vine extension .js script',
1640
  ),
1641
- 'disallowed_ancestor' => array(
1642
- 'amp-sidebar',
 
 
 
 
 
 
 
1643
  ),
 
 
 
 
 
 
1644
  'html_format' => array(
1645
  'amp',
1646
- 'amp4ads',
1647
  ),
1648
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-vine.html',
1649
  ),
1650
 
1651
  ),
1652
  ),
1653
- 'amp-youtube' => array(
1654
  array(
1655
  'attr_spec_list' => array(
1656
- 'autoplay' => array(),
1657
- 'data-videoid' => array(
1658
  'mandatory' => true,
1659
- 'value_regex' => '[^=/?:]+',
 
 
 
 
 
 
 
1660
  ),
1661
  'media' => array(),
1662
  'noloading' => array(
1663
  'value' => '',
1664
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1665
  ),
1666
  'tag_spec' => array(
1667
- 'also_requires_tag' => array(
1668
- 'amp-youtube extension .js script',
1669
  ),
1670
- 'disallowed_ancestor' => array(
1671
- 'amp-sidebar',
 
 
 
 
 
 
 
 
 
 
 
 
 
1672
  ),
 
 
1673
  'html_format' => array(
1674
  'amp',
1675
- 'amp4ads',
1676
  ),
1677
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-youtube.html',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1678
  ),
 
1679
 
1680
  ),
1681
  ),
@@ -1716,7 +2187,7 @@ class AMP_Allowed_Tags_Generated {
1716
  ),
1717
  'mandatory_ancestor' => 'noscript',
1718
  'mandatory_ancestor_suggested_alternative' => 'amp-audio',
1719
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-audio.html',
1720
  ),
1721
 
1722
  ),
@@ -1761,23 +2232,11 @@ class AMP_Allowed_Tags_Generated {
1761
  'align' => array(),
1762
  'cite' => array(
1763
  'blacklisted_value_regex' => '__amp_source_origin',
 
1764
  'allow_relative' => true,
1765
  'allowed_protocol' => array(
1766
- 'fb-messenger',
1767
- 'ftp',
1768
  'http',
1769
  'https',
1770
- 'intent',
1771
- 'mailto',
1772
- 'skype',
1773
- 'sms',
1774
- 'snapchat',
1775
- 'tel',
1776
- 'tg',
1777
- 'threema',
1778
- 'twitter',
1779
- 'viber',
1780
- 'whatsapp',
1781
  ),
1782
  ),
1783
  ),
@@ -1791,7 +2250,7 @@ class AMP_Allowed_Tags_Generated {
1791
  'tag_spec' => array(
1792
  'mandatory' => true,
1793
  'mandatory_parent' => 'html',
1794
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#required-markup',
1795
  'unique' => true,
1796
  ),
1797
 
@@ -1807,10 +2266,15 @@ class AMP_Allowed_Tags_Generated {
1807
  'button' => array(
1808
  array(
1809
  'attr_spec_list' => array(
 
 
 
1810
  'disabled' => array(
1811
  'value' => '',
1812
  ),
1813
- 'name' => array(),
 
 
1814
  'role' => array(),
1815
  'tabindex' => array(),
1816
  'type' => array(),
@@ -1821,7 +2285,9 @@ class AMP_Allowed_Tags_Generated {
1821
  ),
1822
  array(
1823
  'attr_spec_list' => array(
1824
- 'name' => array(),
 
 
1825
  'open-button' => array(
1826
  'value' => '',
1827
  ),
@@ -1831,6 +2297,10 @@ class AMP_Allowed_Tags_Generated {
1831
  'value' => array(),
1832
  ),
1833
  'tag_spec' => array(
 
 
 
 
1834
  'mandatory_ancestor' => 'amp-app-banner',
1835
  'spec_name' => 'amp-app-banner button[open-button]',
1836
  ),
@@ -1855,12 +2325,735 @@ class AMP_Allowed_Tags_Generated {
1855
 
1856
  ),
1857
  ),
1858
- 'circle' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1859
  array(
1860
  'attr_spec_list' => array(
1861
  'alignment-baseline' => array(),
1862
  'baseline-shift' => array(),
1863
- 'class' => array(),
1864
  'clip' => array(),
1865
  'clip-path' => array(),
1866
  'clip-rule' => array(),
@@ -1870,13 +3063,10 @@ class AMP_Allowed_Tags_Generated {
1870
  'color-profile' => array(),
1871
  'color-rendering' => array(),
1872
  'cursor' => array(),
1873
- 'cx' => array(),
1874
- 'cy' => array(),
1875
  'direction' => array(),
1876
  'display' => array(),
1877
  'dominant-baseline' => array(),
1878
  'enable-background' => array(),
1879
- 'externalresourcesrequired' => array(),
1880
  'fill' => array(),
1881
  'fill-opacity' => array(),
1882
  'fill-rule' => array(),
@@ -1892,6 +3082,7 @@ class AMP_Allowed_Tags_Generated {
1892
  'font-weight' => array(),
1893
  'glyph-orientation-horizontal' => array(),
1894
  'glyph-orientation-vertical' => array(),
 
1895
  'image-rendering' => array(),
1896
  'kerning' => array(),
1897
  'letter-spacing' => array(),
@@ -1903,11 +3094,8 @@ class AMP_Allowed_Tags_Generated {
1903
  'opacity' => array(),
1904
  'overflow' => array(),
1905
  'pointer-events' => array(),
1906
- 'r' => array(),
1907
- 'requiredextensions' => array(),
1908
- 'requiredfeatures' => array(),
1909
  'shape-rendering' => array(),
1910
- 'sketch:type' => array(),
1911
  'stop-color' => array(),
1912
  'stop-opacity' => array(),
1913
  'stroke' => array(),
@@ -1918,45 +3106,44 @@ class AMP_Allowed_Tags_Generated {
1918
  'stroke-miterlimit' => array(),
1919
  'stroke-opacity' => array(),
1920
  'stroke-width' => array(),
1921
- 'systemlanguage' => array(),
 
 
1922
  'text-anchor' => array(),
1923
  'text-decoration' => array(),
1924
  'text-rendering' => array(),
1925
- 'transform' => array(),
1926
  'unicode-bidi' => array(),
 
1927
  'visibility' => array(),
 
1928
  'word-spacing' => array(),
1929
  'writing-mode' => array(),
1930
- 'xml:base' => array(),
1931
  'xml:lang' => array(),
1932
  'xml:space' => array(),
1933
  'xmlns' => array(),
1934
  'xmlns:xlink' => array(),
 
1935
  ),
1936
  'tag_spec' => array(
 
 
 
 
1937
  'mandatory_ancestor' => 'svg',
1938
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
1939
  ),
1940
 
1941
  ),
1942
  ),
1943
- 'cite' => array(
1944
- array(
1945
- 'attr_spec_list' => array(),
1946
- 'tag_spec' => array(),
1947
-
1948
- ),
1949
- ),
1950
- 'clippath' => array(
1951
  array(
1952
  'attr_spec_list' => array(
1953
  'alignment-baseline' => array(),
1954
  'baseline-shift' => array(),
1955
- 'class' => array(),
1956
  'clip' => array(),
1957
  'clip-path' => array(),
1958
  'clip-rule' => array(),
1959
- 'clippathunits' => array(),
1960
  'color' => array(),
1961
  'color-interpolation' => array(),
1962
  'color-interpolation-filters' => array(),
@@ -1966,8 +3153,8 @@ class AMP_Allowed_Tags_Generated {
1966
  'direction' => array(),
1967
  'display' => array(),
1968
  'dominant-baseline' => array(),
 
1969
  'enable-background' => array(),
1970
- 'externalresourcesrequired' => array(),
1971
  'fill' => array(),
1972
  'fill-opacity' => array(),
1973
  'fill-rule' => array(),
@@ -1983,7 +3170,9 @@ class AMP_Allowed_Tags_Generated {
1983
  'font-weight' => array(),
1984
  'glyph-orientation-horizontal' => array(),
1985
  'glyph-orientation-vertical' => array(),
 
1986
  'image-rendering' => array(),
 
1987
  'kerning' => array(),
1988
  'letter-spacing' => array(),
1989
  'lighting-color' => array(),
@@ -1994,9 +3183,9 @@ class AMP_Allowed_Tags_Generated {
1994
  'opacity' => array(),
1995
  'overflow' => array(),
1996
  'pointer-events' => array(),
1997
- 'requiredextensions' => array(),
1998
- 'requiredfeatures' => array(),
1999
  'shape-rendering' => array(),
 
2000
  'stop-color' => array(),
2001
  'stop-opacity' => array(),
2002
  'stroke' => array(),
@@ -2007,86 +3196,41 @@ class AMP_Allowed_Tags_Generated {
2007
  'stroke-miterlimit' => array(),
2008
  'stroke-opacity' => array(),
2009
  'stroke-width' => array(),
2010
- 'systemlanguage' => array(),
 
 
2011
  'text-anchor' => array(),
2012
  'text-decoration' => array(),
2013
  'text-rendering' => array(),
2014
- 'transform' => array(),
2015
  'unicode-bidi' => array(),
 
2016
  'visibility' => array(),
 
2017
  'word-spacing' => array(),
2018
  'writing-mode' => array(),
2019
- 'xml:base' => array(),
2020
  'xml:lang' => array(),
2021
  'xml:space' => array(),
2022
  'xmlns' => array(),
2023
  'xmlns:xlink' => array(),
 
2024
  ),
2025
  'tag_spec' => array(
2026
- 'mandatory_ancestor' => 'svg',
2027
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2028
- ),
2029
-
2030
- ),
2031
- ),
2032
- 'code' => array(
2033
- array(
2034
- 'attr_spec_list' => array(),
2035
- 'tag_spec' => array(),
2036
-
2037
- ),
2038
- ),
2039
- 'col' => array(
2040
- array(
2041
- 'attr_spec_list' => array(
2042
- 'span' => array(),
2043
- ),
2044
- 'tag_spec' => array(),
2045
-
2046
- ),
2047
- ),
2048
- 'colgroup' => array(
2049
- array(
2050
- 'attr_spec_list' => array(
2051
- 'span' => array(),
2052
- ),
2053
- 'tag_spec' => array(),
2054
-
2055
- ),
2056
- ),
2057
- 'data' => array(
2058
- array(
2059
- 'attr_spec_list' => array(),
2060
- 'tag_spec' => array(),
2061
-
2062
- ),
2063
- ),
2064
- 'datalist' => array(
2065
- array(
2066
- 'attr_spec_list' => array(),
2067
- 'tag_spec' => array(
2068
- 'also_requires_tag' => array(
2069
- 'amp-form extension .js script',
2070
  ),
2071
- 'mandatory_ancestor' => 'form',
2072
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
2073
  ),
2074
 
2075
  ),
2076
  ),
2077
- 'dd' => array(
2078
- array(
2079
- 'attr_spec_list' => array(),
2080
- 'tag_spec' => array(),
2081
-
2082
- ),
2083
- ),
2084
- 'defs' => array(
2085
  array(
2086
  'attr_spec_list' => array(
2087
  'alignment-baseline' => array(),
2088
  'baseline-shift' => array(),
2089
- 'class' => array(),
2090
  'clip' => array(),
2091
  'clip-path' => array(),
2092
  'clip-rule' => array(),
@@ -2100,7 +3244,6 @@ class AMP_Allowed_Tags_Generated {
2100
  'display' => array(),
2101
  'dominant-baseline' => array(),
2102
  'enable-background' => array(),
2103
- 'externalresourcesrequired' => array(),
2104
  'fill' => array(),
2105
  'fill-opacity' => array(),
2106
  'fill-rule' => array(),
@@ -2116,6 +3259,7 @@ class AMP_Allowed_Tags_Generated {
2116
  'font-weight' => array(),
2117
  'glyph-orientation-horizontal' => array(),
2118
  'glyph-orientation-vertical' => array(),
 
2119
  'image-rendering' => array(),
2120
  'kerning' => array(),
2121
  'letter-spacing' => array(),
@@ -2127,8 +3271,7 @@ class AMP_Allowed_Tags_Generated {
2127
  'opacity' => array(),
2128
  'overflow' => array(),
2129
  'pointer-events' => array(),
2130
- 'requiredextensions' => array(),
2131
- 'requiredfeatures' => array(),
2132
  'shape-rendering' => array(),
2133
  'stop-color' => array(),
2134
  'stop-opacity' => array(),
@@ -2140,148 +3283,64 @@ class AMP_Allowed_Tags_Generated {
2140
  'stroke-miterlimit' => array(),
2141
  'stroke-opacity' => array(),
2142
  'stroke-width' => array(),
2143
- 'systemlanguage' => array(),
 
 
2144
  'text-anchor' => array(),
2145
  'text-decoration' => array(),
2146
  'text-rendering' => array(),
2147
- 'transform' => array(),
2148
  'unicode-bidi' => array(),
 
2149
  'visibility' => array(),
 
2150
  'word-spacing' => array(),
2151
  'writing-mode' => array(),
2152
- 'xml:base' => array(),
2153
  'xml:lang' => array(),
2154
  'xml:space' => array(),
2155
  'xmlns' => array(),
2156
  'xmlns:xlink' => array(),
 
2157
  ),
2158
  'tag_spec' => array(
 
 
 
 
2159
  'mandatory_ancestor' => 'svg',
2160
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2161
  ),
2162
 
2163
  ),
2164
  ),
2165
- 'del' => array(
2166
  array(
2167
  'attr_spec_list' => array(
2168
- 'cite' => array(
2169
- 'blacklisted_value_regex' => '__amp_source_origin',
2170
- 'allow_relative' => true,
2171
- 'allowed_protocol' => array(
2172
- 'fb-messenger',
2173
- 'ftp',
2174
- 'http',
2175
- 'https',
2176
- 'intent',
2177
- 'mailto',
2178
- 'skype',
2179
- 'sms',
2180
- 'snapchat',
2181
- 'tel',
2182
- 'tg',
2183
- 'threema',
2184
- 'twitter',
2185
- 'viber',
2186
- 'whatsapp',
2187
- ),
2188
  ),
2189
- 'datetime' => array(),
2190
- ),
2191
- 'tag_spec' => array(),
2192
-
2193
- ),
2194
- ),
2195
- 'desc' => array(
2196
- array(
2197
- 'attr_spec_list' => array(
2198
- 'class' => array(),
2199
- 'xml:base' => array(),
2200
  'xml:lang' => array(),
2201
  'xml:space' => array(),
2202
  'xmlns' => array(),
2203
  'xmlns:xlink' => array(),
2204
  ),
2205
- 'tag_spec' => array(
2206
- 'mandatory_ancestor' => 'svg',
2207
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2208
- ),
2209
-
2210
- ),
2211
- ),
2212
- 'dfn' => array(
2213
- array(
2214
- 'attr_spec_list' => array(),
2215
- 'tag_spec' => array(),
2216
-
2217
- ),
2218
- ),
2219
- 'dir' => array(
2220
- array(
2221
- 'attr_spec_list' => array(),
2222
  'tag_spec' => array(
2223
  'html_format' => array(
2224
  'amp',
 
2225
  ),
 
 
2226
  ),
2227
 
2228
  ),
2229
  ),
2230
- 'div' => array(
2231
- array(
2232
- 'attr_spec_list' => array(
2233
- 'align' => array(),
2234
- ),
2235
- 'tag_spec' => array(),
2236
-
2237
- ),
2238
- array(
2239
- 'attr_spec_list' => array(
2240
- 'align' => array(),
2241
- 'submit-success' => array(
2242
- 'mandatory' => true,
2243
- ),
2244
- ),
2245
- 'tag_spec' => array(
2246
- 'mandatory_parent' => 'form',
2247
- 'spec_name' => 'form > div [submit-success]',
2248
- ),
2249
-
2250
- ),
2251
- array(
2252
- 'attr_spec_list' => array(
2253
- 'align' => array(),
2254
- 'submit-error' => array(
2255
- 'mandatory' => true,
2256
- ),
2257
- ),
2258
- 'tag_spec' => array(
2259
- 'mandatory_parent' => 'form',
2260
- 'spec_name' => 'form > div [submit-error]',
2261
- ),
2262
-
2263
- ),
2264
- ),
2265
- 'dl' => array(
2266
- array(
2267
- 'attr_spec_list' => array(),
2268
- 'tag_spec' => array(),
2269
-
2270
- ),
2271
- ),
2272
- 'dt' => array(
2273
- array(
2274
- 'attr_spec_list' => array(),
2275
- 'tag_spec' => array(),
2276
-
2277
- ),
2278
- ),
2279
- 'ellipse' => array(
2280
  array(
2281
  'attr_spec_list' => array(
2282
  'alignment-baseline' => array(),
2283
  'baseline-shift' => array(),
2284
- 'class' => array(),
2285
  'clip' => array(),
2286
  'clip-path' => array(),
2287
  'clip-rule' => array(),
@@ -2291,13 +3350,12 @@ class AMP_Allowed_Tags_Generated {
2291
  'color-profile' => array(),
2292
  'color-rendering' => array(),
2293
  'cursor' => array(),
2294
- 'cx' => array(),
2295
- 'cy' => array(),
2296
  'direction' => array(),
2297
  'display' => array(),
2298
  'dominant-baseline' => array(),
 
 
2299
  'enable-background' => array(),
2300
- 'externalresourcesrequired' => array(),
2301
  'fill' => array(),
2302
  'fill-opacity' => array(),
2303
  'fill-rule' => array(),
@@ -2313,7 +3371,9 @@ class AMP_Allowed_Tags_Generated {
2313
  'font-weight' => array(),
2314
  'glyph-orientation-horizontal' => array(),
2315
  'glyph-orientation-vertical' => array(),
 
2316
  'image-rendering' => array(),
 
2317
  'kerning' => array(),
2318
  'letter-spacing' => array(),
2319
  'lighting-color' => array(),
@@ -2324,12 +3384,8 @@ class AMP_Allowed_Tags_Generated {
2324
  'opacity' => array(),
2325
  'overflow' => array(),
2326
  'pointer-events' => array(),
2327
- 'requiredextensions' => array(),
2328
- 'requiredfeatures' => array(),
2329
- 'rx' => array(),
2330
- 'ry' => array(),
2331
  'shape-rendering' => array(),
2332
- 'sketch:type' => array(),
2333
  'stop-color' => array(),
2334
  'stop-opacity' => array(),
2335
  'stroke' => array(),
@@ -2340,40 +3396,44 @@ class AMP_Allowed_Tags_Generated {
2340
  'stroke-miterlimit' => array(),
2341
  'stroke-opacity' => array(),
2342
  'stroke-width' => array(),
2343
- 'systemlanguage' => array(),
 
 
2344
  'text-anchor' => array(),
2345
  'text-decoration' => array(),
2346
  'text-rendering' => array(),
2347
- 'transform' => array(),
2348
  'unicode-bidi' => array(),
 
2349
  'visibility' => array(),
 
2350
  'word-spacing' => array(),
2351
  'writing-mode' => array(),
2352
- 'xml:base' => array(),
2353
  'xml:lang' => array(),
2354
  'xml:space' => array(),
2355
  'xmlns' => array(),
2356
  'xmlns:xlink' => array(),
 
2357
  ),
2358
  'tag_spec' => array(
 
 
 
 
2359
  'mandatory_ancestor' => 'svg',
2360
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2361
  ),
2362
 
2363
  ),
2364
  ),
2365
- 'em' => array(
2366
- array(
2367
- 'attr_spec_list' => array(),
2368
- 'tag_spec' => array(),
2369
-
2370
- ),
2371
- ),
2372
  'fieldset' => array(
2373
  array(
2374
  'attr_spec_list' => array(
 
2375
  'disabled' => array(),
2376
- 'name' => array(),
 
 
2377
  ),
2378
  'tag_spec' => array(),
2379
 
@@ -2398,7 +3458,6 @@ class AMP_Allowed_Tags_Generated {
2398
  'attr_spec_list' => array(
2399
  'alignment-baseline' => array(),
2400
  'baseline-shift' => array(),
2401
- 'class' => array(),
2402
  'clip' => array(),
2403
  'clip-path' => array(),
2404
  'clip-rule' => array(),
@@ -2454,10 +3513,14 @@ class AMP_Allowed_Tags_Generated {
2454
  'stroke-miterlimit' => array(),
2455
  'stroke-opacity' => array(),
2456
  'stroke-width' => array(),
 
 
 
2457
  'text-anchor' => array(),
2458
  'text-decoration' => array(),
2459
  'text-rendering' => array(),
2460
  'unicode-bidi' => array(),
 
2461
  'visibility' => array(),
2462
  'width' => array(),
2463
  'word-spacing' => array(),
@@ -2466,13 +3529,20 @@ class AMP_Allowed_Tags_Generated {
2466
  'xlink:actuate' => array(),
2467
  'xlink:arcrole' => array(),
2468
  'xlink:href' => array(
2469
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
2470
  ),
2471
  'xlink:role' => array(),
2472
  'xlink:show' => array(),
2473
  'xlink:title' => array(),
2474
  'xlink:type' => array(),
2475
- 'xml:base' => array(),
2476
  'xml:lang' => array(),
2477
  'xml:space' => array(),
2478
  'xmlns' => array(),
@@ -2480,8 +3550,12 @@ class AMP_Allowed_Tags_Generated {
2480
  'y' => array(),
2481
  ),
2482
  'tag_spec' => array(
 
 
 
 
2483
  'mandatory_ancestor' => 'svg',
2484
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2485
  ),
2486
 
2487
  ),
@@ -2498,7 +3572,6 @@ class AMP_Allowed_Tags_Generated {
2498
  'attr_spec_list' => array(
2499
  'alignment-baseline' => array(),
2500
  'baseline-shift' => array(),
2501
- 'class' => array(),
2502
  'clip' => array(),
2503
  'clip-path' => array(),
2504
  'clip-rule' => array(),
@@ -2553,18 +3626,21 @@ class AMP_Allowed_Tags_Generated {
2553
  'stroke-miterlimit' => array(),
2554
  'stroke-opacity' => array(),
2555
  'stroke-width' => array(),
 
 
 
2556
  'systemlanguage' => array(),
2557
  'text-anchor' => array(),
2558
  'text-decoration' => array(),
2559
  'text-rendering' => array(),
2560
  'transform' => array(),
2561
  'unicode-bidi' => array(),
 
2562
  'visibility' => array(),
2563
  'width' => array(),
2564
  'word-spacing' => array(),
2565
  'writing-mode' => array(),
2566
  'x' => array(),
2567
- 'xml:base' => array(),
2568
  'xml:lang' => array(),
2569
  'xml:space' => array(),
2570
  'xmlns' => array(),
@@ -2572,8 +3648,12 @@ class AMP_Allowed_Tags_Generated {
2572
  'y' => array(),
2573
  ),
2574
  'tag_spec' => array(
 
 
 
 
2575
  'mandatory_ancestor' => 'svg',
2576
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2577
  ),
2578
 
2579
  ),
@@ -2590,9 +3670,6 @@ class AMP_Allowed_Tags_Generated {
2590
  'allowed_protocol' => array(
2591
  'https',
2592
  ),
2593
- 'disallowed_domain' => array(
2594
- 'cdn.ampproject.org',
2595
- ),
2596
  ),
2597
  'action-xhr' => array(
2598
  'blacklisted_value_regex' => '__amp_source_origin',
@@ -2600,13 +3677,10 @@ class AMP_Allowed_Tags_Generated {
2600
  'allowed_protocol' => array(
2601
  'https',
2602
  ),
2603
- 'disallowed_domain' => array(
2604
- 'cdn.ampproject.org',
2605
- ),
2606
  ),
2607
  'autocomplete' => array(),
2608
  'custom-validation-reporting' => array(
2609
- 'value_regex' => '(show-first-on-submit|show-all-on-submit|as-you-go)',
2610
  ),
2611
  'enctype' => array(),
2612
  'method' => array(
@@ -2618,16 +3692,23 @@ class AMP_Allowed_Tags_Generated {
2618
  'mandatory' => true,
2619
  'value_regex_casei' => '(_blank|_top)',
2620
  ),
 
 
 
 
 
 
 
2621
  ),
2622
  'tag_spec' => array(
2623
- 'also_requires_tag' => array(
2624
- 'amp-form extension .js script',
2625
- ),
2626
  'disallowed_ancestor' => array(
2627
  'amp-app-banner',
2628
  ),
 
 
 
 
2629
  'spec_name' => 'form [method=get]',
2630
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
2631
  ),
2632
 
2633
  ),
@@ -2642,9 +3723,6 @@ class AMP_Allowed_Tags_Generated {
2642
  'allowed_protocol' => array(
2643
  'https',
2644
  ),
2645
- 'disallowed_domain' => array(
2646
- 'cdn.ampproject.org',
2647
- ),
2648
  ),
2649
  'autocomplete' => array(),
2650
  'custom-validation-reporting' => array(
@@ -2652,7 +3730,6 @@ class AMP_Allowed_Tags_Generated {
2652
  ),
2653
  'enctype' => array(),
2654
  'method' => array(
2655
- 'dispatch_key' => true,
2656
  'mandatory' => true,
2657
  'value_casei' => 'post',
2658
  ),
@@ -2662,16 +3739,23 @@ class AMP_Allowed_Tags_Generated {
2662
  'mandatory' => true,
2663
  'value_regex_casei' => '(_blank|_top)',
2664
  ),
 
 
 
 
 
 
 
2665
  ),
2666
  'tag_spec' => array(
2667
- 'also_requires_tag' => array(
2668
- 'amp-form extension .js script',
2669
- ),
2670
  'disallowed_ancestor' => array(
2671
  'amp-app-banner',
2672
  ),
 
 
 
 
2673
  'spec_name' => 'form [method=post]',
2674
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
2675
  ),
2676
 
2677
  ),
@@ -2681,7 +3765,6 @@ class AMP_Allowed_Tags_Generated {
2681
  'attr_spec_list' => array(
2682
  'alignment-baseline' => array(),
2683
  'baseline-shift' => array(),
2684
- 'class' => array(),
2685
  'clip' => array(),
2686
  'clip-path' => array(),
2687
  'clip-rule' => array(),
@@ -2735,24 +3818,31 @@ class AMP_Allowed_Tags_Generated {
2735
  'stroke-miterlimit' => array(),
2736
  'stroke-opacity' => array(),
2737
  'stroke-width' => array(),
 
 
 
2738
  'systemlanguage' => array(),
2739
  'text-anchor' => array(),
2740
  'text-decoration' => array(),
2741
  'text-rendering' => array(),
2742
  'transform' => array(),
2743
  'unicode-bidi' => array(),
 
2744
  'visibility' => array(),
2745
  'word-spacing' => array(),
2746
  'writing-mode' => array(),
2747
- 'xml:base' => array(),
2748
  'xml:lang' => array(),
2749
  'xml:space' => array(),
2750
  'xmlns' => array(),
2751
  'xmlns:xlink' => array(),
2752
  ),
2753
  'tag_spec' => array(
 
 
 
 
2754
  'mandatory_ancestor' => 'svg',
2755
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2756
  ),
2757
 
2758
  ),
@@ -2763,7 +3853,6 @@ class AMP_Allowed_Tags_Generated {
2763
  'alignment-baseline' => array(),
2764
  'arabic-form' => array(),
2765
  'baseline-shift' => array(),
2766
- 'class' => array(),
2767
  'clip' => array(),
2768
  'clip-path' => array(),
2769
  'clip-rule' => array(),
@@ -2818,26 +3907,33 @@ class AMP_Allowed_Tags_Generated {
2818
  'stroke-miterlimit' => array(),
2819
  'stroke-opacity' => array(),
2820
  'stroke-width' => array(),
 
 
 
2821
  'text-anchor' => array(),
2822
  'text-decoration' => array(),
2823
  'text-rendering' => array(),
2824
  'unicode' => array(),
2825
  'unicode-bidi' => array(),
 
2826
  'vert-adv-y' => array(),
2827
  'vert-origin-x' => array(),
2828
  'vert-origin-y' => array(),
2829
  'visibility' => array(),
2830
  'word-spacing' => array(),
2831
  'writing-mode' => array(),
2832
- 'xml:base' => array(),
2833
  'xml:lang' => array(),
2834
  'xml:space' => array(),
2835
  'xmlns' => array(),
2836
  'xmlns:xlink' => array(),
2837
  ),
2838
  'tag_spec' => array(
 
 
 
 
2839
  'mandatory_ancestor' => 'svg',
2840
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2841
  ),
2842
 
2843
  ),
@@ -2847,7 +3943,6 @@ class AMP_Allowed_Tags_Generated {
2847
  'attr_spec_list' => array(
2848
  'alignment-baseline' => array(),
2849
  'baseline-shift' => array(),
2850
- 'class' => array(),
2851
  'clip' => array(),
2852
  'clip-path' => array(),
2853
  'clip-rule' => array(),
@@ -2902,10 +3997,14 @@ class AMP_Allowed_Tags_Generated {
2902
  'stroke-miterlimit' => array(),
2903
  'stroke-opacity' => array(),
2904
  'stroke-width' => array(),
 
 
 
2905
  'text-anchor' => array(),
2906
  'text-decoration' => array(),
2907
  'text-rendering' => array(),
2908
  'unicode-bidi' => array(),
 
2909
  'visibility' => array(),
2910
  'word-spacing' => array(),
2911
  'writing-mode' => array(),
@@ -2913,13 +4012,20 @@ class AMP_Allowed_Tags_Generated {
2913
  'xlink:actuate' => array(),
2914
  'xlink:arcrole' => array(),
2915
  'xlink:href' => array(
2916
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
2917
  ),
2918
  'xlink:role' => array(),
2919
  'xlink:show' => array(),
2920
  'xlink:title' => array(),
2921
  'xlink:type' => array(),
2922
- 'xml:base' => array(),
2923
  'xml:lang' => array(),
2924
  'xml:space' => array(),
2925
  'xmlns' => array(),
@@ -2927,8 +4033,12 @@ class AMP_Allowed_Tags_Generated {
2927
  'y' => array(),
2928
  ),
2929
  'tag_spec' => array(
 
 
 
 
2930
  'mandatory_ancestor' => 'svg',
2931
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
2932
  ),
2933
 
2934
  ),
@@ -3011,17 +4121,23 @@ class AMP_Allowed_Tags_Generated {
3011
  'g1' => array(),
3012
  'g2' => array(),
3013
  'k' => array(),
 
 
 
3014
  'u1' => array(),
3015
  'u2' => array(),
3016
- 'xml:base' => array(),
3017
  'xml:lang' => array(),
3018
  'xml:space' => array(),
3019
  'xmlns' => array(),
3020
  'xmlns:xlink' => array(),
3021
  ),
3022
  'tag_spec' => array(
 
 
 
 
3023
  'mandatory_ancestor' => 'svg',
3024
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
3025
  ),
3026
 
3027
  ),
@@ -3040,12 +4156,49 @@ class AMP_Allowed_Tags_Generated {
3040
 
3041
  ),
3042
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3043
  'image' => array(
3044
  array(
3045
  'attr_spec_list' => array(
3046
  'alignment-baseline' => array(),
3047
  'baseline-shift' => array(),
3048
- 'class' => array(),
3049
  'clip' => array(),
3050
  'clip-path' => array(),
3051
  'clip-rule' => array(),
@@ -3101,12 +4254,16 @@ class AMP_Allowed_Tags_Generated {
3101
  'stroke-miterlimit' => array(),
3102
  'stroke-opacity' => array(),
3103
  'stroke-width' => array(),
 
 
 
3104
  'systemlanguage' => array(),
3105
  'text-anchor' => array(),
3106
  'text-decoration' => array(),
3107
  'text-rendering' => array(),
3108
  'transform' => array(),
3109
  'unicode-bidi' => array(),
 
3110
  'visibility' => array(),
3111
  'width' => array(),
3112
  'word-spacing' => array(),
@@ -3115,18 +4272,22 @@ class AMP_Allowed_Tags_Generated {
3115
  'xlink:actuate' => array(),
3116
  'xlink:arcrole' => array(),
3117
  'xlink:href' => array(
 
 
 
3118
  'blacklisted_value_regex' => '(^|\\s)data:image\\/svg\\+xml',
3119
  'allow_empty' => false,
3120
- 'allow_relative' => false,
3121
  'allowed_protocol' => array(
3122
  'data',
 
 
3123
  ),
3124
  ),
3125
  'xlink:role' => array(),
3126
  'xlink:show' => array(),
3127
  'xlink:title' => array(),
3128
  'xlink:type' => array(),
3129
- 'xml:base' => array(),
3130
  'xml:lang' => array(),
3131
  'xml:space' => array(),
3132
  'xmlns' => array(),
@@ -3134,8 +4295,12 @@ class AMP_Allowed_Tags_Generated {
3134
  'y' => array(),
3135
  ),
3136
  'tag_spec' => array(
 
 
 
 
3137
  'mandatory_ancestor' => 'svg',
3138
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
3139
  ),
3140
 
3141
  ),
@@ -3175,7 +4340,7 @@ class AMP_Allowed_Tags_Generated {
3175
  ),
3176
  'mandatory_ancestor' => 'noscript',
3177
  'mandatory_ancestor_suggested_alternative' => 'amp-img',
3178
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-img.html',
3179
  ),
3180
 
3181
  ),
@@ -3183,12 +4348,34 @@ class AMP_Allowed_Tags_Generated {
3183
  'input' => array(
3184
  array(
3185
  'attr_spec_list' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3186
  'accept' => array(),
3187
  'accesskey' => array(),
3188
  'autocomplete' => array(),
3189
  'autofocus' => array(),
3190
  'checked' => array(),
3191
- 'default-value' => array(),
3192
  'disabled' => array(),
3193
  'height' => array(),
3194
  'inputmode' => array(),
@@ -3199,7 +4386,7 @@ class AMP_Allowed_Tags_Generated {
3199
  'minlength' => array(),
3200
  'multiple' => array(),
3201
  'name' => array(
3202
- 'blacklisted_value_regex' => '__amp_source_origin',
3203
  ),
3204
  'pattern' => array(),
3205
  'placeholder' => array(),
@@ -3217,7 +4404,7 @@ class AMP_Allowed_Tags_Generated {
3217
  'width' => array(),
3218
  ),
3219
  'tag_spec' => array(
3220
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
3221
  ),
3222
 
3223
  ),
@@ -3227,23 +4414,11 @@ class AMP_Allowed_Tags_Generated {
3227
  'attr_spec_list' => array(
3228
  'cite' => array(
3229
  'blacklisted_value_regex' => '__amp_source_origin',
 
3230
  'allow_relative' => true,
3231
  'allowed_protocol' => array(
3232
- 'fb-messenger',
3233
- 'ftp',
3234
  'http',
3235
  'https',
3236
- 'intent',
3237
- 'mailto',
3238
- 'skype',
3239
- 'sms',
3240
- 'snapchat',
3241
- 'tel',
3242
- 'tg',
3243
- 'threema',
3244
- 'twitter',
3245
- 'viber',
3246
- 'whatsapp',
3247
  ),
3248
  ),
3249
  'datetime' => array(),
@@ -3265,7 +4440,7 @@ class AMP_Allowed_Tags_Generated {
3265
  'for' => array(),
3266
  ),
3267
  'tag_spec' => array(
3268
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
3269
  ),
3270
 
3271
  ),
@@ -3293,7 +4468,6 @@ class AMP_Allowed_Tags_Generated {
3293
  'attr_spec_list' => array(
3294
  'alignment-baseline' => array(),
3295
  'baseline-shift' => array(),
3296
- 'class' => array(),
3297
  'clip' => array(),
3298
  'clip-path' => array(),
3299
  'clip-rule' => array(),
@@ -3348,18 +4522,21 @@ class AMP_Allowed_Tags_Generated {
3348
  'stroke-miterlimit' => array(),
3349
  'stroke-opacity' => array(),
3350
  'stroke-width' => array(),
 
 
 
3351
  'systemlanguage' => array(),
3352
  'text-anchor' => array(),
3353
  'text-decoration' => array(),
3354
  'text-rendering' => array(),
3355
  'transform' => array(),
3356
  'unicode-bidi' => array(),
 
3357
  'visibility' => array(),
3358
  'word-spacing' => array(),
3359
  'writing-mode' => array(),
3360
  'x1' => array(),
3361
  'x2' => array(),
3362
- 'xml:base' => array(),
3363
  'xml:lang' => array(),
3364
  'xml:space' => array(),
3365
  'xmlns' => array(),
@@ -3368,8 +4545,12 @@ class AMP_Allowed_Tags_Generated {
3368
  'y2' => array(),
3369
  ),
3370
  'tag_spec' => array(
 
 
 
 
3371
  'mandatory_ancestor' => 'svg',
3372
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
3373
  ),
3374
 
3375
  ),
@@ -3379,7 +4560,6 @@ class AMP_Allowed_Tags_Generated {
3379
  'attr_spec_list' => array(
3380
  'alignment-baseline' => array(),
3381
  'baseline-shift' => array(),
3382
- 'class' => array(),
3383
  'clip' => array(),
3384
  'clip-path' => array(),
3385
  'clip-rule' => array(),
@@ -3434,10 +4614,14 @@ class AMP_Allowed_Tags_Generated {
3434
  'stroke-miterlimit' => array(),
3435
  'stroke-opacity' => array(),
3436
  'stroke-width' => array(),
 
 
 
3437
  'text-anchor' => array(),
3438
  'text-decoration' => array(),
3439
  'text-rendering' => array(),
3440
  'unicode-bidi' => array(),
 
3441
  'visibility' => array(),
3442
  'word-spacing' => array(),
3443
  'writing-mode' => array(),
@@ -3446,13 +4630,20 @@ class AMP_Allowed_Tags_Generated {
3446
  'xlink:actuate' => array(),
3447
  'xlink:arcrole' => array(),
3448
  'xlink:href' => array(
3449
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
3450
  ),
3451
  'xlink:role' => array(),
3452
  'xlink:show' => array(),
3453
  'xlink:title' => array(),
3454
  'xlink:type' => array(),
3455
- 'xml:base' => array(),
3456
  'xml:lang' => array(),
3457
  'xml:space' => array(),
3458
  'xmlns' => array(),
@@ -3461,8 +4652,12 @@ class AMP_Allowed_Tags_Generated {
3461
  'y2' => array(),
3462
  ),
3463
  'tag_spec' => array(
 
 
 
 
3464
  'mandatory_ancestor' => 'svg',
3465
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
3466
  ),
3467
 
3468
  ),
@@ -3489,8 +4684,12 @@ class AMP_Allowed_Tags_Generated {
3489
  'disallowed_ancestor' => array(
3490
  'template',
3491
  ),
 
 
 
 
3492
  'spec_name' => 'link rel=',
3493
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3494
  ),
3495
 
3496
  ),
@@ -3505,7 +4704,6 @@ class AMP_Allowed_Tags_Generated {
3505
  ),
3506
  'hreflang' => array(),
3507
  'itemprop' => array(
3508
- 'dispatch_key' => true,
3509
  'mandatory' => true,
3510
  'value_casei' => 'sameas',
3511
  ),
@@ -3515,8 +4713,12 @@ class AMP_Allowed_Tags_Generated {
3515
  'type' => array(),
3516
  ),
3517
  'tag_spec' => array(
 
 
 
 
3518
  'spec_name' => 'link itemprop=sameas',
3519
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3520
  ),
3521
 
3522
  ),
@@ -3539,8 +4741,40 @@ class AMP_Allowed_Tags_Generated {
3539
  'type' => array(),
3540
  ),
3541
  'tag_spec' => array(
 
 
 
 
3542
  'spec_name' => 'link itemprop=',
3543
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3544
  ),
3545
 
3546
  ),
@@ -3575,7 +4809,6 @@ class AMP_Allowed_Tags_Generated {
3575
  'attr_spec_list' => array(
3576
  'alignment-baseline' => array(),
3577
  'baseline-shift' => array(),
3578
- 'class' => array(),
3579
  'clip' => array(),
3580
  'clip-path' => array(),
3581
  'clip-rule' => array(),
@@ -3634,24 +4867,31 @@ class AMP_Allowed_Tags_Generated {
3634
  'stroke-miterlimit' => array(),
3635
  'stroke-opacity' => array(),
3636
  'stroke-width' => array(),
 
 
 
3637
  'text-anchor' => array(),
3638
  'text-decoration' => array(),
3639
  'text-rendering' => array(),
3640
  'transform' => array(),
3641
  'unicode-bidi' => array(),
 
3642
  'viewbox' => array(),
3643
  'visibility' => array(),
3644
  'word-spacing' => array(),
3645
  'writing-mode' => array(),
3646
- 'xml:base' => array(),
3647
  'xml:lang' => array(),
3648
  'xml:space' => array(),
3649
  'xmlns' => array(),
3650
  'xmlns:xlink' => array(),
3651
  ),
3652
  'tag_spec' => array(
 
 
 
 
3653
  'mandatory_ancestor' => 'svg',
3654
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
3655
  ),
3656
 
3657
  ),
@@ -3661,7 +4901,6 @@ class AMP_Allowed_Tags_Generated {
3661
  'attr_spec_list' => array(
3662
  'alignment-baseline' => array(),
3663
  'baseline-shift' => array(),
3664
- 'class' => array(),
3665
  'clip' => array(),
3666
  'clip-path' => array(),
3667
  'clip-rule' => array(),
@@ -3718,17 +4957,20 @@ class AMP_Allowed_Tags_Generated {
3718
  'stroke-miterlimit' => array(),
3719
  'stroke-opacity' => array(),
3720
  'stroke-width' => array(),
 
 
 
3721
  'systemlanguage' => array(),
3722
  'text-anchor' => array(),
3723
  'text-decoration' => array(),
3724
  'text-rendering' => array(),
3725
  'unicode-bidi' => array(),
 
3726
  'visibility' => array(),
3727
  'width' => array(),
3728
  'word-spacing' => array(),
3729
  'writing-mode' => array(),
3730
  'x' => array(),
3731
- 'xml:base' => array(),
3732
  'xml:lang' => array(),
3733
  'xml:space' => array(),
3734
  'xmlns' => array(),
@@ -3736,8 +4978,12 @@ class AMP_Allowed_Tags_Generated {
3736
  'y' => array(),
3737
  ),
3738
  'tag_spec' => array(
 
 
 
 
3739
  'mandatory_ancestor' => 'svg',
3740
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
3741
  ),
3742
 
3743
  ),
@@ -3747,16 +4993,26 @@ class AMP_Allowed_Tags_Generated {
3747
  'attr_spec_list' => array(
3748
  'content' => array(
3749
  'mandatory' => true,
 
 
 
 
 
 
3750
  ),
3751
  'http-equiv' => array(
3752
- 'dispatch_key' => true,
3753
  'mandatory' => true,
3754
  'value_casei' => 'x-ua-compatible',
3755
  ),
3756
  ),
3757
  'tag_spec' => array(
 
 
 
 
 
3758
  'spec_name' => 'meta http-equiv=x-ua-compatible',
3759
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3760
  ),
3761
 
3762
  ),
@@ -3765,11 +5021,15 @@ class AMP_Allowed_Tags_Generated {
3765
  'content' => array(),
3766
  'itemprop' => array(),
3767
  'name' => array(
3768
- 'blacklisted_value_regex' => '(^|\\s)(viewport|content-disposition|revisit-after|apple-itunes-app)(\\s|$)',
3769
  ),
3770
  'property' => array(),
3771
  ),
3772
  'tag_spec' => array(
 
 
 
 
3773
  'spec_name' => 'meta name= and content=',
3774
  ),
3775
 
@@ -3781,14 +5041,18 @@ class AMP_Allowed_Tags_Generated {
3781
  'value_casei' => 'text/html; charset=utf-8',
3782
  ),
3783
  'http-equiv' => array(
3784
- 'dispatch_key' => true,
3785
  'mandatory' => true,
3786
  'value_casei' => 'content-type',
3787
  ),
3788
  ),
3789
  'tag_spec' => array(
 
 
 
 
 
3790
  'spec_name' => 'meta http-equiv=content-type',
3791
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3792
  ),
3793
 
3794
  ),
@@ -3798,14 +5062,18 @@ class AMP_Allowed_Tags_Generated {
3798
  'mandatory' => true,
3799
  ),
3800
  'http-equiv' => array(
3801
- 'dispatch_key' => true,
3802
  'mandatory' => true,
3803
  'value_casei' => 'content-language',
3804
  ),
3805
  ),
3806
  'tag_spec' => array(
 
 
 
 
 
3807
  'spec_name' => 'meta http-equiv=content-language',
3808
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3809
  ),
3810
 
3811
  ),
@@ -3815,14 +5083,18 @@ class AMP_Allowed_Tags_Generated {
3815
  'mandatory' => true,
3816
  ),
3817
  'http-equiv' => array(
3818
- 'dispatch_key' => true,
3819
  'mandatory' => true,
3820
  'value_casei' => 'pics-label',
3821
  ),
3822
  ),
3823
  'tag_spec' => array(
 
 
 
 
 
3824
  'spec_name' => 'meta http-equiv=pics-label',
3825
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3826
  ),
3827
 
3828
  ),
@@ -3832,14 +5104,18 @@ class AMP_Allowed_Tags_Generated {
3832
  'mandatory' => true,
3833
  ),
3834
  'http-equiv' => array(
3835
- 'dispatch_key' => true,
3836
  'mandatory' => true,
3837
  'value_casei' => 'imagetoolbar',
3838
  ),
3839
  ),
3840
  'tag_spec' => array(
 
 
 
 
 
3841
  'spec_name' => 'meta http-equiv=imagetoolbar',
3842
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3843
  ),
3844
 
3845
  ),
@@ -3850,14 +5126,18 @@ class AMP_Allowed_Tags_Generated {
3850
  'value_casei' => 'text/css',
3851
  ),
3852
  'http-equiv' => array(
3853
- 'dispatch_key' => true,
3854
  'mandatory' => true,
3855
  'value_casei' => 'content-style-type',
3856
  ),
3857
  ),
3858
  'tag_spec' => array(
 
 
 
 
 
3859
  'spec_name' => 'meta http-equiv=content-style-type',
3860
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
3861
  ),
3862
 
3863
  ),
@@ -3868,14 +5148,39 @@ class AMP_Allowed_Tags_Generated {
3868
  'value_casei' => 'text/javascript',
3869
  ),
3870
  'http-equiv' => array(
3871
- 'dispatch_key' => true,
3872
  'mandatory' => true,
3873
  'value_casei' => 'content-script-type',
3874
  ),
3875
  ),
3876
  'tag_spec' => array(
 
 
 
 
 
3877
  'spec_name' => 'meta http-equiv=content-script-type',
3878
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3879
  ),
3880
 
3881
  ),
@@ -3885,15 +5190,55 @@ class AMP_Allowed_Tags_Generated {
3885
  'mandatory' => true,
3886
  ),
3887
  'http-equiv' => array(
3888
- 'dispatch_key' => true,
3889
  'mandatory' => true,
3890
  'value_casei' => 'resource-type',
3891
  ),
3892
  ),
3893
  'tag_spec' => array(
 
 
 
 
 
3894
  'spec_name' => 'meta http-equiv=resource-type',
3895
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#html-tags',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3896
  ),
 
3897
 
3898
  ),
3899
  ),
@@ -3914,6 +5259,24 @@ class AMP_Allowed_Tags_Generated {
3914
  'tag_spec' => array(),
3915
 
3916
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3917
  ),
3918
  'nextid' => array(
3919
  array(
@@ -3983,15 +5346,14 @@ class AMP_Allowed_Tags_Generated {
3983
  'optgroup' => array(
3984
  array(
3985
  'attr_spec_list' => array(
 
 
3986
  'disabled' => array(),
3987
  'label' => array(),
3988
  ),
3989
  'tag_spec' => array(
3990
- 'also_requires_tag' => array(
3991
- 'amp-form extension .js script',
3992
- ),
3993
  'mandatory_parent' => 'select',
3994
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
3995
  ),
3996
 
3997
  ),
@@ -3999,18 +5361,31 @@ class AMP_Allowed_Tags_Generated {
3999
  'option' => array(
4000
  array(
4001
  'attr_spec_list' => array(
 
 
 
 
4002
  'disabled' => array(),
4003
  'label' => array(),
4004
  'selected' => array(),
4005
  'value' => array(),
4006
  ),
4007
  'tag_spec' => array(
4008
- 'also_requires_tag' => array(
4009
- 'amp-form extension .js script',
 
 
 
 
 
 
 
 
 
 
4010
  ),
4011
- 'mandatory_ancestor' => 'form',
4012
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
4013
  ),
 
4014
 
4015
  ),
4016
  ),
@@ -4028,7 +5403,6 @@ class AMP_Allowed_Tags_Generated {
4028
  'attr_spec_list' => array(
4029
  'alignment-baseline' => array(),
4030
  'baseline-shift' => array(),
4031
- 'class' => array(),
4032
  'clip' => array(),
4033
  'clip-path' => array(),
4034
  'clip-rule' => array(),
@@ -4085,24 +5459,31 @@ class AMP_Allowed_Tags_Generated {
4085
  'stroke-miterlimit' => array(),
4086
  'stroke-opacity' => array(),
4087
  'stroke-width' => array(),
 
 
 
4088
  'systemlanguage' => array(),
4089
  'text-anchor' => array(),
4090
  'text-decoration' => array(),
4091
  'text-rendering' => array(),
4092
  'transform' => array(),
4093
  'unicode-bidi' => array(),
 
4094
  'visibility' => array(),
4095
  'word-spacing' => array(),
4096
  'writing-mode' => array(),
4097
- 'xml:base' => array(),
4098
  'xml:lang' => array(),
4099
  'xml:space' => array(),
4100
  'xmlns' => array(),
4101
  'xmlns:xlink' => array(),
4102
  ),
4103
  'tag_spec' => array(
 
 
 
 
4104
  'mandatory_ancestor' => 'svg',
4105
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4106
  ),
4107
 
4108
  ),
@@ -4112,7 +5493,6 @@ class AMP_Allowed_Tags_Generated {
4112
  'attr_spec_list' => array(
4113
  'alignment-baseline' => array(),
4114
  'baseline-shift' => array(),
4115
- 'class' => array(),
4116
  'clip' => array(),
4117
  'clip-path' => array(),
4118
  'clip-rule' => array(),
@@ -4171,11 +5551,15 @@ class AMP_Allowed_Tags_Generated {
4171
  'stroke-miterlimit' => array(),
4172
  'stroke-opacity' => array(),
4173
  'stroke-width' => array(),
 
 
 
4174
  'systemlanguage' => array(),
4175
  'text-anchor' => array(),
4176
  'text-decoration' => array(),
4177
  'text-rendering' => array(),
4178
  'unicode-bidi' => array(),
 
4179
  'viewbox' => array(),
4180
  'visibility' => array(),
4181
  'width' => array(),
@@ -4185,13 +5569,20 @@ class AMP_Allowed_Tags_Generated {
4185
  'xlink:actuate' => array(),
4186
  'xlink:arcrole' => array(),
4187
  'xlink:href' => array(
4188
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
4189
  ),
4190
  'xlink:role' => array(),
4191
  'xlink:show' => array(),
4192
  'xlink:title' => array(),
4193
  'xlink:type' => array(),
4194
- 'xml:base' => array(),
4195
  'xml:lang' => array(),
4196
  'xml:space' => array(),
4197
  'xmlns' => array(),
@@ -4199,8 +5590,12 @@ class AMP_Allowed_Tags_Generated {
4199
  'y' => array(),
4200
  ),
4201
  'tag_spec' => array(
 
 
 
 
4202
  'mandatory_ancestor' => 'svg',
4203
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4204
  ),
4205
 
4206
  ),
@@ -4210,7 +5605,6 @@ class AMP_Allowed_Tags_Generated {
4210
  'attr_spec_list' => array(
4211
  'alignment-baseline' => array(),
4212
  'baseline-shift' => array(),
4213
- 'class' => array(),
4214
  'clip' => array(),
4215
  'clip-path' => array(),
4216
  'clip-rule' => array(),
@@ -4266,24 +5660,31 @@ class AMP_Allowed_Tags_Generated {
4266
  'stroke-miterlimit' => array(),
4267
  'stroke-opacity' => array(),
4268
  'stroke-width' => array(),
 
 
 
4269
  'systemlanguage' => array(),
4270
  'text-anchor' => array(),
4271
  'text-decoration' => array(),
4272
  'text-rendering' => array(),
4273
  'transform' => array(),
4274
  'unicode-bidi' => array(),
 
4275
  'visibility' => array(),
4276
  'word-spacing' => array(),
4277
  'writing-mode' => array(),
4278
- 'xml:base' => array(),
4279
  'xml:lang' => array(),
4280
  'xml:space' => array(),
4281
  'xmlns' => array(),
4282
  'xmlns:xlink' => array(),
4283
  ),
4284
  'tag_spec' => array(
 
 
 
 
4285
  'mandatory_ancestor' => 'svg',
4286
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4287
  ),
4288
 
4289
  ),
@@ -4293,7 +5694,6 @@ class AMP_Allowed_Tags_Generated {
4293
  'attr_spec_list' => array(
4294
  'alignment-baseline' => array(),
4295
  'baseline-shift' => array(),
4296
- 'class' => array(),
4297
  'clip' => array(),
4298
  'clip-path' => array(),
4299
  'clip-rule' => array(),
@@ -4349,24 +5749,31 @@ class AMP_Allowed_Tags_Generated {
4349
  'stroke-miterlimit' => array(),
4350
  'stroke-opacity' => array(),
4351
  'stroke-width' => array(),
 
 
 
4352
  'systemlanguage' => array(),
4353
  'text-anchor' => array(),
4354
  'text-decoration' => array(),
4355
  'text-rendering' => array(),
4356
  'transform' => array(),
4357
  'unicode-bidi' => array(),
 
4358
  'visibility' => array(),
4359
  'word-spacing' => array(),
4360
  'writing-mode' => array(),
4361
- 'xml:base' => array(),
4362
  'xml:lang' => array(),
4363
  'xml:space' => array(),
4364
  'xmlns' => array(),
4365
  'xmlns:xlink' => array(),
4366
  ),
4367
  'tag_spec' => array(
 
 
 
 
4368
  'mandatory_ancestor' => 'svg',
4369
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4370
  ),
4371
 
4372
  ),
@@ -4378,28 +5785,26 @@ class AMP_Allowed_Tags_Generated {
4378
 
4379
  ),
4380
  ),
 
 
 
 
 
 
 
 
 
 
4381
  'q' => array(
4382
  array(
4383
  'attr_spec_list' => array(
4384
  'cite' => array(
4385
  'blacklisted_value_regex' => '__amp_source_origin',
 
4386
  'allow_relative' => true,
4387
  'allowed_protocol' => array(
4388
- 'fb-messenger',
4389
- 'ftp',
4390
  'http',
4391
  'https',
4392
- 'intent',
4393
- 'mailto',
4394
- 'skype',
4395
- 'sms',
4396
- 'snapchat',
4397
- 'tel',
4398
- 'tg',
4399
- 'threema',
4400
- 'twitter',
4401
- 'viber',
4402
- 'whatsapp',
4403
  ),
4404
  ),
4405
  ),
@@ -4412,7 +5817,6 @@ class AMP_Allowed_Tags_Generated {
4412
  'attr_spec_list' => array(
4413
  'alignment-baseline' => array(),
4414
  'baseline-shift' => array(),
4415
- 'class' => array(),
4416
  'clip' => array(),
4417
  'clip-path' => array(),
4418
  'clip-rule' => array(),
@@ -4442,6 +5846,7 @@ class AMP_Allowed_Tags_Generated {
4442
  'font-style' => array(),
4443
  'font-variant' => array(),
4444
  'font-weight' => array(),
 
4445
  'fx' => array(),
4446
  'fy' => array(),
4447
  'glyph-orientation-horizontal' => array(),
@@ -4472,31 +5877,46 @@ class AMP_Allowed_Tags_Generated {
4472
  'stroke-miterlimit' => array(),
4473
  'stroke-opacity' => array(),
4474
  'stroke-width' => array(),
 
 
 
4475
  'text-anchor' => array(),
4476
  'text-decoration' => array(),
4477
  'text-rendering' => array(),
4478
  'unicode-bidi' => array(),
 
4479
  'visibility' => array(),
4480
  'word-spacing' => array(),
4481
  'writing-mode' => array(),
4482
  'xlink:actuate' => array(),
4483
  'xlink:arcrole' => array(),
4484
  'xlink:href' => array(
4485
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
4486
  ),
4487
  'xlink:role' => array(),
4488
  'xlink:show' => array(),
4489
  'xlink:title' => array(),
4490
  'xlink:type' => array(),
4491
- 'xml:base' => array(),
4492
  'xml:lang' => array(),
4493
  'xml:space' => array(),
4494
  'xmlns' => array(),
4495
  'xmlns:xlink' => array(),
4496
  ),
4497
  'tag_spec' => array(
 
 
 
 
4498
  'mandatory_ancestor' => 'svg',
4499
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4500
  ),
4501
 
4502
  ),
@@ -4513,7 +5933,6 @@ class AMP_Allowed_Tags_Generated {
4513
  'attr_spec_list' => array(
4514
  'alignment-baseline' => array(),
4515
  'baseline-shift' => array(),
4516
- 'class' => array(),
4517
  'clip' => array(),
4518
  'clip-path' => array(),
4519
  'clip-rule' => array(),
@@ -4571,18 +5990,21 @@ class AMP_Allowed_Tags_Generated {
4571
  'stroke-miterlimit' => array(),
4572
  'stroke-opacity' => array(),
4573
  'stroke-width' => array(),
 
 
 
4574
  'systemlanguage' => array(),
4575
  'text-anchor' => array(),
4576
  'text-decoration' => array(),
4577
  'text-rendering' => array(),
4578
  'transform' => array(),
4579
  'unicode-bidi' => array(),
 
4580
  'visibility' => array(),
4581
  'width' => array(),
4582
  'word-spacing' => array(),
4583
  'writing-mode' => array(),
4584
  'x' => array(),
4585
- 'xml:base' => array(),
4586
  'xml:lang' => array(),
4587
  'xml:space' => array(),
4588
  'xmlns' => array(),
@@ -4590,8 +6012,12 @@ class AMP_Allowed_Tags_Generated {
4590
  'y' => array(),
4591
  ),
4592
  'tag_spec' => array(
 
 
 
 
4593
  'mandatory_ancestor' => 'svg',
4594
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4595
  ),
4596
 
4597
  ),
@@ -4641,8 +6067,8 @@ class AMP_Allowed_Tags_Generated {
4641
  'script' => array(
4642
  array(
4643
  'attr_spec_list' => array(
 
4644
  'type' => array(
4645
- 'dispatch_key' => true,
4646
  'mandatory' => true,
4647
  'value_casei' => 'application/ld+json',
4648
  ),
@@ -4655,7 +6081,6 @@ class AMP_Allowed_Tags_Generated {
4655
  array(
4656
  'attr_spec_list' => array(
4657
  'type' => array(
4658
- 'dispatch_key' => true,
4659
  'mandatory' => true,
4660
  'value_casei' => 'application/json',
4661
  ),
@@ -4663,21 +6088,33 @@ class AMP_Allowed_Tags_Generated {
4663
  'tag_spec' => array(
4664
  'html_format' => array(
4665
  'amp',
4666
- 'amp4ads',
4667
  ),
4668
- 'mandatory_parent' => 'amp-analytics',
4669
- 'spec_name' => 'amp-analytics extension .json script',
4670
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-analytics.html',
4671
  ),
4672
 
4673
  ),
4674
  array(
4675
  'attr_spec_list' => array(
4676
- 'amp-ad-metadata' => array(
4677
- 'dispatch_key' => true,
4678
  'mandatory' => true,
4679
- 'value' => '',
 
 
 
 
 
4680
  ),
 
 
 
 
 
 
 
 
 
4681
  'type' => array(
4682
  'mandatory' => true,
4683
  'value_casei' => 'application/json',
@@ -4688,16 +6125,30 @@ class AMP_Allowed_Tags_Generated {
4688
  'amp',
4689
  'amp4ads',
4690
  ),
4691
- 'mandatory_parent' => 'body',
4692
- 'spec_name' => 'amp-ad-metadata .json script',
4693
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-analytics.html',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4694
  ),
4695
 
4696
  ),
4697
  array(
4698
  'attr_spec_list' => array(
 
4699
  'type' => array(
4700
- 'dispatch_key' => true,
4701
  'mandatory' => true,
4702
  'value_casei' => 'application/json',
4703
  ),
@@ -4708,14 +6159,14 @@ class AMP_Allowed_Tags_Generated {
4708
  ),
4709
  'mandatory_parent' => 'amp-state',
4710
  'spec_name' => 'amp-bind extension .json script',
4711
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-bind.html',
4712
  ),
4713
 
4714
  ),
4715
  array(
4716
  'attr_spec_list' => array(
 
4717
  'type' => array(
4718
- 'dispatch_key' => true,
4719
  'mandatory' => true,
4720
  'value_casei' => 'application/json',
4721
  ),
@@ -4726,7 +6177,7 @@ class AMP_Allowed_Tags_Generated {
4726
  ),
4727
  'mandatory_parent' => 'amp-experiment',
4728
  'spec_name' => 'amp-experiment extension .json script',
4729
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-experiment.html',
4730
  ),
4731
 
4732
  ),
@@ -4748,10 +6199,6 @@ class AMP_Allowed_Tags_Generated {
4748
  ),
4749
  ),
4750
  'tag_spec' => array(
4751
- 'html_format' => array(
4752
- 'amp',
4753
- 'amp4ads',
4754
- ),
4755
  'mandatory_parent' => 'amp-accordion',
4756
  'spec_name' => 'amp-accordion > section',
4757
  ),
@@ -4761,19 +6208,35 @@ class AMP_Allowed_Tags_Generated {
4761
  'select' => array(
4762
  array(
4763
  'attr_spec_list' => array(
 
 
 
 
 
4764
  'autofocus' => array(),
4765
  'disabled' => array(),
4766
  'multiple' => array(),
4767
- 'name' => array(),
 
 
4768
  'required' => array(),
4769
  'size' => array(),
4770
  ),
4771
  'tag_spec' => array(
4772
- 'also_requires_tag' => array(
4773
- 'amp-form extension .js script',
 
 
 
 
 
 
 
 
 
 
 
4774
  ),
4775
- 'mandatory_ancestor' => 'form',
4776
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
4777
  ),
4778
 
4779
  ),
@@ -4785,9 +6248,95 @@ class AMP_Allowed_Tags_Generated {
4785
 
4786
  ),
4787
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4788
  'source' => array(
4789
  array(
4790
  'attr_spec_list' => array(
 
 
4791
  'media' => array(),
4792
  'src' => array(
4793
  'blacklisted_value_regex' => '__amp_source_origin',
@@ -4799,14 +6348,20 @@ class AMP_Allowed_Tags_Generated {
4799
  'type' => array(),
4800
  ),
4801
  'tag_spec' => array(
 
 
 
 
4802
  'mandatory_parent' => 'amp-video',
4803
  'spec_name' => 'amp-video > source',
4804
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-video.html',
4805
  ),
4806
 
4807
  ),
4808
  array(
4809
  'attr_spec_list' => array(
 
 
4810
  'media' => array(),
4811
  'src' => array(
4812
  'blacklisted_value_regex' => '__amp_source_origin',
@@ -4818,9 +6373,13 @@ class AMP_Allowed_Tags_Generated {
4818
  'type' => array(),
4819
  ),
4820
  'tag_spec' => array(
 
 
 
 
4821
  'mandatory_parent' => 'amp-audio',
4822
  'spec_name' => 'amp-audio > source',
4823
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-audio.html',
4824
  ),
4825
 
4826
  ),
@@ -4840,9 +6399,13 @@ class AMP_Allowed_Tags_Generated {
4840
  ),
4841
  ),
4842
  'tag_spec' => array(
 
 
 
 
4843
  'mandatory_parent' => 'audio',
4844
  'spec_name' => 'audio > source',
4845
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-audio.html',
4846
  ),
4847
 
4848
  ),
@@ -4862,9 +6425,37 @@ class AMP_Allowed_Tags_Generated {
4862
  ),
4863
  ),
4864
  'tag_spec' => array(
4865
- 'mandatory_parent' => 'video',
4866
- 'spec_name' => 'video > source',
4867
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-video.html',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4868
  ),
4869
 
4870
  ),
@@ -4893,11 +6484,18 @@ class AMP_Allowed_Tags_Generated {
4893
  'offset' => array(),
4894
  'stop-color' => array(),
4895
  'stop-opacity' => array(),
 
 
 
4896
  ),
4897
  'tag_spec' => array(
 
 
 
 
4898
  'mandatory_ancestor' => 'lineargradient',
4899
  'spec_name' => 'lineargradient > stop',
4900
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4901
  ),
4902
 
4903
  ),
@@ -4906,11 +6504,18 @@ class AMP_Allowed_Tags_Generated {
4906
  'offset' => array(),
4907
  'stop-color' => array(),
4908
  'stop-opacity' => array(),
 
 
 
4909
  ),
4910
  'tag_spec' => array(
 
 
 
 
4911
  'mandatory_ancestor' => 'radialgradient',
4912
  'spec_name' => 'radialgradient > stop',
4913
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
4914
  ),
4915
 
4916
  ),
@@ -4937,19 +6542,17 @@ class AMP_Allowed_Tags_Generated {
4937
  array(
4938
  'attr_spec_list' => array(
4939
  'amp-boilerplate' => array(
4940
- 'dispatch_key' => true,
4941
  'mandatory' => true,
4942
  'value' => '',
4943
  ),
 
4944
  ),
4945
  'tag_spec' => array(
4946
- 'also_requires_tag' => array(
4947
- 'head > style[amp-boilerplate]',
4948
- ),
4949
  'html_format' => array(
4950
  'amp',
4951
  ),
4952
  'mandatory_alternatives' => 'noscript > style[amp-boilerplate]',
 
4953
  'mandatory_parent' => 'noscript',
4954
  'spec_name' => 'noscript > style[amp-boilerplate]',
4955
  'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
@@ -4957,6 +6560,24 @@ class AMP_Allowed_Tags_Generated {
4957
  ),
4958
 
4959
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4960
  ),
4961
  'sub' => array(
4962
  array(
@@ -4977,7 +6598,6 @@ class AMP_Allowed_Tags_Generated {
4977
  'attr_spec_list' => array(
4978
  'alignment-baseline' => array(),
4979
  'baseline-shift' => array(),
4980
- 'class' => array(),
4981
  'clip' => array(),
4982
  'clip-path' => array(),
4983
  'clip-rule' => array(),
@@ -5040,6 +6660,7 @@ class AMP_Allowed_Tags_Generated {
5040
  'text-decoration' => array(),
5041
  'text-rendering' => array(),
5042
  'unicode-bidi' => array(),
 
5043
  'version' => array(
5044
  'value_regex' => '(1.0|1.1)',
5045
  ),
@@ -5049,7 +6670,6 @@ class AMP_Allowed_Tags_Generated {
5049
  'word-spacing' => array(),
5050
  'writing-mode' => array(),
5051
  'x' => array(),
5052
- 'xml:base' => array(),
5053
  'xml:lang' => array(),
5054
  'xml:space' => array(),
5055
  'xmlns' => array(),
@@ -5058,7 +6678,96 @@ class AMP_Allowed_Tags_Generated {
5058
  'zoomandpan' => array(),
5059
  ),
5060
  'tag_spec' => array(
5061
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5062
  ),
5063
 
5064
  ),
@@ -5068,7 +6777,6 @@ class AMP_Allowed_Tags_Generated {
5068
  'attr_spec_list' => array(
5069
  'alignment-baseline' => array(),
5070
  'baseline-shift' => array(),
5071
- 'class' => array(),
5072
  'clip' => array(),
5073
  'clip-path' => array(),
5074
  'clip-rule' => array(),
@@ -5121,23 +6829,30 @@ class AMP_Allowed_Tags_Generated {
5121
  'stroke-miterlimit' => array(),
5122
  'stroke-opacity' => array(),
5123
  'stroke-width' => array(),
 
 
 
5124
  'text-anchor' => array(),
5125
  'text-decoration' => array(),
5126
  'text-rendering' => array(),
5127
  'unicode-bidi' => array(),
 
5128
  'viewbox' => array(),
5129
  'visibility' => array(),
5130
  'word-spacing' => array(),
5131
  'writing-mode' => array(),
5132
- 'xml:base' => array(),
5133
  'xml:lang' => array(),
5134
  'xml:space' => array(),
5135
  'xmlns' => array(),
5136
  'xmlns:xlink' => array(),
5137
  ),
5138
  'tag_spec' => array(
 
 
 
 
5139
  'mandatory_ancestor' => 'svg',
5140
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5141
  ),
5142
 
5143
  ),
@@ -5191,13 +6906,9 @@ class AMP_Allowed_Tags_Generated {
5191
  ),
5192
  ),
5193
  'tag_spec' => array(
5194
- 'also_requires_tag' => array(
5195
- 'amp-mustache extension .js script',
5196
- ),
5197
  'disallowed_ancestor' => array(
5198
  'template',
5199
  ),
5200
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-mustache.html',
5201
  ),
5202
 
5203
  ),
@@ -5207,7 +6918,6 @@ class AMP_Allowed_Tags_Generated {
5207
  'attr_spec_list' => array(
5208
  'alignment-baseline' => array(),
5209
  'baseline-shift' => array(),
5210
- 'class' => array(),
5211
  'clip' => array(),
5212
  'clip-path' => array(),
5213
  'clip-rule' => array(),
@@ -5265,6 +6975,9 @@ class AMP_Allowed_Tags_Generated {
5265
  'stroke-miterlimit' => array(),
5266
  'stroke-opacity' => array(),
5267
  'stroke-width' => array(),
 
 
 
5268
  'systemlanguage' => array(),
5269
  'text-anchor' => array(),
5270
  'text-decoration' => array(),
@@ -5272,11 +6985,11 @@ class AMP_Allowed_Tags_Generated {
5272
  'textlength' => array(),
5273
  'transform' => array(),
5274
  'unicode-bidi' => array(),
 
5275
  'visibility' => array(),
5276
  'word-spacing' => array(),
5277
  'writing-mode' => array(),
5278
  'x' => array(),
5279
- 'xml:base' => array(),
5280
  'xml:lang' => array(),
5281
  'xml:space' => array(),
5282
  'xmlns' => array(),
@@ -5284,8 +6997,12 @@ class AMP_Allowed_Tags_Generated {
5284
  'y' => array(),
5285
  ),
5286
  'tag_spec' => array(
 
 
 
 
5287
  'mandatory_ancestor' => 'svg',
5288
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5289
  ),
5290
 
5291
  ),
@@ -5293,13 +7010,30 @@ class AMP_Allowed_Tags_Generated {
5293
  'textarea' => array(
5294
  array(
5295
  'attr_spec_list' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5296
  'autocomplete' => array(),
5297
  'autofocus' => array(),
5298
  'cols' => array(),
5299
  'disabled' => array(),
5300
  'maxlength' => array(),
5301
  'minlength' => array(),
5302
- 'name' => array(),
 
 
5303
  'placeholder' => array(),
5304
  'readonly' => array(),
5305
  'required' => array(),
@@ -5311,11 +7045,7 @@ class AMP_Allowed_Tags_Generated {
5311
  'wrap' => array(),
5312
  ),
5313
  'tag_spec' => array(
5314
- 'also_requires_tag' => array(
5315
- 'amp-form extension .js script',
5316
- ),
5317
- 'mandatory_ancestor' => 'form',
5318
- 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
5319
  ),
5320
 
5321
  ),
@@ -5325,7 +7055,6 @@ class AMP_Allowed_Tags_Generated {
5325
  'attr_spec_list' => array(
5326
  'alignment-baseline' => array(),
5327
  'baseline-shift' => array(),
5328
- 'class' => array(),
5329
  'clip' => array(),
5330
  'clip-path' => array(),
5331
  'clip-rule' => array(),
@@ -5382,32 +7111,47 @@ class AMP_Allowed_Tags_Generated {
5382
  'stroke-miterlimit' => array(),
5383
  'stroke-opacity' => array(),
5384
  'stroke-width' => array(),
 
 
 
5385
  'systemlanguage' => array(),
5386
  'text-anchor' => array(),
5387
  'text-decoration' => array(),
5388
  'text-rendering' => array(),
5389
  'unicode-bidi' => array(),
 
5390
  'visibility' => array(),
5391
  'word-spacing' => array(),
5392
  'writing-mode' => array(),
5393
  'xlink:actuate' => array(),
5394
  'xlink:arcrole' => array(),
5395
  'xlink:href' => array(
5396
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
5397
  ),
5398
  'xlink:role' => array(),
5399
  'xlink:show' => array(),
5400
  'xlink:title' => array(),
5401
  'xlink:type' => array(),
5402
- 'xml:base' => array(),
5403
  'xml:lang' => array(),
5404
  'xml:space' => array(),
5405
  'xmlns' => array(),
5406
  'xmlns:xlink' => array(),
5407
  ),
5408
  'tag_spec' => array(
 
 
 
 
5409
  'mandatory_ancestor' => 'svg',
5410
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5411
  ),
5412
 
5413
  ),
@@ -5464,17 +7208,22 @@ class AMP_Allowed_Tags_Generated {
5464
  ),
5465
  array(
5466
  'attr_spec_list' => array(
5467
- 'class' => array(),
5468
- 'xml:base' => array(),
 
5469
  'xml:lang' => array(),
5470
  'xml:space' => array(),
5471
  'xmlns' => array(),
5472
  'xmlns:xlink' => array(),
5473
  ),
5474
  'tag_spec' => array(
 
 
 
 
5475
  'mandatory_ancestor' => 'svg',
5476
  'spec_name' => 'svg title',
5477
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5478
  ),
5479
 
5480
  ),
@@ -5512,6 +7261,10 @@ class AMP_Allowed_Tags_Generated {
5512
  'srclang' => array(),
5513
  ),
5514
  'tag_spec' => array(
 
 
 
 
5515
  'mandatory_parent' => 'audio',
5516
  'spec_name' => 'audio > track',
5517
  ),
@@ -5540,6 +7293,10 @@ class AMP_Allowed_Tags_Generated {
5540
  ),
5541
  ),
5542
  'tag_spec' => array(
 
 
 
 
5543
  'mandatory_parent' => 'audio',
5544
  'spec_name' => 'audio > track[kind=subtitles]',
5545
  ),
@@ -5565,6 +7322,10 @@ class AMP_Allowed_Tags_Generated {
5565
  'srclang' => array(),
5566
  ),
5567
  'tag_spec' => array(
 
 
 
 
5568
  'mandatory_parent' => 'video',
5569
  'spec_name' => 'video > track',
5570
  ),
@@ -5593,6 +7354,10 @@ class AMP_Allowed_Tags_Generated {
5593
  ),
5594
  ),
5595
  'tag_spec' => array(
 
 
 
 
5596
  'mandatory_parent' => 'video',
5597
  'spec_name' => 'video > track[kind=subtitles]',
5598
  ),
@@ -5600,6 +7365,9 @@ class AMP_Allowed_Tags_Generated {
5600
  ),
5601
  array(
5602
  'attr_spec_list' => array(
 
 
 
5603
  'default' => array(
5604
  'value' => '',
5605
  ),
@@ -5618,6 +7386,10 @@ class AMP_Allowed_Tags_Generated {
5618
  'srclang' => array(),
5619
  ),
5620
  'tag_spec' => array(
 
 
 
 
5621
  'mandatory_parent' => 'amp-audio',
5622
  'spec_name' => 'amp-audio > track',
5623
  ),
@@ -5625,6 +7397,9 @@ class AMP_Allowed_Tags_Generated {
5625
  ),
5626
  array(
5627
  'attr_spec_list' => array(
 
 
 
5628
  'default' => array(
5629
  'value' => '',
5630
  ),
@@ -5646,6 +7421,10 @@ class AMP_Allowed_Tags_Generated {
5646
  ),
5647
  ),
5648
  'tag_spec' => array(
 
 
 
 
5649
  'mandatory_parent' => 'amp-audio',
5650
  'spec_name' => 'amp-audio > track[kind=subtitles]',
5651
  ),
@@ -5653,6 +7432,9 @@ class AMP_Allowed_Tags_Generated {
5653
  ),
5654
  array(
5655
  'attr_spec_list' => array(
 
 
 
5656
  'default' => array(
5657
  'value' => '',
5658
  ),
@@ -5671,6 +7453,10 @@ class AMP_Allowed_Tags_Generated {
5671
  'srclang' => array(),
5672
  ),
5673
  'tag_spec' => array(
 
 
 
 
5674
  'mandatory_parent' => 'amp-video',
5675
  'spec_name' => 'amp-video > track',
5676
  ),
@@ -5678,6 +7464,9 @@ class AMP_Allowed_Tags_Generated {
5678
  ),
5679
  array(
5680
  'attr_spec_list' => array(
 
 
 
5681
  'default' => array(
5682
  'value' => '',
5683
  ),
@@ -5699,18 +7488,57 @@ class AMP_Allowed_Tags_Generated {
5699
  ),
5700
  ),
5701
  'tag_spec' => array(
 
 
 
 
5702
  'mandatory_parent' => 'amp-video',
5703
  'spec_name' => 'amp-video > track[kind=subtitles]',
5704
  ),
5705
 
5706
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5707
  ),
5708
  'tref' => array(
5709
  array(
5710
  'attr_spec_list' => array(
5711
  'alignment-baseline' => array(),
5712
  'baseline-shift' => array(),
5713
- 'class' => array(),
5714
  'clip' => array(),
5715
  'clip-path' => array(),
5716
  'clip-rule' => array(),
@@ -5764,32 +7592,47 @@ class AMP_Allowed_Tags_Generated {
5764
  'stroke-miterlimit' => array(),
5765
  'stroke-opacity' => array(),
5766
  'stroke-width' => array(),
 
 
 
5767
  'systemlanguage' => array(),
5768
  'text-anchor' => array(),
5769
  'text-decoration' => array(),
5770
  'text-rendering' => array(),
5771
  'unicode-bidi' => array(),
 
5772
  'visibility' => array(),
5773
  'word-spacing' => array(),
5774
  'writing-mode' => array(),
5775
  'xlink:actuate' => array(),
5776
  'xlink:arcrole' => array(),
5777
  'xlink:href' => array(
5778
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
5779
  ),
5780
  'xlink:role' => array(),
5781
  'xlink:show' => array(),
5782
  'xlink:title' => array(),
5783
  'xlink:type' => array(),
5784
- 'xml:base' => array(),
5785
  'xml:lang' => array(),
5786
  'xml:space' => array(),
5787
  'xmlns' => array(),
5788
  'xmlns:xlink' => array(),
5789
  ),
5790
  'tag_spec' => array(
 
 
 
 
5791
  'mandatory_ancestor' => 'svg',
5792
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5793
  ),
5794
 
5795
  ),
@@ -5799,7 +7642,6 @@ class AMP_Allowed_Tags_Generated {
5799
  'attr_spec_list' => array(
5800
  'alignment-baseline' => array(),
5801
  'baseline-shift' => array(),
5802
- 'class' => array(),
5803
  'clip' => array(),
5804
  'clip-path' => array(),
5805
  'clip-rule' => array(),
@@ -5857,17 +7699,20 @@ class AMP_Allowed_Tags_Generated {
5857
  'stroke-miterlimit' => array(),
5858
  'stroke-opacity' => array(),
5859
  'stroke-width' => array(),
 
 
 
5860
  'systemlanguage' => array(),
5861
  'text-anchor' => array(),
5862
  'text-decoration' => array(),
5863
  'text-rendering' => array(),
5864
  'textlength' => array(),
5865
  'unicode-bidi' => array(),
 
5866
  'visibility' => array(),
5867
  'word-spacing' => array(),
5868
  'writing-mode' => array(),
5869
  'x' => array(),
5870
- 'xml:base' => array(),
5871
  'xml:lang' => array(),
5872
  'xml:space' => array(),
5873
  'xmlns' => array(),
@@ -5875,8 +7720,12 @@ class AMP_Allowed_Tags_Generated {
5875
  'y' => array(),
5876
  ),
5877
  'tag_spec' => array(
 
 
 
 
5878
  'mandatory_ancestor' => 'svg',
5879
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5880
  ),
5881
 
5882
  ),
@@ -5911,7 +7760,6 @@ class AMP_Allowed_Tags_Generated {
5911
  'attr_spec_list' => array(
5912
  'alignment-baseline' => array(),
5913
  'baseline-shift' => array(),
5914
- 'class' => array(),
5915
  'clip' => array(),
5916
  'clip-path' => array(),
5917
  'clip-rule' => array(),
@@ -5966,12 +7814,16 @@ class AMP_Allowed_Tags_Generated {
5966
  'stroke-miterlimit' => array(),
5967
  'stroke-opacity' => array(),
5968
  'stroke-width' => array(),
 
 
 
5969
  'systemlanguage' => array(),
5970
  'text-anchor' => array(),
5971
  'text-decoration' => array(),
5972
  'text-rendering' => array(),
5973
  'transform' => array(),
5974
  'unicode-bidi' => array(),
 
5975
  'visibility' => array(),
5976
  'width' => array(),
5977
  'word-spacing' => array(),
@@ -5980,13 +7832,20 @@ class AMP_Allowed_Tags_Generated {
5980
  'xlink:actuate' => array(),
5981
  'xlink:arcrole' => array(),
5982
  'xlink:href' => array(
5983
- 'value_regex' => '#.*',
 
 
 
 
 
 
 
 
5984
  ),
5985
  'xlink:role' => array(),
5986
  'xlink:show' => array(),
5987
  'xlink:title' => array(),
5988
  'xlink:type' => array(),
5989
- 'xml:base' => array(),
5990
  'xml:lang' => array(),
5991
  'xml:space' => array(),
5992
  'xmlns' => array(),
@@ -5994,8 +7853,12 @@ class AMP_Allowed_Tags_Generated {
5994
  'y' => array(),
5995
  ),
5996
  'tag_spec' => array(
 
 
 
 
5997
  'mandatory_ancestor' => 'svg',
5998
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5999
  ),
6000
 
6001
  ),
@@ -6015,6 +7878,7 @@ class AMP_Allowed_Tags_Generated {
6015
  'height' => array(),
6016
  'loop' => array(),
6017
  'muted' => array(),
 
6018
  'poster' => array(),
6019
  'preload' => array(),
6020
  'src' => array(
@@ -6033,7 +7897,7 @@ class AMP_Allowed_Tags_Generated {
6033
  ),
6034
  'mandatory_ancestor' => 'noscript',
6035
  'mandatory_ancestor_suggested_alternative' => 'amp-video',
6036
- 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-video.html',
6037
  ),
6038
 
6039
  ),
@@ -6043,9 +7907,11 @@ class AMP_Allowed_Tags_Generated {
6043
  'attr_spec_list' => array(
6044
  'externalresourcesrequired' => array(),
6045
  'preserveaspectratio' => array(),
 
 
 
6046
  'viewbox' => array(),
6047
  'viewtarget' => array(),
6048
- 'xml:base' => array(),
6049
  'xml:lang' => array(),
6050
  'xml:space' => array(),
6051
  'xmlns' => array(),
@@ -6053,8 +7919,12 @@ class AMP_Allowed_Tags_Generated {
6053
  'zoomandpan' => array(),
6054
  ),
6055
  'tag_spec' => array(
 
 
 
 
6056
  'mandatory_ancestor' => 'svg',
6057
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
6058
  ),
6059
 
6060
  ),
@@ -6065,17 +7935,23 @@ class AMP_Allowed_Tags_Generated {
6065
  'g1' => array(),
6066
  'g2' => array(),
6067
  'k' => array(),
 
 
 
6068
  'u1' => array(),
6069
  'u2' => array(),
6070
- 'xml:base' => array(),
6071
  'xml:lang' => array(),
6072
  'xml:space' => array(),
6073
  'xmlns' => array(),
6074
  'xmlns:xlink' => array(),
6075
  ),
6076
  'tag_spec' => array(
 
 
 
 
6077
  'mandatory_ancestor' => 'svg',
6078
- 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
6079
  ),
6080
 
6081
  ),
@@ -6101,6 +7977,8 @@ class AMP_Allowed_Tags_Generated {
6101
  );
6102
 
6103
  private static $layout_allowed_attrs = array(
 
 
6104
  'height' => array(),
6105
  'heights' => array(),
6106
  'layout' => array(),
@@ -6111,6 +7989,45 @@ class AMP_Allowed_Tags_Generated {
6111
 
6112
 
6113
  private static $globally_allowed_attrs = array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6114
  'accesskey' => array(),
6115
  'amp-access' => array(),
6116
  'amp-access-behavior' => array(),
@@ -6129,6 +8046,7 @@ class AMP_Allowed_Tags_Generated {
6129
  'aria-busy' => array(),
6130
  'aria-checked' => array(),
6131
  'aria-controls' => array(),
 
6132
  'aria-describedby' => array(),
6133
  'aria-disabled' => array(),
6134
  'aria-dropeffect' => array(),
@@ -6161,15 +8079,21 @@ class AMP_Allowed_Tags_Generated {
6161
  'class' => array(
6162
  'blacklisted_value_regex' => '(^|\\w)i-amphtml-',
6163
  ),
 
 
6164
  'dir' => array(),
6165
  'draggable' => array(),
6166
  'fallback' => array(
6167
  'value' => '',
6168
  ),
 
 
 
6169
  'i-amp-access-id' => array(),
6170
  'id' => array(
6171
- 'blacklisted_value_regex' => '^i-amphtml-',
6172
  ),
 
6173
  'itemid' => array(),
6174
  'itemprop' => array(),
6175
  'itemref' => array(),
@@ -6182,14 +8106,23 @@ class AMP_Allowed_Tags_Generated {
6182
  'placeholder' => array(
6183
  'value' => '',
6184
  ),
 
 
 
 
 
 
 
6185
  'role' => array(),
6186
  'tabindex' => array(),
6187
  'title' => array(),
6188
  'translate' => array(),
 
6189
  'validation-for' => array(),
6190
  'visible-when-invalid' => array(
6191
  'value_regex' => '(badinput|customerror|patternmismatch|rangeoverflow|rangeunderflow|stepmismatch|toolong|typemismatch|valuemissing)',
6192
  ),
 
6193
 
6194
  );
6195
 
1
  <?php
2
  /**
3
+ * Generated by amphtml-update.py - do not edit.
4
  *
5
  * This is a list of HTML tags and attributes that are allowed by the
6
  * AMP specification. Note that tag names have been converted to lowercase.
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(
19
  array(
20
  'attr_spec_list' => array(
21
+ '[href]' => array(),
22
  'border' => array(),
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',
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
  ),
95
 
96
  ),
97
  ),
98
+ 'amp-3q-player' => array(
99
+ array(
100
+ 'attr_spec_list' => array(
101
+ 'autoplay' => array(
102
+ 'value' => '',
103
+ ),
104
+ 'data-id' => array(
105
+ 'mandatory' => true,
106
+ ),
107
+ 'media' => array(),
108
+ 'noloading' => array(
109
+ 'value' => '',
110
+ ),
111
+ ),
112
+ 'tag_spec' => array(
113
+ 'html_format' => array(
114
+ 'amp',
115
+ ),
116
+ ),
117
+
118
+ ),
119
+ ),
120
  'amp-accordion' => array(
121
  array(
122
  'attr_spec_list' => array(
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(
134
+ array(
135
+ 'attr_spec_list' => array(
136
+ 'alt' => array(),
137
+ 'json' => array(),
138
+ 'media' => array(),
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(
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
  ),
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',
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
  ),
248
  'attr_spec_list' => array(
249
  'alt' => array(),
250
  'attribution' => array(),
 
 
 
251
  'controls' => array(),
252
  'media' => array(),
253
  'noloading' => array(
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(
281
+ array(
282
+ 'attr_spec_list' => array(
283
+ 'media' => array(),
284
+ 'noloading' => array(
285
+ 'value' => '',
286
+ ),
287
+ 'trigger' => array(
288
+ 'value' => 'visibility',
289
  ),
290
+ ),
291
+ 'tag_spec' => array(
292
  'html_format' => array(
293
  'amp',
294
  'amp4ads',
295
  ),
 
296
  ),
297
 
298
  ),
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
  ),
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
 
345
  'amp-audio' => array(
346
  array(
347
  'attr_spec_list' => array(
348
+ 'album' => array(),
349
+ 'artist' => array(),
350
+ 'artwork' => array(),
351
  'autoplay' => array(
352
+ 'value' => '',
353
  ),
354
  'controls' => array(),
355
+ 'controlslist' => array(),
356
  'loop' => array(
357
  'value' => '',
358
  ),
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(
387
+ 'album' => array(),
388
+ 'artist' => array(),
389
+ 'artwork' => array(),
390
+ 'autoplay' => array(
391
+ 'mandatory' => true,
392
+ 'value' => '',
393
+ ),
394
  'controls' => array(),
395
+ 'controlslist' => array(),
396
  'loop' => array(
397
  'value' => '',
398
  ),
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
  ),
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
  ),
480
  'amp-brid-player' => array(
481
  array(
482
  'attr_spec_list' => array(
483
+ 'autoplay' => array(),
484
+ 'data-outstream' => array(
485
+ 'value_regex' => '[0-9]+',
486
+ ),
487
  'data-partner' => array(
488
  'mandatory' => true,
489
  'value_regex' => '[0-9]+',
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
  ),
515
  'amp-brightcove' => array(
516
  array(
517
  'attr_spec_list' => array(
518
+ '[data-account]' => array(),
519
+ '[data-embed]' => array(),
520
+ '[data-player-id]' => array(),
521
+ '[data-player]' => array(),
522
+ '[data-playlist-id]' => array(),
523
+ '[data-video-id]' => array(),
524
  'data-account' => array(
525
  'mandatory' => true,
526
  ),
 
 
 
 
527
  'media' => array(),
528
  'noloading' => array(
529
  'value' => '',
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(
542
+ array(
543
+ 'attr_spec_list' => array(
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(),
553
+ 'noloading' => array(
554
+ 'value' => '',
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
  ),
566
  'amp-carousel' => array(
567
  array(
568
  'attr_spec_list' => array(
569
+ '[slide]' => array(),
570
  'arrows' => array(
571
  'value' => '',
572
  ),
592
  ),
593
  ),
594
  'tag_spec' => array(
595
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-carousel',
 
 
 
 
 
 
 
 
 
 
596
  ),
597
 
598
  ),
600
  'amp-dailymotion' => array(
601
  array(
602
  'attr_spec_list' => array(
603
+ 'autoplay' => array(),
604
  'data-endscreen-enable' => array(
605
  'value_regex' => 'true|false',
606
  ),
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(
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
  ),
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
 
736
  ),
737
  ),
738
  'tag_spec' => array(
739
+ 'html_format' => array(
740
+ 'amp',
741
  ),
742
+ ),
743
+
744
+ ),
745
+ ),
746
+ 'amp-facebook-comments' => array(
747
+ array(
748
+ 'attr_spec_list' => array(
749
+ 'data-href' => array(
750
+ 'mandatory' => true,
751
+ ),
752
+ 'media' => array(),
753
+ 'noloading' => array(
754
+ 'value' => '',
755
  ),
756
+ ),
757
+ 'tag_spec' => array(
758
  'html_format' => array(
759
  'amp',
 
760
  ),
 
761
  ),
762
 
763
  ),
764
  ),
765
+ 'amp-facebook-like' => array(
766
  array(
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(),
 
777
  'noloading' => array(
778
  'value' => '',
779
  ),
780
  ),
781
  'tag_spec' => array(
 
 
 
782
  'html_format' => array(
783
  'amp',
 
784
  ),
 
785
  ),
786
 
787
  ),
788
  ),
789
+ 'amp-fit-text' => array(
790
+ array(
791
+ 'attr_spec_list' => array(
792
+ 'max-font-size' => array(),
793
+ 'media' => array(),
794
+ 'min-font-size' => array(),
795
+ 'noloading' => array(
796
+ 'value' => '',
797
+ ),
798
+ ),
799
+ 'tag_spec' => array(),
800
+
801
+ ),
802
+ ),
803
  'amp-font' => array(
804
  array(
805
  'attr_spec_list' => array(
822
  ),
823
  ),
824
  'tag_spec' => array(
 
 
 
 
 
 
825
  'html_format' => array(
826
  'amp',
827
  'amp4ads',
828
  ),
 
829
  ),
830
 
831
  ),
842
  ),
843
  ),
844
  'tag_spec' => array(
 
 
 
 
 
 
845
  'html_format' => array(
846
  'amp',
 
847
  ),
 
848
  ),
849
 
850
  ),
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(
876
+ array(
877
+ 'attr_spec_list' => array(
878
+ 'data-gistid' => array(
879
+ 'mandatory' => true,
880
+ ),
881
+ 'media' => array(),
882
+ 'noloading' => array(
883
+ 'value' => '',
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
  ),
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
  ),
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
  ),
944
+ 'allowpaymentrequest' => array(
945
+ 'value' => '',
946
+ ),
947
  'allowtransparency' => array(
948
  'value' => '',
949
  ),
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(),
1001
+ 'noloading' => array(
1002
+ 'value' => '',
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
  ),
1021
  ),
1022
  ),
1023
  'tag_spec' => array(
 
 
 
 
 
 
1024
  'html_format' => array(
1025
  'amp',
 
1026
  ),
 
1027
  ),
1028
 
1029
  ),
1031
  'amp-img' => array(
1032
  array(
1033
  'attr_spec_list' => array(
1034
+ '[alt]' => array(),
1035
+ '[attribution]' => array(),
1036
+ '[src]' => array(),
1037
+ '[srcset]' => array(),
1038
  'alt' => array(),
1039
  'attribution' => array(),
1040
  'media' => 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(
1070
+ array(
1071
+ 'attr_spec_list' => array(
1072
+ 'data-imgur-id' => array(
1073
+ 'mandatory' => true,
1074
+ ),
1075
+ 'media' => array(),
1076
+ 'noloading' => array(
1077
+ 'value' => '',
1078
+ ),
1079
+ ),
1080
+ 'tag_spec' => array(
1081
+ 'html_format' => array(
1082
+ 'amp',
1083
+ ),
1084
  ),
1085
 
1086
  ),
1098
  ),
1099
  ),
1100
  'tag_spec' => array(
 
 
 
 
 
 
1101
  'html_format' => array(
1102
  'amp',
 
1103
  ),
 
1104
  ),
1105
 
1106
  ),
1108
  'amp-install-serviceworker' => array(
1109
  array(
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,
1125
  ),
1126
  ),
1127
  'tag_spec' => array(
1128
+ 'html_format' => array(
1129
+ 'amp',
1130
  ),
1131
+ ),
1132
+
1133
+ ),
1134
+ ),
1135
+ 'amp-izlesene' => array(
1136
+ array(
1137
+ 'attr_spec_list' => array(
1138
+ 'data-videoid' => array(
1139
+ 'mandatory' => true,
1140
+ 'value_regex' => '[0-9]+',
1141
+ ),
1142
+ 'media' => array(),
1143
+ 'noloading' => array(
1144
+ 'value' => '',
1145
  ),
1146
+ ),
1147
+ 'tag_spec' => array(
1148
  'html_format' => array(
1149
  'amp',
1150
  ),
 
1151
  ),
1152
 
1153
  ),
1167
  ),
1168
  ),
1169
  'tag_spec' => array(
 
 
 
 
 
 
1170
  'html_format' => array(
1171
  'amp',
 
1172
  ),
 
1173
  ),
1174
 
1175
  ),
1186
  ),
1187
  ),
1188
  'tag_spec' => array(
1189
+ 'html_format' => array(
1190
+ 'amp',
1191
  ),
1192
+ ),
1193
+
1194
+ ),
1195
+ ),
1196
+ 'amp-layout' => array(
1197
+ array(
1198
+ 'attr_spec_list' => array(
1199
+ 'media' => array(),
1200
+ 'noloading' => array(
1201
+ 'value' => '',
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
  ),
1223
  'scrollable' => array(),
1224
  ),
1225
  'tag_spec' => array(
 
 
 
 
 
 
1226
  'html_format' => array(
1227
  'amp',
 
1228
  ),
 
1229
  ),
1230
 
1231
  ),
1233
  'amp-list' => array(
1234
  array(
1235
  'attr_spec_list' => array(
1236
+ '[src]' => array(),
1237
+ '[state]' => array(),
1238
  'credentials' => array(),
1239
+ 'items' => array(),
1240
+ 'max-items' => array(),
1241
  'media' => array(),
1242
  'noloading' => array(
1243
  'value' => '',
1244
  ),
1245
+ 'single-item' => array(),
1246
  'src' => array(
1247
  'blacklisted_value_regex' => '__amp_source_origin',
1248
  'mandatory' => true,
1254
  'template' => array(),
1255
  ),
1256
  'tag_spec' => array(
 
 
 
1257
  'html_format' => array(
1258
  'amp',
 
1259
  ),
 
1260
  ),
1261
 
1262
  ),
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
+ ),
1299
+ 'data-mode' => array(
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
+ ),
1313
+ 'media' => array(),
1314
+ 'noloading' => array(
1315
+ 'value' => '',
1316
  ),
1317
+ ),
1318
+ 'tag_spec' => array(
1319
  'html_format' => array(
1320
  'amp',
 
1321
  ),
 
1322
  ),
1323
 
1324
  ),
1329
  'data-bcid' => array(
1330
  'mandatory' => true,
1331
  ),
 
1332
  'data-pid' => array(
1333
  'mandatory' => true,
1334
  ),
 
1335
  'media' => array(),
1336
  'noloading' => array(
1337
  'value' => '',
1338
  ),
1339
  ),
1340
  'tag_spec' => array(
 
 
 
 
 
 
1341
  'html_format' => array(
1342
  'amp',
 
1343
  ),
 
1344
  ),
1345
 
1346
  ),
1348
  'amp-ooyala-player' => array(
1349
  array(
1350
  'attr_spec_list' => array(
 
1351
  'data-embedcode' => array(
1352
  'mandatory' => true,
1353
  ),
1357
  'data-playerid' => array(
1358
  'mandatory' => true,
1359
  ),
 
1360
  'media' => array(),
1361
  'noloading' => array(
1362
  'value' => '',
1363
  ),
1364
  ),
1365
  'tag_spec' => array(
 
 
 
1366
  'html_format' => array(
1367
  'amp',
 
1368
  ),
 
1369
  ),
1370
 
1371
  ),
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
  ),
1397
  'noloading' => array(
1398
  'value' => '',
1399
  ),
1400
+ 'referrerpolicy' => array(
1401
+ 'value' => 'no-referrer',
1402
+ ),
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',
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
  ),
1426
  'data-comments' => array(
1427
  'value_regex_casei' => '(false|true)',
1428
  ),
1429
+ 'data-item' => array(),
1430
  'data-item-info' => array(
1431
  'value_regex_casei' => '(false|true)',
1432
  ),
1437
  'noloading' => array(
1438
  'value' => '',
1439
  ),
1440
+ 'src' => array(),
 
 
1441
  ),
1442
  'tag_spec' => array(
1443
+ 'html_format' => array(
1444
+ 'amp',
1445
+ ),
1446
+ ),
1447
+
1448
+ ),
1449
+ ),
1450
+ 'amp-position-observer' => array(
1451
+ array(
1452
+ 'attr_spec_list' => array(
1453
+ 'intersection-ratios' => array(
1454
+ 'value_regex' => '^([0]*?\\.\\d*$|1$|0$)|([0]*?\\.\\d*|1|0)\\s{1}([0]*?\\.\\d*$|1$|0$)',
1455
+ ),
1456
+ 'media' => array(),
1457
+ 'noloading' => array(
1458
+ 'value' => '',
1459
  ),
1460
+ 'target' => array(),
1461
+ 'viewport-margins' => array(
1462
+ 'value_regex' => '^(\\d+$|\\d+px$|\\d+vh$)|((\\d+|\\d+px|\\d+vh)\\s{1}(\\d+$|\\d+px$|\\d+vh$))',
1463
+ ),
1464
+ ),
1465
+ 'tag_spec' => array(
1466
  'html_format' => array(
1467
  'amp',
1468
  'amp4ads',
1469
  ),
 
1470
  ),
1471
 
1472
  ),
1484
  ),
1485
  ),
1486
  'tag_spec' => array(
 
 
 
 
 
 
1487
  'html_format' => array(
1488
  'amp',
 
1489
  ),
 
1490
  ),
1491
 
1492
  ),
1513
  ),
1514
  ),
1515
  'tag_spec' => array(
 
 
 
 
 
 
1516
  'html_format' => array(
1517
  'amp',
 
1518
  ),
 
1519
  ),
1520
 
1521
  ),
1523
  'amp-selector' => array(
1524
  array(
1525
  'attr_spec_list' => array(
1526
+ '[selected]' => array(),
1527
  'disabled' => array(
1528
  'value' => '',
1529
  ),
1530
  'form' => array(),
1531
+ 'keyboard-select-mode' => array(
1532
+ 'value_regex_casei' => 'focus|none|select',
1533
+ ),
1534
  'media' => array(),
1535
  'multiple' => array(
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' => '',
1543
  ),
1544
  ),
1545
  'tag_spec' => array(
 
 
 
1546
  'disallowed_ancestor' => array(
1547
  'amp-selector',
 
1548
  ),
1549
  'html_format' => array(
1550
  'amp',
 
1551
  ),
 
1552
  ),
1553
 
1554
  ),
1565
  ),
1566
  ),
1567
  'tag_spec' => array(
 
 
 
 
 
 
1568
  'html_format' => array(
1569
  'amp',
1570
  ),
1571
  'mandatory_parent' => 'body',
 
 
1572
  ),
1573
 
1574
  ),
1607
  ),
1608
  ),
1609
  'tag_spec' => array(
 
 
 
1610
  'html_format' => array(
1611
  'amp',
1612
  'amp4ads',
1613
  ),
 
1614
  ),
1615
 
1616
  ),
1621
  'data-color' => array(
1622
  'value_regex_casei' => '([0-9a-f]{3}){1,2}',
1623
  ),
1624
+ 'data-playlistid' => array(
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]+',
1632
  ),
1633
  'data-visual' => array(
1639
  ),
1640
  ),
1641
  'tag_spec' => array(
 
 
 
 
 
 
1642
  'html_format' => array(
1643
  'amp',
 
1644
  ),
 
1645
  ),
1646
 
1647
  ),
1676
  ),
1677
  ),
1678
  'tag_spec' => array(
1679
+ 'html_format' => array(
1680
+ 'amp',
1681
  ),
1682
+ ),
1683
+
1684
+ ),
1685
+ ),
1686
+ 'amp-state' => array(
1687
+ array(
1688
+ 'attr_spec_list' => array(
1689
+ '[src]' => array(),
1690
+ 'credentials' => array(),
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
  ),
1718
  ),
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(
1748
+ 'mandatory' => true,
1749
+ 'value' => '',
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(
1762
+ array(
1763
+ 'attr_spec_list' => array(
1764
+ 'template' => array(
1765
+ 'mandatory' => true,
1766
+ 'value_regex' => '(fill|horizontal|vertical|thirds)',
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(
1779
+ array(
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(
1789
+ 'mandatory' => true,
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(
1802
+ array(
1803
+ 'attr_spec_list' => array(
1804
+ 'cutoff' => array(
1805
+ 'value_regex' => '\\d+',
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(),
1813
  'noloading' => array(
1814
  'value' => '',
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(
1827
+ array(
1828
+ 'attr_spec_list' => array(
1829
+ 'data-tweetid' => array(
1830
+ 'mandatory' => true,
1831
  ),
1832
+ 'media' => array(),
1833
+ 'noloading' => array(
1834
+ 'value' => '',
1835
+ ),
1836
+ ),
1837
+ 'tag_spec' => array(
1838
  'html_format' => array(
1839
  'amp',
 
1840
  ),
 
1841
  ),
1842
 
1843
  ),
1859
  'https',
1860
  ),
1861
  ),
1862
+ 'enctype' => array(
1863
+ 'value' => 'application/x-www-form-urlencoded',
1864
+ ),
1865
  'media' => array(),
1866
  'noloading' => array(
1867
  'value' => '',
1868
  ),
1869
  ),
1870
  'tag_spec' => array(
 
 
 
 
 
 
 
1871
  'html_format' => array(
1872
  'amp',
1873
  ),
 
1874
  ),
1875
 
1876
  ),
1878
  'amp-video' => array(
1879
  array(
1880
  'attr_spec_list' => array(
1881
+ '[album]' => array(),
1882
+ '[alt]' => array(),
1883
+ '[artist]' => array(),
1884
+ '[artwork]' => array(),
1885
+ '[attribution]' => array(),
1886
+ '[controls]' => array(),
1887
+ '[controlslist]' => array(),
1888
+ '[loop]' => array(),
1889
+ '[poster]' => array(),
1890
+ '[preload]' => array(),
1891
+ '[src]' => array(),
1892
+ '[title]' => array(),
1893
+ 'album' => array(),
1894
  'alt' => array(),
1895
+ 'artist' => array(),
1896
+ 'artwork' => array(),
1897
  'attribution' => array(),
1898
  'autoplay' => array(
1899
  'value' => '',
1901
  'controls' => array(
1902
  'value' => '',
1903
  ),
1904
+ 'controlslist' => array(),
1905
+ 'crossorigin' => array(),
1906
+ 'disableremoteplayback' => array(
1907
+ 'value' => '',
1908
+ ),
1909
  'loop' => array(
1910
  'value' => '',
1911
  ),
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(
1946
+ '[album]' => array(),
1947
+ '[alt]' => array(),
1948
+ '[artist]' => array(),
1949
+ '[artwork]' => array(),
1950
+ '[attribution]' => array(),
1951
+ '[controls]' => array(),
1952
+ '[controlslist]' => array(),
1953
+ '[loop]' => array(),
1954
+ '[poster]' => array(),
1955
+ '[preload]' => array(),
1956
+ '[src]' => array(),
1957
+ '[title]' => array(),
1958
+ 'album' => array(),
1959
+ 'alt' => array(),
1960
+ 'artist' => array(),
1961
+ 'artwork' => array(),
1962
+ 'attribution' => array(),
1963
+ 'autoplay' => array(
1964
+ 'value' => '',
1965
+ ),
1966
+ 'controls' => array(
1967
+ 'value' => '',
1968
+ ),
1969
+ 'controlslist' => array(),
1970
+ 'crossorigin' => array(),
1971
+ 'disableremoteplayback' => array(
1972
+ 'value' => '',
1973
+ ),
1974
+ 'loop' => array(
1975
+ 'value' => '',
1976
+ ),
1977
+ 'media' => array(),
1978
+ 'muted' => array(
1979
+ 'value' => '',
1980
+ ),
1981
+ 'noloading' => array(
1982
+ 'value' => '',
1983
+ ),
1984
+ 'placeholder' => array(),
1985
+ 'poster' => array(
1986
+ 'mandatory' => true,
1987
+ ),
1988
+ 'preload' => array(
1989
+ 'value_regex' => '(none|metadata|auto|)',
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
  ),
2021
  ),
2022
  ),
2023
  'tag_spec' => array(
 
 
 
 
 
 
2024
  'html_format' => array(
2025
  'amp',
 
2026
  ),
 
2027
  ),
2028
 
2029
  ),
2040
  ),
2041
  ),
2042
  'tag_spec' => array(
2043
+ 'html_format' => array(
2044
+ 'amp',
2045
  ),
2046
+ ),
2047
+
2048
+ ),
2049
+ ),
2050
+ 'amp-vk' => array(
2051
+ array(
2052
+ 'attr_spec_list' => array(
2053
+ 'data-embedtype' => array(
2054
+ 'mandatory' => true,
2055
  ),
2056
+ 'media' => array(),
2057
+ 'noloading' => array(
2058
+ 'value' => '',
2059
+ ),
2060
+ ),
2061
+ 'tag_spec' => array(
2062
  'html_format' => array(
2063
  'amp',
 
2064
  ),
 
2065
  ),
2066
 
2067
  ),
2068
  ),
2069
+ 'amp-web-push' => array(
2070
  array(
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(
2080
+ 'mandatory' => true,
2081
+ 'value_regex' => 'amp-web-push',
2082
  ),
2083
  'media' => array(),
2084
  'noloading' => array(
2085
  'value' => '',
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(
2112
+ array(
2113
+ 'attr_spec_list' => array(
2114
+ 'media' => array(),
2115
+ 'noloading' => array(
2116
+ 'value' => '',
2117
+ ),
2118
+ 'visibility' => array(
2119
+ 'mandatory' => true,
2120
+ 'value_regex' => '(blocked|subscribed|unsubscribed)',
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(
2133
+ array(
2134
+ 'attr_spec_list' => array(
2135
+ '[data-videoid]' => array(),
2136
+ 'autoplay' => array(),
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
  ),
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
  ),
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
  ),
2250
  'tag_spec' => array(
2251
  'mandatory' => true,
2252
  'mandatory_parent' => 'html',
2253
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec#required-markup',
2254
  'unique' => true,
2255
  ),
2256
 
2266
  'button' => array(
2267
  array(
2268
  'attr_spec_list' => array(
2269
+ '[disabled]' => array(),
2270
+ '[type]' => array(),
2271
+ '[value]' => array(),
2272
  'disabled' => array(
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(),
2280
  'type' => array(),
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' => '',
2293
  ),
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
  ),
2325
 
2326
  ),
2327
  ),
2328
+ 'circle' => array(
2329
+ array(
2330
+ 'attr_spec_list' => array(
2331
+ 'alignment-baseline' => array(),
2332
+ 'baseline-shift' => array(),
2333
+ 'clip' => array(),
2334
+ 'clip-path' => array(),
2335
+ 'clip-rule' => array(),
2336
+ 'color' => array(),
2337
+ 'color-interpolation' => array(),
2338
+ 'color-interpolation-filters' => array(),
2339
+ 'color-profile' => array(),
2340
+ 'color-rendering' => array(),
2341
+ 'cursor' => array(),
2342
+ 'cx' => array(),
2343
+ 'cy' => array(),
2344
+ 'direction' => array(),
2345
+ 'display' => array(),
2346
+ 'dominant-baseline' => array(),
2347
+ 'enable-background' => array(),
2348
+ 'externalresourcesrequired' => array(),
2349
+ 'fill' => array(),
2350
+ 'fill-opacity' => array(),
2351
+ 'fill-rule' => array(),
2352
+ 'filter' => array(),
2353
+ 'flood-color' => array(),
2354
+ 'flood-opacity' => array(),
2355
+ 'font-family' => array(),
2356
+ 'font-size' => array(),
2357
+ 'font-size-adjust' => array(),
2358
+ 'font-stretch' => array(),
2359
+ 'font-style' => array(),
2360
+ 'font-variant' => array(),
2361
+ 'font-weight' => array(),
2362
+ 'glyph-orientation-horizontal' => array(),
2363
+ 'glyph-orientation-vertical' => array(),
2364
+ 'image-rendering' => array(),
2365
+ 'kerning' => array(),
2366
+ 'letter-spacing' => array(),
2367
+ 'lighting-color' => array(),
2368
+ 'marker-end' => array(),
2369
+ 'marker-mid' => array(),
2370
+ 'marker-start' => array(),
2371
+ 'mask' => array(),
2372
+ 'opacity' => array(),
2373
+ 'overflow' => array(),
2374
+ 'pointer-events' => array(),
2375
+ 'r' => array(),
2376
+ 'requiredextensions' => array(),
2377
+ 'requiredfeatures' => array(),
2378
+ 'shape-rendering' => array(),
2379
+ 'sketch:type' => array(),
2380
+ 'stop-color' => array(),
2381
+ 'stop-opacity' => array(),
2382
+ 'stroke' => array(),
2383
+ 'stroke-dasharray' => array(),
2384
+ 'stroke-dashoffset' => array(),
2385
+ 'stroke-linecap' => array(),
2386
+ 'stroke-linejoin' => array(),
2387
+ 'stroke-miterlimit' => array(),
2388
+ 'stroke-opacity' => array(),
2389
+ 'stroke-width' => array(),
2390
+ 'style' => array(
2391
+ 'blacklisted_value_regex' => '!important',
2392
+ ),
2393
+ 'systemlanguage' => array(),
2394
+ 'text-anchor' => array(),
2395
+ 'text-decoration' => array(),
2396
+ 'text-rendering' => array(),
2397
+ 'transform' => array(),
2398
+ 'unicode-bidi' => array(),
2399
+ 'vector-effect' => array(),
2400
+ 'visibility' => array(),
2401
+ 'word-spacing' => array(),
2402
+ 'writing-mode' => array(),
2403
+ 'xml:lang' => array(),
2404
+ 'xml:space' => array(),
2405
+ 'xmlns' => array(),
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(
2427
+ array(
2428
+ 'attr_spec_list' => array(
2429
+ 'alignment-baseline' => array(),
2430
+ 'baseline-shift' => array(),
2431
+ 'clip' => array(),
2432
+ 'clip-path' => array(),
2433
+ 'clip-rule' => array(),
2434
+ 'clippathunits' => array(),
2435
+ 'color' => array(),
2436
+ 'color-interpolation' => array(),
2437
+ 'color-interpolation-filters' => array(),
2438
+ 'color-profile' => array(),
2439
+ 'color-rendering' => array(),
2440
+ 'cursor' => array(),
2441
+ 'direction' => array(),
2442
+ 'display' => array(),
2443
+ 'dominant-baseline' => array(),
2444
+ 'enable-background' => array(),
2445
+ 'externalresourcesrequired' => array(),
2446
+ 'fill' => array(),
2447
+ 'fill-opacity' => array(),
2448
+ 'fill-rule' => array(),
2449
+ 'filter' => array(),
2450
+ 'flood-color' => array(),
2451
+ 'flood-opacity' => array(),
2452
+ 'font-family' => array(),
2453
+ 'font-size' => array(),
2454
+ 'font-size-adjust' => array(),
2455
+ 'font-stretch' => array(),
2456
+ 'font-style' => array(),
2457
+ 'font-variant' => array(),
2458
+ 'font-weight' => array(),
2459
+ 'glyph-orientation-horizontal' => array(),
2460
+ 'glyph-orientation-vertical' => array(),
2461
+ 'image-rendering' => array(),
2462
+ 'kerning' => array(),
2463
+ 'letter-spacing' => array(),
2464
+ 'lighting-color' => array(),
2465
+ 'marker-end' => array(),
2466
+ 'marker-mid' => array(),
2467
+ 'marker-start' => array(),
2468
+ 'mask' => array(),
2469
+ 'opacity' => array(),
2470
+ 'overflow' => array(),
2471
+ 'pointer-events' => array(),
2472
+ 'requiredextensions' => array(),
2473
+ 'requiredfeatures' => array(),
2474
+ 'shape-rendering' => array(),
2475
+ 'stop-color' => array(),
2476
+ 'stop-opacity' => array(),
2477
+ 'stroke' => array(),
2478
+ 'stroke-dasharray' => array(),
2479
+ 'stroke-dashoffset' => array(),
2480
+ 'stroke-linecap' => array(),
2481
+ 'stroke-linejoin' => array(),
2482
+ 'stroke-miterlimit' => array(),
2483
+ 'stroke-opacity' => array(),
2484
+ 'stroke-width' => array(),
2485
+ 'style' => array(
2486
+ 'blacklisted_value_regex' => '!important',
2487
+ ),
2488
+ 'systemlanguage' => array(),
2489
+ 'text-anchor' => array(),
2490
+ 'text-decoration' => array(),
2491
+ 'text-rendering' => array(),
2492
+ 'transform' => array(),
2493
+ 'unicode-bidi' => array(),
2494
+ 'vector-effect' => array(),
2495
+ 'visibility' => array(),
2496
+ 'word-spacing' => array(),
2497
+ 'writing-mode' => array(),
2498
+ 'xml:lang' => array(),
2499
+ 'xml:space' => array(),
2500
+ 'xmlns' => array(),
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(
2522
+ array(
2523
+ 'attr_spec_list' => array(
2524
+ 'span' => array(),
2525
+ ),
2526
+ 'tag_spec' => array(),
2527
+
2528
+ ),
2529
+ ),
2530
+ 'colgroup' => array(
2531
+ array(
2532
+ 'attr_spec_list' => array(
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(
2547
+ array(
2548
+ 'attr_spec_list' => array(),
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(
2563
+ array(
2564
+ 'attr_spec_list' => array(
2565
+ 'alignment-baseline' => array(),
2566
+ 'baseline-shift' => array(),
2567
+ 'clip' => array(),
2568
+ 'clip-path' => array(),
2569
+ 'clip-rule' => array(),
2570
+ 'color' => array(),
2571
+ 'color-interpolation' => array(),
2572
+ 'color-interpolation-filters' => array(),
2573
+ 'color-profile' => array(),
2574
+ 'color-rendering' => array(),
2575
+ 'cursor' => array(),
2576
+ 'direction' => array(),
2577
+ 'display' => array(),
2578
+ 'dominant-baseline' => array(),
2579
+ 'enable-background' => array(),
2580
+ 'externalresourcesrequired' => array(),
2581
+ 'fill' => array(),
2582
+ 'fill-opacity' => array(),
2583
+ 'fill-rule' => array(),
2584
+ 'filter' => array(),
2585
+ 'flood-color' => array(),
2586
+ 'flood-opacity' => array(),
2587
+ 'font-family' => array(),
2588
+ 'font-size' => array(),
2589
+ 'font-size-adjust' => array(),
2590
+ 'font-stretch' => array(),
2591
+ 'font-style' => array(),
2592
+ 'font-variant' => array(),
2593
+ 'font-weight' => array(),
2594
+ 'glyph-orientation-horizontal' => array(),
2595
+ 'glyph-orientation-vertical' => array(),
2596
+ 'image-rendering' => array(),
2597
+ 'kerning' => array(),
2598
+ 'letter-spacing' => array(),
2599
+ 'lighting-color' => array(),
2600
+ 'marker-end' => array(),
2601
+ 'marker-mid' => array(),
2602
+ 'marker-start' => array(),
2603
+ 'mask' => array(),
2604
+ 'opacity' => array(),
2605
+ 'overflow' => array(),
2606
+ 'pointer-events' => array(),
2607
+ 'requiredextensions' => array(),
2608
+ 'requiredfeatures' => array(),
2609
+ 'shape-rendering' => array(),
2610
+ 'stop-color' => array(),
2611
+ 'stop-opacity' => array(),
2612
+ 'stroke' => array(),
2613
+ 'stroke-dasharray' => array(),
2614
+ 'stroke-dashoffset' => array(),
2615
+ 'stroke-linecap' => array(),
2616
+ 'stroke-linejoin' => array(),
2617
+ 'stroke-miterlimit' => array(),
2618
+ 'stroke-opacity' => array(),
2619
+ 'stroke-width' => array(),
2620
+ 'style' => array(
2621
+ 'blacklisted_value_regex' => '!important',
2622
+ ),
2623
+ 'systemlanguage' => array(),
2624
+ 'text-anchor' => array(),
2625
+ 'text-decoration' => array(),
2626
+ 'text-rendering' => array(),
2627
+ 'transform' => array(),
2628
+ 'unicode-bidi' => array(),
2629
+ 'vector-effect' => array(),
2630
+ 'visibility' => array(),
2631
+ 'word-spacing' => array(),
2632
+ 'writing-mode' => array(),
2633
+ 'xml:lang' => array(),
2634
+ 'xml:space' => array(),
2635
+ 'xmlns' => array(),
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(
2650
+ array(
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(
2668
+ array(
2669
+ 'attr_spec_list' => array(
2670
+ 'style' => array(
2671
+ 'blacklisted_value_regex' => '!important',
2672
+ ),
2673
+ 'xml:lang' => array(),
2674
+ 'xml:space' => array(),
2675
+ 'xmlns' => array(),
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(
2708
+ array(
2709
+ 'attr_spec_list' => array(
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(
2730
+ 'align' => array(),
2731
+ 'submit-success' => array(
2732
+ 'mandatory' => true,
2733
+ ),
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(
2743
+ 'align' => array(),
2744
+ 'submit-error' => array(
2745
+ 'mandatory' => true,
2746
+ ),
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(
2770
+ array(
2771
+ 'attr_spec_list' => array(
2772
+ 'alignment-baseline' => array(),
2773
+ 'baseline-shift' => array(),
2774
+ 'clip' => array(),
2775
+ 'clip-path' => array(),
2776
+ 'clip-rule' => array(),
2777
+ 'color' => array(),
2778
+ 'color-interpolation' => array(),
2779
+ 'color-interpolation-filters' => array(),
2780
+ 'color-profile' => array(),
2781
+ 'color-rendering' => array(),
2782
+ 'cursor' => array(),
2783
+ 'cx' => array(),
2784
+ 'cy' => array(),
2785
+ 'direction' => array(),
2786
+ 'display' => array(),
2787
+ 'dominant-baseline' => array(),
2788
+ 'enable-background' => array(),
2789
+ 'externalresourcesrequired' => array(),
2790
+ 'fill' => array(),
2791
+ 'fill-opacity' => array(),
2792
+ 'fill-rule' => array(),
2793
+ 'filter' => array(),
2794
+ 'flood-color' => array(),
2795
+ 'flood-opacity' => array(),
2796
+ 'font-family' => array(),
2797
+ 'font-size' => array(),
2798
+ 'font-size-adjust' => array(),
2799
+ 'font-stretch' => array(),
2800
+ 'font-style' => array(),
2801
+ 'font-variant' => array(),
2802
+ 'font-weight' => array(),
2803
+ 'glyph-orientation-horizontal' => array(),
2804
+ 'glyph-orientation-vertical' => array(),
2805
+ 'image-rendering' => array(),
2806
+ 'kerning' => array(),
2807
+ 'letter-spacing' => array(),
2808
+ 'lighting-color' => array(),
2809
+ 'marker-end' => array(),
2810
+ 'marker-mid' => array(),
2811
+ 'marker-start' => array(),
2812
+ 'mask' => array(),
2813
+ 'opacity' => array(),
2814
+ 'overflow' => array(),
2815
+ 'pointer-events' => array(),
2816
+ 'requiredextensions' => array(),
2817
+ 'requiredfeatures' => array(),
2818
+ 'rx' => array(),
2819
+ 'ry' => array(),
2820
+ 'shape-rendering' => array(),
2821
+ 'sketch:type' => array(),
2822
+ 'stop-color' => array(),
2823
+ 'stop-opacity' => array(),
2824
+ 'stroke' => array(),
2825
+ 'stroke-dasharray' => array(),
2826
+ 'stroke-dashoffset' => array(),
2827
+ 'stroke-linecap' => array(),
2828
+ 'stroke-linejoin' => array(),
2829
+ 'stroke-miterlimit' => array(),
2830
+ 'stroke-opacity' => array(),
2831
+ 'stroke-width' => array(),
2832
+ 'style' => array(
2833
+ 'blacklisted_value_regex' => '!important',
2834
+ ),
2835
+ 'systemlanguage' => array(),
2836
+ 'text-anchor' => array(),
2837
+ 'text-decoration' => array(),
2838
+ 'text-rendering' => array(),
2839
+ 'transform' => array(),
2840
+ 'unicode-bidi' => array(),
2841
+ 'vector-effect' => array(),
2842
+ 'visibility' => array(),
2843
+ 'word-spacing' => array(),
2844
+ 'writing-mode' => array(),
2845
+ 'xml:lang' => array(),
2846
+ 'xml:space' => array(),
2847
+ 'xmlns' => array(),
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(
2869
+ array(
2870
+ 'attr_spec_list' => array(
2871
+ 'alignment-baseline' => array(),
2872
+ 'baseline-shift' => array(),
2873
+ 'clip' => array(),
2874
+ 'clip-path' => array(),
2875
+ 'clip-rule' => array(),
2876
+ 'color' => array(),
2877
+ 'color-interpolation' => array(),
2878
+ 'color-interpolation-filters' => array(),
2879
+ 'color-profile' => array(),
2880
+ 'color-rendering' => array(),
2881
+ 'cursor' => array(),
2882
+ 'direction' => array(),
2883
+ 'display' => array(),
2884
+ 'dominant-baseline' => array(),
2885
+ 'enable-background' => array(),
2886
+ 'fill' => array(),
2887
+ 'fill-opacity' => array(),
2888
+ 'fill-rule' => array(),
2889
+ 'filter' => array(),
2890
+ 'flood-color' => array(),
2891
+ 'flood-opacity' => array(),
2892
+ 'font-family' => array(),
2893
+ 'font-size' => array(),
2894
+ 'font-size-adjust' => array(),
2895
+ 'font-stretch' => array(),
2896
+ 'font-style' => array(),
2897
+ 'font-variant' => array(),
2898
+ 'font-weight' => array(),
2899
+ 'glyph-orientation-horizontal' => array(),
2900
+ 'glyph-orientation-vertical' => array(),
2901
+ 'height' => array(),
2902
+ 'image-rendering' => array(),
2903
+ 'in' => array(),
2904
+ 'kerning' => array(),
2905
+ 'letter-spacing' => array(),
2906
+ 'lighting-color' => array(),
2907
+ 'marker-end' => array(),
2908
+ 'marker-mid' => array(),
2909
+ 'marker-start' => array(),
2910
+ 'mask' => array(),
2911
+ 'opacity' => array(),
2912
+ 'overflow' => array(),
2913
+ 'pointer-events' => array(),
2914
+ 'result' => array(),
2915
+ 'shape-rendering' => array(),
2916
+ 'stop-color' => array(),
2917
+ 'stop-opacity' => array(),
2918
+ 'stroke' => array(),
2919
+ 'stroke-dasharray' => array(),
2920
+ 'stroke-dashoffset' => array(),
2921
+ 'stroke-linecap' => array(),
2922
+ 'stroke-linejoin' => array(),
2923
+ 'stroke-miterlimit' => array(),
2924
+ 'stroke-opacity' => array(),
2925
+ 'stroke-width' => array(),
2926
+ 'style' => array(
2927
+ 'blacklisted_value_regex' => '!important',
2928
+ ),
2929
+ 'text-anchor' => array(),
2930
+ 'text-decoration' => array(),
2931
+ 'text-rendering' => array(),
2932
+ 'type' => array(),
2933
+ 'unicode-bidi' => array(),
2934
+ 'values' => array(),
2935
+ 'vector-effect' => array(),
2936
+ 'visibility' => array(),
2937
+ 'width' => array(),
2938
+ 'word-spacing' => array(),
2939
+ 'writing-mode' => array(),
2940
+ 'x' => array(),
2941
+ 'xml:lang' => array(),
2942
+ 'xml:space' => array(),
2943
+ 'xmlns' => array(),
2944
+ 'xmlns:xlink' => array(),
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(
2959
+ array(
2960
+ 'attr_spec_list' => array(
2961
+ 'alignment-baseline' => array(),
2962
+ 'baseline-shift' => array(),
2963
+ 'clip' => array(),
2964
+ 'clip-path' => array(),
2965
+ 'clip-rule' => array(),
2966
+ 'color' => array(),
2967
+ 'color-interpolation' => array(),
2968
+ 'color-interpolation-filters' => array(),
2969
+ 'color-profile' => array(),
2970
+ 'color-rendering' => array(),
2971
+ 'cursor' => array(),
2972
+ 'direction' => array(),
2973
+ 'display' => array(),
2974
+ 'dominant-baseline' => array(),
2975
+ 'enable-background' => array(),
2976
+ 'fill' => array(),
2977
+ 'fill-opacity' => array(),
2978
+ 'fill-rule' => array(),
2979
+ 'filter' => array(),
2980
+ 'flood-color' => array(),
2981
+ 'flood-opacity' => array(),
2982
+ 'font-family' => array(),
2983
+ 'font-size' => array(),
2984
+ 'font-size-adjust' => array(),
2985
+ 'font-stretch' => array(),
2986
+ 'font-style' => array(),
2987
+ 'font-variant' => array(),
2988
+ 'font-weight' => array(),
2989
+ 'glyph-orientation-horizontal' => array(),
2990
+ 'glyph-orientation-vertical' => array(),
2991
+ 'height' => array(),
2992
+ 'image-rendering' => array(),
2993
+ 'in' => array(),
2994
+ 'in2' => array(),
2995
+ 'k1' => array(),
2996
+ 'k2' => array(),
2997
+ 'k3' => array(),
2998
+ 'k4' => array(),
2999
+ 'kerning' => array(),
3000
+ 'letter-spacing' => array(),
3001
+ 'lighting-color' => array(),
3002
+ 'marker-end' => array(),
3003
+ 'marker-mid' => array(),
3004
+ 'marker-start' => array(),
3005
+ 'mask' => array(),
3006
+ 'opacity' => array(),
3007
+ 'operator' => array(),
3008
+ 'overflow' => array(),
3009
+ 'pointer-events' => array(),
3010
+ 'result' => array(),
3011
+ 'shape-rendering' => array(),
3012
+ 'stop-color' => array(),
3013
+ 'stop-opacity' => array(),
3014
+ 'stroke' => array(),
3015
+ 'stroke-dasharray' => array(),
3016
+ 'stroke-dashoffset' => array(),
3017
+ 'stroke-linecap' => array(),
3018
+ 'stroke-linejoin' => array(),
3019
+ 'stroke-miterlimit' => array(),
3020
+ 'stroke-opacity' => array(),
3021
+ 'stroke-width' => array(),
3022
+ 'style' => array(
3023
+ 'blacklisted_value_regex' => '!important',
3024
+ ),
3025
+ 'text-anchor' => array(),
3026
+ 'text-decoration' => array(),
3027
+ 'text-rendering' => array(),
3028
+ 'unicode-bidi' => array(),
3029
+ 'vector-effect' => array(),
3030
+ 'visibility' => array(),
3031
+ 'width' => array(),
3032
+ 'word-spacing' => array(),
3033
+ 'writing-mode' => array(),
3034
+ 'x' => array(),
3035
+ 'xml:lang' => array(),
3036
+ 'xml:space' => array(),
3037
+ 'xmlns' => array(),
3038
+ 'xmlns:xlink' => array(),
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(
3053
  array(
3054
  'attr_spec_list' => array(
3055
  'alignment-baseline' => array(),
3056
  'baseline-shift' => array(),
 
3057
  'clip' => array(),
3058
  'clip-path' => array(),
3059
  'clip-rule' => array(),
3063
  'color-profile' => array(),
3064
  'color-rendering' => array(),
3065
  'cursor' => array(),
 
 
3066
  'direction' => array(),
3067
  'display' => array(),
3068
  'dominant-baseline' => array(),
3069
  'enable-background' => array(),
 
3070
  'fill' => array(),
3071
  'fill-opacity' => array(),
3072
  'fill-rule' => array(),
3082
  'font-weight' => array(),
3083
  'glyph-orientation-horizontal' => array(),
3084
  'glyph-orientation-vertical' => array(),
3085
+ 'height' => array(),
3086
  'image-rendering' => array(),
3087
  'kerning' => array(),
3088
  'letter-spacing' => array(),
3094
  'opacity' => array(),
3095
  'overflow' => array(),
3096
  'pointer-events' => array(),
3097
+ 'result' => array(),
 
 
3098
  'shape-rendering' => array(),
 
3099
  'stop-color' => array(),
3100
  'stop-opacity' => array(),
3101
  'stroke' => array(),
3106
  'stroke-miterlimit' => array(),
3107
  'stroke-opacity' => array(),
3108
  'stroke-width' => array(),
3109
+ 'style' => array(
3110
+ 'blacklisted_value_regex' => '!important',
3111
+ ),
3112
  'text-anchor' => array(),
3113
  'text-decoration' => array(),
3114
  'text-rendering' => array(),
 
3115
  'unicode-bidi' => array(),
3116
+ 'vector-effect' => array(),
3117
  'visibility' => array(),
3118
+ 'width' => array(),
3119
  'word-spacing' => array(),
3120
  'writing-mode' => array(),
3121
+ 'x' => array(),
3122
  'xml:lang' => array(),
3123
  'xml:space' => array(),
3124
  'xmlns' => array(),
3125
  'xmlns:xlink' => array(),
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(
 
 
 
 
 
 
 
3140
  array(
3141
  'attr_spec_list' => array(
3142
  'alignment-baseline' => array(),
3143
  'baseline-shift' => array(),
 
3144
  'clip' => array(),
3145
  'clip-path' => array(),
3146
  'clip-rule' => array(),
 
3147
  'color' => array(),
3148
  'color-interpolation' => array(),
3149
  'color-interpolation-filters' => array(),
3153
  'direction' => array(),
3154
  'display' => array(),
3155
  'dominant-baseline' => array(),
3156
+ 'edgemode' => array(),
3157
  'enable-background' => array(),
 
3158
  'fill' => array(),
3159
  'fill-opacity' => array(),
3160
  'fill-rule' => array(),
3170
  'font-weight' => array(),
3171
  'glyph-orientation-horizontal' => array(),
3172
  'glyph-orientation-vertical' => array(),
3173
+ 'height' => array(),
3174
  'image-rendering' => array(),
3175
+ 'in' => array(),
3176
  'kerning' => array(),
3177
  'letter-spacing' => array(),
3178
  'lighting-color' => array(),
3183
  'opacity' => array(),
3184
  'overflow' => array(),
3185
  'pointer-events' => array(),
3186
+ 'result' => array(),
 
3187
  'shape-rendering' => array(),
3188
+ 'stddeviation' => array(),
3189
  'stop-color' => array(),
3190
  'stop-opacity' => array(),
3191
  'stroke' => array(),
3196
  'stroke-miterlimit' => array(),
3197
  'stroke-opacity' => array(),
3198
  'stroke-width' => array(),
3199
+ 'style' => array(
3200
+ 'blacklisted_value_regex' => '!important',
3201
+ ),
3202
  'text-anchor' => array(),
3203
  'text-decoration' => array(),
3204
  'text-rendering' => array(),
 
3205
  'unicode-bidi' => array(),
3206
+ 'vector-effect' => array(),
3207
  'visibility' => array(),
3208
+ 'width' => array(),
3209
  'word-spacing' => array(),
3210
  'writing-mode' => array(),
3211
+ 'x' => array(),
3212
  'xml:lang' => array(),
3213
  'xml:space' => array(),
3214
  'xmlns' => array(),
3215
  'xmlns:xlink' => array(),
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(
 
 
 
 
 
 
 
3230
  array(
3231
  'attr_spec_list' => array(
3232
  'alignment-baseline' => array(),
3233
  'baseline-shift' => array(),
 
3234
  'clip' => array(),
3235
  'clip-path' => array(),
3236
  'clip-rule' => array(),
3244
  'display' => array(),
3245
  'dominant-baseline' => array(),
3246
  'enable-background' => array(),
 
3247
  'fill' => array(),
3248
  'fill-opacity' => array(),
3249
  'fill-rule' => array(),
3259
  'font-weight' => array(),
3260
  'glyph-orientation-horizontal' => array(),
3261
  'glyph-orientation-vertical' => array(),
3262
+ 'height' => array(),
3263
  'image-rendering' => array(),
3264
  'kerning' => array(),
3265
  'letter-spacing' => array(),
3271
  'opacity' => array(),
3272
  'overflow' => array(),
3273
  'pointer-events' => array(),
3274
+ 'result' => array(),
 
3275
  'shape-rendering' => array(),
3276
  'stop-color' => array(),
3277
  'stop-opacity' => array(),
3283
  'stroke-miterlimit' => array(),
3284
  'stroke-opacity' => array(),
3285
  'stroke-width' => array(),
3286
+ 'style' => array(
3287
+ 'blacklisted_value_regex' => '!important',
3288
+ ),
3289
  'text-anchor' => array(),
3290
  'text-decoration' => array(),
3291
  'text-rendering' => array(),
 
3292
  'unicode-bidi' => array(),
3293
+ 'vector-effect' => array(),
3294
  'visibility' => array(),
3295
+ 'width' => array(),
3296
  'word-spacing' => array(),
3297
  'writing-mode' => array(),
3298
+ 'x' => array(),
3299
  'xml:lang' => array(),
3300
  'xml:space' => array(),
3301
  'xmlns' => array(),
3302
  'xmlns:xlink' => array(),
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(
3317
  array(
3318
  'attr_spec_list' => array(
3319
+ 'in' => array(),
3320
+ 'style' => array(
3321
+ 'blacklisted_value_regex' => '!important',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3322
  ),
 
 
 
 
 
 
 
 
 
 
 
3323
  'xml:lang' => array(),
3324
  'xml:space' => array(),
3325
  'xmlns' => array(),
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(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3340
  array(
3341
  'attr_spec_list' => array(
3342
  'alignment-baseline' => array(),
3343
  'baseline-shift' => array(),
 
3344
  'clip' => array(),
3345
  'clip-path' => array(),
3346
  'clip-rule' => array(),
3350
  'color-profile' => array(),
3351
  'color-rendering' => array(),
3352
  'cursor' => array(),
 
 
3353
  'direction' => array(),
3354
  'display' => array(),
3355
  'dominant-baseline' => array(),
3356
+ 'dx' => array(),
3357
+ 'dy' => array(),
3358
  'enable-background' => array(),
 
3359
  'fill' => array(),
3360
  'fill-opacity' => array(),
3361
  'fill-rule' => array(),
3371
  'font-weight' => array(),
3372
  'glyph-orientation-horizontal' => array(),
3373
  'glyph-orientation-vertical' => array(),
3374
+ 'height' => array(),
3375
  'image-rendering' => array(),
3376
+ 'in' => array(),
3377
  'kerning' => array(),
3378
  'letter-spacing' => array(),
3379
  'lighting-color' => array(),
3384
  'opacity' => array(),
3385
  'overflow' => array(),
3386
  'pointer-events' => array(),
3387
+ 'result' => array(),
 
 
 
3388
  'shape-rendering' => array(),
 
3389
  'stop-color' => array(),
3390
  'stop-opacity' => array(),
3391
  'stroke' => array(),
3396
  'stroke-miterlimit' => array(),
3397
  'stroke-opacity' => array(),
3398
  'stroke-width' => array(),
3399
+ 'style' => array(
3400
+ 'blacklisted_value_regex' => '!important',
3401
+ ),
3402
  'text-anchor' => array(),
3403
  'text-decoration' => array(),
3404
  'text-rendering' => array(),
 
3405
  'unicode-bidi' => array(),
3406
+ 'vector-effect' => array(),
3407
  'visibility' => array(),
3408
+ 'width' => array(),
3409
  'word-spacing' => array(),
3410
  'writing-mode' => array(),
3411
+ 'x' => array(),
3412
  'xml:lang' => array(),
3413
  'xml:space' => array(),
3414
  'xmlns' => array(),
3415
  'xmlns:xlink' => array(),
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(
3430
  array(
3431
  'attr_spec_list' => array(
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
 
3458
  'attr_spec_list' => array(
3459
  'alignment-baseline' => array(),
3460
  'baseline-shift' => array(),
 
3461
  'clip' => array(),
3462
  'clip-path' => array(),
3463
  'clip-rule' => array(),
3513
  'stroke-miterlimit' => array(),
3514
  'stroke-opacity' => array(),
3515
  'stroke-width' => array(),
3516
+ 'style' => array(
3517
+ 'blacklisted_value_regex' => '!important',
3518
+ ),
3519
  'text-anchor' => array(),
3520
  'text-decoration' => array(),
3521
  'text-rendering' => array(),
3522
  'unicode-bidi' => array(),
3523
+ 'vector-effect' => array(),
3524
  'visibility' => array(),
3525
  'width' => array(),
3526
  'word-spacing' => array(),
3529
  'xlink:actuate' => array(),
3530
  'xlink:arcrole' => array(),
3531
  'xlink:href' => array(
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(),
3543
  'xlink:show' => array(),
3544
  'xlink:title' => array(),
3545
  'xlink:type' => array(),
 
3546
  'xml:lang' => array(),
3547
  'xml:space' => array(),
3548
  'xmlns' => array(),
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
  ),
3572
  'attr_spec_list' => array(
3573
  'alignment-baseline' => array(),
3574
  'baseline-shift' => array(),
 
3575
  'clip' => array(),
3576
  'clip-path' => array(),
3577
  'clip-rule' => array(),
3626
  'stroke-miterlimit' => array(),
3627
  'stroke-opacity' => array(),
3628
  'stroke-width' => array(),
3629
+ 'style' => array(
3630
+ 'blacklisted_value_regex' => '!important',
3631
+ ),
3632
  'systemlanguage' => array(),
3633
  'text-anchor' => array(),
3634
  'text-decoration' => array(),
3635
  'text-rendering' => array(),
3636
  'transform' => array(),
3637
  'unicode-bidi' => array(),
3638
+ 'vector-effect' => array(),
3639
  'visibility' => array(),
3640
  'width' => array(),
3641
  'word-spacing' => array(),
3642
  'writing-mode' => array(),
3643
  'x' => array(),
 
3644
  'xml:lang' => array(),
3645
  'xml:space' => array(),
3646
  'xmlns' => array(),
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
  ),
3670
  'allowed_protocol' => array(
3671
  'https',
3672
  ),
 
 
 
3673
  ),
3674
  'action-xhr' => array(
3675
  'blacklisted_value_regex' => '__amp_source_origin',
3677
  'allowed_protocol' => array(
3678
  'https',
3679
  ),
 
 
 
3680
  ),
3681
  'autocomplete' => array(),
3682
  'custom-validation-reporting' => array(
3683
+ 'value_regex' => '(show-first-on-submit|show-all-on-submit|as-you-go|interact-and-submit)',
3684
  ),
3685
  'enctype' => array(),
3686
  'method' => array(
3692
  'mandatory' => true,
3693
  'value_regex_casei' => '(_blank|_top)',
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
  ),
3723
  'allowed_protocol' => array(
3724
  'https',
3725
  ),
 
 
 
3726
  ),
3727
  'autocomplete' => array(),
3728
  'custom-validation-reporting' => array(
3730
  ),
3731
  'enctype' => array(),
3732
  'method' => array(
 
3733
  'mandatory' => true,
3734
  'value_casei' => 'post',
3735
  ),
3739
  'mandatory' => true,
3740
  'value_regex_casei' => '(_blank|_top)',
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
  ),
3765
  'attr_spec_list' => array(
3766
  'alignment-baseline' => array(),
3767
  'baseline-shift' => array(),
 
3768
  'clip' => array(),
3769
  'clip-path' => array(),
3770
  'clip-rule' => array(),
3818
  'stroke-miterlimit' => array(),
3819
  'stroke-opacity' => array(),
3820
  'stroke-width' => array(),
3821
+ 'style' => array(
3822
+ 'blacklisted_value_regex' => '!important',
3823
+ ),
3824
  'systemlanguage' => array(),
3825
  'text-anchor' => array(),
3826
  'text-decoration' => array(),
3827
  'text-rendering' => array(),
3828
  'transform' => array(),
3829
  'unicode-bidi' => array(),
3830
+ 'vector-effect' => array(),
3831
  'visibility' => array(),
3832
  'word-spacing' => array(),
3833
  'writing-mode' => array(),
 
3834
  'xml:lang' => array(),
3835
  'xml:space' => array(),
3836
  'xmlns' => array(),
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
  ),
3853
  'alignment-baseline' => array(),
3854
  'arabic-form' => array(),
3855
  'baseline-shift' => array(),
 
3856
  'clip' => array(),
3857
  'clip-path' => array(),
3858
  'clip-rule' => array(),
3907
  'stroke-miterlimit' => array(),
3908
  'stroke-opacity' => array(),
3909
  'stroke-width' => array(),
3910
+ 'style' => array(
3911
+ 'blacklisted_value_regex' => '!important',
3912
+ ),
3913
  'text-anchor' => array(),
3914
  'text-decoration' => array(),
3915
  'text-rendering' => array(),
3916
  'unicode' => array(),
3917
  'unicode-bidi' => array(),
3918
+ 'vector-effect' => array(),
3919
  'vert-adv-y' => array(),
3920
  'vert-origin-x' => array(),
3921
  'vert-origin-y' => array(),
3922
  'visibility' => array(),
3923
  'word-spacing' => array(),
3924
  'writing-mode' => array(),
 
3925
  'xml:lang' => array(),
3926
  'xml:space' => array(),
3927
  'xmlns' => array(),
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
  ),
3943
  'attr_spec_list' => array(
3944
  'alignment-baseline' => array(),
3945
  'baseline-shift' => array(),
 
3946
  'clip' => array(),
3947
  'clip-path' => array(),
3948
  'clip-rule' => array(),
3997
  'stroke-miterlimit' => array(),
3998
  'stroke-opacity' => array(),
3999
  'stroke-width' => array(),
4000
+ 'style' => array(
4001
+ 'blacklisted_value_regex' => '!important',
4002
+ ),
4003
  'text-anchor' => array(),
4004
  'text-decoration' => array(),
4005
  'text-rendering' => array(),
4006
  'unicode-bidi' => array(),
4007
+ 'vector-effect' => array(),
4008
  'visibility' => array(),
4009
  'word-spacing' => array(),
4010
  'writing-mode' => array(),
4012
  'xlink:actuate' => array(),
4013
  'xlink:arcrole' => array(),
4014
  'xlink:href' => array(
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(),
4026
  'xlink:show' => array(),
4027
  'xlink:title' => array(),
4028
  'xlink:type' => array(),
 
4029
  'xml:lang' => array(),
4030
  'xml:space' => array(),
4031
  'xmlns' => array(),
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
  ),
4121
  'g1' => array(),
4122
  'g2' => array(),
4123
  'k' => array(),
4124
+ 'style' => array(
4125
+ 'blacklisted_value_regex' => '!important',
4126
+ ),
4127
  'u1' => array(),
4128
  'u2' => array(),
 
4129
  'xml:lang' => array(),
4130
  'xml:space' => array(),
4131
  'xmlns' => array(),
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
  ),
4156
 
4157
  ),
4158
  ),
4159
+ 'iframe' => array(
4160
+ array(
4161
+ 'attr_spec_list' => array(
4162
+ 'frameborder' => array(
4163
+ 'value_regex' => '0|1',
4164
+ ),
4165
+ 'height' => array(),
4166
+ 'name' => array(),
4167
+ 'referrerpolicy' => array(),
4168
+ 'resizable' => array(
4169
+ 'value' => '',
4170
+ ),
4171
+ 'sandbox' => array(),
4172
+ 'scrolling' => array(
4173
+ 'value_regex' => 'auto|yes|no',
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(
4198
  array(
4199
  'attr_spec_list' => array(
4200
  'alignment-baseline' => array(),
4201
  'baseline-shift' => array(),
 
4202
  'clip' => array(),
4203
  'clip-path' => array(),
4204
  'clip-rule' => array(),
4254
  'stroke-miterlimit' => array(),
4255
  'stroke-opacity' => array(),
4256
  'stroke-width' => array(),
4257
+ 'style' => array(
4258
+ 'blacklisted_value_regex' => '!important',
4259
+ ),
4260
  'systemlanguage' => array(),
4261
  'text-anchor' => array(),
4262
  'text-decoration' => array(),
4263
  'text-rendering' => array(),
4264
  'transform' => array(),
4265
  'unicode-bidi' => array(),
4266
+ 'vector-effect' => array(),
4267
  'visibility' => array(),
4268
  'width' => array(),
4269
  'word-spacing' => array(),
4272
  'xlink:actuate' => array(),
4273
  'xlink:arcrole' => array(),
4274
  'xlink:href' => array(
4275
+ 'alternative_names' => array(
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(),
4288
  'xlink:show' => array(),
4289
  'xlink:title' => array(),
4290
  'xlink:type' => array(),
 
4291
  'xml:lang' => array(),
4292
  'xml:space' => array(),
4293
  'xmlns' => array(),
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
  ),
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
  ),
4348
  'input' => array(
4349
  array(
4350
  'attr_spec_list' => array(
4351
+ '[accept]' => array(),
4352
+ '[accesskey]' => array(),
4353
+ '[autocomplete]' => array(),
4354
+ '[checked]' => array(),
4355
+ '[disabled]' => array(),
4356
+ '[height]' => array(),
4357
+ '[inputmode]' => array(),
4358
+ '[max]' => array(),
4359
+ '[maxlength]' => array(),
4360
+ '[min]' => array(),
4361
+ '[minlength]' => array(),
4362
+ '[multiple]' => array(),
4363
+ '[pattern]' => array(),
4364
+ '[placeholder]' => array(),
4365
+ '[readonly]' => array(),
4366
+ '[required]' => array(),
4367
+ '[selectiondirection]' => array(),
4368
+ '[size]' => array(),
4369
+ '[spellcheck]' => array(),
4370
+ '[step]' => array(),
4371
+ '[type]' => array(),
4372
+ '[value]' => array(),
4373
+ '[width]' => array(),
4374
  'accept' => array(),
4375
  'accesskey' => array(),
4376
  'autocomplete' => array(),
4377
  'autofocus' => array(),
4378
  'checked' => array(),
 
4379
  'disabled' => array(),
4380
  'height' => array(),
4381
  'inputmode' => array(),
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(),
4404
  'width' => array(),
4405
  ),
4406
  'tag_spec' => array(
4407
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4408
  ),
4409
 
4410
  ),
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(),
4440
  'for' => array(),
4441
  ),
4442
  'tag_spec' => array(
4443
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
4444
  ),
4445
 
4446
  ),
4468
  'attr_spec_list' => array(
4469
  'alignment-baseline' => array(),
4470
  'baseline-shift' => array(),
 
4471
  'clip' => array(),
4472
  'clip-path' => array(),
4473
  'clip-rule' => array(),
4522
  'stroke-miterlimit' => array(),
4523
  'stroke-opacity' => array(),
4524
  'stroke-width' => array(),
4525
+ 'style' => array(
4526
+ 'blacklisted_value_regex' => '!important',
4527
+ ),
4528
  'systemlanguage' => array(),
4529
  'text-anchor' => array(),
4530
  'text-decoration' => array(),
4531
  'text-rendering' => array(),
4532
  'transform' => array(),
4533
  'unicode-bidi' => array(),
4534
+ 'vector-effect' => array(),
4535
  'visibility' => array(),
4536
  'word-spacing' => array(),
4537
  'writing-mode' => array(),
4538
  'x1' => array(),
4539
  'x2' => array(),
 
4540
  'xml:lang' => array(),
4541
  'xml:space' => array(),
4542
  'xmlns' => array(),
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
  ),
4560
  'attr_spec_list' => array(
4561
  'alignment-baseline' => array(),
4562
  'baseline-shift' => array(),
 
4563
  'clip' => array(),
4564
  'clip-path' => array(),
4565
  'clip-rule' => array(),
4614
  'stroke-miterlimit' => array(),
4615
  'stroke-opacity' => array(),
4616
  'stroke-width' => array(),
4617
+ 'style' => array(
4618
+ 'blacklisted_value_regex' => '!important',
4619
+ ),
4620
  'text-anchor' => array(),
4621
  'text-decoration' => array(),
4622
  'text-rendering' => array(),
4623
  'unicode-bidi' => array(),
4624
+ 'vector-effect' => array(),
4625
  'visibility' => array(),
4626
  'word-spacing' => array(),
4627
  'writing-mode' => array(),
4630
  'xlink:actuate' => array(),
4631
  'xlink:arcrole' => array(),
4632
  'xlink:href' => array(
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(),
4644
  'xlink:show' => array(),
4645
  'xlink:title' => array(),
4646
  'xlink:type' => array(),
 
4647
  'xml:lang' => array(),
4648
  'xml:space' => array(),
4649
  'xmlns' => array(),
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
  ),
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
  ),
4704
  ),
4705
  'hreflang' => array(),
4706
  'itemprop' => array(
 
4707
  'mandatory' => true,
4708
  'value_casei' => 'sameas',
4709
  ),
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
  ),
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(
4755
+ 'charset' => array(
4756
+ 'value_casei' => 'utf-8',
4757
+ ),
4758
+ 'color' => array(),
4759
+ 'href' => array(
4760
+ 'mandatory' => true,
4761
+ ),
4762
+ 'hreflang' => array(),
4763
+ 'media' => array(),
4764
+ 'property' => array(
4765
+ 'mandatory' => true,
4766
+ ),
4767
+ 'sizes' => array(),
4768
+ 'target' => array(),
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
  ),
4809
  'attr_spec_list' => array(
4810
  'alignment-baseline' => array(),
4811
  'baseline-shift' => array(),
 
4812
  'clip' => array(),
4813
  'clip-path' => array(),
4814
  'clip-rule' => array(),
4867
  'stroke-miterlimit' => array(),
4868
  'stroke-opacity' => array(),
4869
  'stroke-width' => array(),
4870
+ 'style' => array(
4871
+ 'blacklisted_value_regex' => '!important',
4872
+ ),
4873
  'text-anchor' => array(),
4874
  'text-decoration' => array(),
4875
  'text-rendering' => array(),
4876
  'transform' => array(),
4877
  'unicode-bidi' => array(),
4878
+ 'vector-effect' => array(),
4879
  'viewbox' => array(),
4880
  'visibility' => array(),
4881
  'word-spacing' => array(),
4882
  'writing-mode' => array(),
 
4883
  'xml:lang' => array(),
4884
  'xml:space' => array(),
4885
  'xmlns' => array(),
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
  ),
4901
  'attr_spec_list' => array(
4902
  'alignment-baseline' => array(),
4903
  'baseline-shift' => array(),
 
4904
  'clip' => array(),
4905
  'clip-path' => array(),
4906
  'clip-rule' => array(),
4957
  'stroke-miterlimit' => array(),
4958
  'stroke-opacity' => array(),
4959
  'stroke-width' => array(),
4960
+ 'style' => array(
4961
+ 'blacklisted_value_regex' => '!important',
4962
+ ),
4963
  'systemlanguage' => array(),
4964
  'text-anchor' => array(),
4965
  'text-decoration' => array(),
4966
  'text-rendering' => array(),
4967
  'unicode-bidi' => array(),
4968
+ 'vector-effect' => array(),
4969
  'visibility' => array(),
4970
  'width' => array(),
4971
  'word-spacing' => array(),
4972
  'writing-mode' => array(),
4973
  'x' => array(),
 
4974
  'xml:lang' => array(),
4975
  'xml:space' => array(),
4976
  'xmlns' => array(),
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
  ),
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
  ),
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
 
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
  ),
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
  ),
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
  ),
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
  ),
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
  ),
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(
5168
+ 'content' => array(
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
  ),
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(
5210
+ array(
5211
+ 'attr_spec_list' => array(
5212
+ 'style' => array(
5213
+ 'blacklisted_value_regex' => '!important',
5214
+ ),
5215
+ 'xml:lang' => array(),
5216
+ 'xml:space' => array(),
5217
+ 'xmlns' => array(),
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(
5232
+ array(
5233
+ 'attr_spec_list' => array(
5234
+ 'high' => array(),
5235
+ 'low' => array(),
5236
+ 'max' => array(),
5237
+ 'min' => array(),
5238
+ 'optimum' => array(),
5239
+ 'value' => array(),
5240
  ),
5241
+ 'tag_spec' => array(),
5242
 
5243
  ),
5244
  ),
5259
  'tag_spec' => array(),
5260
 
5261
  ),
5262
+ array(
5263
+ 'attr_spec_list' => array(
5264
+ 'toolbar' => array(
5265
+ 'mandatory' => true,
5266
+ ),
5267
+ 'toolbar-target' => array(
5268
+ 'mandatory' => true,
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(
5346
  'optgroup' => array(
5347
  array(
5348
  'attr_spec_list' => array(
5349
+ '[disabled]' => array(),
5350
+ '[label]' => array(),
5351
  'disabled' => array(),
5352
  'label' => array(),
5353
  ),
5354
  'tag_spec' => array(
 
 
 
5355
  'mandatory_parent' => 'select',
5356
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
5357
  ),
5358
 
5359
  ),
5361
  'option' => array(
5362
  array(
5363
  'attr_spec_list' => array(
5364
+ '[disabled]' => array(),
5365
+ '[label]' => array(),
5366
+ '[selected]' => array(),
5367
+ '[value]' => array(),
5368
  'disabled' => array(),
5369
  'label' => array(),
5370
  'selected' => array(),
5371
  'value' => array(),
5372
  ),
5373
  'tag_spec' => array(
5374
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
5375
+ ),
5376
+
5377
+ ),
5378
+ ),
5379
+ 'output' => array(
5380
+ array(
5381
+ 'attr_spec_list' => array(
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
  ),
5403
  'attr_spec_list' => array(
5404
  'alignment-baseline' => array(),
5405
  'baseline-shift' => array(),
 
5406
  'clip' => array(),
5407
  'clip-path' => array(),
5408
  'clip-rule' => array(),
5459
  'stroke-miterlimit' => array(),
5460
  'stroke-opacity' => array(),
5461
  'stroke-width' => array(),
5462
+ 'style' => array(
5463
+ 'blacklisted_value_regex' => '!important',
5464
+ ),
5465
  'systemlanguage' => array(),
5466
  'text-anchor' => array(),
5467
  'text-decoration' => array(),
5468
  'text-rendering' => array(),
5469
  'transform' => array(),
5470
  'unicode-bidi' => array(),
5471
+ 'vector-effect' => array(),
5472
  'visibility' => array(),
5473
  'word-spacing' => array(),
5474
  'writing-mode' => array(),
 
5475
  'xml:lang' => array(),
5476
  'xml:space' => array(),
5477
  'xmlns' => array(),
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
  ),
5493
  'attr_spec_list' => array(
5494
  'alignment-baseline' => array(),
5495
  'baseline-shift' => array(),
 
5496
  'clip' => array(),
5497
  'clip-path' => array(),
5498
  'clip-rule' => array(),
5551
  'stroke-miterlimit' => array(),
5552
  'stroke-opacity' => array(),
5553
  'stroke-width' => array(),
5554
+ 'style' => array(
5555
+ 'blacklisted_value_regex' => '!important',
5556
+ ),
5557
  'systemlanguage' => array(),
5558
  'text-anchor' => array(),
5559
  'text-decoration' => array(),
5560
  'text-rendering' => array(),
5561
  'unicode-bidi' => array(),
5562
+ 'vector-effect' => array(),
5563
  'viewbox' => array(),
5564
  'visibility' => array(),
5565
  'width' => array(),
5569
  'xlink:actuate' => array(),
5570
  'xlink:arcrole' => array(),
5571
  'xlink:href' => array(
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(),
5583
  'xlink:show' => array(),
5584
  'xlink:title' => array(),
5585
  'xlink:type' => array(),
 
5586
  'xml:lang' => array(),
5587
  'xml:space' => array(),
5588
  'xmlns' => array(),
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
  ),
5605
  'attr_spec_list' => array(
5606
  'alignment-baseline' => array(),
5607
  'baseline-shift' => array(),
 
5608
  'clip' => array(),
5609
  'clip-path' => array(),
5610
  'clip-rule' => array(),
5660
  'stroke-miterlimit' => array(),
5661
  'stroke-opacity' => array(),
5662
  'stroke-width' => array(),
5663
+ 'style' => array(
5664
+ 'blacklisted_value_regex' => '!important',
5665
+ ),
5666
  'systemlanguage' => array(),
5667
  'text-anchor' => array(),
5668
  'text-decoration' => array(),
5669
  'text-rendering' => array(),
5670
  'transform' => array(),
5671
  'unicode-bidi' => array(),
5672
+ 'vector-effect' => array(),
5673
  'visibility' => array(),
5674
  'word-spacing' => array(),
5675
  'writing-mode' => array(),
 
5676
  'xml:lang' => array(),
5677
  'xml:space' => array(),
5678
  'xmlns' => array(),
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
  ),
5694
  'attr_spec_list' => array(
5695
  'alignment-baseline' => array(),
5696
  'baseline-shift' => array(),
 
5697
  'clip' => array(),
5698
  'clip-path' => array(),
5699
  'clip-rule' => array(),
5749
  'stroke-miterlimit' => array(),
5750
  'stroke-opacity' => array(),
5751
  'stroke-width' => array(),
5752
+ 'style' => array(
5753
+ 'blacklisted_value_regex' => '!important',
5754
+ ),
5755
  'systemlanguage' => array(),
5756
  'text-anchor' => array(),
5757
  'text-decoration' => array(),
5758
  'text-rendering' => array(),
5759
  'transform' => array(),
5760
  'unicode-bidi' => array(),
5761
+ 'vector-effect' => array(),
5762
  'visibility' => array(),
5763
  'word-spacing' => array(),
5764
  'writing-mode' => array(),
 
5765
  'xml:lang' => array(),
5766
  'xml:space' => array(),
5767
  'xmlns' => array(),
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
  ),
5785
 
5786
  ),
5787
  ),
5788
+ 'progress' => array(
5789
+ array(
5790
+ 'attr_spec_list' => array(
5791
+ 'max' => array(),
5792
+ 'value' => array(),
5793
+ ),
5794
+ 'tag_spec' => array(),
5795
+
5796
+ ),
5797
+ ),
5798
  'q' => array(
5799
  array(
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
  ),
5817
  'attr_spec_list' => array(
5818
  'alignment-baseline' => array(),
5819
  'baseline-shift' => array(),
 
5820
  'clip' => array(),
5821
  'clip-path' => array(),
5822
  'clip-rule' => array(),
5846
  'font-style' => array(),
5847
  'font-variant' => array(),
5848
  'font-weight' => array(),
5849
+ 'fr' => array(),
5850
  'fx' => array(),
5851
  'fy' => array(),
5852
  'glyph-orientation-horizontal' => array(),
5877
  'stroke-miterlimit' => array(),
5878
  'stroke-opacity' => array(),
5879
  'stroke-width' => array(),
5880
+ 'style' => array(
5881
+ 'blacklisted_value_regex' => '!important',
5882
+ ),
5883
  'text-anchor' => array(),
5884
  'text-decoration' => array(),
5885
  'text-rendering' => array(),
5886
  'unicode-bidi' => array(),
5887
+ 'vector-effect' => array(),
5888
  'visibility' => array(),
5889
  'word-spacing' => array(),
5890
  'writing-mode' => array(),
5891
  'xlink:actuate' => array(),
5892
  'xlink:arcrole' => array(),
5893
  'xlink:href' => array(
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(),
5905
  'xlink:show' => array(),
5906
  'xlink:title' => array(),
5907
  'xlink:type' => array(),
 
5908
  'xml:lang' => array(),
5909
  'xml:space' => array(),
5910
  'xmlns' => array(),
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
  ),
5933
  'attr_spec_list' => array(
5934
  'alignment-baseline' => array(),
5935
  'baseline-shift' => array(),
 
5936
  'clip' => array(),
5937
  'clip-path' => array(),
5938
  'clip-rule' => array(),
5990
  'stroke-miterlimit' => array(),
5991
  'stroke-opacity' => array(),
5992
  'stroke-width' => array(),
5993
+ 'style' => array(
5994
+ 'blacklisted_value_regex' => '!important',
5995
+ ),
5996
  'systemlanguage' => array(),
5997
  'text-anchor' => array(),
5998
  'text-decoration' => array(),
5999
  'text-rendering' => array(),
6000
  'transform' => array(),
6001
  'unicode-bidi' => array(),
6002
+ 'vector-effect' => array(),
6003
  'visibility' => array(),
6004
  'width' => array(),
6005
  'word-spacing' => array(),
6006
  'writing-mode' => array(),
6007
  'x' => array(),
 
6008
  'xml:lang' => array(),
6009
  'xml:space' => array(),
6010
  'xmlns' => array(),
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
  ),
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
  ),
6081
  array(
6082
  'attr_spec_list' => array(
6083
  'type' => array(
 
6084
  'mandatory' => true,
6085
  'value_casei' => 'application/json',
6086
  ),
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',
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
  ),
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
  ),
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
  ),
6199
  ),
6200
  ),
6201
  'tag_spec' => array(
 
 
 
 
6202
  'mandatory_parent' => 'amp-accordion',
6203
  'spec_name' => 'amp-accordion > section',
6204
  ),
6208
  'select' => array(
6209
  array(
6210
  'attr_spec_list' => array(
6211
+ '[autofocus]' => array(),
6212
+ '[disabled]' => array(),
6213
+ '[multiple]' => array(),
6214
+ '[required]' => array(),
6215
+ '[size]' => array(),
6216
  'autofocus' => array(),
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(),
6224
  ),
6225
  'tag_spec' => array(
6226
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
6227
+ ),
6228
+
6229
+ ),
6230
+ ),
6231
+ 'slot' => array(
6232
+ array(
6233
+ 'attr_spec_list' => array(
6234
+ 'name' => array(),
6235
+ ),
6236
+ 'tag_spec' => array(
6237
+ 'html_format' => array(
6238
+ 'amp',
6239
  ),
 
 
6240
  ),
6241
 
6242
  ),
6248
 
6249
  ),
6250
  ),
6251
+ 'solidcolor' => array(
6252
+ array(
6253
+ 'attr_spec_list' => array(
6254
+ 'alignment-baseline' => array(),
6255
+ 'baseline-shift' => array(),
6256
+ 'clip' => array(),
6257
+ 'clip-path' => array(),
6258
+ 'clip-rule' => array(),
6259
+ 'color' => array(),
6260
+ 'color-interpolation' => array(),
6261
+ 'color-interpolation-filters' => array(),
6262
+ 'color-profile' => array(),
6263
+ 'color-rendering' => array(),
6264
+ 'cursor' => array(),
6265
+ 'direction' => array(),
6266
+ 'display' => array(),
6267
+ 'dominant-baseline' => array(),
6268
+ 'enable-background' => array(),
6269
+ 'fill' => array(),
6270
+ 'fill-opacity' => array(),
6271
+ 'fill-rule' => array(),
6272
+ 'filter' => array(),
6273
+ 'flood-color' => array(),
6274
+ 'flood-opacity' => array(),
6275
+ 'font-family' => array(),
6276
+ 'font-size' => array(),
6277
+ 'font-size-adjust' => array(),
6278
+ 'font-stretch' => array(),
6279
+ 'font-style' => array(),
6280
+ 'font-variant' => array(),
6281
+ 'font-weight' => array(),
6282
+ 'glyph-orientation-horizontal' => array(),
6283
+ 'glyph-orientation-vertical' => array(),
6284
+ 'image-rendering' => array(),
6285
+ 'kerning' => array(),
6286
+ 'letter-spacing' => array(),
6287
+ 'lighting-color' => array(),
6288
+ 'marker-end' => array(),
6289
+ 'marker-mid' => array(),
6290
+ 'marker-start' => array(),
6291
+ 'mask' => array(),
6292
+ 'opacity' => array(),
6293
+ 'overflow' => array(),
6294
+ 'pointer-events' => array(),
6295
+ 'shape-rendering' => array(),
6296
+ 'solid-color' => array(),
6297
+ 'solid-opacity' => array(),
6298
+ 'stop-color' => array(),
6299
+ 'stop-opacity' => array(),
6300
+ 'stroke' => array(),
6301
+ 'stroke-dasharray' => array(),
6302
+ 'stroke-dashoffset' => array(),
6303
+ 'stroke-linecap' => array(),
6304
+ 'stroke-linejoin' => array(),
6305
+ 'stroke-miterlimit' => array(),
6306
+ 'stroke-opacity' => array(),
6307
+ 'stroke-width' => array(),
6308
+ 'style' => array(
6309
+ 'blacklisted_value_regex' => '!important',
6310
+ ),
6311
+ 'text-anchor' => array(),
6312
+ 'text-decoration' => array(),
6313
+ 'text-rendering' => array(),
6314
+ 'unicode-bidi' => array(),
6315
+ 'vector-effect' => array(),
6316
+ 'visibility' => array(),
6317
+ 'word-spacing' => array(),
6318
+ 'writing-mode' => array(),
6319
+ 'xml:lang' => array(),
6320
+ 'xml:space' => array(),
6321
+ 'xmlns' => array(),
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(
6336
  array(
6337
  'attr_spec_list' => array(
6338
+ '[src]' => array(),
6339
+ '[type]' => array(),
6340
  'media' => array(),
6341
  'src' => array(
6342
  'blacklisted_value_regex' => '__amp_source_origin',
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(
6363
+ '[src]' => array(),
6364
+ '[type]' => array(),
6365
  'media' => array(),
6366
  'src' => array(
6367
  'blacklisted_value_regex' => '__amp_source_origin',
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
  ),
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
  ),
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(
6440
+ '[src]' => array(),
6441
+ '[type]' => array(),
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
  ),
6484
  'offset' => array(),
6485
  'stop-color' => array(),
6486
  'stop-opacity' => array(),
6487
+ 'style' => array(
6488
+ 'blacklisted_value_regex' => '!important',
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
  ),
6504
  'offset' => array(),
6505
  'stop-color' => array(),
6506
  'stop-opacity' => array(),
6507
+ 'style' => array(
6508
+ 'blacklisted_value_regex' => '!important',
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
  ),
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',
6557
  'spec_name' => 'noscript > style[amp-boilerplate]',
6558
  'spec_url' => 'https://github.com/ampproject/amphtml/blob/master/spec/amp-boilerplate.md',
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(
6598
  'attr_spec_list' => array(
6599
  'alignment-baseline' => array(),
6600
  'baseline-shift' => array(),
 
6601
  'clip' => array(),
6602
  'clip-path' => array(),
6603
  'clip-rule' => array(),
6660
  'text-decoration' => array(),
6661
  'text-rendering' => array(),
6662
  'unicode-bidi' => array(),
6663
+ 'vector-effect' => array(),
6664
  'version' => array(
6665
  'value_regex' => '(1.0|1.1)',
6666
  ),
6670
  'word-spacing' => array(),
6671
  'writing-mode' => array(),
6672
  'x' => array(),
 
6673
  'xml:lang' => array(),
6674
  'xml:space' => array(),
6675
  'xmlns' => array(),
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(
6691
+ array(
6692
+ 'attr_spec_list' => array(
6693
+ 'alignment-baseline' => array(),
6694
+ 'baseline-shift' => array(),
6695
+ 'clip' => array(),
6696
+ 'clip-path' => array(),
6697
+ 'clip-rule' => array(),
6698
+ 'color' => array(),
6699
+ 'color-interpolation' => array(),
6700
+ 'color-interpolation-filters' => array(),
6701
+ 'color-profile' => array(),
6702
+ 'color-rendering' => array(),
6703
+ 'cursor' => array(),
6704
+ 'direction' => array(),
6705
+ 'display' => array(),
6706
+ 'dominant-baseline' => array(),
6707
+ 'enable-background' => array(),
6708
+ 'fill' => array(),
6709
+ 'fill-opacity' => array(),
6710
+ 'fill-rule' => array(),
6711
+ 'filter' => array(),
6712
+ 'flood-color' => array(),
6713
+ 'flood-opacity' => array(),
6714
+ 'font-family' => array(),
6715
+ 'font-size' => array(),
6716
+ 'font-size-adjust' => array(),
6717
+ 'font-stretch' => array(),
6718
+ 'font-style' => array(),
6719
+ 'font-variant' => array(),
6720
+ 'font-weight' => array(),
6721
+ 'glyph-orientation-horizontal' => array(),
6722
+ 'glyph-orientation-vertical' => array(),
6723
+ 'image-rendering' => array(),
6724
+ 'kerning' => array(),
6725
+ 'letter-spacing' => array(),
6726
+ 'lighting-color' => array(),
6727
+ 'marker-end' => array(),
6728
+ 'marker-mid' => array(),
6729
+ 'marker-start' => array(),
6730
+ 'mask' => array(),
6731
+ 'opacity' => array(),
6732
+ 'overflow' => array(),
6733
+ 'pointer-events' => array(),
6734
+ 'requiredextensions' => array(),
6735
+ 'requiredfeatures' => array(),
6736
+ 'shape-rendering' => array(),
6737
+ 'stop-color' => array(),
6738
+ 'stop-opacity' => array(),
6739
+ 'stroke' => array(),
6740
+ 'stroke-dasharray' => array(),
6741
+ 'stroke-dashoffset' => array(),
6742
+ 'stroke-linecap' => array(),
6743
+ 'stroke-linejoin' => array(),
6744
+ 'stroke-miterlimit' => array(),
6745
+ 'stroke-opacity' => array(),
6746
+ 'stroke-width' => array(),
6747
+ 'style' => array(
6748
+ 'blacklisted_value_regex' => '!important',
6749
+ ),
6750
+ 'systemlanguage' => array(),
6751
+ 'text-anchor' => array(),
6752
+ 'text-decoration' => array(),
6753
+ 'text-rendering' => array(),
6754
+ 'unicode-bidi' => array(),
6755
+ 'vector-effect' => array(),
6756
+ 'visibility' => array(),
6757
+ 'word-spacing' => array(),
6758
+ 'writing-mode' => array(),
6759
+ 'xml:lang' => array(),
6760
+ 'xml:space' => array(),
6761
+ 'xmlns' => array(),
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
  ),
6777
  'attr_spec_list' => array(
6778
  'alignment-baseline' => array(),
6779
  'baseline-shift' => array(),
 
6780
  'clip' => array(),
6781
  'clip-path' => array(),
6782
  'clip-rule' => array(),
6829
  'stroke-miterlimit' => array(),
6830
  'stroke-opacity' => array(),
6831
  'stroke-width' => array(),
6832
+ 'style' => array(
6833
+ 'blacklisted_value_regex' => '!important',
6834
+ ),
6835
  'text-anchor' => array(),
6836
  'text-decoration' => array(),
6837
  'text-rendering' => array(),
6838
  'unicode-bidi' => array(),
6839
+ 'vector-effect' => array(),
6840
  'viewbox' => array(),
6841
  'visibility' => array(),
6842
  'word-spacing' => array(),
6843
  'writing-mode' => array(),
 
6844
  'xml:lang' => array(),
6845
  'xml:space' => array(),
6846
  'xmlns' => array(),
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
  ),
6906
  ),
6907
  ),
6908
  'tag_spec' => array(
 
 
 
6909
  'disallowed_ancestor' => array(
6910
  'template',
6911
  ),
 
6912
  ),
6913
 
6914
  ),
6918
  'attr_spec_list' => array(
6919
  'alignment-baseline' => array(),
6920
  'baseline-shift' => array(),
 
6921
  'clip' => array(),
6922
  'clip-path' => array(),
6923
  'clip-rule' => array(),
6975
  'stroke-miterlimit' => array(),
6976
  'stroke-opacity' => array(),
6977
  'stroke-width' => array(),
6978
+ 'style' => array(
6979
+ 'blacklisted_value_regex' => '!important',
6980
+ ),
6981
  'systemlanguage' => array(),
6982
  'text-anchor' => array(),
6983
  'text-decoration' => array(),
6985
  'textlength' => array(),
6986
  'transform' => array(),
6987
  'unicode-bidi' => array(),
6988
+ 'vector-effect' => array(),
6989
  'visibility' => array(),
6990
  'word-spacing' => array(),
6991
  'writing-mode' => array(),
6992
  'x' => array(),
 
6993
  'xml:lang' => array(),
6994
  'xml:space' => array(),
6995
  'xmlns' => array(),
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
  ),
7010
  'textarea' => array(
7011
  array(
7012
  'attr_spec_list' => array(
7013
+ '[autocomplete]' => array(),
7014
+ '[autofocus]' => array(),
7015
+ '[cols]' => array(),
7016
+ '[disabled]' => array(),
7017
+ '[maxlength]' => array(),
7018
+ '[minlength]' => array(),
7019
+ '[placeholder]' => array(),
7020
+ '[readonly]' => array(),
7021
+ '[required]' => array(),
7022
+ '[rows]' => array(),
7023
+ '[selectiondirection]' => array(),
7024
+ '[selectionend]' => array(),
7025
+ '[selectionstart]' => array(),
7026
+ '[spellcheck]' => array(),
7027
+ '[wrap]' => array(),
7028
  'autocomplete' => array(),
7029
  'autofocus' => array(),
7030
  'cols' => array(),
7031
  'disabled' => array(),
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(),
7039
  'required' => array(),
7045
  'wrap' => array(),
7046
  ),
7047
  'tag_spec' => array(
7048
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/components/amp-form',
 
 
 
 
7049
  ),
7050
 
7051
  ),
7055
  'attr_spec_list' => array(
7056
  'alignment-baseline' => array(),
7057
  'baseline-shift' => array(),
 
7058
  'clip' => array(),
7059
  'clip-path' => array(),
7060
  'clip-rule' => array(),
7111
  'stroke-miterlimit' => array(),
7112
  'stroke-opacity' => array(),
7113
  'stroke-width' => array(),
7114
+ 'style' => array(
7115
+ 'blacklisted_value_regex' => '!important',
7116
+ ),
7117
  'systemlanguage' => array(),
7118
  'text-anchor' => array(),
7119
  'text-decoration' => array(),
7120
  'text-rendering' => array(),
7121
  'unicode-bidi' => array(),
7122
+ 'vector-effect' => array(),
7123
  'visibility' => array(),
7124
  'word-spacing' => array(),
7125
  'writing-mode' => array(),
7126
  'xlink:actuate' => array(),
7127
  'xlink:arcrole' => array(),
7128
  'xlink:href' => array(
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(),
7140
  'xlink:show' => array(),
7141
  'xlink:title' => array(),
7142
  'xlink:type' => array(),
 
7143
  'xml:lang' => array(),
7144
  'xml:space' => array(),
7145
  'xmlns' => array(),
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
  ),
7208
  ),
7209
  array(
7210
  'attr_spec_list' => array(
7211
+ 'style' => array(
7212
+ 'blacklisted_value_regex' => '!important',
7213
+ ),
7214
  'xml:lang' => array(),
7215
  'xml:space' => array(),
7216
  'xmlns' => array(),
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
  ),
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
  ),
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
  ),
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
  ),
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
  ),
7365
  ),
7366
  array(
7367
  'attr_spec_list' => array(
7368
+ '[label]' => array(),
7369
+ '[src]' => array(),
7370
+ '[srclang]' => array(),
7371
  'default' => array(
7372
  'value' => '',
7373
  ),
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
  ),
7397
  ),
7398
  array(
7399
  'attr_spec_list' => array(
7400
+ '[label]' => array(),
7401
+ '[src]' => array(),
7402
+ '[srclang]' => array(),
7403
  'default' => array(
7404
  'value' => '',
7405
  ),
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
  ),
7432
  ),
7433
  array(
7434
  'attr_spec_list' => array(
7435
+ '[label]' => array(),
7436
+ '[src]' => array(),
7437
+ '[srclang]' => array(),
7438
  'default' => array(
7439
  'value' => '',
7440
  ),
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
  ),
7464
  ),
7465
  array(
7466
  'attr_spec_list' => array(
7467
+ '[label]' => array(),
7468
+ '[src]' => array(),
7469
+ '[srclang]' => array(),
7470
  'default' => array(
7471
  'value' => '',
7472
  ),
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(
7502
+ '[label]' => array(),
7503
+ '[src]' => array(),
7504
+ '[srclang]' => array(),
7505
+ 'default' => array(
7506
+ 'value' => '',
7507
+ ),
7508
+ 'kind' => array(
7509
+ 'mandatory' => true,
7510
+ 'value_casei' => 'subtitles',
7511
+ ),
7512
+ 'label' => array(),
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(
7522
+ 'mandatory' => true,
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(
7538
  array(
7539
  'attr_spec_list' => array(
7540
  'alignment-baseline' => array(),
7541
  'baseline-shift' => array(),
 
7542
  'clip' => array(),
7543
  'clip-path' => array(),
7544
  'clip-rule' => array(),
7592
  'stroke-miterlimit' => array(),
7593
  'stroke-opacity' => array(),
7594
  'stroke-width' => array(),
7595
+ 'style' => array(
7596
+ 'blacklisted_value_regex' => '!important',
7597
+ ),
7598
  'systemlanguage' => array(),
7599
  'text-anchor' => array(),
7600
  'text-decoration' => array(),
7601
  'text-rendering' => array(),
7602
  'unicode-bidi' => array(),
7603
+ 'vector-effect' => array(),
7604
  'visibility' => array(),
7605
  'word-spacing' => array(),
7606
  'writing-mode' => array(),
7607
  'xlink:actuate' => array(),
7608
  'xlink:arcrole' => array(),
7609
  'xlink:href' => array(
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(),
7621
  'xlink:show' => array(),
7622
  'xlink:title' => array(),
7623
  'xlink:type' => array(),
 
7624
  'xml:lang' => array(),
7625
  'xml:space' => array(),
7626
  'xmlns' => array(),
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
  ),
7642
  'attr_spec_list' => array(
7643
  'alignment-baseline' => array(),
7644
  'baseline-shift' => array(),
 
7645
  'clip' => array(),
7646
  'clip-path' => array(),
7647
  'clip-rule' => array(),
7699
  'stroke-miterlimit' => array(),
7700
  'stroke-opacity' => array(),
7701
  'stroke-width' => array(),
7702
+ 'style' => array(
7703
+ 'blacklisted_value_regex' => '!important',
7704
+ ),
7705
  'systemlanguage' => array(),
7706
  'text-anchor' => array(),
7707
  'text-decoration' => array(),
7708
  'text-rendering' => array(),
7709
  'textlength' => array(),
7710
  'unicode-bidi' => array(),
7711
+ 'vector-effect' => array(),
7712
  'visibility' => array(),
7713
  'word-spacing' => array(),
7714
  'writing-mode' => array(),
7715
  'x' => array(),
 
7716
  'xml:lang' => array(),
7717
  'xml:space' => array(),
7718
  'xmlns' => array(),
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
  ),
7760
  'attr_spec_list' => array(
7761
  'alignment-baseline' => array(),
7762
  'baseline-shift' => array(),
 
7763
  'clip' => array(),
7764
  'clip-path' => array(),
7765
  'clip-rule' => array(),
7814
  'stroke-miterlimit' => array(),
7815
  'stroke-opacity' => array(),
7816
  'stroke-width' => array(),
7817
+ 'style' => array(
7818
+ 'blacklisted_value_regex' => '!important',
7819
+ ),
7820
  'systemlanguage' => array(),
7821
  'text-anchor' => array(),
7822
  'text-decoration' => array(),
7823
  'text-rendering' => array(),
7824
  'transform' => array(),
7825
  'unicode-bidi' => array(),
7826
+ 'vector-effect' => array(),
7827
  'visibility' => array(),
7828
  'width' => array(),
7829
  'word-spacing' => array(),
7832
  'xlink:actuate' => array(),
7833
  'xlink:arcrole' => array(),
7834
  'xlink:href' => array(
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(),
7846
  'xlink:show' => array(),
7847
  'xlink:title' => array(),
7848
  'xlink:type' => array(),
 
7849
  'xml:lang' => array(),
7850
  'xml:space' => array(),
7851
  'xmlns' => array(),
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
  ),
7878
  'height' => array(),
7879
  'loop' => array(),
7880
  'muted' => array(),
7881
+ 'playsinline' => array(),
7882
  'poster' => array(),
7883
  'preload' => array(),
7884
  'src' => array(
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
  ),
7907
  'attr_spec_list' => array(
7908
  'externalresourcesrequired' => array(),
7909
  'preserveaspectratio' => array(),
7910
+ 'style' => array(
7911
+ 'blacklisted_value_regex' => '!important',
7912
+ ),
7913
  'viewbox' => array(),
7914
  'viewtarget' => array(),
 
7915
  'xml:lang' => array(),
7916
  'xml:space' => array(),
7917
  'xmlns' => array(),
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
  ),
7935
  'g1' => array(),
7936
  'g2' => array(),
7937
  'k' => array(),
7938
+ 'style' => array(
7939
+ 'blacklisted_value_regex' => '!important',
7940
+ ),
7941
  'u1' => array(),
7942
  'u2' => array(),
 
7943
  'xml:lang' => array(),
7944
  'xml:space' => array(),
7945
  'xmlns' => array(),
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
  ),
7977
  );
7978
 
7979
  private static $layout_allowed_attrs = array(
7980
+ '[height]' => array(),
7981
+ '[width]' => array(),
7982
  'height' => array(),
7983
  'heights' => array(),
7984
  'layout' => array(),
7989
 
7990
 
7991
  private static $globally_allowed_attrs = array(
7992
+ '[aria-activedescendant]' => array(),
7993
+ '[aria-atomic]' => array(),
7994
+ '[aria-autocomplete]' => array(),
7995
+ '[aria-busy]' => array(),
7996
+ '[aria-checked]' => array(),
7997
+ '[aria-controls]' => array(),
7998
+ '[aria-describedby]' => array(),
7999
+ '[aria-disabled]' => array(),
8000
+ '[aria-dropeffect]' => array(),
8001
+ '[aria-expanded]' => array(),
8002
+ '[aria-flowto]' => array(),
8003
+ '[aria-grabbed]' => array(),
8004
+ '[aria-haspopup]' => array(),
8005
+ '[aria-hidden]' => array(),
8006
+ '[aria-invalid]' => array(),
8007
+ '[aria-label]' => array(),
8008
+ '[aria-labelledby]' => array(),
8009
+ '[aria-level]' => array(),
8010
+ '[aria-live]' => array(),
8011
+ '[aria-multiline]' => array(),
8012
+ '[aria-multiselectable]' => array(),
8013
+ '[aria-orientation]' => array(),
8014
+ '[aria-owns]' => array(),
8015
+ '[aria-posinset]' => array(),
8016
+ '[aria-pressed]' => array(),
8017
+ '[aria-readonly]' => array(),
8018
+ '[aria-relevant]' => array(),
8019
+ '[aria-required]' => array(),
8020
+ '[aria-selected]' => array(),
8021
+ '[aria-setsize]' => array(),
8022
+ '[aria-sort]' => array(),
8023
+ '[aria-valuemax]' => array(),
8024
+ '[aria-valuemin]' => array(),
8025
+ '[aria-valuenow]' => array(),
8026
+ '[aria-valuetext]' => array(),
8027
+ '[class]' => array(),
8028
+ '[hidden]' => array(),
8029
+ '[text]' => array(),
8030
+ 'about' => array(),
8031
  'accesskey' => array(),
8032
  'amp-access' => array(),
8033
  'amp-access-behavior' => array(),
8046
  'aria-busy' => array(),
8047
  'aria-checked' => array(),
8048
  'aria-controls' => array(),
8049
+ 'aria-current' => array(),
8050
  'aria-describedby' => array(),
8051
  'aria-disabled' => array(),
8052
  'aria-dropeffect' => 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(),
8098
  'itemprop' => array(),
8099
  'itemref' => 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
 
includes/sanitizers/class-amp-audio-sanitizer.php CHANGED
@@ -1,53 +1,121 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
4
-
 
 
 
 
 
 
 
 
5
  class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
 
 
 
 
 
 
 
6
  public static $tag = 'audio';
7
 
 
 
 
 
 
 
 
8
  private static $script_slug = 'amp-audio';
 
 
 
 
 
 
 
 
9
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-audio-0.1.js';
10
 
 
 
 
 
 
 
 
 
 
 
 
11
  public function get_scripts() {
12
  if ( ! $this->did_convert_elements ) {
13
  return array();
14
  }
15
-
16
  return array( self::$script_slug => self::$script_src );
17
  }
18
 
 
 
 
 
 
19
  public function sanitize() {
20
- $nodes = $this->dom->getElementsByTagName( self::$tag );
21
  $num_nodes = $nodes->length;
22
  if ( 0 === $num_nodes ) {
23
  return;
24
  }
25
 
26
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
27
- $node = $nodes->item( $i );
28
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
29
 
30
  $new_attributes = $this->filter_attributes( $old_attributes );
31
 
32
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-audio', $new_attributes );
33
 
34
- // TODO: `source` does not have closing tag, and DOMDocument doesn't handle it well.
35
  foreach ( $node->childNodes as $child_node ) {
 
 
 
 
 
 
 
 
 
36
  $new_child_node = $child_node->cloneNode( true );
 
 
 
 
37
  $old_child_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $new_child_node );
38
  $new_child_attributes = $this->filter_attributes( $old_child_attributes );
39
 
40
- // Only append source tags with a valid src attribute
41
- if ( ! empty( $new_child_attributes['src'] ) && 'source' === $new_child_node->tagName ) {
42
- $new_node->appendChild( $new_child_node );
 
 
43
  }
 
 
 
 
 
 
 
44
  }
45
 
46
- // If the node has at least one valid source, replace the old node with it.
47
- // Otherwise, just remove the node.
48
- //
49
- // TODO: Add a fallback handler.
50
- // See: https://github.com/ampproject/amphtml/issues/2261
 
 
51
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
52
  $node->parentNode->removeChild( $node );
53
  } else {
@@ -58,6 +126,24 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
58
  }
59
  }
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  private function filter_attributes( $attributes ) {
62
  $out = array();
63
 
@@ -83,7 +169,7 @@ class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
83
  }
84
  break;
85
 
86
- default;
87
  break;
88
  }
89
  }
1
  <?php
2
+ /**
3
+ * Class AMP_Audio_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Audio_Sanitizer
10
+ *
11
+ * Converts <audio> tags to <amp-audio>
12
+ */
13
  class AMP_Audio_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Tag.
17
+ *
18
+ * @var string HTML audio tag to identify and replace with AMP version.
19
+ * @since 0.2
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
+ *
62
+ * @since 0.2
63
+ */
64
  public function sanitize() {
65
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
66
  $num_nodes = $nodes->length;
67
  if ( 0 === $num_nodes ) {
68
  return;
69
  }
70
 
71
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
72
+ $node = $nodes->item( $i );
73
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
74
 
75
  $new_attributes = $this->filter_attributes( $old_attributes );
76
 
77
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-audio', $new_attributes );
78
 
 
79
  foreach ( $node->childNodes as $child_node ) {
80
+
81
+ /**
82
+ * Child node.
83
+ *
84
+ * @todo: Fix when `source` has no closing tag as DOMDocument does not handle well.
85
+ *
86
+ * @var DOMNode $child_node
87
+ */
88
+
89
  $new_child_node = $child_node->cloneNode( true );
90
+ if ( ! $new_child_node instanceof DOMElement ) {
91
+ continue;
92
+ }
93
+
94
  $old_child_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $new_child_node );
95
  $new_child_attributes = $this->filter_attributes( $old_child_attributes );
96
 
97
+ if ( empty( $new_child_attributes['src'] ) ) {
98
+ continue;
99
+ }
100
+ if ( 'source' !== $new_child_node->tagName ) {
101
+ continue;
102
  }
103
+
104
+ // The textContent is invalid for `source` nodes.
105
+ $new_child_node->textContent = null;
106
+
107
+ // Only append source tags with a valid src attribute.
108
+ $new_node->appendChild( $new_child_node );
109
+
110
  }
111
 
112
+ /**
113
+ * If the node has at least one valid source, replace the old node with it.
114
+ * Otherwise, just remove the node.
115
+ *
116
+ * @todo: Add a fallback handler.
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 {
126
  }
127
  }
128
 
129
+ /**
130
+ * "Filter" HTML attributes for <amp-audio> elements.
131
+ *
132
+ * @since 0.2
133
+ *
134
+ * @param string[] $attributes {
135
+ * Attributes.
136
+ *
137
+ * @type string $src Audio URL - Empty if HTTPS required per $this->args['require_https_src']
138
+ * @type int $width <audio> attribute - Set to numeric value if px or %
139
+ * @type int $height <audio> attribute - Set to numeric value if px or %
140
+ * @type string $class <audio> attribute - Pass along if found
141
+ * @type bool $loop <audio> attribute - Convert 'false' to empty string ''
142
+ * @type bool $muted <audio> attribute - Convert 'false' to empty string ''
143
+ * @type bool $autoplay <audio> attribute - Convert 'false' to empty string ''
144
+ * }
145
+ * @return array Returns HTML attributes; removes any not specifically declared above from input.
146
+ */
147
  private function filter_attributes( $attributes ) {
148
  $out = array();
149
 
169
  }
170
  break;
171
 
172
+ default:
173
  break;
174
  }
175
  }
includes/sanitizers/class-amp-base-sanitizer.php CHANGED
@@ -1,36 +1,138 @@
1
  <?php
 
 
 
 
 
2
 
 
 
 
3
  abstract class AMP_Base_Sanitizer {
 
 
 
 
 
 
 
 
4
  const FALLBACK_HEIGHT = 400;
5
 
 
 
 
 
 
 
 
6
  protected $DEFAULT_ARGS = array();
7
 
 
 
 
 
 
 
 
8
  protected $dom;
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  protected $args;
 
 
 
 
 
 
 
 
 
10
  protected $did_convert_elements = false;
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  public function __construct( $dom, $args = array() ) {
13
- $this->dom = $dom;
14
  $this->args = array_merge( $this->DEFAULT_ARGS, $args );
15
  }
16
 
 
 
 
17
  abstract public function sanitize();
18
 
 
 
 
 
 
 
 
 
 
 
19
  public function get_scripts() {
20
  return array();
21
  }
22
 
 
 
 
 
 
 
 
23
  public function get_styles() {
24
  return array();
25
  }
26
 
 
 
 
 
 
27
  protected function get_body_node() {
28
  return $this->dom->getElementsByTagName( 'body' )->item( 0 );
29
  }
30
 
 
 
 
 
 
 
 
 
31
  public function sanitize_dimension( $value, $dimension ) {
32
  if ( empty( $value ) ) {
33
- return $value;
34
  }
35
 
36
  if ( false !== filter_var( $value, FILTER_VALIDATE_INT ) ) {
@@ -51,6 +153,20 @@ abstract class AMP_Base_Sanitizer {
51
  return '';
52
  }
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  public function enforce_fixed_height( $attributes ) {
55
  if ( empty( $attributes['height'] ) ) {
56
  unset( $attributes['width'] );
@@ -71,6 +187,17 @@ abstract class AMP_Base_Sanitizer {
71
  *
72
  * See https://github.com/ampproject/amphtml/issues/1280#issuecomment-171533526
73
  * See https://github.com/Automattic/amp-wp/issues/101
 
 
 
 
 
 
 
 
 
 
 
74
  */
75
  public function enforce_sizes_attribute( $attributes ) {
76
  if ( ! isset( $attributes['width'], $attributes['height'] ) ) {
@@ -89,6 +216,25 @@ abstract class AMP_Base_Sanitizer {
89
  return $attributes;
90
  }
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  public function add_or_append_attribute( &$attributes, $key, $value, $separator = ' ' ) {
93
  if ( isset( $attributes[ $key ] ) ) {
94
  $attributes[ $key ] .= $separator . $value;
@@ -99,22 +245,23 @@ abstract class AMP_Base_Sanitizer {
99
 
100
  /**
101
  * Decide if we should remove a src attribute if https is required.
 
102
  * If not required, the implementing class may want to try and force https instead.
103
  *
104
- * @param string $src
105
- * @param boolean $force_https
106
- * @return string
107
  */
108
  public function maybe_enforce_https_src( $src, $force_https = false ) {
109
  $protocol = strtok( $src, ':' );
110
  if ( 'https' !== $protocol ) {
111
- // Check if https is required
112
  if ( isset( $this->args['require_https_src'] ) && true === $this->args['require_https_src'] ) {
113
  // Remove the src. Let the implementing class decide what do from here.
114
  $src = '';
115
  } elseif ( ( ! isset( $this->args['require_https_src'] ) || false === $this->args['require_https_src'] )
116
  && true === $force_https ) {
117
- // Don't remove the src, but force https instead
118
  $src = set_url_scheme( $src, 'https' );
119
  }
120
  }
1
  <?php
2
+ /**
3
+ * Class AMP_Base_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Class AMP_Base_Sanitizer
10
+ */
11
  abstract class AMP_Base_Sanitizer {
12
+
13
+ /**
14
+ * Value used with the height attribute in an $attributes parameter is empty.
15
+ *
16
+ * @since 0.3.3
17
+ *
18
+ * @const int
19
+ */
20
  const FALLBACK_HEIGHT = 400;
21
 
22
+ /**
23
+ * Placeholder for default args, to be set in child classes.
24
+ *
25
+ * @since 0.2
26
+ *
27
+ * @var array
28
+ */
29
  protected $DEFAULT_ARGS = array();
30
 
31
+ /**
32
+ * DOM.
33
+ *
34
+ * @var DOMDocument A standard PHP representation of an HTML document in object form.
35
+ *
36
+ * @since 0.2
37
+ */
38
  protected $dom;
39
+
40
+ /**
41
+ * Array of flags used to control sanitization.
42
+ *
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
+ *
60
+ * @var bool
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
74
+ * @type bool $add_placeholder
75
+ * @type bool $require_https_src
76
+ * @type string[] $amp_allowed_tags
77
+ * @type string[] $amp_globally_allowed_attributes
78
+ * @type string[] $amp_layout_allowed_attributes
79
+ * }
80
+ */
81
  public function __construct( $dom, $args = array() ) {
82
+ $this->dom = $dom;
83
  $this->args = array_merge( $this->DEFAULT_ARGS, $args );
84
  }
85
 
86
+ /**
87
+ * Sanitize the HTML contained in the DOMDocument received by the constructor
88
+ */
89
  abstract public function sanitize();
90
 
91
+ /**
92
+ * Return array of values that would be valid as an HTML `script` element.
93
+ *
94
+ * Array keys are AMP element names and array values are their respective
95
+ * Javascript URLs from https://cdn.ampproject.org
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();
103
  }
104
 
105
+ /**
106
+ * Return array of values that would be valid as an HTML `style` attribute.
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
+ /**
126
+ * Sanitizes a CSS dimension specifier while being sensitive to dimension context.
127
+ *
128
+ * @param string $value A valid CSS dimension specifier; e.g. 50, 50px, 50%.
129
+ * @param string $dimension 'width' or ignored. 'width' only affects $values ending in '%'.
130
+ *
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 ) ) {
153
  return '';
154
  }
155
 
156
+ /**
157
+ * Enforce fixed height.
158
+ *
159
+ * @param string[] $attributes {
160
+ * Attributes.
161
+ *
162
+ * @type int $height
163
+ * @type int $width
164
+ * @type string $sizes
165
+ * @type string $class
166
+ * @type string $layout
167
+ * }
168
+ * @return string[]
169
+ */
170
  public function enforce_fixed_height( $attributes ) {
171
  if ( empty( $attributes['height'] ) ) {
172
  unset( $attributes['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'] ) ) {
216
  return $attributes;
217
  }
218
 
219
+ /**
220
+ * Adds or appends key and value to list of attributes
221
+ *
222
+ * Adds key and value to list of attributes, or if the key already exists in the array
223
+ * it concatenates to existing attribute separator by a space or other supplied separator.
224
+ *
225
+ * @param string[] $attributes {
226
+ * Attributes.
227
+ *
228
+ * @type int $height
229
+ * @type int $width
230
+ * @type string $sizes
231
+ * @type string $class
232
+ * @type string $layout
233
+ * }
234
+ * @param string $key Valid associative array index to add.
235
+ * @param string $value Value to add or append to array indexed at the key.
236
+ * @param string $separator Optional; defaults to space but some other separator if needed.
237
+ */
238
  public function add_or_append_attribute( &$attributes, $key, $value, $separator = ' ' ) {
239
  if ( isset( $attributes[ $key ] ) ) {
240
  $attributes[ $key ] .= $separator . $value;
245
 
246
  /**
247
  * Decide if we should remove a src attribute if https is required.
248
+ *
249
  * If not required, the implementing class may want to try and force https instead.
250
  *
251
+ * @param string $src URL to convert to HTTPS if forced, or made empty if $args['require_https_src'].
252
+ * @param boolean $force_https Force setting of HTTPS if true.
253
+ * @return string URL which may have been updated with HTTPS, or may have been made empty.
254
  */
255
  public function maybe_enforce_https_src( $src, $force_https = false ) {
256
  $protocol = strtok( $src, ':' );
257
  if ( 'https' !== $protocol ) {
258
+ // Check if https is required.
259
  if ( isset( $this->args['require_https_src'] ) && true === $this->args['require_https_src'] ) {
260
  // Remove the src. Let the implementing class decide what do from here.
261
  $src = '';
262
  } elseif ( ( ! isset( $this->args['require_https_src'] ) || false === $this->args['require_https_src'] )
263
  && true === $force_https ) {
264
+ // Don't remove the src, but force https instead.
265
  $src = set_url_scheme( $src, 'https' );
266
  }
267
  }
includes/sanitizers/class-amp-blacklist-sanitizer.php CHANGED
@@ -1,6 +1,9 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
 
 
 
4
 
5
  /**
6
  * Strips blacklisted tags and attributes from content.
@@ -8,28 +11,43 @@ require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' )
8
  * See following for blacklist:
9
  * https://github.com/ampproject/amphtml/blob/master/spec/amp-html-format.md#html-tags
10
  *
11
- * As of AMP 0.5 this has been replaced by AMP_Tag_And_Attribute_Sanitizer but is kept around for back-compat.
12
- *
13
  */
14
  class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
15
  const PATTERN_REL_WP_ATTACHMENT = '#wp-att-([\d]+)#';
16
 
 
 
 
 
 
17
  protected $DEFAULT_ARGS = array(
18
  'add_blacklisted_protocols' => array(),
19
  'add_blacklisted_tags' => array(),
20
  'add_blacklisted_attributes' => array(),
21
  );
22
 
 
 
 
23
  public function sanitize() {
24
- $blacklisted_tags = $this->get_blacklisted_tags();
25
  $blacklisted_attributes = $this->get_blacklisted_attributes();
26
- $blacklisted_protocols = $this->get_blacklisted_protocols();
27
 
28
  $body = $this->get_body_node();
29
  $this->strip_tags( $body, $blacklisted_tags );
30
  $this->strip_attributes_recursive( $body, $blacklisted_attributes, $blacklisted_protocols );
31
  }
32
 
 
 
 
 
 
 
 
33
  private function strip_attributes_recursive( $node, $bad_attributes, $bad_protocols ) {
34
  if ( XML_ELEMENT_NODE !== $node->nodeType ) {
35
  return;
@@ -50,14 +68,14 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
50
  if ( $node->hasAttributes() ) {
51
  $length = $node->attributes->length;
52
  for ( $i = $length - 1; $i >= 0; $i-- ) {
53
- $attribute = $node->attributes->item( $i );
54
  $attribute_name = strtolower( $attribute->name );
55
  if ( in_array( $attribute_name, $bad_attributes, true ) ) {
56
  $node->removeAttribute( $attribute_name );
57
  continue;
58
  }
59
 
60
- // on* attributes (like onclick) are a special case
61
  if ( 0 === stripos( $attribute_name, 'on' ) && 'on' !== $attribute_name ) {
62
  $node->removeAttribute( $attribute_name );
63
  continue;
@@ -75,16 +93,22 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
75
  }
76
  }
77
 
 
 
 
 
 
 
78
  private function strip_tags( $node, $tag_names ) {
79
  foreach ( $tag_names as $tag_name ) {
80
  $elements = $node->getElementsByTagName( $tag_name );
81
- $length = $elements->length;
82
  if ( 0 === $length ) {
83
  continue;
84
  }
85
 
86
  for ( $i = $length - 1; $i >= 0; $i-- ) {
87
- $element = $elements->item( $i );
88
  $parent_node = $element->parentNode;
89
  $parent_node->removeChild( $element );
90
 
@@ -95,6 +119,12 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
95
  }
96
  }
97
 
 
 
 
 
 
 
98
  private function sanitize_a_attribute( $node, $attribute ) {
99
  $attribute_name = strtolower( $attribute->name );
100
 
@@ -117,23 +147,31 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
117
  // _new is not allowed; swap with _blank
118
  $node->setAttribute( $attribute_name, '_blank' );
119
  } else {
120
- // only _blank is allowed
121
  $node->removeAttribute( $attribute_name );
122
  }
123
  }
124
  }
125
 
 
 
 
 
 
 
126
  private function validate_a_node( $node ) {
127
- // Get the href attribute
128
  $href = $node->getAttribute( 'href' );
129
 
130
  if ( empty( $href ) ) {
131
- // If no href, check that a is an anchor or not.
132
- // We don't need to validate anchors any further.
 
 
133
  return $node->hasAttribute( 'name' ) || $node->hasAttribute( 'id' );
134
  }
135
 
136
- // If this is an anchor link, just return true
137
  if ( 0 === strpos( $href, '#' ) ) {
138
  return true;
139
  }
@@ -143,9 +181,9 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
143
  $href = untrailingslashit( get_home_url() ) . $href;
144
  }
145
 
146
- $valid_protocols = array( 'http', 'https', 'mailto', 'sms', 'tel', 'viber', 'whatsapp' );
147
- $special_protocols = array( 'tel', 'sms' ); // these ones don't valid with `filter_var+FILTER_VALIDATE_URL`
148
- $protocol = strtok( $href, ':' );
149
 
150
  if ( false === filter_var( $href, FILTER_VALIDATE_URL )
151
  && ! in_array( $protocol, $special_protocols, true ) ) {
@@ -159,6 +197,13 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
159
  return true;
160
  }
161
 
 
 
 
 
 
 
 
162
  private function replace_node_with_children( $node, $bad_attributes, $bad_protocols ) {
163
  // If the node has children and also has a parent node,
164
  // clone and re-add all the children just before current node.
@@ -176,8 +221,15 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
176
  }
177
  }
178
 
 
 
 
 
 
 
 
179
  private function merge_defaults_with_args( $key, $values ) {
180
- // Merge default values with user specified args
181
  if ( ! empty( $this->args[ $key ] )
182
  && is_array( $this->args[ $key ] ) ) {
183
  $values = array_merge( $values, $this->args[ $key ] );
@@ -186,12 +238,22 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
186
  return $values;
187
  }
188
 
 
 
 
 
 
189
  private function get_blacklisted_protocols() {
190
  return $this->merge_defaults_with_args( 'add_blacklisted_protocols', array(
191
  'javascript',
192
  ) );
193
  }
194
 
 
 
 
 
 
195
  private function get_blacklisted_tags() {
196
  return $this->merge_defaults_with_args( 'add_blacklisted_tags', array(
197
  'script',
@@ -215,17 +277,16 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
215
  'embed',
216
  'embedvideo',
217
 
218
- // Other weird ones
219
  'comments-count',
220
-
221
- // These are converted into amp-* versions
222
- //'img',
223
- //'video',
224
- //'audio',
225
- //'iframe',
226
  ) );
227
  }
228
 
 
 
 
 
 
229
  private function get_blacklisted_attributes() {
230
  return $this->merge_defaults_with_args( 'add_blacklisted_attributes', array(
231
  'style',
1
  <?php
2
+ /**
3
+ * Class AMP_Blacklist_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
  * Strips blacklisted tags and attributes from content.
11
  * See following for blacklist:
12
  * https://github.com/ampproject/amphtml/blob/master/spec/amp-html-format.md#html-tags
13
  *
14
+ * @since 0.5 This has been replaced by AMP_Tag_And_Attribute_Sanitizer but is kept around for back-compat.
15
+ * @deprecated
16
  */
17
  class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
18
  const PATTERN_REL_WP_ATTACHMENT = '#wp-att-([\d]+)#';
19
 
20
+ /**
21
+ * Default args.
22
+ *
23
+ * @var array
24
+ */
25
  protected $DEFAULT_ARGS = array(
26
  'add_blacklisted_protocols' => array(),
27
  'add_blacklisted_tags' => array(),
28
  'add_blacklisted_attributes' => array(),
29
  );
30
 
31
+ /**
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
  }
43
 
44
+ /**
45
+ * Strip attributes recursively.
46
+ *
47
+ * @param DOMNode $node DOM Node.
48
+ * @param array $bad_attributes Bad attributes.
49
+ * @param array $bad_protocols Bad protocols.
50
+ */
51
  private function strip_attributes_recursive( $node, $bad_attributes, $bad_protocols ) {
52
  if ( XML_ELEMENT_NODE !== $node->nodeType ) {
53
  return;
68
  if ( $node->hasAttributes() ) {
69
  $length = $node->attributes->length;
70
  for ( $i = $length - 1; $i >= 0; $i-- ) {
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;
93
  }
94
  }
95
 
96
+ /**
97
+ * Strip tags.
98
+ *
99
+ * @param DOMElement $node Node.
100
+ * @param string[] $tag_names Tag names.
101
+ */
102
  private function strip_tags( $node, $tag_names ) {
103
  foreach ( $tag_names as $tag_name ) {
104
  $elements = $node->getElementsByTagName( $tag_name );
105
+ $length = $elements->length;
106
  if ( 0 === $length ) {
107
  continue;
108
  }
109
 
110
  for ( $i = $length - 1; $i >= 0; $i-- ) {
111
+ $element = $elements->item( $i );
112
  $parent_node = $element->parentNode;
113
  $parent_node->removeChild( $element );
114
 
119
  }
120
  }
121
 
122
+ /**
123
+ * Sanitize attribute.
124
+ *
125
+ * @param DOMElement $node Node.
126
+ * @param DOMAttr $attribute Attribute.
127
+ */
128
  private function sanitize_a_attribute( $node, $attribute ) {
129
  $attribute_name = strtolower( $attribute->name );
130
 
147
  // _new is not allowed; swap with _blank
148
  $node->setAttribute( $attribute_name, '_blank' );
149
  } else {
150
+ // Only _blank is allowed.
151
  $node->removeAttribute( $attribute_name );
152
  }
153
  }
154
  }
155
 
156
+ /**
157
+ * Validate node.
158
+ *
159
+ * @param DOMElement $node Node.
160
+ * @return bool
161
+ */
162
  private function validate_a_node( $node ) {
163
+ // Get the href attribute.
164
  $href = $node->getAttribute( 'href' );
165
 
166
  if ( empty( $href ) ) {
167
+ /*
168
+ * If no href, check that a is an anchor or not.
169
+ * We don't need to validate anchors any further.
170
+ */
171
  return $node->hasAttribute( 'name' ) || $node->hasAttribute( 'id' );
172
  }
173
 
174
+ // If this is an anchor link, just return true.
175
  if ( 0 === strpos( $href, '#' ) ) {
176
  return true;
177
  }
181
  $href = untrailingslashit( get_home_url() ) . $href;
182
  }
183
 
184
+ $valid_protocols = array( 'http', 'https', 'mailto', 'sms', 'tel', 'viber', 'whatsapp' );
185
+ $special_protocols = array( 'tel', 'sms' ); // These ones don't valid with `filter_var+FILTER_VALIDATE_URL`.
186
+ $protocol = strtok( $href, ':' );
187
 
188
  if ( false === filter_var( $href, FILTER_VALIDATE_URL )
189
  && ! in_array( $protocol, $special_protocols, true ) ) {
197
  return true;
198
  }
199
 
200
+ /**
201
+ * Replace node with children.
202
+ *
203
+ * @param DOMElement $node Node.
204
+ * @param array $bad_attributes Bad attributes.
205
+ * @param array $bad_protocols Bad protocols.
206
+ */
207
  private function replace_node_with_children( $node, $bad_attributes, $bad_protocols ) {
208
  // If the node has children and also has a parent node,
209
  // clone and re-add all the children just before current node.
221
  }
222
  }
223
 
224
+ /**
225
+ * Merge defaults with args.
226
+ *
227
+ * @param string $key Key.
228
+ * @param array $values Values.
229
+ * @return array Merged args.
230
+ */
231
  private function merge_defaults_with_args( $key, $values ) {
232
+ // Merge default values with user specified args.
233
  if ( ! empty( $this->args[ $key ] )
234
  && is_array( $this->args[ $key ] ) ) {
235
  $values = array_merge( $values, $this->args[ $key ] );
238
  return $values;
239
  }
240
 
241
+ /**
242
+ * Get blacklisted protocols.
243
+ *
244
+ * @return array Protocols.
245
+ */
246
  private function get_blacklisted_protocols() {
247
  return $this->merge_defaults_with_args( 'add_blacklisted_protocols', array(
248
  'javascript',
249
  ) );
250
  }
251
 
252
+ /**
253
+ * Get blacklisted tags.
254
+ *
255
+ * @return array Tags.
256
+ */
257
  private function get_blacklisted_tags() {
258
  return $this->merge_defaults_with_args( 'add_blacklisted_tags', array(
259
  'script',
277
  'embed',
278
  'embedvideo',
279
 
280
+ // Other weird ones.
281
  'comments-count',
 
 
 
 
 
 
282
  ) );
283
  }
284
 
285
+ /**
286
+ * Get blacklisted attributes.
287
+ *
288
+ * @return array Attributes.
289
+ */
290
  private function get_blacklisted_attributes() {
291
  return $this->merge_defaults_with_args( 'add_blacklisted_attributes', array(
292
  'style',
includes/sanitizers/class-amp-iframe-sanitizer.php CHANGED
@@ -1,50 +1,114 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
 
 
 
4
 
5
  /**
 
 
6
  * Converts <iframe> tags to <amp-iframe>
7
  */
8
  class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
 
 
 
 
 
 
 
 
9
  const FALLBACK_HEIGHT = 400;
 
 
 
 
 
 
 
 
10
  const SANDBOX_DEFAULTS = 'allow-scripts allow-same-origin';
11
 
 
 
 
 
 
 
 
12
  public static $tag = 'iframe';
13
 
 
 
 
 
 
 
 
14
  private static $script_slug = 'amp-iframe';
 
 
 
 
 
 
 
 
15
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-iframe-0.1.js';
16
 
 
 
 
 
 
17
  protected $DEFAULT_ARGS = array(
18
  'add_placeholder' => false,
19
  );
20
 
 
 
 
 
 
 
 
 
 
 
 
21
  public function get_scripts() {
22
  if ( ! $this->did_convert_elements ) {
23
  return array();
24
  }
25
-
26
  return array( self::$script_slug => self::$script_src );
27
  }
28
 
 
 
 
 
 
29
  public function sanitize() {
30
- $nodes = $this->dom->getElementsByTagName( self::$tag );
31
  $num_nodes = $nodes->length;
32
  if ( 0 === $num_nodes ) {
33
  return;
34
  }
35
 
36
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
37
- $node = $nodes->item( $i );
38
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
39
 
40
  $new_attributes = $this->filter_attributes( $old_attributes );
41
 
42
- // If the src doesn't exist, remove the node.
43
- // This means that it never existed or was invalidated
44
- // while filtering attributes above.
45
- //
46
- // TODO: add a filter to allow for a fallback element in this instance.
47
- // See: https://github.com/ampproject/amphtml/issues/2261
 
48
  if ( empty( $new_attributes['src'] ) ) {
49
  $node->parentNode->removeChild( $node );
50
  continue;
@@ -63,20 +127,40 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
63
  }
64
 
65
  $parent_node = $node->parentNode;
66
- if ( 'p' === strtolower( $parent_node->tagName ) ) {
67
- // AMP does not like iframes in p tags
 
 
68
  $parent_node->removeChild( $node );
69
  $parent_node->parentNode->insertBefore( $new_node, $parent_node->nextSibling );
70
 
71
  if ( AMP_DOM_Utils::is_node_empty( $parent_node ) ) {
72
  $parent_node->parentNode->removeChild( $parent_node );
73
  }
74
- } else {
75
- $parent_node->replaceChild( $new_node, $node );
76
  }
77
  }
78
  }
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  private function filter_attributes( $attributes ) {
81
  $out = array();
82
 
@@ -111,7 +195,7 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
111
  }
112
  break;
113
 
114
- default;
115
  break;
116
  }
117
  }
@@ -123,6 +207,19 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
123
  return $out;
124
  }
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  private function build_placeholder( $parent_attributes ) {
127
  $placeholder_node = AMP_DOM_Utils::create_node( $this->dom, 'div', array(
128
  'placeholder' => '',
1
  <?php
2
+ /**
3
+ * Class AMP_Iframe_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
+ * Class AMP_Iframe_Sanitizer
10
+ *
11
  * Converts <iframe> tags to <amp-iframe>
12
  */
13
  class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Value used for height attribute when $attributes['height'] is empty.
17
+ *
18
+ * @since 0.2
19
+ *
20
+ * @const int
21
+ */
22
  const FALLBACK_HEIGHT = 400;
23
+
24
+ /**
25
+ * Default values for sandboxing IFrame.
26
+ *
27
+ * @since 0.2
28
+ *
29
+ * @const int
30
+ */
31
  const SANDBOX_DEFAULTS = 'allow-scripts allow-same-origin';
32
 
33
+ /**
34
+ * Tag.
35
+ *
36
+ * @var string HTML <iframe> tag to identify and replace with AMP version.
37
+ *
38
+ * @since 0.2
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
+ *
63
+ * @var array
64
+ */
65
  protected $DEFAULT_ARGS = array(
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
+ *
90
+ * @since 0.2
91
+ */
92
  public function sanitize() {
93
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
94
  $num_nodes = $nodes->length;
95
  if ( 0 === $num_nodes ) {
96
  return;
97
  }
98
 
99
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
100
+ $node = $nodes->item( $i );
101
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
102
 
103
  $new_attributes = $this->filter_attributes( $old_attributes );
104
 
105
+ /**
106
+ * If the src doesn't exist, remove the node. Either it never
107
+ * existed or was invalidated while filtering attributes above.
108
+ *
109
+ * @todo: add a filter to allow for a fallback element in this instance.
110
+ * @see: https://github.com/ampproject/amphtml/issues/2261
111
+ */
112
  if ( empty( $new_attributes['src'] ) ) {
113
  $node->parentNode->removeChild( $node );
114
  continue;
127
  }
128
 
129
  $parent_node = $node->parentNode;
130
+ if ( 'p' !== strtolower( $parent_node->tagName ) ) {
131
+ $parent_node->replaceChild( $new_node, $node );
132
+ } else {
133
+ // AMP does not like iframes in <p> tags.
134
  $parent_node->removeChild( $node );
135
  $parent_node->parentNode->insertBefore( $new_node, $parent_node->nextSibling );
136
 
137
  if ( AMP_DOM_Utils::is_node_empty( $parent_node ) ) {
138
  $parent_node->parentNode->removeChild( $parent_node );
139
  }
 
 
140
  }
141
  }
142
  }
143
 
144
+ /**
145
+ * "Filter" HTML attributes for <amp-iframe> elements.
146
+ *
147
+ * @since 0.2
148
+ *
149
+ * @param string[] $attributes {
150
+ * Attributes.
151
+ *
152
+ * @type string $src IFrame URL - Empty if HTTPS required per $this->args['require_https_src']
153
+ * @type int $width <iframe> width attribute - Set to numeric value if px or %
154
+ * @type int $height <iframe> width attribute - Set to numeric value if px or %
155
+ * @type string $sandbox <iframe> `sandbox` attribute - Pass along if found; default to value of self::SANDBOX_DEFAULTS
156
+ * @type string $class <iframe> `class` attribute - Pass along if found
157
+ * @type string $sizes <iframe> `sizes` attribute - Pass along if found
158
+ * @type int $frameborder <iframe> `frameborder` attribute - Filter to '0' or '1'; default to '0'
159
+ * @type bool $allowfullscreen <iframe> `allowfullscreen` attribute - Convert 'false' to empty string ''
160
+ * @type bool $allowtransparency <iframe> `allowtransparency` attribute - Convert 'false' to empty string ''
161
+ * }
162
+ * @return array Returns HTML attributes; removes any not specifically declared above from input.
163
+ */
164
  private function filter_attributes( $attributes ) {
165
  $out = array();
166
 
195
  }
196
  break;
197
 
198
+ default:
199
  break;
200
  }
201
  }
207
  return $out;
208
  }
209
 
210
+ /**
211
+ * Builds a DOMElement to use as a placeholder for an <iframe>.
212
+ *
213
+ * @since 0.2
214
+ *
215
+ * @param string[] $parent_attributes {
216
+ * Attributes.
217
+ *
218
+ * @type string $placeholder AMP HTML <amp-iframe> `placeholder` attribute; default to 'amp-wp-iframe-placeholder'
219
+ * @type string $class AMP HTML <amp-iframe> `class` attribute; default to 'amp-wp-iframe-placeholder'
220
+ * }
221
+ * @return DOMElement|false
222
+ */
223
  private function build_placeholder( $parent_attributes ) {
224
  $placeholder_node = AMP_DOM_Utils::create_node( $this->dom, 'div', array(
225
  'placeholder' => '',
includes/sanitizers/class-amp-img-sanitizer.php CHANGED
@@ -1,25 +1,101 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
4
- require_once( AMP__DIR__ . '/includes/utils/class-amp-image-dimension-extractor.php' );
 
 
5
 
6
  /**
 
 
7
  * Converts <img> tags to <amp-img> or <amp-anim>
8
  */
9
  class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
 
 
 
 
 
 
 
 
10
  const FALLBACK_WIDTH = 600;
 
 
 
 
 
 
 
 
11
  const FALLBACK_HEIGHT = 400;
12
 
 
 
 
 
 
 
 
13
  public static $tag = 'img';
14
 
 
 
 
 
 
15
  private static $anim_extension = '.gif';
16
 
 
 
 
 
 
 
 
17
  private static $script_slug = 'amp-anim';
 
 
 
 
 
 
 
 
18
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-anim-0.1.js';
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  public function sanitize() {
21
 
22
- $nodes = $this->dom->getElementsByTagName( self::$tag );
 
 
 
 
 
23
  $need_dimensions = array();
24
 
25
  $num_nodes = $nodes->length;
@@ -30,6 +106,9 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
30
 
31
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
32
  $node = $nodes->item( $i );
 
 
 
33
 
34
  if ( ! $node->hasAttribute( 'src' ) || '' === $node->getAttribute( 'src' ) ) {
35
  $node->parentNode->removeChild( $node );
@@ -49,36 +128,103 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
49
  }
50
 
51
  /**
52
- * Figure out width and height attribute values for images that don't have them by
53
- * attempting to determine actual dimensions and setting reasonable defaults otherwise.
54
  *
55
- * @param array $need_dimensions List of Img src url to node mappings corresponding to images that need dimensions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  */
57
  private function determine_dimensions( $need_dimensions ) {
 
58
  $dimensions_by_url = AMP_Image_Dimension_Extractor::extract( array_keys( $need_dimensions ) );
59
 
60
  foreach ( $dimensions_by_url as $url => $dimensions ) {
61
  foreach ( $need_dimensions[ $url ] as $node ) {
 
 
 
 
62
  // Provide default dimensions for images whose dimensions we couldn't fetch.
63
- if ( false === $dimensions ) {
64
- $width = isset( $this->args['content_max_width'] ) ? $this->args['content_max_width'] : self::FALLBACK_WIDTH;
 
 
 
65
  $height = self::FALLBACK_HEIGHT;
66
  $node->setAttribute( 'width', $width );
67
  $node->setAttribute( 'height', $height );
68
  $class = $node->hasAttribute( 'class' ) ? $node->getAttribute( 'class' ) . ' amp-wp-unknown-size' : 'amp-wp-unknown-size';
69
  $node->setAttribute( 'class', $class );
70
- } else {
71
- $node->setAttribute( 'width', $dimensions['width'] );
72
- $node->setAttribute( 'height', $dimensions['height'] );
73
  }
74
  }
75
  }
76
  }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  /**
79
  * Make final modifications to DOMNode
80
  *
81
- * @param DOMNode $node The DOMNode to adjust and replace
82
  */
83
  private function adjust_and_replace_node( $node ) {
84
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
@@ -86,6 +232,7 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
86
  $new_attributes = $this->enforce_sizes_attribute( $new_attributes );
87
  if ( $this->is_gif_url( $new_attributes['src'] ) ) {
88
  $this->did_convert_elements = true;
 
89
  $new_tag = 'amp-anim';
90
  } else {
91
  $new_tag = 'amp-img';
@@ -95,55 +242,16 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
95
  }
96
 
97
  /**
98
- * Now that all images have width and height attributes, make final tweaks and replace original image nodes
 
 
 
 
99
  *
100
- * @param array $node_lists Img DOM nodes (now with width and height attributes).
101
  */
102
- private function adjust_and_replace_nodes_in_array_map( $node_lists ) {
103
- foreach ( $node_lists as $node_list ) {
104
- foreach ( $node_list as $node ) {
105
- $this->adjust_and_replace_node( $node );
106
- }
107
- }
108
- }
109
-
110
- public function get_scripts() {
111
- if ( ! $this->did_convert_elements ) {
112
- return array();
113
- }
114
-
115
- return array( self::$script_slug => self::$script_src );
116
- }
117
-
118
- private function filter_attributes( $attributes ) {
119
- $out = array();
120
-
121
- foreach ( $attributes as $name => $value ) {
122
- switch ( $name ) {
123
- case 'src':
124
- case 'alt':
125
- case 'class':
126
- case 'srcset':
127
- case 'sizes':
128
- case 'on':
129
- $out[ $name ] = $value;
130
- break;
131
-
132
- case 'width':
133
- case 'height':
134
- $out[ $name ] = $this->sanitize_dimension( $value, $name );
135
- break;
136
-
137
- default;
138
- break;
139
- }
140
- }
141
-
142
- return $out;
143
- }
144
-
145
  private function is_gif_url( $url ) {
146
- $ext = self::$anim_extension;
147
  $path = AMP_WP_Utils::parse_url( $url, PHP_URL_PATH );
148
  return substr( $path, -strlen( $ext ) ) === $ext;
149
  }
1
  <?php
2
+ /**
3
+ * Class AMP_Img_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
+ * Class AMP_Img_Sanitizer
10
+ *
11
  * Converts <img> tags to <amp-img> or <amp-anim>
12
  */
13
  class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
14
+
15
+ /**
16
+ * Value used for width attribute when $attributes['width'] is empty.
17
+ *
18
+ * @since 0.2
19
+ *
20
+ * @const int
21
+ */
22
  const FALLBACK_WIDTH = 600;
23
+
24
+ /**
25
+ * Value used for height attribute when $attributes['height'] is empty.
26
+ *
27
+ * @since 0.2
28
+ *
29
+ * @const int
30
+ */
31
  const FALLBACK_HEIGHT = 400;
32
 
33
+ /**
34
+ * Tag.
35
+ *
36
+ * @var string HTML <img> tag to identify and replace with AMP version.
37
+ *
38
+ * @since 0.2
39
+ */
40
  public static $tag = 'img';
41
 
42
+ /**
43
+ * Animation extension.
44
+ *
45
+ * @var string
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
+ *
89
+ * @since 0.2
90
+ */
91
  public function sanitize() {
92
 
93
+ /**
94
+ * Node list.
95
+ *
96
+ * @var DOMNodeList $node
97
+ */
98
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
99
  $need_dimensions = array();
100
 
101
  $num_nodes = $nodes->length;
106
 
107
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
108
  $node = $nodes->item( $i );
109
+ if ( ! $node instanceof DOMElement ) {
110
+ continue;
111
+ }
112
 
113
  if ( ! $node->hasAttribute( 'src' ) || '' === $node->getAttribute( 'src' ) ) {
114
  $node->parentNode->removeChild( $node );
128
  }
129
 
130
  /**
131
+ * "Filter" HTML attributes for <amp-anim> elements.
 
132
  *
133
+ * @since 0.2
134
+ *
135
+ * @param string[] $attributes {
136
+ * Attributes.
137
+ *
138
+ * @type string $src Image URL - Pass along if found
139
+ * @type string $alt <img> `alt` attribute - Pass along if found
140
+ * @type string $class <img> `class` attribute - Pass along if found
141
+ * @type string $srcset <img> `srcset` attribute - Pass along if found
142
+ * @type string $sizes <img> `sizes` attribute - Pass along if found
143
+ * @type string $on <img> `on` attribute - Pass along if found
144
+ * @type string $attribution <img> `attribution` attribute - Pass along if found
145
+ * @type int $width <img> width attribute - Set to numeric value if px or %
146
+ * @type int $height <img> width attribute - Set to numeric value if px or %
147
+ * }
148
+ * @return array Returns HTML attributes; removes any not specifically declared above from input.
149
+ */
150
+ private function filter_attributes( $attributes ) {
151
+ $out = array();
152
+
153
+ foreach ( $attributes as $name => $value ) {
154
+ switch ( $name ) {
155
+ case 'src':
156
+ case 'alt':
157
+ case 'class':
158
+ case 'srcset':
159
+ case 'sizes':
160
+ case 'on':
161
+ case 'attribution':
162
+ $out[ $name ] = $value;
163
+ break;
164
+
165
+ case 'width':
166
+ case 'height':
167
+ $out[ $name ] = $this->sanitize_dimension( $value, $name );
168
+ break;
169
+
170
+ default:
171
+ break;
172
+ }
173
+ }
174
+
175
+ return $out;
176
+ }
177
+
178
+ /**
179
+ * Determine width and height attribute values for images without them.
180
+ *
181
+ * Attempt to determine actual dimensions, otherwise set reasonable defaults.
182
+ *
183
+ * @param DOMElement[][] $need_dimensions Map <img> @src URLs to node for images with missing dimensions.
184
  */
185
  private function determine_dimensions( $need_dimensions ) {
186
+
187
  $dimensions_by_url = AMP_Image_Dimension_Extractor::extract( array_keys( $need_dimensions ) );
188
 
189
  foreach ( $dimensions_by_url as $url => $dimensions ) {
190
  foreach ( $need_dimensions[ $url ] as $node ) {
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
  }
209
  }
210
 
211
+ /**
212
+ * Now that all images have width and height attributes, make final tweaks and replace original image nodes
213
+ *
214
+ * @param DOMNodeList[] $node_lists Img DOM nodes (now with width and height attributes).
215
+ */
216
+ private function adjust_and_replace_nodes_in_array_map( $node_lists ) {
217
+ foreach ( $node_lists as $node_list ) {
218
+ foreach ( $node_list as $node ) {
219
+ $this->adjust_and_replace_node( $node );
220
+ }
221
+ }
222
+ }
223
+
224
  /**
225
  * Make final modifications to DOMNode
226
  *
227
+ * @param DOMNode $node The DOMNode to adjust and replace.
228
  */
229
  private function adjust_and_replace_node( $node ) {
230
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
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
+
236
  $new_tag = 'amp-anim';
237
  } else {
238
  $new_tag = 'amp-img';
242
  }
243
 
244
  /**
245
+ * Determines is a URL is considered a GIF URL
246
+ *
247
+ * @since 0.2
248
+ *
249
+ * @param string $url URL to inspect for GIF vs. JPEG or PNG.
250
  *
251
+ * @return bool Returns true if $url ends in `.gif`
252
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  private function is_gif_url( $url ) {
254
+ $ext = self::$anim_extension;
255
  $path = AMP_WP_Utils::parse_url( $url, PHP_URL_PATH );
256
  return substr( $path, -strlen( $ext ) ) === $ext;
257
  }
includes/sanitizers/class-amp-playbuzz-sanitizer.php CHANGED
@@ -1,46 +1,100 @@
1
  <?php
2
-
3
- require_once AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php';
 
 
 
4
 
5
  /**
 
 
6
  * Converts Playbuzz embed to <amp-playbuzz>
 
 
7
  */
8
  class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
9
 
10
-
 
 
 
 
 
11
  public static $tag = 'div';
 
 
 
 
 
 
 
 
12
  public static $pb_class = 'pb_feed';
 
 
 
 
 
 
 
 
13
  private static $script_slug = 'amp-playbuzz';
14
- private static $height = '500';
15
- private static $script_src = 'https://cdn.ampproject.org/v0/amp-playbuzz-0.1.js';
16
 
 
 
 
 
 
 
 
 
17
 
 
 
 
 
 
 
 
 
18
 
 
 
 
 
 
 
 
 
 
 
 
19
  public function get_scripts() {
20
  if ( ! $this->did_convert_elements ) {
21
  return array();
22
  }
23
- return array(
24
- self::$script_slug => self::$script_src,
25
- );
26
  }
27
 
28
 
 
 
 
 
 
29
  public function sanitize() {
30
 
31
- $nodes = $this->dom->getElementsByTagName( self::$tag );
32
  $num_nodes = $nodes->length;
33
 
34
  if ( 0 === $num_nodes ) {
35
-
36
  return;
37
-
38
  }
39
 
40
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
41
  $node = $nodes->item( $i );
42
 
43
- if ( self::$pb_class !== $node -> getAttribute( 'class' ) ) {
44
  continue;
45
  }
46
 
@@ -62,7 +116,23 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
62
 
63
  }
64
 
65
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  private function filter_attributes( $attributes ) {
67
  $out = array();
68
 
@@ -72,28 +142,28 @@ class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
72
  if ( ! empty( $value ) ) {
73
  $out['data-item'] = $value;
74
  }
75
- break;
76
 
77
  case 'data-game':
78
  if ( ! empty( $value ) ) {
79
  $out['src'] = $value;
80
  }
81
- break;
82
 
83
  case 'data-game-info':
84
  $out['data-item-info'] = $value;
85
- break;
86
 
87
  case 'data-shares':
88
  $out['data-share-buttons'] = $value;
89
- break;
90
 
91
  case 'data-comments':
92
  $out['data-comments'] = $value;
93
- break;
94
 
95
- default;
96
- break;
97
  }
98
  }
99
 
1
  <?php
2
+ /**
3
+ * Class AMP_Playbuzz_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
+ * Class AMP_Playbuzz_Sanitizer
10
+ *
11
  * Converts Playbuzz embed to <amp-playbuzz>
12
+ *
13
+ * @see https://www.playbuzz.com/
14
  */
15
  class AMP_Playbuzz_Sanitizer extends AMP_Base_Sanitizer {
16
 
17
+ /**
18
+ * Tag.
19
+ *
20
+ * @var string HTML tag to identify and replace with AMP version.
21
+ * @since 0.2
22
+ */
23
  public static $tag = 'div';
24
+
25
+ /**
26
+ * PlayBuzz class.
27
+ *
28
+ * @var string CSS class to identify Playbuzz <div> to replace with AMP version.
29
+ *
30
+ * @since 0.2
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
+ *
55
+ * @var string
56
+ *
57
+ * @since 0.2
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
+ *
83
+ * @since 0.2
84
+ */
85
  public function sanitize() {
86
 
87
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
88
  $num_nodes = $nodes->length;
89
 
90
  if ( 0 === $num_nodes ) {
 
91
  return;
 
92
  }
93
 
94
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
95
  $node = $nodes->item( $i );
96
 
97
+ if ( self::$pb_class !== $node->getAttribute( 'class' ) ) {
98
  continue;
99
  }
100
 
116
 
117
  }
118
 
119
+ /**
120
+ * "Filter" HTML attributes for <amp-audio> elements.
121
+ *
122
+ * @since 0.2
123
+ *
124
+ * @param string[] $attributes {
125
+ * Attributes.
126
+ *
127
+ * @type string $data-item Playbuzz <div> attribute - Pass along if found and not empty.
128
+ * @type string $data-game Playbuzz <div> attribute - Assign to its value to $attributes['src'] if found and not empty.
129
+ * @type string $data-game-info Playbuzz <div> attribute - Assign to its value to $attributes['data-item-info'] if found.
130
+ * @type string $data-shares Playbuzz <div> attribute - Assign to its value to $attributes['data-share-buttons'] if found.
131
+ * @type string $data-comments Playbuzz <div> attribute - Pass along if found.
132
+ * @type int $height Playbuzz <div> attribute - Set to hardcoded value of 500.
133
+ * }
134
+ * @return array Returns HTML attributes; removes any not specifically declared above from input.
135
+ */
136
  private function filter_attributes( $attributes ) {
137
  $out = array();
138
 
142
  if ( ! empty( $value ) ) {
143
  $out['data-item'] = $value;
144
  }
145
+ break;
146
 
147
  case 'data-game':
148
  if ( ! empty( $value ) ) {
149
  $out['src'] = $value;
150
  }
151
+ break;
152
 
153
  case 'data-game-info':
154
  $out['data-item-info'] = $value;
155
+ break;
156
 
157
  case 'data-shares':
158
  $out['data-share-buttons'] = $value;
159
+ break;
160
 
161
  case 'data-comments':
162
  $out['data-comments'] = $value;
163
+ break;
164
 
165
+ default:
166
+ break;
167
  }
168
  }
169
 
includes/sanitizers/class-amp-rule-spec.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Rule_Spec
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Rule_Spec
10
+ *
11
+ * Set of constants used throughout the sanitizer.
12
+ */
13
+ abstract class AMP_Rule_Spec {
14
+
15
+ /**
16
+ * AMP rule_spec types
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
30
+ */
31
+ const DISALLOWED_ANCESTOR = 'disallowed_ancestor';
32
+ const MANDATORY_ANCESTOR = 'mandatory_ancestor';
33
+ const MANDATORY_PARENT = 'mandatory_parent';
34
+
35
+ /**
36
+ * HTML Element Attribute rule names
37
+ */
38
+ const ALLOW_EMPTY = 'allow_empty';
39
+ const ALLOW_RELATIVE = 'allow_relative';
40
+ const ALLOWED_PROTOCOL = 'allowed_protocol';
41
+ const ALTERNATIVE_NAMES = 'alternative_names';
42
+ const BLACKLISTED_VALUE_REGEX = 'blacklisted_value_regex';
43
+ const DISALLOWED_DOMAIN = 'disallowed_domain';
44
+ const MANDATORY = 'mandatory';
45
+ const VALUE = 'value';
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
52
+ * removed if it is invalid. This is mainly because any children will be
53
+ * non-functional without this parent.
54
+ *
55
+ * If a tag is not listed here, it will be replaced by its children if it
56
+ * is invalid.
57
+ *
58
+ * @todo There are other nodes that should probably be listed here as well.
59
+ *
60
+ * @var array
61
+ */
62
+ public static $node_types_to_remove_if_invalid = array(
63
+ 'form',
64
+ 'input',
65
+ 'link',
66
+ 'meta',
67
+ 'style',
68
+ // Include 'script' here?
69
+ );
70
+
71
+ /**
72
+ * It is mentioned in the documentation in several places that data-*
73
+ * is generally allowed, but there is no specific rule for it in the
74
+ * protoascii file, so we include it here.
75
+ *
76
+ * @var array
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
+ /**
84
+ * Additional allowed tags.
85
+ *
86
+ * @var array
87
+ */
88
+ public static $additional_allowed_tags = array(
89
+
90
+ // An experimental tag with no protoascii.
91
+ 'amp-share-tracking' => array(
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
+ }
includes/sanitizers/class-amp-style-sanitizer.php CHANGED
@@ -1,22 +1,64 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
 
 
 
4
 
5
  /**
 
 
6
  * Collects inline styles and outputs them in the amp-custom stylesheet.
7
  */
8
  class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
 
 
 
 
 
 
 
 
9
  private $styles = array();
10
 
 
 
 
 
 
 
 
11
  public function get_styles() {
 
 
 
12
  return $this->styles;
13
  }
14
 
 
 
 
 
 
15
  public function sanitize() {
16
  $body = $this->get_body_node();
17
  $this->collect_styles_recursive( $body );
 
18
  }
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  private function collect_styles_recursive( $node ) {
21
  if ( XML_ELEMENT_NODE !== $node->nodeType ) {
22
  return;
@@ -35,7 +77,6 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
35
  $node->setAttribute( 'class', $new_class );
36
  $this->styles[ '.' . $class_name ] = $style;
37
  }
38
-
39
  $node->removeAttribute( 'style' );
40
  }
41
  }
@@ -43,29 +84,42 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
43
  $length = $node->childNodes->length;
44
  for ( $i = $length - 1; $i >= 0; $i -- ) {
45
  $child_node = $node->childNodes->item( $i );
46
-
47
  $this->collect_styles_recursive( $child_node );
48
  }
49
  }
50
 
 
 
 
 
 
 
 
 
51
  private function process_style( $string ) {
52
- // Filter properties
 
 
 
53
  $string = safecss_filter_attr( esc_html( $string ) );
54
 
55
  if ( ! $string ) {
56
  return array();
57
  }
58
 
59
- // safecss returns a string but we want individual rules.
60
- // Using preg_split to break up rules by `;` but only if the semi-colon is not inside parens (like a data-encoded image).
61
- $styles = array_map( 'trim', preg_split( "/;(?![^(]*\))/", $string ) );
 
 
 
62
 
63
- // Normalize the order of the styles
64
  sort( $styles );
65
 
66
  $processed_styles = array();
67
 
68
- // Normalize whitespace and filter rules
69
  foreach ( $styles as $index => $rule ) {
70
  $arr2 = array_map( 'trim', explode( ':', $rule, 2 ) );
71
  if ( 2 !== count( $arr2 ) ) {
@@ -77,26 +131,43 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
77
  continue;
78
  }
79
 
80
- $processed_styles[ $index ] = $property . ':' . $value;
81
  }
82
 
83
  return $processed_styles;
84
  }
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  private function filter_style( $property, $value ) {
87
- // Handle overflow rule
88
- // https://www.ampproject.org/docs/reference/spec.html#properties
89
- if ( 0 === strpos( $property, 'overflow' )
90
- && ( false !== strpos( $value, 'auto' ) || false !== strpos( $value, 'scroll' ) )
91
- ) {
92
- return false;
 
 
93
  }
94
 
95
  if ( 'width' === $property ) {
96
  $property = 'max-width';
97
  }
98
 
99
- // !important is not allowed
 
 
100
  if ( false !== strpos( $value, 'important' ) ) {
101
  $value = preg_replace( '/\s*\!\s*important$/', '', $value );
102
  }
@@ -104,6 +175,16 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
104
  return array( $property, $value );
105
  }
106
 
 
 
 
 
 
 
 
 
 
 
107
  private function generate_class_name( $data ) {
108
  $string = maybe_serialize( $data );
109
  return 'amp-wp-inline-' . md5( $string );
1
  <?php
2
+ /**
3
+ * Class AMP_Style_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
+ * Class AMP_Style_Sanitizer
10
+ *
11
  * Collects inline styles and outputs them in the amp-custom stylesheet.
12
  */
13
  class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
14
+
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 ) {
33
+ return array();
34
+ }
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;
77
  $node->setAttribute( 'class', $new_class );
78
  $this->styles[ '.' . $class_name ] = $style;
79
  }
 
80
  $node->removeAttribute( 'style' );
81
  }
82
  }
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
+ /**
92
+ * Sanitize and convert individual styles.
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 );
119
 
120
  $processed_styles = array();
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 ) ) {
131
  continue;
132
  }
133
 
134
+ $processed_styles[ $index ] = "{$property}:{$value}";
135
  }
136
 
137
  return $processed_styles;
138
  }
139
 
140
+ /**
141
+ * Filter individual CSS name/value pairs.
142
+ *
143
+ * - Remove overflow if value is `auto` or `scroll`
144
+ * - Change `width` to `max-width`
145
+ * - Remove !important
146
+ *
147
+ * @since 0.4
148
+ *
149
+ * @param string $property Property.
150
+ * @param string $value Value.
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
 
164
  if ( 'width' === $property ) {
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 );
173
  }
175
  return array( $property, $value );
176
  }
177
 
178
+ /**
179
+ * Generate a unique class name
180
+ *
181
+ * Use the md5() of the $data parameter
182
+ *
183
+ * @since 0.4
184
+ *
185
+ * @param string $data Data.
186
+ * @return string Class name.
187
+ */
188
  private function generate_class_name( $data ) {
189
  $string = maybe_serialize( $data );
190
  return 'amp-wp-inline-' . md5( $string );
includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php CHANGED
@@ -1,33 +1,84 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
4
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-allowed-tags-generated.php' );
 
 
5
 
6
  /**
7
- * Strips tags and attributes not allowed by the AMP sped from the content.
8
  *
9
  * Allowed tags array is generated from this protocol buffer:
 
10
  * https://github.com/ampproject/amphtml/blob/master/validator/validator-main.protoascii
11
  * by the python script in amp-wp/bin/amp_wp_build.py. See the comment at the top
12
  * of that file for instructions to generate class-amp-allowed-tags-generated.php.
13
  *
14
- * TODO: AMP Spec items not checked by this sanitizer -
15
- * - `if_value_regex` - if one attribute value matches, this places a restriction
16
- * on another attribute/value.
17
- * - `also_requires_attr` - if one attribute is present, this requires another.
18
- * - `mandatory_oneof` - Within the context of the tag, exactly one of the attributes
19
- * must be present.
20
- * - `CdataSpec` - CDATA is not validated or sanitized.
21
- * - `ChildTagSpec` - Places restrictions on the number and type of child tags.
 
22
  */
23
  class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
 
 
 
 
 
 
 
 
24
  protected $allowed_tags;
 
 
 
 
 
 
 
 
25
  protected $globally_allowed_attributes;
 
 
 
 
 
 
 
 
26
  protected $layout_allowed_attributes;
 
 
 
 
 
 
 
 
27
  private $stack = array();
28
 
 
 
 
 
 
 
 
29
  protected $DEFAULT_ARGS = array();
30
 
 
 
 
 
 
 
 
 
31
  public function __construct( $dom, $args = array() ) {
32
  $this->DEFAULT_ARGS = array(
33
  'amp_allowed_tags' => AMP_Allowed_Tags_Generated::get_allowed_tags(),
@@ -37,66 +88,94 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
37
 
38
  parent::__construct( $dom, $args );
39
 
40
- // Prepare whitelists
41
- $this->allowed_tags = $this->args['amp_allowed_tags'];
 
 
42
  $this->globally_allowed_attributes = $this->args['amp_globally_allowed_attributes'];
43
- $this->layout_allowed_attributes = $this->args['amp_layout_allowed_attributes'];
44
  }
45
 
 
 
 
 
 
46
  public function sanitize() {
47
- foreach( AMP_Rule_Spec::$additional_allowed_tags as $tag_name => $tag_rule_spec ) {
 
48
  $this->allowed_tags[ $tag_name ][] = $tag_rule_spec;
49
  }
50
 
51
- // Add root of content to the stack
 
 
 
 
52
  $body = $this->get_body_node();
 
53
  $this->stack[] = $body;
54
 
55
- // This loop traverses through the DOM tree iteratively.
56
- while ( 0 < count( $this->stack ) ) {
 
 
57
 
58
  // Get the next node to process.
59
  $node = array_pop( $this->stack );
60
 
61
- // Process this node.
 
 
62
  $this->process_node( $node );
63
 
64
- // Push child nodes onto the stack, if any exist.
65
- // Note: if the node was removed, then it's parentNode value is null.
 
 
66
  if ( $node->parentNode ) {
67
  $child = $node->firstChild;
68
  while ( $child ) {
69
  $this->stack[] = $child;
70
- $child = $child->nextSibling;
71
  }
72
  }
73
  }
74
  }
75
 
 
 
 
 
 
76
  private function process_node( $node ) {
77
- // Don't process text or comment nodes
78
- if ( ( XML_TEXT_NODE == $node->nodeType ) ||
79
- ( XML_COMMENT_NODE == $node->nodeType ) ||
80
- ( XML_CDATA_SECTION_NODE == $node->nodeType ) ) {
81
  return;
82
  }
83
 
84
- // Remove nodes with non-whitelisted tags.
85
  if ( ! $this->is_amp_allowed_tag( $node ) ) {
86
- // If it's not an allowed tag, replace the node with it's children
 
87
  $this->replace_node_with_children( $node );
 
88
  // Return early since this node no longer exists.
89
  return;
90
  }
91
 
92
- // Compile a list of rule_specs to validate for this node based on
93
- // tag name of the node.
 
 
94
  $rule_spec_list_to_validate = array();
 
95
  if ( isset( $this->allowed_tags[ $node->nodeName ] ) ) {
96
  $rule_spec_list = $this->allowed_tags[ $node->nodeName ];
97
  }
98
  foreach ( $rule_spec_list as $id => $rule_spec ) {
99
- if ( $this->validate_tag_spec_for_node( $node, $rule_spec[AMP_Rule_Spec::TAG_SPEC] ) ) {
100
  $rule_spec_list_to_validate[ $id ] = $rule_spec;
101
  }
102
  }
@@ -110,52 +189,56 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
110
  // The remaining validations all have to do with attributes.
111
  $attr_spec_list = array();
112
 
113
- // If we have exactly one rule_spec, use it's attr_spec_list to
114
- // validate the node's attributes.
115
- if ( 1 == count( $rule_spec_list_to_validate ) ) {
116
- $rule_spec = array_pop( $rule_spec_list_to_validate );
117
- $attr_spec_list = $rule_spec[AMP_Rule_Spec::ATTR_SPEC_LIST];
118
- } else {
119
- // If there is more than one valid rule_spec for this node, then
120
- // try to deduce which one is intended by inspecting the node's
121
- // attributes.
122
 
123
- // Get a score from each attr_spec_list by seeing how many
124
- // attributes and values match the node.
 
 
 
 
 
 
 
 
 
125
  $attr_spec_scores = array();
126
  foreach ( $rule_spec_list_to_validate as $spec_id => $rule_spec ) {
127
- $attr_spec_scores[ $spec_id ] = $this->validate_attr_spec_list_for_node( $node, $rule_spec[AMP_Rule_Spec::ATTR_SPEC_LIST] );
128
  }
129
 
130
  // Get the key(s) to the highest score(s).
131
- $spec_ids_sorted = array_keys( $attr_spec_scores, max( $attr_spec_scores ) );
132
 
133
  // If there is exactly one attr_spec with a max score, use that one.
134
- if ( 1 == count( $spec_ids_sorted ) ) {
135
- $attr_spec_list = $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][AMP_Rule_Spec::ATTR_SPEC_LIST];
136
- // Otherwise...
137
  } else {
138
  // This should not happen very often, but...
139
  // If we're here, then we're not sure which spec should
140
  // be used. Let's use the top scoring ones.
141
- foreach( $spec_ids_sorted as $id ) {
142
- $attr_spec_list = array_merge( $attr_spec_list, $rule_spec_list_to_validate[ $id ][AMP_Rule_Spec::ATTR_SPEC_LIST] );
 
 
 
 
 
 
 
143
  }
144
  }
145
- }
146
-
147
- // If an attribute is mandatory and we don't have it, just remove the node and move on.
148
- foreach ( $attr_spec_list as $attr_name => $attr_spec_rule_value ) {
149
- $is_mandatory =
150
- isset( $attr_spec_rule_value[ AMP_Rule_Spec::MANDATORY ] )
151
- ? (bool) $attr_spec_rule_value[ AMP_Rule_Spec::MANDATORY ]
152
- : false;
153
- $attribute_exists = $node->hasAttribute( $attr_name );
154
 
155
- if ( $is_mandatory && ! $attribute_exists ) {
156
- $this->remove_node( $node );
157
- return;
158
- }
159
  }
160
 
161
  // Remove any remaining disallowed attributes.
@@ -166,30 +249,54 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
166
  }
167
 
168
  /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  * Checks to see if a node's placement with the DOM is be valid for the
170
  * given tag_spec. If there are restrictions placed on the type of node
171
  * that can be an immediate parent or an ancestor of this node, then make
172
  * sure those restrictions are met.
173
  *
174
- * If any of the tests on the restrictions fail, return false, otherwise
175
- * return true.
 
 
 
176
  */
177
  private function validate_tag_spec_for_node( $node, $tag_spec ) {
178
- if ( ! empty( $tag_spec[AMP_Rule_Spec::MANDATORY_PARENT] ) &&
179
- ! $this->has_parent( $node, $tag_spec[AMP_Rule_Spec::MANDATORY_PARENT] ) ) {
180
  return false;
181
  }
182
 
183
- if ( ! empty( $tag_spec[AMP_Rule_Spec::DISALLOWED_ANCESTOR] ) ) {
184
- foreach ( $tag_spec[AMP_Rule_Spec::DISALLOWED_ANCESTOR] as $disallowed_ancestor_node_name ) {
185
  if ( $this->has_ancestor( $node, $disallowed_ancestor_node_name ) ) {
186
  return false;
187
  }
188
  }
189
  }
190
 
191
- if ( ! empty( $tag_spec[AMP_Rule_Spec::MANDATORY_ANCESTOR] ) &&
192
- ! $this->has_ancestor( $node, $tag_spec[AMP_Rule_Spec::MANDATORY_ANCESTOR] ) ) {
193
  return false;
194
  }
195
 
@@ -197,144 +304,196 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
197
  }
198
 
199
  /**
200
- * Checks to see if a spec is potentially valid for the given node based on
201
- * the attributes present in the node.
 
202
  *
203
- * Note: This can be a very expensive function. Use it sparingly.
 
 
 
 
 
204
  */
205
  private function validate_attr_spec_list_for_node( $node, $attr_spec_list ) {
206
- // If this node doesn't have any attributes, there is no point in
207
- // continuing.
 
 
208
  if ( ! $node->hasAttributes() ) {
209
  return 0;
210
  }
211
 
212
- foreach( $node->attributes as $attr_name => $attr_node ) {
213
- if ( isset( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
214
- foreach( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] as $attr_alt_name ) {
215
- $attr_spec_list[ $attr_alt_name ] = $attr_spec_list[ $attr_name ];
216
- }
217
  }
 
 
 
 
 
 
 
 
 
 
 
218
  }
219
 
220
  $score = 0;
221
 
222
- // Iterate through each attribute rule in this attr spec list and run
223
- // the series of tests. Each filter is given a `$node`, an `$attr_name`,
224
- // and an `$attr_spec_rule`. If the `$attr_spec_rule` seems to be valid
225
- // for the given node, then the filter should increment the score by one.
 
 
226
  foreach ( $attr_spec_list as $attr_name => $attr_spec_rule ) {
227
 
228
- // If a mandatory attribute is required, and attribute exists, pass.
229
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::MANDATORY] ) ) {
230
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule ) ) {
231
- $score += 1;
232
  }
233
  }
234
 
235
- // Check 'value' - case sensitive
236
- // Given attribute's value must exactly equal value of the rule to pass.
237
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE] ) ) {
238
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) ) {
239
- $score += 1;
 
 
240
  }
241
  }
242
 
243
- // Check 'value_regex' - case sensitive regex match
244
- // Given attribute's value must be a case insensitive match to regex pattern
245
- // specified by the value of rule to pass.
246
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX] ) ) {
247
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
248
- $score += 1;
 
 
249
  }
250
  }
251
 
252
- // Check 'value_casei' - case insensitive
253
- // Given attribute's value must be a case insensitive match to the value of
254
- // the rule to pass.
255
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] ) ) {
256
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
257
- $score += 1;
 
 
258
  }
259
  }
260
 
261
- // Check 'value_regex_casei' - case insensitive regex match
262
- // Given attribute's value must be a case insensitive match to the regex
263
- // pattern specified by the value of the rule to pass.
264
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI] ) ) {
265
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
266
- $score += 1;
 
 
267
  }
268
  }
269
 
270
- // If given attribute's value is a URL with a protocol, the protocol must
271
- // be in the array specified by the rule's value to pass.
272
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
273
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
274
- $score += 1;
 
 
275
  }
276
  }
277
 
278
- // If the given attribute's value is *not* a relative path, and the rule's
279
- // value is `false`, then pass.
280
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) ) {
281
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
282
- $score += 1;
 
 
283
  }
284
  }
285
 
286
- // If the given attribute's value exists, is non-empty and the rule's value
287
- // is false, then pass.
288
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) ) {
289
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
290
- $score += 1;
 
 
291
  }
292
  }
293
 
294
- // If the given attribute's value is a URL and does not match any of the list
295
- // of domains in the value of the rule, then pass.
296
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] ) ) {
297
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) ) {
298
- $score += 1;
 
 
299
  }
300
  }
301
 
302
- // If the attribute's value exists and does not match the regex specified
303
- // by the rule's value, then pass.
304
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] ) ) {
305
- if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
306
- $score += 1;
 
 
307
  }
308
  }
309
  }
310
 
311
- // return true;
312
  return $score;
313
  }
314
 
315
  /**
316
- * If an attribute is not listed in $allowed_attrs, then it will be removed
317
- * from $node.
 
 
318
  */
319
  private function sanitize_disallowed_attributes_in_node( $node, $attr_spec_list ) {
320
- // Note: We can't remove the attributes inside the 'foreach' loop
321
- // because that breaks the process of iterating through the attrs. So,
322
- // we keep track of what needs to be removed in the first loop, then
323
- // actually remove the attributes in the second loop.
 
 
 
 
 
 
 
 
 
 
324
  $attrs_to_remove = array();
325
  foreach ( $node->attributes as $attr_name => $attr_node ) {
326
- // see if this attribute is allowed for this node
327
  if ( ! $this->is_amp_allowed_attribute( $attr_name, $attr_spec_list ) ) {
 
 
 
328
  $attrs_to_remove[] = $attr_name;
329
  }
330
  }
331
 
332
  if ( ! empty( $attrs_to_remove ) ) {
333
- // Make sure we're not removing an attribtue that is listed as an alternative
334
- // for some other allowed attribtue. ex. 'srcset' is an alternative for 'src'.
 
 
335
  foreach ( $attr_spec_list as $attr_name => $attr_spec_rule_value ) {
336
- if ( isset( $attr_spec_rule_value[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
337
- foreach ( $attr_spec_rule_value[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
338
  $alt_name_keys = array_keys( $attrs_to_remove, $alternative_name, true );
339
  if ( ! empty( $alt_name_keys ) ) {
340
  unset( $attrs_to_remove[ $alt_name_keys[0] ] );
@@ -343,7 +502,7 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
343
  }
344
  }
345
 
346
- // Remove the disllowed attributes
347
  foreach ( $attrs_to_remove as $attr_name ) {
348
  $node->removeAttribute( $attr_name );
349
  }
@@ -351,71 +510,91 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
351
  }
352
 
353
  /**
354
- * If a node has an attribute value that is disallowed, then that value will
355
- * be removed from the attribute on this node.
 
 
 
 
356
  */
357
  private function sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ) {
358
- $this->_sanitize_disallowed_attribute_values_in_node( $node, $this->globally_allowed_attributes );
 
 
 
 
 
 
 
 
 
359
  if ( ! empty( $attr_spec_list ) ) {
360
- $this->_sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list );
361
  }
362
  }
363
- private function _sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ) {
 
 
 
 
 
 
 
 
 
364
  $attrs_to_remove = array();
365
 
366
- foreach( $attr_spec_list as $attr_name => $attr_val ) {
367
- if ( isset( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
368
- foreach( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] as $attr_alt_name ) {
369
  $attr_spec_list[ $attr_alt_name ] = $attr_spec_list[ $attr_name ];
370
  }
371
  }
372
  }
373
 
374
- $mandatory_attributes = array();
375
-
376
- foreach( $node->attributes as $attr_name => $attr_node ) {
377
 
378
- if ( ! isset( $attr_spec_list[$attr_name] ) ) {
379
  continue;
380
  }
381
 
382
  $should_remove_node = false;
383
- $attr_spec_rule = $attr_spec_list[ $attr_name ];
384
 
385
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE] ) &&
386
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) ) {
387
  $should_remove_node = true;
388
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] ) &&
389
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
390
  $should_remove_node = true;
391
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX] ) &&
392
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
393
  $should_remove_node = true;
394
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI] ) &&
395
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
396
  $should_remove_node = true;
397
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) &&
398
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
399
  $should_remove_node = true;
400
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) &&
401
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
402
  $should_remove_node = true;
403
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) &&
404
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
405
  $should_remove_node = true;
406
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] ) &&
407
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) ) {
408
  $should_remove_node = true;
409
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] ) &&
410
- AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
411
  $should_remove_node = true;
412
  }
413
 
414
  if ( $should_remove_node ) {
415
  $is_mandatory =
416
  isset( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] )
417
- ? (bool) $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ]
418
- : false;
419
 
420
  if ( $is_mandatory ) {
421
  $this->remove_node( $node );
@@ -426,12 +605,11 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
426
  }
427
  }
428
 
429
- // Remove the disallowed values
430
  foreach ( $attrs_to_remove as $attr_name ) {
431
- if ( isset( $attr_spec_list[$attr_name][AMP_Rule_Spec::ALLOW_EMPTY] ) &&
432
- ( true == $attr_spec_list[$attr_name][AMP_Rule_Spec::ALLOW_EMPTY] ) ) {
433
- $attr = $node->attributes;
434
- $attr[ $attr_name ]->value = '';
435
  } else {
436
  $node->removeAttribute( $attr_name );
437
  }
@@ -439,29 +617,32 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
439
  }
440
 
441
  /**
442
- * Checks to see if the given attribute is mandatory for the given node and
443
- * whether the attribute (or a specified alternate) exists.
444
  *
445
- * Returns:
446
- * - AMP_Rule_Spec::PASS - $attr_name is mandatory and it exists
447
- * - AMP_Rule_Spec::FAIL - $attr_name is mandatory, but doesn't exist
448
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name is not mandatory
 
 
 
 
 
 
449
  */
450
  private function check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule ) {
451
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::MANDATORY] ) &&
452
- ( true == $attr_spec_rule[AMP_Rule_Spec::MANDATORY] ) ) {
453
  if ( $node->hasAttribute( $attr_name ) ) {
454
  return AMP_Rule_Spec::PASS;
455
  } else {
456
- // check if an alternative name list is specified
457
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
458
- foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alt_name ) {
459
  if ( $node->hasAttribute( $alt_name ) ) {
460
  return AMP_Rule_Spec::PASS;
461
  }
462
  }
463
  }
464
-
465
  return AMP_Rule_Spec::FAIL;
466
  }
467
  }
@@ -469,27 +650,32 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
469
  }
470
 
471
  /**
472
- * Checks to see if the given attribute exists, has a value rule, and whether
473
- * the attribute matches the value.
474
- *
475
- * Returns:
476
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
477
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
478
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
479
- * is no rule for this attribute.
 
 
 
 
 
480
  */
481
  private function check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) {
482
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE] ) ) {
483
  if ( $node->hasAttribute( $attr_name ) ) {
484
- if ( $node->getAttribute( $attr_name ) == $attr_spec_rule[AMP_Rule_Spec::VALUE] ) {
485
  return AMP_Rule_Spec::PASS;
486
  } else {
487
  return AMP_Rule_Spec::FAIL;
488
  }
489
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
490
- foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
491
  if ( $node->hasAttribute( $alternative_name ) ) {
492
- if ( $node->getAttribute( $alternative_name ) == $attr_spec_rule[AMP_Rule_Spec::VALUE] ) {
493
  return AMP_Rule_Spec::PASS;
494
  } else {
495
  return AMP_Rule_Spec::FAIL;
@@ -502,31 +688,36 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
502
  }
503
 
504
  /**
505
- * Checks to see if the given attribute exists, has a value rule, and whether
506
- * or not the value matches the rule without respect to case.
507
- *
508
- * Returns:
509
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
510
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
511
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
512
- * is no rule for this attribute.
 
 
 
513
  */
514
  private function check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) {
515
- // check 'value_casei' - case insensitive
516
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] ) ) {
517
- $rule_value = strtolower( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] );
 
 
518
  if ( $node->hasAttribute( $attr_name ) ) {
519
  $attr_value = strtolower( $node->getAttribute( $attr_name ) );
520
- if ( $attr_value == $rule_value ) {
521
  return AMP_Rule_Spec::PASS;
522
  } else {
523
  return AMP_Rule_Spec::FAIL;
524
  }
525
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
526
- foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
527
  if ( $node->hasAttribute( $alternative_name ) ) {
528
  $attr_value = strtolower( $node->getAttribute( $alternative_name ) );
529
- if ( $attr_value == $rule_value ) {
530
  return AMP_Rule_Spec::PASS;
531
  } else {
532
  return AMP_Rule_Spec::FAIL;
@@ -539,25 +730,30 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
539
  }
540
 
541
  /**
542
- * Checks to see if the given attribute exists, has a value_regex rule, and
543
- * whether or not the value matches the rule.
544
- *
545
- * Returns:
546
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
547
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
548
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
549
- * is no rule for this attribute.
 
 
 
550
  */
551
  private function check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) {
552
- // check 'value_regex' - case sensitive regex match
553
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX] ) && $node->hasAttribute( $attr_name ) ) {
554
- $rule_value = $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX];
555
- // Note: I added in the '^' and '$' to the regex pattern even though
556
- // they weren't in the AMP spec. But leaving them out would allow
557
- // both '_blank' and 'yyy_blankzzz' to be matched by a regex rule of
558
- // '(_blank|_self|_top)'. The AMP JS validator only accepts '_blank',
559
- // so I'm leaving it this way for now.
560
- if ( preg_match('@^' . $rule_value . '$@u', $node->getAttribute( $attr_name )) ) {
 
 
561
  return AMP_Rule_Spec::PASS;
562
  } else {
563
  return AMP_Rule_Spec::FAIL;
@@ -567,21 +763,27 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
567
  }
568
 
569
  /**
570
- * Checks to see if the given attribute exists, has a value_regex_casei rule,
571
- * and whether or not the value matches the rule without respect to case.
572
- *
573
- * Returns:
574
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
575
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
576
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
577
- * is no rule for this attribute.
 
 
 
578
  */
579
  private function check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) {
580
- // check 'value_regex_casei' - case insensitive regex match
581
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI] ) && $node->hasAttribute( $attr_name ) ) {
582
- $rule_value = $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI];
 
 
 
583
  // See note above regarding the '^' and '$' that are added here.
584
- if ( preg_match('/^' . $rule_value . '$/ui', $node->getAttribute( $attr_name ) ) ) {
585
  return AMP_Rule_Spec::PASS;
586
  } else {
587
  return AMP_Rule_Spec::FAIL;
@@ -591,42 +793,51 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
591
  }
592
 
593
  /**
594
- * Checks to see if the given attribute exists, has a protocol rule, and
595
- * whether or not the value matches the rule.
596
- *
597
- * Returns:
598
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
599
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
600
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
601
- * is no rule for this attribute.
 
 
 
602
  */
603
  private function check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) {
604
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
605
  if ( $node->hasAttribute( $attr_name ) ) {
606
- $attr_value = $node->getAttribute( $attr_name );
607
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
608
  $urls_to_test = explode( ',', $attr_value );
609
  foreach ( $urls_to_test as $url ) {
610
- // This seems to be an acceptable check since the AMP validator
611
- // will allow a URL with no protocol to pass validation.
612
- if ( $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME ) ) {
613
- if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
 
 
 
614
  return AMP_Rule_Spec::FAIL;
615
  }
616
  }
617
  }
618
  return AMP_Rule_Spec::PASS;
619
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
620
- foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
621
  if ( $node->hasAttribute( $alternative_name ) ) {
622
- $attr_value = $node->getAttribute( $alternative_name );
623
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
624
  $urls_to_test = explode( ',', $attr_value );
625
  foreach ( $urls_to_test as $url ) {
626
- // This seems to be an acceptable check since the AMP validator
627
- // will allow a URL with no protocol to pass validation.
628
- if ( $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME ) ) {
629
- if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
 
 
 
630
  return AMP_Rule_Spec::FAIL;
631
  }
632
  }
@@ -640,39 +851,44 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
640
  }
641
 
642
  /**
643
- * Checks to see if the given attribute exists, has a disallowed_relative rule,
644
- * and whether or not the value matches the rule.
645
- *
646
- * Returns:
647
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
648
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
649
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
650
- * is no rule for this attribute.
 
 
 
651
  */
652
  private function check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) {
653
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) &&
654
- ( false == $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) ) {
655
  if ( $node->hasAttribute( $attr_name ) ) {
656
- $attr_value = $node->getAttribute( $attr_name );
657
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
658
  $urls_to_test = explode( ',', $attr_value );
659
  foreach ( $urls_to_test as $url ) {
660
  $parsed_url = AMP_WP_Utils::parse_url( $url );
661
- // The JS AMP validator seems to consider 'relative' to mean
662
- // *protocol* relative, not *host* relative for this rule. So,
663
- // a url with an empty 'scheme' is considered "relative" by AMP.
664
- // ie. '//domain.com/path' and '/path' should both be considered
665
- // relative for purposes of AMP validation.
 
 
 
666
  if ( empty( $parsed_url['scheme'] ) ) {
667
  return AMP_Rule_Spec::FAIL;
668
  }
669
  }
670
  return AMP_Rule_Spec::PASS;
671
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
672
- foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
673
  if ( $node->hasAttribute( $alternative_name ) ) {
674
- $attr_value = $node->getAttribute( $alternative_name );
675
- $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
676
  $urls_to_test = explode( ',', $attr_value );
677
  foreach ( $urls_to_test as $url ) {
678
  $parsed_url = AMP_WP_Utils::parse_url( $url );
@@ -689,19 +905,20 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
689
  }
690
 
691
  /**
692
- * Checks to see if the given attribute exists, has a disallowed_relative rule,
693
- * and whether or not the value matches the rule.
694
- *
695
- * Returns:
696
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
697
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
698
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
699
- * is no rule for this attribute.
 
 
 
700
  */
701
  private function check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) {
702
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) &&
703
- ( false == $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) &&
704
- $node->hasAttribute( $attr_name ) ) {
705
  $attr_value = $node->getAttribute( $attr_name );
706
  if ( empty( $attr_value ) ) {
707
  return AMP_Rule_Spec::FAIL;
@@ -712,23 +929,26 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
712
  }
713
 
714
  /**
715
- * Checks to see if the given attribute exists, has a disallowed_domain rule,
716
- * and whether or not the value matches the rule.
717
- *
718
- * Returns:
719
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
720
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
721
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
722
- * is no rule for this attribute.
 
 
 
723
  */
724
  private function check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) {
725
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] ) &&
726
- $node->hasAttribute( $attr_name ) ) {
727
  $attr_value = $node->getAttribute( $attr_name );
728
  $url_domain = AMP_WP_Utils::parse_url( $attr_value, PHP_URL_HOST );
729
  if ( ! empty( $url_domain ) ) {
730
- foreach ( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] as $disallowed_domain ) {
731
- if ( strtolower( $url_domain ) == strtolower( $disallowed_domain ) ) {
 
732
  // Found a disallowed domain, fail validation.
733
  return AMP_Rule_Spec::FAIL;
734
  }
@@ -740,18 +960,23 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
740
  }
741
 
742
  /**
743
- * Checks to see if the given attribute exists, has a blacklisted_value_regex rule,
744
- * and whether or not the value matches the rule.
745
- *
746
- * Returns:
747
- * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
748
- * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
749
- * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
750
- * is no rule for this attribute.
 
 
 
 
 
751
  */
752
  private function check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) {
753
- if ( isset( $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] ) ) {
754
- $pattern = '/' . $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] . '/u';
755
  if ( $node->hasAttribute( $attr_name ) ) {
756
  $attr_value = $node->getAttribute( $attr_name );
757
  if ( preg_match( $pattern, $attr_value ) ) {
@@ -759,8 +984,8 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
759
  } else {
760
  return AMP_Rule_Spec::PASS;
761
  }
762
- } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
763
- foreach( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
764
  if ( $node->hasAttribute( $alternative_name ) ) {
765
  $attr_value = $node->getAttribute( $alternative_name );
766
  if ( preg_match( $pattern, $attr_value ) ) {
@@ -776,12 +1001,16 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
776
  }
777
 
778
  /**
779
- * Return true if the attribute name is valid for this attr_spec, false otherwise.
 
 
 
 
 
 
780
  */
781
  private function is_amp_allowed_attribute( $attr_name, $attr_spec_list ) {
782
- if ( isset( $this->globally_allowed_attributes[ $attr_name ] ) ||
783
- isset( $this->layout_allowed_attributes[ $attr_name ] ) ||
784
- isset( $attr_spec_list[ $attr_name ] ) ) {
785
  return true;
786
  } else {
787
  foreach ( AMP_Rule_Spec::$whitelisted_attr_regex as $whitelisted_attr_regex ) {
@@ -794,31 +1023,52 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
794
  }
795
 
796
  /**
797
- * Return true if the specified node's name is an AMP allowed tag, false otherwise.
 
 
 
 
 
798
  */
799
  private function is_amp_allowed_tag( $node ) {
800
- // Return true if node is on the allowed tags list or if it is a text
801
- // or comment node.
802
- return ( ( XML_TEXT_NODE == $node->nodeType ) ||
 
 
 
 
 
803
  isset( $this->allowed_tags[ $node->nodeName ] ) ||
804
- ( XML_COMMENT_NODE == $node->nodeType ) ||
805
- ( XML_CDATA_SECTION_NODE == $node->nodeType ) );
 
806
  }
807
 
808
  /**
809
- * Return true if the given node has a direct parent with the given name,
810
- * otherwise return false.
 
 
 
 
 
811
  */
812
  private function has_parent( $node, $parent_tag_name ) {
813
- if ( $node && $node->parentNode && ( $node->parentNode->nodeName == $parent_tag_name ) ) {
814
  return true;
815
  }
816
  return false;
817
  }
818
 
819
  /**
820
- * Return true if the given node has any ancestor with the give name,
821
- * otherwise return false.
 
 
 
 
 
822
  */
823
  private function has_ancestor( $node, $ancestor_tag_name ) {
824
  if ( $this->get_ancestor_with_tag_name( $node, $ancestor_tag_name ) ) {
@@ -828,12 +1078,18 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
828
  }
829
 
830
  /**
831
- * Returns the first ancestor with the given tag name. If no ancestor
832
- * with that name is found, returns null.
 
 
 
 
 
833
  */
834
  private function get_ancestor_with_tag_name( $node, $ancestor_tag_name ) {
835
- while ( $node && $node = $node->parentNode ) {
836
- if ( $node->nodeName == $ancestor_tag_name ) {
 
837
  return $node;
838
  }
839
  }
@@ -841,44 +1097,64 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
841
  }
842
 
843
  /**
844
- * Replaces the given node with it's child nodes, if any, and adds them to
845
- * the stack for processing by the sanitize() function.
 
 
 
 
 
846
  */
847
  private function replace_node_with_children( $node ) {
848
- // If node has children, replace it with them and push children onto stack
849
- if ( $node->hasChildNodes() && $node->parentNode ) {
850
 
851
- // create a DOM fragment to hold the children
 
 
 
 
 
 
 
 
 
852
  $fragment = $this->dom->createDocumentFragment();
853
 
854
- // Add all children to fragment/stack
855
  $child = $node->firstChild;
856
- while( $child ) {
857
  $fragment->appendChild( $child );
858
  $this->stack[] = $child;
859
- $child = $node->firstChild;
860
  }
861
 
862
- // replace node with fragment
863
  $node->parentNode->replaceChild( $fragment, $node );
864
 
865
- // If node has no children, just remove the node.
866
- } else {
867
- $this->remove_node( $node );
868
  }
869
  }
870
 
871
  /**
872
- * Remove a node. If removing the node makes the parent node empty, then
873
- * remove the parent as well. Continue until a non-empty parent is reached
874
- * or the 'body' element is reached.
 
 
 
 
 
875
  */
876
  private function remove_node( $node ) {
877
- if ( $node && $parent = $node->parentNode ) {
 
 
 
 
 
 
878
  $parent->removeChild( $node );
879
  }
880
- while( $parent && ( ! $parent->hasChildNodes() ) && ( 'body' != $parent->nodeName ) ) {
881
- $node = $parent;
882
  $parent = $parent->parentNode;
883
  if ( $parent ) {
884
  $parent->removeChild( $node );
@@ -887,91 +1163,3 @@ class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
887
  }
888
  }
889
 
890
- /**
891
- * This is a set of constants that are used throughout the sanitizer.
892
- * The rule name strings are listed here because it's easier to have the php
893
- * interpreter catch a typo than for me to catch mistyping a string.
894
- */
895
- abstract class AMP_Rule_Spec {
896
-
897
- // AMP rule_spec types
898
- const ATTR_SPEC_LIST = 'attr_spec_list';
899
- const TAG_SPEC = 'tag_spec';
900
-
901
- // AMP attr_spec value check results
902
- const PASS = 'pass';
903
- const FAIL = 'fail';
904
- const NOT_APPLICABLE = 'not_applicable';
905
-
906
- // tag rule names
907
- const DISALLOWED_ANCESTOR = 'disallowed_ancestor';
908
- const MANDATORY_ANCESTOR = 'mandatory_ancestor';
909
- const MANDATORY_PARENT = 'mandatory_parent';
910
-
911
- // attr rule names
912
- const ALLOW_EMPTY = 'allow_empty';
913
- const ALLOW_RELATIVE = 'allow_relative';
914
- const ALLOWED_PROTOCOL = 'allowed_protocol';
915
- const ALTERNATIVE_NAMES = 'alternative_names';
916
- const BLACKLISTED_VALUE_REGEX = 'blacklisted_value_regex';
917
- const DISALLOWED_DOMAIN = 'disallowed_domain';
918
- const MANDATORY = 'mandatory';
919
- const VALUE = 'value';
920
- const VALUE_CASEI = 'value_casei';
921
- const VALUE_REGEX = 'value_regex';
922
- const VALUE_REGEX_CASEI = 'value_regex_casei';
923
-
924
- // If a node type listed here is invalid, it and it's subtree will be
925
- // removed if it is invalid. This is mainly because any children will be
926
- // non-functional without this parent.
927
- //
928
- // If a tag is not listed here, it will be replaced by its children if it
929
- // is invalid.
930
- //
931
- // TODO: There are other nodes that should probably be listed here as well.
932
- static $node_types_to_remove_if_invalid = array(
933
- 'form',
934
- 'input',
935
- 'link',
936
- 'meta',
937
- // 'script',
938
- 'style',
939
- );
940
-
941
- // It is mentioned in the documentation in several places that data-* is
942
- // generally allowed, but there is no specific rule for it in the protoascii
943
- // file, so I'm including it here.
944
- static $whitelisted_attr_regex = array(
945
- '@^data-[a-zA-Z][\\w:.-]*$@uis',
946
- '(update|item|pagination)', // allowed for live reference points
947
- );
948
-
949
- static $additional_allowed_tags = array(
950
-
951
- // this is an experimental tag with no protoascii
952
- 'amp-share-tracking' => array(
953
- 'attr_spec_list' => array(),
954
- 'tag_spec' => array(),
955
- ),
956
-
957
- // this is needed for some tags such as analytics
958
- 'script' => array(
959
- 'attr_spec_list' => array(
960
- 'type' => array(
961
- 'mandatory' => true,
962
- 'value_casei' => 'text/javascript',
963
- ),
964
- ),
965
- 'tag_spec' => array(),
966
- ),
967
- 'script' => array(
968
- 'attr_spec_list' => array(
969
- 'type' => array(
970
- 'mandatory' => true,
971
- 'value_casei' => 'application/json',
972
- ),
973
- ),
974
- 'tag_spec' => array(),
975
- ),
976
- );
977
- }
1
  <?php
2
+ /**
3
+ * Class AMP_Tag_And_Attribute_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
+ * Strips the tags and attributes from the content that are not allowed by the AMP spec.
10
  *
11
  * Allowed tags array is generated from this protocol buffer:
12
+ *
13
  * https://github.com/ampproject/amphtml/blob/master/validator/validator-main.protoascii
14
  * by the python script in amp-wp/bin/amp_wp_build.py. See the comment at the top
15
  * of that file for instructions to generate class-amp-allowed-tags-generated.php.
16
  *
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.
24
+ * - `mandatory_oneof` - Within the context of the tag, exactly one of the attributes
25
+ * must be present.
26
  */
27
  class AMP_Tag_And_Attribute_Sanitizer extends AMP_Base_Sanitizer {
28
+
29
+ /**
30
+ * Allowed tags.
31
+ *
32
+ * @since 0.5
33
+ *
34
+ * @var string[]
35
+ */
36
  protected $allowed_tags;
37
+
38
+ /**
39
+ * Globally-allowed attributes.
40
+ *
41
+ * @since 0.5
42
+ *
43
+ * @var array[][]
44
+ */
45
  protected $globally_allowed_attributes;
46
+
47
+ /**
48
+ * Layout-allowed attributes.
49
+ *
50
+ * @since 0.5
51
+ *
52
+ * @var string[]
53
+ */
54
  protected $layout_allowed_attributes;
55
+
56
+ /**
57
+ * Stack.
58
+ *
59
+ * @since 0.5
60
+ *
61
+ * @var DOMElement[]
62
+ */
63
  private $stack = array();
64
 
65
+ /**
66
+ * Default args.
67
+ *
68
+ * @since 0.5
69
+ *
70
+ * @var array
71
+ */
72
  protected $DEFAULT_ARGS = array();
73
 
74
+ /**
75
+ * AMP_Tag_And_Attribute_Sanitizer constructor.
76
+ *
77
+ * @since 0.5
78
+ *
79
+ * @param DOMDocument $dom DOM.
80
+ * @param array $args Args.
81
+ */
82
  public function __construct( $dom, $args = array() ) {
83
  $this->DEFAULT_ARGS = array(
84
  'amp_allowed_tags' => AMP_Allowed_Tags_Generated::get_allowed_tags(),
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.
121
+ */
122
+ while ( ! empty( $this->stack ) ) {
123
 
124
  // Get the next node to process.
125
  $node = array_pop( $this->stack );
126
 
127
+ /**
128
+ * Process this node.
129
+ */
130
  $this->process_node( $node );
131
 
132
+ /*
133
+ * Push child nodes onto the stack, if any exist.
134
+ * if node was removed, then it's parentNode value is null.
135
+ */
136
  if ( $node->parentNode ) {
137
  $child = $node->firstChild;
138
  while ( $child ) {
139
  $this->stack[] = $child;
140
+ $child = $child->nextSibling;
141
  }
142
  }
143
  }
144
  }
145
 
146
+ /**
147
+ * Process a node by sanitizing and/or validating it per.
148
+ *
149
+ * @param DOMNode $node Node.
150
+ */
151
  private function process_node( $node ) {
152
+
153
+ // Don't process text or comment nodes.
154
+ if ( XML_TEXT_NODE === $node->nodeType || XML_COMMENT_NODE === $node->nodeType || XML_CDATA_SECTION_NODE === $node->nodeType ) {
 
155
  return;
156
  }
157
 
158
+ // Remove nodes with tags that have not been whitelisted.
159
  if ( ! $this->is_amp_allowed_tag( $node ) ) {
160
+
161
+ // If it's not an allowed tag, replace the node with it's children.
162
  $this->replace_node_with_children( $node );
163
+
164
  // Return early since this node no longer exists.
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.
171
+ */
172
  $rule_spec_list_to_validate = array();
173
+ $rule_spec_list = array();
174
  if ( isset( $this->allowed_tags[ $node->nodeName ] ) ) {
175
  $rule_spec_list = $this->allowed_tags[ $node->nodeName ];
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
  }
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
194
+ * to validate the node's attributes.
195
+ */
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,
203
+ * then try to deduce which one is intended by inspecting
204
+ * the node's attributes.
205
+ */
206
+
207
+ /*
208
+ * Get a score from each attr_spec_list by seeing how many
209
+ * attributes and values match the node.
210
+ */
211
  $attr_spec_scores = array();
212
  foreach ( $rule_spec_list_to_validate as $spec_id => $rule_spec ) {
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
225
  // be used. Let's use the top scoring ones.
226
+ foreach ( $spec_ids_sorted as $id ) {
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 );
233
+ if ( empty( $attr_spec_list ) && isset( $first_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ] ) ) {
234
+ $attr_spec_list = $first_spec[ AMP_Rule_Spec::ATTR_SPEC_LIST ];
235
  }
236
  }
237
+ } // End if().
 
 
 
 
 
 
 
 
238
 
239
+ if ( ! empty( $attr_spec_list ) && $this->is_missing_mandatory_attribute( $attr_spec_list, $node ) ) {
240
+ $this->remove_node( $node );
241
+ return;
 
242
  }
243
 
244
  // Remove any remaining disallowed attributes.
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 ) {
259
+ if ( ! is_array( $attr_spec ) ) {
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
+ }
268
+ }
269
+ return false;
270
+ }
271
+
272
+ /**
273
+ * Determines is a node is currently valid per its tag specification.
274
+ *
275
  * Checks to see if a node's placement with the DOM is be valid for the
276
  * given tag_spec. If there are restrictions placed on the type of node
277
  * that can be an immediate parent or an ancestor of this node, then make
278
  * sure those restrictions are met.
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 ) ) {
294
  return false;
295
  }
296
  }
297
  }
298
 
299
+ if ( ! empty( $tag_spec[ AMP_Rule_Spec::MANDATORY_ANCESTOR ] ) && ! $this->has_ancestor( $node, $tag_spec[ AMP_Rule_Spec::MANDATORY_ANCESTOR ] ) ) {
 
300
  return false;
301
  }
302
 
304
  }
305
 
306
  /**
307
+ * Checks to see if a spec is potentially valid.
308
+ *
309
+ * Checks the given node based on the attributes present in the node.
310
  *
311
+ * @note This can be a very expensive function. Use it sparingly.
312
+ *
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
 
327
+ foreach ( $node->attributes as $attr_name => $attr_node ) {
328
+ if ( ! isset( $attr_spec_list[ $attr_name ][ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
329
+ continue;
 
 
330
  }
331
+ foreach ( $attr_spec_list[ $attr_name ][ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $attr_alt_name ) {
332
+ $attr_spec_list[ $attr_alt_name ] = $attr_spec_list[ $attr_name ];
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`,
349
+ * and an `$attr_spec_rule`. If the `$attr_spec_rule` seems to be valid
350
+ * for the given node, then the filter should increment the score by one.
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
 
361
+ /*
362
+ * Check 'value' - case sensitive
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
 
371
+ /*
372
+ * Check 'value_regex' - case sensitive regex match
373
+ * Given attribute's value must be a case insensitive match to regex pattern
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
 
382
+ /*
383
+ * Check 'value_casei' - case insensitive
384
+ * Given attribute's value must be a case insensitive match to the value of
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
 
393
+ /*
394
+ * Check 'value_regex_casei' - case insensitive regex match
395
+ * Given attribute's value must be a case insensitive match to the regex
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
 
404
+ /*
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
 
414
+ /*
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
 
424
+ /*
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
 
434
+ /*
435
+ * If the given attribute's value is a URL and does not match any of the list
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
 
444
+ /*
445
+ * If the attribute's value exists and does not match the regex specified
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;
456
  }
457
 
458
  /**
459
+ * Remove attributes from $node that are not listed in $allowed_attrs.
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
+ /*
475
+ * We can't remove attributes inside the 'foreach' loop without
476
+ * breaking the iteration. So we keep track of the attributes to
477
+ * remove in the first loop, then remove them in the second loop.
478
+ */
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] ] );
502
  }
503
  }
504
 
505
+ // Remove the disallowed attributes.
506
  foreach ( $attrs_to_remove as $attr_name ) {
507
  $node->removeAttribute( $attr_name );
508
  }
510
  }
511
 
512
  /**
513
+ * Remove invalid AMP attributes values from $node that have been implicitly disallowed.
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
+ /**
537
+ * Remove attributes values from $node that have been disallowed by AMP.
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 ) {
548
+ if ( isset( $attr_spec_list[ $attr_name ][ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
549
+ foreach ( $attr_spec_list[ $attr_name ][ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $attr_alt_name ) {
550
  $attr_spec_list[ $attr_alt_name ] = $attr_spec_list[ $attr_name ];
551
  }
552
  }
553
  }
554
 
555
+ foreach ( $node->attributes as $attr_name => $attr_node ) {
 
 
556
 
557
+ if ( ! isset( $attr_spec_list[ $attr_name ] ) ) {
558
  continue;
559
  }
560
 
561
  $should_remove_node = false;
562
+ $attr_spec_rule = $attr_spec_list[ $attr_name ];
563
 
564
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE ] ) &&
565
+ AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) ) {
566
  $should_remove_node = true;
567
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_CASEI ] ) &&
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 ] ) &&
586
+ AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) ) {
587
  $should_remove_node = true;
588
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX ] ) &&
589
+ AMP_Rule_Spec::FAIL === $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
590
  $should_remove_node = true;
591
  }
592
 
593
  if ( $should_remove_node ) {
594
  $is_mandatory =
595
  isset( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] )
596
+ ? (bool) $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ]
597
+ : false;
598
 
599
  if ( $is_mandatory ) {
600
  $this->remove_node( $node );
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
  }
617
  }
618
 
619
  /**
620
+ * Check if attribute is mandatory determine whether it exists in $node.
 
621
  *
622
+ * When checking for the given attribute it also checks valid alternates.
623
+ *
624
+ * @param DOMElement $node Node.
625
+ * @param string $attr_name Attribute name.
626
+ * @param array[] $attr_spec_rule Attribute spec rule.
627
+ *
628
+ * @return string:
629
+ * - AMP_Rule_Spec::PASS - $attr_name is mandatory and it exists
630
+ * - AMP_Rule_Spec::FAIL - $attr_name is mandatory, but doesn't exist
631
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name is not mandatory
632
  */
633
  private function check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule ) {
634
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] ) && ( $attr_spec_rule[ AMP_Rule_Spec::MANDATORY ] ) ) {
 
635
  if ( $node->hasAttribute( $attr_name ) ) {
636
  return AMP_Rule_Spec::PASS;
637
  } else {
638
+ // Check if an alternative name list is specified.
639
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
640
+ foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alt_name ) {
641
  if ( $node->hasAttribute( $alt_name ) ) {
642
  return AMP_Rule_Spec::PASS;
643
  }
644
  }
645
  }
 
646
  return AMP_Rule_Spec::FAIL;
647
  }
648
  }
650
  }
651
 
652
  /**
653
+ * Check if attribute has a value rule determine if its value is valid.
654
+ *
655
+ * Checks for value validity by matches against valid values.
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.
663
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
664
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
665
+ * is no rule for this attribute.
666
  */
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;
674
  }
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;
688
  }
689
 
690
  /**
691
+ * Check if attribute has a value rule determine if its value matches ignoring case.
692
+ *
693
+ * @param DOMElement $node Node.
694
+ * @param string $attr_name Attribute name.
695
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
696
+ *
697
+ * @return string:
698
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
699
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
700
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
701
+ * is no rule for this attribute.
702
  */
703
  private function check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) {
704
+ /**
705
+ * Check 'value_casei' - case insensitive
706
+ */
707
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_CASEI ] ) ) {
708
+ $rule_value = strtolower( $attr_spec_rule[ AMP_Rule_Spec::VALUE_CASEI ] );
709
  if ( $node->hasAttribute( $attr_name ) ) {
710
  $attr_value = strtolower( $node->getAttribute( $attr_name ) );
711
+ if ( $attr_value === (string) $rule_value ) {
712
  return AMP_Rule_Spec::PASS;
713
  } else {
714
  return AMP_Rule_Spec::FAIL;
715
  }
716
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
717
+ foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
718
  if ( $node->hasAttribute( $alternative_name ) ) {
719
  $attr_value = strtolower( $node->getAttribute( $alternative_name ) );
720
+ if ( $attr_value === (string) $rule_value ) {
721
  return AMP_Rule_Spec::PASS;
722
  } else {
723
  return AMP_Rule_Spec::FAIL;
730
  }
731
 
732
  /**
733
+ * Check if attribute has a regex value rule determine if it matches.
734
+ *
735
+ * @param DOMElement $node Node.
736
+ * @param string $attr_name Attribute name.
737
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
738
+ *
739
+ * @return string:
740
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
741
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
742
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
743
+ * is no rule for this attribute.
744
  */
745
  private function check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) {
746
+ // Check 'value_regex' - case sensitive regex match.
747
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX ] ) && $node->hasAttribute( $attr_name ) ) {
748
+ $rule_value = $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX ];
749
+
750
+ /*
751
+ * The regex pattern has '^' and '$' though they are not in the AMP spec.
752
+ * Leaving them out would allow both '_blank' and 'yyy_blankzzz' to be
753
+ * matched by a regex rule of '(_blank|_self|_top)'. As the AMP JS validator
754
+ * only accepts '_blank' we leave it this way for now.
755
+ */
756
+ if ( preg_match( '@^' . $rule_value . '$@u', $node->getAttribute( $attr_name ) ) ) {
757
  return AMP_Rule_Spec::PASS;
758
  } else {
759
  return AMP_Rule_Spec::FAIL;
763
  }
764
 
765
  /**
766
+ * Check if attribute has a case-insensitive regex value rule determine if it matches.
767
+ *
768
+ * @param DOMElement $node Node.
769
+ * @param string $attr_name Attribute name.
770
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
771
+ *
772
+ * @return string:
773
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
774
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
775
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
776
+ * is no rule for this attribute.
777
  */
778
  private function check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) {
779
+ /**
780
+ * Check 'value_regex_casei' - case insensitive regex match
781
+ */
782
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX_CASEI ] ) && $node->hasAttribute( $attr_name ) ) {
783
+ $rule_value = $attr_spec_rule[ AMP_Rule_Spec::VALUE_REGEX_CASEI ];
784
+
785
  // See note above regarding the '^' and '$' that are added here.
786
+ if ( preg_match( '/^' . $rule_value . '$/ui', $node->getAttribute( $attr_name ) ) ) {
787
  return AMP_Rule_Spec::PASS;
788
  } else {
789
  return AMP_Rule_Spec::FAIL;
793
  }
794
 
795
  /**
796
+ * Check if attribute has a protocol value rule determine if it matches.
797
+ *
798
+ * @param DOMElement $node Node.
799
+ * @param string $attr_name Attribute name.
800
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
801
+ *
802
+ * @return string:
803
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
804
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
805
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
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
817
+ * will allow a URL with no protocol to pass validation.
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
  }
825
  }
826
  return AMP_Rule_Spec::PASS;
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
836
+ * will allow a URL with no protocol to pass validation.
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
  }
851
  }
852
 
853
  /**
854
+ * Check if attribute has disallowed relative value rule determine if disallowed relative value matches.
855
+ *
856
+ * @param DOMElement $node Node.
857
+ * @param string $attr_name Attribute name.
858
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
859
+ *
860
+ * @return string:
861
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
862
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
863
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
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
+
875
+ /*
876
+ * The JS AMP validator seems to consider 'relative' to mean
877
+ * *protocol* relative, not *host* relative for this rule. So,
878
+ * a url with an empty 'scheme' is considered "relative" by AMP.
879
+ * ie. '//domain.com/path' and '/path' should both be considered
880
+ * relative for purposes of AMP validation.
881
+ */
882
  if ( empty( $parsed_url['scheme'] ) ) {
883
  return AMP_Rule_Spec::FAIL;
884
  }
885
  }
886
  return AMP_Rule_Spec::PASS;
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 );
905
  }
906
 
907
  /**
908
+ * Check if attribute has disallowed empty value rule determine if value is empty.
909
+ *
910
+ * @param DOMElement $node Node.
911
+ * @param string $attr_name Attribute name.
912
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
913
+ *
914
+ * @return string:
915
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
916
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
917
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
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;
929
  }
930
 
931
  /**
932
+ * Check if attribute has disallowed domain value rule determine if value matches.
933
+ *
934
+ * @param DOMElement $node Node.
935
+ * @param string $attr_name Attribute name.
936
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
937
+ *
938
+ * @return string:
939
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
940
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
941
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
942
+ * is no rule for this attribute.
943
  */
944
  private function check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) {
945
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::DISALLOWED_DOMAIN ] ) && $node->hasAttribute( $attr_name ) ) {
 
946
  $attr_value = $node->getAttribute( $attr_name );
947
  $url_domain = AMP_WP_Utils::parse_url( $attr_value, PHP_URL_HOST );
948
  if ( ! empty( $url_domain ) ) {
949
+ foreach ( $attr_spec_rule[ AMP_Rule_Spec::DISALLOWED_DOMAIN ] as $disallowed_domain ) {
950
+ if ( strtolower( $url_domain ) === strtolower( $disallowed_domain ) ) {
951
+
952
  // Found a disallowed domain, fail validation.
953
  return AMP_Rule_Spec::FAIL;
954
  }
960
  }
961
 
962
  /**
963
+ * Check if attribute has blacklisted value via regex match determine if value matches.
964
+ *
965
+ * @since 0.5
966
+ *
967
+ * @param DOMElement $node Node.
968
+ * @param string $attr_name Attribute name.
969
+ * @param array[]|string[] $attr_spec_rule Attribute spec rule.
970
+ *
971
+ * @return string:
972
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
973
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
974
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
975
+ * is no rule for this attribute.
976
  */
977
  private function check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) {
978
+ if ( isset( $attr_spec_rule[ AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX ] ) ) {
979
+ $pattern = '/' . $attr_spec_rule[ AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX ] . '/u';
980
  if ( $node->hasAttribute( $attr_name ) ) {
981
  $attr_value = $node->getAttribute( $attr_name );
982
  if ( preg_match( $pattern, $attr_value ) ) {
984
  } else {
985
  return AMP_Rule_Spec::PASS;
986
  }
987
+ } elseif ( isset( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] ) ) {
988
+ foreach ( $attr_spec_rule[ AMP_Rule_Spec::ALTERNATIVE_NAMES ] as $alternative_name ) {
989
  if ( $node->hasAttribute( $alternative_name ) ) {
990
  $attr_value = $node->getAttribute( $alternative_name );
991
  if ( preg_match( $pattern, $attr_value ) ) {
1001
  }
1002
 
1003
  /**
1004
+ * Determine if the supplied attribute name is allowed for AMP.
1005
+ *
1006
+ * @since 0.5
1007
+ *
1008
+ * @param string $attr_name Attribute name.
1009
+ * @param array[]|string[] $attr_spec_list Attribute spec list.
1010
+ * @return bool Return true if attribute name is valid for this attr_spec_list, false otherwise.
1011
  */
1012
  private function is_amp_allowed_attribute( $attr_name, $attr_spec_list ) {
1013
+ if ( isset( $this->globally_allowed_attributes[ $attr_name ] ) || isset( $this->layout_allowed_attributes[ $attr_name ] ) || isset( $attr_spec_list[ $attr_name ] ) ) {
 
 
1014
  return true;
1015
  } else {
1016
  foreach ( AMP_Rule_Spec::$whitelisted_attr_regex as $whitelisted_attr_regex ) {
1023
  }
1024
 
1025
  /**
1026
+ * Determine if the supplied $node's HTML tag is allowed for AMP.
1027
+ *
1028
+ * @since 0.5
1029
+ *
1030
+ * @param DOMNode $node Node.
1031
+ * @return bool Return true if the specified node's name is an AMP allowed tag, false otherwise.
1032
  */
1033
  private function is_amp_allowed_tag( $node ) {
1034
+ if ( ! $node instanceof DOMElement ) {
1035
+ return false;
1036
+ }
1037
+ /**
1038
+ * Return true if node is an allowed tags or is a text or comment node.
1039
+ */
1040
+ return (
1041
+ ( XML_TEXT_NODE === $node->nodeType ) ||
1042
  isset( $this->allowed_tags[ $node->nodeName ] ) ||
1043
+ ( XML_COMMENT_NODE === $node->nodeType ) ||
1044
+ ( XML_CDATA_SECTION_NODE === $node->nodeType )
1045
+ );
1046
  }
1047
 
1048
  /**
1049
+ * Determine if the supplied $node has a parent with the specified tag name.
1050
+ *
1051
+ * @since 0.5
1052
+ *
1053
+ * @param DOMNode $node Node.
1054
+ * @param string $parent_tag_name Parent tag name.
1055
+ * @return bool Return true if given node has direct parent with the given name, false otherwise.
1056
  */
1057
  private function has_parent( $node, $parent_tag_name ) {
1058
+ if ( $node && $node->parentNode && ( $node->parentNode->nodeName === $parent_tag_name ) ) {
1059
  return true;
1060
  }
1061
  return false;
1062
  }
1063
 
1064
  /**
1065
+ * Determine if the supplied $node has an ancestor with the specified tag name.
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.
1072
  */
1073
  private function has_ancestor( $node, $ancestor_tag_name ) {
1074
  if ( $this->get_ancestor_with_tag_name( $node, $ancestor_tag_name ) ) {
1078
  }
1079
 
1080
  /**
1081
+ * Get the first ancestor node matching the specified tag name for the supplied $node.
1082
+ *
1083
+ * @since 0.5
1084
+ *
1085
+ * @param DOMNode $node Node.
1086
+ * @param string $ancestor_tag_name Ancestor tag name.
1087
+ * @return DOMNode|null Returns an ancestor node for the name specified, or null if not found.
1088
  */
1089
  private function get_ancestor_with_tag_name( $node, $ancestor_tag_name ) {
1090
+ while ( $node && $node->parentNode ) {
1091
+ $node = $node->parentNode;
1092
+ if ( $node->nodeName === $ancestor_tag_name ) {
1093
  return $node;
1094
  }
1095
  }
1097
  }
1098
 
1099
  /**
1100
+ * Replaces the given node with it's child nodes, if any
1101
+ *
1102
+ * Also adds them to the stack for processing by the sanitize() function.
1103
+ *
1104
+ * @since 0.3.3
1105
+ *
1106
+ * @param DOMNode $node Node.
1107
  */
1108
  private function replace_node_with_children( $node ) {
 
 
1109
 
1110
+ if ( ! $node->hasChildNodes() || ! $node->parentNode ) {
1111
+ // If node has no children or no parent, just remove the node.
1112
+ $this->remove_node( $node );
1113
+
1114
+ } else {
1115
+ /*
1116
+ * If node has children, replace it with them and push children onto stack
1117
+ *
1118
+ * Create a DOM fragment to hold the children
1119
+ */
1120
  $fragment = $this->dom->createDocumentFragment();
1121
 
1122
+ // Add all children to fragment/stack.
1123
  $child = $node->firstChild;
1124
+ while ( $child ) {
1125
  $fragment->appendChild( $child );
1126
  $this->stack[] = $child;
1127
+ $child = $node->firstChild;
1128
  }
1129
 
1130
+ // Replace node with fragment.
1131
  $node->parentNode->replaceChild( $fragment, $node );
1132
 
 
 
 
1133
  }
1134
  }
1135
 
1136
  /**
1137
+ * Removes a node from its parent node.
1138
+ *
1139
+ * If removing the node makes the parent node empty, then it will remove the parent
1140
+ * too. It will Continue until a non-empty parent or the 'body' element is reached.
1141
+ *
1142
+ * @since 0.5
1143
+ *
1144
+ * @param DOMNode $node Node.
1145
  */
1146
  private function remove_node( $node ) {
1147
+ /**
1148
+ * Parent.
1149
+ *
1150
+ * @var DOMNode $parent
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 ) {
1160
  $parent->removeChild( $node );
1163
  }
1164
  }
1165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/sanitizers/class-amp-video-sanitizer.php CHANGED
@@ -1,35 +1,87 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
 
 
 
4
 
5
  /**
 
 
 
 
6
  * Converts <video> tags to <amp-video>
7
  */
8
  class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
 
 
 
 
 
 
 
 
9
  const FALLBACK_HEIGHT = 400;
10
 
 
 
 
 
 
 
 
11
  public static $tag = 'video';
12
 
 
 
 
 
 
 
 
13
  private static $script_slug = 'amp-video';
 
 
 
 
 
 
 
 
14
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-video-0.1.js';
15
 
 
 
 
 
 
 
 
 
 
 
 
16
  public function get_scripts() {
17
  if ( ! $this->did_convert_elements ) {
18
  return array();
19
  }
20
-
21
  return array( self::$script_slug => self::$script_src );
22
  }
23
 
 
 
 
 
 
24
  public function sanitize() {
25
- $nodes = $this->dom->getElementsByTagName( self::$tag );
26
  $num_nodes = $nodes->length;
27
  if ( 0 === $num_nodes ) {
28
  return;
29
  }
30
 
31
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
32
- $node = $nodes->item( $i );
33
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
34
 
35
  $new_attributes = $this->filter_attributes( $old_attributes );
@@ -39,23 +91,43 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
39
 
40
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-video', $new_attributes );
41
 
42
- // TODO: `source` does not have closing tag, and DOMDocument doesn't handle it well.
43
  foreach ( $node->childNodes as $child_node ) {
 
 
 
 
 
 
 
44
  $new_child_node = $child_node->cloneNode( true );
 
 
 
 
45
  $old_child_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $new_child_node );
46
  $new_child_attributes = $this->filter_attributes( $old_child_attributes );
47
 
48
- // Only append source tags with a valid src attribute
49
- if ( ! empty( $new_child_attributes['src'] ) && 'source' === $new_child_node->tagName ) {
50
- $new_node->appendChild( $new_child_node );
 
 
51
  }
 
 
 
 
 
 
52
  }
53
 
54
- // If the node has at least one valid source, replace the old node with it.
55
- // Otherwise, just remove the node.
56
- //
57
- // TODO: Add a fallback handler.
58
- // See: https://github.com/ampproject/amphtml/issues/2261
 
 
59
  if ( 0 === $new_node->childNodes->length && empty( $new_attributes['src'] ) ) {
60
  $node->parentNode->removeChild( $node );
61
  } else {
@@ -63,9 +135,30 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
63
  }
64
 
65
  $this->did_convert_elements = true;
 
66
  }
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  private function filter_attributes( $attributes ) {
70
  $out = array();
71
 
@@ -95,7 +188,7 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
95
  }
96
  break;
97
 
98
- default;
99
  break;
100
  }
101
  }
1
  <?php
2
+ /**
3
+ * Class AMP_Video_Sanitizer.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
  /**
9
+ * Class AMP_Video_Sanitizer
10
+ *
11
+ * @since 0.2
12
+ *
13
  * Converts <video> tags to <amp-video>
14
  */
15
  class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
16
+
17
+ /**
18
+ * Value used for height attribute when $attributes['height'] is empty.
19
+ *
20
+ * @since 0.2
21
+ *
22
+ * @const int
23
+ */
24
  const FALLBACK_HEIGHT = 400;
25
 
26
+ /**
27
+ * Tag.
28
+ *
29
+ * @var string HTML <video> tag to identify and replace with AMP version.
30
+ *
31
+ * @since 0.2
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
+ *
74
+ * @since 0.2
75
+ */
76
  public function sanitize() {
77
+ $nodes = $this->dom->getElementsByTagName( self::$tag );
78
  $num_nodes = $nodes->length;
79
  if ( 0 === $num_nodes ) {
80
  return;
81
  }
82
 
83
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
84
+ $node = $nodes->item( $i );
85
  $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
86
 
87
  $new_attributes = $this->filter_attributes( $old_attributes );
91
 
92
  $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-video', $new_attributes );
93
 
 
94
  foreach ( $node->childNodes as $child_node ) {
95
+ /**
96
+ * Child node.
97
+ *
98
+ * @todo: Fix when `source` has no closing tag as DOMDocument does not handle well.
99
+ *
100
+ * @var DOMNode $child_node
101
+ */
102
  $new_child_node = $child_node->cloneNode( true );
103
+ if ( ! $new_child_node instanceof DOMElement ) {
104
+ continue;
105
+ }
106
+
107
  $old_child_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $new_child_node );
108
  $new_child_attributes = $this->filter_attributes( $old_child_attributes );
109
 
110
+ if ( empty( $new_child_attributes['src'] ) ) {
111
+ continue;
112
+ }
113
+ if ( 'source' !== $new_child_node->tagName ) {
114
+ continue;
115
  }
116
+
117
+ /**
118
+ * Only append source tags with a valid src attribute
119
+ */
120
+ $new_node->appendChild( $new_child_node );
121
+
122
  }
123
 
124
+ /*
125
+ * If the node has at least one valid source, replace the old node with it.
126
+ * Otherwise, just remove the node.
127
+ *
128
+ * TODO: Add a fallback handler.
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 {
135
  }
136
 
137
  $this->did_convert_elements = true;
138
+
139
  }
140
  }
141
 
142
+ /**
143
+ * "Filter" HTML attributes for <amp-audio> elements.
144
+ *
145
+ * @since 0.2
146
+ *
147
+ * @param string[] $attributes {
148
+ * Attributes.
149
+ *
150
+ * @type string $src Video URL - Empty if HTTPS required per $this->args['require_https_src']
151
+ * @type int $width <video> attribute - Set to numeric value if px or %
152
+ * @type int $height <video> attribute - Set to numeric value if px or %
153
+ * @type string $poster <video> attribute - Pass along if found
154
+ * @type string $class <video> attribute - Pass along if found
155
+ * @type bool $controls <video> attribute - Convert 'false' to empty string ''
156
+ * @type bool $loop <video> attribute - Convert 'false' to empty string ''
157
+ * @type bool $muted <video> attribute - Convert 'false' to empty string ''
158
+ * @type bool $autoplay <video> attribute - Convert 'false' to empty string ''
159
+ * }
160
+ * @return array Returns HTML attributes; removes any not specifically declared above from input.
161
+ */
162
  private function filter_attributes( $attributes ) {
163
  $out = array();
164
 
188
  }
189
  break;
190
 
191
+ default:
192
  break;
193
  }
194
  }
includes/settings/class-amp-customizer-design-settings.php CHANGED
@@ -1,24 +1,84 @@
1
  <?php
 
 
 
 
 
2
 
 
 
 
3
  class AMP_Customizer_Design_Settings {
 
 
 
 
 
 
4
  const DEFAULT_HEADER_COLOR = '#fff';
 
 
 
 
 
 
5
  const DEFAULT_HEADER_BACKGROUND_COLOR = '#0a89c0';
 
 
 
 
 
 
6
  const DEFAULT_COLOR_SCHEME = 'light';
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  public static function init() {
9
  add_action( 'amp_customizer_init', array( __CLASS__, 'init_customizer' ) );
10
 
11
- add_filter( 'amp_customizer_get_settings', array( __CLASS__, 'append_settings' ) );
 
 
12
  }
13
 
 
 
 
14
  public static function init_customizer() {
15
- add_action( 'amp_customizer_register_settings', array( __CLASS__, 'register_customizer_settings' ) );
16
- add_action( 'amp_customizer_register_ui', array( __CLASS__, 'register_customizer_ui' ) );
17
- add_action( 'amp_customizer_enqueue_preview_scripts', array( __CLASS__, 'enqueue_customizer_preview_scripts' ) );
 
 
18
  }
19
 
 
 
 
 
 
20
  public static function register_customizer_settings( $wp_customize ) {
21
- // Header text color setting
 
22
  $wp_customize->add_setting( 'amp_customizer[header_color]', array(
23
  'type' => 'option',
24
  'default' => self::DEFAULT_HEADER_COLOR,
@@ -26,7 +86,7 @@ class AMP_Customizer_Design_Settings {
26
  'transport' => 'postMessage',
27
  ) );
28
 
29
- // Header background color
30
  $wp_customize->add_setting( 'amp_customizer[header_background_color]', array(
31
  'type' => 'option',
32
  'default' => self::DEFAULT_HEADER_BACKGROUND_COLOR,
@@ -34,15 +94,20 @@ class AMP_Customizer_Design_Settings {
34
  'transport' => 'postMessage',
35
  ) );
36
 
37
- // Background color scheme
38
  $wp_customize->add_setting( 'amp_customizer[color_scheme]', array(
39
  'type' => 'option',
40
  'default' => self::DEFAULT_COLOR_SCHEME,
41
- 'sanitize_callback' => array( __CLASS__ , 'sanitize_color_scheme' ),
42
  'transport' => 'postMessage',
43
  ) );
44
  }
45
 
 
 
 
 
 
46
  public static function register_customizer_ui( $wp_customize ) {
47
  $wp_customize->add_section( 'amp_design', array(
48
  'title' => __( 'Design', 'amp' ),
@@ -52,7 +117,7 @@ class AMP_Customizer_Design_Settings {
52
  // Header text color control.
53
  $wp_customize->add_control(
54
  new WP_Customize_Color_Control( $wp_customize, 'amp_header_color', array(
55
- 'settings' => 'amp_customizer[header_color]',
56
  'label' => __( 'Header Text Color', 'amp' ),
57
  'section' => 'amp_design',
58
  'priority' => 10,
@@ -62,14 +127,14 @@ class AMP_Customizer_Design_Settings {
62
  // Header background color control.
63
  $wp_customize->add_control(
64
  new WP_Customize_Color_Control( $wp_customize, 'amp_header_background_color', array(
65
- 'settings' => 'amp_customizer[header_background_color]',
66
  'label' => __( 'Header Background & Link Color', 'amp' ),
67
  'section' => 'amp_design',
68
  'priority' => 20,
69
  ) )
70
  );
71
 
72
- // Background color scheme
73
  $wp_customize->add_control( 'amp_color_scheme', array(
74
  'settings' => 'amp_customizer[color_scheme]',
75
  'label' => __( 'Color Scheme', 'amp' ),
@@ -77,22 +142,80 @@ class AMP_Customizer_Design_Settings {
77
  'type' => 'radio',
78
  'priority' => 30,
79
  'choices' => self::get_color_scheme_names(),
80
- ));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  public static function enqueue_customizer_preview_scripts() {
 
 
84
  wp_enqueue_script(
85
  'amp-customizer-design-preview',
86
  amp_get_asset_url( 'js/amp-customizer-design-preview.js' ),
87
- array( 'amp-customizer' ),
88
  false,
89
  true
90
  );
91
  wp_localize_script( 'amp-customizer-design-preview', 'amp_customizer_design', array(
92
  'color_schemes' => self::get_color_schemes(),
93
  ) );
 
 
 
 
 
 
94
  }
95
 
 
 
 
 
 
 
 
 
96
  public static function append_settings( $settings ) {
97
  $settings = wp_parse_args( $settings, array(
98
  'header_color' => self::DEFAULT_HEADER_COLOR,
@@ -107,6 +230,11 @@ class AMP_Customizer_Design_Settings {
107
  ) );
108
  }
109
 
 
 
 
 
 
110
  protected static function get_color_scheme_names() {
111
  return array(
112
  'light' => __( 'Light', 'amp' ),
@@ -114,17 +242,22 @@ class AMP_Customizer_Design_Settings {
114
  );
115
  }
116
 
 
 
 
 
 
117
  protected static function get_color_schemes() {
118
  return array(
119
  'light' => array(
120
- // Convert colors to greyscale for light theme color; see http://goo.gl/2gDLsp
121
  'theme_color' => '#fff',
122
  'text_color' => '#353535',
123
  'muted_text_color' => '#696969',
124
  'border_color' => '#c2c2c2',
125
  ),
126
  'dark' => array(
127
- // Convert and invert colors to greyscale for dark theme color; see http://goo.gl/uVB2cO
128
  'theme_color' => '#0a0a0a',
129
  'text_color' => '#dedede',
130
  'muted_text_color' => '#b1b1b1',
@@ -133,6 +266,12 @@ class AMP_Customizer_Design_Settings {
133
  );
134
  }
135
 
 
 
 
 
 
 
136
  protected static function get_colors_for_color_scheme( $scheme ) {
137
  $color_schemes = self::get_color_schemes();
138
 
@@ -143,6 +282,12 @@ class AMP_Customizer_Design_Settings {
143
  return $color_schemes[ self::DEFAULT_COLOR_SCHEME ];
144
  }
145
 
 
 
 
 
 
 
146
  public static function sanitize_color_scheme( $value ) {
147
  $schemes = self::get_color_scheme_names();
148
  $scheme_slugs = array_keys( $schemes );
1
  <?php
2
+ /**
3
+ * Class AMP_Customizer_Design_Settings
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Class AMP_Customizer_Design_Settings
10
+ */
11
  class AMP_Customizer_Design_Settings {
12
+
13
+ /**
14
+ * Default header color.
15
+ *
16
+ * @var string
17
+ */
18
  const DEFAULT_HEADER_COLOR = '#fff';
19
+
20
+ /**
21
+ * Default header background color.
22
+ *
23
+ * @var string
24
+ */
25
  const DEFAULT_HEADER_BACKGROUND_COLOR = '#0a89c0';
26
+
27
+ /**
28
+ * Default color scheme.
29
+ *
30
+ * @var string
31
+ */
32
  const DEFAULT_COLOR_SCHEME = 'light';
33
 
34
+ /**
35
+ * Returns whether the AMP design settings are enabled.
36
+ *
37
+ * @since 0.6
38
+ * @return bool AMP Customizer design settings enabled.
39
+ */
40
+ public static function is_amp_customizer_enabled() {
41
+
42
+ /**
43
+ * Filter whether to enable the AMP default template design settings.
44
+ *
45
+ * @since 0.4
46
+ * @since 0.6 This filter now controls whether or not the default settings, controls, and sections are registered for the Customizer. The AMP panel will be registered regardless.
47
+ * @param bool $enable Whether to enable the AMP default template design settings. Default true.
48
+ */
49
+ return apply_filters( 'amp_customizer_is_enabled', true );
50
+ }
51
+
52
+ /**
53
+ * Init.
54
+ */
55
  public static function init() {
56
  add_action( 'amp_customizer_init', array( __CLASS__, 'init_customizer' ) );
57
 
58
+ if ( self::is_amp_customizer_enabled() ) {
59
+ add_filter( 'amp_customizer_get_settings', array( __CLASS__, 'append_settings' ) );
60
+ }
61
  }
62
 
63
+ /**
64
+ * Init customizer.
65
+ */
66
  public static function init_customizer() {
67
+ if ( self::is_amp_customizer_enabled() ) {
68
+ add_action( 'amp_customizer_register_settings', array( __CLASS__, 'register_customizer_settings' ) );
69
+ add_action( 'amp_customizer_register_ui', array( __CLASS__, 'register_customizer_ui' ) );
70
+ add_action( 'amp_customizer_enqueue_preview_scripts', array( __CLASS__, 'enqueue_customizer_preview_scripts' ) );
71
+ }
72
  }
73
 
74
+ /**
75
+ * Register default Customizer settings for AMP.
76
+ *
77
+ * @param WP_Customize_Manager $wp_customize Manager.
78
+ */
79
  public static function register_customizer_settings( $wp_customize ) {
80
+
81
+ // Header text color setting.
82
  $wp_customize->add_setting( 'amp_customizer[header_color]', array(
83
  'type' => 'option',
84
  'default' => self::DEFAULT_HEADER_COLOR,
86
  'transport' => 'postMessage',
87
  ) );
88
 
89
+ // Header background color.
90
  $wp_customize->add_setting( 'amp_customizer[header_background_color]', array(
91
  'type' => 'option',
92
  'default' => self::DEFAULT_HEADER_BACKGROUND_COLOR,
94
  'transport' => 'postMessage',
95
  ) );
96
 
97
+ // Background color scheme.
98
  $wp_customize->add_setting( 'amp_customizer[color_scheme]', array(
99
  'type' => 'option',
100
  'default' => self::DEFAULT_COLOR_SCHEME,
101
+ 'sanitize_callback' => array( __CLASS__, 'sanitize_color_scheme' ),
102
  'transport' => 'postMessage',
103
  ) );
104
  }
105
 
106
+ /**
107
+ * Register default Customizer sections and controls for AMP.
108
+ *
109
+ * @param WP_Customize_Manager $wp_customize Manager.
110
+ */
111
  public static function register_customizer_ui( $wp_customize ) {
112
  $wp_customize->add_section( 'amp_design', array(
113
  'title' => __( 'Design', 'amp' ),
117
  // Header text color control.
118
  $wp_customize->add_control(
119
  new WP_Customize_Color_Control( $wp_customize, 'amp_header_color', array(
120
+ 'settings' => 'amp_customizer[header_color]',
121
  'label' => __( 'Header Text Color', 'amp' ),
122
  'section' => 'amp_design',
123
  'priority' => 10,
127
  // Header background color control.
128
  $wp_customize->add_control(
129
  new WP_Customize_Color_Control( $wp_customize, 'amp_header_background_color', array(
130
+ 'settings' => 'amp_customizer[header_background_color]',
131
  'label' => __( 'Header Background & Link Color', 'amp' ),
132
  'section' => 'amp_design',
133
  'priority' => 20,
134
  ) )
135
  );
136
 
137
+ // Background color scheme.
138
  $wp_customize->add_control( 'amp_color_scheme', array(
139
  'settings' => 'amp_customizer[color_scheme]',
140
  'label' => __( 'Color Scheme', 'amp' ),
142
  'type' => 'radio',
143
  'priority' => 30,
144
  'choices' => self::get_color_scheme_names(),
145
+ ) );
146
+
147
+ // Header.
148
+ $wp_customize->selective_refresh->add_partial( 'amp-wp-header', array(
149
+ 'selector' => '.amp-wp-header',
150
+ 'settings' => array( 'blogname' ), // @todo Site Icon.
151
+ 'render_callback' => array( __CLASS__, 'render_header_bar' ),
152
+ 'fallback_refresh' => false,
153
+ ) );
154
+
155
+ // Header.
156
+ $wp_customize->selective_refresh->add_partial( 'amp-wp-footer', array(
157
+ 'selector' => '.amp-wp-footer',
158
+ 'settings' => array( 'blogname' ),
159
+ 'render_callback' => array( __CLASS__, 'render_footer' ),
160
+ 'fallback_refresh' => false,
161
+ 'container_inclusive' => true,
162
+ ) );
163
+ }
164
+
165
+ /**
166
+ * Render header bar template.
167
+ */
168
+ public static function render_header_bar() {
169
+ if ( is_singular() ) {
170
+ $post_template = new AMP_Post_Template( get_post() );
171
+ $post_template->load_parts( array( 'header-bar' ) );
172
+ }
173
  }
174
 
175
+ /**
176
+ * Render footer template.
177
+ */
178
+ public static function render_footer() {
179
+ if ( is_singular() ) {
180
+ $post_template = new AMP_Post_Template( get_post() );
181
+ $post_template->load_parts( array( 'footer' ) );
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Enqueue scripts for default AMP Customizer preview.
187
+ *
188
+ * @global WP_Customize_Manager $wp_customize
189
+ */
190
  public static function enqueue_customizer_preview_scripts() {
191
+ global $wp_customize;
192
+
193
  wp_enqueue_script(
194
  'amp-customizer-design-preview',
195
  amp_get_asset_url( 'js/amp-customizer-design-preview.js' ),
196
+ array( 'amp-customize-preview' ),
197
  false,
198
  true
199
  );
200
  wp_localize_script( 'amp-customizer-design-preview', 'amp_customizer_design', array(
201
  'color_schemes' => self::get_color_schemes(),
202
  ) );
203
+
204
+ // Prevent a theme's registered blogname partial from causing full page refreshes.
205
+ $blogname_partial = $wp_customize->selective_refresh->get_partial( 'blogname' );
206
+ if ( $blogname_partial ) {
207
+ $blogname_partial->fallback_refresh = false;
208
+ }
209
  }
210
 
211
+ /**
212
+ * Merge default Customizer settings on top of settings for merging into AMP post template.
213
+ *
214
+ * @see AMP_Post_Template::build_customizer_settings()
215
+ *
216
+ * @param array $settings Settings.
217
+ * @return array Merged settings.
218
+ */
219
  public static function append_settings( $settings ) {
220
  $settings = wp_parse_args( $settings, array(
221
  'header_color' => self::DEFAULT_HEADER_COLOR,
230
  ) );
231
  }
232
 
233
+ /**
234
+ * Get color scheme names.
235
+ *
236
+ * @return array Color scheme names.
237
+ */
238
  protected static function get_color_scheme_names() {
239
  return array(
240
  'light' => __( 'Light', 'amp' ),
242
  );
243
  }
244
 
245
+ /**
246
+ * Get color schemes.
247
+ *
248
+ * @return array Color schemes.
249
+ */
250
  protected static function get_color_schemes() {
251
  return array(
252
  'light' => array(
253
+ // Convert colors to greyscale for light theme color; see <http://goo.gl/2gDLsp>.
254
  'theme_color' => '#fff',
255
  'text_color' => '#353535',
256
  'muted_text_color' => '#696969',
257
  'border_color' => '#c2c2c2',
258
  ),
259
  'dark' => array(
260
+ // Convert and invert colors to greyscale for dark theme color; see <http://goo.gl/uVB2cO>.
261
  'theme_color' => '#0a0a0a',
262
  'text_color' => '#dedede',
263
  'muted_text_color' => '#b1b1b1',
266
  );
267
  }
268
 
269
+ /**
270
+ * Get colors for color scheme.
271
+ *
272
+ * @param string $scheme Color scheme.
273
+ * @return array Colors.
274
+ */
275
  protected static function get_colors_for_color_scheme( $scheme ) {
276
  $color_schemes = self::get_color_schemes();
277
 
282
  return $color_schemes[ self::DEFAULT_COLOR_SCHEME ];
283
  }
284
 
285
+ /**
286
+ * Sanitize color scheme.
287
+ *
288
+ * @param string $value Color scheme name.
289
+ * @return string Sanitized name.
290
+ */
291
  public static function sanitize_color_scheme( $value ) {
292
  $schemes = self::get_color_scheme_names();
293
  $scheme_slugs = array_keys( $schemes );
includes/templates/class-amp-content-sanitizer.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class AMP_Content_Sanitizer
4
+ *
5
+ * @package AMP
6
+ */
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 */
38
+ _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) must extend `AMP_Base_Sanitizer`', 'amp' ), esc_html( $sanitizer_class ) ), '0.1' );
39
+ continue;
40
+ }
41
+
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
+
includes/{class-amp-content.php → templates/class-amp-content.php} RENAMED
@@ -1,61 +1,148 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/utils/class-amp-dom-utils.php' );
4
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' );
5
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-base-embed-handler.php' );
6
-
 
 
 
 
7
  class AMP_Content {
 
 
 
 
 
 
8
  private $content;
 
 
 
 
 
 
9
  private $amp_content = '';
 
 
 
 
 
 
10
  private $amp_scripts = array();
 
 
 
 
 
 
11
  private $amp_styles = array();
 
 
 
 
 
 
12
  private $args = array();
 
 
 
 
 
 
13
  private $embed_handler_classes = array();
 
 
 
 
 
 
14
  private $sanitizer_classes = array();
15
 
 
 
 
 
 
 
 
 
16
  public function __construct( $content, $embed_handler_classes, $sanitizer_classes, $args = array() ) {
17
- $this->content = $content;
18
- $this->args = $args;
19
  $this->embed_handler_classes = $embed_handler_classes;
20
- $this->sanitizer_classes = $sanitizer_classes;
21
 
22
  $this->transform();
23
  }
24
 
 
 
 
 
 
25
  public function get_amp_content() {
26
  return $this->amp_content;
27
  }
28
 
 
 
 
 
 
29
  public function get_amp_scripts() {
30
  return $this->amp_scripts;
31
  }
32
 
 
 
 
 
 
33
  public function get_amp_styles() {
34
  return $this->amp_styles;
35
  }
36
 
 
 
 
37
  private function transform() {
38
  $content = $this->content;
39
 
40
- // First, embeds + the_content filter
41
  $embed_handlers = $this->register_embed_handlers();
42
- $content = apply_filters( 'the_content', $content );
43
  $this->unregister_embed_handlers( $embed_handlers );
44
 
45
- // Then, sanitize to strip and/or convert non-amp content
46
  $content = $this->sanitize( $content );
47
 
48
  $this->amp_content = $content;
49
  }
50
 
 
 
 
 
 
51
  private function add_scripts( $scripts ) {
52
  $this->amp_scripts = array_merge( $this->amp_scripts, $scripts );
53
  }
54
 
 
 
 
 
 
55
  private function add_styles( $styles ) {
56
  $this->amp_styles = array_merge( $this->amp_styles, $styles );
57
  }
58
 
 
 
 
 
 
59
  private function register_embed_handlers() {
60
  $embed_handlers = array();
61
 
@@ -63,7 +150,8 @@ class AMP_Content {
63
  $embed_handler = new $embed_handler_class( array_merge( $this->args, $args ) );
64
 
65
  if ( ! is_subclass_of( $embed_handler, 'AMP_Base_Embed_Handler' ) ) {
66
- _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Embed Handler (%s) must extend `AMP_Embed_Handler`', 'amp' ), $embed_handler_class ), '0.1' );
 
67
  continue;
68
  }
69
 
@@ -74,13 +162,25 @@ class AMP_Content {
74
  return $embed_handlers;
75
  }
76
 
 
 
 
 
 
77
  private function unregister_embed_handlers( $embed_handlers ) {
78
  foreach ( $embed_handlers as $embed_handler ) {
79
- $this->add_scripts( $embed_handler->get_scripts() );
80
- $embed_handler->unregister_embed();
81
  }
82
  }
83
 
 
 
 
 
 
 
 
84
  private function sanitize( $content ) {
85
  list( $sanitized_content, $scripts, $styles ) = AMP_Content_Sanitizer::sanitize( $content, $this->sanitizer_classes, $this->args );
86
 
@@ -90,34 +190,3 @@ class AMP_Content {
90
  return $sanitized_content;
91
  }
92
  }
93
-
94
- class AMP_Content_Sanitizer {
95
- public static function sanitize( $content, $sanitizer_classes, $global_args = array() ) {
96
- $scripts = array();
97
- $styles = array();
98
- $dom = AMP_DOM_Utils::get_dom_from_content( $content );
99
-
100
- foreach ( $sanitizer_classes as $sanitizer_class => $args ) {
101
- if ( ! class_exists( $sanitizer_class ) ) {
102
- _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) class does not exist', 'amp' ), esc_html( $sanitizer_class ) ), '0.4.1' );
103
- continue;
104
- }
105
-
106
- $sanitizer = new $sanitizer_class( $dom, array_merge( $global_args, $args ) );
107
-
108
- if ( ! is_subclass_of( $sanitizer, 'AMP_Base_Sanitizer' ) ) {
109
- _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Sanitizer (%s) must extend `AMP_Base_Sanitizer`', 'amp' ), esc_html( $sanitizer_class ) ), '0.1' );
110
- continue;
111
- }
112
-
113
- $sanitizer->sanitize();
114
-
115
- $scripts = array_merge( $scripts, $sanitizer->get_scripts() );
116
- $styles = array_merge( $styles, $sanitizer->get_styles() );
117
- }
118
-
119
- $sanitized_content = AMP_DOM_Utils::get_content_from_dom( $dom );
120
-
121
- return array( $sanitized_content, $scripts, $styles );
122
- }
123
- }
1
  <?php
2
+ /**
3
+ * Class AMP_Content
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Content
10
+ */
11
  class AMP_Content {
12
+
13
+ /**
14
+ * Content.
15
+ *
16
+ * @var string
17
+ */
18
  private $content;
19
+
20
+ /**
21
+ * AMP content.
22
+ *
23
+ * @var string
24
+ */
25
  private $amp_content = '';
26
+
27
+ /**
28
+ * AMP scripts.
29
+ *
30
+ * @var array
31
+ */
32
  private $amp_scripts = array();
33
+
34
+ /**
35
+ * AMP styles.
36
+ *
37
+ * @var array
38
+ */
39
  private $amp_styles = array();
40
+
41
+ /**
42
+ * Args.
43
+ *
44
+ * @var array
45
+ */
46
  private $args = array();
47
+
48
+ /**
49
+ * Embed handler class names.
50
+ *
51
+ * @var string[]
52
+ */
53
  private $embed_handler_classes = array();
54
+
55
+ /**
56
+ * Sanitizer class names.
57
+ *
58
+ * @var string[]
59
+ */
60
  private $sanitizer_classes = array();
61
 
62
+ /**
63
+ * AMP_Content constructor.
64
+ *
65
+ * @param string $content Content.
66
+ * @param string[] $embed_handler_classes Embed handler class names.
67
+ * @param string[] $sanitizer_classes Sanitizer class names.
68
+ * @param array $args Args.
69
+ */
70
  public function __construct( $content, $embed_handler_classes, $sanitizer_classes, $args = array() ) {
71
+ $this->content = $content;
72
+ $this->args = $args;
73
  $this->embed_handler_classes = $embed_handler_classes;
74
+ $this->sanitizer_classes = $sanitizer_classes;
75
 
76
  $this->transform();
77
  }
78
 
79
+ /**
80
+ * Get AMP content.
81
+ *
82
+ * @return string
83
+ */
84
  public function get_amp_content() {
85
  return $this->amp_content;
86
  }
87
 
88
+ /**
89
+ * Get AMP scripts.
90
+ *
91
+ * @return array
92
+ */
93
  public function get_amp_scripts() {
94
  return $this->amp_scripts;
95
  }
96
 
97
+ /**
98
+ * Get AMP styles.
99
+ *
100
+ * @return array
101
+ */
102
  public function get_amp_styles() {
103
  return $this->amp_styles;
104
  }
105
 
106
+ /**
107
+ * Transform.
108
+ */
109
  private function transform() {
110
  $content = $this->content;
111
 
112
+ // First, embeds + the_content filter.
113
  $embed_handlers = $this->register_embed_handlers();
114
+ $content = apply_filters( 'the_content', $content );
115
  $this->unregister_embed_handlers( $embed_handlers );
116
 
117
+ // Then, sanitize to strip and/or convert non-amp content.
118
  $content = $this->sanitize( $content );
119
 
120
  $this->amp_content = $content;
121
  }
122
 
123
+ /**
124
+ * Add scripts.
125
+ *
126
+ * @param array $scripts Scripts.
127
+ */
128
  private function add_scripts( $scripts ) {
129
  $this->amp_scripts = array_merge( $this->amp_scripts, $scripts );
130
  }
131
 
132
+ /**
133
+ * Add styles.
134
+ *
135
+ * @param array $styles Styles.
136
+ */
137
  private function add_styles( $styles ) {
138
  $this->amp_styles = array_merge( $this->amp_styles, $styles );
139
  }
140
 
141
+ /**
142
+ * Register embed handlers.
143
+ *
144
+ * @return array
145
+ */
146
  private function register_embed_handlers() {
147
  $embed_handlers = array();
148
 
150
  $embed_handler = new $embed_handler_class( array_merge( $this->args, $args ) );
151
 
152
  if ( ! is_subclass_of( $embed_handler, 'AMP_Base_Embed_Handler' ) ) {
153
+ /* translators: %s is embed handler */
154
+ _doing_it_wrong( __METHOD__, esc_html( sprintf( __( 'Embed Handler (%s) must extend `AMP_Embed_Handler`', 'amp' ), $embed_handler_class ) ), '0.1' );
155
  continue;
156
  }
157
 
162
  return $embed_handlers;
163
  }
164
 
165
+ /**
166
+ * Unregister embed handlers.
167
+ *
168
+ * @param array $embed_handlers Embed handlers.
169
+ */
170
  private function unregister_embed_handlers( $embed_handlers ) {
171
  foreach ( $embed_handlers as $embed_handler ) {
172
+ $this->add_scripts( $embed_handler->get_scripts() );
173
+ $embed_handler->unregister_embed();
174
  }
175
  }
176
 
177
+ /**
178
+ * Sanitize.
179
+ *
180
+ * @see AMP_Content_Sanitizer::sanitize()
181
+ * @param string $content Content.
182
+ * @return array Sanitized content.
183
+ */
184
  private function sanitize( $content ) {
185
  list( $sanitized_content, $scripts, $styles ) = AMP_Content_Sanitizer::sanitize( $content, $this->sanitizer_classes, $this->args );
186
 
190
  return $sanitized_content;
191
  }
192
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/{class-amp-post-template.php → templates/class-amp-post-template.php} RENAMED
@@ -1,48 +1,100 @@
1
  <?php
2
-
3
- require_once( AMP__DIR__ . '/includes/utils/class-amp-dom-utils.php' );
4
- require_once( AMP__DIR__ . '/includes/utils/class-amp-html-utils.php' );
5
- require_once( AMP__DIR__ . '/includes/utils/class-amp-string-utils.php' );
6
- require_once( AMP__DIR__ . '/includes/utils/class-amp-wp-utils.php' );
7
-
8
- require_once( AMP__DIR__ . '/includes/class-amp-content.php' );
9
-
10
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-style-sanitizer.php' );
11
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-blacklist-sanitizer.php' );
12
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php' );
13
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-img-sanitizer.php' );
14
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-video-sanitizer.php' );
15
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-iframe-sanitizer.php' );
16
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-audio-sanitizer.php' );
17
- require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-playbuzz-sanitizer.php' );
18
-
19
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-twitter-embed.php' );
20
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-youtube-embed.php' );
21
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-dailymotion-embed.php' );
22
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-vimeo-embed.php' );
23
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-soundcloud-embed.php' );
24
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-gallery-embed.php' );
25
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-instagram-embed.php' );
26
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-vine-embed.php' );
27
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-facebook-embed.php' );
28
- require_once( AMP__DIR__ . '/includes/embeds/class-amp-pinterest-embed.php' );
29
-
30
  class AMP_Post_Template {
 
 
 
 
 
 
 
31
  const SITE_ICON_SIZE = 32;
 
 
 
 
 
 
 
32
  const CONTENT_MAX_WIDTH = 600;
33
 
34
- // Needed for 0.3 back-compat
 
 
 
 
 
 
 
35
  const DEFAULT_NAVBAR_BACKGROUND = '#0a89c0';
 
 
 
 
 
 
 
 
 
36
  const DEFAULT_NAVBAR_COLOR = '#fff';
37
 
 
 
 
 
 
 
38
  private $template_dir;
 
 
 
 
 
 
 
39
  private $data;
40
 
41
- public function __construct( $post_id ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  $this->template_dir = apply_filters( 'amp_post_template_dir', AMP__DIR__ . '/templates' );
43
 
44
- $this->ID = $post_id;
45
- $this->post = get_post( $post_id );
 
 
 
 
46
 
47
  $content_max_width = self::CONTENT_MAX_WIDTH;
48
  if ( isset( $GLOBALS['content_width'] ) && $GLOBALS['content_width'] > 0 ) {
@@ -51,33 +103,33 @@ class AMP_Post_Template {
51
  $content_max_width = apply_filters( 'amp_content_max_width', $content_max_width );
52
 
53
  $this->data = array(
54
- 'content_max_width' => $content_max_width,
55
 
56
- 'document_title' => function_exists( 'wp_get_document_title' ) ? wp_get_document_title() : wp_title( '', false ), // back-compat with 4.3
57
- 'canonical_url' => get_permalink( $post_id ),
58
- 'home_url' => home_url(),
59
- 'blog_name' => get_bloginfo( 'name' ),
60
 
61
- 'html_tag_attributes' => array(),
62
- 'body_class' => '',
63
 
64
- 'site_icon_url' => apply_filters( 'amp_site_icon_url', function_exists( 'get_site_icon_url' ) ? get_site_icon_url( self::SITE_ICON_SIZE ) : '' ),
65
  'placeholder_image_url' => amp_get_asset_url( 'images/placeholder-icon.png' ),
66
 
67
- 'featured_image' => false,
68
- 'comments_link_url' => false,
69
- 'comments_link_text' => false,
70
 
71
- 'amp_runtime_script' => 'https://cdn.ampproject.org/v0.js',
72
  'amp_component_scripts' => array(),
73
 
74
- 'customizer_settings' => array(),
75
 
76
- 'font_urls' => array(
77
  'merriweather' => 'https://fonts.googleapis.com/css?family=Merriweather:400,400italic,700,700italic',
78
  ),
79
 
80
- 'post_amp_styles' => array(),
81
 
82
  /**
83
  * Add amp-analytics tags.
@@ -85,31 +137,55 @@ class AMP_Post_Template {
85
  * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting.
86
  *
87
  * @since 0.4
88
- *.
89
- * @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.
90
- * @param object $post The current post.
91
  */
92
- 'amp_analytics' => apply_filters( 'amp_post_template_analytics', array(), $this->post ),
93
- );
94
 
95
  $this->build_post_content();
96
  $this->build_post_data();
97
  $this->build_customizer_settings();
98
  $this->build_html_tag_attributes();
99
 
 
 
 
 
 
 
 
 
100
  $this->data = apply_filters( 'amp_post_template_data', $this->data, $this->post );
101
  }
102
 
 
 
 
 
 
 
 
 
103
  public function get( $property, $default = null ) {
104
  if ( isset( $this->data[ $property ] ) ) {
105
  return $this->data[ $property ];
106
  } else {
107
- _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Called for non-existant key ("%s").', 'amp' ), esc_html( $property ) ), '0.1' );
 
108
  }
109
 
110
  return $default;
111
  }
112
 
 
 
 
 
 
 
 
113
  public function get_customizer_setting( $name, $default = null ) {
114
  $settings = $this->get( 'customizer_settings' );
115
  if ( ! empty( $settings[ $name ] ) ) {
@@ -119,10 +195,20 @@ class AMP_Post_Template {
119
  return $default;
120
  }
121
 
 
 
 
122
  public function load() {
123
- $this->load_parts( array( 'single' ) );
 
 
124
  }
125
 
 
 
 
 
 
126
  public function load_parts( $templates ) {
127
  foreach ( $templates as $template ) {
128
  $file = $this->get_template_path( $template );
@@ -130,18 +216,41 @@ class AMP_Post_Template {
130
  }
131
  }
132
 
 
 
 
 
 
 
133
  private function get_template_path( $template ) {
134
  return sprintf( '%s/%s.php', $this->template_dir, $template );
135
  }
136
 
 
 
 
 
 
137
  private function add_data( $data ) {
138
  $this->data = array_merge( $this->data, $data );
139
  }
140
 
 
 
 
 
 
 
141
  private function add_data_by_key( $key, $value ) {
142
  $this->data[ $key ] = $value;
143
  }
144
 
 
 
 
 
 
 
145
  private function merge_data_for_key( $key, $value ) {
146
  if ( is_array( $this->data[ $key ] ) ) {
147
  $this->data[ $key ] = array_merge( $this->data[ $key ], $value );
@@ -150,45 +259,54 @@ class AMP_Post_Template {
150
  }
151
  }
152
 
 
 
 
 
 
153
  private function build_post_data() {
154
- $post_title = get_the_title( $this->ID );
155
- $post_publish_timestamp = get_the_date( 'U', $this->ID );
156
  $post_modified_timestamp = get_post_modified_time( 'U', false, $this->post );
157
- $post_author = get_userdata( $this->post->post_author );
158
 
159
- $this->add_data( array(
160
- 'post' => $this->post,
161
- 'post_id' => $this->ID,
162
- 'post_title' => $post_title,
163
- 'post_publish_timestamp' => $post_publish_timestamp,
164
- 'post_modified_timestamp' => $post_modified_timestamp,
165
- 'post_author' => $post_author,
166
- ) );
 
 
167
 
168
  $metadata = array(
169
- '@context' => 'http://schema.org',
170
- '@type' => 'BlogPosting',
171
  'mainEntityOfPage' => $this->get( 'canonical_url' ),
172
- 'publisher' => array(
173
  '@type' => 'Organization',
174
- 'name' => $this->get( 'blog_name' ),
175
- ),
176
- 'headline' => $post_title,
177
- 'datePublished' => date( 'c', $post_publish_timestamp ),
178
- 'dateModified' => date( 'c', $post_modified_timestamp ),
179
- 'author' => array(
180
- '@type' => 'Person',
181
- 'name' => $post_author->display_name,
182
  ),
 
 
 
183
  );
 
 
 
 
 
 
184
 
185
  $site_icon_url = $this->get( 'site_icon_url' );
186
  if ( $site_icon_url ) {
187
  $metadata['publisher']['logo'] = array(
188
- '@type' => 'ImageObject',
189
- 'url' => $site_icon_url,
190
  'height' => self::SITE_ICON_SIZE,
191
- 'width' => self::SITE_ICON_SIZE,
192
  );
193
  }
194
 
@@ -203,6 +321,9 @@ class AMP_Post_Template {
203
  $this->build_post_commments_data();
204
  }
205
 
 
 
 
206
  private function build_post_commments_data() {
207
  if ( ! post_type_supports( $this->post->post_type, 'comments' ) ) {
208
  return;
@@ -210,49 +331,59 @@ class AMP_Post_Template {
210
 
211
  $comments_open = comments_open( $this->ID );
212
 
213
- // Don't show link if close and no comments
214
  if ( ! $comments_open
215
  && ! $this->post->comment_count ) {
216
  return;
217
  }
218
 
219
- $comments_link_url = get_comments_link( $this->ID );
220
  $comments_link_text = $comments_open
221
  ? __( 'Leave a Comment', 'amp' )
222
  : __( 'View Comments', 'amp' );
223
 
224
- $this->add_data( array(
225
- 'comments_link_url' => $comments_link_url,
226
- 'comments_link_text' => $comments_link_text,
227
- ) );
 
 
228
  }
229
 
 
 
 
230
  private function build_post_content() {
231
- $amp_content = new AMP_Content( $this->post->post_content,
232
- apply_filters( 'amp_content_embed_handlers', array(
233
- 'AMP_Twitter_Embed_Handler' => array(),
234
- 'AMP_YouTube_Embed_Handler' => array(),
235
- 'AMP_DailyMotion_Embed_Handler' => array(),
236
- 'AMP_Vimeo_Embed_Handler' => array(),
237
- 'AMP_SoundCloud_Embed_Handler' => array(),
238
- 'AMP_Instagram_Embed_Handler' => array(),
239
- 'AMP_Vine_Embed_Handler' => array(),
240
- 'AMP_Facebook_Embed_Handler' => array(),
241
- 'AMP_Pinterest_Embed_Handler' => array(),
242
- 'AMP_Gallery_Embed_Handler' => array(),
243
- ), $this->post ),
244
- apply_filters( 'amp_content_sanitizers', array(
245
- 'AMP_Style_Sanitizer' => array(),
246
- // 'AMP_Blacklist_Sanitizer' => array(),
247
- 'AMP_Img_Sanitizer' => array(),
248
- 'AMP_Video_Sanitizer' => array(),
249
- 'AMP_Audio_Sanitizer' => array(),
250
- 'AMP_Playbuzz_Sanitizer' => array(),
251
- 'AMP_Iframe_Sanitizer' => array(
252
- 'add_placeholder' => true,
253
- ),
254
- 'AMP_Tag_And_Attribute_Sanitizer' => array(),
255
- ), $this->post ),
 
 
 
 
 
256
  array(
257
  'content_max_width' => $this->get( 'content_max_width' ),
258
  )
@@ -263,8 +394,11 @@ class AMP_Post_Template {
263
  $this->merge_data_for_key( 'post_amp_styles', $amp_content->get_amp_styles() );
264
  }
265
 
 
 
 
266
  private function build_post_featured_image() {
267
- $post_id = $this->ID;
268
  $featured_html = get_the_post_thumbnail( $post_id, 'large' );
269
 
270
  // Skip featured image if no featured image is available.
@@ -293,10 +427,12 @@ class AMP_Post_Template {
293
  )
294
  );
295
 
296
- $this->add_data_by_key( 'featured_image', array(
297
- 'amp_html' => $sanitized_html,
298
- 'caption' => $featured_image->post_excerpt,
299
- ) );
 
 
300
 
301
  if ( $featured_scripts ) {
302
  $this->merge_data_for_key( 'amp_component_scripts', $featured_scripts );
@@ -307,6 +443,9 @@ class AMP_Post_Template {
307
  }
308
  }
309
 
 
 
 
310
  private function build_customizer_settings() {
311
  $settings = AMP_Customizer_Settings::get_settings();
312
 
@@ -334,21 +473,23 @@ class AMP_Post_Template {
334
  */
335
  private function get_post_image_metadata() {
336
  $post_image_meta = null;
337
- $post_image_id = false;
338
 
339
  if ( has_post_thumbnail( $this->ID ) ) {
340
  $post_image_id = get_post_thumbnail_id( $this->ID );
341
  } else {
342
- $attached_image_ids = get_posts( array(
343
- 'post_parent' => $this->ID,
344
- 'post_type' => 'attachment',
345
- 'post_mime_type' => 'image',
346
- 'posts_per_page' => 1,
347
- 'orderby' => 'menu_order',
348
- 'order' => 'ASC',
349
- 'fields' => 'ids',
350
- 'suppress_filters' => false,
351
- ) );
 
 
352
 
353
  if ( ! empty( $attached_image_ids ) ) {
354
  $post_image_id = array_shift( $attached_image_ids );
@@ -363,9 +504,9 @@ class AMP_Post_Template {
363
 
364
  if ( is_array( $post_image_src ) ) {
365
  $post_image_meta = array(
366
- '@type' => 'ImageObject',
367
- 'url' => $post_image_src[0],
368
- 'width' => $post_image_src[1],
369
  'height' => $post_image_src[2],
370
  );
371
  }
@@ -373,6 +514,9 @@ class AMP_Post_Template {
373
  return $post_image_meta;
374
  }
375
 
 
 
 
376
  private function build_html_tag_attributes() {
377
  $attributes = array();
378
 
@@ -388,6 +532,12 @@ class AMP_Post_Template {
388
  $this->add_data_by_key( 'html_tag_attributes', $attributes );
389
  }
390
 
 
 
 
 
 
 
391
  private function verify_and_include( $file, $template_type ) {
392
  $located_file = $this->locate_template( $file );
393
  if ( $located_file ) {
@@ -396,20 +546,32 @@ class AMP_Post_Template {
396
 
397
  $file = apply_filters( 'amp_post_template_file', $file, $template_type, $this->post );
398
  if ( ! $this->is_valid_template( $file ) ) {
399
- _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Path validation for template (%s) failed. Path cannot traverse and must be located in `%s`.', 'amp' ), esc_html( $file ), 'WP_CONTENT_DIR' ), '0.1' );
 
400
  return;
401
  }
402
 
403
  do_action( 'amp_post_template_include_' . $template_type, $this );
404
- include( $file );
405
  }
406
 
407
-
 
 
 
 
 
408
  private function locate_template( $file ) {
409
  $search_file = sprintf( 'amp/%s', basename( $file ) );
410
  return locate_template( array( $search_file ), false );
411
  }
412
 
 
 
 
 
 
 
413
  private function is_valid_template( $template ) {
414
  if ( false !== strpos( $template, '..' ) ) {
415
  return false;
1
  <?php
2
+ /**
3
+ * AMP_Post_Template class.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class AMP_Post_Template
10
+ *
11
+ * @since 0.2
12
+ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  class AMP_Post_Template {
14
+
15
+ /**
16
+ * Site icon size.
17
+ *
18
+ * @since 0.2
19
+ * @var int
20
+ */
21
  const SITE_ICON_SIZE = 32;
22
+
23
+ /**
24
+ * Content max width.
25
+ *
26
+ * @since 0.4
27
+ * @var int
28
+ */
29
  const CONTENT_MAX_WIDTH = 600;
30
 
31
+ /**
32
+ * Default navbar background.
33
+ *
34
+ * Needed for 0.3 back-compat
35
+ *
36
+ * @since 0.4
37
+ * @var string
38
+ */
39
  const DEFAULT_NAVBAR_BACKGROUND = '#0a89c0';
40
+
41
+ /**
42
+ * Default navbar color.
43
+ *
44
+ * Needed for 0.3 back-compat
45
+ *
46
+ * @since 0.4
47
+ * @var string
48
+ */
49
  const DEFAULT_NAVBAR_COLOR = '#fff';
50
 
51
+ /**
52
+ * Template directory.
53
+ *
54
+ * @since 0.2
55
+ * @var string
56
+ */
57
  private $template_dir;
58
+
59
+ /**
60
+ * Post template data.
61
+ *
62
+ * @since 0.2
63
+ * @var array
64
+ */
65
  private $data;
66
 
67
+ /**
68
+ * Post ID.
69
+ *
70
+ * @since 0.2
71
+ * @var int
72
+ */
73
+ public $ID;
74
+
75
+ /**
76
+ * Post.
77
+ *
78
+ * @since 0.2
79
+ * @var WP_Post
80
+ */
81
+ public $post;
82
+
83
+ /**
84
+ * AMP_Post_Template constructor.
85
+ *
86
+ * @param WP_Post|int $post Post.
87
+ */
88
+ public function __construct( $post ) {
89
+
90
  $this->template_dir = apply_filters( 'amp_post_template_dir', AMP__DIR__ . '/templates' );
91
 
92
+ if ( $post instanceof WP_Post ) {
93
+ $this->post = $post;
94
+ } else {
95
+ $this->post = get_post( $post );
96
+ }
97
+ $this->ID = $this->post->ID;
98
 
99
  $content_max_width = self::CONTENT_MAX_WIDTH;
100
  if ( isset( $GLOBALS['content_width'] ) && $GLOBALS['content_width'] > 0 ) {
103
  $content_max_width = apply_filters( 'amp_content_max_width', $content_max_width );
104
 
105
  $this->data = array(
106
+ 'content_max_width' => $content_max_width,
107
 
108
+ 'document_title' => function_exists( 'wp_get_document_title' ) ? wp_get_document_title() : wp_title( '', false ), // Back-compat with 4.3.
109
+ 'canonical_url' => get_permalink( $this->ID ),
110
+ 'home_url' => home_url(),
111
+ 'blog_name' => get_bloginfo( 'name' ),
112
 
113
+ 'html_tag_attributes' => array(),
114
+ 'body_class' => '',
115
 
116
+ 'site_icon_url' => apply_filters( 'amp_site_icon_url', function_exists( 'get_site_icon_url' ) ? get_site_icon_url( self::SITE_ICON_SIZE ) : '' ),
117
  'placeholder_image_url' => amp_get_asset_url( 'images/placeholder-icon.png' ),
118
 
119
+ 'featured_image' => false,
120
+ 'comments_link_url' => false,
121
+ 'comments_link_text' => false,
122
 
123
+ 'amp_runtime_script' => 'https://cdn.ampproject.org/v0.js',
124
  'amp_component_scripts' => array(),
125
 
126
+ 'customizer_settings' => array(),
127
 
128
+ 'font_urls' => array(
129
  'merriweather' => 'https://fonts.googleapis.com/css?family=Merriweather:400,400italic,700,700italic',
130
  ),
131
 
132
+ 'post_amp_styles' => array(),
133
 
134
  /**
135
  * Add amp-analytics tags.
137
  * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting.
138
  *
139
  * @since 0.4
140
+ *
141
+ * @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.
142
+ * @param WP_Post $post The current post.
143
  */
144
+ 'amp_analytics' => apply_filters( 'amp_post_template_analytics', array(), $this->post ),
145
+ );
146
 
147
  $this->build_post_content();
148
  $this->build_post_data();
149
  $this->build_customizer_settings();
150
  $this->build_html_tag_attributes();
151
 
152
+ /**
153
+ * Filters AMP template data.
154
+ *
155
+ * @since 0.2
156
+ *
157
+ * @param array $data Template data.
158
+ * @param WP_Post $post Post.
159
+ */
160
  $this->data = apply_filters( 'amp_post_template_data', $this->data, $this->post );
161
  }
162
 
163
+ /**
164
+ * Getter.
165
+ *
166
+ * @param string $property Property name.
167
+ * @param mixed $default Default value.
168
+ *
169
+ * @return mixed Value.
170
+ */
171
  public function get( $property, $default = null ) {
172
  if ( isset( $this->data[ $property ] ) ) {
173
  return $this->data[ $property ];
174
  } else {
175
+ /* translators: %s is key name */
176
+ _doing_it_wrong( __METHOD__, esc_html( sprintf( __( 'Called for non-existent key ("%s").', 'amp' ), $property ) ), '0.1' );
177
  }
178
 
179
  return $default;
180
  }
181
 
182
+ /**
183
+ * Get customizer setting.
184
+ *
185
+ * @param string $name Name.
186
+ * @param mixed $default Default value.
187
+ * @return mixed value.
188
+ */
189
  public function get_customizer_setting( $name, $default = null ) {
190
  $settings = $this->get( 'customizer_settings' );
191
  if ( ! empty( $settings[ $name ] ) ) {
195
  return $default;
196
  }
197
 
198
+ /**
199
+ * Load and print the template parts for the given post.
200
+ */
201
  public function load() {
202
+ global $wp_query;
203
+ $template = is_page() || $wp_query->is_posts_page ? 'page' : 'single';
204
+ $this->load_parts( array( $template ) );
205
  }
206
 
207
+ /**
208
+ * Load template parts.
209
+ *
210
+ * @param string[] $templates Templates.
211
+ */
212
  public function load_parts( $templates ) {
213
  foreach ( $templates as $template ) {
214
  $file = $this->get_template_path( $template );
216
  }
217
  }
218
 
219
+ /**
220
+ * Get template path.
221
+ *
222
+ * @param string $template Template name.
223
+ * @return string Template path.
224
+ */
225
  private function get_template_path( $template ) {
226
  return sprintf( '%s/%s.php', $this->template_dir, $template );
227
  }
228
 
229
+ /**
230
+ * Add data.
231
+ *
232
+ * @param array $data Data.
233
+ */
234
  private function add_data( $data ) {
235
  $this->data = array_merge( $this->data, $data );
236
  }
237
 
238
+ /**
239
+ * Add data by key.
240
+ *
241
+ * @param string $key Key.
242
+ * @param mixed $value Value.
243
+ */
244
  private function add_data_by_key( $key, $value ) {
245
  $this->data[ $key ] = $value;
246
  }
247
 
248
+ /**
249
+ * Merge data for key.
250
+ *
251
+ * @param string $key Key.
252
+ * @param mixed $value Value.
253
+ */
254
  private function merge_data_for_key( $key, $value ) {
255
  if ( is_array( $this->data[ $key ] ) ) {
256
  $this->data[ $key ] = array_merge( $this->data[ $key ], $value );
259
  }
260
  }
261
 
262
+ /**
263
+ * Build post data.
264
+ *
265
+ * @since 0.2
266
+ */
267
  private function build_post_data() {
268
+ $post_title = get_the_title( $this->ID );
269
+ $post_publish_timestamp = get_the_date( 'U', $this->ID );
270
  $post_modified_timestamp = get_post_modified_time( 'U', false, $this->post );
271
+ $post_author = get_userdata( $this->post->post_author );
272
 
273
+ $this->add_data(
274
+ array(
275
+ 'post' => $this->post,
276
+ 'post_id' => $this->ID,
277
+ 'post_title' => $post_title,
278
+ 'post_publish_timestamp' => $post_publish_timestamp,
279
+ 'post_modified_timestamp' => $post_modified_timestamp,
280
+ 'post_author' => $post_author,
281
+ )
282
+ );
283
 
284
  $metadata = array(
285
+ '@context' => 'http://schema.org',
286
+ '@type' => is_page() ? 'WebPage' : 'BlogPosting',
287
  'mainEntityOfPage' => $this->get( 'canonical_url' ),
288
+ 'publisher' => array(
289
  '@type' => 'Organization',
290
+ 'name' => $this->get( 'blog_name' ),
 
 
 
 
 
 
 
291
  ),
292
+ 'headline' => $post_title,
293
+ 'datePublished' => date( 'c', $post_publish_timestamp ),
294
+ 'dateModified' => date( 'c', $post_modified_timestamp ),
295
  );
296
+ if ( $post_author ) {
297
+ $metadata['author'] = array(
298
+ '@type' => 'Person',
299
+ 'name' => html_entity_decode( $post_author->display_name, ENT_QUOTES, get_bloginfo( 'charset' ) ),
300
+ );
301
+ }
302
 
303
  $site_icon_url = $this->get( 'site_icon_url' );
304
  if ( $site_icon_url ) {
305
  $metadata['publisher']['logo'] = array(
306
+ '@type' => 'ImageObject',
307
+ 'url' => $site_icon_url,
308
  'height' => self::SITE_ICON_SIZE,
309
+ 'width' => self::SITE_ICON_SIZE,
310
  );
311
  }
312
 
321
  $this->build_post_commments_data();
322
  }
323
 
324
+ /**
325
+ * Buuild post comments data.
326
+ */
327
  private function build_post_commments_data() {
328
  if ( ! post_type_supports( $this->post->post_type, 'comments' ) ) {
329
  return;
331
 
332
  $comments_open = comments_open( $this->ID );
333
 
334
+ // Don't show link if close and no comments.
335
  if ( ! $comments_open
336
  && ! $this->post->comment_count ) {
337
  return;
338
  }
339
 
340
+ $comments_link_url = get_comments_link( $this->ID );
341
  $comments_link_text = $comments_open
342
  ? __( 'Leave a Comment', 'amp' )
343
  : __( 'View Comments', 'amp' );
344
 
345
+ $this->add_data(
346
+ array(
347
+ 'comments_link_url' => $comments_link_url,
348
+ 'comments_link_text' => $comments_link_text,
349
+ )
350
+ );
351
  }
352
 
353
+ /**
354
+ * Build post content.
355
+ */
356
  private function build_post_content() {
357
+ $amp_content = new AMP_Content(
358
+ $this->post->post_content,
359
+ apply_filters(
360
+ 'amp_content_embed_handlers', array(
361
+ 'AMP_Twitter_Embed_Handler' => array(),
362
+ 'AMP_YouTube_Embed_Handler' => array(),
363
+ 'AMP_DailyMotion_Embed_Handler' => array(),
364
+ 'AMP_Vimeo_Embed_Handler' => array(),
365
+ 'AMP_SoundCloud_Embed_Handler' => array(),
366
+ 'AMP_Instagram_Embed_Handler' => array(),
367
+ 'AMP_Vine_Embed_Handler' => array(),
368
+ 'AMP_Facebook_Embed_Handler' => array(),
369
+ 'AMP_Pinterest_Embed_Handler' => array(),
370
+ 'AMP_Gallery_Embed_Handler' => array(),
371
+ 'WPCOM_AMP_Polldaddy_Embed' => array(),
372
+ ), $this->post
373
+ ),
374
+ apply_filters(
375
+ 'amp_content_sanitizers', array(
376
+ 'AMP_Style_Sanitizer' => array(),
377
+ 'AMP_Img_Sanitizer' => array(),
378
+ 'AMP_Video_Sanitizer' => array(),
379
+ 'AMP_Audio_Sanitizer' => array(),
380
+ 'AMP_Playbuzz_Sanitizer' => array(),
381
+ 'AMP_Iframe_Sanitizer' => array(
382
+ 'add_placeholder' => true,
383
+ ),
384
+ '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.
385
+ ), $this->post
386
+ ),
387
  array(
388
  'content_max_width' => $this->get( 'content_max_width' ),
389
  )
394
  $this->merge_data_for_key( 'post_amp_styles', $amp_content->get_amp_styles() );
395
  }
396
 
397
+ /**
398
+ * Build post featured image.
399
+ */
400
  private function build_post_featured_image() {
401
+ $post_id = $this->ID;
402
  $featured_html = get_the_post_thumbnail( $post_id, 'large' );
403
 
404
  // Skip featured image if no featured image is available.
427
  )
428
  );
429
 
430
+ $this->add_data_by_key(
431
+ 'featured_image', array(
432
+ 'amp_html' => $sanitized_html,
433
+ 'caption' => $featured_image->post_excerpt,
434
+ )
435
+ );
436
 
437
  if ( $featured_scripts ) {
438
  $this->merge_data_for_key( 'amp_component_scripts', $featured_scripts );
443
  }
444
  }
445
 
446
+ /**
447
+ * Build customizer settings.
448
+ */
449
  private function build_customizer_settings() {
450
  $settings = AMP_Customizer_Settings::get_settings();
451
 
473
  */
474
  private function get_post_image_metadata() {
475
  $post_image_meta = null;
476
+ $post_image_id = false;
477
 
478
  if ( has_post_thumbnail( $this->ID ) ) {
479
  $post_image_id = get_post_thumbnail_id( $this->ID );
480
  } else {
481
+ $attached_image_ids = get_posts(
482
+ array(
483
+ 'post_parent' => $this->ID,
484
+ 'post_type' => 'attachment',
485
+ 'post_mime_type' => 'image',
486
+ 'posts_per_page' => 1,
487
+ 'orderby' => 'menu_order',
488
+ 'order' => 'ASC',
489
+ 'fields' => 'ids',
490
+ 'suppress_filters' => false,
491
+ )
492
+ );
493
 
494
  if ( ! empty( $attached_image_ids ) ) {
495
  $post_image_id = array_shift( $attached_image_ids );
504
 
505
  if ( is_array( $post_image_src ) ) {
506
  $post_image_meta = array(
507
+ '@type' => 'ImageObject',
508
+ 'url' => $post_image_src[0],
509
+ 'width' => $post_image_src[1],
510
  'height' => $post_image_src[2],
511
  );
512
  }
514
  return $post_image_meta;
515
  }
516
 
517
+ /**
518
+ * Build HTML tag attributes.
519
+ */
520
  private function build_html_tag_attributes() {
521
  $attributes = array();
522
 
532
  $this->add_data_by_key( 'html_tag_attributes', $attributes );
533
  }
534
 
535
+ /**
536
+ * Verify and include.
537
+ *
538
+ * @param string $file File.
539
+ * @param string $template_type Template type.
540
+ */
541
  private function verify_and_include( $file, $template_type ) {
542
  $located_file = $this->locate_template( $file );
543
  if ( $located_file ) {
546
 
547
  $file = apply_filters( 'amp_post_template_file', $file, $template_type, $this->post );
548
  if ( ! $this->is_valid_template( $file ) ) {
549
+ /* translators: %1$s is template file, %2$s is 'WP_CONTENT_DIR' string. */
550
+ _doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Path validation for template (%1$s) failed. Path cannot traverse and must be located in `%2$s`.', 'amp' ), esc_html( $file ), 'WP_CONTENT_DIR' ), '0.1' );
551
  return;
552
  }
553
 
554
  do_action( 'amp_post_template_include_' . $template_type, $this );
555
+ include $file;
556
  }
557
 
558
+ /**
559
+ * Locate template.
560
+ *
561
+ * @param string $file File.
562
+ * @return string The template filename if one is located.
563
+ */
564
  private function locate_template( $file ) {
565
  $search_file = sprintf( 'amp/%s', basename( $file ) );
566
  return locate_template( array( $search_file ), false );
567
  }
568
 
569
+ /**
570
+ * Is valid template.
571
+ *
572
+ * @param string $template Template name.
573
+ * @return bool Whether valid.
574
+ */
575
  private function is_valid_template( $template ) {
576
  if ( false !== strpos( $template, '..' ) ) {
577
  return false;
includes/utils/class-amp-dom-utils.php CHANGED
@@ -1,14 +1,40 @@
1
  <?php
 
 
 
 
 
2
 
 
 
 
 
 
3
  class AMP_DOM_Utils {
 
 
 
 
 
 
 
 
 
 
 
 
4
  public static function get_dom_from_content( $content ) {
5
  $libxml_previous_state = libxml_use_internal_errors( true );
6
 
7
- $dom = new DOMDocument;
8
- // Wrap in dummy tags, since XML needs one parent node.
9
- // It also makes it easier to loop through nodes.
10
- // We can later use this to extract our nodes.
11
- // Add utf-8 charset so loadHTML does not have problems parsing it. See: http://php.net/manual/en/domdocument.loadhtml.php#78243
 
 
 
 
12
  $result = $dom->loadHTML( '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $content . '</body></html>' );
13
 
14
  libxml_clear_errors();
@@ -21,28 +47,124 @@ class AMP_DOM_Utils {
21
  return $dom;
22
  }
23
 
 
 
 
 
 
 
 
 
 
 
 
24
  public static function get_content_from_dom( $dom ) {
25
- // Only want children of the body tag, since we have a subset of HTML.
26
- $out = '';
 
 
27
  $body = $dom->getElementsByTagName( 'body' )->item( 0 );
28
 
29
- // AMP elements always need closing tags.
30
- // To force them, we can't use saveHTML (node support is 5.3+) and LIBXML_NOEMPTYTAG results in issues with self-closing tags like `br` and `hr`.
31
- // So, we're manually forcing closing tags.
32
- self::recursive_force_closing_tags( $dom, $body );
 
 
 
 
33
 
34
- foreach ( $body->childNodes as $node ) {
35
- $out .= $dom->saveXML( $node );
36
  }
 
37
  return $out;
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  public static function create_node( $dom, $tag, $attributes ) {
41
  $node = $dom->createElement( $tag );
42
  self::add_attributes_to_node( $node, $attributes );
 
43
  return $node;
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
46
  public static function get_node_attributes_as_assoc_array( $node ) {
47
  $attributes = array();
48
  if ( ! $node->hasAttributes() ) {
@@ -52,48 +174,110 @@ class AMP_DOM_Utils {
52
  foreach ( $node->attributes as $attribute ) {
53
  $attributes[ $attribute->nodeName ] = $attribute->nodeValue;
54
  }
 
55
  return $attributes;
56
  }
57
 
 
 
 
 
 
 
 
 
58
  public static function add_attributes_to_node( $node, $attributes ) {
59
  foreach ( $attributes as $name => $value ) {
60
  $node->setAttribute( $name, $value );
61
  }
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
64
  public static function is_node_empty( $node ) {
65
- return false === $node->hasChildNodes()
66
- && empty( $node->textContent );
67
  }
68
 
69
- public static function recursive_force_closing_tags( $dom, $node ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  if ( XML_ELEMENT_NODE !== $node->nodeType ) {
71
  return;
72
  }
73
 
74
  if ( self::is_self_closing_tag( $node->nodeName ) ) {
 
 
 
 
75
  return;
76
  }
77
 
78
  if ( self::is_node_empty( $node ) ) {
79
  $text_node = $dom->createTextNode( '' );
80
  $node->appendChild( $text_node );
 
81
  return;
82
  }
83
 
84
  $num_children = $node->childNodes->length;
85
- for ( $i = $num_children - 1; $i >= 0; $i-- ) {
86
  $child = $node->childNodes->item( $i );
87
  self::recursive_force_closing_tags( $dom, $child );
88
  }
 
89
  }
90
 
 
 
 
 
 
 
 
 
91
  private static function is_self_closing_tag( $tag ) {
92
- // This function is called a lot; the static var prevents having to re-create the array every time.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  static $self_closing_tags;
94
  if ( ! isset( $self_closing_tags ) ) {
95
- // https://www.w3.org/TR/html5/syntax.html#serializing-html-fragments
96
- // Not all are valid AMP, but we include them for completeness.
 
 
97
  $self_closing_tags = array(
98
  'area',
99
  'base',
@@ -115,7 +299,6 @@ class AMP_DOM_Utils {
115
  'wbr',
116
  );
117
  }
118
-
119
- return in_array( $tag, $self_closing_tags, true );
120
  }
121
  }
1
  <?php
2
+ /**
3
+ * Class AMP_DOM_Utils.
4
+ *
5
+ * @package AMP
6
+ */
7
 
8
+ /**
9
+ * Class AMP_DOM_Utils
10
+ *
11
+ * Functionality to simplify working with DOMDocuments and DOMElements.
12
+ */
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();
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
 
68
+ /**
69
+ * The DOMDocument may contain no body. In which case return nothing.
70
+ */
71
+ if ( is_null( $body ) ) {
72
+ return '';
73
+ }
74
+
75
+ $out = '';
76
 
77
+ foreach ( $body->childNodes as $child_node ) {
78
+ $out .= self::get_content_from_dom_node( $dom, $child_node );
79
  }
80
+
81
  return $out;
82
  }
83
 
84
+
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 ) {
97
+ /**
98
+ * Self closing tags regex.
99
+ *
100
+ * @var string Regular expression to match self-closing tags
101
+ * that saveXML() has generated a closing tag for.
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
+ /**
141
+ * Create a new node w/attributes (a DOMElement) and add to the passed DOMDocument.
142
+ *
143
+ * @since 0.2
144
+ *
145
+ * @param DOMDocument $dom A representation of an HTML document to add the new node to.
146
+ * @param string $tag A valid HTML element tag for the element to be added.
147
+ * @param string[] $attributes One of more valid attributes for the new node.
148
+ *
149
+ * @return DOMElement|false The DOMElement for the given $tag, or false on failure
150
+ */
151
  public static function create_node( $dom, $tag, $attributes ) {
152
  $node = $dom->createElement( $tag );
153
  self::add_attributes_to_node( $node, $attributes );
154
+
155
  return $node;
156
  }
157
 
158
+ /**
159
+ * Extract a DOMElement node's HTML element attributes and return as an array.
160
+ *
161
+ * @since 0.2
162
+ *
163
+ * @param DOMNode $node Represents an HTML element for which to extract attributes.
164
+ *
165
+ * @return string[] The attributes for the passed node, or an
166
+ * empty array if it has no attributes.
167
+ */
168
  public static function get_node_attributes_as_assoc_array( $node ) {
169
  $attributes = array();
170
  if ( ! $node->hasAttributes() ) {
174
  foreach ( $node->attributes as $attribute ) {
175
  $attributes[ $attribute->nodeName ] = $attribute->nodeValue;
176
  }
177
+
178
  return $attributes;
179
  }
180
 
181
+ /**
182
+ * Add one or more HTML element attributes to a node's DOMElement.
183
+ *
184
+ * @since 0.2
185
+ *
186
+ * @param DOMElement $node Represents an HTML element.
187
+ * @param string[] $attributes One or more attributes for the node's HTML element.
188
+ */
189
  public static function add_attributes_to_node( $node, $attributes ) {
190
  foreach ( $attributes as $name => $value ) {
191
  $node->setAttribute( $name, $value );
192
  }
193
  }
194
 
195
+ /**
196
+ * Determines if a DOMElement's node is empty or not..
197
+ *
198
+ * @since 0.2
199
+ *
200
+ * @param DOMElement $node Represents an HTML element.
201
+ * @return bool Returns true if the DOMElement has no child nodes and
202
+ * the textContent property of the DOMElement is empty;
203
+ * Otherwise it returns false.
204
+ */
205
  public static function is_node_empty( $node ) {
206
+ return false === $node->hasChildNodes() && empty( $node->textContent );
 
207
  }
208
 
209
+ /**
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 );
222
+ }
223
+
224
  if ( XML_ELEMENT_NODE !== $node->nodeType ) {
225
  return;
226
  }
227
 
228
  if ( self::is_self_closing_tag( $node->nodeName ) ) {
229
+ /*
230
+ * Ensure there is no text content to accidentally force a child
231
+ */
232
+ $node->textContent = null;
233
  return;
234
  }
235
 
236
  if ( self::is_node_empty( $node ) ) {
237
  $text_node = $dom->createTextNode( '' );
238
  $node->appendChild( $text_node );
239
+
240
  return;
241
  }
242
 
243
  $num_children = $node->childNodes->length;
244
+ for ( $i = $num_children - 1; $i >= 0; $i -- ) {
245
  $child = $node->childNodes->item( $i );
246
  self::recursive_force_closing_tags( $dom, $child );
247
  }
248
+
249
  }
250
 
251
+ /**
252
+ * Determines if an HTML element tag is validly a self-closing tag per W3C HTML5 specs.
253
+ *
254
+ * @since 0.2
255
+ *
256
+ * @param string $tag Tag.
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',
299
  'wbr',
300
  );
301
  }
302
+ return $self_closing_tags;
 
303
  }
304
  }
includes/utils/class-amp-image-dimension-extractor.php CHANGED
@@ -172,12 +172,9 @@ class AMP_Image_Dimension_Extractor {
172
  * @param array $images Array to populate with results of image/dimension inspection.
173
  */
174
  private static function fetch_images_via_fast_image( $urls_to_fetch, &$images ) {
175
- if ( ! class_exists( 'FastImage' ) ) {
176
- require_once( AMP__DIR__ . '/includes/lib/fastimage/class-fastimage.php' );
177
- }
178
 
179
  $image = new FastImage();
180
- $urls = array_keys( $urls_to_fetch );
181
 
182
  foreach ( $urls as $url ) {
183
  $result = $image->load( $url );
@@ -185,6 +182,7 @@ class AMP_Image_Dimension_Extractor {
185
  $images[ $url ]['size'] = self::STATUS_IMAGE_EXTRACTION_FAILED;
186
  } else {
187
  $size = $image->getSize();
 
188
  $images[ $url ]['size'] = $size;
189
  }
190
  }
172
  * @param array $images Array to populate with results of image/dimension inspection.
173
  */
174
  private static function fetch_images_via_fast_image( $urls_to_fetch, &$images ) {
 
 
 
175
 
176
  $image = new FastImage();
177
+ $urls = array_keys( $urls_to_fetch );
178
 
179
  foreach ( $urls as $url ) {
180
  $result = $image->load( $url );
182
  $images[ $url ]['size'] = self::STATUS_IMAGE_EXTRACTION_FAILED;
183
  } else {
184
  $size = $image->getSize();
185
+
186
  $images[ $url ]['size'] = $size;
187
  }
188
  }
readme-assets/amp-options-analytics.png DELETED
Binary file
readme-assets/analytics-option-entries.png DELETED
Binary file
readme-assets/invalid-input.png DELETED
Binary file
readme-assets/options-saved.png DELETED
Binary file
readme.md DELETED
@@ -1,684 +0,0 @@
1
- # AMP for WordPress
2
-
3
- ## Overview
4
-
5
- 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.
6
-
7
- 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`
8
-
9
- Note #1: that Pages and archives are not currently supported.
10
-
11
- 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).
12
-
13
- ## Customization / Templating
14
-
15
- The plugin ships with a default template that looks nice and clean and we tried to find a good balance between ease and extensibility when it comes to customization.
16
-
17
- You can tweak small pieces of the template or the entire thing depending on your needs.
18
-
19
- ### Where Do I Put My Code?
20
-
21
- The code snippets below and any other code-level customizations should happen in one of the following locations.
22
-
23
- If you're using an off-the-shelf theme (like from the WordPress.org Theme Directory):
24
-
25
- - A [child theme](https://developer.wordpress.org/themes/advanced-topics/child-themes/).
26
- - A custom plugin that you activate via the Dashboard.
27
- - A [mu-plugin](https://codex.wordpress.org/Must_Use_Plugins).
28
-
29
- If you're using a custom theme:
30
-
31
- - `functions.php` (or via a 'require' call to files that load from `functions.php`).
32
- - Any of the options above.
33
-
34
- ### Theme Mods
35
-
36
- The default template will attempt to draw from various theme mods, such as site icon, if supported by the active theme.
37
-
38
- #### Site Icon
39
-
40
- If you add a site icon, we will automatically replace the WordPress logo in the template.
41
-
42
- If you'd prefer to do it via code:
43
-
44
- ```php
45
- add_filter( 'amp_post_template_data', 'xyz_amp_set_site_icon_url' );
46
-
47
- function xyz_amp_set_site_icon_url( $data ) {
48
- // Ideally a 32x32 image
49
- $data[ 'site_icon_url' ] = get_stylesheet_directory_uri() . '/images/amp-site-icon.png';
50
- return $data;
51
- }
52
- ```
53
-
54
- #### Logo Only
55
-
56
- If you want to hide the site text and just show a logo, use the `amp_post_template_css` action. The following colors the title bar black, hides the site title, and replaces it with a centered logo:
57
-
58
- ```php
59
- add_action( 'amp_post_template_css', 'xyz_amp_additional_css_styles' );
60
-
61
- function xyz_amp_additional_css_styles( $amp_template ) {
62
- // only CSS here please...
63
- ?>
64
- header.amp-wp-header {
65
- padding: 12px 0;
66
- background: #000;
67
- }
68
- header.amp-wp-header a {
69
- background-image: url( 'https://example.com/path/to/logo.png' );
70
- background-repeat: no-repeat;
71
- background-size: contain;
72
- display: block;
73
- height: 28px;
74
- width: 94px;
75
- margin: 0 auto;
76
- text-indent: -9999px;
77
- }
78
- <?php
79
- }
80
- ```
81
-
82
- Note: you will need to adjust the colors and sizes based on your brand.
83
-
84
- ### Template Tweaks
85
-
86
- You can tweak various parts of the template via code.
87
-
88
- #### Featured Image
89
-
90
- The default template displays the featured image. If you don't want to display the featured image in your amp page, use the following code:
91
-
92
- ```php
93
- add_filter( 'amp_post_template_data', 'xyz_amp_remove_featured_image' );
94
-
95
- function xyz_amp_remove_featured_image( $data ) {
96
- $data['featured_image'] = false;
97
- return $data;
98
- }
99
- ```
100
-
101
- #### Content Width
102
-
103
- By default, your theme's `$content_width` value will be used to determine the size of the `amp` content well. You can change this:
104
-
105
- ```php
106
- add_filter( 'amp_content_max_width', 'xyz_amp_change_content_width' );
107
-
108
- function xyz_amp_change_content_width( $content_max_width ) {
109
- return 1200;
110
- }
111
- ```
112
-
113
- #### Template Data
114
-
115
- Use the `amp_post_template_data` filter to override default template data. The following changes the placeholder image used for iframes to a file located in the current theme:
116
-
117
- ```php
118
- add_filter( 'amp_post_template_data', 'xyz_amp_set_custom_placeholder_image' );
119
-
120
- function xyz_set_custom_placeholder_image( $data ) {
121
- $data[ 'placeholder_image_url' ] = get_stylesheet_directory_uri() . '/images/amp-iframe-placeholder.png';
122
- return $data;
123
- }
124
- ```
125
-
126
- Note: The path must pass the default criteria set out by [`validate_file`](https://developer.wordpress.org/reference/functions/validate_file/) and must be somewhere in a subfolder of `WP_CONTENT_DIR`.
127
-
128
- #### Schema.org (JSON) Metadata
129
-
130
- The plugin adds some default metadata to enable ["Rich Snippet" support](https://developers.google.com/structured-data/rich-snippets/articles). You can modify this using the `amp_post_template_metadata` filter. The following changes the type annotation to `NewsArticle` (from the default `BlogPosting`) and overrides the default Publisher Logo.
131
-
132
- ```php
133
- add_filter( 'amp_post_template_metadata', 'xyz_amp_modify_json_metadata', 10, 2 );
134
-
135
- function xyz_amp_modify_json_metadata( $metadata, $post ) {
136
- $metadata['@type'] = 'NewsArticle';
137
-
138
- $metadata['publisher']['logo'] = array(
139
- '@type' => 'ImageObject',
140
- 'url' => get_template_directory_uri() . '/images/my-amp-metadata-logo.png',
141
- 'height' => 60,
142
- 'width' => 600,
143
- );
144
-
145
- return $metadata;
146
- }
147
- ```
148
-
149
- #### Template Meta (Author, Date, etc.)
150
-
151
- For the meta section of the template (i.e. author, date, taxonomies, etc.), you can override templates for the existing sections, remove them, add new ones.
152
-
153
- ##### Example: Override Author Template from Theme
154
-
155
- Create a folder in your theme called `amp` and add a file called `meta-author.php` with the following:
156
-
157
- ```php
158
- <li class="xyz-byline">
159
- <span>Anonymous</span>
160
- </li>
161
- ```
162
-
163
- Replace the contents, as needed.
164
-
165
- ##### Example: Override Taxonomy Template via filter
166
-
167
- This will load the file `t/meta-custom-tax.php` for the `taxonomy` section:
168
-
169
- ```php
170
- add_filter( 'amp_post_template_file', 'xyz_amp_set_custom_tax_meta_template', 10, 3 );
171
-
172
- function xyz_amp_set_custom_tax_meta_template( $file, $type, $post ) {
173
- if ( 'meta-taxonomy' === $type ) {
174
- $file = dirname( __FILE__ ) . '/t/meta-custom-tax.php';
175
- }
176
- return $file;
177
- }
178
- ```
179
-
180
- In `t/meta-custom-tax.php`, you can add something like the following to replace the default category and tags with your custom `author` taxonomy:
181
-
182
- ```php
183
- <li class="xyz-tax-authors">
184
- <?php echo get_the_term_list( $this->get( 'post_id' ), 'xyz-author', '', ', ' ); ?>
185
- </li>
186
- ```
187
-
188
- ##### Example: Remove Author from `header_meta`
189
-
190
- This will completely remove the author section:
191
-
192
- ```php
193
- add_filter( 'amp_post_article_header_meta', 'xyz_amp_remove_author_meta' );
194
-
195
- function xyz_amp_remove_author_meta( $meta_parts ) {
196
- foreach ( array_keys( $meta_parts, 'meta-author', true ) as $key ) {
197
- unset( $meta_parts[ $key ] );
198
- }
199
- return $meta_parts;
200
- }
201
- ```
202
-
203
- ##### Example: Add Comment Count to `footer_meta`
204
-
205
- This adds a new section to display the comment count:
206
-
207
- ```php
208
- add_filter( 'amp_post_article_footer_meta', 'xyz_amp_add_comment_count_meta' );
209
-
210
- function xyz_amp_add_comment_count_meta( $meta_parts ) {
211
- $meta_parts[] = 'xyz-meta-comment-count';
212
- return $meta_parts;
213
- }
214
-
215
- add_filter( 'amp_post_template_file', 'xyz_amp_set_comment_count_meta_path', 10, 3 );
216
-
217
- function xyz_amp_set_comment_count_meta_path( $file, $type, $post ) {
218
- if ( 'xyz-meta-comment-count' === $type ) {
219
- $file = dirname( __FILE__ ) . '/templates/xyz-meta-comment-count.php';
220
- }
221
- return $file;
222
- }
223
- ```
224
-
225
- Then, in `templates/xyz-meta-comment-count.php`:
226
-
227
- ```php
228
- <li>
229
- <?php printf( _n( '%d comment', '%d comments', $this->get( 'post' )->comment_count, 'xyz-text-domain' ) ); ?>
230
- </li>
231
- ```
232
-
233
- #### Custom CSS
234
-
235
- ##### Rule Additions
236
-
237
- If you want to append to the existing CSS rules (e.g. styles for a custom embed handler), you can use the `amp_post_template_css` action:
238
-
239
- ```php
240
- add_action( 'amp_post_template_css', 'xyz_amp_my_additional_css_styles' );
241
-
242
- function xyz_amp_my_additional_css_styles( $amp_template ) {
243
- // only CSS here please...
244
- ?>
245
- .amp-wp-byline amp-img {
246
- border-radius: 0; /* we don't want round avatars! */
247
- }
248
- .my-custom-class {
249
- color: blue;
250
- }
251
- <?php
252
- }
253
- ```
254
-
255
- ##### Completely Override CSS
256
-
257
- If you'd prefer to use your own styles, you can either:
258
-
259
- - Create a folder in your theme called `amp` and add a file called `style.php` with your custom CSS.
260
- - Specify a custom template using the `amp_post_template_file` filter for `'style' === $type`. See the "Override" examples in the "Meta" section for examples.
261
-
262
- Note: the file should only include CSS, not the `<style>` opening and closing tag.
263
-
264
- #### Head and Footer
265
-
266
- If you want to add stuff to the head or footer of the default AMP template, use the `amp_post_template_head` and `amp_post_template_footer` actions.
267
-
268
- ```php
269
- add_action( 'amp_post_template_footer', 'xyz_amp_add_pixel' );
270
-
271
- function xyz_amp_add_pixel( $amp_template ) {
272
- $post_id = $amp_template->get( 'post_id' );
273
- ?>
274
- <amp-pixel src="https://example.com/hi.gif?x=RANDOM"></amp-pixel>
275
- <?php
276
- }
277
- ```
278
-
279
- #### AMP Endpoint
280
-
281
- If you don't want to use the default `/amp` endpoint, use the `amp_query_var` filter to change it to anything else.
282
-
283
- ```php
284
- add_filter( 'amp_query_var' , 'xyz_amp_change_endpoint' );
285
-
286
- function xyz_amp_change_endpoint( $amp_endpoint ) {
287
- return 'foo';
288
- }
289
- ```
290
-
291
- ### Custom Template
292
-
293
- If you want complete control over the look and feel of your AMP content, you can override the default template using the `amp_post_template_file` filter and pass it the path to a custom template:
294
-
295
- ```php
296
- add_filter( 'amp_post_template_file', 'xyz_amp_set_custom_template', 10, 3 );
297
-
298
- function xyz_amp_set_custom_template( $file, $type, $post ) {
299
- if ( 'single' === $type ) {
300
- $file = dirname( __FILE__ ) . '/templates/my-amp-template.php';
301
- }
302
- return $file;
303
- }
304
- ```
305
-
306
- Note: there are some requirements for a custom template:
307
-
308
- * You must trigger the `amp_post_template_head` action in the `<head>` section:
309
-
310
- ```php
311
- do_action( 'amp_post_template_head', $this );
312
- ```
313
-
314
- * You must trigger the `amp_post_template_footer` action right before the `</body>` tag:
315
-
316
- ```php
317
- do_action( 'amp_post_template_footer', $this );
318
- ```
319
-
320
- * Within your `amp-custom` `style` tags, you must trigger the `amp_post_template_css` action:
321
-
322
- ```php
323
- do_action( 'amp_post_template_css', $this );
324
- ```
325
-
326
- * You must include [all required mark-up](https://www.ampproject.org/docs/get_started/create/basic_markup.html) that isn't already output via the `amp_post_template_head` action.
327
-
328
- ## Handling Media
329
-
330
- By default, the plugin attempts to gracefully handle the following media elements in your content:
331
-
332
- - images (converted from `img` => `amp-img` or `amp-anim`)
333
- - videos (converted from `video` => `amp-video`; Note: Flash is not supported)
334
- - audio (converted from `audio` => `amp-audio`)
335
- - iframes (converted from `iframes` => `amp-iframes`)
336
- - YouTube, Instagram, Twitter, and Vine oEmbeds and shortcodes (converted from the embed to the matching `amp-` component)
337
-
338
- For additional media content such as custom shortcodes, oEmbeds or manually inserted embeds, ads, etc. there are several customization options available and outlined below.
339
-
340
- ### Do Nothing
341
-
342
- If your embeds/media use standard iframes, you can choose to do nothing and let the plugin handle things. They should "just work" in most cases.
343
-
344
- ### `the_content` filter
345
-
346
- All existing hooks on `the_content` will continue to work. This can be a good or bad thing. Good, because existing plugin integrations will continue to work. Bad, because not all added content may make sense in an AMP context.
347
-
348
- You can add additional callbacks to `the_content` filter to output additional content as needed. Use the `is_amp_endpoint()` function to check if an AMP version of a post is being viewed. However, we recommend using an Embed Handler instead.
349
-
350
- Caveat: with this method, if you add a custom component that requires inclusion of a script, you will need to add that script manually to the template using the `amp_post_template_head` action.
351
-
352
- ### Update Existing Shortcodes
353
-
354
- In your existing shortcode or oEmbed callbacks, you can branch using the `is_amp_endpoint()` and output customized content for AMP content.
355
-
356
- The same caveat about scripts for custom AMP components applies.
357
-
358
- ### Custom Embed Handler
359
-
360
- Embed Handlers are helper classes to inject AMP-specific content for your oEmbeds and shortcodes.
361
-
362
- Embed Handlers register the embeds they handle using standard WordPress functions such as `add_shortcode`. For working examples, check out the existing implementations for Instagram, Twitter, etc. as guides to build your own.
363
-
364
- While the primary purpose of Embed Handlers is for use with embeds, you can use them for adding AMP-specific `the_content` callbacks as well.
365
-
366
- #### Step 1: Build the Embed Handler
367
-
368
- Your Embed Handler class needs to extend the `AMP_Base_Embed_Handler` class.
369
-
370
- Note: make sure to set proper priorities or remove existing callbacks for your regular content.
371
-
372
- In `classes/class-amp-related-posts-embed.php`:
373
-
374
- ```php
375
- class XYZ_AMP_Related_Posts_Embed extends AMP_Base_Embed_Handler {
376
- public function register_embed() {
377
- // If we have an existing callback we are overriding, remove it.
378
- remove_filter( 'the_content', 'xyz_add_related_posts' );
379
-
380
- // Add our new callback
381
- add_filter( 'the_content', array( $this, 'add_related_posts' ) );
382
- }
383
-
384
- public function unregister_embed() {
385
- // Let's clean up after ourselves, just in case.
386
- add_filter( 'the_content', 'xyz_add_related_posts' );
387
- remove_filter( 'the_content', array( $this, 'add_related_posts' ) );
388
- }
389
-
390
- public function get_scripts() {
391
- return array(
392
- 'amp-mustache' => 'https://cdn.ampproject.org/v0/amp-mustache-0.1.js',
393
- 'amp-list' => 'https://cdn.ampproject.org/v0/amp-list-0.1.js',
394
- );
395
- }
396
-
397
- public function add_related_posts( $content ) {
398
- // See https://github.com/ampproject/amphtml/blob/master/extensions/amp-list/amp-list.md for details on amp-list
399
- $related_posts_list = '
400
- <amp-list src="https://data.com/articles.json?ref=CANONICAL_URL" width=300 height=200 layout=responsive>
401
- <template type="amp-mustache">
402
- <div>
403
- <amp-img src="{{imageUrl}}" width=50 height=50></amp-img>
404
- {{title}}
405
- </div>
406
- </template>
407
- <div overflow role=button aria-label="Show more" class="list-overflow">
408
- Show more
409
- </div>
410
- </amp-list>';
411
-
412
- $content .= $related_posts_list;
413
-
414
- return $content;
415
- }
416
- }
417
- ```
418
-
419
- #### Step 2: Load the Embed Handler
420
-
421
- ```php
422
- add_filter( 'amp_content_embed_handlers', 'xyz_amp_add_related_embed', 10, 2 );
423
-
424
- function xyz_amp_add_related_embed( $embed_handler_classes, $post ) {
425
- require_once( dirname( __FILE__ ) . '/classes/class-amp-related-posts-embed.php' );
426
- $embed_handler_classes[ 'XYZ_AMP_Related_Posts_Embed' ] = array();
427
- return $embed_handler_classes;
428
- }
429
- ```
430
-
431
- ### Custom Sanitizer
432
-
433
- The name "sanitizer" is a bit of a misnomer. These are primarily used internally in the plugin to make your site's content compatible with the amp spec. This involves stripping unsupported tags and attributes and transforming media elements to their matching amp version (e.g. `img` => `amp-img`).
434
-
435
- Sanitizers are pretty versatile and, unlike Embed Handlers -- which work with HTML content as a string -- they can be used to manipulate your post's AMP content using [PHP's `DOM` library](http://php.net/manual/en/book.dom.php). We've included an example that shows you how to use a custom sanitizer to inject ads into your content. You can, of course, do many other things such as add related content.
436
-
437
- #### Step 1: Build the Sanitizer
438
-
439
- Your sanitizer needs to extend the `AMP_Base_Sanitizer`. In `classes/class-ad-inject-sanitizer.php`:
440
-
441
- ```php
442
- class XYZ_AMP_Ad_Injection_Sanitizer extends AMP_Base_Sanitizer {
443
- public function sanitize() {
444
- $body = $this->get_body_node();
445
-
446
- // Build our amp-ad tag
447
- $ad_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-ad', array(
448
- // Taken from example at https://github.com/ampproject/amphtml/blob/master/builtins/amp-ad.md
449
- 'width' => 300,
450
- 'height' => 250,
451
- 'type' => 'a9',
452
- 'data-aax_size' => '300x250',
453
- 'data-aax_pubname' => 'test123',
454
- 'data-aax_src' => '302',
455
- ) );
456
-
457
- // Add a placeholder to show while loading
458
- $fallback_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-img', array(
459
- 'placeholder' => '',
460
- 'layout' => 'fill',
461
- 'src' => 'https://placehold.it/300X250',
462
- ) );
463
- $ad_node->appendChild( $fallback_node );
464
-
465
- // If we have a lot of paragraphs, insert before the 4th one.
466
- // Otherwise, add it to the end.
467
- $p_nodes = $body->getElementsByTagName( 'p' );
468
- if ( $p_nodes->length > 6 ) {
469
- $p_nodes->item( 4 )->parentNode->insertBefore( $ad_node, $p_nodes->item( 4 ));
470
- } else {
471
- $body->appendChild( $ad_node );
472
- }
473
- }
474
- }
475
- ```
476
-
477
- #### Step 2: Load the Sanitizer
478
-
479
- ```php
480
- add_filter( 'amp_content_sanitizers', 'xyz_amp_add_ad_sanitizer', 10, 2 );
481
-
482
- function xyz_amp_add_ad_sanitizer( $sanitizer_classes, $post ) {
483
- require_once( dirname( __FILE__ ) . '/classes/class-ad-inject-sanitizer.php' );
484
- $sanitizer_classes[ 'XYZ_AMP_Ad_Injection_Sanitizer' ] = array(); // the array can be used to pass args to your sanitizer and accessed within the class via `$this->args`
485
- return $sanitizer_classes;
486
- }
487
- ```
488
-
489
- ## Extracting Image Dimensions
490
-
491
- AMP requires images to have width and height attributes. When these attributes aren't present in an image tag, AMP-WP will
492
- attempt to determine them for the image.
493
-
494
- ### Extraction Methods
495
-
496
- #### Concurrent Dimension Extraction - PHP 5.3+ and cURL
497
- If you're using PHP 5.3+ and have the cURL extension installed, AMP-WP will attempt to determine dimensions for all images
498
- that need them concurrently. Only the minimum number of bytes required to determine the dimensions for a given image type
499
- are retrieved. Dimensions are then cached via transients for subsequent requests. This is the fastest and therefore recommended method.
500
- #### Sequential Dimension Extraction - PHP 5.2 or no cURL
501
- If you're using PHP 5.2 or do not have the cURL extension installed, AMP-WP will attempt to determine image dimensions
502
- sequentially. Only the minimum number of bytes required to determine the dimensions for a given image type are retrieved,
503
- but the time it takes to retrieve each image's dimensions sequentially can still add up. Dimensions are then cached via transients for subsequent requests.
504
- #### Custom Dimension Extraction
505
- You can implement your own image dimension extraction method by adding a callback to the **amp_extract_image_dimensions_batch** filter.
506
-
507
- amp_extract_image_dimensions_batch callback functions take a single argument, *$dimensions* by convention, which is a map/array of image urls to either an array containing the
508
- dimensions of the image at the url (if another callback for the filter was able to determine them), or false if the dimensions have yet to be determined, e.g.
509
-
510
- ```php
511
- array(
512
- 'http://i0.wp.com/placehold.it/350x150.png' => array(
513
- 'width' => 350,
514
- 'height' => 150,
515
- ),
516
- 'http://i0.wp.com/placehold.it/1024x768.png' => false,
517
- );
518
- ```
519
- Your custom dimension extraction callback would iterate through the mappings contained in this single argument, determining
520
- dimensions via your custom method for all image url keys whose values are not arrays of dimensions, e.g.
521
- ```php
522
- function my_custom_dimension_extraction_callback( $dimensions ) {
523
- foreach ( $dimensions as $url => $value ) {
524
- // Skip if dimensions have already been determined for this image.
525
- if ( is_array( $value ) ) {
526
- continue;
527
- }
528
- $width = <YOUR CUSTOM CODE TO DETERMINE WIDTH>
529
- $height = <YOUR CUSTOM CODE TO DETERMINE HEIGHT>
530
- $dimensions[ $url ] = array(
531
- 'width' => $width,
532
- 'height' => $height,
533
- );
534
- }
535
-
536
- return $dimensions;
537
- ```
538
- Your callback needs to return $dimensions so that the value either cascades to the next callback that was added to the *amp_extract_image_dimensions_batch* filter or is
539
- returned to the apply_filter() call (if there are no more unprocessed callbacks).
540
-
541
- The default callback provided by WP-AMP described above, *extract_by_downloading_images*, will fire unless explicitly removed, so be sure
542
- to remove it from the callback chain if you don't want it to, e.g.
543
-
544
- ```php
545
- remove_filter( 'amp_extract_image_dimensions_batch', array( 'AMP_Image_Dimension_Extractor', 'extract_by_downloading_images' ), 999, 1 );
546
- ````
547
-
548
- **Note that if you previously added a custom dimension extraction callback to the *amp_extract_image_dimensions* filter,
549
- you need to update it to hook into the *amp_extract_image_dimensions_batch* filter instead and iterate over the key value
550
- pairs in the single argument as per the example above.**
551
-
552
- ## Analytics
553
-
554
- There are two options you can follow to include analytics tags in your posts.
555
-
556
- ### Plugin Analytics Options
557
-
558
- The plugin defines an analytics option to enable the addition of
559
- [amp-analytics](https://www.ampproject.org/docs/reference/components/amp-analytics) in your posts. When the plugin is
560
- active, an AMP top-level menu appears in the Dashboard with one inner sub-menu called 'Analytics':
561
-
562
- ![AMP Options Menu](https://github.com/Automattic/amp-wp/blob/amedina/amp-analytics-customizer/readme-assets/amp-options-analytics.png)
563
-
564
- Selecting the `Analytics` sub-menu in the AMP options menu takes us to an Analytics Options entry page, where we can
565
- define the analytics tags we want to have, by specifying the vendor type (e.g. Parsely), and the associated JSON
566
- configuration.
567
-
568
- ![AMP Options Menu](https://github.com/Automattic/amp-wp/blob/amedina/amp-analytics-customizer/readme-assets/analytics-option-entries.png)
569
-
570
- Notice that the goal of this option of the plugin is to provide a simple mechanism to insert analytics tags;
571
- it provides very simple validation based solely on the validity of the JSON string provided. It is the users
572
- responsibility to make sure that the values in the configuration string and the vendor type used are coherent with
573
- the analytics requirements of their site . Please review the documentation in the [AMP project ](https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/amp-analytics.md) and in [AMPByExample](https://ampbyexample.com/components/amp-analytics/).
574
-
575
- The AMP Analytics options entry form provides a very simple validation feedback mechanism: if the JSON configuration
576
- string entered is invalid (i.e. not valid JSON), an error message (in red) is displayed below the title of the
577
- options window and the entry form is reloaded:
578
-
579
- ![AMP Options Menu](https://github.com/Automattic/amp-wp/blob/amedina/amp-analytics-customizer/readme-assets/invalid-input.png)
580
-
581
- And, if the configuration provided is actually a valid JSON string, a success message (in green) is displayed at the
582
- top of the window below the title, and again the entry form is reloaded.
583
-
584
- ![AMP Options Menu](https://github.com/Automattic/amp-wp/blob/amedina/amp-analytics-customizer/readme-assets/options-saved.png)
585
-
586
- ### Manually
587
-
588
- Alaternatively, you can use the `amp_post_template_analytics` filter:
589
-
590
- ```php
591
- add_filter( 'amp_post_template_analytics', 'xyz_amp_add_custom_analytics' );
592
- function xyz_amp_add_custom_analytics( $analytics ) {
593
- if ( ! is_array( $analytics ) ) {
594
- $analytics = array();
595
- }
596
-
597
- // https://developers.google.com/analytics/devguides/collection/amp-analytics/
598
- $analytics['xyz-googleanalytics'] = array(
599
- 'type' => 'googleanalytics',
600
- 'attributes' => array(
601
- // 'data-credentials' => 'include',
602
- ),
603
- 'config_data' => array(
604
- 'vars' => array(
605
- 'account' => "UA-XXXXX-Y"
606
- ),
607
- 'triggers' => array(
608
- 'trackPageview' => array(
609
- 'on' => 'visible',
610
- 'request' => 'pageview',
611
- ),
612
- ),
613
- ),
614
- );
615
-
616
- // https://www.parsely.com/docs/integration/tracking/google-amp.html
617
- $analytics['xyz-parsely'] = array(
618
- 'type' => 'parsely',
619
- 'attributes' => array(),
620
- 'config_data' => array(
621
- 'vars' => array(
622
- 'apikey' => 'YOUR APIKEY GOES HERE',
623
- )
624
- ),
625
- );
626
-
627
- return $analytics;
628
- }
629
- ```
630
-
631
- Each analytics entry must include a unique array key and the following attributes:
632
-
633
- - `type`: `(string)` one of the [valid vendors](https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/amp-analytics.md#analytics-vendors) for amp-analytics.
634
- - `attributes`: `(array)` any [additional valid attributes](https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/amp-analytics.md#attributes) to add to the `amp-analytics` element.
635
- - `config_data`: `(array)` the [config data](https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/amp-analytics.md#configuration) to include in the `amp-analytics` script tag. This is `json_encode`-d on output.
636
-
637
- ## Custom Post Type Support
638
-
639
- By default, the plugin only creates AMP content for posts. You can add support for other post_types using the post_type parameter used when registering the custom post type (assume our post_type is `xyz-review`):
640
-
641
- ```php
642
- add_action( 'amp_init', 'xyz_amp_add_review_cpt' );
643
- function xyz_amp_add_review_cpt() {
644
- add_post_type_support( 'xyz-review', AMP_QUERY_VAR );
645
- }
646
- ```
647
-
648
- You'll need to flush your rewrite rules after this.
649
-
650
- If you want a custom template for your post type:
651
-
652
- ```php
653
- add_filter( 'amp_post_template_file', 'xyz_amp_set_review_template', 10, 3 );
654
-
655
- function xyz_amp_set_review_template( $file, $type, $post ) {
656
- if ( 'single' === $type && 'xyz-review' === $post->post_type ) {
657
- $file = dirname( __FILE__ ) . '/templates/my-amp-review-template.php';
658
- }
659
- return $file;
660
- }
661
- ```
662
-
663
- We may provide better ways to handle this in the future.
664
-
665
- ## Plugin integrations
666
-
667
- ### Jetpack
668
-
669
- Jetpack integration is baked in. More support for things like Related Posts to come.
670
-
671
- ### Parse.ly
672
-
673
- [Parse.ly's WordPress plugin](https://wordpress.org/plugins/wp-parsely/) automatically tracks AMP pages when enabled along with this plugin.
674
-
675
-
676
- ### Yoast SEO
677
-
678
- If you're using [Yoast SEO](https://wordpress.org/plugins/wordpress-seo/), check out the companion plugin here: https://github.com/Yoast/yoastseo-amp
679
-
680
- ## Compatibility Issues
681
-
682
- The following plugins have been known to cause issues with this plugin:
683
-
684
- - Cloudflare Rocket Loader (modifies the output of the AMP page, which breaks validation.)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,25 +1,34 @@
1
- === AMP ===
2
- Contributors: batmoo, joen, automattic, potatomaster, albertomedina
3
  Tags: amp, mobile
4
  Requires at least: 4.7
5
- Tested up to: 4.8
6
- Stable tag: 0.5.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
 
9
 
10
  Enable Accelerated Mobile Pages (AMP) on your WordPress site.
11
 
12
  == Description ==
13
 
14
- This plugin adds support for the [Accelerated Mobile Pages](https://www.ampproject.org) (AMP) Project, which is an an open source initiative that aims to provide mobile optimized content that can load instantly everywhere.
15
 
16
  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`
17
 
18
- Note #1: that Pages and archives are not currently supported. Pages support is being worked on.
19
 
20
  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).
21
 
22
- Follow along with or contribute to the development of this plugin at https://github.com/Automattic/amp-wp
 
 
 
 
 
 
 
 
23
 
24
  == Installation ==
25
 
@@ -27,31 +36,23 @@ Follow along with or contribute to the development of this plugin at https://git
27
  1. Activate the plugin through the 'Plugins' menu in WordPress
28
  1. You may need to refresh your permalinks by going to `Settings > Permalinks` and tapping the `Save` button.
29
 
30
- == Frequently Asked Questions ==
31
-
32
- = How do I customize the AMP output for my site? =
33
-
34
- You can tweak a few things like colors from the AMP Customizer. From the Dashboard, go to `Appearance > AMP`.
35
-
36
- For deeper level customizations, please see the readme at https://github.com/Automattic/amp-wp/blob/master/readme.md
37
-
38
- = What about ads and shortcodes and such? =
39
-
40
- Check out https://github.com/Automattic/amp-wp/blob/master/readme.md#handling-media
41
-
42
- = What about analytics? =
43
-
44
- Many plugins are adding AMP support already. If you handling analytics yourself, please see https://github.com/Automattic/amp-wp/blob/master/readme.md#analytics
45
-
46
- = Google Webmaster Tools is reporting validation errors for my site. How do I fix them? =
47
-
48
- The best place to start is to open a new discussion in the [support forum](https://wordpress.org/support/plugin/amp) with details on what the specific validation error is.
49
 
50
- = Why aren't Pages supported yet =
51
 
52
- A wise green Yoda once said, "Patience you must have, my young padawan." We're working on it :)
 
 
 
 
 
 
 
 
 
 
53
 
54
- == Changelog ==
55
 
56
  = 0.5.1 (2017-08-17) =
57
 
@@ -167,11 +168,11 @@ A wise green Yoda once said, "Patience you must have, my young padawan." We're w
167
 
168
  * Breaking change: The new template has changes to markup, class names, and styles that may not work with existing customizations. If you want to stay on the old template for now, you can use the following code snippet:
169
 
170
- ```
171
  if ( function_exists( 'amp_backcompat_use_v03_templates' ) ) {
172
- amp_backcompat_use_v03_templates();
173
  }
174
- ```
175
 
176
  For more details, please see https://wordpress.org/support/topic/v0-4-whats-new-and-possible-breaking-changes/
177
 
1
+ === AMP for WordPress ===
2
+ Contributors: batmoo, joen, automattic, potatomaster, albertomedina, google, xwp, westonruter
3
  Tags: amp, mobile
4
  Requires at least: 4.7
5
+ Tested up to: 4.9
6
+ Stable tag: 0.6.0
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
+
25
+ == Screenshots ==
26
+
27
+ 1. Post rendered in AMP template.
28
+ 1. Customizing appearance of AMP template.
29
+ 1. Article from New York Post showing customized AMP template.
30
+ 1. Article from TNW showing customized AMP template.
31
+ 1. Article from Halfbrick showing customized AMP template.
32
 
33
  == Installation ==
34
 
36
  1. Activate the plugin through the 'Plugins' menu in WordPress
37
  1. You may need to refresh your permalinks by going to `Settings > Permalinks` and tapping the `Save` button.
38
 
39
+ == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ = 0.6.0 (2018-01-23) =
42
 
43
+ - Add support for the "page" post type. A new `page.php` is introduced with template parts factored out (`html-start.php`, `header.php`, `footer.php`, `html-end.php`) and re-used from `single.php`. Note that AMP URLs will end in `?amp` instead of `/amp/`. See [#825](https://github.com/Automattic/amp-wp/pull/825). Props technosailor, ThierryA, westonruter.
44
+ - Add AMP post preview button alongside non-AMP preview button. See [#813](https://github.com/Automattic/amp-wp/pull/813). Props ThierryA, westonruter.
45
+ - Add ability to disable AMP on a per-post basis via toggle in publish metabox. See [#813](https://github.com/Automattic/amp-wp/pull/813). Props ThierryA, westonruter.
46
+ - Add AMP settings admin screen for managing which post types have AMP support, eliminating the requirement to add `add_post_type_support()` calls in theme or plugin. See [#811](https://github.com/Automattic/amp-wp/pull/811). Props ThierryA, westonruter.
47
+ - Add generator meta tag for AMP. See [#810](https://github.com/Automattic/amp-wp/pull/810). Props vaporwavre.
48
+ - Add code quality checking via phpcs, eslint, jscs, and jshint. See [#795](https://github.com/Automattic/amp-wp/pull/795). Props westonruter.
49
+ - Add autoloader to reduce complexity. See [#828](https://github.com/Automattic/amp-wp/pull/828). Props mikeschinkel, westonruter, ThierryA.
50
+ - 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.
51
+ - Merge AMP Customizer into main Customizer. See [#819](https://github.com/Automattic/amp-wp/pull/819). Props kaitnyl, westonruter.
52
+ - 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.
53
+ - Remove erroneous hash from id on amp-wp-header. See [#853](https://github.com/Automattic/amp-wp/pull/853). Props eshannon3.
54
 
55
+ See [0.6 milestone](https://github.com/Automattic/amp-wp/milestone/5?closed=1).
56
 
57
  = 0.5.1 (2017-08-17) =
58
 
168
 
169
  * Breaking change: The new template has changes to markup, class names, and styles that may not work with existing customizations. If you want to stay on the old template for now, you can use the following code snippet:
170
 
171
+ <pre lang="php">
172
  if ( function_exists( 'amp_backcompat_use_v03_templates' ) ) {
173
+ amp_backcompat_use_v03_templates();
174
  }
175
+ </pre>
176
 
177
  For more details, please see https://wordpress.org/support/topic/v0-4-whats-new-and-possible-breaking-changes/
178
 
screenshot-1.png DELETED
Binary file
screenshot-2.png DELETED
Binary file
screenshot-3.png DELETED
Binary file
screenshot-4.png DELETED
Binary file
screenshot-5.png DELETED
Binary file
templates/admin/amp-status.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * AMP status option in the submit meta box.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ // Check referrer.
9
+ if ( ! ( $this instanceof AMP_Post_Meta_Box ) ) {
10
+ return;
11
+ }
12
+
13
+ /**
14
+ * Inherited template vars.
15
+ *
16
+ * @var array $labels Labels for enabled or disabled.
17
+ * @var string $status Enabled or disabled.
18
+ * @var array $errors Support errors.
19
+ */
20
+ ?>
21
+ <div class="misc-pub-section misc-amp-status">
22
+ <span class="amp-icon"></span>
23
+ <?php esc_html_e( 'AMP:', 'amp' ); ?>
24
+ <strong class="amp-status-text"><?php echo esc_html( $labels[ $status ] ); ?></strong>
25
+ <a href="#amp_status" class="edit-amp-status hide-if-no-js" role="button">
26
+ <span aria-hidden="true"><?php esc_html_e( 'Edit', 'amp' ); ?></span>
27
+ <span class="screen-reader-text"><?php esc_html_e( 'Edit Status', 'amp' ); ?></span>
28
+ </a>
29
+ <div id="amp-status-select" class="hide-if-js" data-amp-status="<?php echo esc_attr( $status ); ?>">
30
+ <?php if ( empty( $errors ) ) : ?>
31
+ <fieldset>
32
+ <input id="amp-status-enabled" type="radio" name="<?php echo esc_attr( self::STATUS_INPUT_NAME ); ?>" value="<?php echo esc_attr( self::ENABLED_STATUS ); ?>" <?php checked( self::ENABLED_STATUS, $status ); ?>>
33
+ <label for="amp-status-enabled" class="selectit"><?php echo esc_html( $labels['enabled'] ); ?></label>
34
+ <br />
35
+ <input id="amp-status-disabled" type="radio" name="<?php echo esc_attr( self::STATUS_INPUT_NAME ); ?>" value="<?php echo esc_attr( self::DISABLED_STATUS ); ?>" <?php checked( self::DISABLED_STATUS, $status ); ?>>
36
+ <label for="amp-status-disabled" class="selectit"><?php echo esc_html( $labels['disabled'] ); ?></label>
37
+ <br />
38
+ <?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME ); ?>
39
+ </fieldset>
40
+ <?php else : ?>
41
+ <div class="inline notice notice-warning notice-alt">
42
+ <p>
43
+ <?php
44
+ $support_errors_codes = AMP_Post_Type_Support::get_support_errors( $post );
45
+ $support_errors = array();
46
+ if ( in_array( 'password-protected', $support_errors_codes, true ) ) {
47
+ $support_errors[] = __( 'AMP cannot be enabled on password protected posts.', 'amp' );
48
+ }
49
+ if ( in_array( 'post-type-support', $support_errors_codes, true ) ) {
50
+ /* translators: %s is URL to AMP settings screen */
51
+ $support_errors[] = wp_kses_post( sprintf( __( 'AMP cannot be enabled because this <a href="%s">post type does not support it</a>.', 'amp' ), admin_url( 'admin.php?page=amp-options' ) ) );
52
+ }
53
+ if ( in_array( 'skip-post', $support_errors_codes, true ) ) {
54
+ $support_errors[] = __( 'A plugin or theme has disabled AMP support.', 'amp' );
55
+ }
56
+ if ( count( array_diff( $support_errors_codes, array( 'page-on-front', 'page-for-posts', 'password-protected', 'post-type-support', 'skip-post' ) ) ) > 0 ) {
57
+ $support_errors[] = __( 'Unavailable for an unknown reason.', 'amp' );
58
+ }
59
+ echo implode( ' ', $support_errors ); // WPCS: xss ok.
60
+ ?>
61
+ </p>
62
+ </div>
63
+ <?php endif; ?>
64
+ <div class="amp-status-actions">
65
+ <?php if ( empty( $errors ) ) : ?>
66
+ <a href="#amp_status" class="save-amp-status hide-if-no-js button"><?php esc_html_e( 'OK', 'amp' ); ?></a>
67
+ <?php endif; ?>
68
+ <a href="#amp_status" class="cancel-amp-status hide-if-no-js button-cancel"><?php esc_html_e( 'Cancel', 'amp' ); ?></a>
69
+ </div>
70
+ </div>
71
+ </div>
templates/footer.php CHANGED
@@ -1,8 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <footer class="amp-wp-footer">
2
  <div>
3
- <h2><?php echo esc_html( $this->get( 'blog_name' ) ); ?></h2>
4
  <p>
5
- <a href="<?php echo esc_url( esc_html__( 'https://wordpress.org/', 'amp' ) ); ?>"><?php echo esc_html( sprintf( __( 'Powered by %s', 'amp' ), 'WordPress' ) ); ?></a>
 
 
 
 
 
6
  </p>
7
  <a href="#top" class="back-to-top"><?php esc_html_e( 'Back to top', 'amp' ); ?></a>
8
  </div>
1
+ <?php
2
+ /**
3
+ * Footer template part.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
+ ?>
14
  <footer class="amp-wp-footer">
15
  <div>
16
+ <h2><?php echo esc_html( wptexturize( $this->get( 'blog_name' ) ) ); ?></h2>
17
  <p>
18
+ <a href="<?php echo esc_url( esc_html__( 'https://wordpress.org/', 'amp' ) ); ?>">
19
+ <?php
20
+ // translators: %s is WordPress.
21
+ echo esc_html( sprintf( __( 'Powered by %s', 'amp' ), 'WordPress' ) );
22
+ ?>
23
+ </a>
24
  </p>
25
  <a href="#top" class="back-to-top"><?php esc_html_e( 'Back to top', 'amp' ); ?></a>
26
  </div>
templates/header-bar.php CHANGED
@@ -1,11 +1,21 @@
1
- <header id="#top" class="amp-wp-header">
 
 
 
 
 
 
 
 
2
  <div>
3
  <a href="<?php echo esc_url( $this->get( 'home_url' ) ); ?>">
4
- <?php $site_icon_url = $this->get( 'site_icon_url' );
5
- if ( $site_icon_url ) : ?>
6
  <amp-img src="<?php echo esc_url( $site_icon_url ); ?>" width="32" height="32" class="amp-wp-site-icon"></amp-img>
7
  <?php endif; ?>
8
- <?php echo esc_html( $this->get( 'blog_name' ) ); ?>
 
 
9
  </a>
10
  </div>
11
  </header>
1
+ <?php
2
+ /**
3
+ * Header bar template part.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ ?>
9
+ <header id="top" class="amp-wp-header">
10
  <div>
11
  <a href="<?php echo esc_url( $this->get( 'home_url' ) ); ?>">
12
+ <?php $site_icon_url = $this->get( 'site_icon_url' ); ?>
13
+ <?php if ( $site_icon_url ) : ?>
14
  <amp-img src="<?php echo esc_url( $site_icon_url ); ?>" width="32" height="32" class="amp-wp-site-icon"></amp-img>
15
  <?php endif; ?>
16
+ <span class="amp-site-title">
17
+ <?php echo esc_html( wptexturize( $this->get( 'blog_name' ) ) ); ?>
18
+ </span>
19
  </a>
20
  </div>
21
  </header>
templates/header.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Header template part.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
+
14
+ $this->load_parts( array( 'header-bar' ) );
templates/html-end.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * HTML end template part.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
+ ?>
14
+
15
+ <?php do_action( 'amp_post_template_footer', $this ); ?>
16
+
17
+ </body>
18
+ </html>
templates/html-start.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * HTML start template part.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
+ ?>
14
+ <!doctype html>
15
+ <html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); // WPCS: XSS ok. ?>>
16
+ <head>
17
+ <meta charset="utf-8">
18
+ <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
19
+ <?php do_action( 'amp_post_template_head', $this ); ?>
20
+ <style amp-custom>
21
+ <?php $this->load_parts( array( 'style' ) ); ?>
22
+ <?php do_action( 'amp_post_template_css', $this ); ?>
23
+ </style>
24
+ </head>
25
+
26
+ <body class="<?php echo esc_attr( $this->get( 'body_class' ) ); ?>">
templates/page.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Page view template.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
+
14
+ $this->load_parts( array( 'html-start' ) );
15
+ ?>
16
+
17
+ <?php $this->load_parts( array( 'header' ) ); ?>
18
+
19
+ <article class="amp-wp-article">
20
+ <header class="amp-wp-article-header">
21
+ <h1 class="amp-wp-title"><?php echo esc_html( $this->get( 'post_title' ) ); ?></h1>
22
+ </header>
23
+
24
+ <?php $this->load_parts( array( 'featured-image' ) ); ?>
25
+
26
+ <div class="amp-wp-article-content">
27
+ <?php echo $this->get( 'post_amp_content' ); // WPCS: XSS ok. Handled in AMP_Content::transform(). ?>
28
+ </div>
29
+ </article>
30
+
31
+ <?php $this->load_parts( array( 'footer' ) ); ?>
32
+
33
+ <?php
34
+ $this->load_parts( array( 'html-end' ) );
templates/single.php CHANGED
@@ -1,41 +1,39 @@
1
- <!doctype html>
2
- <html amp <?php echo AMP_HTML_Utils::build_attributes_string( $this->get( 'html_tag_attributes' ) ); ?>>
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
6
- <?php do_action( 'amp_post_template_head', $this ); ?>
7
- <style amp-custom>
8
- <?php $this->load_parts( array( 'style' ) ); ?>
9
- <?php do_action( 'amp_post_template_css', $this ); ?>
10
- </style>
11
- </head>
12
-
13
- <body class="<?php echo esc_attr( $this->get( 'body_class' ) ); ?>">
14
-
15
- <?php $this->load_parts( array( 'header-bar' ) ); ?>
16
 
17
- <article class="amp-wp-article">
 
 
 
 
 
 
 
 
 
18
 
 
19
  <header class="amp-wp-article-header">
20
- <h1 class="amp-wp-title"><?php echo wp_kses_data( $this->get( 'post_title' ) ); ?></h1>
21
  <?php $this->load_parts( apply_filters( 'amp_post_article_header_meta', array( 'meta-author', 'meta-time' ) ) ); ?>
22
  </header>
23
 
24
  <?php $this->load_parts( array( 'featured-image' ) ); ?>
25
 
26
  <div class="amp-wp-article-content">
27
- <?php echo $this->get( 'post_amp_content' ); // amphtml content; no kses ?>
28
  </div>
29
 
30
  <footer class="amp-wp-article-footer">
31
  <?php $this->load_parts( apply_filters( 'amp_post_article_footer_meta', array( 'meta-taxonomy', 'meta-comments-link' ) ) ); ?>
32
  </footer>
33
-
34
  </article>
35
 
36
  <?php $this->load_parts( array( 'footer' ) ); ?>
37
 
38
- <?php do_action( 'amp_post_template_footer', $this ); ?>
39
-
40
- </body>
41
- </html>
1
+ <?php
2
+ /**
3
+ * Single view template.
4
+ *
5
+ * @package AMP
6
+ */
 
 
 
 
 
 
 
 
 
7
 
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
+
14
+ $this->load_parts( array( 'html-start' ) );
15
+ ?>
16
+
17
+ <?php $this->load_parts( array( 'header' ) ); ?>
18
 
19
+ <article class="amp-wp-article">
20
  <header class="amp-wp-article-header">
21
+ <h1 class="amp-wp-title"><?php echo esc_html( $this->get( 'post_title' ) ); ?></h1>
22
  <?php $this->load_parts( apply_filters( 'amp_post_article_header_meta', array( 'meta-author', 'meta-time' ) ) ); ?>
23
  </header>
24
 
25
  <?php $this->load_parts( array( 'featured-image' ) ); ?>
26
 
27
  <div class="amp-wp-article-content">
28
+ <?php echo $this->get( 'post_amp_content' ); // WPCS: XSS ok. Handled in AMP_Content::transform(). ?>
29
  </div>
30
 
31
  <footer class="amp-wp-article-footer">
32
  <?php $this->load_parts( apply_filters( 'amp_post_article_footer_meta', array( 'meta-taxonomy', 'meta-comments-link' ) ) ); ?>
33
  </footer>
 
34
  </article>
35
 
36
  <?php $this->load_parts( array( 'footer' ) ); ?>
37
 
38
+ <?php
39
+ $this->load_parts( array( 'html-end' ) );
 
 
templates/style.php CHANGED
@@ -1,8 +1,17 @@
1
  <?php
2
- // Get content width
3
- $content_max_width = absint( $this->get( 'content_max_width' ) );
 
 
 
 
 
 
 
 
 
4
 
5
- // Get template colors
6
  $theme_color = $this->get_customizer_setting( 'theme_color' );
7
  $text_color = $this->get_customizer_setting( 'text_color' );
8
  $muted_text_color = $this->get_customizer_setting( 'muted_text_color' );
@@ -160,7 +169,7 @@ blockquote p:last-child {
160
  display: flex;
161
  flex-wrap: wrap;
162
  justify-content: space-between;
163
- margin: 1.5em 16px 1.5em;
164
  }
165
 
166
  .amp-wp-title {
@@ -180,7 +189,7 @@ blockquote p:last-child {
180
  flex: 2 1 50%;
181
  font-size: .875em;
182
  line-height: 1.5em;
183
- margin: 0;
184
  padding: 0;
185
  }
186
 
1
  <?php
2
+ /**
3
+ * Style template.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Context.
10
+ *
11
+ * @var AMP_Post_Template $this
12
+ */
13
 
14
+ $content_max_width = absint( $this->get( 'content_max_width' ) );
15
  $theme_color = $this->get_customizer_setting( 'theme_color' );
16
  $text_color = $this->get_customizer_setting( 'text_color' );
17
  $muted_text_color = $this->get_customizer_setting( 'muted_text_color' );
169
  display: flex;
170
  flex-wrap: wrap;
171
  justify-content: space-between;
172
+ margin: 1.5em 16px 0;
173
  }
174
 
175
  .amp-wp-title {
189
  flex: 2 1 50%;
190
  font-size: .875em;
191
  line-height: 1.5em;
192
+ margin: 0 0 1.5em;
193
  padding: 0;
194
  }
195
 
wpcom-helper.php ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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' );
18
+
19
+ remove_filter( 'the_title', 'widont' );
20
+
21
+ remove_filter( 'pre_kses', array( 'Filter_Embedded_HTML_Objects', 'filter' ), 11 );
22
+ remove_filter( 'pre_kses', array( 'Filter_Embedded_HTML_Objects', 'maybe_create_links' ), 100 );
23
+ }
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
+ }
31
+ }
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'] ) ) {
41
+ $metadata = wpcom_amp_add_image_to_metadata( $metadata, $post );
42
+ }
43
+
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 );
66
+ }
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 ) ) {
77
+ return wpcom_amp_add_fallback_image_to_metadata( $metadata );
78
+ }
79
+
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
+
110
+ return $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
+ }
147
+ }
148
+ return $dimensions;
149
+ }
150
+
151
+ function wpcom_amp_extract_image_dimensions_from_getimagesize( $dimensions ) {
152
+ if ( ! function_exists( 'require_lib' ) ) {
153
+ return $dimensions;
154
+ }
155
+ require_lib( 'wpcom/imagesize' );
156
+
157
+ foreach ( $dimensions as $url => $value ) {
158
+ if ( is_array( $value ) ) {
159
+ continue;
160
+ }
161
+ $result = wpcom_getimagesize( $url );
162
+ if ( is_array( $result ) ) {
163
+ $dimensions[ $url ] = array(
164
+ 'width' => $result[0],
165
+ 'height' => $result[1],
166
+ );
167
+ }
168
+ }
169
+
170
+ return $dimensions;
171
+ }
wpcom/class-amp-polldaddy-embed.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class WPCOM_AMP_Polldaddy_Embed
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ /**
9
+ * Class WPCOM_AMP_Polldaddy_Embed
10
+ */
11
+ class WPCOM_AMP_Polldaddy_Embed extends AMP_Base_Embed_Handler {
12
+
13
+ /**
14
+ * Register embed.
15
+ */
16
+ public function register_embed() {
17
+ add_shortcode( 'polldaddy', array( $this, 'shortcode' ) );
18
+ add_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10, 3 );
19
+ }
20
+
21
+ /**
22
+ * Unregister embed.
23
+ */
24
+ public function unregister_embed() {
25
+ remove_shortcode( 'polldaddy' );
26
+ remove_filter( 'embed_oembed_html', array( $this, 'filter_embed_oembed_html' ), 10 );
27
+ }
28
+
29
+ /**
30
+ * Shortcode.
31
+ *
32
+ * @param array $attr Shortcode attributes.
33
+ * @return string Shortcode.
34
+ * @global WP_Embed $wp_embed
35
+ */
36
+ public function shortcode( $attr ) {
37
+ global $wp_embed;
38
+
39
+ $output = '';
40
+ $url = 'https://polldaddy.com/';
41
+ if ( ! empty( $attr['poll'] ) ) {
42
+ $url .= 'poll/' . $attr['poll'] . '/';
43
+ } elseif ( ! empty( $attr['survey'] ) ) {
44
+ $url .= 's/' . $attr['survey'] . '/';
45
+ }
46
+
47
+ if ( ! empty( $attr['title'] ) ) {
48
+ $output = $this->render_link( $url, $attr['title'] );
49
+ } elseif ( $url ) {
50
+ $output = $wp_embed->shortcode( $attr, $url );
51
+ }
52
+
53
+ return $output;
54
+ }
55
+
56
+ /**
57
+ * Filter oEmbed HTML for PollDaddy to for AMP output.
58
+ *
59
+ * @param string $cache Cache for oEmbed.
60
+ * @param string $url Embed URL.
61
+ * @param array $attr Shortcode attributes.
62
+ * @return string Embed.
63
+ */
64
+ public function filter_embed_oembed_html( $cache, $url, $attr ) {
65
+ $parsed_url = wp_parse_url( $url );
66
+ if ( false === strpos( $parsed_url['host'], 'polldaddy.com' ) ) {
67
+ return $cache;
68
+ }
69
+
70
+ $output = '';
71
+
72
+ // Poll oEmbed responses include noscript.
73
+ if ( preg_match( '#<noscript>(.+?)</noscript>#', $cache, $matches ) ) {
74
+ $output = $matches[1];
75
+ }
76
+
77
+ if ( empty( $output ) ) {
78
+ if ( ! empty( $attr['title'] ) ) {
79
+ $name = $attr['title'];
80
+ } elseif ( false !== strpos( $url, 'polldaddy.com/s' ) ) {
81
+ $name = __( 'View Survey', 'amp' );
82
+ } else {
83
+ $name = __( 'View Poll', 'amp' );
84
+ }
85
+ $output = $this->render_link( $url, $name );
86
+ }
87
+
88
+ return $output;
89
+ }
90
+
91
+ /**
92
+ * Render poll/survey link.
93
+ *
94
+ * @param string $url Link URL.
95
+ * @param string $title Link Text.
96
+ * @return string Link.
97
+ */
98
+ private function render_link( $url, $title ) {
99
+ return sprintf( '<p><a href="' . esc_url( $url ) . '">' . esc_html( $title ) . '</a></p>' );
100
+ }
101
+ }
wpcom/shortcodes.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Shortcode functions for WordPress.com.
4
+ *
5
+ * @package AMP
6
+ */
7
+
8
+ _deprecated_file( __FILE__, '0.6' );
9
+
10
+ /**
11
+ * Add custom embeds for WordPress.com.
12
+ *
13
+ * @deprecated Now PollDaddy is supported in core AMP.
14
+ * @param array $embed_handler_classes Embed handler classes.
15
+ * @return mixed
16
+ */
17
+ function wpcom_amp_add_custom_embeds( $embed_handler_classes ) {
18
+ _deprecated_function( __FUNCTION__, '0.6' );
19
+ return $embed_handler_classes;
20
+ }