PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 4.6.3
Jetpack – WP Security, Backup, Speed, & Growth v4.6.3
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.photon.php
jetpack Last commit date
3rd-party 9 years ago _inc 1 year ago bin 9 years ago css 9 years ago images 1 year ago json-endpoints 9 years ago languages 9 years ago modules 1 year ago sal 9 years ago scss 9 years ago sync 9 years ago views 9 years ago .svnignore 12 years ago changelog.txt 9 years ago class.frame-nonce-preview.php 9 years ago class.jetpack-admin.php 9 years ago class.jetpack-autoupdate.php 9 years ago class.jetpack-bbpress-json-api-compat.php 9 years ago class.jetpack-cli.php 9 years ago class.jetpack-client-server.php 9 years ago class.jetpack-client.php 9 years ago class.jetpack-connection-banner.php 9 years ago class.jetpack-constants.php 9 years ago class.jetpack-data.php 9 years ago class.jetpack-debugger.php 9 years ago class.jetpack-error.php 10 years ago class.jetpack-heartbeat.php 9 years ago class.jetpack-idc.php 9 years ago class.jetpack-ixr-client.php 10 years ago class.jetpack-jitm.php 9 years ago class.jetpack-modules-list-table.php 9 years ago class.jetpack-network-sites-list-table.php 9 years ago class.jetpack-network.php 9 years ago class.jetpack-options.php 9 years ago class.jetpack-post-images.php 9 years ago class.jetpack-signature.php 9 years ago class.jetpack-tracks.php 9 years ago class.jetpack-twitter-cards.php 9 years ago class.jetpack-user-agent.php 9 years ago class.jetpack-xmlrpc-server.php 9 years ago class.jetpack.php 9 years ago class.json-api-endpoints.php 3 years ago class.json-api.php 10 years ago class.photon.php 9 years ago composer.json 10 years ago functions.compat.php 9 years ago functions.gallery.php 10 years ago functions.global.php 9 years ago functions.opengraph.php 9 years ago functions.photon.php 9 years ago jetpack.php 1 year ago json-api-config.php 10 years ago json-endpoints.php 9 years ago locales.php 9 years ago readme.txt 1 year ago require-lib.php 10 years ago rest-api.md 9 years ago uninstall.php 9 years ago webpack.config.js 9 years ago wpml-config.xml 10 years ago
class.photon.php
948 lines
1 <?php
2
3 class Jetpack_Photon {
4 /**
5 * Class variables
6 */
7 // Oh look, a singleton
8 private static $__instance = null;
9
10 // Allowed extensions must match http://code.trac.wordpress.org/browser/photon/index.php#L31
11 protected static $extensions = array(
12 'gif',
13 'jpg',
14 'jpeg',
15 'png'
16 );
17
18 // Don't access this directly. Instead, use self::image_sizes() so it's actually populated with something.
19 protected static $image_sizes = null;
20
21 /**
22 * Singleton implementation
23 *
24 * @return object
25 */
26 public static function instance() {
27 if ( ! is_a( self::$__instance, 'Jetpack_Photon' ) ) {
28 self::$__instance = new Jetpack_Photon;
29 self::$__instance->setup();
30 }
31
32 return self::$__instance;
33 }
34
35 /**
36 * Silence is golden.
37 */
38 private function __construct() {}
39
40 /**
41 * Register actions and filters, but only if basic Photon functions are available.
42 * The basic functions are found in ./functions.photon.php.
43 *
44 * @uses add_action, add_filter
45 * @return null
46 */
47 private function setup() {
48 if ( ! function_exists( 'jetpack_photon_url' ) )
49 return;
50
51 // Images in post content and galleries
52 add_filter( 'the_content', array( __CLASS__, 'filter_the_content' ), 999999 );
53 add_filter( 'get_post_galleries', array( __CLASS__, 'filter_the_galleries' ), 999999 );
54
55 // Core image retrieval
56 add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
57
58 // Responsive image srcset substitution
59 add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 10, 4 );
60 add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still easily filter.
61
62 // Helpers for maniuplated images
63 add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
64 }
65
66 /**
67 ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS
68 **/
69
70 /**
71 * Match all images and any relevant <a> tags in a block of HTML.
72 *
73 * @param string $content Some HTML.
74 * @return array An array of $images matches, where $images[0] is
75 * an array of full matches, and the link_url, img_tag,
76 * and img_url keys are arrays of those matches.
77 */
78 public static function parse_images_from_html( $content ) {
79 $images = array();
80
81 if ( preg_match_all( '#(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag><img[^>]*?\s+?src=["|\'](?P<img_url>[^\s]+?)["|\'].*?>){1}(?:\s*</a>)?#is', $content, $images ) ) {
82 foreach ( $images as $key => $unused ) {
83 // Simplify the output as much as possible, mostly for confirming test results.
84 if ( is_numeric( $key ) && $key > 0 )
85 unset( $images[$key] );
86 }
87
88 return $images;
89 }
90
91 return array();
92 }
93
94 /**
95 * Try to determine height and width from strings WP appends to resized image filenames.
96 *
97 * @param string $src The image URL.
98 * @return array An array consisting of width and height.
99 */
100 public static function parse_dimensions_from_filename( $src ) {
101 $width_height_string = array();
102
103 if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . '){1}$#i', $src, $width_height_string ) ) {
104 $width = (int) $width_height_string[1];
105 $height = (int) $width_height_string[2];
106
107 if ( $width && $height )
108 return array( $width, $height );
109 }
110
111 return array( false, false );
112 }
113
114 /**
115 * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon.
116 *
117 * @param string $content
118 * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url
119 * @filter the_content
120 * @return string
121 */
122 public static function filter_the_content( $content ) {
123 $images = Jetpack_Photon::parse_images_from_html( $content );
124
125 if ( ! empty( $images ) ) {
126 $content_width = Jetpack::get_content_width();
127
128 $image_sizes = self::image_sizes();
129 $upload_dir = wp_get_upload_dir();
130
131 foreach ( $images[0] as $index => $tag ) {
132 // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained
133 $transform = 'resize';
134
135 // Start with a clean attachment ID each time
136 $attachment_id = false;
137
138 // Flag if we need to munge a fullsize URL
139 $fullsize_url = false;
140
141 // Identify image source
142 $src = $src_orig = $images['img_url'][ $index ];
143
144 /**
145 * Allow specific images to be skipped by Photon.
146 *
147 * @module photon
148 *
149 * @since 2.0.3
150 *
151 * @param bool false Should Photon ignore this image. Default to false.
152 * @param string $src Image URL.
153 * @param string $tag Image Tag (Image HTML output).
154 */
155 if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) )
156 continue;
157
158 // Support Automattic's Lazy Load plugin
159 // Can't modify $tag yet as we need unadulterated version later
160 if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
161 $placeholder_src = $placeholder_src_orig = $src;
162 $src = $src_orig = $lazy_load_src[1];
163 } elseif ( preg_match( '#data-lazy-original=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
164 $placeholder_src = $placeholder_src_orig = $src;
165 $src = $src_orig = $lazy_load_src[1];
166 }
167
168 // Check if image URL should be used with Photon
169 if ( self::validate_image_url( $src ) ) {
170 // Find the width and height attributes
171 $width = $height = false;
172
173 // First, check the image tag
174 if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) )
175 $width = $width_string[1];
176
177 if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) )
178 $height = $height_string[1];
179
180 // Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout.
181 if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) )
182 $width = $height = false;
183
184 // Detect WP registered image size from HTML class
185 if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
186 $size = array_pop( $size );
187
188 if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) {
189 $width = (int) $image_sizes[ $size ]['width'];
190 $height = (int) $image_sizes[ $size ]['height'];
191 $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
192 }
193 } else {
194 unset( $size );
195 }
196
197 // WP Attachment ID, if uploaded to this site
198 if (
199 preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) &&
200 (
201 0 === strpos( $src, $upload_dir['baseurl'] ) ||
202 /**
203 * Filter whether an image using an attachment ID in its class has to be uploaded to the local site to go through Photon.
204 *
205 * @module photon
206 *
207 * @since 2.0.3
208 *
209 * @param bool false Was the image uploaded to the local site. Default to false.
210 * @param array $args {
211 * Array of image details.
212 *
213 * @type $src Image URL.
214 * @type tag Image tag (Image HTML output).
215 * @type $images Array of information about the image.
216 * @type $index Image index.
217 * }
218 */
219 apply_filters( 'jetpack_photon_image_is_local', false, compact( 'src', 'tag', 'images', 'index' ) )
220 )
221 ) {
222 $attachment_id = intval( array_pop( $attachment_id ) );
223
224 if ( $attachment_id ) {
225 $attachment = get_post( $attachment_id );
226
227 // Basic check on returned post object
228 if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) {
229 $src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' );
230
231 if ( self::validate_image_url( $src_per_wp[0] ) ) {
232 $src = $src_per_wp[0];
233 $fullsize_url = true;
234
235 // Prevent image distortion if a detected dimension exceeds the image's natural dimensions
236 if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
237 $width = false === $width ? false : min( $width, $src_per_wp[1] );
238 $height = false === $height ? false : min( $height, $src_per_wp[2] );
239 }
240
241 // If no width and height are found, max out at source image's natural dimensions
242 // Otherwise, respect registered image sizes' cropping setting
243 if ( false === $width && false === $height ) {
244 $width = $src_per_wp[1];
245 $height = $src_per_wp[2];
246 $transform = 'fit';
247 } elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
248 $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
249 }
250 }
251 } else {
252 unset( $attachment_id );
253 unset( $attachment );
254 }
255 }
256 }
257
258 // If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames.
259 if ( false === $width && false === $height ) {
260 list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src );
261 }
262
263 // If width is available, constrain to $content_width
264 if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) {
265 if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) {
266 $height = round( ( $content_width * $height ) / $width );
267 $width = $content_width;
268 } elseif ( $width > $content_width ) {
269 $width = $content_width;
270 }
271 }
272
273 // Set a width if none is found and $content_width is available
274 // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing
275 if ( false === $width && is_numeric( $content_width ) ) {
276 $width = (int) $content_width;
277
278 if ( false !== $height )
279 $transform = 'fit';
280 }
281
282 // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
283 if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . '){1}$#i', basename( $src ), $filename ) )
284 $fullsize_url = true;
285
286 // Build URL, first maybe removing WP's resized string so we pass the original image to Photon
287 if ( ! $fullsize_url ) {
288 $src = self::strip_image_dimensions_maybe( $src );
289 }
290
291 // Build array of Photon args and expose to filter before passing to Photon URL function
292 $args = array();
293
294 if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) )
295 $args[ $transform ] = $width . ',' . $height;
296 elseif ( false !== $width )
297 $args['w'] = $width;
298 elseif ( false !== $height )
299 $args['h'] = $height;
300
301 /**
302 * Filter the array of Photon arguments added to an image when it goes through Photon.
303 * By default, only includes width and height values.
304 * @see https://developer.wordpress.com/docs/photon/api/
305 *
306 * @module photon
307 *
308 * @since 2.0.0
309 *
310 * @param array $args Array of Photon Arguments.
311 * @param array $args {
312 * Array of image details.
313 *
314 * @type $tag Image tag (Image HTML output).
315 * @type $src Image URL.
316 * @type $src_orig Original Image URL.
317 * @type $width Image width.
318 * @type $height Image height.
319 * }
320 */
321 $args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
322
323 $photon_url = jetpack_photon_url( $src, $args );
324
325 // Modify image tag if Photon function provides a URL
326 // Ensure changes are only applied to the current image by copying and modifying the matched tag, then replacing the entire tag with our modified version.
327 if ( $src != $photon_url ) {
328 $new_tag = $tag;
329
330 // If present, replace the link href with a Photoned URL for the full-size image.
331 if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) )
332 $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
333
334 // Supplant the original source value with our Photon URL
335 $photon_url = esc_url( $photon_url );
336 $new_tag = str_replace( $src_orig, $photon_url, $new_tag );
337
338 // If Lazy Load is in use, pass placeholder image through Photon
339 if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) {
340 $placeholder_src = jetpack_photon_url( $placeholder_src );
341
342 if ( $placeholder_src != $placeholder_src_orig )
343 $new_tag = str_replace( $placeholder_src_orig, esc_url( $placeholder_src ), $new_tag );
344
345 unset( $placeholder_src );
346 }
347
348 // Remove the width and height arguments from the tag to prevent distortion
349 $new_tag = preg_replace( '#(?<=\s)(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag );
350
351 // Tag an image for dimension checking
352 $new_tag = preg_replace( '#(\s?/)?>(\s*</a>)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag );
353
354 // Replace original tag with modified version
355 $content = str_replace( $tag, $new_tag, $content );
356 }
357 } elseif ( preg_match( '#^http(s)?://i[\d]{1}.wp.com#', $src ) && ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) {
358 $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $tag, 1 );
359
360 $content = str_replace( $tag, $new_tag, $content );
361 }
362 }
363 }
364
365 return $content;
366 }
367
368 public static function filter_the_galleries( $galleries ) {
369 if ( empty( $galleries ) || ! is_array( $galleries ) ) {
370 return $galleries;
371 }
372
373 // Pass by reference, so we can modify them in place.
374 foreach ( $galleries as &$this_gallery ) {
375 if ( is_string( $this_gallery ) ) {
376 $this_gallery = self::filter_the_content( $this_gallery );
377 // LEAVING COMMENTED OUT as for the moment it doesn't seem
378 // necessary and I'm not sure how it would propagate through.
379 // } elseif ( is_array( $this_gallery )
380 // && ! empty( $this_gallery['src'] )
381 // && ! empty( $this_gallery['type'] )
382 // && in_array( $this_gallery['type'], array( 'rectangle', 'square', 'circle' ) ) ) {
383 // $this_gallery['src'] = array_map( 'jetpack_photon_url', $this_gallery['src'] );
384 }
385 }
386 unset( $this_gallery ); // break the reference.
387
388 return $galleries;
389 }
390
391 /**
392 ** CORE IMAGE RETRIEVAL
393 **/
394
395 /**
396 * Filter post thumbnail image retrieval, passing images through Photon
397 *
398 * @param string|bool $image
399 * @param int $attachment_id
400 * @param string|array $size
401 * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url
402 * @filter image_downsize
403 * @return string|bool
404 */
405 public function filter_image_downsize( $image, $attachment_id, $size ) {
406 // Don't foul up the admin side of things, and provide plugins a way of preventing Photon from being applied to images.
407 if (
408 is_admin() ||
409 /**
410 * Provide plugins a way of preventing Photon from being applied to images retrieved from WordPress Core.
411 *
412 * @module photon
413 *
414 * @since 2.0.0
415 *
416 * @param bool false Stop Photon from being applied to the image. Default to false.
417 * @param array $args {
418 * Array of image details.
419 *
420 * @type $image Image URL.
421 * @type $attachment_id Attachment ID of the image.
422 * @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
423 * }
424 */
425 apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
426 )
427 return $image;
428
429 // Get the image URL and proceed with Photon-ification if successful
430 $image_url = wp_get_attachment_url( $attachment_id );
431
432 // Set this to true later when we know we have size meta.
433 $has_size_meta = false;
434
435 if ( $image_url ) {
436 // Check if image URL should be used with Photon
437 if ( ! self::validate_image_url( $image_url ) )
438 return $image;
439
440 $intermediate = true; // For the fourth array item returned by the image_downsize filter.
441
442 // If an image is requested with a size known to WordPress, use that size's settings with Photon
443 if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
444 $image_args = self::image_sizes();
445 $image_args = $image_args[ $size ];
446
447 $photon_args = array();
448
449 $image_meta = image_get_intermediate_size( $attachment_id, $size );
450
451 // 'full' is a special case: We need consistent data regardless of the requested size.
452 if ( 'full' == $size ) {
453 $image_meta = wp_get_attachment_metadata( $attachment_id );
454 $intermediate = false;
455 } elseif ( ! $image_meta ) {
456 // If we still don't have any image meta at this point, it's probably from a custom thumbnail size
457 // for an image that was uploaded before the custom image was added to the theme. Try to determine the size manually.
458 $image_meta = wp_get_attachment_metadata( $attachment_id );
459
460 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
461 $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
462 if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
463 $image_meta['width'] = $image_resized[6];
464 $image_meta['height'] = $image_resized[7];
465 }
466 }
467 }
468
469 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
470 $image_args['width'] = $image_meta['width'];
471 $image_args['height'] = $image_meta['height'];
472
473 list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
474 $has_size_meta = true;
475 }
476
477 // Expose determined arguments to a filter before passing to Photon
478 $transform = $image_args['crop'] ? 'resize' : 'fit';
479
480 // Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
481 if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
482 if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) {
483 $photon_args['h'] = $image_args['height'];
484 } elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) {
485 $photon_args['w'] = $image_args['width'];
486 }
487 } else {
488 if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) {
489 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
490 // Lets make sure that we don't upscale images since wp never upscales them as well
491 $smaller_width = ( ( $image_meta['width'] < $image_args['width'] ) ? $image_meta['width'] : $image_args['width'] );
492 $smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
493
494 $photon_args[ $transform ] = $smaller_width . ',' . $smaller_height;
495 }
496 } else {
497 $photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
498 }
499
500 }
501
502
503 /**
504 * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string.
505 * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
506 *
507 * @module photon
508 *
509 * @since 2.0.0
510 *
511 * @param array $photon_args Array of Photon arguments.
512 * @param array $args {
513 * Array of image details.
514 *
515 * @type $image_args Array of Image arguments (width, height, crop).
516 * @type $image_url Image URL.
517 * @type $attachment_id Attachment ID of the image.
518 * @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
519 * @type $transform Value can be resize or fit.
520 * @see https://developer.wordpress.com/docs/photon/api
521 * }
522 */
523 $photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
524
525 // Generate Photon URL
526 $image = array(
527 jetpack_photon_url( $image_url, $photon_args ),
528 $has_size_meta ? $image_args['width'] : false,
529 $has_size_meta ? $image_args['height'] : false,
530 $intermediate
531 );
532 } elseif ( is_array( $size ) ) {
533 // Pull width and height values from the provided array, if possible
534 $width = isset( $size[0] ) ? (int) $size[0] : false;
535 $height = isset( $size[1] ) ? (int) $size[1] : false;
536
537 // Don't bother if necessary parameters aren't passed.
538 if ( ! $width || ! $height ) {
539 return $image;
540 }
541
542 $image_meta = wp_get_attachment_metadata( $attachment_id );
543 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
544 $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height );
545
546 if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
547 $width = $image_resized[6];
548 $height = $image_resized[7];
549 } else {
550 $width = $image_meta['width'];
551 $height = $image_meta['height'];
552 }
553
554 $has_size_meta = true;
555 }
556
557 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
558
559 // Expose arguments to a filter before passing to Photon
560 $photon_args = array(
561 'fit' => $width . ',' . $height
562 );
563
564 /**
565 * Filter the Photon Arguments added to an image when going through Photon,
566 * when the image size is an array of height and width values.
567 *
568 * @module photon
569 *
570 * @since 2.0.0
571 *
572 * @param array $photon_args Array of Photon arguments.
573 * @param array $args {
574 * Array of image details.
575 *
576 * @type $width Image width.
577 * @type height Image height.
578 * @type $image_url Image URL.
579 * @type $attachment_id Attachment ID of the image.
580 * }
581 */
582 $photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
583
584 // Generate Photon URL
585 $image = array(
586 jetpack_photon_url( $image_url, $photon_args ),
587 $has_size_meta ? $width : false,
588 $has_size_meta ? $height : false,
589 $intermediate
590 );
591 }
592 }
593
594 return $image;
595 }
596
597 /**
598 * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent.
599 *
600 * @since 3.8.0
601 * @since 4.0.4 Added automatically additional sizes beyond declared image sizes.
602 * @param array $sources An array of image urls and widths.
603 * @uses self::validate_image_url, jetpack_photon_url, Jetpack_Photon::parse_from_filename
604 * @uses Jetpack_Photon::strip_image_dimensions_maybe, Jetpack::get_content_width
605 * @return array An array of Photon image urls and widths.
606 */
607 public function filter_srcset_array( $sources, $size_array, $image_src, $image_meta ) {
608 $upload_dir = wp_get_upload_dir();
609
610 foreach ( $sources as $i => $source ) {
611 if ( ! self::validate_image_url( $source['url'] ) ) {
612 continue;
613 }
614
615 /** This filter is already documented in class.photon.php */
616 if ( apply_filters( 'jetpack_photon_skip_image', false, $source['url'], $source ) ) {
617 continue;
618 }
619
620 $url = $source['url'];
621 list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $url );
622
623 // It's quicker to get the full size with the data we have already, if available
624 if ( isset( $image_meta['file'] ) ) {
625 $url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
626 } else {
627 $url = Jetpack_Photon::strip_image_dimensions_maybe( $url );
628 }
629
630 $args = array();
631 if ( 'w' === $source['descriptor'] ) {
632 if ( $height && ( $source['value'] == $width ) ) {
633 $args['resize'] = $width . ',' . $height;
634 } else {
635 $args['w'] = $source['value'];
636 }
637
638 }
639
640 $sources[ $i ]['url'] = jetpack_photon_url( $url, $args );
641 }
642
643 /**
644 * At this point, $sources is the original srcset with Photonized URLs.
645 * Now, we're going to construct additional sizes based on multiples of the content_width.
646 * This will reduce the gap between the largest defined size and the original image.
647 */
648
649 /**
650 * Filter the multiplier Photon uses to create new srcset items.
651 * Return false to short-circuit and bypass auto-generation.
652 *
653 * @module photon
654 *
655 * @since 4.0.4
656 *
657 * @param array|bool $multipliers Array of multipliers to use or false to bypass.
658 */
659 $multipliers = apply_filters( 'jetpack_photon_srcset_multipliers', array( 2, 3 ) );
660 $url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
661
662 if (
663 /** Short-circuit via jetpack_photon_srcset_multipliers filter. */
664 is_array( $multipliers )
665 /** This filter is already documented in class.photon.php */
666 && ! apply_filters( 'jetpack_photon_skip_image', false, $url, null )
667 /** Verify basic meta is intact. */
668 && isset( $image_meta['width'] ) && isset( $image_meta['height'] ) && isset( $image_meta['file'] )
669 /** Verify we have the requested width/height. */
670 && isset( $size_array[0] ) && isset( $size_array[1] )
671 ) {
672
673 $fullwidth = $image_meta['width'];
674 $fullheight = $image_meta['height'];
675 $reqwidth = $size_array[0];
676 $reqheight = $size_array[1];
677
678 $constrained_size = wp_constrain_dimensions( $fullwidth, $fullheight, $reqwidth );
679 $expected_size = array( $reqwidth, $reqheight );
680
681 if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) {
682 $crop = 'soft';
683 $base = Jetpack::get_content_width() ? Jetpack::get_content_width() : 1000; // Provide a default width if none set by the theme.
684 } else {
685 $crop = 'hard';
686 $base = $reqwidth;
687 }
688
689
690 $currentwidths = array_keys( $sources );
691 $newsources = null;
692
693 foreach ( $multipliers as $multiplier ) {
694
695 $newwidth = $base * $multiplier;
696 foreach ( $currentwidths as $currentwidth ){
697 // If a new width would be within 100 pixes of an existing one or larger than the full size image, skip.
698 if ( abs( $currentwidth - $newwidth ) < 50 || ( $newwidth > $fullwidth ) ) {
699 continue 2; // Back to the foreach ( $multipliers as $multiplier )
700 }
701 } // foreach ( $currentwidths as $currentwidth ){
702
703 if ( 'soft' == $crop ) {
704 $args = array(
705 'w' => $newwidth,
706 );
707 } else { // hard crop, e.g. add_image_size( 'example', 200, 200, true );
708 $args = array(
709 'zoom' => $multiplier,
710 'resize' => $reqwidth . ',' . $reqheight,
711 );
712 }
713
714 $newsources[ $newwidth ] = array(
715 'url' => jetpack_photon_url( $url, $args ),
716 'descriptor' => 'w',
717 'value' => $newwidth,
718 );
719 } // foreach ( $multipliers as $multiplier )
720 if ( is_array( $newsources ) ) {
721 if ( function_exists( 'array_replace' ) ) { // PHP 5.3+, preferred
722 $sources = array_replace( $sources, $newsources );
723 } else { // For PHP 5.2 using WP shim function
724 $sources = array_replace_recursive( $sources, $newsources );
725 }
726 }
727 } // if ( isset( $image_meta['width'] ) && isset( $image_meta['file'] ) )
728
729 return $sources;
730 }
731
732 /**
733 * Filters an array of image `sizes` values, using $content_width instead of image's full size.
734 *
735 * @since 4.0.4
736 * @since 4.1.0 Returns early for images not within the_content.
737 * @param array $sizes An array of media query breakpoints.
738 * @param array $size Width and height of the image
739 * @uses Jetpack::get_content_width
740 * @return array An array of media query breakpoints.
741 */
742 public function filter_sizes( $sizes, $size ) {
743 if ( ! doing_filter( 'the_content' ) ){
744 return $sizes;
745 }
746 $content_width = Jetpack::get_content_width();
747 if ( ! $content_width ) {
748 $content_width = 1000;
749 }
750
751 if ( ( is_array( $size ) && $size[0] < $content_width ) ) {
752 return $sizes;
753 }
754
755 return sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $content_width );
756 }
757
758 /**
759 ** GENERAL FUNCTIONS
760 **/
761
762 /**
763 * Ensure image URL is valid for Photon.
764 * Though Photon functions address some of the URL issues, we should avoid unnecessary processing if we know early on that the image isn't supported.
765 *
766 * @param string $url
767 * @uses wp_parse_args
768 * @return bool
769 */
770 protected static function validate_image_url( $url ) {
771 $parsed_url = @parse_url( $url );
772
773 if ( ! $parsed_url )
774 return false;
775
776 // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
777 $url_info = wp_parse_args( $parsed_url, array(
778 'scheme' => null,
779 'host' => null,
780 'port' => null,
781 'path' => null
782 ) );
783
784 // Bail if scheme isn't http or port is set that isn't port 80
785 if (
786 ( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) &&
787 /**
788 * Allow Photon to fetch images that are served via HTTPS.
789 *
790 * @module photon
791 *
792 * @since 2.4.0
793 * @since 3.9.0 Default to false.
794 *
795 * @param bool $reject_https Should Photon ignore images using the HTTPS scheme. Default to false.
796 */
797 apply_filters( 'jetpack_photon_reject_https', false )
798 ) {
799 return false;
800 }
801
802 // Bail if no host is found
803 if ( is_null( $url_info['host'] ) )
804 return false;
805
806 // Bail if the image alredy went through Photon
807 if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
808 return false;
809
810 // Bail if no path is found
811 if ( is_null( $url_info['path'] ) )
812 return false;
813
814 // Ensure image extension is acceptable
815 if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
816 return false;
817
818 // If we got this far, we should have an acceptable image URL
819 // But let folks filter to decline if they prefer.
820 /**
821 * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon.
822 *
823 * @module photon
824 *
825 * @since 3.0.0
826 *
827 * @param bool true Is the image URL valid and can it be used by Photon. Default to true.
828 * @param string $url Image URL.
829 * @param array $parsed_url Array of information about the image.
830 */
831 return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url );
832 }
833
834 /**
835 * Checks if the file exists before it passes the file to photon
836 *
837 * @param string $src The image URL
838 * @return string
839 **/
840 protected static function strip_image_dimensions_maybe( $src ){
841 $stripped_src = $src;
842
843 // Build URL, first removing WP's resized string so we pass the original image to Photon
844 if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) ) {
845 $stripped_src = str_replace( $src_parts[1], '', $src );
846 $upload_dir = wp_get_upload_dir();
847
848 // Extracts the file path to the image minus the base url
849 $file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) );
850
851 if( file_exists( $upload_dir["basedir"] . $file_path ) )
852 $src = $stripped_src;
853 }
854
855 return $src;
856 }
857
858 /**
859 * Provide an array of available image sizes and corresponding dimensions.
860 * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
861 *
862 * @global $wp_additional_image_sizes
863 * @uses get_option
864 * @return array
865 */
866 protected static function image_sizes() {
867 if ( null == self::$image_sizes ) {
868 global $_wp_additional_image_sizes;
869
870 // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
871 $images = array(
872 'thumb' => array(
873 'width' => intval( get_option( 'thumbnail_size_w' ) ),
874 'height' => intval( get_option( 'thumbnail_size_h' ) ),
875 'crop' => (bool) get_option( 'thumbnail_crop' )
876 ),
877 'medium' => array(
878 'width' => intval( get_option( 'medium_size_w' ) ),
879 'height' => intval( get_option( 'medium_size_h' ) ),
880 'crop' => false
881 ),
882 'large' => array(
883 'width' => intval( get_option( 'large_size_w' ) ),
884 'height' => intval( get_option( 'large_size_h' ) ),
885 'crop' => false
886 ),
887 'full' => array(
888 'width' => null,
889 'height' => null,
890 'crop' => false
891 )
892 );
893
894 // Compatibility mapping as found in wp-includes/media.php
895 $images['thumbnail'] = $images['thumb'];
896
897 // Update class variable, merging in $_wp_additional_image_sizes if any are set
898 if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
899 self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
900 else
901 self::$image_sizes = $images;
902 }
903
904 return is_array( self::$image_sizes ) ? self::$image_sizes : array();
905 }
906
907 /**
908 * Pass og:image URLs through Photon
909 *
910 * @param array $tags
911 * @param array $parameters
912 * @uses jetpack_photon_url
913 * @return array
914 */
915 function filter_open_graph_tags( $tags, $parameters ) {
916 if ( empty( $tags['og:image'] ) ) {
917 return $tags;
918 }
919
920 $photon_args = array(
921 'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
922 );
923
924 if ( is_array( $tags['og:image'] ) ) {
925 $images = array();
926 foreach ( $tags['og:image'] as $image ) {
927 $images[] = jetpack_photon_url( $image, $photon_args );
928 }
929 $tags['og:image'] = $images;
930 } else {
931 $tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
932 }
933
934 return $tags;
935 }
936
937 /**
938 * Enqueue Photon helper script
939 *
940 * @uses wp_enqueue_script, plugins_url
941 * @action wp_enqueue_script
942 * @return null
943 */
944 public function action_wp_enqueue_scripts() {
945 wp_enqueue_script( 'jetpack-photon', plugins_url( 'modules/photon/photon.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), 20130122, true );
946 }
947 }
948