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
+