AMP for WordPress - Version 0.5

Version Description

(2017-08-04) =

  • Whitelist Sanitizer: Replace Blacklist Sanitizer with a whitelist-based approach using the AMP spec (props delputnam)
  • Image Dimensions: Replace fastimage with fasterimage for PHP 5.4+. Enables faster downloads and wider support (props gititon)
  • Embed Handlers: Added support for Vimeo, SoundCloud, Pinterest (props amedina) and PlayBuzz (props lysk88)
  • Analytics: UI for easier addition of analytics tags (props amedina)
  • Fix: parse query strings properly (props amyevans)
  • Fix: Old slug redirect for AMP URLs (props rahulsprajapati)
  • Fix: Handle issues with data uri images in CSS (props trepmal)
  • Fix: Add amp-video js for amp-video tags (props ptbello)
  • Fix: Output CSS for feature image (props mjangda)
  • Fix: Fix attribute when adding AMP Mustache lib (props luigitec)
  • Fix: Various documentation updates (props piersb, bhhaskin)
  • Fix: PHP Warnings from register_customizer_ui (props jahvi)
  • Fix: Coding Standards (props paulschreiber)
Download this release

Release Info

Developer batmoo
Plugin Icon 128x128 AMP for WordPress
Version 0.5
Comparing to
See all releases

Code changes from version 0.4.2 to 0.5

Files changed (58) hide show
  1. amp.php +34 -4
  2. assets/images/placeholder-icon.png +0 -0
  3. includes/admin/class-amp-customizer.php +3 -3
  4. includes/admin/functions.php +59 -19
  5. includes/amp-helper-functions.php +8 -3
  6. includes/amp-post-template-actions.php +20 -17
  7. includes/amp-post-template-functions.php +1 -1
  8. includes/class-amp-content.php +3 -3
  9. includes/class-amp-post-template.php +22 -7
  10. includes/embeds/class-amp-dailymotion-embed.php +103 -0
  11. includes/embeds/class-amp-gallery-embed.php +1 -1
  12. includes/embeds/class-amp-instagram-embed.php +1 -1
  13. includes/embeds/class-amp-pinterest-embed.php +60 -0
  14. includes/embeds/class-amp-soundcloud-embed.php +95 -0
  15. includes/embeds/class-amp-vimeo-embed.php +114 -0
  16. includes/embeds/class-amp-vine-embed.php +1 -1
  17. includes/embeds/class-amp-youtube-embed.php +3 -3
  18. includes/lib/class-fastimage.php +0 -253
  19. includes/lib/fasterimage/Exception/InvalidImageException.php +8 -0
  20. includes/lib/fasterimage/ExifParser.php +134 -0
  21. includes/lib/fasterimage/FasterImage.php +187 -0
  22. includes/lib/fasterimage/ImageParser.php +377 -0
  23. includes/lib/fasterimage/Stream/Exception/StreamBufferTooSmallException.php +8 -0
  24. includes/lib/fasterimage/Stream/Stream.php +78 -0
  25. includes/lib/fasterimage/Stream/StreamableInterface.php +39 -0
  26. includes/lib/fasterimage/amp-fasterimage.php +25 -0
  27. includes/lib/fastimage/class-fastimage.php +201 -0
  28. includes/options/class-amp-analytics-options-submenu.php +53 -0
  29. includes/options/class-amp-options-menu.php +54 -0
  30. includes/options/views/class-amp-analytics-options-submenu-page.php +101 -0
  31. includes/options/views/class-amp-options-manager.php +84 -0
  32. includes/options/views/class-amp-options-menu-page.php +17 -0
  33. includes/sanitizers/class-amp-allowed-tags-generated.php +6210 -0
  34. includes/sanitizers/class-amp-base-sanitizer.php +2 -2
  35. includes/sanitizers/class-amp-blacklist-sanitizer.php +12 -14
  36. includes/sanitizers/class-amp-iframe-sanitizer.php +2 -3
  37. includes/sanitizers/class-amp-img-sanitizer.php +68 -27
  38. includes/sanitizers/class-amp-playbuzz-sanitizer.php +104 -0
  39. includes/sanitizers/class-amp-style-sanitizer.php +6 -3
  40. includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php +978 -0
  41. includes/sanitizers/class-amp-video-sanitizer.php +13 -0
  42. includes/settings/class-amp-customizer-design-settings.php +9 -9
  43. includes/utils/class-amp-dom-utils.php +19 -2
  44. includes/utils/class-amp-html-utils.php +12 -0
  45. includes/utils/class-amp-image-dimension-extractor.php +173 -53
  46. includes/utils/class-amp-string-utils.php +1 -1
  47. includes/utils/class-amp-wp-utils.php +61 -0
  48. jetpack-helper.php +2 -2
  49. readme-assets/amp-options-analytics.png +0 -0
  50. readme-assets/analytics-option-entries.png +0 -0
  51. readme-assets/invalid-input.png +0 -0
  52. readme-assets/options-saved.png +0 -0
  53. readme.md +118 -25
  54. readme.txt +21 -5
  55. templates/footer.php +2 -2
  56. templates/header-bar.php +1 -1
  57. templates/meta-author.php +1 -2
  58. templates/style.php +1 -1
amp.php CHANGED
@@ -5,7 +5,7 @@
5
  * Plugin URI: https://github.com/automattic/amp-wp
6
  * Author: Automattic
7
  * Author URI: https://automattic.com
8
- * Version: 0.4.2
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
@@ -13,7 +13,7 @@
13
 
14
  define( 'AMP__FILE__', __FILE__ );
15
  define( 'AMP__DIR__', dirname( __FILE__ ) );
16
- define( 'AMP__VERSION', '0.4.2' );
17
 
18
  require_once( AMP__DIR__ . '/back-compat/back-compat.php' );
19
  require_once( AMP__DIR__ . '/includes/amp-helper-functions.php' );
@@ -61,6 +61,9 @@ function amp_init() {
61
  add_filter( 'request', 'amp_force_query_var_value' );
62
  add_action( 'wp', 'amp_maybe_add_actions' );
63
 
 
 
 
64
  if ( class_exists( 'Jetpack' ) && ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
65
  require_once( AMP__DIR__ . '/jetpack-helper.php' );
66
  }
@@ -114,6 +117,7 @@ function amp_add_frontend_actions() {
114
  function amp_add_post_template_actions() {
115
  require_once( AMP__DIR__ . '/includes/amp-post-template-actions.php' );
116
  require_once( AMP__DIR__ . '/includes/amp-post-template-functions.php' );
 
117
  }
118
 
119
  function amp_prepare_render() {
@@ -121,15 +125,24 @@ function amp_prepare_render() {
121
  }
122
 
123
  function amp_render() {
 
 
 
 
 
 
 
 
 
 
 
124
  amp_load_classes();
125
 
126
- $post_id = get_queried_object_id();
127
  do_action( 'pre_amp_render_post', $post_id );
128
 
129
  amp_add_post_template_actions();
130
  $template = new AMP_Post_Template( $post_id );
131
  $template->load();
132
- exit;
133
  }
134
 
135
  /**
@@ -157,3 +170,20 @@ function _amp_bootstrap_customizer() {
157
  }
158
  }
159
  add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  * Plugin URI: https://github.com/automattic/amp-wp
6
  * Author: Automattic
7
  * Author URI: https://automattic.com
8
+ * Version: 0.5
9
  * Text Domain: amp
10
  * Domain Path: /languages/
11
  * License: GPLv2 or later
13
 
14
  define( 'AMP__FILE__', __FILE__ );
15
  define( 'AMP__DIR__', dirname( __FILE__ ) );
16
+ define( 'AMP__VERSION', '0.5' );
17
 
18
  require_once( AMP__DIR__ . '/back-compat/back-compat.php' );
19
  require_once( AMP__DIR__ . '/includes/amp-helper-functions.php' );
61
  add_filter( 'request', 'amp_force_query_var_value' );
62
  add_action( 'wp', 'amp_maybe_add_actions' );
63
 
64
+ // Redirect the old url of amp page to the updated url.
65
+ add_filter( 'old_slug_redirect_url', 'amp_redirect_old_slug_to_new_url' );
66
+
67
  if ( class_exists( 'Jetpack' ) && ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
68
  require_once( AMP__DIR__ . '/jetpack-helper.php' );
69
  }
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
 
123
  function amp_prepare_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
  /**
170
  }
171
  }
172
  add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 );
173
+
174
+ /**
175
+ * Redirects the old AMP URL to the new AMP URL.
176
+ * If post slug is updated the amp page with old post slug will be redirected to the updated url.
177
+ *
178
+ * @param string $link New URL of the post.
179
+ *
180
+ * @return string $link URL to be redirected.
181
+ */
182
+ function amp_redirect_old_slug_to_new_url( $link ) {
183
+
184
+ if ( is_amp_endpoint() ) {
185
+ $link = trailingslashit( trailingslashit( $link ) . AMP_QUERY_VAR );
186
+ }
187
+
188
+ return $link;
189
+ }
assets/images/placeholder-icon.png CHANGED
Binary file
includes/admin/class-amp-customizer.php CHANGED
@@ -46,7 +46,7 @@ class AMP_Template_Customizer {
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'] ) ) {
50
  $wp_customize->set_preview_url( amp_admin_get_preview_permalink() );
51
  }
52
 
@@ -156,7 +156,7 @@ class AMP_Template_Customizer {
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
  }
@@ -165,6 +165,6 @@ class AMP_Template_Customizer {
165
  }
166
 
167
  public static function is_amp_customizer() {
168
- return ! empty( $_REQUEST[ AMP_CUSTOMIZER_QUERY_VAR ] );
169
  }
170
  }
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
 
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
  }
165
  }
166
 
167
  public static function is_amp_customizer() {
168
+ return ! empty( $_REQUEST[ AMP_CUSTOMIZER_QUERY_VAR ] ); // input var ok
169
  }
170
  }
includes/admin/functions.php CHANGED
@@ -1,6 +1,9 @@
1
  <?php
2
  // Callbacks for adding AMP-related things to the admin.
3
 
 
 
 
4
  define( 'AMP_CUSTOMIZER_QUERY_VAR', 'customize_amp' );
5
 
6
  /**
@@ -22,6 +25,34 @@ function amp_init_customizer() {
22
  add_action( 'admin_menu', 'amp_add_customizer_link' );
23
  }
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  /**
26
  * Registers a submenu page to access the AMP template editor panel in the Customizer.
27
  */
@@ -34,7 +65,7 @@ function amp_add_customizer_link() {
34
  ), 'customize.php' );
35
 
36
  // Add the theme page.
37
- $page = add_theme_page(
38
  __( 'AMP', 'amp' ),
39
  __( 'AMP', 'amp' ),
40
  'edit_theme_options',
@@ -42,30 +73,39 @@ function amp_add_customizer_link() {
42
  );
43
  }
44
 
45
- function amp_admin_get_preview_permalink() {
46
- /**
47
- * Filter the post type to retrieve the latest of for use in the AMP template customizer.
48
- *
49
- * @param string $post_type Post type slug. Default 'post'.
50
- */
51
- $post_type = (string) apply_filters( 'amp_customizer_post_type', 'post' );
52
 
53
- if ( ! post_type_supports( $post_type, 'amp' ) ) {
 
54
  return;
55
  }
56
 
57
- $post_ids = get_posts( array(
58
- 'post_status' => 'publish',
59
- 'post_type' => $post_type,
60
- 'posts_per_page' => 1,
61
- 'fields' => 'ids',
62
- ) );
63
 
64
- if ( empty( $post_ids ) ) {
65
- return false;
 
 
 
66
  }
67
 
68
- $post_id = $post_ids[0];
 
 
 
 
 
 
69
 
70
- return amp_get_permalink( $post_id );
71
  }
 
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
  /**
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.
31
+ *
32
+ * @param string $post_type Post type slug. Default 'post'.
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 ) ) {
48
+ return false;
49
+ }
50
+
51
+ $post_id = $post_ids[0];
52
+
53
+ return amp_get_permalink( $post_id );
54
+ }
55
+
56
  /**
57
  * Registers a submenu page to access the AMP template editor panel in the Customizer.
58
  */
65
  ), 'customize.php' );
66
 
67
  // Add the theme page.
68
+ add_theme_page(
69
  __( 'AMP', 'amp' ),
70
  __( 'AMP', 'amp' ),
71
  'edit_theme_options',
73
  );
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
+
97
+ if ( ! $analytics_entries ) {
98
+ return $analytics;
99
  }
100
 
101
+ foreach ( $analytics_entries as $entry_id => $entry ) {
102
+ $analytics[ $entry_id ] = array(
103
+ 'type' => $entry['type'],
104
+ 'attributes' => array(),
105
+ 'config_data' => json_decode( $entry['config'] ),
106
+ );
107
+ }
108
 
109
+ return $analytics;
110
  }
111
+ add_filter( 'amp_post_template_analytics', 'amp_add_custom_analytics' );
includes/amp-helper-functions.php CHANGED
@@ -7,10 +7,11 @@ function amp_get_permalink( $post_id ) {
7
  return $pre_url;
8
  }
9
 
10
- if ( '' != get_option( 'permalink_structure' ) ) {
11
- $amp_url = trailingslashit( get_permalink( $post_id ) ) . user_trailingslashit( AMP_QUERY_VAR, 'single_amp' );
12
- } else {
13
  $amp_url = add_query_arg( AMP_QUERY_VAR, 1, get_permalink( $post_id ) );
 
 
14
  }
15
 
16
  return apply_filters( 'amp_get_permalink', $amp_url, $post_id );
@@ -39,6 +40,10 @@ function post_supports_amp( $post ) {
39
  * Note: will always return `false` if called before the `parse_query` hook.
40
  */
41
  function is_amp_endpoint() {
 
 
 
 
42
  return false !== get_query_var( AMP_QUERY_VAR, false );
43
  }
44
 
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 );
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' ) ) {
44
+ _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( "is_amp_endpoint() was called before the 'parse_query' hook was called. This function will always return 'false' before the 'parse_query' hook is called.", 'amp' ) ), '0.4.2' );
45
+ }
46
+
47
  return false !== get_query_var( AMP_QUERY_VAR, false );
48
  }
49
 
includes/amp-post-template-actions.php CHANGED
@@ -1,69 +1,74 @@
1
  <?php
2
  // Callbacks for adding content to an AMP template
3
 
4
- add_action( 'amp_post_template_head', 'amp_post_template_add_title' );
 
 
 
 
 
 
 
 
 
 
 
5
  function amp_post_template_add_title( $amp_template ) {
6
  ?>
7
  <title><?php echo esc_html( $amp_template->get( 'document_title' ) ); ?></title>
8
  <?php
9
  }
10
 
11
- add_action( 'amp_post_template_head', 'amp_post_template_add_canonical' );
12
  function amp_post_template_add_canonical( $amp_template ) {
13
  ?>
14
  <link rel="canonical" href="<?php echo esc_url( $amp_template->get( 'canonical_url' ) ); ?>" />
15
  <?php
16
  }
17
 
18
- add_action( 'amp_post_template_head', 'amp_post_template_add_scripts' );
19
  function amp_post_template_add_scripts( $amp_template ) {
20
  $scripts = $amp_template->get( 'amp_component_scripts', array() );
21
- foreach ( $scripts as $element => $script ) : ?>
22
- <script custom-element="<?php echo esc_attr( $element ); ?>" src="<?php echo esc_url( $script ); ?>" async></script>
 
23
  <?php endforeach; ?>
24
  <script src="<?php echo esc_url( $amp_template->get( 'amp_runtime_script' ) ); ?>" async></script>
25
  <?php
26
  }
27
 
28
- add_action( 'amp_post_template_head', 'amp_post_template_add_fonts' );
29
  function amp_post_template_add_fonts( $amp_template ) {
30
  $font_urls = $amp_template->get( 'font_urls', array() );
31
- foreach( $font_urls as $slug => $url ) : ?>
32
  <link rel="stylesheet" href="<?php echo esc_url( $url ); ?>">
33
  <?php endforeach;
34
  }
35
 
36
- add_action( 'amp_post_template_head', 'amp_post_template_add_boilerplate_css' );
37
  function amp_post_template_add_boilerplate_css( $amp_template ) {
38
  ?>
39
  <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>
40
  <?php
41
  }
42
 
43
- add_action( 'amp_post_template_head', 'amp_post_template_add_schemaorg_metadata' );
44
  function amp_post_template_add_schemaorg_metadata( $amp_template ) {
45
  $metadata = $amp_template->get( 'metadata' );
46
  if ( empty( $metadata ) ) {
47
  return;
48
  }
49
  ?>
50
- <script type="application/ld+json"><?php echo json_encode( $metadata ); ?></script>
51
  <?php
52
  }
53
 
54
- add_action( 'amp_post_template_css', 'amp_post_template_add_styles', 99 );
55
  function amp_post_template_add_styles( $amp_template ) {
56
  $styles = $amp_template->get( 'post_amp_styles' );
57
  if ( ! empty( $styles ) ) {
58
  echo '/* Inline styles */' . PHP_EOL;
59
  foreach ( $styles as $selector => $declarations ) {
60
- $declarations = implode( ";", $declarations ) . ";";
61
  printf( '%1$s{%2$s}', $selector, $declarations );
62
  }
63
  }
64
  }
65
 
66
- add_action( 'amp_post_template_data', 'amp_post_template_add_analytics_script' );
67
  function amp_post_template_add_analytics_script( $data ) {
68
  if ( ! empty( $data['amp_analytics'] ) ) {
69
  $data['amp_component_scripts']['amp-analytics'] = 'https://cdn.ampproject.org/v0/amp-analytics-0.1.js';
@@ -71,7 +76,6 @@ function amp_post_template_add_analytics_script( $data ) {
71
  return $data;
72
  }
73
 
74
- add_action( 'amp_post_template_footer', 'amp_post_template_add_analytics_data' );
75
  function amp_post_template_add_analytics_data( $amp_template ) {
76
  $analytics_entries = $amp_template->get( 'amp_analytics' );
77
  if ( empty( $analytics_entries ) ) {
@@ -80,13 +84,12 @@ function amp_post_template_add_analytics_data( $amp_template ) {
80
 
81
  foreach ( $analytics_entries as $id => $analytics_entry ) {
82
  if ( ! isset( $analytics_entry['type'], $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) {
83
- _doing_it_wrong( __FUNCTION__, sprintf( __( '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' );
84
  continue;
85
  }
86
-
87
  $script_element = AMP_HTML_Utils::build_tag( 'script', array(
88
  'type' => 'application/json',
89
- ), json_encode( $analytics_entry['config_data'] ) );
90
 
91
  $amp_analytics_attr = array_merge( array(
92
  'id' => $id,
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' );
7
+ add_action( 'amp_post_template_head', 'amp_post_template_add_scripts' );
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 ) ) {
54
  return;
55
  }
56
  ?>
57
+ <script type="application/ld+json"><?php echo wp_json_encode( $metadata ); ?></script>
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
  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
 
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(
91
  'type' => 'application/json',
92
+ ), wp_json_encode( $analytics_entry['config_data'] ) );
93
 
94
  $amp_analytics_attr = array_merge( array(
95
  'id' => $id,
includes/amp-post-template-functions.php CHANGED
@@ -8,7 +8,7 @@ if ( ! function_exists( 'sanitize_hex_color' ) ) {
8
  }
9
 
10
  // 3 or 6 hex digits, or the empty string.
11
- if ( preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
12
  return $color;
13
  }
14
  }
8
  }
9
 
10
  // 3 or 6 hex digits, or the empty string.
11
+ if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
12
  return $color;
13
  }
14
  }
includes/class-amp-content.php CHANGED
@@ -63,7 +63,7 @@ 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( __( 'Embed Handler (%s) must extend `AMP_Embed_Handler`', 'amp' ), $embed_handler_class ), '0.1' );
67
  continue;
68
  }
69
 
@@ -99,14 +99,14 @@ class AMP_Content_Sanitizer {
99
 
100
  foreach ( $sanitizer_classes as $sanitizer_class => $args ) {
101
  if ( ! class_exists( $sanitizer_class ) ) {
102
- _doing_it_wrong( __METHOD__, sprintf( __( '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( __( 'Sanitizer (%s) must extend `AMP_Base_Sanitizer`', 'amp' ), esc_html( $sanitizer_class ) ), '0.1' );
110
  continue;
111
  }
112
 
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
 
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
 
includes/class-amp-post-template.php CHANGED
@@ -3,22 +3,29 @@
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
 
7
  require_once( AMP__DIR__ . '/includes/class-amp-content.php' );
8
 
9
  require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-style-sanitizer.php' );
10
  require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-blacklist-sanitizer.php' );
 
11
  require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-img-sanitizer.php' );
12
  require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-video-sanitizer.php' );
13
  require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-iframe-sanitizer.php' );
14
  require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-audio-sanitizer.php' );
 
15
 
16
  require_once( AMP__DIR__ . '/includes/embeds/class-amp-twitter-embed.php' );
17
  require_once( AMP__DIR__ . '/includes/embeds/class-amp-youtube-embed.php' );
 
 
 
18
  require_once( AMP__DIR__ . '/includes/embeds/class-amp-gallery-embed.php' );
19
  require_once( AMP__DIR__ . '/includes/embeds/class-amp-instagram-embed.php' );
20
  require_once( AMP__DIR__ . '/includes/embeds/class-amp-vine-embed.php' );
21
  require_once( AMP__DIR__ . '/includes/embeds/class-amp-facebook-embed.php' );
 
22
 
23
  class AMP_Post_Template {
24
  const SITE_ICON_SIZE = 32;
@@ -70,6 +77,8 @@ class AMP_Post_Template {
70
  'merriweather' => 'https://fonts.googleapis.com/css?family=Merriweather:400,400italic,700,700italic',
71
  ),
72
 
 
 
73
  /**
74
  * Add amp-analytics tags.
75
  *
@@ -81,7 +90,7 @@ class AMP_Post_Template {
81
  * @param object $post The current post.
82
  */
83
  'amp_analytics' => apply_filters( 'amp_post_template_analytics', array(), $this->post ),
84
- );
85
 
86
  $this->build_post_content();
87
  $this->build_post_data();
@@ -95,7 +104,7 @@ class AMP_Post_Template {
95
  if ( isset( $this->data[ $property ] ) ) {
96
  return $this->data[ $property ];
97
  } else {
98
- _doing_it_wrong( __METHOD__, sprintf( __( 'Called for non-existant key ("%s").', 'amp' ), esc_html( $property ) ), '0.1' );
99
  }
100
 
101
  return $default;
@@ -223,20 +232,26 @@ class AMP_Post_Template {
223
  apply_filters( 'amp_content_embed_handlers', array(
224
  'AMP_Twitter_Embed_Handler' => array(),
225
  'AMP_YouTube_Embed_Handler' => array(),
 
 
 
226
  'AMP_Instagram_Embed_Handler' => array(),
227
  'AMP_Vine_Embed_Handler' => array(),
228
  'AMP_Facebook_Embed_Handler' => array(),
 
229
  'AMP_Gallery_Embed_Handler' => array(),
230
  ), $this->post ),
231
  apply_filters( 'amp_content_sanitizers', array(
232
  'AMP_Style_Sanitizer' => array(),
233
- 'AMP_Blacklist_Sanitizer' => array(),
234
  'AMP_Img_Sanitizer' => array(),
235
  'AMP_Video_Sanitizer' => array(),
236
  'AMP_Audio_Sanitizer' => array(),
 
237
  'AMP_Iframe_Sanitizer' => array(
238
  'add_placeholder' => true,
239
  ),
 
240
  ), $this->post ),
241
  array(
242
  'content_max_width' => $this->get( 'content_max_width' ),
@@ -245,7 +260,7 @@ class AMP_Post_Template {
245
 
246
  $this->add_data_by_key( 'post_amp_content', $amp_content->get_amp_content() );
247
  $this->merge_data_for_key( 'amp_component_scripts', $amp_content->get_amp_scripts() );
248
- $this->add_data_by_key( 'post_amp_styles', $amp_content->get_amp_styles() );
249
  }
250
 
251
  private function build_post_featured_image() {
@@ -274,7 +289,7 @@ class AMP_Post_Template {
274
  $featured_html,
275
  array( 'AMP_Img_Sanitizer' => array() ),
276
  array(
277
- 'content_max_width' => $this->get( 'content_max_width' )
278
  )
279
  );
280
 
@@ -288,7 +303,7 @@ class AMP_Post_Template {
288
  }
289
 
290
  if ( $featured_styles ) {
291
- $this->add_data_by_key( 'post_amp_styles', $featured_styles );
292
  }
293
  }
294
 
@@ -381,7 +396,7 @@ class AMP_Post_Template {
381
 
382
  $file = apply_filters( 'amp_post_template_file', $file, $template_type, $this->post );
383
  if ( ! $this->is_valid_template( $file ) ) {
384
- _doing_it_wrong( __METHOD__, sprintf( __( 'Path validation for template (%s) failed. Path cannot traverse and must be located in `%s`.', 'amp' ), esc_html( $file ), 'WP_CONTENT_DIR' ), '0.1' );
385
  return;
386
  }
387
 
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;
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.
84
  *
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();
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;
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' ),
260
 
261
  $this->add_data_by_key( 'post_amp_content', $amp_content->get_amp_content() );
262
  $this->merge_data_for_key( 'amp_component_scripts', $amp_content->get_amp_scripts() );
263
+ $this->merge_data_for_key( 'post_amp_styles', $amp_content->get_amp_styles() );
264
  }
265
 
266
  private function build_post_featured_image() {
289
  $featured_html,
290
  array( 'AMP_Img_Sanitizer' => array() ),
291
  array(
292
+ 'content_max_width' => $this->get( 'content_max_width' ),
293
  )
294
  );
295
 
303
  }
304
 
305
  if ( $featured_styles ) {
306
+ $this->merge_data_for_key( 'post_amp_styles', $featured_styles );
307
  }
308
  }
309
 
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
 
includes/embeds/class-amp-dailymotion-embed.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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';
9
+ const RATIO = 0.5625;
10
+
11
+ protected $DEFAULT_WIDTH = 600;
12
+ protected $DEFAULT_HEIGHT = 338;
13
+
14
+ private static $script_slug = 'amp-dailymotion';
15
+ private static $script_src = 'https://cdn.ampproject.org/v0/amp-dailymotion-0.1.js';
16
+
17
+ function __construct( $args = array() ) {
18
+ parent::__construct( $args );
19
+
20
+ if ( isset( $this->args['content_max_width'] ) ) {
21
+ $max_width = $this->args['content_max_width'];
22
+ $this->args['width'] = $max_width;
23
+ $this->args['height'] = round( $max_width * self::RATIO );
24
+ }
25
+ }
26
+
27
+ function register_embed() {
28
+ wp_embed_register_handler( 'amp-dailymotion', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
29
+ add_shortcode( 'dailymotion', array( $this, 'shortcode' ) );
30
+ }
31
+
32
+ public function unregister_embed() {
33
+ wp_embed_unregister_handler( 'amp-dailymotion', -1 );
34
+ remove_shortcode( 'dailymotion' );
35
+ }
36
+
37
+ public function get_scripts() {
38
+ if ( ! $this->did_convert_elements ) {
39
+ return array();
40
+ }
41
+
42
+ return array( self::$script_slug => self::$script_src );
43
+ }
44
+
45
+ public function shortcode( $attr ) {
46
+ $video_id = false;
47
+
48
+ if ( isset( $attr['id'] ) ) {
49
+ $video_id = $attr['id'];
50
+ } elseif ( isset( $attr[0] ) ) {
51
+ $video_id = $attr[0];
52
+ } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
53
+ $video_id = shortcode_new_to_old_params( $attr );
54
+ }
55
+
56
+ if ( empty( $video_id ) ) {
57
+ return '';
58
+ }
59
+
60
+ return $this->render( array(
61
+ 'video_id' => $video_id,
62
+ ) );
63
+ }
64
+
65
+ public function oembed( $matches, $attr, $url, $rawattr ) {
66
+ $video_id = $this->get_video_id_from_url( $url );
67
+ return $this->render( array(
68
+ 'video_id' => $video_id,
69
+ ) );
70
+ }
71
+
72
+ public function render( $args ) {
73
+ $args = wp_parse_args( $args, array(
74
+ 'video_id' => false,
75
+ ) );
76
+
77
+ if ( empty( $args['video_id'] ) ) {
78
+ return AMP_HTML_Utils::build_tag( 'a', array( 'href' => esc_url( $args['url'] ), 'class' => 'amp-wp-embed-fallback' ), esc_html( $args['url'] ) );
79
+ }
80
+
81
+ $this->did_convert_elements = true;
82
+
83
+ return AMP_HTML_Utils::build_tag(
84
+ 'amp-dailymotion',
85
+ array(
86
+ 'data-videoid' => $args['video_id'],
87
+ 'layout' => 'responsive',
88
+ 'width' => $this->args['width'],
89
+ 'height' => $this->args['height'],
90
+ )
91
+ );
92
+ }
93
+
94
+ private function get_video_id_from_url( $url ) {
95
+ $parsed_url = AMP_WP_Utils::parse_url( $url );
96
+ parse_str( $parsed_url['path'], $path );
97
+ $tok = explode( '/', $parsed_url['path'] );
98
+ $tok = explode( '_', $tok[2] );
99
+ $video_id = $tok[0];
100
+
101
+ return $video_id;
102
+ }
103
+ }
includes/embeds/class-amp-gallery-embed.php CHANGED
@@ -39,7 +39,7 @@ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler {
39
  'id' => $post ? $post->ID : 0,
40
  'include' => '',
41
  'exclude' => '',
42
- 'size' => array( $this->args['width'], $this->args['height'] )
43
  ), $attr, 'gallery' );
44
 
45
  $id = intval( $atts['id'] );
39
  'id' => $post ? $post->ID : 0,
40
  'include' => '',
41
  'exclude' => '',
42
+ 'size' => array( $this->args['width'], $this->args['height'] ),
43
  ), $attr, 'gallery' );
44
 
45
  $id = intval( $atts['id'] );
includes/embeds/class-amp-instagram-embed.php CHANGED
@@ -52,7 +52,7 @@ class AMP_Instagram_Embed_Handler extends AMP_Base_Embed_Handler {
52
  }
53
 
54
  public function oembed( $matches, $attr, $url, $rawattr ) {
55
- return $this->render( array( 'url' => $url, 'instagram_id' => end( $matches ) ) );
56
  }
57
 
58
  public function render( $args ) {
52
  }
53
 
54
  public function oembed( $matches, $attr, $url, $rawattr ) {
55
+ return $this->render( array( 'url' => $url, 'instagram_id' => end( $matches ) ) );
56
  }
57
 
58
  public function render( $args ) {
includes/embeds/class-amp-pinterest-embed.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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';
8
+
9
+ protected $DEFAULT_WIDTH = 450;
10
+ protected $DEFAULT_HEIGHT = 750;
11
+
12
+ private static $script_slug = 'amp-pinterest';
13
+ private static $script_src = 'https://cdn.ampproject.org/v0/amp-pinterest-0.1.js';
14
+
15
+ public function register_embed() {
16
+ wp_embed_register_handler(
17
+ 'amp-pinterest',
18
+ self::URL_PATTERN,
19
+ array($this, 'oembed'),
20
+ -1);
21
+ }
22
+
23
+ public function unregister_embed() {
24
+ wp_embed_unregister_handler('amp-pinterest', -1);
25
+ }
26
+
27
+ public function get_scripts() {
28
+ if ( ! $this->did_convert_elements) {
29
+ return array();
30
+ }
31
+
32
+ return array( self::$script_slug => self::$script_src);
33
+ }
34
+
35
+ public function oembed( $matches, $attr, $url, $rawattr ) {
36
+ return $this->render( array( 'url' => $url ) );
37
+ }
38
+
39
+ public function render( $args ) {
40
+ $args = wp_parse_args( $args, array(
41
+ 'url' => false,
42
+ ) );
43
+
44
+ if ( empty( $args['url'] ) ) {
45
+ return '';
46
+ }
47
+
48
+ $this->did_convert_elements = true;
49
+
50
+ return AMP_HTML_Utils::build_tag(
51
+ 'amp-pinterest',
52
+ array(
53
+ 'width' => $this->args['width'],
54
+ 'height' => $this->args['height'],
55
+ 'data-do' => "embedPin",
56
+ 'data-url' => $args['url'],
57
+ )
58
+ );
59
+ }
60
+ }
includes/embeds/class-amp-soundcloud-embed.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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();
25
+ }
26
+
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] ) ) {
48
+ $url = $attr[0];
49
+ } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
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;
77
+
78
+ return AMP_HTML_Utils::build_tag(
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
+ }
includes/embeds/class-amp-vimeo-embed.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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';
9
+
10
+ const RATIO = 0.5625;
11
+
12
+ protected $DEFAULT_WIDTH = 600;
13
+ protected $DEFAULT_HEIGHT = 338;
14
+
15
+ private static $script_slug = 'amp-vimeo';
16
+ private static $script_src = 'https://cdn.ampproject.org/v0/amp-vimeo-0.1.js';
17
+
18
+ function __construct( $args = array() ) {
19
+ parent::__construct( $args );
20
+
21
+ if ( isset( $this->args['content_max_width'] ) ) {
22
+ $max_width = $this->args['content_max_width'];
23
+ $this->args['width'] = $max_width;
24
+ $this->args['height'] = round( $max_width * self::RATIO );
25
+ }
26
+ }
27
+
28
+ function register_embed() {
29
+ wp_embed_register_handler( 'amp-vimeo', self::URL_PATTERN, array( $this, 'oembed' ), -1 );
30
+ add_shortcode( 'vimeo', array( $this, 'shortcode' ) );
31
+ }
32
+
33
+ public function unregister_embed() {
34
+ wp_embed_unregister_handler( 'amp-vimeo', -1 );
35
+ remove_shortcode( 'vimeo' );
36
+ }
37
+
38
+ public function get_scripts() {
39
+ if ( ! $this->did_convert_elements ) {
40
+ return array();
41
+ }
42
+
43
+ return array( self::$script_slug => self::$script_src );
44
+ }
45
+
46
+ public function shortcode( $attr ) {
47
+ $video_id = false;
48
+
49
+ if ( isset( $attr['id'] ) ) {
50
+ $video_id = $attr['id'];
51
+ } elseif ( isset( $attr['url'] ) ) {
52
+ $video_id = $this->get_video_id_from_url($attr['url']);
53
+ }elseif ( isset( $attr[0] ) ) {
54
+ $video_id = $this->get_video_id_from_url($attr[0]);
55
+ } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
56
+ $video_id = shortcode_new_to_old_params( $attr );
57
+ }
58
+
59
+ if ( empty( $video_id ) ) {
60
+ return '';
61
+ }
62
+
63
+ return $this->render( array(
64
+ 'video_id' => $video_id,
65
+ ) );
66
+ }
67
+
68
+ public function oembed( $matches, $attr, $url, $rawattr ) {
69
+ $video_id = $this->get_video_id_from_url( $url );
70
+
71
+ return $this->render( array(
72
+ 'url' => $url,
73
+ 'video_id' => $video_id,
74
+ ) );
75
+ }
76
+
77
+ public function render( $args ) {
78
+ $args = wp_parse_args( $args, array(
79
+ 'video_id' => false,
80
+ ) );
81
+
82
+ if ( empty( $args['video_id'] ) ) {
83
+ return AMP_HTML_Utils::build_tag( 'a', array( 'href' => esc_url( $args['url'] ), 'class' => 'amp-wp-embed-fallback' ), esc_html( $args['url'] ) );
84
+ }
85
+
86
+ $this->did_convert_elements = true;
87
+
88
+ return AMP_HTML_Utils::build_tag(
89
+ 'amp-vimeo',
90
+ array(
91
+ 'data-videoid' => $args['video_id'],
92
+ 'layout' => 'responsive',
93
+ 'width' => $this->args['width'],
94
+ 'height' => $this->args['height'],
95
+ )
96
+ );
97
+ }
98
+
99
+ // get_video_id_from_url()
100
+ // Takes the last component of a Vimeo URL
101
+ // and returns it as the associated video id
102
+ private function get_video_id_from_url( $url ) {
103
+ $parsed_url = parse_url( $url );
104
+ parse_str( $parsed_url['path'], $path );
105
+
106
+ $video_id = "";
107
+ if ( $path ) {
108
+ $tok = explode( '/', $parsed_url['path'] );
109
+ $video_id = end($tok);
110
+ }
111
+
112
+ return $video_id;
113
+ }
114
+ }
includes/embeds/class-amp-vine-embed.php CHANGED
@@ -28,7 +28,7 @@ class AMP_Vine_Embed_Handler extends AMP_Base_Embed_Handler {
28
  }
29
 
30
  public function oembed( $matches, $attr, $url, $rawattr ) {
31
- return $this->render( array( 'url' => $url, 'vine_id' => end( $matches ) ) );
32
  }
33
 
34
  public function render( $args ) {
28
  }
29
 
30
  public function oembed( $matches, $attr, $url, $rawattr ) {
31
+ return $this->render( array( 'url' => $url, 'vine_id' => end( $matches ) ) );
32
  }
33
 
34
  public function render( $args ) {
includes/embeds/class-amp-youtube-embed.php CHANGED
@@ -48,7 +48,7 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
48
  $video_id = false;
49
  if ( isset( $attr[0] ) ) {
50
  $url = ltrim( $attr[0] , '=' );
51
- } elseif ( function_exists ( 'shortcode_new_to_old_params' ) ) {
52
  $url = shortcode_new_to_old_params( $attr );
53
  }
54
 
@@ -92,7 +92,7 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
92
 
93
  private function get_video_id_from_url( $url ) {
94
  $video_id = false;
95
- $parsed_url = parse_url( $url );
96
 
97
  if ( self::SHORT_URL_HOST === substr( $parsed_url['host'], -strlen( self::SHORT_URL_HOST ) ) ) {
98
  // youtu.be/{id}
@@ -113,7 +113,7 @@ class AMP_YouTube_Embed_Handler extends AMP_Base_Embed_Handler {
113
  // /(v|e|embed)/{id}
114
  $parts = explode( '/', $parsed_url['path'] );
115
 
116
- if ( in_array( $parts[1], array( 'v', 'e', 'embed' ) ) ) {
117
  $video_id = $parts[2];
118
  }
119
  }
48
  $video_id = false;
49
  if ( isset( $attr[0] ) ) {
50
  $url = ltrim( $attr[0] , '=' );
51
+ } elseif ( function_exists( 'shortcode_new_to_old_params' ) ) {
52
  $url = shortcode_new_to_old_params( $attr );
53
  }
54
 
92
 
93
  private function get_video_id_from_url( $url ) {
94
  $video_id = false;
95
+ $parsed_url = AMP_WP_Utils::parse_url( $url );
96
 
97
  if ( self::SHORT_URL_HOST === substr( $parsed_url['host'], -strlen( self::SHORT_URL_HOST ) ) ) {
98
  // youtu.be/{id}
113
  // /(v|e|embed)/{id}
114
  $parts = explode( '/', $parsed_url['path'] );
115
 
116
+ if ( in_array( $parts[1], array( 'v', 'e', 'embed' ), true ) ) {
117
  $video_id = $parts[2];
118
  }
119
  }
includes/lib/class-fastimage.php DELETED
@@ -1,253 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * FastImage - Because sometimes you just want the size!
5
- * Based on the Ruby Implementation by Steven Sykes (https://github.com/sdsykes/fastimage)
6
- *
7
- * Copyright (c) 2012 Tom Moor
8
- * Tom Moor, http://tommoor.com
9
- *
10
- * MIT Licensed
11
- * @version 0.1
12
- */
13
-
14
- class FastImage
15
- {
16
- private $strpos = 0;
17
- private $str;
18
- private $type;
19
- private $handle;
20
-
21
- public function __construct($uri = null)
22
- {
23
- if ($uri) $this->load($uri);
24
- }
25
-
26
-
27
- public function load($uri)
28
- {
29
- if ($this->handle) $this->close();
30
-
31
- $this->handle = fopen($uri, 'r');
32
- }
33
-
34
-
35
- public function close()
36
- {
37
- if ($this->handle)
38
- {
39
- fclose($this->handle);
40
- $this->handle = null;
41
- $this->type = null;
42
- $this->str = null;
43
- }
44
- }
45
-
46
-
47
- public function getSize()
48
- {
49
- if (!$this->handle)
50
- {
51
- return false;
52
- }
53
-
54
- $this->strpos = 0;
55
- if ($this->getType())
56
- {
57
- return array_values($this->parseSize());
58
- }
59
-
60
- return false;
61
- }
62
-
63
-
64
- public function getType()
65
- {
66
- if (!$this->handle)
67
- {
68
- return false;
69
- }
70
-
71
- $this->strpos = 0;
72
-
73
- if (!$this->type)
74
- {
75
- switch ($this->getChars(2))
76
- {
77
- case "BM":
78
- return $this->type = 'bmp';
79
- case "GI":
80
- return $this->type = 'gif';
81
- case chr(0xFF).chr(0xd8):
82
- return $this->type = 'jpeg';
83
- case chr(0x89).'P':
84
- return $this->type = 'png';
85
- default:
86
- return false;
87
- }
88
- }
89
-
90
- return $this->type;
91
- }
92
-
93
-
94
- private function parseSize()
95
- {
96
- $this->strpos = 0;
97
-
98
- switch ($this->type)
99
- {
100
- case 'png':
101
- return $this->parseSizeForPNG();
102
- case 'gif':
103
- return $this->parseSizeForGIF();
104
- case 'bmp':
105
- return $this->parseSizeForBMP();
106
- case 'jpeg':
107
- return $this->parseSizeForJPEG();
108
- }
109
-
110
- return null;
111
- }
112
-
113
-
114
- private function parseSizeForPNG()
115
- {
116
- $chars = $this->getChars(25);
117
-
118
- return unpack("N*", substr($chars, 16, 8));
119
- }
120
-
121
-
122
- private function parseSizeForGIF()
123
- {
124
- $chars = $this->getChars(11);
125
-
126
- return unpack("S*", substr($chars, 6, 4));
127
- }
128
-
129
-
130
- private function parseSizeForBMP()
131
- {
132
- $chars = $this->getChars(29);
133
- $chars = substr($chars, 14, 14);
134
- $type = unpack('C', $chars);
135
-
136
- return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8));
137
- }
138
-
139
-
140
- private function parseSizeForJPEG()
141
- {
142
- $state = null;
143
- $i = 0;
144
-
145
- while (true)
146
- {
147
- switch ($state)
148
- {
149
- default:
150
- $this->getChars(2);
151
- $state = 'started';
152
- break;
153
-
154
- case 'started':
155
- $b = $this->getByte();
156
- if ($b === false) return false;
157
-
158
- $state = $b == 0xFF ? 'sof' : 'started';
159
- break;
160
-
161
- case 'sof':
162
- $b = $this->getByte();
163
- if (in_array($b, range(0xe0, 0xef)))
164
- {
165
- $state = 'skipframe';
166
- }
167
- elseif (in_array($b, array_merge(range(0xC0,0xC3), range(0xC5,0xC7), range(0xC9,0xCB), range(0xCD,0xCF))))
168
- {
169
- $state = 'readsize';
170
- }
171
- elseif ($b == 0xFF)
172
- {
173
- $state = 'sof';
174
- }
175
- else
176
- {
177
- $state = 'skipframe';
178
- }
179
- break;
180
-
181
- case 'skipframe':
182
- $skip = $this->readInt($this->getChars(2)) - 2;
183
- $state = 'doskip';
184
- break;
185
-
186
- case 'doskip':
187
- $this->getChars($skip);
188
- $state = 'started';
189
- break;
190
-
191
- case 'readsize':
192
- $c = $this->getChars(7);
193
-
194
- return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2)));
195
- }
196
- }
197
- }
198
-
199
-
200
- private function getChars($n)
201
- {
202
- $response = null;
203
-
204
- // do we need more data?
205
- if ($this->strpos + $n -1 >= strlen($this->str))
206
- {
207
- $end = ($this->strpos + $n);
208
-
209
- while (strlen($this->str) < $end && $response !== false)
210
- {
211
- // read more from the file handle
212
- $need = $end - ftell($this->handle);
213
-
214
- if ($response = fread($this->handle, $need))
215
- {
216
- $this->str .= $response;
217
- }
218
- else
219
- {
220
- return false;
221
- }
222
- }
223
- }
224
-
225
- $result = substr($this->str, $this->strpos, $n);
226
- $this->strpos += $n;
227
-
228
- return $result;
229
- }
230
-
231
-
232
- private function getByte()
233
- {
234
- $c = $this->getChars(1);
235
- $b = unpack("C", $c);
236
-
237
- return reset($b);
238
- }
239
-
240
-
241
- private function readInt($str)
242
- {
243
- $size = unpack("C*", $str);
244
-
245
- return ($size[1] << 8) + $size[2];
246
- }
247
-
248
-
249
- public function __destruct()
250
- {
251
- $this->close();
252
- }
253
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/fasterimage/Exception/InvalidImageException.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php namespace FasterImage\Exception;
2
+
3
+ /**
4
+ * Class InvalidImageException
5
+ *
6
+ * @package FasterImage\Exception
7
+ */
8
+ class InvalidImageException extends \Exception {}
includes/lib/fasterimage/ExifParser.php ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php namespace FasterImage;
2
+
3
+ use FasterImage\Exception\InvalidImageException;
4
+ use WillWashburn\Stream\StreamableInterface;
5
+
6
+ /**
7
+ * Class ExifParser
8
+ *
9
+ * @package FasterImage
10
+ */
11
+ class ExifParser
12
+ {
13
+ /**
14
+ * @var int
15
+ */
16
+ protected $width;
17
+ /**
18
+ * @var int
19
+ */
20
+ protected $height;
21
+
22
+ /**
23
+ * @var
24
+ */
25
+ protected $short;
26
+
27
+ /**
28
+ * @var
29
+ */
30
+ protected $long;
31
+
32
+ /**
33
+ * @var StreamableInterface
34
+ */
35
+ protected $stream;
36
+
37
+ /**
38
+ * @var int
39
+ */
40
+ protected $orientation;
41
+
42
+ /**
43
+ * ExifParser constructor.
44
+ *
45
+ * @param StreamableInterface $stream
46
+ */
47
+ public function __construct(StreamableInterface $stream)
48
+ {
49
+ $this->stream = $stream;
50
+ $this->parseExifIfd();
51
+ }
52
+
53
+ /**
54
+ * @return int
55
+ */
56
+ public function getHeight()
57
+ {
58
+ return $this->height;
59
+ }
60
+
61
+ /**
62
+ * @return int
63
+ */
64
+ public function getWidth()
65
+ {
66
+ return $this->width;
67
+ }
68
+
69
+ /**
70
+ * @return bool
71
+ */
72
+ public function isRotated()
73
+ {
74
+ return (! empty($this->orientation) && $this->orientation >= 5);
75
+ }
76
+
77
+ /**
78
+ * @return bool
79
+ * @throws \FasterImage\Exception\InvalidImageException
80
+ */
81
+ protected function parseExifIfd()
82
+ {
83
+ $byte_order = $this->stream->read(2);
84
+
85
+ switch ( $byte_order ) {
86
+ case 'II':
87
+ $this->short = 'v';
88
+ $this->long = 'V';
89
+ break;
90
+ case 'MM':
91
+ $this->short = 'n';
92
+ $this->long = 'N';
93
+ break;
94
+ default:
95
+ throw new InvalidImageException;
96
+ break;
97
+ }
98
+
99
+ $this->stream->read(2);
100
+
101
+ $offset = current(unpack($this->long, $this->stream->read(4)));
102
+
103
+ $this->stream->read($offset - 8);
104
+
105
+ $tag_count = current(unpack($this->short, $this->stream->read(2)));
106
+
107
+ for ( $i = $tag_count; $i > 0; $i-- ) {
108
+
109
+ $type = current(unpack($this->short, $this->stream->read(2)));
110
+ $this->stream->read(6);
111
+ $data = current(unpack($this->short, $this->stream->read(2)));
112
+
113
+ switch ( $type ) {
114
+ case 0x0100:
115
+ $this->width = $data;
116
+ break;
117
+ case 0x0101:
118
+ $this->height = $data;
119
+ break;
120
+ case 0x0112:
121
+ $this->orientation = $data;
122
+ break;
123
+ }
124
+
125
+ if ( isset($this->width) && isset($this->height) && isset($this->orientation) ) {
126
+ return true;
127
+ }
128
+
129
+ $this->stream->read(2);
130
+ }
131
+
132
+ return false;
133
+ }
134
+ }
includes/lib/fasterimage/FasterImage.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php namespace FasterImage;
2
+
3
+ use FasterImage\Exception\InvalidImageException;
4
+ use WillWashburn\Stream\Exception\StreamBufferTooSmallException;
5
+ use WillWashburn\Stream\Stream;
6
+
7
+ /**
8
+ * FasterImage - Because sometimes you just want the size, and you want them in
9
+ * parallel!
10
+ *
11
+ * Based on the PHP stream implementation by Tom Moor (http://tommoor.com)
12
+ * which was based on the original Ruby Implementation by Steven Sykes
13
+ * (https://github.com/sdsykes/fastimage)
14
+ *
15
+ * MIT Licensed
16
+ *
17
+ * @version 0.01
18
+ */
19
+ class FasterImage
20
+ {
21
+ /**
22
+ * The default timeout
23
+ *
24
+ * @var int
25
+ */
26
+ protected $timeout = 10;
27
+
28
+ /**
29
+ * Get the size of each of the urls in a list
30
+ *
31
+ * @param array $urls
32
+ *
33
+ * @return array
34
+ * @throws \Exception
35
+ */
36
+ public function batch(array $urls)
37
+ {
38
+
39
+ $multi = curl_multi_init();
40
+ $results = array();
41
+
42
+ // Create the curl handles and add them to the multi_request
43
+ foreach ( array_values($urls) as $count => $uri ) {
44
+
45
+ $results[$uri] = [];
46
+
47
+ $$count = $this->handle($uri, $results[$uri]);
48
+
49
+ $code = curl_multi_add_handle($multi, $$count);
50
+
51
+ if ( $code != CURLM_OK ) {
52
+ throw new \Exception("Curl handle for $uri could not be added");
53
+ }
54
+ }
55
+
56
+ // Perform the requests
57
+ do {
58
+ while ( ($mrc = curl_multi_exec($multi, $active)) == CURLM_CALL_MULTI_PERFORM ) ;
59
+ if ( $mrc != CURLM_OK && $mrc != CURLM_CALL_MULTI_PERFORM ) {
60
+ throw new \Exception("Curl error code: $mrc");
61
+ }
62
+
63
+ if ( $active && curl_multi_select($multi) === -1 ) {
64
+ // Perform a usleep if a select returns -1.
65
+ // See: https://bugs.php.net/bug.php?id=61141
66
+ usleep(250);
67
+ }
68
+ } while ( $active );
69
+
70
+ // Figure out why individual requests may have failed
71
+ foreach ( array_values($urls) as $count => $uri ) {
72
+ $error = curl_error($$count);
73
+
74
+ if ( $error ) {
75
+ $results[$uri]['failure_reason'] = $error;
76
+ }
77
+ }
78
+
79
+ return $results;
80
+ }
81
+
82
+ /**
83
+ * @param $seconds
84
+ */
85
+ public function setTimeout($seconds)
86
+ {
87
+ $this->timeout = $seconds;
88
+ }
89
+
90
+ /**
91
+ * Create the handle for the curl request
92
+ *
93
+ * @param $url
94
+ * @param $result
95
+ *
96
+ * @return resource
97
+ */
98
+ protected function handle($url, & $result)
99
+ {
100
+ $stream = new Stream();
101
+ $parser = new ImageParser($stream);
102
+ $result['rounds'] = 0;
103
+ $result['bytes'] = 0;
104
+ $result['size'] = 'failed';
105
+
106
+ $ch = curl_init();
107
+ curl_setopt($ch, CURLOPT_URL, $url);
108
+ curl_setopt($ch, CURLOPT_HEADER, 0);
109
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
110
+ curl_setopt($ch, CURLOPT_BUFFERSIZE, 256);
111
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
112
+ curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
113
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
114
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
115
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
116
+
117
+ # Some web servers require the useragent to be not a bot. So we are liars.
118
+ curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36');
119
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
120
+ "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
121
+ "Cache-Control: max-age=0",
122
+ "Connection: keep-alive",
123
+ "Keep-Alive: 300",
124
+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
125
+ "Accept-Language: en-us,en;q=0.5",
126
+ "Pragma: ", // browsers keep this blank.
127
+ ]);
128
+ curl_setopt($ch, CURLOPT_ENCODING, "");
129
+
130
+ curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $str) use (& $result, & $parser, & $stream, $url) {
131
+
132
+ $result['rounds']++;
133
+ $result['bytes'] += strlen($str);
134
+
135
+ $stream->write($str);
136
+
137
+ try {
138
+ // store the type in the result array by looking at the bits
139
+ $result['type'] = $parser->parseType();
140
+
141
+ /*
142
+ * We try here to parse the buffer of characters we already have
143
+ * for the size.
144
+ */
145
+ $result['size'] = $parser->parseSize() ?: 'failed';
146
+ }
147
+ catch (StreamBufferTooSmallException $e) {
148
+ /*
149
+ * If this exception is thrown, we don't have enough of the stream buffered
150
+ * so in order to tell curl to keep streaming we need to return the number
151
+ * of bytes we have already handled
152
+ *
153
+ * We set the 'size' to 'failed' in the case that we've done
154
+ * the entire image and we couldn't figure it out. Otherwise
155
+ * it'll get overwritten with the next round.
156
+ */
157
+ $result['size'] = 'failed';
158
+
159
+ return strlen($str);
160
+ }
161
+ catch (InvalidImageException $e) {
162
+
163
+ /*
164
+ * This means we've determined that we're lost and don't know
165
+ * how to parse this image.
166
+ *
167
+ * We set the size to invalid and move on
168
+ */
169
+ $result['size'] = 'invalid';
170
+
171
+ return -1;
172
+ }
173
+
174
+
175
+ /*
176
+ * We return -1 to abort the transfer when we have enough buffered
177
+ * to find the size
178
+ */
179
+ //
180
+ // hey curl! this is an error. But really we just are stopping cause
181
+ // we already have what we wwant
182
+ return -1;
183
+ });
184
+
185
+ return $ch;
186
+ }
187
+ }
includes/lib/fasterimage/ImageParser.php ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php namespace FasterImage;
2
+
3
+ use WillWashburn\Stream\Exception\StreamBufferTooSmallException;
4
+ use WillWashburn\Stream\Stream;
5
+ use WillWashburn\Stream\StreamableInterface;
6
+
7
+ /**
8
+ * Parses the stream of the image and determines the size and type of the image
9
+ *
10
+ * @package FasterImage
11
+ */
12
+ class ImageParser
13
+ {
14
+ /**
15
+ * The type of image we've determined this is
16
+ *
17
+ * @var string
18
+ */
19
+ protected $type;
20
+ /**
21
+ * @var StreamableInterface $stream
22
+ */
23
+ private $stream;
24
+
25
+ /**
26
+ * ImageParser constructor.
27
+ *
28
+ * @param StreamableInterface $stream
29
+ */
30
+ public function __construct(StreamableInterface & $stream)
31
+ {
32
+ $this->stream = $stream;
33
+ }
34
+
35
+ /**
36
+ * @return array|bool|null
37
+ */
38
+ public function parseSize()
39
+ {
40
+ $this->stream->resetPointer();
41
+
42
+ switch ( $this->type ) {
43
+ case 'png':
44
+ return $this->parseSizeForPNG();
45
+ case 'ico':
46
+ case 'cur':
47
+ return $this->parseSizeForIco();
48
+ case 'gif':
49
+ return $this->parseSizeForGIF();
50
+ case 'bmp':
51
+ return $this->parseSizeForBMP();
52
+ case 'jpeg':
53
+ return $this->parseSizeForJPEG();
54
+ case 'tiff':
55
+ return $this->parseSizeForTiff();
56
+ case 'psd':
57
+ return $this->parseSizeForPSD();
58
+ case 'webp':
59
+ return $this->parseSizeForWebp();
60
+ }
61
+
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * @return array
67
+ */
68
+ public function parseSizeForIco()
69
+ {
70
+ $this->stream->read(6);
71
+
72
+ $b1 = $this->getByte();
73
+ $b2 = $this->getByte();
74
+
75
+ return [
76
+ $b1 == 0 ? 256 : $b1,
77
+ $b2 == 0 ? 256 : $b2
78
+ ];
79
+ }
80
+
81
+ /**
82
+ * @return array
83
+ */
84
+ protected function parseSizeForPSD() {
85
+
86
+ $this->stream->read(14);
87
+ $sizes = unpack("N*",$this->stream->read(12));
88
+
89
+ return [
90
+ $sizes[2],
91
+ $sizes[1]
92
+ ];
93
+ }
94
+
95
+ /**
96
+ * Reads and returns the type of the image
97
+ *
98
+ * @return bool|string
99
+ */
100
+ public function parseType()
101
+ {
102
+ if ( ! $this->type ) {
103
+ $this->stream->resetPointer();
104
+
105
+ switch ( $this->stream->read(2) ) {
106
+ case "BM":
107
+ return $this->type = 'bmp';
108
+ case "GI":
109
+ return $this->type = 'gif';
110
+ case chr(0xFF) . chr(0xd8):
111
+ return $this->type = 'jpeg';
112
+ case "\0\0":
113
+ switch ( $this->readByte($this->stream->peek(1)) ) {
114
+ case 1:
115
+ return $this->type = 'ico';
116
+ case 2:
117
+ return $this->type = 'cur';
118
+ }
119
+
120
+ return false;
121
+
122
+ case chr(0x89) . 'P':
123
+ return $this->type = 'png';
124
+ case "RI":
125
+ if ( substr($this->stream->read(10), 6, 4) == 'WEBP' ) {
126
+ return $this->type = 'webp';
127
+ }
128
+
129
+ return false;
130
+ case'8B':
131
+ return $this->type = 'psd';
132
+ case "II":
133
+ case "MM":
134
+ return $this->type = 'tiff';
135
+ default:
136
+ return false;
137
+ }
138
+ }
139
+
140
+ return $this->type;
141
+ }
142
+
143
+ /**
144
+ * @return array
145
+ */
146
+ protected function parseSizeForBMP()
147
+ {
148
+ $chars = $this->stream->read(29);
149
+ $chars = substr($chars, 14, 14);
150
+ $type = unpack('C', $chars);
151
+
152
+ $size = (reset($type) == 40) ? unpack('l*', substr($chars, 4)) : unpack('l*', substr($chars, 4, 8));
153
+
154
+ return [
155
+ current($size),
156
+ abs(next($size))
157
+ ];
158
+ }
159
+
160
+ /**
161
+ * @return array
162
+ */
163
+ protected function parseSizeForGIF()
164
+ {
165
+ $chars = $this->stream->read(11);
166
+
167
+ $size = unpack("S*", substr($chars, 6, 4));
168
+
169
+ return [
170
+ current($size),
171
+ next($size)
172
+ ];
173
+ }
174
+
175
+ /**
176
+ * @return array|bool
177
+ */
178
+ protected function parseSizeForJPEG()
179
+ {
180
+ $state = null;
181
+
182
+ while ( true ) {
183
+ switch ( $state ) {
184
+ default:
185
+ $this->stream->read(2);
186
+ $state = 'started';
187
+ break;
188
+
189
+ case 'started':
190
+ $b = $this->getByte();
191
+ if ( $b === false ) return false;
192
+
193
+ $state = $b == 0xFF ? 'sof' : 'started';
194
+ break;
195
+
196
+ case 'sof':
197
+ $b = $this->getByte();
198
+
199
+ if ( $b === 0xe1 ) {
200
+ $data = $this->stream->read($this->readInt($this->stream->read(2)) - 2);
201
+
202
+ $stream = new Stream;
203
+ $stream->write($data);
204
+
205
+ if ( $stream->read(4) === 'Exif' ) {
206
+
207
+ $stream->read(2);
208
+
209
+ // Some Exif data is broken/wrong so we'll ignore
210
+ // any exceptions here
211
+ try {
212
+ $exif = new ExifParser($stream);
213
+ } catch (\Exception $e) {}
214
+
215
+ }
216
+
217
+ break;
218
+ }
219
+
220
+ if ( in_array($b, range(0xe0, 0xef)) ) {
221
+ $state = 'skipframe';
222
+ break;
223
+ }
224
+
225
+ if ( in_array($b, array_merge(range(0xC0, 0xC3), range(0xC5, 0xC7), range(0xC9, 0xCB), range(0xCD, 0xCF))) ) {
226
+ $state = 'readsize';
227
+ break;
228
+ }
229
+ if ( $b == 0xFF ) {
230
+ $state = 'sof';
231
+ break;
232
+ }
233
+
234
+ $state = 'skipframe';
235
+ break;
236
+
237
+ case 'skipframe':
238
+ $skip = $this->readInt($this->stream->read(2)) - 2;
239
+ $this->stream->read($skip);
240
+ $state = 'started';
241
+ break;
242
+
243
+ case 'readsize':
244
+ $c = $this->stream->read(7);
245
+
246
+ $size = array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2)));
247
+
248
+ if ( isset($exif) && $exif->isRotated() ) {
249
+ return array_reverse($size);
250
+ }
251
+
252
+ return $size;
253
+ }
254
+ }
255
+
256
+ return false;
257
+ }
258
+
259
+ /**
260
+ * @return array
261
+ */
262
+ protected function parseSizeForPNG()
263
+ {
264
+ $chars = $this->stream->read(25);
265
+
266
+ $size = unpack("N*", substr($chars, 16, 8));
267
+
268
+ return [
269
+ current($size),
270
+ next($size)
271
+ ];
272
+
273
+ }
274
+
275
+ /**
276
+ * @return array|bool
277
+ * @throws \FasterImage\Exception\InvalidImageException
278
+ * @throws StreamBufferTooSmallException
279
+ */
280
+ protected function parseSizeForTiff()
281
+ {
282
+ $exif = new ExifParser($this->stream);
283
+
284
+ if ( $exif->isRotated() ) {
285
+ return [$exif->getHeight(), $exif->getWidth()];
286
+ }
287
+
288
+ return [$exif->getWidth(), $exif->getHeight()];
289
+ }
290
+
291
+ /**
292
+ * @return null
293
+ * @throws StreamBufferTooSmallException
294
+ */
295
+ protected function parseSizeForWebp()
296
+ {
297
+ $vp8 = substr($this->stream->read(16), 12, 4);
298
+ $len = unpack("V", $this->stream->read(4));
299
+
300
+ switch ( trim($vp8) ) {
301
+
302
+ case 'VP8':
303
+ $this->stream->read(6);
304
+
305
+ $width = current(unpack("v", $this->stream->read(2)));
306
+ $height = current(unpack("v", $this->stream->read(2)));
307
+
308
+ return [
309
+ $width & 0x3fff,
310
+ $height & 0x3fff
311
+ ];
312
+
313
+ case 'VP8L':
314
+ $this->stream->read(1);
315
+
316
+ $b1 = $this->getByte();
317
+ $b2 = $this->getByte();
318
+ $b3 = $this->getByte();
319
+ $b4 = $this->getByte();
320
+
321
+ $width = 1 + ((($b2 & 0x3f) << 8) | $b1);
322
+ $height = 1 + ((($b4 & 0xf) << 10) | ($b3 << 2) | (($b2 & 0xc0) >> 6));
323
+
324
+ return [$width, $height];
325
+
326
+ case 'VP8X':
327
+
328
+ $flags = current(unpack("C", $this->stream->read(4)));
329
+
330
+ $b1 = $this->getByte();
331
+ $b2 = $this->getByte();
332
+ $b3 = $this->getByte();
333
+ $b4 = $this->getByte();
334
+ $b5 = $this->getByte();
335
+ $b6 = $this->getByte();
336
+
337
+ $width = 1 + $b1 + ($b2 << 8) + ($b3 << 16);
338
+
339
+ $height = 1 + $b4 + ($b5 << 8) + ($b6 << 16);
340
+
341
+ return [$width, $height];
342
+ default:
343
+ return null;
344
+ }
345
+
346
+ }
347
+
348
+ /**
349
+ * @return mixed
350
+ */
351
+ private function getByte()
352
+ {
353
+ return $this->readByte($this->stream->read(1));
354
+ }
355
+
356
+ /**
357
+ * @param $string
358
+ *
359
+ * @return mixed
360
+ */
361
+ private function readByte($string)
362
+ {
363
+ return current(unpack("C", $string));
364
+ }
365
+
366
+ /**
367
+ * @param $str
368
+ *
369
+ * @return int
370
+ */
371
+ private function readInt($str)
372
+ {
373
+ $size = unpack("C*", $str);
374
+
375
+ return ($size[1] << 8) + $size[2];
376
+ }
377
+ }
includes/lib/fasterimage/Stream/Exception/StreamBufferTooSmallException.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php namespace WillWashburn\Stream\Exception;
2
+
3
+ /**
4
+ * Class StreamBufferTooSmallException
5
+ *
6
+ * @package WillWashburn\Stream\Exception
7
+ */
8
+ class StreamBufferTooSmallException extends \Exception {}
includes/lib/fasterimage/Stream/Stream.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php namespace WillWashburn\Stream;
2
+
3
+ use WillWashburn\Stream\Exception\StreamBufferTooSmallException;
4
+
5
+
6
+ /**
7
+ * Class Stream
8
+ *
9
+ * @package FasterImage
10
+ */
11
+ class Stream implements StreamableInterface
12
+ {
13
+ /**
14
+ * The string that we have downloaded so far
15
+ */
16
+ protected $stream_string = '';
17
+
18
+ /**
19
+ * The pointer in the string
20
+ *
21
+ * @var int
22
+ */
23
+ protected $strpos = 0;
24
+
25
+ /**
26
+ * Get characters from the string but don't move the pointer
27
+ *
28
+ * @param $characters
29
+ *
30
+ * @return string
31
+ * @throws StreamBufferTooSmallException
32
+ */
33
+ public function peek($characters)
34
+ {
35
+ if ( strlen($this->stream_string) < $this->strpos + $characters ) {
36
+ throw new StreamBufferTooSmallException('Not enough of the stream available.');
37
+ }
38
+
39
+ return substr($this->stream_string, $this->strpos, $characters);
40
+ }
41
+
42
+ /**
43
+ * Get Characters from the string
44
+ *
45
+ * @param $characters
46
+ *
47
+ * @return string
48
+ * @throws StreamBufferTooSmallException
49
+ */
50
+ public function read($characters)
51
+ {
52
+ $result = $this->peek($characters);
53
+
54
+ $this->strpos += $characters;
55
+
56
+ return $result;
57
+ }
58
+
59
+ /**
60
+ * Resets the pointer to the 0 position
61
+ *
62
+ * @return mixed
63
+ */
64
+ public function resetPointer()
65
+ {
66
+ $this->strpos = 0;
67
+ }
68
+
69
+ /**
70
+ * Append to the stream string
71
+ *
72
+ * @param $string
73
+ */
74
+ public function write($string)
75
+ {
76
+ $this->stream_string .= $string;
77
+ }
78
+ }
includes/lib/fasterimage/Stream/StreamableInterface.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php namespace WillWashburn\Stream;
2
+
3
+ /**
4
+ * Interface StreamableInterface
5
+ *
6
+ * @package FasterImage
7
+ */
8
+ interface StreamableInterface {
9
+
10
+ /**
11
+ * Append to the stream string
12
+ *
13
+ * @param $string
14
+ */
15
+ public function write($string);
16
+
17
+ /**
18
+ * Get Characters from the string
19
+ *
20
+ * @param $characters
21
+ */
22
+ public function read($characters);
23
+
24
+ /**
25
+ * Get characters from the string but don't move the pointer
26
+ *
27
+ * @param $characters
28
+ *
29
+ * @return mixed
30
+ */
31
+ public function peek($characters);
32
+
33
+ /**
34
+ * Resets the pointer to the 0 position
35
+ * @return mixed
36
+ */
37
+ public function resetPointer();
38
+
39
+ }
includes/lib/fasterimage/amp-fasterimage.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
includes/lib/fastimage/class-fastimage.php ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * FastImage - Because sometimes you just want the size!
4
+ * Based on the Ruby Implementation by Steven Sykes (https://github.com/sdsykes/fastimage)
5
+ *
6
+ * Copyright (c) 2012 Tom Moor
7
+ * Tom Moor, http://tommoor.com
8
+ *
9
+ * MIT Licensed
10
+ * @version 0.1
11
+ */
12
+ class FastImage
13
+ {
14
+ private $strpos = 0;
15
+ private $str;
16
+ private $type;
17
+ private $handle;
18
+ public function __construct($uri = null)
19
+ {
20
+ if ($uri) $this->load($uri);
21
+ }
22
+ public function load($uri)
23
+ {
24
+ if ($this->handle) $this->close();
25
+ $this->handle = @fopen($uri, 'r');
26
+ if ( false === $this->handle ) {
27
+ return false;
28
+ }
29
+ }
30
+ public function close()
31
+ {
32
+ if ($this->handle)
33
+ {
34
+ fclose($this->handle);
35
+ $this->handle = null;
36
+ $this->type = null;
37
+ $this->str = null;
38
+ }
39
+ }
40
+ public function getSize()
41
+ {
42
+ if (!$this->handle)
43
+ {
44
+ return false;
45
+ }
46
+ $this->strpos = 0;
47
+ if ($this->getType())
48
+ {
49
+ return array_values($this->parseSize());
50
+ }
51
+ return false;
52
+ }
53
+ public function getType()
54
+ {
55
+ if (!$this->handle)
56
+ {
57
+ return false;
58
+ }
59
+ $this->strpos = 0;
60
+ if (!$this->type)
61
+ {
62
+ switch ($this->getChars(2))
63
+ {
64
+ case "BM":
65
+ return $this->type = 'bmp';
66
+ case "GI":
67
+ return $this->type = 'gif';
68
+ case chr(0xFF).chr(0xd8):
69
+ return $this->type = 'jpeg';
70
+ case chr(0x89).'P':
71
+ return $this->type = 'png';
72
+ default:
73
+ return false;
74
+ }
75
+ }
76
+ return $this->type;
77
+ }
78
+ private function parseSize()
79
+ {
80
+ $this->strpos = 0;
81
+ switch ($this->type)
82
+ {
83
+ case 'png':
84
+ return $this->parseSizeForPNG();
85
+ case 'gif':
86
+ return $this->parseSizeForGIF();
87
+ case 'bmp':
88
+ return $this->parseSizeForBMP();
89
+ case 'jpeg':
90
+ return $this->parseSizeForJPEG();
91
+ }
92
+ return null;
93
+ }
94
+ private function parseSizeForPNG()
95
+ {
96
+ $chars = $this->getChars(25);
97
+ return unpack("N*", substr($chars, 16, 8));
98
+ }
99
+ private function parseSizeForGIF()
100
+ {
101
+ $chars = $this->getChars(11);
102
+ return unpack("S*", substr($chars, 6, 4));
103
+ }
104
+ private function parseSizeForBMP()
105
+ {
106
+ $chars = $this->getChars(29);
107
+ $chars = substr($chars, 14, 14);
108
+ $type = unpack('C', $chars);
109
+ return (reset($type) == 40) ? unpack('L*', substr($chars, 4)) : unpack('L*', substr($chars, 4, 8));
110
+ }
111
+ private function parseSizeForJPEG()
112
+ {
113
+ $state = null;
114
+ $i = 0;
115
+ while (true)
116
+ {
117
+ switch ($state)
118
+ {
119
+ default:
120
+ $this->getChars(2);
121
+ $state = 'started';
122
+ break;
123
+ case 'started':
124
+ $b = $this->getByte();
125
+ if ($b === false) return false;
126
+ $state = $b == 0xFF ? 'sof' : 'started';
127
+ break;
128
+ case 'sof':
129
+ $b = $this->getByte();
130
+ if (in_array($b, range(0xe0, 0xef)))
131
+ {
132
+ $state = 'skipframe';
133
+ }
134
+ elseif (in_array($b, array_merge(range(0xC0,0xC3), range(0xC5,0xC7), range(0xC9,0xCB), range(0xCD,0xCF))))
135
+ {
136
+ $state = 'readsize';
137
+ }
138
+ elseif ($b == 0xFF)
139
+ {
140
+ $state = 'sof';
141
+ }
142
+ else
143
+ {
144
+ $state = 'skipframe';
145
+ }
146
+ break;
147
+ case 'skipframe':
148
+ $skip = $this->readInt($this->getChars(2)) - 2;
149
+ $state = 'doskip';
150
+ break;
151
+ case 'doskip':
152
+ $this->getChars($skip);
153
+ $state = 'started';
154
+ break;
155
+ case 'readsize':
156
+ $c = $this->getChars(7);
157
+ return array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2)));
158
+ }
159
+ }
160
+ }
161
+ private function getChars($n)
162
+ {
163
+ $response = null;
164
+ // do we need more data?
165
+ if ($this->strpos + $n -1 >= strlen($this->str))
166
+ {
167
+ $end = ($this->strpos + $n);
168
+ while (strlen($this->str) < $end && $response !== false)
169
+ {
170
+ // read more from the file handle
171
+ $need = $end - ftell($this->handle);
172
+ if ($response = fread($this->handle, $need))
173
+ {
174
+ $this->str .= $response;
175
+ }
176
+ else
177
+ {
178
+ return false;
179
+ }
180
+ }
181
+ }
182
+ $result = substr($this->str, $this->strpos, $n);
183
+ $this->strpos += $n;
184
+ return $result;
185
+ }
186
+ private function getByte()
187
+ {
188
+ $c = $this->getChars(1);
189
+ $b = unpack("C", $c);
190
+ return reset($b);
191
+ }
192
+ private function readInt($str)
193
+ {
194
+ $size = unpack("C*", $str);
195
+ return ($size[1] << 8) + $size[2];
196
+ }
197
+ public function __destruct()
198
+ {
199
+ $this->close();
200
+ }
201
+ }
includes/options/class-amp-analytics-options-submenu.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
9
+ private $menu_slug;
10
+ private $menu_page;
11
+
12
+ public function __construct( $parent_menu_slug ) {
13
+ $this->parent_menu_slug = $parent_menu_slug;
14
+ $this->menu_slug = 'amp-analytics-options';
15
+ $this->menu_page = new AMP_Analytics_Options_Submenu_Page();
16
+ }
17
+
18
+ public function init() {
19
+ $this->add_submenu();
20
+ add_action(
21
+ 'admin_print_styles-amp_page_' . $this->menu_slug,
22
+ array( $this, 'amp_options_styles' )
23
+ );
24
+ }
25
+
26
+ private function add_submenu() {
27
+ add_submenu_page(
28
+ $this->parent_menu_slug,
29
+ __( 'AMP Analytics Options', 'amp' ),
30
+ __( 'Analytics', 'amp' ),
31
+ 'manage_options',
32
+ $this->menu_slug,
33
+ array( $this->menu_page, 'render' )
34
+ );
35
+ }
36
+
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;
44
+ margin: 0 5px;
45
+ }
46
+ .amp-analytics-options.notice {
47
+ width: 300px;
48
+ }
49
+ </style>;
50
+
51
+ <?php
52
+ }
53
+ }
includes/options/class-amp-options-menu.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
includes/options/views/class-amp-analytics-options-submenu-page.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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' ) ); ?>">
20
+ <h2>
21
+ <?php echo esc_html( $analytics_title ); ?>
22
+ </h2>
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">
43
+ </div>
44
+ <p>
45
+ <?php
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>
53
+ </form>
54
+ </div><!-- #analytics-data-container -->
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
+ }
88
+
89
+ public function render() {
90
+ $analytics_entries = AMP_Options_Manager::get_option( 'analytics', array() );
91
+
92
+ $this->render_title();
93
+
94
+ // Render entries stored in the DB
95
+ foreach ( $analytics_entries as $entry_id => $entry ) {
96
+ $this->render_entry( $entry_id, $entry['type'], $entry['config'] );
97
+ }
98
+ // Empty form for adding more entries
99
+ $this->render_entry();
100
+ }
101
+ }
includes/options/views/class-amp-options-manager.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,6210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
7
+ *
8
+ * Note: This file only contains tags that are relevant to the `body` of
9
+ * an AMP page. To include additional elements modify the variable
10
+ * `mandatory_parent_blacklist` in the amp_wp_build.py script.
11
+ */
12
+ class AMP_Allowed_Tags_Generated {
13
+
14
+ private static $spec_file_revision = 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',
28
+ 'fb-messenger',
29
+ 'ftp',
30
+ 'http',
31
+ 'https',
32
+ 'intent',
33
+ 'line',
34
+ 'mailto',
35
+ 'skype',
36
+ 'sms',
37
+ 'snapchat',
38
+ 'tel',
39
+ 'tg',
40
+ 'threema',
41
+ 'twitter',
42
+ 'viber',
43
+ 'whatsapp',
44
+ ),
45
+ ),
46
+ 'hreflang' => array(),
47
+ 'media' => array(),
48
+ 'name' => array(),
49
+ 'rel' => array(
50
+ 'blacklisted_value_regex' => '(^|\\s)(components|dns-prefetch|import|manifest|preconnect|prefetch|preload|prerender|serviceworker|stylesheet|subresource|)(\\s|$)',
51
+ ),
52
+ 'role' => array(),
53
+ 'tabindex' => array(),
54
+ 'target' => array(
55
+ 'value_regex' => '(_blank|_self|_top)',
56
+ ),
57
+ 'type' => array(
58
+ 'value_casei' => 'text/html',
59
+ ),
60
+ ),
61
+ 'tag_spec' => array(
62
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#links',
63
+ ),
64
+
65
+ ),
66
+ ),
67
+ 'abbr' => array(
68
+ array(
69
+ 'attr_spec_list' => array(),
70
+ 'tag_spec' => array(),
71
+
72
+ ),
73
+ ),
74
+ 'acronym' => array(
75
+ array(
76
+ 'attr_spec_list' => array(),
77
+ 'tag_spec' => array(
78
+ 'html_format' => array(
79
+ 'amp',
80
+ ),
81
+ ),
82
+
83
+ ),
84
+ ),
85
+ 'address' => array(
86
+ array(
87
+ 'attr_spec_list' => array(),
88
+ 'tag_spec' => array(),
89
+
90
+ ),
91
+ ),
92
+ 'amp-accordion' => array(
93
+ array(
94
+ 'attr_spec_list' => array(
95
+ 'disable-session-states' => array(
96
+ 'value' => '',
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(
119
+ 'value' => '',
120
+ ),
121
+ 'src' => array(
122
+ 'blacklisted_value_regex' => '__amp_source_origin',
123
+ 'allow_relative' => true,
124
+ 'allowed_protocol' => array(
125
+ 'https',
126
+ ),
127
+ ),
128
+ 'type' => array(
129
+ 'mandatory' => true,
130
+ ),
131
+ ),
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
+ ),
144
+ ),
145
+ 'amp-analytics' => array(
146
+ array(
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',
153
+ ),
154
+ ),
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
+ ),
172
+ ),
173
+ 'amp-anim' => array(
174
+ array(
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(
184
+ 'value' => '',
185
+ ),
186
+ 'src' => array(
187
+ 'alternative_names' => array(
188
+ 'srcset',
189
+ ),
190
+ 'blacklisted_value_regex' => '__amp_source_origin',
191
+ 'mandatory' => true,
192
+ 'allow_relative' => true,
193
+ 'allowed_protocol' => array(
194
+ 'data',
195
+ 'http',
196
+ 'https',
197
+ ),
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
+ ),
215
+ ),
216
+ 'amp-apester-media' => array(
217
+ array(
218
+ 'attr_spec_list' => array(
219
+ 'data-apester-channel-token' => array(
220
+ 'value_regex' => '[0-9a-za-z]+',
221
+ ),
222
+ 'data-apester-media-id' => array(
223
+ 'value_regex' => '[0-9a-za-z]+',
224
+ ),
225
+ 'media' => array(),
226
+ 'noloading' => array(
227
+ 'value' => '',
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
+ ),
241
+ ),
242
+ 'amp-app-banner' => array(
243
+ array(
244
+ 'attr_spec_list' => array(
245
+ 'id' => array(
246
+ 'mandatory' => true,
247
+ ),
248
+ 'media' => array(),
249
+ 'noloading' => array(
250
+ 'value' => '',
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
+
267
+ ),
268
+ ),
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
+ ),
279
+ 'media' => array(),
280
+ 'muted' => array(
281
+ 'value' => '',
282
+ ),
283
+ 'noloading' => array(
284
+ 'value' => '',
285
+ ),
286
+ 'src' => array(
287
+ 'blacklisted_value_regex' => '__amp_source_origin',
288
+ 'allow_relative' => true,
289
+ 'allowed_protocol' => array(
290
+ 'https',
291
+ ),
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
+ ),
314
+ 'media' => array(),
315
+ 'muted' => array(
316
+ 'value' => '',
317
+ ),
318
+ 'noloading' => array(
319
+ 'value' => '',
320
+ ),
321
+ 'src' => array(
322
+ 'blacklisted_value_regex' => '__amp_source_origin',
323
+ 'allow_relative' => true,
324
+ 'allowed_protocol' => array(
325
+ 'https',
326
+ ),
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
+ ),
344
+ ),
345
+ 'amp-auto-ads' => array(
346
+ array(
347
+ 'attr_spec_list' => array(
348
+ 'media' => array(),
349
+ 'noloading' => array(
350
+ 'value' => '',
351
+ ),
352
+ 'type' => array(
353
+ 'mandatory' => true,
354
+ ),
355
+ ),
356
+ 'tag_spec' => array(
357
+ 'html_format' => array(
358
+ 'amp',
359
+ ),
360
+ ),
361
+
362
+ ),
363
+ ),
364
+ 'amp-brid-player' => array(
365
+ array(
366
+ 'attr_spec_list' => array(
367
+ 'data-partner' => array(
368
+ 'mandatory' => true,
369
+ 'value_regex' => '[0-9]+',
370
+ ),
371
+ 'data-player' => array(
372
+ 'mandatory' => true,
373
+ 'value_regex' => '[0-9]+',
374
+ ),
375
+ 'data-playlist' => array(
376
+ 'value_regex' => '[0-9]+',
377
+ ),
378
+ 'data-video' => array(
379
+ 'value_regex' => '[0-9]+',
380
+ ),
381
+ 'media' => array(),
382
+ 'noloading' => array(
383
+ 'value' => '',
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
+ ),
401
+ ),
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
+ ),
432
+ ),
433
+ 'amp-carousel' => array(
434
+ array(
435
+ 'attr_spec_list' => array(
436
+ 'arrows' => array(
437
+ 'value' => '',
438
+ ),
439
+ 'autoplay' => array(
440
+ 'value' => '',
441
+ ),
442
+ 'controls' => array(),
443
+ 'delay' => array(
444
+ 'value_regex' => '[0-9]+',
445
+ ),
446
+ 'dots' => array(
447
+ 'value' => '',
448
+ ),
449
+ 'loop' => array(
450
+ 'value' => '',
451
+ ),
452
+ 'media' => array(),
453
+ 'noloading' => array(
454
+ 'value' => '',
455
+ ),
456
+ 'type' => array(
457
+ 'value_regex' => 'slides|carousel',
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
+ ),
475
+ ),
476
+ 'amp-dailymotion' => array(
477
+ array(
478
+ 'attr_spec_list' => array(
479
+ 'data-endscreen-enable' => array(
480
+ 'value_regex' => 'true|false',
481
+ ),
482
+ 'data-info' => array(
483
+ 'value_regex' => 'true|false',
484
+ ),
485
+ 'data-mute' => array(
486
+ 'value_regex' => 'true|false',
487
+ ),
488
+ 'data-sharing-enable' => array(
489
+ 'value_regex' => 'true|false',
490
+ ),
491
+ 'data-start' => array(
492
+ 'value_regex' => '[0-9]+',
493
+ ),
494
+ 'data-ui-highlight' => array(
495
+ 'value_regex_casei' => '([0-9a-f]{3}){1,2}',
496
+ ),
497
+ 'data-ui-logo' => array(
498
+ 'value_regex' => 'true|false',
499
+ ),
500
+ 'data-videoid' => array(
501
+ 'mandatory' => true,
502
+ 'value_regex_casei' => '[a-z0-9]+',
503
+ ),
504
+ 'media' => array(),
505
+ 'noloading' => array(
506
+ 'value' => '',
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(
532
+ 'value' => '',
533
+ ),
534
+ 'src' => array(
535
+ 'blacklisted_value_regex' => '__amp_source_origin',
536
+ 'allow_relative' => true,
537
+ 'allowed_protocol' => array(
538
+ 'https',
539
+ ),
540
+ ),
541
+ 'type' => array(
542
+ 'mandatory' => true,
543
+ ),
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
+ ),
556
+ ),
557
+ 'amp-experiment' => array(
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
+
574
+ ),
575
+ ),
576
+ 'amp-facebook' => array(
577
+ array(
578
+ 'attr_spec_list' => array(
579
+ 'data-href' => array(
580
+ 'mandatory' => true,
581
+ ),
582
+ 'media' => array(),
583
+ 'noloading' => array(
584
+ 'value' => '',
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(
629
+ 'font-family' => array(
630
+ 'mandatory' => true,
631
+ ),
632
+ 'font-style' => array(),
633
+ 'font-variant' => array(),
634
+ 'font-weight' => array(),
635
+ 'media' => array(),
636
+ 'noloading' => array(
637
+ 'value' => '',
638
+ ),
639
+ 'on-error-add-class' => array(),
640
+ 'on-error-remove-class' => array(),
641
+ 'on-load-add-class' => array(),
642
+ 'on-load-remove-class' => array(),
643
+ 'timeout' => array(
644
+ 'value_regex' => '[0-9]+',
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
+ ),
662
+ ),
663
+ 'amp-fx-flying-carpet' => array(
664
+ array(
665
+ 'attr_spec_list' => array(
666
+ 'height' => array(
667
+ 'mandatory' => true,
668
+ ),
669
+ 'media' => array(),
670
+ 'noloading' => array(
671
+ 'value' => '',
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
+ ),
689
+ ),
690
+ 'amp-gfycat' => array(
691
+ array(
692
+ 'attr_spec_list' => array(
693
+ 'data-gfyid' => array(
694
+ 'mandatory' => true,
695
+ ),
696
+ 'media' => array(),
697
+ 'noautoplay' => array(
698
+ 'value' => '',
699
+ ),
700
+ 'noloading' => array(
701
+ 'value' => '',
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
+ ),
719
+ ),
720
+ 'amp-hulu' => array(
721
+ array(
722
+ 'attr_spec_list' => array(
723
+ 'data-eid' => array(
724
+ 'mandatory' => true,
725
+ ),
726
+ 'media' => array(),
727
+ 'noloading' => array(
728
+ 'value' => '',
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
+ ),
745
+ ),
746
+ 'amp-iframe' => array(
747
+ array(
748
+ 'attr_spec_list' => array(
749
+ 'allowfullscreen' => array(
750
+ 'value' => '',
751
+ ),
752
+ 'allowtransparency' => array(
753
+ 'value' => '',
754
+ ),
755
+ 'frameborder' => array(
756
+ 'value_regex' => '0|1',
757
+ ),
758
+ 'media' => array(),
759
+ 'noloading' => array(
760
+ 'value' => '',
761
+ ),
762
+ 'referrerpolicy' => array(),
763
+ 'resizable' => array(
764
+ 'value' => '',
765
+ ),
766
+ 'sandbox' => array(),
767
+ 'scrolling' => array(
768
+ 'value_regex' => 'auto|yes|no',
769
+ ),
770
+ 'src' => array(
771
+ 'blacklisted_value_regex' => '__amp_source_origin',
772
+ 'allow_relative' => true,
773
+ 'allowed_protocol' => array(
774
+ 'data',
775
+ 'https',
776
+ ),
777
+ ),
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
+ ),
794
+ ),
795
+ 'amp-image-lightbox' => array(
796
+ array(
797
+ 'attr_spec_list' => array(
798
+ 'controls' => array(),
799
+ 'media' => array(),
800
+ 'noloading' => array(
801
+ 'value' => '',
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
+ ),
819
+ ),
820
+ 'amp-img' => array(
821
+ array(
822
+ 'attr_spec_list' => array(
823
+ 'alt' => array(),
824
+ 'attribution' => array(),
825
+ 'media' => array(),
826
+ 'noloading' => array(
827
+ 'value' => '',
828
+ ),
829
+ 'placeholder' => array(),
830
+ 'src' => array(
831
+ 'alternative_names' => array(
832
+ 'srcset',
833
+ ),
834
+ 'blacklisted_value_regex' => '__amp_source_origin',
835
+ 'mandatory' => true,
836
+ 'allow_relative' => true,
837
+ 'allowed_protocol' => array(
838
+ 'data',
839
+ 'http',
840
+ 'https',
841
+ ),
842
+ ),
843
+ ),
844
+ 'tag_spec' => array(
845
+ 'html_format' => array(
846
+ 'amp',
847
+ 'amp4ads',
848
+ ),
849
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/amp-img.html',
850
+ ),
851
+
852
+ ),
853
+ ),
854
+ 'amp-instagram' => array(
855
+ array(
856
+ 'attr_spec_list' => array(
857
+ 'alt' => array(),
858
+ 'data-shortcode' => array(
859
+ 'mandatory' => true,
860
+ ),
861
+ 'media' => array(),
862
+ 'noloading' => array(
863
+ 'value' => '',
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
+ ),
881
+ ),
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,
888
+ 'allow_relative' => true,
889
+ 'allowed_protocol' => array(
890
+ 'https',
891
+ ),
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
+ ),
908
+ ),
909
+ 'amp-jwplayer' => array(
910
+ array(
911
+ 'attr_spec_list' => array(
912
+ 'data-media-id' => array(
913
+ 'value_regex_casei' => '[0-9a-z]{8}',
914
+ ),
915
+ 'data-player-id' => array(
916
+ 'mandatory' => true,
917
+ 'value_regex_casei' => '[0-9a-z]{8}',
918
+ ),
919
+ 'data-playlist-id' => array(
920
+ 'value_regex_casei' => '[0-9a-z]{8}',
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
+ ),
938
+ ),
939
+ 'amp-kaltura-player' => array(
940
+ array(
941
+ 'attr_spec_list' => array(
942
+ 'data-partner' => array(
943
+ 'mandatory' => true,
944
+ ),
945
+ 'media' => array(),
946
+ 'noloading' => array(
947
+ 'value' => '',
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
+ ),
965
+ ),
966
+ 'amp-lightbox' => array(
967
+ array(
968
+ 'attr_spec_list' => array(
969
+ 'controls' => array(),
970
+ 'from' => array(),
971
+ 'media' => array(),
972
+ 'noloading' => array(
973
+ 'value' => '',
974
+ ),
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
+ ),
992
+ ),
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,
1004
+ 'allow_relative' => true,
1005
+ 'allowed_protocol' => array(
1006
+ 'https',
1007
+ ),
1008
+ ),
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
+ ),
1023
+ ),
1024
+ 'amp-live-list' => array(
1025
+ array(
1026
+ 'attr_spec_list' => array(
1027
+ 'data-max-items-per-page' => array(
1028
+ 'mandatory' => true,
1029
+ 'value_regex' => '\\d+',
1030
+ ),
1031
+ 'data-poll-interval' => array(
1032
+ 'value_regex' => '\\d{5,}',
1033
+ ),
1034
+ 'disabled' => array(
1035
+ 'value' => '',
1036
+ ),
1037
+ 'id' => array(
1038
+ 'mandatory' => true,
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
+ ),
1053
+ ),
1054
+ 'amp-o2-player' => array(
1055
+ array(
1056
+ 'attr_spec_list' => array(
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
+ ),
1085
+ ),
1086
+ 'amp-ooyala-player' => array(
1087
+ array(
1088
+ 'attr_spec_list' => array(
1089
+ 'data-config' => array(),
1090
+ 'data-embedcode' => array(
1091
+ 'mandatory' => true,
1092
+ ),
1093
+ 'data-pcode' => array(
1094
+ 'mandatory' => true,
1095
+ ),
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
+ ),
1117
+ ),
1118
+ 'amp-pinterest' => array(
1119
+ array(
1120
+ 'attr_spec_list' => array(
1121
+ 'data-do' => array(
1122
+ 'mandatory' => true,
1123
+ ),
1124
+ 'media' => array(),
1125
+ 'noloading' => array(
1126
+ 'value' => '',
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
+ ),
1144
+ ),
1145
+ 'amp-pixel' => array(
1146
+ array(
1147
+ 'attr_spec_list' => array(
1148
+ 'media' => array(),
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',
1158
+ ),
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
+ ),
1173
+ ),
1174
+ 'amp-playbuzz' => array(
1175
+ array(
1176
+ 'attr_spec_list' => array(
1177
+ 'data-comments' => array(
1178
+ 'value_regex_casei' => '(false|true)',
1179
+ ),
1180
+ 'data-item-info' => array(
1181
+ 'value_regex_casei' => '(false|true)',
1182
+ ),
1183
+ 'data-share-buttons' => array(
1184
+ 'value_regex_casei' => '(false|true)',
1185
+ ),
1186
+ 'media' => array(),
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
+ ),
1206
+ ),
1207
+ 'amp-reach-player' => array(
1208
+ array(
1209
+ 'attr_spec_list' => array(
1210
+ 'data-embed-id' => array(
1211
+ 'mandatory' => true,
1212
+ 'value_regex' => '[0-9a-z-]+',
1213
+ ),
1214
+ 'media' => array(),
1215
+ 'noloading' => array(
1216
+ 'value' => '',
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
+ ),
1234
+ ),
1235
+ 'amp-reddit' => array(
1236
+ array(
1237
+ 'attr_spec_list' => array(
1238
+ 'data-embedlive' => array(
1239
+ 'value_regex_casei' => '(false|true)',
1240
+ ),
1241
+ 'data-embedparent' => array(
1242
+ 'value_regex_casei' => '(false|true)',
1243
+ ),
1244
+ 'data-embedtype' => array(
1245
+ 'mandatory' => true,
1246
+ 'value_regex_casei' => '(comment|post)',
1247
+ ),
1248
+ 'data-src' => array(
1249
+ 'mandatory' => true,
1250
+ ),
1251
+ 'media' => array(),
1252
+ 'noloading' => array(
1253
+ 'value' => '',
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
+ ),
1271
+ ),
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
+ ),
1304
+ ),
1305
+ 'amp-sidebar' => array(
1306
+ array(
1307
+ 'attr_spec_list' => array(
1308
+ 'media' => array(),
1309
+ 'noloading' => array(
1310
+ 'value' => '',
1311
+ ),
1312
+ 'side' => array(
1313
+ 'value_regex' => '(left|right)',
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
+ ),
1332
+ ),
1333
+ 'amp-social-share' => array(
1334
+ array(
1335
+ 'attr_spec_list' => array(
1336
+ 'data-share-endpoint' => array(
1337
+ 'blacklisted_value_regex' => '__amp_source_origin',
1338
+ 'allow_relative' => false,
1339
+ 'allowed_protocol' => array(
1340
+ 'bbmi',
1341
+ 'fb-messenger',
1342
+ 'ftp',
1343
+ 'http',
1344
+ 'https',
1345
+ 'intent',
1346
+ 'line',
1347
+ 'mailto',
1348
+ 'skype',
1349
+ 'sms',
1350
+ 'snapchat',
1351
+ 'tel',
1352
+ 'tg',
1353
+ 'threema',
1354
+ 'viber',
1355
+ 'whatsapp',
1356
+ ),
1357
+ ),
1358
+ 'media' => array(),
1359
+ 'noloading' => array(
1360
+ 'value' => '',
1361
+ ),
1362
+ 'type' => array(
1363
+ 'mandatory' => true,
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
+ ),
1378
+ ),
1379
+ 'amp-soundcloud' => array(
1380
+ array(
1381
+ 'attr_spec_list' => array(
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(
1393
+ 'value_regex' => 'true|false',
1394
+ ),
1395
+ 'media' => array(),
1396
+ 'noloading' => array(
1397
+ 'value' => '',
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
+ ),
1415
+ ),
1416
+ 'amp-springboard-player' => array(
1417
+ array(
1418
+ 'attr_spec_list' => array(
1419
+ 'data-content-id' => array(
1420
+ 'mandatory' => true,
1421
+ ),
1422
+ 'data-domain' => array(
1423
+ 'mandatory' => true,
1424
+ ),
1425
+ 'data-items' => array(
1426
+ 'mandatory' => true,
1427
+ ),
1428
+ 'data-mode' => array(
1429
+ 'mandatory' => true,
1430
+ 'value_regex_casei' => 'playlist|video',
1431
+ ),
1432
+ 'data-player-id' => array(
1433
+ 'mandatory' => true,
1434
+ 'value_regex_casei' => '[a-z0-9]+',
1435
+ ),
1436
+ 'data-site-id' => array(
1437
+ 'mandatory' => true,
1438
+ 'value_regex' => '[0-9]+',
1439
+ ),
1440
+ 'media' => array(),
1441
+ 'noloading' => array(
1442
+ 'value' => '',
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
+ ),
1460
+ ),
1461
+ 'amp-sticky-ad' => array(
1462
+ array(
1463
+ 'attr_spec_list' => array(
1464
+ 'media' => array(),
1465
+ 'noloading' => array(
1466
+ 'value' => '',
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
+ ),
1512
+ ),
1513
+ 'amp-user-notification' => array(
1514
+ array(
1515
+ 'attr_spec_list' => array(
1516
+ 'data-dismiss-href' => array(
1517
+ 'allow_empty' => false,
1518
+ 'allow_relative' => false,
1519
+ 'allowed_protocol' => array(
1520
+ 'https',
1521
+ ),
1522
+ ),
1523
+ 'data-show-if-href' => array(
1524
+ 'allow_empty' => false,
1525
+ 'allow_relative' => false,
1526
+ 'allowed_protocol' => array(
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
+ ),
1550
+ ),
1551
+ 'amp-video' => array(
1552
+ array(
1553
+ 'attr_spec_list' => array(
1554
+ 'alt' => array(),
1555
+ 'attribution' => array(),
1556
+ 'autoplay' => array(
1557
+ 'value' => '',
1558
+ ),
1559
+ 'controls' => array(
1560
+ 'value' => '',
1561
+ ),
1562
+ 'loop' => array(
1563
+ 'value' => '',
1564
+ ),
1565
+ 'media' => array(),
1566
+ 'muted' => array(
1567
+ 'value' => '',
1568
+ ),
1569
+ 'noloading' => array(
1570
+ 'value' => '',
1571
+ ),
1572
+ 'placeholder' => array(),
1573
+ 'poster' => array(),
1574
+ 'preload' => array(
1575
+ 'value_regex' => '(none|metadata|auto|)',
1576
+ ),
1577
+ 'src' => array(
1578
+ 'blacklisted_value_regex' => '__amp_source_origin',
1579
+ 'allow_relative' => true,
1580
+ 'allowed_protocol' => array(
1581
+ 'https',
1582
+ ),
1583
+ ),
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
+ ),
1597
+ ),
1598
+ 'amp-vimeo' => array(
1599
+ array(
1600
+ 'attr_spec_list' => array(
1601
+ 'data-videoid' => array(
1602
+ 'mandatory' => true,
1603
+ 'value_regex' => '[0-9]+',
1604
+ ),
1605
+ 'media' => array(),
1606
+ 'noloading' => array(
1607
+ 'value' => '',
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
+ ),
1625
+ ),
1626
+ 'amp-vine' => array(
1627
+ array(
1628
+ 'attr_spec_list' => array(
1629
+ 'data-vineid' => array(
1630
+ 'mandatory' => true,
1631
+ ),
1632
+ 'media' => array(),
1633
+ 'noloading' => array(
1634
+ 'value' => '',
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
+ ),
1682
+ 'article' => array(
1683
+ array(
1684
+ 'attr_spec_list' => array(),
1685
+ 'tag_spec' => array(),
1686
+
1687
+ ),
1688
+ ),
1689
+ 'aside' => array(
1690
+ array(
1691
+ 'attr_spec_list' => array(),
1692
+ 'tag_spec' => array(),
1693
+
1694
+ ),
1695
+ ),
1696
+ 'audio' => array(
1697
+ array(
1698
+ 'attr_spec_list' => array(
1699
+ 'autoplay' => array(),
1700
+ 'controls' => array(),
1701
+ 'loop' => array(),
1702
+ 'muted' => array(),
1703
+ 'preload' => array(),
1704
+ 'src' => array(
1705
+ 'blacklisted_value_regex' => '__amp_source_origin',
1706
+ 'allow_relative' => false,
1707
+ 'allowed_protocol' => array(
1708
+ 'data',
1709
+ 'https',
1710
+ ),
1711
+ ),
1712
+ ),
1713
+ 'tag_spec' => array(
1714
+ 'html_format' => array(
1715
+ 'amp',
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
+ ),
1723
+ ),
1724
+ 'b' => array(
1725
+ array(
1726
+ 'attr_spec_list' => array(),
1727
+ 'tag_spec' => array(),
1728
+
1729
+ ),
1730
+ ),
1731
+ 'bdi' => array(
1732
+ array(
1733
+ 'attr_spec_list' => array(),
1734
+ 'tag_spec' => array(),
1735
+
1736
+ ),
1737
+ ),
1738
+ 'bdo' => array(
1739
+ array(
1740
+ 'attr_spec_list' => array(
1741
+ 'dir' => array(),
1742
+ ),
1743
+ 'tag_spec' => array(),
1744
+
1745
+ ),
1746
+ ),
1747
+ 'big' => array(
1748
+ array(
1749
+ 'attr_spec_list' => array(),
1750
+ 'tag_spec' => array(
1751
+ 'html_format' => array(
1752
+ 'amp',
1753
+ ),
1754
+ ),
1755
+
1756
+ ),
1757
+ ),
1758
+ 'blockquote' => array(
1759
+ array(
1760
+ 'attr_spec_list' => array(
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
+ ),
1784
+ 'tag_spec' => array(),
1785
+
1786
+ ),
1787
+ ),
1788
+ 'body' => array(
1789
+ array(
1790
+ 'attr_spec_list' => array(),
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
+
1798
+ ),
1799
+ ),
1800
+ 'br' => array(
1801
+ array(
1802
+ 'attr_spec_list' => array(),
1803
+ 'tag_spec' => array(),
1804
+
1805
+ ),
1806
+ ),
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(),
1817
+ 'value' => array(),
1818
+ ),
1819
+ 'tag_spec' => array(),
1820
+
1821
+ ),
1822
+ array(
1823
+ 'attr_spec_list' => array(
1824
+ 'name' => array(),
1825
+ 'open-button' => array(
1826
+ 'value' => '',
1827
+ ),
1828
+ 'role' => array(),
1829
+ 'tabindex' => array(),
1830
+ 'type' => array(),
1831
+ 'value' => array(),
1832
+ ),
1833
+ 'tag_spec' => array(
1834
+ 'mandatory_ancestor' => 'amp-app-banner',
1835
+ 'spec_name' => 'amp-app-banner button[open-button]',
1836
+ ),
1837
+
1838
+ ),
1839
+ ),
1840
+ 'caption' => array(
1841
+ array(
1842
+ 'attr_spec_list' => array(),
1843
+ 'tag_spec' => array(),
1844
+
1845
+ ),
1846
+ ),
1847
+ 'center' => array(
1848
+ array(
1849
+ 'attr_spec_list' => array(),
1850
+ 'tag_spec' => array(
1851
+ 'html_format' => array(
1852
+ 'amp',
1853
+ ),
1854
+ ),
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(),
1867
+ 'color' => array(),
1868
+ 'color-interpolation' => array(),
1869
+ 'color-interpolation-filters' => array(),
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(),
1883
+ 'filter' => array(),
1884
+ 'flood-color' => array(),
1885
+ 'flood-opacity' => array(),
1886
+ 'font-family' => array(),
1887
+ 'font-size' => array(),
1888
+ 'font-size-adjust' => array(),
1889
+ 'font-stretch' => array(),
1890
+ 'font-style' => array(),
1891
+ 'font-variant' => array(),
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(),
1898
+ 'lighting-color' => array(),
1899
+ 'marker-end' => array(),
1900
+ 'marker-mid' => array(),
1901
+ 'marker-start' => array(),
1902
+ 'mask' => array(),
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(),
1914
+ 'stroke-dasharray' => array(),
1915
+ 'stroke-dashoffset' => array(),
1916
+ 'stroke-linecap' => array(),
1917
+ 'stroke-linejoin' => array(),
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(),
1963
+ 'color-profile' => array(),
1964
+ 'color-rendering' => array(),
1965
+ 'cursor' => array(),
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(),
1974
+ 'filter' => array(),
1975
+ 'flood-color' => array(),
1976
+ 'flood-opacity' => array(),
1977
+ 'font-family' => array(),
1978
+ 'font-size' => array(),
1979
+ 'font-size-adjust' => array(),
1980
+ 'font-stretch' => array(),
1981
+ 'font-style' => array(),
1982
+ 'font-variant' => array(),
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(),
1990
+ 'marker-end' => array(),
1991
+ 'marker-mid' => array(),
1992
+ 'marker-start' => array(),
1993
+ 'mask' => array(),
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(),
2003
+ 'stroke-dasharray' => array(),
2004
+ 'stroke-dashoffset' => array(),
2005
+ 'stroke-linecap' => array(),
2006
+ 'stroke-linejoin' => array(),
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(),
2093
+ 'color' => array(),
2094
+ 'color-interpolation' => array(),
2095
+ 'color-interpolation-filters' => array(),
2096
+ 'color-profile' => array(),
2097
+ 'color-rendering' => array(),
2098
+ 'cursor' => array(),
2099
+ 'direction' => array(),
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(),
2107
+ 'filter' => array(),
2108
+ 'flood-color' => array(),
2109
+ 'flood-opacity' => array(),
2110
+ 'font-family' => array(),
2111
+ 'font-size' => array(),
2112
+ 'font-size-adjust' => array(),
2113
+ 'font-stretch' => array(),
2114
+ 'font-style' => array(),
2115
+ 'font-variant' => array(),
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(),
2122
+ 'lighting-color' => array(),
2123
+ 'marker-end' => array(),
2124
+ 'marker-mid' => array(),
2125
+ 'marker-start' => array(),
2126
+ 'mask' => array(),
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(),
2135
+ 'stroke' => array(),
2136
+ 'stroke-dasharray' => array(),
2137
+ 'stroke-dashoffset' => array(),
2138
+ 'stroke-linecap' => array(),
2139
+ 'stroke-linejoin' => array(),
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(),
2288
+ 'color' => array(),
2289
+ 'color-interpolation' => array(),
2290
+ 'color-interpolation-filters' => array(),
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(),
2304
+ 'filter' => array(),
2305
+ 'flood-color' => array(),
2306
+ 'flood-opacity' => array(),
2307
+ 'font-family' => array(),
2308
+ 'font-size' => array(),
2309
+ 'font-size-adjust' => array(),
2310
+ 'font-stretch' => array(),
2311
+ 'font-style' => array(),
2312
+ 'font-variant' => array(),
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(),
2320
+ 'marker-end' => array(),
2321
+ 'marker-mid' => array(),
2322
+ 'marker-start' => array(),
2323
+ 'mask' => array(),
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(),
2336
+ 'stroke-dasharray' => array(),
2337
+ 'stroke-dashoffset' => array(),
2338
+ 'stroke-linecap' => array(),
2339
+ 'stroke-linejoin' => array(),
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
+
2380
+ ),
2381
+ ),
2382
+ 'figcaption' => array(
2383
+ array(
2384
+ 'attr_spec_list' => array(),
2385
+ 'tag_spec' => array(),
2386
+
2387
+ ),
2388
+ ),
2389
+ 'figure' => array(
2390
+ array(
2391
+ 'attr_spec_list' => array(),
2392
+ 'tag_spec' => array(),
2393
+
2394
+ ),
2395
+ ),
2396
+ 'filter' => array(
2397
+ array(
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(),
2405
+ 'color' => array(),
2406
+ 'color-interpolation' => array(),
2407
+ 'color-interpolation-filters' => array(),
2408
+ 'color-profile' => array(),
2409
+ 'color-rendering' => array(),
2410
+ 'cursor' => array(),
2411
+ 'direction' => array(),
2412
+ 'display' => array(),
2413
+ 'dominant-baseline' => array(),
2414
+ 'enable-background' => array(),
2415
+ 'externalresourcesrequired' => array(),
2416
+ 'fill' => array(),
2417
+ 'fill-opacity' => array(),
2418
+ 'fill-rule' => array(),
2419
+ 'filter' => array(),
2420
+ 'filterres' => array(),
2421
+ 'filterunits' => array(),
2422
+ 'flood-color' => array(),
2423
+ 'flood-opacity' => array(),
2424
+ 'font-family' => array(),
2425
+ 'font-size' => array(),
2426
+ 'font-size-adjust' => array(),
2427
+ 'font-stretch' => array(),
2428
+ 'font-style' => array(),
2429
+ 'font-variant' => array(),
2430
+ 'font-weight' => array(),
2431
+ 'glyph-orientation-horizontal' => array(),
2432
+ 'glyph-orientation-vertical' => array(),
2433
+ 'height' => array(),
2434
+ 'image-rendering' => array(),
2435
+ 'kerning' => array(),
2436
+ 'letter-spacing' => array(),
2437
+ 'lighting-color' => array(),
2438
+ 'marker-end' => array(),
2439
+ 'marker-mid' => array(),
2440
+ 'marker-start' => array(),
2441
+ 'mask' => array(),
2442
+ 'opacity' => array(),
2443
+ 'overflow' => array(),
2444
+ 'pointer-events' => array(),
2445
+ 'primitiveunits' => array(),
2446
+ 'shape-rendering' => array(),
2447
+ 'stop-color' => array(),
2448
+ 'stop-opacity' => array(),
2449
+ 'stroke' => array(),
2450
+ 'stroke-dasharray' => array(),
2451
+ 'stroke-dashoffset' => array(),
2452
+ 'stroke-linecap' => array(),
2453
+ 'stroke-linejoin' => array(),
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(),
2464
+ 'writing-mode' => array(),
2465
+ 'x' => array(),
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(),
2479
+ 'xmlns:xlink' => array(),
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
+ ),
2488
+ ),
2489
+ 'footer' => array(
2490
+ array(
2491
+ 'attr_spec_list' => array(),
2492
+ 'tag_spec' => array(),
2493
+
2494
+ ),
2495
+ ),
2496
+ 'foreignobject' => array(
2497
+ array(
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(),
2505
+ 'color' => array(),
2506
+ 'color-interpolation' => array(),
2507
+ 'color-interpolation-filters' => array(),
2508
+ 'color-profile' => array(),
2509
+ 'color-rendering' => array(),
2510
+ 'cursor' => array(),
2511
+ 'direction' => array(),
2512
+ 'display' => array(),
2513
+ 'dominant-baseline' => array(),
2514
+ 'enable-background' => array(),
2515
+ 'externalresourcesrequired' => array(),
2516
+ 'fill' => array(),
2517
+ 'fill-opacity' => array(),
2518
+ 'fill-rule' => array(),
2519
+ 'filter' => array(),
2520
+ 'flood-color' => array(),
2521
+ 'flood-opacity' => array(),
2522
+ 'font-family' => array(),
2523
+ 'font-size' => array(),
2524
+ 'font-size-adjust' => array(),
2525
+ 'font-stretch' => array(),
2526
+ 'font-style' => array(),
2527
+ 'font-variant' => array(),
2528
+ 'font-weight' => array(),
2529
+ 'glyph-orientation-horizontal' => array(),
2530
+ 'glyph-orientation-vertical' => array(),
2531
+ 'height' => array(),
2532
+ 'image-rendering' => array(),
2533
+ 'kerning' => array(),
2534
+ 'letter-spacing' => array(),
2535
+ 'lighting-color' => array(),
2536
+ 'marker-end' => array(),
2537
+ 'marker-mid' => array(),
2538
+ 'marker-start' => array(),
2539
+ 'mask' => array(),
2540
+ 'opacity' => array(),
2541
+ 'overflow' => array(),
2542
+ 'pointer-events' => array(),
2543
+ 'requiredextensions' => array(),
2544
+ 'requiredfeatures' => array(),
2545
+ 'shape-rendering' => array(),
2546
+ 'stop-color' => array(),
2547
+ 'stop-opacity' => array(),
2548
+ 'stroke' => array(),
2549
+ 'stroke-dasharray' => array(),
2550
+ 'stroke-dashoffset' => array(),
2551
+ 'stroke-linecap' => array(),
2552
+ 'stroke-linejoin' => array(),
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(),
2571
+ 'xmlns:xlink' => array(),
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
+ ),
2580
+ ),
2581
+ 'form' => array(
2582
+ array(
2583
+ 'attr_spec_list' => array(
2584
+ 'accept' => array(),
2585
+ 'accept-charset' => array(),
2586
+ 'action' => array(
2587
+ 'blacklisted_value_regex' => '__amp_source_origin',
2588
+ 'mandatory' => true,
2589
+ 'allow_relative' => true,
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',
2599
+ 'allow_relative' => true,
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(
2613
+ 'value_casei' => 'get',
2614
+ ),
2615
+ 'name' => array(),
2616
+ 'novalidate' => array(),
2617
+ 'target' => array(
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
+ ),
2634
+ array(
2635
+ 'attr_spec_list' => array(
2636
+ 'accept' => array(),
2637
+ 'accept-charset' => array(),
2638
+ 'action-xhr' => array(
2639
+ 'blacklisted_value_regex' => '__amp_source_origin',
2640
+ 'mandatory' => true,
2641
+ 'allow_relative' => true,
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(
2651
+ 'value_regex' => '(show-first-on-submit|show-all-on-submit|as-you-go)',
2652
+ ),
2653
+ 'enctype' => array(),
2654
+ 'method' => array(
2655
+ 'dispatch_key' => true,
2656
+ 'mandatory' => true,
2657
+ 'value_casei' => 'post',
2658
+ ),
2659
+ 'name' => array(),
2660
+ 'novalidate' => array(),
2661
+ 'target' => array(
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
+ ),
2678
+ ),
2679
+ 'g' => array(
2680
+ array(
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(),
2688
+ 'color' => array(),
2689
+ 'color-interpolation' => array(),
2690
+ 'color-interpolation-filters' => array(),
2691
+ 'color-profile' => array(),
2692
+ 'color-rendering' => array(),
2693
+ 'cursor' => array(),
2694
+ 'direction' => array(),
2695
+ 'display' => array(),
2696
+ 'dominant-baseline' => array(),
2697
+ 'enable-background' => array(),
2698
+ 'externalresourcesrequired' => array(),
2699
+ 'fill' => array(),
2700
+ 'fill-opacity' => array(),
2701
+ 'fill-rule' => array(),
2702
+ 'filter' => array(),
2703
+ 'flood-color' => array(),
2704
+ 'flood-opacity' => array(),
2705
+ 'font-family' => array(),
2706
+ 'font-size' => array(),
2707
+ 'font-size-adjust' => array(),
2708
+ 'font-stretch' => array(),
2709
+ 'font-style' => array(),
2710
+ 'font-variant' => array(),
2711
+ 'font-weight' => array(),
2712
+ 'glyph-orientation-horizontal' => array(),
2713
+ 'glyph-orientation-vertical' => array(),
2714
+ 'image-rendering' => array(),
2715
+ 'kerning' => array(),
2716
+ 'letter-spacing' => array(),
2717
+ 'lighting-color' => array(),
2718
+ 'marker-end' => array(),
2719
+ 'marker-mid' => array(),
2720
+ 'marker-start' => array(),
2721
+ 'mask' => array(),
2722
+ 'opacity' => array(),
2723
+ 'overflow' => array(),
2724
+ 'pointer-events' => array(),
2725
+ 'requiredextensions' => array(),
2726
+ 'requiredfeatures' => array(),
2727
+ 'shape-rendering' => array(),
2728
+ 'stop-color' => array(),
2729
+ 'stop-opacity' => array(),
2730
+ 'stroke' => array(),
2731
+ 'stroke-dasharray' => array(),
2732
+ 'stroke-dashoffset' => array(),
2733
+ 'stroke-linecap' => array(),
2734
+ 'stroke-linejoin' => array(),
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
+ ),
2759
+ ),
2760
+ 'glyph' => array(
2761
+ array(
2762
+ 'attr_spec_list' => array(
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(),
2770
+ 'color' => array(),
2771
+ 'color-interpolation' => array(),
2772
+ 'color-interpolation-filters' => array(),
2773
+ 'color-profile' => array(),
2774
+ 'color-rendering' => array(),
2775
+ 'cursor' => array(),
2776
+ 'd' => array(),
2777
+ 'direction' => array(),
2778
+ 'display' => array(),
2779
+ 'dominant-baseline' => array(),
2780
+ 'enable-background' => array(),
2781
+ 'fill' => array(),
2782
+ 'fill-opacity' => array(),
2783
+ 'fill-rule' => array(),
2784
+ 'filter' => array(),
2785
+ 'flood-color' => array(),
2786
+ 'flood-opacity' => array(),
2787
+ 'font-family' => array(),
2788
+ 'font-size' => array(),
2789
+ 'font-size-adjust' => array(),
2790
+ 'font-stretch' => array(),
2791
+ 'font-style' => array(),
2792
+ 'font-variant' => array(),
2793
+ 'font-weight' => array(),
2794
+ 'glyph-name' => array(),
2795
+ 'glyph-orientation-horizontal' => array(),
2796
+ 'glyph-orientation-vertical' => array(),
2797
+ 'horiz-adv-x' => array(),
2798
+ 'image-rendering' => array(),
2799
+ 'kerning' => array(),
2800
+ 'letter-spacing' => array(),
2801
+ 'lighting-color' => array(),
2802
+ 'marker-end' => array(),
2803
+ 'marker-mid' => array(),
2804
+ 'marker-start' => array(),
2805
+ 'mask' => array(),
2806
+ 'opacity' => array(),
2807
+ 'orientation' => array(),
2808
+ 'overflow' => array(),
2809
+ 'pointer-events' => array(),
2810
+ 'shape-rendering' => array(),
2811
+ 'stop-color' => array(),
2812
+ 'stop-opacity' => array(),
2813
+ 'stroke' => array(),
2814
+ 'stroke-dasharray' => array(),
2815
+ 'stroke-dashoffset' => array(),
2816
+ 'stroke-linecap' => array(),
2817
+ 'stroke-linejoin' => array(),
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
+ ),
2844
+ ),
2845
+ 'glyphref' => array(
2846
+ array(
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(),
2854
+ 'color' => array(),
2855
+ 'color-interpolation' => array(),
2856
+ 'color-interpolation-filters' => array(),
2857
+ 'color-profile' => array(),
2858
+ 'color-rendering' => array(),
2859
+ 'cursor' => array(),
2860
+ 'direction' => array(),
2861
+ 'display' => array(),
2862
+ 'dominant-baseline' => array(),
2863
+ 'dx' => array(),
2864
+ 'dy' => array(),
2865
+ 'enable-background' => array(),
2866
+ 'fill' => array(),
2867
+ 'fill-opacity' => array(),
2868
+ 'fill-rule' => array(),
2869
+ 'filter' => array(),
2870
+ 'flood-color' => array(),
2871
+ 'flood-opacity' => array(),
2872
+ 'font-family' => array(),
2873
+ 'font-size' => array(),
2874
+ 'font-size-adjust' => array(),
2875
+ 'font-stretch' => array(),
2876
+ 'font-style' => array(),
2877
+ 'font-variant' => array(),
2878
+ 'font-weight' => array(),
2879
+ 'format' => array(),
2880
+ 'glyph-orientation-horizontal' => array(),
2881
+ 'glyph-orientation-vertical' => array(),
2882
+ 'glyphref' => array(),
2883
+ 'image-rendering' => array(),
2884
+ 'kerning' => array(),
2885
+ 'letter-spacing' => array(),
2886
+ 'lighting-color' => array(),
2887
+ 'marker-end' => array(),
2888
+ 'marker-mid' => array(),
2889
+ 'marker-start' => array(),
2890
+ 'mask' => array(),
2891
+ 'opacity' => array(),
2892
+ 'overflow' => array(),
2893
+ 'pointer-events' => array(),
2894
+ 'shape-rendering' => array(),
2895
+ 'stop-color' => array(),
2896
+ 'stop-opacity' => array(),
2897
+ 'stroke' => array(),
2898
+ 'stroke-dasharray' => array(),
2899
+ 'stroke-dashoffset' => array(),
2900
+ 'stroke-linecap' => array(),
2901
+ 'stroke-linejoin' => array(),
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(),
2912
+ 'x' => array(),
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(),
2926
+ 'xmlns:xlink' => array(),
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
+ ),
2935
+ ),
2936
+ 'h1' => array(
2937
+ array(
2938
+ 'attr_spec_list' => array(
2939
+ 'align' => array(),
2940
+ ),
2941
+ 'tag_spec' => array(),
2942
+
2943
+ ),
2944
+ ),
2945
+ 'h2' => array(
2946
+ array(
2947
+ 'attr_spec_list' => array(
2948
+ 'align' => array(),
2949
+ ),
2950
+ 'tag_spec' => array(),
2951
+
2952
+ ),
2953
+ ),
2954
+ 'h3' => array(
2955
+ array(
2956
+ 'attr_spec_list' => array(
2957
+ 'align' => array(),
2958
+ ),
2959
+ 'tag_spec' => array(),
2960
+
2961
+ ),
2962
+ ),
2963
+ 'h4' => array(
2964
+ array(
2965
+ 'attr_spec_list' => array(
2966
+ 'align' => array(),
2967
+ ),
2968
+ 'tag_spec' => array(),
2969
+
2970
+ ),
2971
+ ),
2972
+ 'h5' => array(
2973
+ array(
2974
+ 'attr_spec_list' => array(
2975
+ 'align' => array(),
2976
+ ),
2977
+ 'tag_spec' => array(),
2978
+
2979
+ ),
2980
+ ),
2981
+ 'h6' => array(
2982
+ array(
2983
+ 'attr_spec_list' => array(
2984
+ 'align' => array(),
2985
+ ),
2986
+ 'tag_spec' => array(),
2987
+
2988
+ ),
2989
+ ),
2990
+ 'header' => array(
2991
+ array(
2992
+ 'attr_spec_list' => array(),
2993
+ 'tag_spec' => array(),
2994
+
2995
+ ),
2996
+ ),
2997
+ 'hgroup' => array(
2998
+ array(
2999
+ 'attr_spec_list' => array(),
3000
+ 'tag_spec' => array(
3001
+ 'html_format' => array(
3002
+ 'amp',
3003
+ ),
3004
+ ),
3005
+
3006
+ ),
3007
+ ),
3008
+ 'hkern' => array(
3009
+ array(
3010
+ 'attr_spec_list' => array(
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
+ ),
3028
+ ),
3029
+ 'hr' => array(
3030
+ array(
3031
+ 'attr_spec_list' => array(),
3032
+ 'tag_spec' => array(),
3033
+
3034
+ ),
3035
+ ),
3036
+ 'i' => array(
3037
+ array(
3038
+ 'attr_spec_list' => array(),
3039
+ 'tag_spec' => array(),
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(),
3052
+ 'color' => array(),
3053
+ 'color-interpolation' => array(),
3054
+ 'color-interpolation-filters' => array(),
3055
+ 'color-profile' => array(),
3056
+ 'color-rendering' => array(),
3057
+ 'cursor' => array(),
3058
+ 'direction' => array(),
3059
+ 'display' => array(),
3060
+ 'dominant-baseline' => array(),
3061
+ 'enable-background' => array(),
3062
+ 'externalresourcesrequired' => array(),
3063
+ 'fill' => array(),
3064
+ 'fill-opacity' => array(),
3065
+ 'fill-rule' => array(),
3066
+ 'filter' => array(),
3067
+ 'flood-color' => array(),
3068
+ 'flood-opacity' => array(),
3069
+ 'font-family' => array(),
3070
+ 'font-size' => array(),
3071
+ 'font-size-adjust' => array(),
3072
+ 'font-stretch' => array(),
3073
+ 'font-style' => array(),
3074
+ 'font-variant' => array(),
3075
+ 'font-weight' => array(),
3076
+ 'glyph-orientation-horizontal' => array(),
3077
+ 'glyph-orientation-vertical' => array(),
3078
+ 'height' => array(),
3079
+ 'image-rendering' => array(),
3080
+ 'kerning' => array(),
3081
+ 'letter-spacing' => array(),
3082
+ 'lighting-color' => array(),
3083
+ 'marker-end' => array(),
3084
+ 'marker-mid' => array(),
3085
+ 'marker-start' => array(),
3086
+ 'mask' => array(),
3087
+ 'opacity' => array(),
3088
+ 'overflow' => array(),
3089
+ 'pointer-events' => array(),
3090
+ 'preserveaspectratio' => array(),
3091
+ 'requiredextensions' => array(),
3092
+ 'requiredfeatures' => array(),
3093
+ 'shape-rendering' => array(),
3094
+ 'stop-color' => array(),
3095
+ 'stop-opacity' => array(),
3096
+ 'stroke' => array(),
3097
+ 'stroke-dasharray' => array(),
3098
+ 'stroke-dashoffset' => array(),
3099
+ 'stroke-linecap' => array(),
3100
+ 'stroke-linejoin' => array(),
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(),
3113
+ 'writing-mode' => array(),
3114
+ 'x' => array(),
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(),
3133
+ 'xmlns:xlink' => array(),
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
+ ),
3142
+ ),
3143
+ 'img' => array(
3144
+ array(
3145
+ 'attr_spec_list' => array(
3146
+ 'alt' => array(),
3147
+ 'border' => array(),
3148
+ 'height' => array(),
3149
+ 'ismap' => array(),
3150
+ 'longdesc' => array(
3151
+ 'blacklisted_value_regex' => '__amp_source_origin',
3152
+ 'allow_relative' => true,
3153
+ 'allowed_protocol' => array(
3154
+ 'http',
3155
+ 'https',
3156
+ ),
3157
+ ),
3158
+ 'src' => array(
3159
+ 'alternative_names' => array(
3160
+ 'srcset',
3161
+ ),
3162
+ 'blacklisted_value_regex' => '__amp_source_origin',
3163
+ 'mandatory' => true,
3164
+ 'allow_relative' => true,
3165
+ 'allowed_protocol' => array(
3166
+ 'data',
3167
+ 'https',
3168
+ ),
3169
+ ),
3170
+ 'width' => array(),
3171
+ ),
3172
+ 'tag_spec' => array(
3173
+ 'html_format' => array(
3174
+ 'amp',
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
+ ),
3182
+ ),
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(),
3195
+ 'list' => array(),
3196
+ 'max' => array(),
3197
+ 'maxlength' => array(),
3198
+ 'min' => array(),
3199
+ 'minlength' => array(),
3200
+ 'multiple' => array(),
3201
+ 'name' => array(
3202
+ 'blacklisted_value_regex' => '__amp_source_origin',
3203
+ ),
3204
+ 'pattern' => array(),
3205
+ 'placeholder' => array(),
3206
+ 'readonly' => array(),
3207
+ 'required' => array(),
3208
+ 'selectiondirection' => array(),
3209
+ 'size' => array(),
3210
+ 'spellcheck' => array(),
3211
+ 'step' => array(),
3212
+ 'tabindex' => array(),
3213
+ 'type' => array(
3214
+ 'blacklisted_value_regex' => '(^|\\s)(button|file|image|password|)(\\s|$)',
3215
+ ),
3216
+ 'value' => array(),
3217
+ 'width' => array(),
3218
+ ),
3219
+ 'tag_spec' => array(
3220
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
3221
+ ),
3222
+
3223
+ ),
3224
+ ),
3225
+ 'ins' => array(
3226
+ array(
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(),
3250
+ ),
3251
+ 'tag_spec' => array(),
3252
+
3253
+ ),
3254
+ ),
3255
+ 'kbd' => array(
3256
+ array(
3257
+ 'attr_spec_list' => array(),
3258
+ 'tag_spec' => array(),
3259
+
3260
+ ),
3261
+ ),
3262
+ 'label' => array(
3263
+ array(
3264
+ 'attr_spec_list' => array(
3265
+ 'for' => array(),
3266
+ ),
3267
+ 'tag_spec' => array(
3268
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/extended/amp-form.html',
3269
+ ),
3270
+
3271
+ ),
3272
+ ),
3273
+ 'legend' => array(
3274
+ array(
3275
+ 'attr_spec_list' => array(),
3276
+ 'tag_spec' => array(),
3277
+
3278
+ ),
3279
+ ),
3280
+ 'li' => array(
3281
+ array(
3282
+ 'attr_spec_list' => array(
3283
+ 'value' => array(
3284
+ 'value_regex' => '[0-9]*',
3285
+ ),
3286
+ ),
3287
+ 'tag_spec' => array(),
3288
+
3289
+ ),
3290
+ ),
3291
+ 'line' => array(
3292
+ array(
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(),
3300
+ 'color' => array(),
3301
+ 'color-interpolation' => array(),
3302
+ 'color-interpolation-filters' => array(),
3303
+ 'color-profile' => array(),
3304
+ 'color-rendering' => array(),
3305
+ 'cursor' => array(),
3306
+ 'direction' => array(),
3307
+ 'display' => array(),
3308
+ 'dominant-baseline' => array(),
3309
+ 'enable-background' => array(),
3310
+ 'externalresourcesrequired' => array(),
3311
+ 'fill' => array(),
3312
+ 'fill-opacity' => array(),
3313
+ 'fill-rule' => array(),
3314
+ 'filter' => array(),
3315
+ 'flood-color' => array(),
3316
+ 'flood-opacity' => array(),
3317
+ 'font-family' => array(),
3318
+ 'font-size' => array(),
3319
+ 'font-size-adjust' => array(),
3320
+ 'font-stretch' => array(),
3321
+ 'font-style' => array(),
3322
+ 'font-variant' => array(),
3323
+ 'font-weight' => array(),
3324
+ 'glyph-orientation-horizontal' => array(),
3325
+ 'glyph-orientation-vertical' => array(),
3326
+ 'image-rendering' => array(),
3327
+ 'kerning' => array(),
3328
+ 'letter-spacing' => array(),
3329
+ 'lighting-color' => array(),
3330
+ 'marker-end' => array(),
3331
+ 'marker-mid' => array(),
3332
+ 'marker-start' => array(),
3333
+ 'mask' => array(),
3334
+ 'opacity' => array(),
3335
+ 'overflow' => array(),
3336
+ 'pointer-events' => array(),
3337
+ 'requiredextensions' => array(),
3338
+ 'requiredfeatures' => array(),
3339
+ 'shape-rendering' => array(),
3340
+ 'sketch:type' => array(),
3341
+ 'stop-color' => array(),
3342
+ 'stop-opacity' => array(),
3343
+ 'stroke' => array(),
3344
+ 'stroke-dasharray' => array(),
3345
+ 'stroke-dashoffset' => array(),
3346
+ 'stroke-linecap' => array(),
3347
+ 'stroke-linejoin' => array(),
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(),
3366
+ 'xmlns:xlink' => array(),
3367
+ 'y1' => array(),
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
+ ),
3376
+ ),
3377
+ 'lineargradient' => array(
3378
+ array(
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(),
3386
+ 'color' => array(),
3387
+ 'color-interpolation' => array(),
3388
+ 'color-interpolation-filters' => array(),
3389
+ 'color-profile' => array(),
3390
+ 'color-rendering' => array(),
3391
+ 'cursor' => array(),
3392
+ 'direction' => array(),
3393
+ 'display' => array(),
3394
+ 'dominant-baseline' => array(),
3395
+ 'enable-background' => array(),
3396
+ 'externalresourcesrequired' => array(),
3397
+ 'fill' => array(),
3398
+ 'fill-opacity' => array(),
3399
+ 'fill-rule' => array(),
3400
+ 'filter' => array(),
3401
+ 'flood-color' => array(),
3402
+ 'flood-opacity' => array(),
3403
+ 'font-family' => array(),
3404
+ 'font-size' => array(),
3405
+ 'font-size-adjust' => array(),
3406
+ 'font-stretch' => array(),
3407
+ 'font-style' => array(),
3408
+ 'font-variant' => array(),
3409
+ 'font-weight' => array(),
3410
+ 'glyph-orientation-horizontal' => array(),
3411
+ 'glyph-orientation-vertical' => array(),
3412
+ 'gradienttransform' => array(),
3413
+ 'gradientunits' => array(),
3414
+ 'image-rendering' => array(),
3415
+ 'kerning' => array(),
3416
+ 'letter-spacing' => array(),
3417
+ 'lighting-color' => array(),
3418
+ 'marker-end' => array(),
3419
+ 'marker-mid' => array(),
3420
+ 'marker-start' => array(),
3421
+ 'mask' => array(),
3422
+ 'opacity' => array(),
3423
+ 'overflow' => array(),
3424
+ 'pointer-events' => array(),
3425
+ 'shape-rendering' => array(),
3426
+ 'spreadmethod' => array(),
3427
+ 'stop-color' => array(),
3428
+ 'stop-opacity' => array(),
3429
+ 'stroke' => array(),
3430
+ 'stroke-dasharray' => array(),
3431
+ 'stroke-dashoffset' => array(),
3432
+ 'stroke-linecap' => array(),
3433
+ 'stroke-linejoin' => array(),
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(),
3444
+ 'x1' => array(),
3445
+ 'x2' => array(),
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(),
3459
+ 'xmlns:xlink' => array(),
3460
+ 'y1' => array(),
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
+ ),
3469
+ ),
3470
+ 'link' => array(
3471
+ array(
3472
+ 'attr_spec_list' => array(
3473
+ 'charset' => array(
3474
+ 'value_casei' => 'utf-8',
3475
+ ),
3476
+ 'color' => array(),
3477
+ 'href' => array(),
3478
+ 'hreflang' => array(),
3479
+ 'media' => array(),
3480
+ 'rel' => array(
3481
+ 'blacklisted_value_regex' => '(^|\\s)(canonical|components|import|manifest|serviceworker|stylesheet|subresource|)(\\s|$)',
3482
+ 'mandatory' => true,
3483
+ ),
3484
+ 'sizes' => array(),
3485
+ 'target' => array(),
3486
+ 'type' => array(),
3487
+ ),
3488
+ 'tag_spec' => array(
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
+ ),
3497
+ array(
3498
+ 'attr_spec_list' => array(
3499
+ 'charset' => array(
3500
+ 'value_casei' => 'utf-8',
3501
+ ),
3502
+ 'color' => array(),
3503
+ 'href' => array(
3504
+ 'mandatory' => true,
3505
+ ),
3506
+ 'hreflang' => array(),
3507
+ 'itemprop' => array(
3508
+ 'dispatch_key' => true,
3509
+ 'mandatory' => true,
3510
+ 'value_casei' => 'sameas',
3511
+ ),
3512
+ 'media' => array(),
3513
+ 'sizes' => array(),
3514
+ 'target' => array(),
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
+ ),
3523
+ array(
3524
+ 'attr_spec_list' => array(
3525
+ 'charset' => array(
3526
+ 'value_casei' => 'utf-8',
3527
+ ),
3528
+ 'color' => array(),
3529
+ 'href' => array(
3530
+ 'mandatory' => true,
3531
+ ),
3532
+ 'hreflang' => array(),
3533
+ 'itemprop' => array(
3534
+ 'mandatory' => true,
3535
+ ),
3536
+ 'media' => array(),
3537
+ 'sizes' => array(),
3538
+ 'target' => array(),
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
+ ),
3547
+ ),
3548
+ 'listing' => array(
3549
+ array(
3550
+ 'attr_spec_list' => array(),
3551
+ 'tag_spec' => array(
3552
+ 'html_format' => array(
3553
+ 'amp',
3554
+ ),
3555
+ ),
3556
+
3557
+ ),
3558
+ ),
3559
+ 'main' => array(
3560
+ array(
3561
+ 'attr_spec_list' => array(),
3562
+ 'tag_spec' => array(),
3563
+
3564
+ ),
3565
+ ),
3566
+ 'mark' => array(
3567
+ array(
3568
+ 'attr_spec_list' => array(),
3569
+ 'tag_spec' => array(),
3570
+
3571
+ ),
3572
+ ),
3573
+ 'marker' => array(
3574
+ array(
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(),
3582
+ 'color' => array(),
3583
+ 'color-interpolation' => array(),
3584
+ 'color-interpolation-filters' => array(),
3585
+ 'color-profile' => array(),
3586
+ 'color-rendering' => array(),
3587
+ 'cursor' => array(),
3588
+ 'direction' => array(),
3589
+ 'display' => array(),
3590
+ 'dominant-baseline' => array(),
3591
+ 'enable-background' => array(),
3592
+ 'externalresourcesrequired' => array(),
3593
+ 'fill' => array(),
3594
+ 'fill-opacity' => array(),
3595
+ 'fill-rule' => array(),
3596
+ 'filter' => array(),
3597
+ 'flood-color' => array(),
3598
+ 'flood-opacity' => array(),
3599
+ 'font-family' => array(),
3600
+ 'font-size' => array(),
3601
+ 'font-size-adjust' => array(),
3602
+ 'font-stretch' => array(),
3603
+ 'font-style' => array(),
3604
+ 'font-variant' => array(),
3605
+ 'font-weight' => array(),
3606
+ 'glyph-orientation-horizontal' => array(),
3607
+ 'glyph-orientation-vertical' => array(),
3608
+ 'image-rendering' => array(),
3609
+ 'kerning' => array(),
3610
+ 'letter-spacing' => array(),
3611
+ 'lighting-color' => array(),
3612
+ 'marker-end' => array(),
3613
+ 'marker-mid' => array(),
3614
+ 'marker-start' => array(),
3615
+ 'markerheight' => array(),
3616
+ 'markerunits' => array(),
3617
+ 'markerwidth' => array(),
3618
+ 'mask' => array(),
3619
+ 'opacity' => array(),
3620
+ 'orient' => array(),
3621
+ 'overflow' => array(),
3622
+ 'pointer-events' => array(),
3623
+ 'preserveaspectratio' => array(),
3624
+ 'refx' => array(),
3625
+ 'refy' => array(),
3626
+ 'shape-rendering' => array(),
3627
+ 'stop-color' => array(),
3628
+ 'stop-opacity' => array(),
3629
+ 'stroke' => array(),
3630
+ 'stroke-dasharray' => array(),
3631
+ 'stroke-dashoffset' => array(),
3632
+ 'stroke-linecap' => array(),
3633
+ 'stroke-linejoin' => array(),
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
+ ),
3658
+ ),
3659
+ 'mask' => array(
3660
+ array(
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(),
3668
+ 'color' => array(),
3669
+ 'color-interpolation' => array(),
3670
+ 'color-interpolation-filters' => array(),
3671
+ 'color-profile' => array(),
3672
+ 'color-rendering' => array(),
3673
+ 'cursor' => array(),
3674
+ 'direction' => array(),
3675
+ 'display' => array(),
3676
+ 'dominant-baseline' => array(),
3677
+ 'enable-background' => array(),
3678
+ 'externalresourcesrequired' => array(),
3679
+ 'fill' => array(),
3680
+ 'fill-opacity' => array(),
3681
+ 'fill-rule' => array(),
3682
+ 'filter' => array(),
3683
+ 'flood-color' => array(),
3684
+ 'flood-opacity' => array(),
3685
+ 'font-family' => array(),
3686
+ 'font-size' => array(),
3687
+ 'font-size-adjust' => array(),
3688
+ 'font-stretch' => array(),
3689
+ 'font-style' => array(),
3690
+ 'font-variant' => array(),
3691
+ 'font-weight' => array(),
3692
+ 'glyph-orientation-horizontal' => array(),
3693
+ 'glyph-orientation-vertical' => array(),
3694
+ 'height' => array(),
3695
+ 'image-rendering' => array(),
3696
+ 'kerning' => array(),
3697
+ 'letter-spacing' => array(),
3698
+ 'lighting-color' => array(),
3699
+ 'marker-end' => array(),
3700
+ 'marker-mid' => array(),
3701
+ 'marker-start' => array(),
3702
+ 'mask' => array(),
3703
+ 'maskcontentunits' => array(),
3704
+ 'maskunits' => array(),
3705
+ 'opacity' => array(),
3706
+ 'overflow' => array(),
3707
+ 'pointer-events' => array(),
3708
+ 'requiredextensions' => array(),
3709
+ 'requiredfeatures' => array(),
3710
+ 'shape-rendering' => array(),
3711
+ 'stop-color' => array(),
3712
+ 'stop-opacity' => array(),
3713
+ 'stroke' => array(),
3714
+ 'stroke-dasharray' => array(),
3715
+ 'stroke-dashoffset' => array(),
3716
+ 'stroke-linecap' => array(),
3717
+ 'stroke-linejoin' => array(),
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(),
3735
+ 'xmlns:xlink' => array(),
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
+ ),
3744
+ ),
3745
+ 'meta' => array(
3746
+ array(
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
+ ),
3763
+ array(
3764
+ 'attr_spec_list' => array(
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
+
3776
+ ),
3777
+ array(
3778
+ 'attr_spec_list' => array(
3779
+ 'content' => array(
3780
+ 'mandatory' => true,
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
+ ),
3795
+ array(
3796
+ 'attr_spec_list' => array(
3797
+ 'content' => array(
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
+ ),
3812
+ array(
3813
+ 'attr_spec_list' => array(
3814
+ 'content' => array(
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
+ ),
3829
+ array(
3830
+ 'attr_spec_list' => array(
3831
+ 'content' => array(
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
+ ),
3846
+ array(
3847
+ 'attr_spec_list' => array(
3848
+ 'content' => array(
3849
+ 'mandatory' => true,
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
+ ),
3864
+ array(
3865
+ 'attr_spec_list' => array(
3866
+ 'content' => array(
3867
+ 'mandatory' => true,
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
+ ),
3882
+ array(
3883
+ 'attr_spec_list' => array(
3884
+ 'content' => array(
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
+ ),
3900
+ 'multicol' => array(
3901
+ array(
3902
+ 'attr_spec_list' => array(),
3903
+ 'tag_spec' => array(
3904
+ 'html_format' => array(
3905
+ 'amp',
3906
+ ),
3907
+ ),
3908
+
3909
+ ),
3910
+ ),
3911
+ 'nav' => array(
3912
+ array(
3913
+ 'attr_spec_list' => array(),
3914
+ 'tag_spec' => array(),
3915
+
3916
+ ),
3917
+ ),
3918
+ 'nextid' => array(
3919
+ array(
3920
+ 'attr_spec_list' => array(),
3921
+ 'tag_spec' => array(
3922
+ 'html_format' => array(
3923
+ 'amp',
3924
+ ),
3925
+ ),
3926
+
3927
+ ),
3928
+ ),
3929
+ 'nobr' => array(
3930
+ array(
3931
+ 'attr_spec_list' => array(),
3932
+ 'tag_spec' => array(
3933
+ 'html_format' => array(
3934
+ 'amp',
3935
+ ),
3936
+ ),
3937
+
3938
+ ),
3939
+ ),
3940
+ 'noscript' => array(
3941
+ array(
3942
+ 'attr_spec_list' => array(),
3943
+ 'tag_spec' => array(
3944
+ 'disallowed_ancestor' => array(
3945
+ 'noscript',
3946
+ ),
3947
+ 'html_format' => array(
3948
+ 'amp',
3949
+ ),
3950
+ 'mandatory_ancestor' => 'body',
3951
+ ),
3952
+
3953
+ ),
3954
+ ),
3955
+ 'o:p' => array(
3956
+ array(
3957
+ 'attr_spec_list' => array(),
3958
+ 'tag_spec' => array(
3959
+ 'html_format' => array(
3960
+ 'amp',
3961
+ ),
3962
+ ),
3963
+
3964
+ ),
3965
+ ),
3966
+ 'ol' => array(
3967
+ array(
3968
+ 'attr_spec_list' => array(
3969
+ 'reversed' => array(
3970
+ 'value' => '',
3971
+ ),
3972
+ 'start' => array(
3973
+ 'value_regex' => '[0-9]*',
3974
+ ),
3975
+ 'type' => array(
3976
+ 'value_regex' => '[1aaii]',
3977
+ ),
3978
+ ),
3979
+ 'tag_spec' => array(),
3980
+
3981
+ ),
3982
+ ),
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
+ ),
3998
+ ),
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
+ ),
4017
+ 'p' => array(
4018
+ array(
4019
+ 'attr_spec_list' => array(
4020
+ 'align' => array(),
4021
+ ),
4022
+ 'tag_spec' => array(),
4023
+
4024
+ ),
4025
+ ),
4026
+ 'path' => array(
4027
+ array(
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(),
4035
+ 'color' => array(),
4036
+ 'color-interpolation' => array(),
4037
+ 'color-interpolation-filters' => array(),
4038
+ 'color-profile' => array(),
4039
+ 'color-rendering' => array(),
4040
+ 'cursor' => array(),
4041
+ 'd' => array(),
4042
+ 'direction' => array(),
4043
+ 'display' => array(),
4044
+ 'dominant-baseline' => array(),
4045
+ 'enable-background' => array(),
4046
+ 'externalresourcesrequired' => array(),
4047
+ 'fill' => array(),
4048
+ 'fill-opacity' => array(),
4049
+ 'fill-rule' => array(),
4050
+ 'filter' => array(),
4051
+ 'flood-color' => array(),
4052
+ 'flood-opacity' => array(),
4053
+ 'font-family' => array(),
4054
+ 'font-size' => array(),
4055
+ 'font-size-adjust' => array(),
4056
+ 'font-stretch' => array(),
4057
+ 'font-style' => array(),
4058
+ 'font-variant' => array(),
4059
+ 'font-weight' => array(),
4060
+ 'glyph-orientation-horizontal' => array(),
4061
+ 'glyph-orientation-vertical' => array(),
4062
+ 'image-rendering' => array(),
4063
+ 'kerning' => array(),
4064
+ 'letter-spacing' => array(),
4065
+ 'lighting-color' => array(),
4066
+ 'marker-end' => array(),
4067
+ 'marker-mid' => array(),
4068
+ 'marker-start' => array(),
4069
+ 'mask' => array(),
4070
+ 'opacity' => array(),
4071
+ 'overflow' => array(),
4072
+ 'pathlength' => array(),
4073
+ 'pointer-events' => array(),
4074
+ 'requiredextensions' => array(),
4075
+ 'requiredfeatures' => array(),
4076
+ 'shape-rendering' => array(),
4077
+ 'sketch:type' => array(),
4078
+ 'stop-color' => array(),
4079
+ 'stop-opacity' => array(),
4080
+ 'stroke' => array(),
4081
+ 'stroke-dasharray' => array(),
4082
+ 'stroke-dashoffset' => array(),
4083
+ 'stroke-linecap' => array(),
4084
+ 'stroke-linejoin' => array(),
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
+ ),
4109
+ ),
4110
+ 'pattern' => array(
4111
+ array(
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(),
4119
+ 'color' => array(),
4120
+ 'color-interpolation' => array(),
4121
+ 'color-interpolation-filters' => array(),
4122
+ 'color-profile' => array(),
4123
+ 'color-rendering' => array(),
4124
+ 'cursor' => array(),
4125
+ 'direction' => array(),
4126
+ 'display' => array(),
4127
+ 'dominant-baseline' => array(),
4128
+ 'enable-background' => array(),
4129
+ 'externalresourcesrequired' => array(),
4130
+ 'fill' => array(),
4131
+ 'fill-opacity' => array(),
4132
+ 'fill-rule' => array(),
4133
+ 'filter' => array(),
4134
+ 'flood-color' => array(),
4135
+ 'flood-opacity' => array(),
4136
+ 'font-family' => array(),
4137
+ 'font-size' => array(),
4138
+ 'font-size-adjust' => array(),
4139
+ 'font-stretch' => array(),
4140
+ 'font-style' => array(),
4141
+ 'font-variant' => array(),
4142
+ 'font-weight' => array(),
4143
+ 'glyph-orientation-horizontal' => array(),
4144
+ 'glyph-orientation-vertical' => array(),
4145
+ 'height' => array(),
4146
+ 'image-rendering' => array(),
4147
+ 'kerning' => array(),
4148
+ 'letter-spacing' => array(),
4149
+ 'lighting-color' => array(),
4150
+ 'marker-end' => array(),
4151
+ 'marker-mid' => array(),
4152
+ 'marker-start' => array(),
4153
+ 'mask' => array(),
4154
+ 'opacity' => array(),
4155
+ 'overflow' => array(),
4156
+ 'patterncontentunits' => array(),
4157
+ 'patterntransform' => array(),
4158
+ 'patternunits' => array(),
4159
+ 'pointer-events' => array(),
4160
+ 'preserveaspectratio' => array(),
4161
+ 'requiredextensions' => array(),
4162
+ 'requiredfeatures' => array(),
4163
+ 'shape-rendering' => array(),
4164
+ 'stop-color' => array(),
4165
+ 'stop-opacity' => array(),
4166
+ 'stroke' => array(),
4167
+ 'stroke-dasharray' => array(),
4168
+ 'stroke-dashoffset' => array(),
4169
+ 'stroke-linecap' => array(),
4170
+ 'stroke-linejoin' => array(),
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(),
4182
+ 'word-spacing' => array(),
4183
+ 'writing-mode' => array(),
4184
+ 'x' => array(),
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(),
4198
+ 'xmlns:xlink' => array(),
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
+ ),
4207
+ ),
4208
+ 'polygon' => array(
4209
+ array(
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(),
4217
+ 'color' => array(),
4218
+ 'color-interpolation' => array(),
4219
+ 'color-interpolation-filters' => array(),
4220
+ 'color-profile' => array(),
4221
+ 'color-rendering' => array(),
4222
+ 'cursor' => array(),
4223
+ 'direction' => array(),
4224
+ 'display' => array(),
4225
+ 'dominant-baseline' => array(),
4226
+ 'enable-background' => array(),
4227
+ 'externalresourcesrequired' => array(),
4228
+ 'fill' => array(),
4229
+ 'fill-opacity' => array(),
4230
+ 'fill-rule' => array(),
4231
+ 'filter' => array(),
4232
+ 'flood-color' => array(),
4233
+ 'flood-opacity' => array(),
4234
+ 'font-family' => array(),
4235
+ 'font-size' => array(),
4236
+ 'font-size-adjust' => array(),
4237
+ 'font-stretch' => array(),
4238
+ 'font-style' => array(),
4239
+ 'font-variant' => array(),
4240
+ 'font-weight' => array(),
4241
+ 'glyph-orientation-horizontal' => array(),
4242
+ 'glyph-orientation-vertical' => array(),
4243
+ 'image-rendering' => array(),
4244
+ 'kerning' => array(),
4245
+ 'letter-spacing' => array(),
4246
+ 'lighting-color' => array(),
4247
+ 'marker-end' => array(),
4248
+ 'marker-mid' => array(),
4249
+ 'marker-start' => array(),
4250
+ 'mask' => array(),
4251
+ 'opacity' => array(),
4252
+ 'overflow' => array(),
4253
+ 'pointer-events' => array(),
4254
+ 'points' => array(),
4255
+ 'requiredextensions' => array(),
4256
+ 'requiredfeatures' => array(),
4257
+ 'shape-rendering' => array(),
4258
+ 'sketch:type' => array(),
4259
+ 'stop-color' => array(),
4260
+ 'stop-opacity' => array(),
4261
+ 'stroke' => array(),
4262
+ 'stroke-dasharray' => array(),
4263
+ 'stroke-dashoffset' => array(),
4264
+ 'stroke-linecap' => array(),
4265
+ 'stroke-linejoin' => array(),
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
+ ),
4290
+ ),
4291
+ 'polyline' => array(
4292
+ array(
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(),
4300
+ 'color' => array(),
4301
+ 'color-interpolation' => array(),
4302
+ 'color-interpolation-filters' => array(),
4303
+ 'color-profile' => array(),
4304
+ 'color-rendering' => array(),
4305
+ 'cursor' => array(),
4306
+ 'direction' => array(),
4307
+ 'display' => array(),
4308
+ 'dominant-baseline' => array(),
4309
+ 'enable-background' => array(),
4310
+ 'externalresourcesrequired' => array(),
4311
+ 'fill' => array(),
4312
+ 'fill-opacity' => array(),
4313
+ 'fill-rule' => array(),
4314
+ 'filter' => array(),
4315
+ 'flood-color' => array(),
4316
+ 'flood-opacity' => array(),
4317
+ 'font-family' => array(),
4318
+ 'font-size' => array(),
4319
+ 'font-size-adjust' => array(),
4320
+ 'font-stretch' => array(),
4321
+ 'font-style' => array(),
4322
+ 'font-variant' => array(),
4323
+ 'font-weight' => array(),
4324
+ 'glyph-orientation-horizontal' => array(),
4325
+ 'glyph-orientation-vertical' => array(),
4326
+ 'image-rendering' => array(),
4327
+ 'kerning' => array(),
4328
+ 'letter-spacing' => array(),
4329
+ 'lighting-color' => array(),
4330
+ 'marker-end' => array(),
4331
+ 'marker-mid' => array(),
4332
+ 'marker-start' => array(),
4333
+ 'mask' => array(),
4334
+ 'opacity' => array(),
4335
+ 'overflow' => array(),
4336
+ 'pointer-events' => array(),
4337
+ 'points' => array(),
4338
+ 'requiredextensions' => array(),
4339
+ 'requiredfeatures' => array(),
4340
+ 'shape-rendering' => array(),
4341
+ 'sketch:type' => array(),
4342
+ 'stop-color' => array(),
4343
+ 'stop-opacity' => array(),
4344
+ 'stroke' => array(),
4345
+ 'stroke-dasharray' => array(),
4346
+ 'stroke-dashoffset' => array(),
4347
+ 'stroke-linecap' => array(),
4348
+ 'stroke-linejoin' => array(),
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
+ ),
4373
+ ),
4374
+ 'pre' => array(
4375
+ array(
4376
+ 'attr_spec_list' => array(),
4377
+ 'tag_spec' => array(),
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
+ ),
4406
+ 'tag_spec' => array(),
4407
+
4408
+ ),
4409
+ ),
4410
+ 'radialgradient' => array(
4411
+ array(
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(),
4419
+ 'color' => array(),
4420
+ 'color-interpolation' => array(),
4421
+ 'color-interpolation-filters' => array(),
4422
+ 'color-profile' => array(),
4423
+ 'color-rendering' => array(),
4424
+ 'cursor' => array(),
4425
+ 'cx' => array(),
4426
+ 'cy' => array(),
4427
+ 'direction' => array(),
4428
+ 'display' => array(),
4429
+ 'dominant-baseline' => array(),
4430
+ 'enable-background' => array(),
4431
+ 'externalresourcesrequired' => array(),
4432
+ 'fill' => array(),
4433
+ 'fill-opacity' => array(),
4434
+ 'fill-rule' => array(),
4435
+ 'filter' => array(),
4436
+ 'flood-color' => array(),
4437
+ 'flood-opacity' => array(),
4438
+ 'font-family' => array(),
4439
+ 'font-size' => array(),
4440
+ 'font-size-adjust' => array(),
4441
+ 'font-stretch' => array(),
4442
+ 'font-style' => array(),
4443
+ 'font-variant' => array(),
4444
+ 'font-weight' => array(),
4445
+ 'fx' => array(),
4446
+ 'fy' => array(),
4447
+ 'glyph-orientation-horizontal' => array(),
4448
+ 'glyph-orientation-vertical' => array(),
4449
+ 'gradienttransform' => array(),
4450
+ 'gradientunits' => array(),
4451
+ 'image-rendering' => array(),
4452
+ 'kerning' => array(),
4453
+ 'letter-spacing' => array(),
4454
+ 'lighting-color' => array(),
4455
+ 'marker-end' => array(),
4456
+ 'marker-mid' => array(),
4457
+ 'marker-start' => array(),
4458
+ 'mask' => array(),
4459
+ 'opacity' => array(),
4460
+ 'overflow' => array(),
4461
+ 'pointer-events' => array(),
4462
+ 'r' => array(),
4463
+ 'shape-rendering' => array(),
4464
+ 'spreadmethod' => array(),
4465
+ 'stop-color' => array(),
4466
+ 'stop-opacity' => array(),
4467
+ 'stroke' => array(),
4468
+ 'stroke-dasharray' => array(),
4469
+ 'stroke-dashoffset' => array(),
4470
+ 'stroke-linecap' => array(),
4471
+ 'stroke-linejoin' => array(),
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
+ ),
4503
+ ),
4504
+ 'rb' => array(
4505
+ array(
4506
+ 'attr_spec_list' => array(),
4507
+ 'tag_spec' => array(),
4508
+
4509
+ ),
4510
+ ),
4511
+ 'rect' => array(
4512
+ array(
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(),
4520
+ 'color' => array(),
4521
+ 'color-interpolation' => array(),
4522
+ 'color-interpolation-filters' => array(),
4523
+ 'color-profile' => array(),
4524
+ 'color-rendering' => array(),
4525
+ 'cursor' => array(),
4526
+ 'direction' => array(),
4527
+ 'display' => array(),
4528
+ 'dominant-baseline' => array(),
4529
+ 'enable-background' => array(),
4530
+ 'externalresourcesrequired' => array(),
4531
+ 'fill' => array(),
4532
+ 'fill-opacity' => array(),
4533
+ 'fill-rule' => array(),
4534
+ 'filter' => array(),
4535
+ 'flood-color' => array(),
4536
+ 'flood-opacity' => array(),
4537
+ 'font-family' => array(),
4538
+ 'font-size' => array(),
4539
+ 'font-size-adjust' => array(),
4540
+ 'font-stretch' => array(),
4541
+ 'font-style' => array(),
4542
+ 'font-variant' => array(),
4543
+ 'font-weight' => array(),
4544
+ 'glyph-orientation-horizontal' => array(),
4545
+ 'glyph-orientation-vertical' => array(),
4546
+ 'height' => array(),
4547
+ 'image-rendering' => array(),
4548
+ 'kerning' => array(),
4549
+ 'letter-spacing' => array(),
4550
+ 'lighting-color' => array(),
4551
+ 'marker-end' => array(),
4552
+ 'marker-mid' => array(),
4553
+ 'marker-start' => array(),
4554
+ 'mask' => array(),
4555
+ 'opacity' => array(),
4556
+ 'overflow' => array(),
4557
+ 'pointer-events' => array(),
4558
+ 'requiredextensions' => array(),
4559
+ 'requiredfeatures' => array(),
4560
+ 'rx' => array(),
4561
+ 'ry' => array(),
4562
+ 'shape-rendering' => array(),
4563
+ 'sketch:type' => array(),
4564
+ 'stop-color' => array(),
4565
+ 'stop-opacity' => array(),
4566
+ 'stroke' => array(),
4567
+ 'stroke-dasharray' => array(),
4568
+ 'stroke-dashoffset' => array(),
4569
+ 'stroke-linecap' => array(),
4570
+ 'stroke-linejoin' => array(),
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(),
4589
+ 'xmlns:xlink' => array(),
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
+ ),
4598
+ ),
4599
+ 'rp' => array(
4600
+ array(
4601
+ 'attr_spec_list' => array(),
4602
+ 'tag_spec' => array(),
4603
+
4604
+ ),
4605
+ ),
4606
+ 'rt' => array(
4607
+ array(
4608
+ 'attr_spec_list' => array(),
4609
+ 'tag_spec' => array(),
4610
+
4611
+ ),
4612
+ ),
4613
+ 'rtc' => array(
4614
+ array(
4615
+ 'attr_spec_list' => array(),
4616
+ 'tag_spec' => array(),
4617
+
4618
+ ),
4619
+ ),
4620
+ 'ruby' => array(
4621
+ array(
4622
+ 'attr_spec_list' => array(),
4623
+ 'tag_spec' => array(),
4624
+
4625
+ ),
4626
+ ),
4627
+ 's' => array(
4628
+ array(
4629
+ 'attr_spec_list' => array(),
4630
+ 'tag_spec' => array(),
4631
+
4632
+ ),
4633
+ ),
4634
+ 'samp' => array(
4635
+ array(
4636
+ 'attr_spec_list' => array(),
4637
+ 'tag_spec' => array(),
4638
+
4639
+ ),
4640
+ ),
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
+ ),
4649
+ ),
4650
+ 'tag_spec' => array(
4651
+ 'spec_name' => 'script type=application/ld+json',
4652
+ ),
4653
+
4654
+ ),
4655
+ array(
4656
+ 'attr_spec_list' => array(
4657
+ 'type' => array(
4658
+ 'dispatch_key' => true,
4659
+ 'mandatory' => true,
4660
+ 'value_casei' => 'application/json',
4661
+ ),
4662
+ ),
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',
4684
+ ),
4685
+ ),
4686
+ 'tag_spec' => array(
4687
+ 'html_format' => array(
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
+ ),
4704
+ ),
4705
+ 'tag_spec' => array(
4706
+ 'html_format' => array(
4707
+ 'amp',
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
+ ),
4722
+ ),
4723
+ 'tag_spec' => array(
4724
+ 'html_format' => array(
4725
+ 'amp',
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
+ ),
4733
+ ),
4734
+ 'section' => array(
4735
+ array(
4736
+ 'attr_spec_list' => array(),
4737
+ 'tag_spec' => array(
4738
+ 'disallowed_ancestor' => array(
4739
+ 'amp-accordion',
4740
+ ),
4741
+ ),
4742
+
4743
+ ),
4744
+ array(
4745
+ 'attr_spec_list' => array(
4746
+ 'expanded' => array(
4747
+ 'value' => '',
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
+ ),
4758
+
4759
+ ),
4760
+ ),
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
+ ),
4780
+ ),
4781
+ 'small' => array(
4782
+ array(
4783
+ 'attr_spec_list' => array(),
4784
+ 'tag_spec' => array(),
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',
4794
+ 'allow_relative' => true,
4795
+ 'allowed_protocol' => array(
4796
+ 'https',
4797
+ ),
4798
+ ),
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',
4813
+ 'allow_relative' => true,
4814
+ 'allowed_protocol' => array(
4815
+ 'https',
4816
+ ),
4817
+ ),
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
+ ),
4827
+ array(
4828
+ 'attr_spec_list' => array(
4829
+ 'media' => array(),
4830
+ 'src' => array(
4831
+ 'blacklisted_value_regex' => '__amp_source_origin',
4832
+ 'mandatory' => true,
4833
+ 'allow_relative' => true,
4834
+ 'allowed_protocol' => array(
4835
+ 'https',
4836
+ ),
4837
+ ),
4838
+ 'type' => array(
4839
+ 'mandatory' => true,
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
+ ),
4849
+ array(
4850
+ 'attr_spec_list' => array(
4851
+ 'media' => array(),
4852
+ 'src' => array(
4853
+ 'blacklisted_value_regex' => '__amp_source_origin',
4854
+ 'mandatory' => true,
4855
+ 'allow_relative' => true,
4856
+ 'allowed_protocol' => array(
4857
+ 'https',
4858
+ ),
4859
+ ),
4860
+ 'type' => array(
4861
+ 'mandatory' => true,
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
+ ),
4871
+ ),
4872
+ 'spacer' => array(
4873
+ array(
4874
+ 'attr_spec_list' => array(),
4875
+ 'tag_spec' => array(
4876
+ 'html_format' => array(
4877
+ 'amp',
4878
+ ),
4879
+ ),
4880
+
4881
+ ),
4882
+ ),
4883
+ 'span' => array(
4884
+ array(
4885
+ 'attr_spec_list' => array(),
4886
+ 'tag_spec' => array(),
4887
+
4888
+ ),
4889
+ ),
4890
+ 'stop' => array(
4891
+ array(
4892
+ 'attr_spec_list' => array(
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
+ ),
4904
+ array(
4905
+ 'attr_spec_list' => array(
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
+ ),
4917
+ ),
4918
+ 'strike' => array(
4919
+ array(
4920
+ 'attr_spec_list' => array(),
4921
+ 'tag_spec' => array(
4922
+ 'html_format' => array(
4923
+ 'amp',
4924
+ ),
4925
+ ),
4926
+
4927
+ ),
4928
+ ),
4929
+ 'strong' => array(
4930
+ array(
4931
+ 'attr_spec_list' => array(),
4932
+ 'tag_spec' => array(),
4933
+
4934
+ ),
4935
+ ),
4936
+ 'style' => array(
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',
4956
+ 'unique' => true,
4957
+ ),
4958
+
4959
+ ),
4960
+ ),
4961
+ 'sub' => array(
4962
+ array(
4963
+ 'attr_spec_list' => array(),
4964
+ 'tag_spec' => array(),
4965
+
4966
+ ),
4967
+ ),
4968
+ 'sup' => array(
4969
+ array(
4970
+ 'attr_spec_list' => array(),
4971
+ 'tag_spec' => array(),
4972
+
4973
+ ),
4974
+ ),
4975
+ 'svg' => array(
4976
+ array(
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(),
4984
+ 'color' => array(),
4985
+ 'color-interpolation' => array(),
4986
+ 'color-interpolation-filters' => array(),
4987
+ 'color-profile' => array(),
4988
+ 'color-rendering' => array(),
4989
+ 'contentscripttype' => array(),
4990
+ 'contentstyletype' => array(),
4991
+ 'cursor' => array(),
4992
+ 'direction' => array(),
4993
+ 'display' => array(),
4994
+ 'dominant-baseline' => array(),
4995
+ 'enable-background' => array(),
4996
+ 'externalresourcesrequired' => array(),
4997
+ 'fill' => array(),
4998
+ 'fill-opacity' => array(),
4999
+ 'fill-rule' => array(),
5000
+ 'filter' => array(),
5001
+ 'flood-color' => array(),
5002
+ 'flood-opacity' => array(),
5003
+ 'font-family' => array(),
5004
+ 'font-size' => array(),
5005
+ 'font-size-adjust' => array(),
5006
+ 'font-stretch' => array(),
5007
+ 'font-style' => array(),
5008
+ 'font-variant' => array(),
5009
+ 'font-weight' => array(),
5010
+ 'glyph-orientation-horizontal' => array(),
5011
+ 'glyph-orientation-vertical' => array(),
5012
+ 'height' => array(),
5013
+ 'image-rendering' => array(),
5014
+ 'kerning' => array(),
5015
+ 'letter-spacing' => array(),
5016
+ 'lighting-color' => array(),
5017
+ 'marker-end' => array(),
5018
+ 'marker-mid' => array(),
5019
+ 'marker-start' => array(),
5020
+ 'mask' => array(),
5021
+ 'opacity' => array(),
5022
+ 'overflow' => array(),
5023
+ 'pointer-events' => array(),
5024
+ 'preserveaspectratio' => array(),
5025
+ 'requiredextensions' => array(),
5026
+ 'requiredfeatures' => array(),
5027
+ 'shape-rendering' => array(),
5028
+ 'stop-color' => array(),
5029
+ 'stop-opacity' => array(),
5030
+ 'stroke' => array(),
5031
+ 'stroke-dasharray' => array(),
5032
+ 'stroke-dashoffset' => array(),
5033
+ 'stroke-linecap' => array(),
5034
+ 'stroke-linejoin' => array(),
5035
+ 'stroke-miterlimit' => array(),
5036
+ 'stroke-opacity' => array(),
5037
+ 'stroke-width' => array(),
5038
+ 'systemlanguage' => array(),
5039
+ 'text-anchor' => array(),
5040
+ 'text-decoration' => array(),
5041
+ 'text-rendering' => array(),
5042
+ 'unicode-bidi' => array(),
5043
+ 'version' => array(
5044
+ 'value_regex' => '(1.0|1.1)',
5045
+ ),
5046
+ 'viewbox' => array(),
5047
+ 'visibility' => array(),
5048
+ 'width' => array(),
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(),
5056
+ 'xmlns:xlink' => array(),
5057
+ 'y' => array(),
5058
+ 'zoomandpan' => array(),
5059
+ ),
5060
+ 'tag_spec' => array(
5061
+ 'spec_url' => 'https://www.ampproject.org/docs/reference/spec.html#svg',
5062
+ ),
5063
+
5064
+ ),
5065
+ ),
5066
+ 'symbol' => array(
5067
+ array(
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(),
5075
+ 'color' => array(),
5076
+ 'color-interpolation' => array(),
5077
+ 'color-interpolation-filters' => array(),
5078
+ 'color-profile' => array(),
5079
+ 'color-rendering' => array(),
5080
+ 'cursor' => array(),
5081
+ 'direction' => array(),
5082
+ 'display' => array(),
5083
+ 'dominant-baseline' => array(),
5084
+ 'enable-background' => array(),
5085
+ 'externalresourcesrequired' => array(),
5086
+ 'fill' => array(),
5087
+ 'fill-opacity' => array(),
5088
+ 'fill-rule' => array(),
5089
+ 'filter' => array(),
5090
+ 'flood-color' => array(),
5091
+ 'flood-opacity' => array(),
5092
+ 'font-family' => array(),
5093
+ 'font-size' => array(),
5094
+ 'font-size-adjust' => array(),
5095
+ 'font-stretch' => array(),
5096
+ 'font-style' => array(),
5097
+ 'font-variant' => array(),
5098
+ 'font-weight' => array(),
5099
+ 'glyph-orientation-horizontal' => array(),
5100
+ 'glyph-orientation-vertical' => array(),
5101
+ 'image-rendering' => array(),
5102
+ 'kerning' => array(),
5103
+ 'letter-spacing' => array(),
5104
+ 'lighting-color' => array(),
5105
+ 'marker-end' => array(),
5106
+ 'marker-mid' => array(),
5107
+ 'marker-start' => array(),
5108
+ 'mask' => array(),
5109
+ 'opacity' => array(),
5110
+ 'overflow' => array(),
5111
+ 'pointer-events' => array(),
5112
+ 'preserveaspectratio' => array(),
5113
+ 'shape-rendering' => array(),
5114
+ 'stop-color' => array(),
5115
+ 'stop-opacity' => array(),
5116
+ 'stroke' => array(),
5117
+ 'stroke-dasharray' => array(),
5118
+ 'stroke-dashoffset' => array(),
5119
+ 'stroke-linecap' => array(),
5120
+ 'stroke-linejoin' => array(),
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
+ ),
5144
+ ),
5145
+ 'table' => array(
5146
+ array(
5147
+ 'attr_spec_list' => array(
5148
+ 'align' => array(),
5149
+ 'bgcolor' => array(),
5150
+ 'border' => array(
5151
+ 'value_regex' => '0|1',
5152
+ ),
5153
+ 'cellpadding' => array(),
5154
+ 'cellspacing' => array(),
5155
+ 'sortable' => array(),
5156
+ 'width' => array(),
5157
+ ),
5158
+ 'tag_spec' => array(),
5159
+
5160
+ ),
5161
+ ),
5162
+ 'tbody' => array(
5163
+ array(
5164
+ 'attr_spec_list' => array(),
5165
+ 'tag_spec' => array(),
5166
+
5167
+ ),
5168
+ ),
5169
+ 'td' => array(
5170
+ array(
5171
+ 'attr_spec_list' => array(
5172
+ 'align' => array(),
5173
+ 'bgcolor' => array(),
5174
+ 'colspan' => array(),
5175
+ 'headers' => array(),
5176
+ 'height' => array(),
5177
+ 'rowspan' => array(),
5178
+ 'valign' => array(),
5179
+ 'width' => array(),
5180
+ ),
5181
+ 'tag_spec' => array(),
5182
+
5183
+ ),
5184
+ ),
5185
+ 'template' => array(
5186
+ array(
5187
+ 'attr_spec_list' => array(
5188
+ 'type' => array(
5189
+ 'mandatory' => true,
5190
+ 'value' => 'amp-mustache',
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
+ ),
5204
+ ),
5205
+ 'text' => array(
5206
+ array(
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(),
5214
+ 'color' => array(),
5215
+ 'color-interpolation' => array(),
5216
+ 'color-interpolation-filters' => array(),
5217
+ 'color-profile' => array(),
5218
+ 'color-rendering' => array(),
5219
+ 'cursor' => array(),
5220
+ 'direction' => array(),
5221
+ 'display' => array(),
5222
+ 'dominant-baseline' => array(),
5223
+ 'dx' => array(),
5224
+ 'dy' => array(),
5225
+ 'enable-background' => array(),
5226
+ 'externalresourcesrequired' => array(),
5227
+ 'fill' => array(),
5228
+ 'fill-opacity' => array(),
5229
+ 'fill-rule' => array(),
5230
+ 'filter' => array(),
5231
+ 'flood-color' => array(),
5232
+ 'flood-opacity' => array(),
5233
+ 'font-family' => array(),
5234
+ 'font-size' => array(),
5235
+ 'font-size-adjust' => array(),
5236
+ 'font-stretch' => array(),
5237
+ 'font-style' => array(),
5238
+ 'font-variant' => array(),
5239
+ 'font-weight' => array(),
5240
+ 'glyph-orientation-horizontal' => array(),
5241
+ 'glyph-orientation-vertical' => array(),
5242
+ 'image-rendering' => array(),
5243
+ 'kerning' => array(),
5244
+ 'lengthadjust' => array(),
5245
+ 'letter-spacing' => array(),
5246
+ 'lighting-color' => array(),
5247
+ 'marker-end' => array(),
5248
+ 'marker-mid' => array(),
5249
+ 'marker-start' => array(),
5250
+ 'mask' => array(),
5251
+ 'opacity' => array(),
5252
+ 'overflow' => array(),
5253
+ 'pointer-events' => array(),
5254
+ 'requiredextensions' => array(),
5255
+ 'requiredfeatures' => array(),
5256
+ 'rotate' => array(),
5257
+ 'shape-rendering' => array(),
5258
+ 'stop-color' => array(),
5259
+ 'stop-opacity' => array(),
5260
+ 'stroke' => array(),
5261
+ 'stroke-dasharray' => array(),
5262
+ 'stroke-dashoffset' => array(),
5263
+ 'stroke-linecap' => array(),
5264
+ 'stroke-linejoin' => array(),
5265
+ 'stroke-miterlimit' => array(),
5266
+ 'stroke-opacity' => array(),
5267
+ 'stroke-width' => array(),
5268
+ 'systemlanguage' => array(),
5269
+ 'text-anchor' => array(),
5270
+ 'text-decoration' => array(),
5271
+ 'text-rendering' => array(),
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(),
5283
+ 'xmlns:xlink' => array(),
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
+ ),
5292
+ ),
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(),
5306
+ 'rows' => array(),
5307
+ 'selectiondirection' => array(),
5308
+ 'selectionend' => array(),
5309
+ 'selectionstart' => array(),
5310
+ 'spellcheck' => array(),
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
+ ),
5322
+ ),
5323
+ 'textpath' => array(
5324
+ array(
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(),
5332
+ 'color' => array(),
5333
+ 'color-interpolation' => array(),
5334
+ 'color-interpolation-filters' => array(),
5335
+ 'color-profile' => array(),
5336
+ 'color-rendering' => array(),
5337
+ 'cursor' => array(),
5338
+ 'direction' => array(),
5339
+ 'display' => array(),
5340
+ 'dominant-baseline' => array(),
5341
+ 'enable-background' => array(),
5342
+ 'externalresourcesrequired' => array(),
5343
+ 'fill' => array(),
5344
+ 'fill-opacity' => array(),
5345
+ 'fill-rule' => array(),
5346
+ 'filter' => array(),
5347
+ 'flood-color' => array(),
5348
+ 'flood-opacity' => array(),
5349
+ 'font-family' => array(),
5350
+ 'font-size' => array(),
5351
+ 'font-size-adjust' => array(),
5352
+ 'font-stretch' => array(),
5353
+ 'font-style' => array(),
5354
+ 'font-variant' => array(),
5355
+ 'font-weight' => array(),
5356
+ 'glyph-orientation-horizontal' => array(),
5357
+ 'glyph-orientation-vertical' => array(),
5358
+ 'image-rendering' => array(),
5359
+ 'kerning' => array(),
5360
+ 'letter-spacing' => array(),
5361
+ 'lighting-color' => array(),
5362
+ 'marker-end' => array(),
5363
+ 'marker-mid' => array(),
5364
+ 'marker-start' => array(),
5365
+ 'mask' => array(),
5366
+ 'method' => array(),
5367
+ 'opacity' => array(),
5368
+ 'overflow' => array(),
5369
+ 'pointer-events' => array(),
5370
+ 'requiredextensions' => array(),
5371
+ 'requiredfeatures' => array(),
5372
+ 'shape-rendering' => array(),
5373
+ 'spacing' => array(),
5374
+ 'startoffset' => array(),
5375
+ 'stop-color' => array(),
5376
+ 'stop-opacity' => array(),
5377
+ 'stroke' => array(),
5378
+ 'stroke-dasharray' => array(),
5379
+ 'stroke-dashoffset' => array(),
5380
+ 'stroke-linecap' => array(),
5381
+ 'stroke-linejoin' => array(),
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
+ ),
5414
+ ),
5415
+ 'tfoot' => array(
5416
+ array(
5417
+ 'attr_spec_list' => array(),
5418
+ 'tag_spec' => array(),
5419
+
5420
+ ),
5421
+ ),
5422
+ 'th' => array(
5423
+ array(
5424
+ 'attr_spec_list' => array(
5425
+ 'abbr' => array(),
5426
+ 'align' => array(),
5427
+ 'bgcolor' => array(),
5428
+ 'colspan' => array(),
5429
+ 'headers' => array(),
5430
+ 'height' => array(),
5431
+ 'rowspan' => array(),
5432
+ 'scope' => array(),
5433
+ 'sorted' => array(),
5434
+ 'valign' => array(),
5435
+ 'width' => array(),
5436
+ ),
5437
+ 'tag_spec' => array(),
5438
+
5439
+ ),
5440
+ ),
5441
+ 'thead' => array(
5442
+ array(
5443
+ 'attr_spec_list' => array(),
5444
+ 'tag_spec' => array(),
5445
+
5446
+ ),
5447
+ ),
5448
+ 'time' => array(
5449
+ array(
5450
+ 'attr_spec_list' => array(
5451
+ 'datetime' => array(),
5452
+ ),
5453
+ 'tag_spec' => array(),
5454
+
5455
+ ),
5456
+ ),
5457
+ 'title' => array(
5458
+ array(
5459
+ 'attr_spec_list' => array(),
5460
+ 'tag_spec' => array(
5461
+ 'spec_name' => 'title',
5462
+ ),
5463
+
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
+ ),
5481
+ ),
5482
+ 'tr' => array(
5483
+ array(
5484
+ 'attr_spec_list' => array(
5485
+ 'align' => array(),
5486
+ 'bgcolor' => array(),
5487
+ 'height' => array(),
5488
+ 'valign' => array(),
5489
+ ),
5490
+ 'tag_spec' => array(),
5491
+
5492
+ ),
5493
+ ),
5494
+ 'track' => array(
5495
+ array(
5496
+ 'attr_spec_list' => array(
5497
+ 'default' => array(
5498
+ 'value' => '',
5499
+ ),
5500
+ 'kind' => array(
5501
+ 'value_regex' => '(captions|descriptions|chapters|metadata)',
5502
+ ),
5503
+ 'label' => array(),
5504
+ 'src' => array(
5505
+ 'blacklisted_value_regex' => '__amp_source_origin',
5506
+ 'mandatory' => true,
5507
+ 'allow_relative' => false,
5508
+ 'allowed_protocol' => array(
5509
+ 'https',
5510
+ ),
5511
+ ),
5512
+ 'srclang' => array(),
5513
+ ),
5514
+ 'tag_spec' => array(
5515
+ 'mandatory_parent' => 'audio',
5516
+ 'spec_name' => 'audio > track',
5517
+ ),
5518
+
5519
+ ),
5520
+ array(
5521
+ 'attr_spec_list' => array(
5522
+ 'default' => array(
5523
+ 'value' => '',
5524
+ ),
5525
+ 'kind' => array(
5526
+ 'mandatory' => true,
5527
+ 'value_casei' => 'subtitles',
5528
+ ),
5529
+ 'label' => array(),
5530
+ 'src' => array(
5531
+ 'blacklisted_value_regex' => '__amp_source_origin',
5532
+ 'mandatory' => true,
5533
+ 'allow_relative' => false,
5534
+ 'allowed_protocol' => array(
5535
+ 'https',
5536
+ ),
5537
+ ),
5538
+ 'srclang' => array(
5539
+ 'mandatory' => true,
5540
+ ),
5541
+ ),
5542
+ 'tag_spec' => array(
5543
+ 'mandatory_parent' => 'audio',
5544
+ 'spec_name' => 'audio > track[kind=subtitles]',
5545
+ ),
5546
+
5547
+ ),
5548
+ array(
5549
+ 'attr_spec_list' => array(
5550
+ 'default' => array(
5551
+ 'value' => '',
5552
+ ),
5553
+ 'kind' => array(
5554
+ 'value_regex' => '(captions|descriptions|chapters|metadata)',
5555
+ ),
5556
+ 'label' => array(),
5557
+ 'src' => array(
5558
+ 'blacklisted_value_regex' => '__amp_source_origin',
5559
+ 'mandatory' => true,
5560
+ 'allow_relative' => false,
5561
+ 'allowed_protocol' => array(
5562
+ 'https',
5563
+ ),
5564
+ ),
5565
+ 'srclang' => array(),
5566
+ ),
5567
+ 'tag_spec' => array(
5568
+ 'mandatory_parent' => 'video',
5569
+ 'spec_name' => 'video > track',
5570
+ ),
5571
+
5572
+ ),
5573
+ array(
5574
+ 'attr_spec_list' => array(
5575
+ 'default' => array(
5576
+ 'value' => '',
5577
+ ),
5578
+ 'kind' => array(
5579
+ 'mandatory' => true,
5580
+ 'value_casei' => 'subtitles',
5581
+ ),
5582
+ 'label' => array(),
5583
+ 'src' => array(
5584
+ 'blacklisted_value_regex' => '__amp_source_origin',
5585
+ 'mandatory' => true,
5586
+ 'allow_relative' => false,
5587
+ 'allowed_protocol' => array(
5588
+ 'https',
5589
+ ),
5590
+ ),
5591
+ 'srclang' => array(
5592
+ 'mandatory' => true,
5593
+ ),
5594
+ ),
5595
+ 'tag_spec' => array(
5596
+ 'mandatory_parent' => 'video',
5597
+ 'spec_name' => 'video > track[kind=subtitles]',
5598
+ ),
5599
+
5600
+ ),
5601
+ array(
5602
+ 'attr_spec_list' => array(
5603
+ 'default' => array(
5604
+ 'value' => '',
5605
+ ),
5606
+ 'kind' => array(
5607
+ 'value_regex' => '(captions|descriptions|chapters|metadata)',
5608
+ ),
5609
+ 'label' => array(),
5610
+ 'src' => array(
5611
+ 'blacklisted_value_regex' => '__amp_source_origin',
5612
+ 'mandatory' => true,
5613
+ 'allow_relative' => false,
5614
+ 'allowed_protocol' => array(
5615
+ 'https',
5616
+ ),
5617
+ ),
5618
+ 'srclang' => array(),
5619
+ ),
5620
+ 'tag_spec' => array(
5621
+ 'mandatory_parent' => 'amp-audio',
5622
+ 'spec_name' => 'amp-audio > track',
5623
+ ),
5624
+
5625
+ ),
5626
+ array(
5627
+ 'attr_spec_list' => array(
5628
+ 'default' => array(
5629
+ 'value' => '',
5630
+ ),
5631
+ 'kind' => array(
5632
+ 'mandatory' => true,
5633
+ 'value_casei' => 'subtitles',
5634
+ ),
5635
+ 'label' => array(),
5636
+ 'src' => array(
5637
+ 'blacklisted_value_regex' => '__amp_source_origin',
5638
+ 'mandatory' => true,
5639
+ 'allow_relative' => false,
5640
+ 'allowed_protocol' => array(
5641
+ 'https',
5642
+ ),
5643
+ ),
5644
+ 'srclang' => array(
5645
+ 'mandatory' => true,
5646
+ ),
5647
+ ),
5648
+ 'tag_spec' => array(
5649
+ 'mandatory_parent' => 'amp-audio',
5650
+ 'spec_name' => 'amp-audio > track[kind=subtitles]',
5651
+ ),
5652
+
5653
+ ),
5654
+ array(
5655
+ 'attr_spec_list' => array(
5656
+ 'default' => array(
5657
+ 'value' => '',
5658
+ ),
5659
+ 'kind' => array(
5660
+ 'value_regex' => '(captions|descriptions|chapters|metadata)',
5661
+ ),
5662
+ 'label' => array(),
5663
+ 'src' => array(
5664
+ 'blacklisted_value_regex' => '__amp_source_origin',
5665
+ 'mandatory' => true,
5666
+ 'allow_relative' => false,
5667
+ 'allowed_protocol' => array(
5668
+ 'https',
5669
+ ),
5670
+ ),
5671
+ 'srclang' => array(),
5672
+ ),
5673
+ 'tag_spec' => array(
5674
+ 'mandatory_parent' => 'amp-video',
5675
+ 'spec_name' => 'amp-video > track',
5676
+ ),
5677
+
5678
+ ),
5679
+ array(
5680
+ 'attr_spec_list' => array(
5681
+ 'default' => array(
5682
+ 'value' => '',
5683
+ ),
5684
+ 'kind' => array(
5685
+ 'mandatory' => true,
5686
+ 'value_casei' => 'subtitles',
5687
+ ),
5688
+ 'label' => array(),
5689
+ 'src' => array(
5690
+ 'blacklisted_value_regex' => '__amp_source_origin',
5691
+ 'mandatory' => true,
5692
+ 'allow_relative' => false,
5693
+ 'allowed_protocol' => array(
5694
+ 'https',
5695
+ ),
5696
+ ),
5697
+ 'srclang' => array(
5698
+ 'mandatory' => true,
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(),
5717
+ 'color' => array(),
5718
+ 'color-interpolation' => array(),
5719
+ 'color-interpolation-filters' => array(),
5720
+ 'color-profile' => array(),
5721
+ 'color-rendering' => array(),
5722
+ 'cursor' => array(),
5723
+ 'direction' => array(),
5724
+ 'display' => array(),
5725
+ 'dominant-baseline' => array(),
5726
+ 'enable-background' => array(),
5727
+ 'externalresourcesrequired' => array(),
5728
+ 'fill' => array(),
5729
+ 'fill-opacity' => array(),
5730
+ 'fill-rule' => array(),
5731
+ 'filter' => array(),
5732
+ 'flood-color' => array(),
5733
+ 'flood-opacity' => array(),
5734
+ 'font-family' => array(),
5735
+ 'font-size' => array(),
5736
+ 'font-size-adjust' => array(),
5737
+ 'font-stretch' => array(),
5738
+ 'font-style' => array(),
5739
+ 'font-variant' => array(),
5740
+ 'font-weight' => array(),
5741
+ 'glyph-orientation-horizontal' => array(),
5742
+ 'glyph-orientation-vertical' => array(),
5743
+ 'image-rendering' => array(),
5744
+ 'kerning' => array(),
5745
+ 'letter-spacing' => array(),
5746
+ 'lighting-color' => array(),
5747
+ 'marker-end' => array(),
5748
+ 'marker-mid' => array(),
5749
+ 'marker-start' => array(),
5750
+ 'mask' => array(),
5751
+ 'opacity' => array(),
5752
+ 'overflow' => array(),
5753
+ 'pointer-events' => array(),
5754
+ 'requiredextensions' => array(),
5755
+ 'requiredfeatures' => array(),
5756
+ 'shape-rendering' => array(),
5757
+ 'stop-color' => array(),
5758
+ 'stop-opacity' => array(),
5759
+ 'stroke' => array(),
5760
+ 'stroke-dasharray' => array(),
5761
+ 'stroke-dashoffset' => array(),
5762
+ 'stroke-linecap' => array(),
5763
+ 'stroke-linejoin' => array(),
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
+ ),
5796
+ ),
5797
+ 'tspan' => array(
5798
+ array(
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(),
5806
+ 'color' => array(),
5807
+ 'color-interpolation' => array(),
5808
+ 'color-interpolation-filters' => array(),
5809
+ 'color-profile' => array(),
5810
+ 'color-rendering' => array(),
5811
+ 'cursor' => array(),
5812
+ 'direction' => array(),
5813
+ 'display' => array(),
5814
+ 'dominant-baseline' => array(),
5815
+ 'dx' => array(),
5816
+ 'dy' => array(),
5817
+ 'enable-background' => array(),
5818
+ 'externalresourcesrequired' => array(),
5819
+ 'fill' => array(),
5820
+ 'fill-opacity' => array(),
5821
+ 'fill-rule' => array(),
5822
+ 'filter' => array(),
5823
+ 'flood-color' => array(),
5824
+ 'flood-opacity' => array(),
5825
+ 'font-family' => array(),
5826
+ 'font-size' => array(),
5827
+ 'font-size-adjust' => array(),
5828
+ 'font-stretch' => array(),
5829
+ 'font-style' => array(),
5830
+ 'font-variant' => array(),
5831
+ 'font-weight' => array(),
5832
+ 'glyph-orientation-horizontal' => array(),
5833
+ 'glyph-orientation-vertical' => array(),
5834
+ 'image-rendering' => array(),
5835
+ 'kerning' => array(),
5836
+ 'lengthadjust' => array(),
5837
+ 'letter-spacing' => array(),
5838
+ 'lighting-color' => array(),
5839
+ 'marker-end' => array(),
5840
+ 'marker-mid' => array(),
5841
+ 'marker-start' => array(),
5842
+ 'mask' => array(),
5843
+ 'opacity' => array(),
5844
+ 'overflow' => array(),
5845
+ 'pointer-events' => array(),
5846
+ 'requiredextensions' => array(),
5847
+ 'requiredfeatures' => array(),
5848
+ 'rotate' => array(),
5849
+ 'shape-rendering' => array(),
5850
+ 'stop-color' => array(),
5851
+ 'stop-opacity' => array(),
5852
+ 'stroke' => array(),
5853
+ 'stroke-dasharray' => array(),
5854
+ 'stroke-dashoffset' => array(),
5855
+ 'stroke-linecap' => array(),
5856
+ 'stroke-linejoin' => array(),
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(),
5874
+ 'xmlns:xlink' => array(),
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
+ ),
5883
+ ),
5884
+ 'tt' => array(
5885
+ array(
5886
+ 'attr_spec_list' => array(),
5887
+ 'tag_spec' => array(
5888
+ 'html_format' => array(
5889
+ 'amp',
5890
+ ),
5891
+ ),
5892
+
5893
+ ),
5894
+ ),
5895
+ 'u' => array(
5896
+ array(
5897
+ 'attr_spec_list' => array(),
5898
+ 'tag_spec' => array(),
5899
+
5900
+ ),
5901
+ ),
5902
+ 'ul' => array(
5903
+ array(
5904
+ 'attr_spec_list' => array(),
5905
+ 'tag_spec' => array(),
5906
+
5907
+ ),
5908
+ ),
5909
+ 'use' => array(
5910
+ array(
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(),
5918
+ 'color' => array(),
5919
+ 'color-interpolation' => array(),
5920
+ 'color-interpolation-filters' => array(),
5921
+ 'color-profile' => array(),
5922
+ 'color-rendering' => array(),
5923
+ 'cursor' => array(),
5924
+ 'direction' => array(),
5925
+ 'display' => array(),
5926
+ 'dominant-baseline' => array(),
5927
+ 'enable-background' => array(),
5928
+ 'externalresourcesrequired' => array(),
5929
+ 'fill' => array(),
5930
+ 'fill-opacity' => array(),
5931
+ 'fill-rule' => array(),
5932
+ 'filter' => array(),
5933
+ 'flood-color' => array(),
5934
+ 'flood-opacity' => array(),
5935
+ 'font-family' => array(),
5936
+ 'font-size' => array(),
5937
+ 'font-size-adjust' => array(),
5938
+ 'font-stretch' => array(),
5939
+ 'font-style' => array(),
5940
+ 'font-variant' => array(),
5941
+ 'font-weight' => array(),
5942
+ 'glyph-orientation-horizontal' => array(),
5943
+ 'glyph-orientation-vertical' => array(),
5944
+ 'height' => array(),
5945
+ 'image-rendering' => array(),
5946
+ 'kerning' => array(),
5947
+ 'letter-spacing' => array(),
5948
+ 'lighting-color' => array(),
5949
+ 'marker-end' => array(),
5950
+ 'marker-mid' => array(),
5951
+ 'marker-start' => array(),
5952
+ 'mask' => array(),
5953
+ 'opacity' => array(),
5954
+ 'overflow' => array(),
5955
+ 'pointer-events' => array(),
5956
+ 'requiredextensions' => array(),
5957
+ 'requiredfeatures' => array(),
5958
+ 'shape-rendering' => array(),
5959
+ 'stop-color' => array(),
5960
+ 'stop-opacity' => array(),
5961
+ 'stroke' => array(),
5962
+ 'stroke-dasharray' => array(),
5963
+ 'stroke-dashoffset' => array(),
5964
+ 'stroke-linecap' => array(),
5965
+ 'stroke-linejoin' => array(),
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(),
5978
+ 'writing-mode' => array(),
5979
+ 'x' => array(),
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(),
5993
+ 'xmlns:xlink' => array(),
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
+ ),
6002
+ ),
6003
+ 'var' => array(
6004
+ array(
6005
+ 'attr_spec_list' => array(),
6006
+ 'tag_spec' => array(),
6007
+
6008
+ ),
6009
+ ),
6010
+ 'video' => array(
6011
+ array(
6012
+ 'attr_spec_list' => array(
6013
+ 'autoplay' => array(),
6014
+ 'controls' => array(),
6015
+ 'height' => array(),
6016
+ 'loop' => array(),
6017
+ 'muted' => array(),
6018
+ 'poster' => array(),
6019
+ 'preload' => array(),
6020
+ 'src' => array(
6021
+ 'blacklisted_value_regex' => '__amp_source_origin',
6022
+ 'allow_relative' => false,
6023
+ 'allowed_protocol' => array(
6024
+ 'data',
6025
+ 'https',
6026
+ ),
6027
+ ),
6028
+ 'width' => array(),
6029
+ ),
6030
+ 'tag_spec' => array(
6031
+ 'html_format' => array(
6032
+ 'amp',
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
+ ),
6040
+ ),
6041
+ 'view' => array(
6042
+ array(
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(),
6052
+ 'xmlns:xlink' => array(),
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
+ ),
6061
+ ),
6062
+ 'vkern' => array(
6063
+ array(
6064
+ 'attr_spec_list' => array(
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
+ ),
6082
+ ),
6083
+ 'wbr' => array(
6084
+ array(
6085
+ 'attr_spec_list' => array(),
6086
+ 'tag_spec' => array(),
6087
+
6088
+ ),
6089
+ ),
6090
+ 'xmp' => array(
6091
+ array(
6092
+ 'attr_spec_list' => array(),
6093
+ 'tag_spec' => array(
6094
+ 'html_format' => array(
6095
+ 'amp',
6096
+ ),
6097
+ ),
6098
+
6099
+ ),
6100
+ ),
6101
+ );
6102
+
6103
+ private static $layout_allowed_attrs = array(
6104
+ 'height' => array(),
6105
+ 'heights' => array(),
6106
+ 'layout' => array(),
6107
+ 'sizes' => array(),
6108
+ 'width' => array(),
6109
+
6110
+ );
6111
+
6112
+
6113
+ private static $globally_allowed_attrs = array(
6114
+ 'accesskey' => array(),
6115
+ 'amp-access' => array(),
6116
+ 'amp-access-behavior' => array(),
6117
+ 'amp-access-hide' => array(),
6118
+ 'amp-access-id' => array(),
6119
+ 'amp-access-loader' => array(),
6120
+ 'amp-access-loading' => array(),
6121
+ 'amp-access-off' => array(),
6122
+ 'amp-access-on' => array(),
6123
+ 'amp-access-show' => array(),
6124
+ 'amp-access-style' => array(),
6125
+ 'amp-access-template' => array(),
6126
+ 'aria-activedescendant' => array(),
6127
+ 'aria-atomic' => array(),
6128
+ 'aria-autocomplete' => array(),
6129
+ 'aria-busy' => array(),
6130
+ 'aria-checked' => array(),
6131
+ 'aria-controls' => array(),
6132
+ 'aria-describedby' => array(),
6133
+ 'aria-disabled' => array(),
6134
+ 'aria-dropeffect' => array(),
6135
+ 'aria-expanded' => array(),
6136
+ 'aria-flowto' => array(),
6137
+ 'aria-grabbed' => array(),
6138
+ 'aria-haspopup' => array(),
6139
+ 'aria-hidden' => array(),
6140
+ 'aria-invalid' => array(),
6141
+ 'aria-label' => array(),
6142
+ 'aria-labelledby' => array(),
6143
+ 'aria-level' => array(),
6144
+ 'aria-live' => array(),
6145
+ 'aria-multiline' => array(),
6146
+ 'aria-multiselectable' => array(),
6147
+ 'aria-orientation' => array(),
6148
+ 'aria-owns' => array(),
6149
+ 'aria-posinset' => array(),
6150
+ 'aria-pressed' => array(),
6151
+ 'aria-readonly' => array(),
6152
+ 'aria-relevant' => array(),
6153
+ 'aria-required' => array(),
6154
+ 'aria-selected' => array(),
6155
+ 'aria-setsize' => array(),
6156
+ 'aria-sort' => array(),
6157
+ 'aria-valuemax' => array(),
6158
+ 'aria-valuemin' => array(),
6159
+ 'aria-valuenow' => array(),
6160
+ 'aria-valuetext' => array(),
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(),
6176
+ 'itemscope' => array(),
6177
+ 'itemtype' => array(),
6178
+ 'lang' => array(),
6179
+ 'lightbox' => array(),
6180
+ 'on' => array(),
6181
+ 'overflow' => array(),
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
+
6196
+ public static function get_allowed_tags() {
6197
+ return self::$allowed_tags;
6198
+ }
6199
+
6200
+ public static function get_allowed_attributes() {
6201
+ return self::$globally_allowed_attrs;
6202
+ }
6203
+
6204
+ public static function get_layout_attributes() {
6205
+ return self::$layout_allowed_attrs;
6206
+ }
6207
+
6208
+ }
6209
+
6210
+ ?>
includes/sanitizers/class-amp-base-sanitizer.php CHANGED
@@ -42,9 +42,9 @@ abstract class AMP_Base_Sanitizer {
42
  }
43
 
44
  if ( AMP_String_Utils::endswith( $value, '%' ) ) {
45
- if ( 'width' === $dimension && isset( $this->args[ 'content_max_width'] ) ) {
46
  $percentage = absint( $value ) / 100;
47
- return round( $percentage * $this->args[ 'content_max_width'] );
48
  }
49
  }
50
 
42
  }
43
 
44
  if ( AMP_String_Utils::endswith( $value, '%' ) ) {
45
+ if ( 'width' === $dimension && isset( $this->args['content_max_width'] ) ) {
46
  $percentage = absint( $value ) / 100;
47
+ return round( $percentage * $this->args['content_max_width'] );
48
  }
49
  }
50
 
includes/sanitizers/class-amp-blacklist-sanitizer.php CHANGED
@@ -7,6 +7,9 @@ require_once( AMP__DIR__ . '/includes/sanitizers/class-amp-base-sanitizer.php' )
7
  *
8
  * See following for blacklist:
9
  * https://github.com/ampproject/amphtml/blob/master/spec/amp-html-format.md#html-tags
 
 
 
10
  */
11
  class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
12
  const PATTERN_REL_WP_ATTACHMENT = '#wp-att-([\d]+)#';
@@ -28,7 +31,7 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
28
  }
29
 
30
  private function strip_attributes_recursive( $node, $bad_attributes, $bad_protocols ) {
31
- if ( $node->nodeType !== XML_ELEMENT_NODE ) {
32
  return;
33
  }
34
 
@@ -36,7 +39,7 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
36
 
37
  // Some nodes may contain valid content but are themselves invalid.
38
  // Remove the node but preserve the children.
39
- if ( 'font' === $node_name ) {
40
  $this->replace_node_with_children( $node, $bad_attributes, $bad_protocols );
41
  return;
42
  } elseif ( 'a' === $node_name && false === $this->validate_a_node( $node ) ) {
@@ -49,13 +52,13 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
49
  for ( $i = $length - 1; $i >= 0; $i-- ) {
50
  $attribute = $node->attributes->item( $i );
51
  $attribute_name = strtolower( $attribute->name );
52
- if ( in_array( $attribute_name, $bad_attributes ) ) {
53
  $node->removeAttribute( $attribute_name );
54
  continue;
55
  }
56
 
57
  // on* attributes (like onclick) are a special case
58
- if ( 0 === stripos( $attribute_name, 'on' ) && $attribute_name != 'on' ) {
59
  $node->removeAttribute( $attribute_name );
60
  continue;
61
  } elseif ( 'a' === $node_name ) {
@@ -124,15 +127,10 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
124
  // Get the href attribute
125
  $href = $node->getAttribute( 'href' );
126
 
127
- // If no href is set and this isn't an anchor, it's invalid
128
  if ( empty( $href ) ) {
129
- $name_attr = $node->getAttribute( 'name' );
130
- if ( ! empty( $name_attr ) ) {
131
- // No further validation is required
132
- return true;
133
- } else {
134
- return false;
135
- }
136
  }
137
 
138
  // If this is an anchor link, just return true
@@ -150,11 +148,11 @@ class AMP_Blacklist_Sanitizer extends AMP_Base_Sanitizer {
150
  $protocol = strtok( $href, ':' );
151
 
152
  if ( false === filter_var( $href, FILTER_VALIDATE_URL )
153
- && ! in_array( $protocol, $special_protocols ) ) {
154
  return false;
155
  }
156
 
157
- if ( ! in_array( $protocol, $valid_protocols ) ) {
158
  return false;
159
  }
160
 
7
  *
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]+)#';
31
  }
32
 
33
  private function strip_attributes_recursive( $node, $bad_attributes, $bad_protocols ) {
34
+ if ( XML_ELEMENT_NODE !== $node->nodeType ) {
35
  return;
36
  }
37
 
39
 
40
  // Some nodes may contain valid content but are themselves invalid.
41
  // Remove the node but preserve the children.
42
+ if ( 'font' === $node_name ) {
43
  $this->replace_node_with_children( $node, $bad_attributes, $bad_protocols );
44
  return;
45
  } elseif ( 'a' === $node_name && false === $this->validate_a_node( $node ) ) {
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;
64
  } elseif ( 'a' === $node_name ) {
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
148
  $protocol = strtok( $href, ':' );
149
 
150
  if ( false === filter_var( $href, FILTER_VALIDATE_URL )
151
+ && ! in_array( $protocol, $special_protocols, true ) ) {
152
  return false;
153
  }
154
 
155
+ if ( ! in_array( $protocol, $valid_protocols, true ) ) {
156
  return false;
157
  }
158
 
includes/sanitizers/class-amp-iframe-sanitizer.php CHANGED
@@ -97,7 +97,6 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
97
  $out[ $name ] = $this->sanitize_dimension( $value, $name );
98
  break;
99
 
100
-
101
  case 'frameborder':
102
  if ( '0' !== $value && '1' !== $value ) {
103
  $value = '0';
@@ -117,8 +116,8 @@ class AMP_Iframe_Sanitizer extends AMP_Base_Sanitizer {
117
  }
118
  }
119
 
120
- if ( ! isset( $out[ 'sandbox' ] ) ) {
121
- $out[ 'sandbox' ] = self::SANDBOX_DEFAULTS;
122
  }
123
 
124
  return $out;
97
  $out[ $name ] = $this->sanitize_dimension( $value, $name );
98
  break;
99
 
 
100
  case 'frameborder':
101
  if ( '0' !== $value && '1' !== $value ) {
102
  $value = '0';
116
  }
117
  }
118
 
119
+ if ( ! isset( $out['sandbox'] ) ) {
120
+ $out['sandbox'] = self::SANDBOX_DEFAULTS;
121
  }
122
 
123
  return $out;
includes/sanitizers/class-amp-img-sanitizer.php CHANGED
@@ -18,51 +18,92 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
18
  private static $script_src = 'https://cdn.ampproject.org/v0/amp-anim-0.1.js';
19
 
20
  public function sanitize() {
 
21
  $nodes = $this->dom->getElementsByTagName( self::$tag );
 
 
22
  $num_nodes = $nodes->length;
 
23
  if ( 0 === $num_nodes ) {
24
  return;
25
  }
26
 
27
  for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
28
  $node = $nodes->item( $i );
29
- $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
30
 
31
- if ( empty( $old_attributes['src'] ) ) {
32
  $node->parentNode->removeChild( $node );
33
  continue;
34
  }
35
 
36
- $new_attributes = $this->filter_attributes( $old_attributes );
37
-
38
- // Try to extract dimensions for the image, if not set.
39
- if ( ! isset( $new_attributes['width'] ) || ! isset( $new_attributes['height'] ) ) {
40
- $dimensions = AMP_Image_Dimension_Extractor::extract( $new_attributes['src'] );
41
- if ( is_array( $dimensions ) ) {
42
- $new_attributes['width'] = $dimensions[0];
43
- $new_attributes['height'] = $dimensions[1];
44
- }
45
  }
 
46
 
47
- // Final fallback when we have no dimensions.
48
- if ( ! isset( $new_attributes['width'] ) || ! isset( $new_attributes['height'] ) ) {
49
- $new_attributes['width'] = isset( $this->args['content_max_width'] ) ? $this->args['content_max_width'] : self::FALLBACK_WIDTH;
50
- $new_attributes['height'] = self::FALLBACK_HEIGHT;
51
 
52
- $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-unknown-size' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
 
 
54
 
55
- $new_attributes = $this->enforce_sizes_attribute( $new_attributes );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- if ( $this->is_gif_url( $new_attributes['src'] ) ) {
58
- $this->did_convert_elements = true;
59
- $new_tag = 'amp-anim';
60
- } else {
61
- $new_tag = 'amp-img';
 
 
 
 
62
  }
63
-
64
- $new_node = AMP_DOM_Utils::create_node( $this->dom, $new_tag, $new_attributes );
65
- $node->parentNode->replaceChild( $new_node, $node );
66
  }
67
  }
68
 
@@ -103,7 +144,7 @@ class AMP_Img_Sanitizer extends AMP_Base_Sanitizer {
103
 
104
  private function is_gif_url( $url ) {
105
  $ext = self::$anim_extension;
106
- $path = parse_url( $url, PHP_URL_PATH );
107
- return $ext === substr( $path, -strlen( $ext ) );
108
  }
109
  }
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;
26
+
27
  if ( 0 === $num_nodes ) {
28
  return;
29
  }
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 );
36
  continue;
37
  }
38
 
39
+ // Determine which images need their dimensions determined/extracted.
40
+ if ( '' === $node->getAttribute( 'width' ) || '' === $node->getAttribute( 'height' ) ) {
41
+ $need_dimensions[ $node->getAttribute( 'src' ) ][] = $node;
42
+ } else {
43
+ $this->adjust_and_replace_node( $node );
 
 
 
 
44
  }
45
+ }
46
 
47
+ $this->determine_dimensions( $need_dimensions );
48
+ $this->adjust_and_replace_nodes_in_array_map( $need_dimensions );
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 );
85
+ $new_attributes = $this->filter_attributes( $old_attributes );
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';
92
+ }
93
+ $new_node = AMP_DOM_Utils::create_node( $this->dom, $new_tag, $new_attributes );
94
+ $node->parentNode->replaceChild( $new_node, $node );
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
 
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
  }
150
  }
includes/sanitizers/class-amp-playbuzz-sanitizer.php ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
47
+ $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
48
+
49
+ $new_attributes = $this->filter_attributes( $old_attributes );
50
+
51
+ if ( ! isset( $new_attributes['data-item'] ) && ! isset( $new_attributes['src'] ) ) {
52
+ continue;
53
+ }
54
+
55
+ $new_node = AMP_DOM_Utils::create_node( $this->dom, self::$script_slug, $new_attributes );
56
+
57
+ $node->parentNode->replaceChild( $new_node, $node );
58
+
59
+ $this->did_convert_elements = true;
60
+
61
+ }
62
+
63
+ }
64
+
65
+
66
+ private function filter_attributes( $attributes ) {
67
+ $out = array();
68
+
69
+ foreach ( $attributes as $name => $value ) {
70
+ switch ( $name ) {
71
+ case 'data-item':
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
+
100
+ $out['height'] = self::$height;
101
+
102
+ return $out;
103
+ }
104
+ }
includes/sanitizers/class-amp-style-sanitizer.php CHANGED
@@ -18,7 +18,7 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
18
  }
19
 
20
  private function collect_styles_recursive( $node ) {
21
- if ( $node->nodeType !== XML_ELEMENT_NODE ) {
22
  return;
23
  }
24
 
@@ -56,8 +56,11 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
56
  return array();
57
  }
58
 
59
- // Normalize order
60
- $styles = array_map( 'trim', explode( ';', $string ) );
 
 
 
61
  sort( $styles );
62
 
63
  $processed_styles = array();
18
  }
19
 
20
  private function collect_styles_recursive( $node ) {
21
+ if ( XML_ELEMENT_NODE !== $node->nodeType ) {
22
  return;
23
  }
24
 
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();
includes/sanitizers/class-amp-tag-and-attribute-sanitizer.php ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(),
34
+ 'amp_globally_allowed_attributes' => AMP_Allowed_Tags_Generated::get_allowed_attributes(),
35
+ 'amp_layout_allowed_attributes' => AMP_Allowed_Tags_Generated::get_layout_attributes(),
36
+ );
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
+
87
+ // If it's not an allowed tag, replace the node with it's children
88
+ $this->replace_node_with_children( $node );
89
+ // Return early since this node no longer exists.
90
+ return;
91
+ }
92
+
93
+ // Compile a list of rule_specs to validate for this node based on
94
+ // tag name of the node.
95
+ $rule_spec_list_to_validate = array();
96
+ if ( isset( $this->allowed_tags[ $node->nodeName ] ) ) {
97
+ $rule_spec_list = $this->allowed_tags[ $node->nodeName ];
98
+ }
99
+ foreach ( $rule_spec_list as $id => $rule_spec ) {
100
+ if ( $this->validate_tag_spec_for_node( $node, $rule_spec[AMP_Rule_Spec::TAG_SPEC] ) ) {
101
+ $rule_spec_list_to_validate[ $id ] = $rule_spec;
102
+ }
103
+ }
104
+
105
+ // If no valid rule_specs exist, then remove this node and return.
106
+ if ( empty( $rule_spec_list_to_validate ) ) {
107
+ $this->remove_node( $node );
108
+ return;
109
+ }
110
+
111
+ // The remaining validations all have to do with attributes.
112
+ if ( $node->hasAttributes() ) {
113
+ $attr_spec_list = array();
114
+
115
+ // If we have exactly one rule_spec, use it's attr_spec_list to
116
+ // validate the node's attributes.
117
+ if ( 1 == count( $rule_spec_list_to_validate ) ) {
118
+ $rule_spec = array_pop( $rule_spec_list_to_validate );
119
+ $attr_spec_list = $rule_spec[AMP_Rule_Spec::ATTR_SPEC_LIST];
120
+
121
+ // If there is more than one valid rule_spec for this node, then
122
+ // try to deduce which one is intended by inspecting the node's
123
+ // attributes.
124
+ } else {
125
+
126
+ // Get a score from each attr_spec_list by seeing how many
127
+ // attributes and values match the node.
128
+ $attr_spec_scores = array();
129
+ foreach ( $rule_spec_list_to_validate as $spec_id => $rule_spec ) {
130
+ $attr_spec_scores[ $spec_id ] = $this->validate_attr_spec_list_for_node( $node, $rule_spec[AMP_Rule_Spec::ATTR_SPEC_LIST] );
131
+ }
132
+
133
+ // Get the key(s) to the highest score(s).
134
+ $spec_ids_sorted = array_keys( $attr_spec_scores, max( $attr_spec_scores ) );
135
+
136
+ // If there is exactly one attr_spec with a max score, use that one.
137
+ if ( 1 == count( $spec_ids_sorted ) ) {
138
+ $attr_spec_list = $rule_spec_list_to_validate[ $spec_ids_sorted[0] ][AMP_Rule_Spec::ATTR_SPEC_LIST];
139
+
140
+ // Otherwise...
141
+ } else {
142
+ // This shoud not happen very often, but...
143
+ // If we're here, then we have no idea which spec should
144
+ // be used. Try to merge the top scoring ones and cross
145
+ // your fingers.
146
+ foreach( $spec_ids_sorted as $id ) {
147
+ $attr_spec_list = array_merge( $attr_spec_list, $rule_spec_list_to_validate[ $id ][AMP_Rule_Spec::ATTR_SPEC_LIST] );
148
+ }
149
+ }
150
+ }
151
+
152
+ // Remove any remaining disallowed attributes.
153
+ $this->sanitize_disallowed_attributes_in_node( $node, $attr_spec_list );
154
+
155
+ // Remove values that don't conform to the attr_spec.
156
+ $this->sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list );
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Checks to see if a node's placement with the DOM is be valid for the
162
+ * given tag_spec. If there are restrictions placed on the type of node
163
+ * that can be an immediate parent or an ancestor of this node, then make
164
+ * sure those restrictions are met.
165
+ *
166
+ * If any of the tests on the restrictions fail, return false, otherwise
167
+ * return true.
168
+ */
169
+ private function validate_tag_spec_for_node( $node, $tag_spec ) {
170
+
171
+ if ( ! empty( $tag_spec[AMP_Rule_Spec::MANDATORY_PARENT] ) &&
172
+ ! $this->has_parent( $node, $tag_spec[AMP_Rule_Spec::MANDATORY_PARENT] ) ) {
173
+ return false;
174
+ }
175
+
176
+ if ( ! empty( $tag_spec[AMP_Rule_Spec::DISALLOWED_ANCESTOR] ) ) {
177
+ foreach ( $tag_spec[AMP_Rule_Spec::DISALLOWED_ANCESTOR] as $disallowed_ancestor_node_name ) {
178
+ if ( $this->has_ancestor( $node, $disallowed_ancestor_node_name ) ) {
179
+ return false;
180
+ }
181
+ }
182
+ }
183
+
184
+ if ( ! empty( $tag_spec[AMP_Rule_Spec::MANDATORY_ANCESTOR] ) &&
185
+ ! $this->has_ancestor( $node, $tag_spec[AMP_Rule_Spec::MANDATORY_ANCESTOR] ) ) {
186
+ return false;
187
+ }
188
+
189
+ return true;
190
+ }
191
+
192
+ /**
193
+ * Checks to see if a spec is potentially valid for the given node based on
194
+ * the attributes present in the node.
195
+ *
196
+ * Note: This can be a very expensive function. Use it sparingly.
197
+ */
198
+ private function validate_attr_spec_list_for_node( $node, $attr_spec_list ) {
199
+ // If this node doesn't have any attributes, there is no point in
200
+ // continuing.
201
+ if ( ! $node->hasAttributes() ) {
202
+ return 0;
203
+ }
204
+
205
+ foreach( $node->attributes as $attr_name => $attr_node ) {
206
+ if ( isset( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
207
+ foreach( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] as $attr_alt_name ) {
208
+ $attr_spec_list[ $attr_alt_name ] = $attr_spec_list[ $attr_name ];
209
+ }
210
+ }
211
+ }
212
+
213
+ $score = 0;
214
+
215
+ // Iterate through each attribute rule in this attr spec list and run
216
+ // the series of tests. Each filter is given a `$node`, an `$attr_name`,
217
+ // and an `$attr_spec_rule`. If the `$attr_spec_rule` seems to be valid
218
+ // for the given node, then the filter should increment the score by one.
219
+ foreach ( $attr_spec_list as $attr_name => $attr_spec_rule ) {
220
+
221
+ // If a mandatory attribute is required, and attribute exists, pass.
222
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::MANDATORY] ) ) {
223
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule ) ) {
224
+ $score += 1;
225
+ }
226
+ }
227
+
228
+ // Check 'value' - case sensitive
229
+ // Given attribute's value must exactly equal value of the rule to pass.
230
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE] ) ) {
231
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) ) {
232
+ $score += 1;
233
+ }
234
+ }
235
+
236
+ // Check 'value_regex' - case sensitive regex match
237
+ // Given attribute's value must be a case insensitive match to regex pattern
238
+ // specified by the value of rule to pass.
239
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX] ) ) {
240
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
241
+ $score += 1;
242
+ }
243
+ }
244
+
245
+ // Check 'value_casei' - case insensitive
246
+ // Given attribute's value must be a case insensitive match to the value of
247
+ // the rule to pass.
248
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] ) ) {
249
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
250
+ $score += 1;
251
+ }
252
+ }
253
+
254
+ // Check 'value_regex_casei' - case insensitive regex match
255
+ // Given attribute's value must be a case insensitive match to the regex
256
+ // pattern specified by the value of the rule to pass.
257
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI] ) ) {
258
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
259
+ $score += 1;
260
+ }
261
+ }
262
+
263
+ // If given attribute's value is a URL with a protocol, the protocol must
264
+ // be in the array specified by the rule's value to pass.
265
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
266
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
267
+ $score += 1;
268
+ }
269
+ }
270
+
271
+ // If the given attribute's value is *not* a relative path, and the rule's
272
+ // value is `false`, then pass.
273
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) ) {
274
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
275
+ $score += 1;
276
+ }
277
+ }
278
+
279
+ // If the given attribute's value exists, is non-empty and the rule's value
280
+ // is false, then pass.
281
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) ) {
282
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
283
+ $score += 1;
284
+ }
285
+ }
286
+
287
+ // If the given attribute's value is a URL and does not match any of the list
288
+ // of domains in the value of the rule, then pass.
289
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] ) ) {
290
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) ) {
291
+ $score += 1;
292
+ }
293
+ }
294
+
295
+ // If the attribute's value exists and does not match the regex specified
296
+ // by the rule's value, then pass.
297
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] ) ) {
298
+ if ( AMP_Rule_Spec::PASS == $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
299
+ $score += 1;
300
+ }
301
+ }
302
+ }
303
+
304
+ // return true;
305
+ return $score;
306
+ }
307
+
308
+ /**
309
+ * If an attribute is not listed in $allowed_attrs, then it will be removed
310
+ * from $node.
311
+ */
312
+ private function sanitize_disallowed_attributes_in_node( $node, $attr_spec_list ) {
313
+ // Note: We can't remove the attributes inside the 'foreach' loop
314
+ // because that breaks the process of iterating through the attrs. So,
315
+ // we keep track of what needs to be removed in the first loop, then
316
+ // actually remove the attributes in the second loop.
317
+ $attrs_to_remove = array();
318
+ foreach ( $node->attributes as $attr_name => $attr_node ) {
319
+ // see if this attribute is allowed for this node
320
+ if ( ! $this->is_amp_allowed_attribute( $attr_name, $attr_spec_list ) ) {
321
+ $attrs_to_remove[] = $attr_name;
322
+ }
323
+ }
324
+
325
+ if ( ! empty( $attrs_to_remove ) ) {
326
+ // Make sure we're not removing an attribtue that is listed as an alternative
327
+ // for some other allowed attribtue. ex. 'srcset' is an alternative for 'src'.
328
+ foreach ( $attr_spec_list as $attr_name => $attr_spec_rule_value ) {
329
+ if ( isset( $attr_spec_rule_value[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
330
+ foreach ( $attr_spec_rule_value[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
331
+ $alt_name_keys = array_keys( $attrs_to_remove, $alternative_name, true );
332
+ if ( ! empty( $alt_name_keys ) ) {
333
+ unset( $attrs_to_remove[ $alt_name_keys[0] ] );
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ // Remove the disllowed attributes
340
+ foreach ( $attrs_to_remove as $attr_name ) {
341
+ $node->removeAttribute( $attr_name );
342
+ }
343
+ }
344
+ }
345
+
346
+ /**
347
+ * If a node has an attribute value that is disallowed, then that value will
348
+ * be removed from the attribute on this node.
349
+ */
350
+ private function sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ) {
351
+ $this->_sanitize_disallowed_attribute_values_in_node( $node, $this->globally_allowed_attributes );
352
+ if ( ! empty( $attr_spec_list ) ) {
353
+ $this->_sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list );
354
+ }
355
+ }
356
+ private function _sanitize_disallowed_attribute_values_in_node( $node, $attr_spec_list ) {
357
+ $attrs_to_remove = array();
358
+
359
+ foreach( $attr_spec_list as $attr_name => $attr_val ) {
360
+ if ( isset( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
361
+ foreach( $attr_spec_list[ $attr_name ][AMP_Rule_Spec::ALTERNATIVE_NAMES] as $attr_alt_name ) {
362
+ $attr_spec_list[ $attr_alt_name ] = $attr_spec_list[ $attr_name ];
363
+ }
364
+ }
365
+ }
366
+
367
+ foreach( $node->attributes as $attr_name => $attr_node ) {
368
+
369
+ if ( ! isset( $attr_spec_list[$attr_name] ) ) {
370
+ continue;
371
+ }
372
+
373
+ $attr_spec_rule = $attr_spec_list[$attr_name];
374
+
375
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE] ) &&
376
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) ) {
377
+ $attrs_to_remove[] = $attr_name;
378
+ continue;
379
+ }
380
+
381
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] ) &&
382
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) ) {
383
+ $attrs_to_remove[] = $attr_name;
384
+ continue;
385
+ }
386
+
387
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX] ) &&
388
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
389
+ $attrs_to_remove[] = $attr_name;
390
+ continue;
391
+ }
392
+
393
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI] ) &&
394
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) ) {
395
+ $attrs_to_remove[] = $attr_name;
396
+ continue;
397
+ }
398
+
399
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) &&
400
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) ) {
401
+ $attrs_to_remove[] = $attr_name;
402
+ continue;
403
+ }
404
+
405
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) &&
406
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) ) {
407
+ $attrs_to_remove[] = $attr_name;
408
+ continue;
409
+ }
410
+
411
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) &&
412
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) ) {
413
+ $attrs_to_remove[] = $attr_name;
414
+ continue;
415
+ }
416
+
417
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] ) &&
418
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) ) {
419
+ $attrs_to_remove[] = $attr_name;
420
+ continue;
421
+ }
422
+
423
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] ) &&
424
+ AMP_Rule_Spec::FAIL == $this->check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) ) {
425
+ $attrs_to_remove[] = $attr_name;
426
+ continue;
427
+ }
428
+ }
429
+
430
+ // Remove the disallowed values
431
+ foreach ( $attrs_to_remove as $attr_name ) {
432
+ if ( isset( $attr_spec_list[$attr_name][AMP_Rule_Spec::ALLOW_EMPTY] ) &&
433
+ ( true == $attr_spec_list[$attr_name][AMP_Rule_Spec::ALLOW_EMPTY] ) ) {
434
+ $attr = $node->attributes;
435
+ $attr[ $attr_name ]->value = '';
436
+ } else {
437
+ $node->removeAttribute( $attr_name );
438
+ }
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Checks to see if the given attribute is mandatory for the given node and
444
+ * whether the attribute (or a specified alternate) exists.
445
+ *
446
+ * Returns:
447
+ * - AMP_Rule_Spec::PASS - $attr_name is mandatory and it exists
448
+ * - AMP_Rule_Spec::FAIL - $attr_name is mandatory, but doesn't exist
449
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name is not mandatory
450
+ */
451
+ private function check_attr_spec_rule_mandatory( $node, $attr_name, $attr_spec_rule ) {
452
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::MANDATORY] ) &&
453
+ ( true == $attr_spec_rule[AMP_Rule_Spec::MANDATORY] ) ) {
454
+ if ( $node->hasAttribute( $attr_name ) ) {
455
+ return AMP_Rule_Spec::PASS;
456
+ } else {
457
+ // check if an alternative name list is specified
458
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
459
+ foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alt_name ) {
460
+ if ( $node->hasAttribute( $alt_name ) ) {
461
+ return AMP_Rule_Spec::PASS;
462
+ }
463
+ }
464
+ }
465
+
466
+ return AMP_Rule_Spec::FAIL;
467
+ }
468
+ }
469
+ return AMP_Rule_Spec::NOT_APPLICABLE;
470
+ }
471
+
472
+ /**
473
+ * Checks to see if the given attribute exists, has a value rule, and whether
474
+ * the attribute matches the value.
475
+ *
476
+ * Returns:
477
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
478
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
479
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
480
+ * is no rule for this attribute.
481
+ */
482
+ private function check_attr_spec_rule_value( $node, $attr_name, $attr_spec_rule ) {
483
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE] ) ) {
484
+ if ( $node->hasAttribute( $attr_name ) ) {
485
+ if ( $node->getAttribute( $attr_name ) == $attr_spec_rule[AMP_Rule_Spec::VALUE] ) {
486
+ return AMP_Rule_Spec::PASS;
487
+ } else {
488
+ return AMP_Rule_Spec::FAIL;
489
+ }
490
+ } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
491
+ foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
492
+ if ( $node->hasAttribute( $alternative_name ) ) {
493
+ if ( $node->getAttribute( $alternative_name ) == $attr_spec_rule[AMP_Rule_Spec::VALUE] ) {
494
+ return AMP_Rule_Spec::PASS;
495
+ } else {
496
+ return AMP_Rule_Spec::FAIL;
497
+ }
498
+ }
499
+ }
500
+ }
501
+ }
502
+ return AMP_Rule_Spec::NOT_APPLICABLE;
503
+ }
504
+
505
+ /**
506
+ * Checks to see if the given attribute exists, has a value rule, and whether
507
+ * or not the value matches the rule without respect to case.
508
+ *
509
+ * Returns:
510
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
511
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
512
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
513
+ * is no rule for this attribute.
514
+ */
515
+ private function check_attr_spec_rule_value_casei( $node, $attr_name, $attr_spec_rule ) {
516
+ // check 'value_casei' - case insensitive
517
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] ) ) {
518
+ $rule_value = strtolower( $attr_spec_rule[AMP_Rule_Spec::VALUE_CASEI] );
519
+ if ( $node->hasAttribute( $attr_name ) ) {
520
+ $attr_value = strtolower( $node->getAttribute( $attr_name ) );
521
+ if ( $attr_value == $rule_value ) {
522
+ return AMP_Rule_Spec::PASS;
523
+ } else {
524
+ return AMP_Rule_Spec::FAIL;
525
+ }
526
+ } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
527
+ foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
528
+ if ( $node->hasAttribute( $alternative_name ) ) {
529
+ $attr_value = strtolower( $node->getAttribute( $alternative_name ) );
530
+ if ( $attr_value == $rule_value ) {
531
+ return AMP_Rule_Spec::PASS;
532
+ } else {
533
+ return AMP_Rule_Spec::FAIL;
534
+ }
535
+ }
536
+ }
537
+ }
538
+ }
539
+ return AMP_Rule_Spec::NOT_APPLICABLE;
540
+ }
541
+
542
+ /**
543
+ * Checks to see if the given attribute exists, has a value_regex rule, and
544
+ * whether or not the value matches the rule.
545
+ *
546
+ * Returns:
547
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
548
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
549
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
550
+ * is no rule for this attribute.
551
+ */
552
+ private function check_attr_spec_rule_value_regex( $node, $attr_name, $attr_spec_rule ) {
553
+ // check 'value_regex' - case sensitive regex match
554
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX] ) && $node->hasAttribute( $attr_name ) ) {
555
+ $rule_value = $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX];
556
+ // Note: I added in the '^' and '$' to the regex pattern even though
557
+ // they weren't in the AMP spec. But leaving them out would allow
558
+ // both '_blank' and 'yyy_blankzzz' to be matched by a regex rule of
559
+ // '(_blank|_self|_top)'. The AMP JS validator only accepts '_blank',
560
+ // so I'm leaving it this way for now.
561
+ if ( preg_match('@^' . $rule_value . '$@u', $node->getAttribute( $attr_name )) ) {
562
+ return AMP_Rule_Spec::PASS;
563
+ } else {
564
+ return AMP_Rule_Spec::FAIL;
565
+ }
566
+ }
567
+ return AMP_Rule_Spec::NOT_APPLICABLE;
568
+ }
569
+
570
+ /**
571
+ * Checks to see if the given attribute exists, has a value_regex_casei rule,
572
+ * and whether or not the value matches the rule without respect to case.
573
+ *
574
+ * Returns:
575
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
576
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
577
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
578
+ * is no rule for this attribute.
579
+ */
580
+ private function check_attr_spec_rule_value_regex_casei( $node, $attr_name, $attr_spec_rule ) {
581
+ // check 'value_regex_casei' - case insensitive regex match
582
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI] ) && $node->hasAttribute( $attr_name ) ) {
583
+ $rule_value = $attr_spec_rule[AMP_Rule_Spec::VALUE_REGEX_CASEI];
584
+ // See note above regarding the '^' and '$' that are added here.
585
+ if ( preg_match('/^' . $rule_value . '$/ui', $node->getAttribute( $attr_name ) ) ) {
586
+ return AMP_Rule_Spec::PASS;
587
+ } else {
588
+ return AMP_Rule_Spec::FAIL;
589
+ }
590
+ }
591
+ return AMP_Rule_Spec::NOT_APPLICABLE;
592
+ }
593
+
594
+ /**
595
+ * Checks to see if the given attribute exists, has a protocol rule, and
596
+ * whether or not the value matches the rule.
597
+ *
598
+ * Returns:
599
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
600
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
601
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
602
+ * is no rule for this attribute.
603
+ */
604
+ private function check_attr_spec_rule_allowed_protocol( $node, $attr_name, $attr_spec_rule ) {
605
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
606
+ if ( $node->hasAttribute( $attr_name ) ) {
607
+ $attr_value = $node->getAttribute( $attr_name );
608
+ $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
609
+ $urls_to_test = explode( ',', $attr_value );
610
+ foreach ( $urls_to_test as $url ) {
611
+ // This seems to be an acceptable check since the AMP validator
612
+ // will allow a URL with no protocol to pass validation.
613
+ if ( $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME ) ) {
614
+ if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
615
+ return AMP_Rule_Spec::FAIL;
616
+ }
617
+ }
618
+ }
619
+ return AMP_Rule_Spec::PASS;
620
+ } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
621
+ foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
622
+ if ( $node->hasAttribute( $alternative_name ) ) {
623
+ $attr_value = $node->getAttribute( $alternative_name );
624
+ $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
625
+ $urls_to_test = explode( ',', $attr_value );
626
+ foreach ( $urls_to_test as $url ) {
627
+ // This seems to be an acceptable check since the AMP validator
628
+ // will allow a URL with no protocol to pass validation.
629
+ if ( $url_scheme = AMP_WP_Utils::parse_url( $url, PHP_URL_SCHEME ) ) {
630
+ if ( ! in_array( strtolower( $url_scheme ), $attr_spec_rule[AMP_Rule_Spec::ALLOWED_PROTOCOL] ) ) {
631
+ return AMP_Rule_Spec::FAIL;
632
+ }
633
+ }
634
+ }
635
+ return AMP_Rule_Spec::PASS;
636
+ }
637
+ }
638
+ }
639
+ }
640
+ return AMP_Rule_Spec::NOT_APPLICABLE;
641
+ }
642
+
643
+ /**
644
+ * Checks to see if the given attribute exists, has a disallowed_relative rule,
645
+ * and whether or not the value matches the rule.
646
+ *
647
+ * Returns:
648
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
649
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
650
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
651
+ * is no rule for this attribute.
652
+ */
653
+ private function check_attr_spec_rule_disallowed_relative( $node, $attr_name, $attr_spec_rule ) {
654
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) &&
655
+ ( false == $attr_spec_rule[AMP_Rule_Spec::ALLOW_RELATIVE] ) ) {
656
+ if ( $node->hasAttribute( $attr_name ) ) {
657
+ $attr_value = $node->getAttribute( $attr_name );
658
+ $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
659
+ $urls_to_test = explode( ',', $attr_value );
660
+ foreach ( $urls_to_test as $url ) {
661
+ $parsed_url = AMP_WP_Utils::parse_url( $url );
662
+ // The JS AMP validator seems to consider 'relative' to mean
663
+ // *protocol* relative, not *host* relative for this rule. So,
664
+ // a url with an empty 'scheme' is considered "relative" by AMP.
665
+ // ie. '//domain.com/path' and '/path' should both be considered
666
+ // relative for purposes of AMP validation.
667
+ if ( empty( $parsed_url['scheme'] ) ) {
668
+ return AMP_Rule_Spec::FAIL;
669
+ }
670
+ }
671
+ return AMP_Rule_Spec::PASS;
672
+ } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
673
+ foreach ( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
674
+ if ( $node->hasAttribute( $alternative_name ) ) {
675
+ $attr_value = $node->getAttribute( $alternative_name );
676
+ $attr_value = preg_replace( '/\s*,\s*/', ',', $attr_value );
677
+ $urls_to_test = explode( ',', $attr_value );
678
+ foreach ( $urls_to_test as $url ) {
679
+ $parsed_url = AMP_WP_Utils::parse_url( $url );
680
+ if ( empty( $parsed_url['scheme'] ) ) {
681
+ return AMP_Rule_Spec::FAIL;
682
+ }
683
+ }
684
+ }
685
+ }
686
+ return AMP_Rule_Spec::PASS;
687
+ }
688
+ }
689
+ return AMP_Rule_Spec::NOT_APPLICABLE;
690
+ }
691
+
692
+ /**
693
+ * Checks to see if the given attribute exists, has a disallowed_relative rule,
694
+ * and whether or not the value matches the rule.
695
+ *
696
+ * Returns:
697
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
698
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
699
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
700
+ * is no rule for this attribute.
701
+ */
702
+ private function check_attr_spec_rule_disallowed_empty( $node, $attr_name, $attr_spec_rule ) {
703
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) &&
704
+ ( false == $attr_spec_rule[AMP_Rule_Spec::ALLOW_EMPTY] ) &&
705
+ $node->hasAttribute( $attr_name ) ) {
706
+ $attr_value = $node->getAttribute( $attr_name );
707
+ if ( empty( $attr_value ) ) {
708
+ return AMP_Rule_Spec::FAIL;
709
+ }
710
+ return AMP_Rule_Spec::PASS;
711
+ }
712
+ return AMP_Rule_Spec::NOT_APPLICABLE;
713
+ }
714
+
715
+ /**
716
+ * Checks to see if the given attribute exists, has a disallowed_domain rule,
717
+ * and whether or not the value matches the rule.
718
+ *
719
+ * Returns:
720
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
721
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
722
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
723
+ * is no rule for this attribute.
724
+ */
725
+ private function check_attr_spec_rule_disallowed_domain( $node, $attr_name, $attr_spec_rule ) {
726
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] ) &&
727
+ $node->hasAttribute( $attr_name ) ) {
728
+ $attr_value = $node->getAttribute( $attr_name );
729
+ $url_domain = AMP_WP_Utils::parse_url( $attr_value, PHP_URL_HOST );
730
+ if ( ! empty( $url_domain ) ) {
731
+ foreach ( $attr_spec_rule[AMP_Rule_Spec::DISALLOWED_DOMAIN] as $disallowed_domain ) {
732
+ if ( strtolower( $url_domain ) == strtolower( $disallowed_domain ) ) {
733
+ // Found a disallowed domain, fail validation.
734
+ return AMP_Rule_Spec::FAIL;
735
+ }
736
+ }
737
+ return AMP_Rule_Spec::PASS;
738
+ }
739
+ }
740
+ return AMP_Rule_Spec::NOT_APPLICABLE;
741
+ }
742
+
743
+ /**
744
+ * Checks to see if the given attribute exists, has a blacklisted_value_regex rule,
745
+ * and whether or not the value matches the rule.
746
+ *
747
+ * Returns:
748
+ * - AMP_Rule_Spec::PASS - $attr_name has a value that matches the rule.
749
+ * - AMP_Rule_Spec::FAIL - $attr_name has a value that does *not* match rule.
750
+ * - AMP_Rule_Spec::NOT_APPLICABLE - $attr_name does not exist or there
751
+ * is no rule for this attribute.
752
+ */
753
+ private function check_attr_spec_rule_blacklisted_value_regex( $node, $attr_name, $attr_spec_rule ) {
754
+ if ( isset( $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] ) ) {
755
+ $pattern = '/' . $attr_spec_rule[AMP_Rule_Spec::BLACKLISTED_VALUE_REGEX] . '/u';
756
+ if ( $node->hasAttribute( $attr_name ) ) {
757
+ $attr_value = $node->getAttribute( $attr_name );
758
+ if ( preg_match( $pattern, $attr_value ) ) {
759
+ return AMP_Rule_Spec::FAIL;
760
+ } else {
761
+ return AMP_Rule_Spec::PASS;
762
+ }
763
+ } elseif ( isset( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] ) ) {
764
+ foreach( $attr_spec_rule[AMP_Rule_Spec::ALTERNATIVE_NAMES] as $alternative_name ) {
765
+ if ( $node->hasAttribute( $alternative_name ) ) {
766
+ $attr_value = $node->getAttribute( $alternative_name );
767
+ if ( preg_match( $pattern, $attr_value ) ) {
768
+ return AMP_Rule_Spec::FAIL;
769
+ } else {
770
+ return AMP_Rule_Spec::PASS;
771
+ }
772
+ }
773
+ }
774
+ }
775
+ }
776
+ return AMP_Rule_Spec::NOT_APPLICABLE;
777
+ }
778
+
779
+ /**
780
+ * Return true if the attribute name is valid for this attr_spec, false otherwise.
781
+ */
782
+ private function is_amp_allowed_attribute( $attr_name, $attr_spec_list ) {
783
+ if ( isset( $this->globally_allowed_attributes[ $attr_name ] ) ||
784
+ isset( $this->layout_allowed_attributes[ $attr_name ] ) ||
785
+ isset( $attr_spec_list[ $attr_name ] ) ) {
786
+ return true;
787
+ } else {
788
+ foreach ( AMP_Rule_Spec::$whitelisted_attr_regex as $whitelisted_attr_regex ) {
789
+ if ( preg_match( $whitelisted_attr_regex, $attr_name ) ) {
790
+ return true;
791
+ }
792
+ }
793
+ }
794
+ return false;
795
+ }
796
+
797
+ /**
798
+ * Return true if the specified node's name is an AMP allowed tag, false otherwise.
799
+ */
800
+ private function is_amp_allowed_tag( $node ) {
801
+ // Return true if node is on the allowed tags list or if it is a text
802
+ // or comment node.
803
+ return ( ( XML_TEXT_NODE == $node->nodeType ) ||
804
+ isset( $this->allowed_tags[ $node->nodeName ] ) ||
805
+ ( XML_COMMENT_NODE == $node->nodeType ) ||
806
+ ( XML_CDATA_SECTION_NODE == $node->nodeType ) );
807
+ }
808
+
809
+ /**
810
+ * Return true if the given node has a direct parent with the given name,
811
+ * otherwise return false.
812
+ */
813
+ private function has_parent( $node, $parent_tag_name ) {
814
+ if ( $node && $node->parentNode && ( $node->parentNode->nodeName == $parent_tag_name ) ) {
815
+ return true;
816
+ }
817
+ return false;
818
+ }
819
+
820
+ /**
821
+ * Return true if the given node has any ancestor with the give name,
822
+ * otherwise return false.
823
+ */
824
+ private function has_ancestor( $node, $ancestor_tag_name ) {
825
+ if ( $this->get_ancestor_with_tag_name( $node, $ancestor_tag_name ) ) {
826
+ return true;
827
+ }
828
+ return false;
829
+ }
830
+
831
+ /**
832
+ * Returns the first ancestor with the given tag name. If no ancestor
833
+ * with that name is found, returns null.
834
+ */
835
+ private function get_ancestor_with_tag_name( $node, $ancestor_tag_name ) {
836
+ while ( $node && $node = $node->parentNode ) {
837
+ if ( $node->nodeName == $ancestor_tag_name ) {
838
+ return $node;
839
+ }
840
+ }
841
+ return null;
842
+ }
843
+
844
+ /**
845
+ * Replaces the given node with it's child nodes, if any, and adds them to
846
+ * the stack for processing by the sanitize() function.
847
+ */
848
+ private function replace_node_with_children( $node ) {
849
+ // If node has children, replace it with them and push children onto stack
850
+ if ( $node->hasChildNodes() && $node->parentNode ) {
851
+
852
+ // create a DOM fragment to hold the children
853
+ $fragment = $this->dom->createDocumentFragment();
854
+
855
+ // Add all children to fragment/stack
856
+ $child = $node->firstChild;
857
+ while( $child ) {
858
+ $fragment->appendChild( $child );
859
+ $this->stack[] = $child;
860
+ $child = $node->firstChild;
861
+ }
862
+
863
+ // replace node with fragment
864
+ $node->parentNode->replaceChild( $fragment, $node );
865
+
866
+ // If node has no children, just remove the node.
867
+ } else {
868
+ $this->remove_node( $node );
869
+ }
870
+ }
871
+
872
+ /**
873
+ * Remove a node. If removing the node makes the parent node empty, then
874
+ * remove the parent as well. Continue until a non-empty parent is reached
875
+ * or the 'body' element is reached.
876
+ */
877
+ private function remove_node( $node ) {
878
+ if ( $node && $parent = $node->parentNode ) {
879
+ $parent->removeChild( $node );
880
+ }
881
+ while( $parent && ( ! $parent->hasChildNodes() ) && ( 'body' != $parent->nodeName ) ) {
882
+ $node = $parent;
883
+ $parent = $parent->parentNode;
884
+ if ( $parent ) {
885
+ $parent->removeChild( $node );
886
+ }
887
+ }
888
+ }
889
+ }
890
+
891
+ /**
892
+ * This is a set of constants that are used throughout the sanitizer.
893
+ * The rule name strings are listed here because it's easier to have the php
894
+ * interpreter catch a typo than for me to catch mistyping a string.
895
+ */
896
+ abstract class AMP_Rule_Spec {
897
+
898
+ // AMP rule_spec types
899
+ const ATTR_SPEC_LIST = 'attr_spec_list';
900
+ const TAG_SPEC = 'tag_spec';
901
+
902
+ // AMP attr_spec value check results
903
+ const PASS = 'pass';
904
+ const FAIL = 'fail';
905
+ const NOT_APPLICABLE = 'not_applicable';
906
+
907
+ // tag rule names
908
+ const DISALLOWED_ANCESTOR = 'disallowed_ancestor';
909
+ const MANDATORY_ANCESTOR = 'mandatory_ancestor';
910
+ const MANDATORY_PARENT = 'mandatory_parent';
911
+
912
+ // attr rule names
913
+ const ALLOW_EMPTY = 'allow_empty';
914
+ const ALLOW_RELATIVE = 'allow_relative';
915
+ const ALLOWED_PROTOCOL = 'allowed_protocol';
916
+ const ALTERNATIVE_NAMES = 'alternative_names';
917
+ const BLACKLISTED_VALUE_REGEX = 'blacklisted_value_regex';
918
+ const DISALLOWED_DOMAIN = 'disallowed_domain';
919
+ const MANDATORY = 'mandatory';
920
+ const VALUE = 'value';
921
+ const VALUE_CASEI = 'value_casei';
922
+ const VALUE_REGEX = 'value_regex';
923
+ const VALUE_REGEX_CASEI = 'value_regex_casei';
924
+
925
+ // If a node type listed here is invalid, it and it's subtree will be
926
+ // removed if it is invalid. This is mainly because any children will be
927
+ // non-functional without this parent.
928
+ //
929
+ // If a tag is not listed here, it will be replaced by its children if it
930
+ // is invalid.
931
+ //
932
+ // TODO: There are other nodes that should probably be listed here as well.
933
+ static $node_types_to_remove_if_invalid = array(
934
+ 'form',
935
+ 'input',
936
+ 'link',
937
+ 'meta',
938
+ // 'script',
939
+ 'style',
940
+ );
941
+
942
+ // It is mentioned in the documentation in several places that data-* is
943
+ // generally allowed, but there is no specific rule for it in the protoascii
944
+ // file, so I'm including it here.
945
+ static $whitelisted_attr_regex = array(
946
+ '@^data-[a-zA-Z][\\w:.-]*$@uis',
947
+ '(update|item|pagination)', // allowed for live reference points
948
+ );
949
+
950
+ static $additional_allowed_tags = array(
951
+
952
+ // this is an experimental tag with no protoascii
953
+ 'amp-share-tracking' => array(
954
+ 'attr_spec_list' => array(),
955
+ 'tag_spec' => array(),
956
+ ),
957
+
958
+ // this is needed for some tags such as analytics
959
+ 'script' => array(
960
+ 'attr_spec_list' => array(
961
+ 'type' => array(
962
+ 'mandatory' => true,
963
+ 'value_casei' => 'text/javascript',
964
+ ),
965
+ ),
966
+ 'tag_spec' => array(),
967
+ ),
968
+ 'script' => array(
969
+ 'attr_spec_list' => array(
970
+ 'type' => array(
971
+ 'mandatory' => true,
972
+ 'value_casei' => 'application/json',
973
+ ),
974
+ ),
975
+ 'tag_spec' => array(),
976
+ ),
977
+ );
978
+ }
includes/sanitizers/class-amp-video-sanitizer.php CHANGED
@@ -10,6 +10,17 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
10
 
11
  public static $tag = 'video';
12
 
 
 
 
 
 
 
 
 
 
 
 
13
  public function sanitize() {
14
  $nodes = $this->dom->getElementsByTagName( self::$tag );
15
  $num_nodes = $nodes->length;
@@ -50,6 +61,8 @@ class AMP_Video_Sanitizer extends AMP_Base_Sanitizer {
50
  } else {
51
  $node->parentNode->replaceChild( $new_node, $node );
52
  }
 
 
53
  }
54
  }
55
 
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;
61
  } else {
62
  $node->parentNode->replaceChild( $new_node, $node );
63
  }
64
+
65
+ $this->did_convert_elements = true;
66
  }
67
  }
68
 
includes/settings/class-amp-customizer-design-settings.php CHANGED
@@ -23,7 +23,7 @@ class AMP_Customizer_Design_Settings {
23
  'type' => 'option',
24
  'default' => self::DEFAULT_HEADER_COLOR,
25
  'sanitize_callback' => 'sanitize_hex_color',
26
- 'transport' => 'postMessage'
27
  ) );
28
 
29
  // Header background color
@@ -31,7 +31,7 @@ class AMP_Customizer_Design_Settings {
31
  'type' => 'option',
32
  'default' => self::DEFAULT_HEADER_BACKGROUND_COLOR,
33
  'sanitize_callback' => 'sanitize_hex_color',
34
- 'transport' => 'postMessage'
35
  ) );
36
 
37
  // Background color scheme
@@ -39,11 +39,11 @@ class AMP_Customizer_Design_Settings {
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 function register_customizer_ui( $wp_customize ) {
47
  $wp_customize->add_section( 'amp_design', array(
48
  'title' => __( 'Design', 'amp' ),
49
  'panel' => AMP_Template_Customizer::PANEL_ID,
@@ -55,7 +55,7 @@ class AMP_Customizer_Design_Settings {
55
  'settings' => 'amp_customizer[header_color]',
56
  'label' => __( 'Header Text Color', 'amp' ),
57
  'section' => 'amp_design',
58
- 'priority' => 10
59
  ) )
60
  );
61
 
@@ -65,7 +65,7 @@ class AMP_Customizer_Design_Settings {
65
  'settings' => 'amp_customizer[header_background_color]',
66
  'label' => __( 'Header Background & Link Color', 'amp' ),
67
  'section' => 'amp_design',
68
- 'priority' => 20
69
  ) )
70
  );
71
 
@@ -109,7 +109,7 @@ class AMP_Customizer_Design_Settings {
109
 
110
  protected static function get_color_scheme_names() {
111
  return array(
112
- 'light' => __( 'Light', 'amp'),
113
  'dark' => __( 'Dark', 'amp' ),
114
  );
115
  }
@@ -129,7 +129,7 @@ class AMP_Customizer_Design_Settings {
129
  'text_color' => '#dedede',
130
  'muted_text_color' => '#b1b1b1',
131
  'border_color' => '#707070',
132
- )
133
  );
134
  }
135
 
@@ -147,7 +147,7 @@ class AMP_Customizer_Design_Settings {
147
  $schemes = self::get_color_scheme_names();
148
  $scheme_slugs = array_keys( $schemes );
149
 
150
- if ( ! in_array( $value, $scheme_slugs ) ) {
151
  $value = self::DEFAULT_COLOR_SCHEME;
152
  }
153
 
23
  'type' => 'option',
24
  'default' => self::DEFAULT_HEADER_COLOR,
25
  'sanitize_callback' => 'sanitize_hex_color',
26
+ 'transport' => 'postMessage',
27
  ) );
28
 
29
  // Header background color
31
  'type' => 'option',
32
  'default' => self::DEFAULT_HEADER_BACKGROUND_COLOR,
33
  'sanitize_callback' => 'sanitize_hex_color',
34
+ 'transport' => 'postMessage',
35
  ) );
36
 
37
  // Background color scheme
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' ),
49
  'panel' => AMP_Template_Customizer::PANEL_ID,
55
  'settings' => 'amp_customizer[header_color]',
56
  'label' => __( 'Header Text Color', 'amp' ),
57
  'section' => 'amp_design',
58
+ 'priority' => 10,
59
  ) )
60
  );
61
 
65
  'settings' => 'amp_customizer[header_background_color]',
66
  'label' => __( 'Header Background & Link Color', 'amp' ),
67
  'section' => 'amp_design',
68
+ 'priority' => 20,
69
  ) )
70
  );
71
 
109
 
110
  protected static function get_color_scheme_names() {
111
  return array(
112
+ 'light' => __( 'Light', 'amp' ),
113
  'dark' => __( 'Dark', 'amp' ),
114
  );
115
  }
129
  'text_color' => '#dedede',
130
  'muted_text_color' => '#b1b1b1',
131
  'border_color' => '#707070',
132
+ ),
133
  );
134
  }
135
 
147
  $schemes = self::get_color_scheme_names();
148
  $scheme_slugs = array_keys( $schemes );
149
 
150
+ if ( ! in_array( $value, $scheme_slugs, true ) ) {
151
  $value = self::DEFAULT_COLOR_SCHEME;
152
  }
153
 
includes/utils/class-amp-dom-utils.php CHANGED
@@ -95,10 +95,27 @@ class AMP_DOM_Utils {
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', 'base', 'basefont', 'bgsound', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  );
100
  }
101
 
102
- return in_array( $tag, $self_closing_tags );
103
  }
104
  }
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',
100
+ 'basefont',
101
+ 'bgsound',
102
+ 'br',
103
+ 'col',
104
+ 'embed',
105
+ 'frame',
106
+ 'hr',
107
+ 'img',
108
+ 'input',
109
+ 'keygen',
110
+ 'link',
111
+ 'meta',
112
+ 'param',
113
+ 'source',
114
+ 'track',
115
+ 'wbr',
116
  );
117
  }
118
 
119
+ return in_array( $tag, $self_closing_tags, true );
120
  }
121
  }
includes/utils/class-amp-html-utils.php CHANGED
@@ -17,4 +17,16 @@ class AMP_HTML_Utils {
17
  }
18
  return implode( ' ', $string );
19
  }
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
17
  }
18
  return implode( ' ', $string );
19
  }
20
+
21
+ public static function is_valid_json( $data ) {
22
+ if ( ! empty( $data ) ) {
23
+ $decoded = json_decode( $data );
24
+ if ( function_exists( 'json_last_error' ) ) {
25
+ return ( json_last_error() === JSON_ERROR_NONE );
26
+ } else { // PHP 5.2 back-compatibility
27
+ return null !== $decoded;
28
+ }
29
+ }
30
+ return false;
31
+ }
32
  }
includes/utils/class-amp-image-dimension-extractor.php CHANGED
@@ -2,18 +2,26 @@
2
 
3
  class AMP_Image_Dimension_Extractor {
4
  static $callbacks_registered = false;
 
 
5
 
6
- static public function extract( $url ) {
7
  if ( ! self::$callbacks_registered ) {
8
  self::register_callbacks();
9
  }
10
 
11
- $url = self::normalize_url( $url );
12
- if ( false === $url ) {
13
- return false;
 
 
 
14
  }
15
 
16
- return apply_filters( 'amp_extract_image_dimensions', false, $url );
 
 
 
17
  }
18
 
19
  public static function normalize_url( $url ) {
@@ -29,7 +37,7 @@ class AMP_Image_Dimension_Extractor {
29
  return set_url_scheme( $url, 'http' );
30
  }
31
 
32
- $parsed = parse_url( $url );
33
  if ( ! isset( $parsed['host'] ) ) {
34
  $path = '';
35
  if ( isset( $parsed['path'] ) ) {
@@ -47,73 +55,185 @@ class AMP_Image_Dimension_Extractor {
47
  private static function register_callbacks() {
48
  self::$callbacks_registered = true;
49
 
50
- add_filter( 'amp_extract_image_dimensions', array( __CLASS__, 'extract_from_attachment_metadata' ), 10, 2 );
51
- add_filter( 'amp_extract_image_dimensions', array( __CLASS__, 'extract_by_downloading_image' ), 999, 2 ); // Run really late since this is our last resort
52
 
53
- do_action( 'amp_extract_image_dimensions_callbacks_registered' );
54
  }
55
 
56
- public static function extract_from_attachment_metadata( $dimensions, $url ) {
57
- if ( is_array( $dimensions ) ) {
58
- return $dimensions;
59
- }
 
 
 
 
 
60
 
61
- $url = strtok( $url, '?' );
62
- $attachment_id = attachment_url_to_postid( $url );
63
- if ( empty( $attachment_id ) ) {
64
- return false;
65
- }
66
 
67
- $metadata = wp_get_attachment_metadata( $attachment_id );
68
- if ( ! $metadata ) {
69
- return false;
70
- }
71
 
72
- return array( $metadata['width'], $metadata['height'] );
73
  }
74
 
75
- public static function extract_by_downloading_image( $dimensions, $url ) {
76
- if ( is_array( $dimensions ) ) {
77
- return $dimensions;
78
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- $url_hash = md5( $url );
81
- $transient_name = sprintf( 'amp_img_%s', $url_hash );
82
- $transient_expiry = 30 * DAY_IN_SECONDS;
83
- $transient_fail = 'fail';
 
84
 
85
- $dimensions = get_transient( $transient_name );
86
 
87
- if ( is_array( $dimensions ) ) {
88
- return $dimensions;
89
- } elseif ( $transient_fail === $dimensions ) {
90
- return false;
 
 
 
 
 
 
 
 
91
  }
 
92
 
93
- // Very simple lock to prevent stampedes
94
- $transient_lock_name = sprintf( 'amp_lock_%s', $url_hash );
95
- if ( false !== get_transient( $transient_lock_name ) ) {
96
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
- set_transient( $transient_lock_name, 1, MINUTE_IN_SECONDS );
99
 
100
- // Note to other developers: please don't use this class directly as it may not stick around forever...
 
 
 
 
 
 
101
  if ( ! class_exists( 'FastImage' ) ) {
102
- require_once( AMP__DIR__ . '/includes/lib/class-fastimage.php' );
103
  }
104
 
105
- // TODO: look into using curl+stream (https://github.com/willwashburn/FasterImage)
106
- $image = new FastImage( $url );
107
- $dimensions = $image->getSize();
108
 
109
- if ( ! is_array( $dimensions ) ) {
110
- set_transient( $transient_name, $transient_fail, $transient_expiry );
111
- delete_transient( $transient_lock_name );
112
- return false;
 
 
 
 
113
  }
 
114
 
115
- set_transient( $transient_name, $dimensions, $transient_expiry );
116
- delete_transient( $transient_lock_name );
117
- return $dimensions;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
  }
2
 
3
  class AMP_Image_Dimension_Extractor {
4
  static $callbacks_registered = false;
5
+ const STATUS_FAILED_LAST_ATTEMPT = 'failed';
6
+ const STATUS_IMAGE_EXTRACTION_FAILED = 'failed';
7
 
8
+ static public function extract( $urls ) {
9
  if ( ! self::$callbacks_registered ) {
10
  self::register_callbacks();
11
  }
12
 
13
+ $valid_urls = array();
14
+ foreach ( $urls as $url ) {
15
+ $url = self::normalize_url( $url );
16
+ if ( false !== $url ) {
17
+ $valid_urls[] = $url;
18
+ }
19
  }
20
 
21
+ $dimensions = array_fill_keys( $valid_urls, false );
22
+ $dimensions = apply_filters( 'amp_extract_image_dimensions_batch', $dimensions );
23
+
24
+ return $dimensions;
25
  }
26
 
27
  public static function normalize_url( $url ) {
37
  return set_url_scheme( $url, 'http' );
38
  }
39
 
40
+ $parsed = AMP_WP_Utils::parse_url( $url );
41
  if ( ! isset( $parsed['host'] ) ) {
42
  $path = '';
43
  if ( isset( $parsed['path'] ) ) {
55
  private static function register_callbacks() {
56
  self::$callbacks_registered = true;
57
 
58
+ add_filter( 'amp_extract_image_dimensions_batch', array( __CLASS__, 'extract_by_downloading_images' ), 999, 1 );
 
59
 
60
+ do_action( 'amp_extract_image_dimensions_batch_callbacks_registered' );
61
  }
62
 
63
+ /**
64
+ * Extract dimensions from downloaded images (or transient/cached dimensions from downloaded images)
65
+ *
66
+ * @param array $dimensions Image urls mapped to dimensions.
67
+ * @param string $mode Whether image dimensions should be extracted concurrently or synchronously.
68
+ * @return array Dimensions mapped to image urls, or false if they could not be retrieved
69
+ */
70
+ public static function extract_by_downloading_images( $dimensions, $mode = 'concurrent' ) {
71
+ $transient_expiration = 30 * DAY_IN_SECONDS;
72
 
73
+ $urls_to_fetch = array();
74
+ $images = array();
 
 
 
75
 
76
+ self::determine_which_images_to_fetch( $dimensions, $urls_to_fetch );
77
+ self::fetch_images( $urls_to_fetch, $images, $mode );
78
+ self::process_fetched_images( $urls_to_fetch, $images, $dimensions, $transient_expiration );
 
79
 
80
+ return $dimensions;
81
  }
82
 
83
+ /**
84
+ * Determine which images to fetch by checking for dimensions in transient/cache.
85
+ * Creates a short lived transient that acts as a semaphore so that another visitor
86
+ * doesn't trigger a remote fetch for the same image at the same time.
87
+ *
88
+ * @param array $dimensions Image urls mapped to dimensions.
89
+ * @param array $urls_to_fetch Urls of images to fetch because dimensions are not in transient/cache.
90
+ */
91
+ private static function determine_which_images_to_fetch( &$dimensions, &$urls_to_fetch ) {
92
+ foreach ( $dimensions as $url => $value ) {
93
+
94
+ // Check whether some other callback attached to the filter already provided dimensions for this image.
95
+ if ( is_array( $value ) ) {
96
+ continue;
97
+ }
98
+
99
+ $url_hash = md5( $url );
100
+ $transient_name = sprintf( 'amp_img_%s', $url_hash );
101
+ $cached_dimensions = get_transient( $transient_name );
102
+
103
+ // If we're able to retrieve the dimensions from a transient, set them and move on.
104
+ if ( is_array( $cached_dimensions ) ) {
105
+ $dimensions[ $url ] = array(
106
+ 'width' => $cached_dimensions[0],
107
+ 'height' => $cached_dimensions[1],
108
+ );
109
+ continue;
110
+ }
111
 
112
+ // If the value in the transient reflects we couldn't get dimensions for this image the last time we tried, move on.
113
+ if ( self::STATUS_FAILED_LAST_ATTEMPT === $cached_dimensions ) {
114
+ $dimensions[ $url ] = false;
115
+ continue;
116
+ }
117
 
118
+ $transient_lock_name = sprintf( 'amp_lock_%s', $url_hash );
119
 
120
+ // If somebody is already trying to extract dimensions for this transient right now, move on.
121
+ if ( false !== get_transient( $transient_lock_name ) ) {
122
+ $dimensions[ $url ] = false;
123
+ continue;
124
+ }
125
+
126
+ // Include the image as a url to fetch.
127
+ $urls_to_fetch[ $url ] = array();
128
+ $urls_to_fetch[ $url ]['url'] = $url;
129
+ $urls_to_fetch[ $url ]['transient_name'] = $transient_name;
130
+ $urls_to_fetch[ $url ]['transient_lock_name'] = $transient_lock_name;
131
+ set_transient( $transient_lock_name, 1, MINUTE_IN_SECONDS );
132
  }
133
+ }
134
 
135
+ /**
136
+ * Fetch dimensions of remote images
137
+ *
138
+ * @param array $urls_to_fetch Image src urls to fetch.
139
+ * @param array $images Array to populate with results of image/dimension inspection.
140
+ * @param string $mode Whether image dimensions should be extracted concurrently or synchronously.
141
+ */
142
+ private static function fetch_images( $urls_to_fetch, &$images, $mode ) {
143
+ // Use FasterImage when for compatible PHP versions
144
+ if ( 'synchronous' === $mode ||
145
+ false === function_exists( 'curl_multi_exec' ) ||
146
+ version_compare( PHP_VERSION, '5.4.0' ) < 0
147
+ ) {
148
+ self::fetch_images_via_fast_image( $urls_to_fetch, $images );
149
+ } else {
150
+ self::fetch_images_via_faster_image( $urls_to_fetch, $images );
151
  }
152
+ }
153
 
154
+ /**
155
+ * Fetch images via FastImage library
156
+ *
157
+ * @param array $urls_to_fetch Image src urls to fetch.
158
+ * @param array $images Array to populate with results of image/dimension inspection.
159
+ */
160
+ private static function fetch_images_via_fast_image( $urls_to_fetch, &$images ) {
161
  if ( ! class_exists( 'FastImage' ) ) {
162
+ require_once( AMP__DIR__ . '/includes/lib/fastimage/class-fastimage.php' );
163
  }
164
 
165
+ $image = new FastImage();
166
+ $urls = array_keys( $urls_to_fetch );
 
167
 
168
+ foreach ( $urls as $url ) {
169
+ $result = $image->load( $url );
170
+ if ( false === $result ) {
171
+ $images[ $url ]['size'] = self::STATUS_IMAGE_EXTRACTION_FAILED;
172
+ } else {
173
+ $size = $image->getSize();
174
+ $images[ $url ]['size'] = $size;
175
+ }
176
  }
177
+ }
178
 
179
+ /**
180
+ * Fetch images via FasterImage library
181
+ *
182
+ * @param array $urls_to_fetch Image src urls to fetch.
183
+ * @param array $images Array to populate with results of image/dimension inspection.
184
+ */
185
+ private static function fetch_images_via_faster_image( $urls_to_fetch, &$images ) {
186
+ $urls = array_keys( $urls_to_fetch );
187
+
188
+ if ( ! function_exists( 'amp_get_fasterimage_client' ) ) {
189
+ require_once( AMP__DIR__ . '/includes/lib/fasterimage/amp-fasterimage.php' );
190
+ }
191
+
192
+ $user_agent = apply_filters( 'amp_extract_image_dimensions_get_user_agent', self::get_default_user_agent() );
193
+ $client = amp_get_fasterimage_client( $user_agent );
194
+ $images = $client->batch( $urls );
195
+ }
196
+
197
+ /**
198
+ * Determine success or failure of remote fetch, integrate fetched dimensions into url to dimension mapping,
199
+ * cache fetched dimensions via transient and release/delete semaphore transient
200
+ *
201
+ * @param array $urls_to_fetch List of image urls that were fetched and transient names corresponding to each (for unlocking semaphore, setting "real" transient).
202
+ * @param array $images Results of remote fetch mapping fetched image url to dimensions.
203
+ * @param array $dimensions Map of image url to dimensions to be updated with results of remote fetch.
204
+ * @param int $transient_expiration Duration image dimensions should exist in transient/cache.
205
+ */
206
+ private static function process_fetched_images( $urls_to_fetch, $images, &$dimensions, $transient_expiration ) {
207
+ foreach ( $urls_to_fetch as $url_data ) {
208
+ $image_data = $images[ $url_data['url'] ];
209
+ if ( self::STATUS_IMAGE_EXTRACTION_FAILED === $image_data['size'] ) {
210
+ $dimensions[ $url_data['url'] ] = false;
211
+ set_transient( $url_data['transient_name'], self::STATUS_FAILED_LAST_ATTEMPT, $transient_expiration );
212
+ } else {
213
+ $dimensions[ $url_data['url'] ] = array(
214
+ 'width' => $image_data['size'][0],
215
+ 'height' => $image_data['size'][1],
216
+ );
217
+ set_transient(
218
+ $url_data['transient_name'],
219
+ array(
220
+ $image_data['size'][0],
221
+ $image_data['size'][1],
222
+ ),
223
+ $transient_expiration
224
+ );
225
+ }
226
+ delete_transient( $url_data['transient_lock_name'] );
227
+ }
228
+ }
229
+
230
+
231
+ /**
232
+ * Get default user agent
233
+ *
234
+ * @return string
235
+ */
236
+ public static function get_default_user_agent() {
237
+ return 'amp-wp, v' . AMP__VERSION . ', ' . get_site_url();
238
  }
239
  }
includes/utils/class-amp-string-utils.php CHANGED
@@ -4,6 +4,6 @@ class AMP_String_Utils {
4
  public static function endswith( $haystack, $needle ) {
5
  return '' !== $haystack
6
  && '' !== $needle
7
- && $needle === substr( $haystack, -strlen( $needle ) );
8
  }
9
  }
4
  public static function endswith( $haystack, $needle ) {
5
  return '' !== $haystack
6
  && '' !== $needle
7
+ && substr( $haystack, -strlen( $needle ) ) === $needle;
8
  }
9
  }
includes/utils/class-amp-wp-utils.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class AMP_WP_Utils {
4
+ /**
5
+ * wp_parse_url in < WordPress 4.7 does not respect the component arg, so we're adding this helper so we can use it.
6
+ *
7
+ * This can be removed once 4.8 is out and we bump up our min supported WP version.
8
+ */
9
+ public static function parse_url( $url, $component = -1 ) {
10
+ $parsed = wp_parse_url( $url, $component );
11
+
12
+ // Because < 4.7 always returned a full array regardless of component
13
+ if ( -1 !== $component && is_array( $parsed ) ) {
14
+ return self::_get_component_from_parsed_url_array( $parsed, $component );
15
+ }
16
+
17
+ return $parsed;
18
+ }
19
+
20
+ /**
21
+ * Included for 4.6 back-compat
22
+ *
23
+ * Copied from https://developer.wordpress.org/reference/functions/_get_component_from_parsed_url_array/
24
+ */
25
+ protected static function _get_component_from_parsed_url_array( $url_parts, $component = -1 ) {
26
+ if ( -1 === $component ) {
27
+ return $url_parts;
28
+ }
29
+
30
+ $key = self::_wp_translate_php_url_constant_to_key( $component );
31
+ if ( false !== $key && is_array( $url_parts ) && isset( $url_parts[ $key ] ) ) {
32
+ return $url_parts[ $key ];
33
+ }
34
+
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Included for 4.6 back-compat
40
+ *
41
+ * Copied from https://developer.wordpress.org/reference/functions/_wp_translate_php_url_constant_to_key/
42
+ */
43
+ protected static function _wp_translate_php_url_constant_to_key( $constant ) {
44
+ $translation = array(
45
+ PHP_URL_SCHEME => 'scheme',
46
+ PHP_URL_HOST => 'host',
47
+ PHP_URL_PORT => 'port',
48
+ PHP_URL_USER => 'user',
49
+ PHP_URL_PASS => 'pass',
50
+ PHP_URL_PATH => 'path',
51
+ PHP_URL_QUERY => 'query',
52
+ PHP_URL_FRAGMENT => 'fragment',
53
+ );
54
+
55
+ if ( isset( $translation[ $constant ] ) ) {
56
+ return $translation[ $constant ];
57
+ }
58
+
59
+ return false;
60
+ }
61
+ }
jetpack-helper.php CHANGED
@@ -56,14 +56,14 @@ function jetpack_amp_build_stats_pixel_url() {
56
  $blog = Jetpack_Options::get_option( 'id' );
57
  $tz = get_option( 'gmt_offset' );
58
  $v = 'ext';
59
- $blog_url = parse_url( site_url() );
60
  $srv = $blog_url['host'];
61
  $j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
62
  $post = $wp_the_query->get_queried_object_id();
63
  $data = compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
64
  }
65
 
66
- $data['host'] = rawurlencode( $_SERVER['HTTP_HOST'] );
67
  $data['rand'] = 'RANDOM'; // amp placeholder
68
  $data['ref'] = 'DOCUMENT_REFERRER'; // amp placeholder
69
  $data = array_map( 'rawurlencode' , $data );
56
  $blog = Jetpack_Options::get_option( 'id' );
57
  $tz = get_option( 'gmt_offset' );
58
  $v = 'ext';
59
+ $blog_url = AMP_WP_Utils::parse_url( site_url() );
60
  $srv = $blog_url['host'];
61
  $j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION );
62
  $post = $wp_the_query->get_queried_object_id();
63
  $data = compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' );
64
  }
65
 
66
+ $data['host'] = isset( $_SERVER['HTTP_HOST'] ) ? rawurlencode( $_SERVER['HTTP_HOST'] ) : ''; // input var ok
67
  $data['rand'] = 'RANDOM'; // amp placeholder
68
  $data['ref'] = 'DOCUMENT_REFERRER'; // amp placeholder
69
  $data = array_map( 'rawurlencode' , $data );
readme-assets/amp-options-analytics.png ADDED
Binary file
readme-assets/analytics-option-entries.png ADDED
Binary file
readme-assets/invalid-input.png ADDED
Binary file
readme-assets/options-saved.png ADDED
Binary file
readme.md CHANGED
@@ -4,7 +4,7 @@
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/2016/01/01/amp-on/?amp=1`
8
 
9
  Note #1: that Pages and archives are not currently supported.
10
 
@@ -53,19 +53,19 @@ function xyz_amp_set_site_icon_url( $data ) {
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 colours the title bar black, hides the site title, and replaces it with a centered logo:
57
 
58
- ```
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
- nav.amp-wp-title-bar {
65
  padding: 12px 0;
66
  background: #000;
67
  }
68
- nav.amp-wp-title-bar a {
69
  background-image: url( 'https://example.com/path/to/logo.png' );
70
  background-repeat: no-repeat;
71
  background-size: contain;
@@ -79,7 +79,7 @@ function xyz_amp_additional_css_styles( $amp_template ) {
79
  }
80
  ```
81
 
82
- Note: you will need to adjust the colours and sizes based on your brand.
83
 
84
  ### Template Tweaks
85
 
@@ -87,21 +87,14 @@ You can tweak various parts of the template via code.
87
 
88
  #### Featured Image
89
 
90
- The default template does not display the featured image currently. There are many ways to add it, such as the snippet below:
91
 
92
  ```php
93
- add_action( 'pre_amp_render_post', 'xyz_amp_add_custom_actions' );
94
- function xyz_amp_add_custom_actions() {
95
- add_filter( 'the_content', 'xyz_amp_add_featured_image' );
96
- }
97
 
98
- function xyz_amp_add_featured_image( $content ) {
99
- if ( has_post_thumbnail() ) {
100
- // Just add the raw <img /> tag; our sanitizer will take care of it later.
101
- $image = sprintf( '<p class="xyz-featured-image">%s</p>', get_the_post_thumbnail() );
102
- $content = $image . $content;
103
- }
104
- return $content;
105
  }
106
  ```
107
 
@@ -136,7 +129,7 @@ Note: The path must pass the default criteria set out by [`validate_file`](https
136
 
137
  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.
138
 
139
- ```
140
  add_filter( 'amp_post_template_metadata', 'xyz_amp_modify_json_metadata', 10, 2 );
141
 
142
  function xyz_amp_modify_json_metadata( $metadata, $post ) {
@@ -314,13 +307,13 @@ Note: there are some requirements for a custom template:
314
 
315
  * You must trigger the `amp_post_template_head` action in the `<head>` section:
316
 
317
- ```
318
  do_action( 'amp_post_template_head', $this );
319
  ```
320
 
321
  * You must trigger the `amp_post_template_footer` action right before the `</body>` tag:
322
 
323
- ```
324
  do_action( 'amp_post_template_footer', $this );
325
  ```
326
 
@@ -395,7 +388,10 @@ class XYZ_AMP_Related_Posts_Embed extends AMP_Base_Embed_Handler {
395
  }
396
 
397
  public function get_scripts() {
398
- return array( 'amp-mustache' => 'https://cdn.ampproject.org/v0/amp-mustache-0.1.js' );
 
 
 
399
  }
400
 
401
  public function add_related_posts( $content ) {
@@ -490,11 +486,108 @@ function xyz_amp_add_ad_sanitizer( $sanitizer_classes, $post ) {
490
  }
491
  ```
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  ## Analytics
494
 
495
- To output proper analytics tags, you can use the `amp_post_template_analytics` filter:
496
 
497
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  add_filter( 'amp_post_template_analytics', 'xyz_amp_add_custom_analytics' );
499
  function xyz_amp_add_custom_analytics( $analytics ) {
500
  if ( ! is_array( $analytics ) ) {
@@ -556,7 +649,7 @@ You'll need to flush your rewrite rules after this.
556
 
557
  If you want a custom template for your post type:
558
 
559
- ```
560
  add_filter( 'amp_post_template_file', 'xyz_amp_set_review_template', 10, 3 );
561
 
562
  function xyz_amp_set_review_template( $file, $type, $post ) {
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
 
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;
79
  }
80
  ```
81
 
82
+ Note: you will need to adjust the colors and sizes based on your brand.
83
 
84
  ### Template Tweaks
85
 
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
 
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 ) {
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
 
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 ) {
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 ) ) {
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 ) {
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === AMP ===
2
  Contributors: batmoo, joen, automattic, potatomaster
3
  Tags: amp, mobile
4
- Requires at least: 4.4
5
  Tested up to: 4.8
6
- Stable tag: 0.4.2
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -13,7 +13,7 @@ Enable Accelerated Mobile Pages (AMP) on your WordPress site.
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/2016/01/01/amp-on/?amp=1`
17
 
18
  Note #1: that Pages and archives are not currently supported. Pages support is being worked on.
19
 
@@ -31,7 +31,7 @@ Follow along with or contribute to the development of this plugin at https://git
31
 
32
  = How do I customize the AMP output for my site? =
33
 
34
- You can tweak a few things like colours 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
 
@@ -53,6 +53,22 @@ A wise green Yoda once said, "Patience you must have, my young padawan." We're w
53
 
54
  == Changelog ==
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  = 0.4.2 (2016-10-13) =
57
 
58
  - Fix: Prevent validation errors for `html` tag (h/t Maxime2 and everyone else that reported this error)
@@ -72,7 +88,7 @@ A wise green Yoda once said, "Patience you must have, my young padawan." We're w
72
 
73
  - New template: spiffy, shiny, and has the fresh theme smell (props allancole and the Automattic Theme Team).
74
  - *Warning*: The template update has potential breaking changes. Please see https://wordpress.org/support/topic/v0-4-whats-new-and-possible-breaking-changes/
75
- - AMP Customizer: Pick your colours and make the template your own (props DrewAPicture and 10up)
76
  - Fix: support for inline styles (props coreymckrill).
77
  - Fix: no more fatal errors when tags not supported by post type (props david-binda)
78
  - Fix: no more unnecessary `<br>` tags.
1
  === AMP ===
2
  Contributors: batmoo, joen, automattic, potatomaster
3
  Tags: amp, mobile
4
+ Requires at least: 4.7
5
  Tested up to: 4.8
6
+ Stable tag: 0.5
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
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
 
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
 
53
 
54
  == Changelog ==
55
 
56
+ = 0.5 (2017-08-04) =
57
+
58
+ - Whitelist Sanitizer: Replace Blacklist Sanitizer with a whitelist-based approach using the AMP spec (props delputnam)
59
+ - Image Dimensions: Replace fastimage with fasterimage for PHP 5.4+. Enables faster downloads and wider support (props gititon)
60
+ - Embed Handlers: Added support for Vimeo, SoundCloud, Pinterest (props amedina) and PlayBuzz (props lysk88)
61
+ - Analytics: UI for easier addition of analytics tags (props amedina)
62
+ - Fix: parse query strings properly (props amyevans)
63
+ - Fix: Old slug redirect for AMP URLs (props rahulsprajapati)
64
+ - Fix: Handle issues with data uri images in CSS (props trepmal)
65
+ - Fix: Add amp-video js for amp-video tags (props ptbello)
66
+ - Fix: Output CSS for feature image (props mjangda)
67
+ - Fix: Fix attribute when adding AMP Mustache lib (props luigitec)
68
+ - Fix: Various documentation updates (props piersb, bhhaskin)
69
+ - Fix: PHP Warnings from `register_customizer_ui` (props jahvi)
70
+ - Fix: Coding Standards (props paulschreiber)
71
+
72
  = 0.4.2 (2016-10-13) =
73
 
74
  - Fix: Prevent validation errors for `html` tag (h/t Maxime2 and everyone else that reported this error)
88
 
89
  - New template: spiffy, shiny, and has the fresh theme smell (props allancole and the Automattic Theme Team).
90
  - *Warning*: The template update has potential breaking changes. Please see https://wordpress.org/support/topic/v0-4-whats-new-and-possible-breaking-changes/
91
+ - AMP Customizer: Pick your colors and make the template your own (props DrewAPicture and 10up)
92
  - Fix: support for inline styles (props coreymckrill).
93
  - Fix: no more fatal errors when tags not supported by post type (props david-binda)
94
  - Fix: no more unnecessary `<br>` tags.
templates/footer.php CHANGED
@@ -2,8 +2,8 @@
2
  <div>
3
  <h2><?php echo esc_html( $this->get( 'blog_name' ) ); ?></h2>
4
  <p>
5
- <a href="<?php echo esc_url( __( 'https://wordpress.org/', 'amp' ) ); ?>"><?php printf( __( 'Powered by %s', 'amp' ), 'WordPress' ); ?></a>
6
  </p>
7
- <a href="#top" class="back-to-top"><?php _e( 'Back to top', 'amp' ); ?></a>
8
  </div>
9
  </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>
9
  </footer>
templates/header-bar.php CHANGED
@@ -2,7 +2,7 @@
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' ) ); ?>
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' ) ); ?>
templates/meta-author.php CHANGED
@@ -1,9 +1,8 @@
1
  <?php $post_author = $this->get( 'post_author' ); ?>
2
  <?php if ( $post_author ) : ?>
3
- <?php $author_avatar_url = get_avatar_url( $post_author->user_email, array( 'size' => 24 ) ); ?>
4
  <div class="amp-wp-meta amp-wp-byline">
5
  <?php if ( function_exists( 'get_avatar_url' ) ) : ?>
6
- <amp-img src="<?php echo esc_url( $author_avatar_url ); ?>" width="24" height="24" layout="fixed"></amp-img>
7
  <?php endif; ?>
8
  <span class="amp-wp-author author vcard"><?php echo esc_html( $post_author->display_name ); ?></span>
9
  </div>
1
  <?php $post_author = $this->get( 'post_author' ); ?>
2
  <?php if ( $post_author ) : ?>
 
3
  <div class="amp-wp-meta amp-wp-byline">
4
  <?php if ( function_exists( 'get_avatar_url' ) ) : ?>
5
+ <amp-img src="<?php echo esc_url( get_avatar_url( $post_author->user_email, array( 'size' => 24 ) ) ); ?>" width="24" height="24" layout="fixed"></amp-img>
6
  <?php endif; ?>
7
  <span class="amp-wp-author author vcard"><?php echo esc_html( $post_author->display_name ); ?></span>
8
  </div>
templates/style.php CHANGED
@@ -134,7 +134,7 @@ blockquote p:last-child {
134
  .amp-wp-header .amp-wp-site-icon {
135
  /** site icon is 32px **/
136
  background-color: <?php echo sanitize_hex_color( $header_color ); ?>;
137
- border: 1px solid <?php echo sanitize_hex_color( $header_color ); ?>;
138
  border-radius: 50%;
139
  position: absolute;
140
  right: 18px;
134
  .amp-wp-header .amp-wp-site-icon {
135
  /** site icon is 32px **/
136
  background-color: <?php echo sanitize_hex_color( $header_color ); ?>;
137
+ border: 1px solid <?php echo sanitize_hex_color( $header_color ); ?>;
138
  border-radius: 50%;
139
  position: absolute;
140
  right: 18px;