Completed
Push — update/travis-matrix-7 ( 497e3e...cc9f21 )
by
unknown
34:26 queued 14:09
created

Jetpack_Photon::image_sizes()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 40
rs 8.439
nc 6
cc 5
eloc 26
nop 0
1
<?php
2
3
class Jetpack_Photon {
1 ignored issue
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
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
		// Display warning if site is private
49
		add_action( 'jetpack_activate_module_photon', array( $this, 'action_jetpack_activate_module_photon' ) );
50
51
		if ( ! function_exists( 'jetpack_photon_url' ) )
52
			return;
53
54
		// Images in post content and galleries
55
		add_filter( 'the_content', array( __CLASS__, 'filter_the_content' ), 999999 );
56
		add_filter( 'get_post_galleries', array( __CLASS__, 'filter_the_galleries' ), 999999 );
57
58
		// Core image retrieval
59
		add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
60
61
		// Responsive image srcset substitution
62
		add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 10, 4 );
63
64
		// Helpers for maniuplated images
65
		add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
66
	}
67
68
	/**
69
	 * Check if site is private and warn user if it is
70
	 *
71
	 * @uses Jetpack::check_privacy
72
	 * @action jetpack_activate_module_photon
73
	 * @return null
74
	 */
75
	public function action_jetpack_activate_module_photon() {
76
		Jetpack::check_privacy( __FILE__ );
77
	}
78
79
	/**
80
	 ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS
81
	 **/
82
83
	/**
84
	 * Match all images and any relevant <a> tags in a block of HTML.
85
	 *
86
	 * @param string $content Some HTML.
87
	 * @return array An array of $images matches, where $images[0] is
88
	 *         an array of full matches, and the link_url, img_tag,
89
	 *         and img_url keys are arrays of those matches.
90
	 */
91
	public static function parse_images_from_html( $content ) {
92
		$images = array();
93
94
		if ( preg_match_all( '#(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag><img[^>]+?src=["|\'](?P<img_url>[^\s]+?)["|\'].*?>){1}(?:\s*</a>)?#is', $content, $images ) ) {
95
			foreach ( $images as $key => $unused ) {
96
				// Simplify the output as much as possible, mostly for confirming test results.
97
				if ( is_numeric( $key ) && $key > 0 )
98
					unset( $images[$key] );
99
			}
100
101
			return $images;
102
		}
103
104
		return array();
105
	}
106
107
	/**
108
	 * Try to determine height and width from strings WP appends to resized image filenames.
109
	 *
110
	 * @param string $src The image URL.
111
	 * @return array An array consisting of width and height.
112
	 */
113
	public static function parse_dimensions_from_filename( $src ) {
114
		$width_height_string = array();
115
116
		if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . '){1}$#i', $src, $width_height_string ) ) {
117
			$width = (int) $width_height_string[1];
118
			$height = (int) $width_height_string[2];
119
120
			if ( $width && $height )
121
				return array( $width, $height );
122
		}
123
124
		return array( false, false );
125
	}
126
127
	/**
128
	 * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon.
129
	 *
130
	 * @param string $content
131
	 * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url
132
	 * @filter the_content
133
	 * @return string
134
	 */
135
	public static function filter_the_content( $content ) {
136
		$images = Jetpack_Photon::parse_images_from_html( $content );
137
138
		if ( ! empty( $images ) ) {
139
			$content_width = Jetpack::get_content_width();
140
141
			$image_sizes = self::image_sizes();
142
			$upload_dir = wp_upload_dir();
143
144
			foreach ( $images[0] as $index => $tag ) {
145
				// Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained
146
				$transform = 'resize';
147
148
				// Start with a clean attachment ID each time
149
				$attachment_id = false;
150
151
				// Flag if we need to munge a fullsize URL
152
				$fullsize_url = false;
153
154
				// Identify image source
155
				$src = $src_orig = $images['img_url'][ $index ];
156
157
				/**
158
				 * Allow specific images to be skipped by Photon.
159
				 *
160
				 * @module photon
161
				 *
162
				 * @since 2.0.3
163
				 *
164
				 * @param bool false Should Photon ignore this image. Default to false.
165
				 * @param string $src Image URL.
166
				 * @param string $tag Image Tag (Image HTML output).
167
				 */
168
				if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) )
169
					continue;
170
171
				// Support Automattic's Lazy Load plugin
172
				// Can't modify $tag yet as we need unadulterated version later
173
				if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
174
					$placeholder_src = $placeholder_src_orig = $src;
175
					$src = $src_orig = $lazy_load_src[1];
176
				} elseif ( preg_match( '#data-lazy-original=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
177
					$placeholder_src = $placeholder_src_orig = $src;
178
					$src = $src_orig = $lazy_load_src[1];
179
				}
180
181
				// Check if image URL should be used with Photon
182
				if ( self::validate_image_url( $src ) ) {
183
					// Find the width and height attributes
184
					$width = $height = false;
185
186
					// First, check the image tag
187
					if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) )
188
						$width = $width_string[1];
189
190
					if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) )
191
						$height = $height_string[1];
192
193
					// Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout.
194
					if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) )
195
						$width = $height = false;
196
197
					// Detect WP registered image size from HTML class
198
					if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
199
						$size = array_pop( $size );
200
201
						if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) {
202
							$width = (int) $image_sizes[ $size ]['width'];
203
							$height = (int) $image_sizes[ $size ]['height'];
204
							$transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
205
						}
206
					} else {
207
						unset( $size );
208
					}
209
210
					// WP Attachment ID, if uploaded to this site
211
					if (
212
						preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) &&
213
						(
214
							0 === strpos( $src, $upload_dir['baseurl'] ) ||
215
							/**
216
							 * Filter whether an image using an attachment ID in its class has to be uploaded to the local site to go through Photon.
217
							 *
218
							 * @module photon
219
							 *
220
							 * @since 2.0.3
221
							 *
222
							 * @param bool false Was the image uploaded to the local site. Default to false.
223
							 * @param array $args {
224
							 * 	 Array of image details.
225
							 *
226
							 * 	 @type $src Image URL.
227
							 * 	 @type tag Image tag (Image HTML output).
228
							 * 	 @type $images Array of information about the image.
229
							 * 	 @type $index Image index.
230
							 * }
231
							 */
232
							apply_filters( 'jetpack_photon_image_is_local', false, compact( 'src', 'tag', 'images', 'index' ) )
233
						)
234
					) {
235
						$attachment_id = intval( array_pop( $attachment_id ) );
236
237
						if ( $attachment_id ) {
238
							$attachment = get_post( $attachment_id );
239
240
							// Basic check on returned post object
241
							if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) {
242
								$src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' );
243
244
								if ( self::validate_image_url( $src_per_wp[0] ) ) {
245
									$src = $src_per_wp[0];
246
									$fullsize_url = true;
247
248
									// Prevent image distortion if a detected dimension exceeds the image's natural dimensions
249
									if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
250
										$width = false == $width ? false : min( $width, $src_per_wp[1] );
251
										$height = false == $height ? false : min( $height, $src_per_wp[2] );
252
									}
253
254
									// If no width and height are found, max out at source image's natural dimensions
255
									// Otherwise, respect registered image sizes' cropping setting
256
									if ( false == $width && false == $height ) {
257
										$width = $src_per_wp[1];
258
										$height = $src_per_wp[2];
259
										$transform = 'fit';
260
									} elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
261
										$transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
262
									}
263
								}
264
							} else {
265
								unset( $attachment_id );
266
								unset( $attachment );
267
							}
268
						}
269
					}
270
271
					// If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames.
272
					if ( false === $width && false === $height ) {
273
						list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src );
274
					}
275
276
					// If width is available, constrain to $content_width
277
					if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) {
278
						if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) {
279
							$height = round( ( $content_width * $height ) / $width );
280
							$width = $content_width;
281
						} elseif ( $width > $content_width ) {
282
							$width = $content_width;
283
						}
284
					}
285
286
					// Set a width if none is found and $content_width is available
287
					// If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing
288
					if ( false === $width && is_numeric( $content_width ) ) {
289
						$width = (int) $content_width;
290
291
						if ( false !== $height )
292
							$transform = 'fit';
293
					}
294
295
					// Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
296
					if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . '){1}$#i', basename( $src ), $filename ) )
297
						$fullsize_url = true;
298
299
					// Build URL, first maybe removing WP's resized string so we pass the original image to Photon
300
					if ( ! $fullsize_url ) {
301
						$src = self::strip_image_dimensions_maybe( $src );
302
					}
303
304
					// Build array of Photon args and expose to filter before passing to Photon URL function
305
					$args = array();
306
307
					if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) )
308
						$args[ $transform ] = $width . ',' . $height;
309
					elseif ( false !== $width )
310
						$args['w'] = $width;
311
					elseif ( false !== $height )
312
						$args['h'] = $height;
313
314
					/**
315
					 * Filter the array of Photon arguments added to an image when it goes through Photon.
316
					 * By default, only includes width and height values.
317
					 * @see https://developer.wordpress.com/docs/photon/api/
318
					 *
319
					 * @module photon
320
					 *
321
					 * @since 2.0.0
322
					 *
323
					 * @param array $args Array of Photon Arguments.
324
					 * @param array $args {
325
					 * 	 Array of image details.
326
					 *
327
					 * 	 @type $tag Image tag (Image HTML output).
328
					 * 	 @type $src Image URL.
329
					 * 	 @type $src_orig Original Image URL.
330
					 * 	 @type $width Image width.
331
					 * 	 @type $height Image height.
332
					 * }
333
					 */
334
					$args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
335
336
					$photon_url = jetpack_photon_url( $src, $args );
337
338
					// Modify image tag if Photon function provides a URL
339
					// 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.
340
					if ( $src != $photon_url ) {
341
						$new_tag = $tag;
342
343
						// If present, replace the link href with a Photoned URL for the full-size image.
344
						if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) )
345
							$new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
346
347
						// Supplant the original source value with our Photon URL
348
						$photon_url = esc_url( $photon_url );
349
						$new_tag = str_replace( $src_orig, $photon_url, $new_tag );
350
351
						// If Lazy Load is in use, pass placeholder image through Photon
352
						if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) {
353
							$placeholder_src = jetpack_photon_url( $placeholder_src );
354
355
							if ( $placeholder_src != $placeholder_src_orig )
356
								$new_tag = str_replace( $placeholder_src_orig, esc_url( $placeholder_src ), $new_tag );
0 ignored issues
show
Bug introduced by
The variable $placeholder_src_orig does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
357
358
							unset( $placeholder_src );
359
						}
360
361
						// Remove the width and height arguments from the tag to prevent distortion
362
						$new_tag = preg_replace( '#(?<=\s)(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag );
363
364
						// Tag an image for dimension checking
365
						$new_tag = preg_replace( '#(\s?/)?>(\s*</a>)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag );
366
367
						// Replace original tag with modified version
368
						$content = str_replace( $tag, $new_tag, $content );
369
					}
370
				} elseif ( preg_match( '#^http(s)?://i[\d]{1}.wp.com#', $src ) && ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) {
371
					$new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $tag, 1 );
372
373
					$content = str_replace( $tag, $new_tag, $content );
374
				}
375
			}
376
		}
377
378
		return $content;
379
	}
380
381
	public static function filter_the_galleries( $galleries ) {
382
		if ( empty( $galleries ) || ! is_array( $galleries ) ) {
383
			return $galleries;
384
		}
385
386
		// Pass by reference, so we can modify them in place.
387
		foreach ( $galleries as &$this_gallery ) {
388
			if ( is_string( $this_gallery ) ) {
389
				$this_gallery = self::filter_the_content( $this_gallery );
390
		// LEAVING COMMENTED OUT as for the moment it doesn't seem
391
		// necessary and I'm not sure how it would propagate through.
392
		//	} elseif ( is_array( $this_gallery )
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
393
		//	           && ! empty( $this_gallery['src'] )
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
394
		//	           && ! empty( $this_gallery['type'] )
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
395
		//	           && in_array( $this_gallery['type'], array( 'rectangle', 'square', 'circle' ) ) ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
396
		//		$this_gallery['src'] = array_map( 'jetpack_photon_url', $this_gallery['src'] );
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
397
			}
398
		}
399
		unset( $this_gallery ); // break the reference.
400
401
		return $galleries;
402
	}
403
404
	/**
405
	 ** CORE IMAGE RETRIEVAL
406
	 **/
407
408
	/**
409
	 * Filter post thumbnail image retrieval, passing images through Photon
410
	 *
411
	 * @param string|bool $image
412
	 * @param int $attachment_id
413
	 * @param string|array $size
414
	 * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url
415
	 * @filter image_downsize
416
	 * @return string|bool
417
	 */
418
	public function filter_image_downsize( $image, $attachment_id, $size ) {
419
		// Don't foul up the admin side of things, and provide plugins a way of preventing Photon from being applied to images.
420
		if (
421
			is_admin() ||
422
			/**
423
			 * Provide plugins a way of preventing Photon from being applied to images retrieved from WordPress Core.
424
			 *
425
			 * @module photon
426
			 *
427
			 * @since 2.0.0
428
			 *
429
			 * @param bool false Stop Photon from being applied to the image. Default to false.
430
			 * @param array $args {
431
			 * 	 Array of image details.
432
			 *
433
			 * 	 @type $image Image URL.
434
			 * 	 @type $attachment_id Attachment ID of the image.
435
			 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
436
			 * }
437
			 */
438
			apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
439
		)
440
			return $image;
441
442
		// Get the image URL and proceed with Photon-ification if successful
443
		$image_url = wp_get_attachment_url( $attachment_id );
444
445
		if ( $image_url ) {
446
			// Check if image URL should be used with Photon
447
			if ( ! self::validate_image_url( $image_url ) )
448
				return $image;
449
450
			// If an image is requested with a size known to WordPress, use that size's settings with Photon
451
			if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
452
				$image_args = self::image_sizes();
453
				$image_args = $image_args[ $size ];
454
455
				$photon_args = array();
456
457
				$image_meta = image_get_intermediate_size( $attachment_id, $size );
458
459
				// 'full' is a special case: We need consistent data regardless of the requested size.
460
				if ( 'full' == $size ) {
461
					$image_meta = wp_get_attachment_metadata( $attachment_id );
462
				} elseif ( ! $image_meta ) {
463
					// If we still don't have any image meta at this point, it's probably from a custom thumbnail size
464
					// for an image that was uploaded before the custom image was added to the theme.  Try to determine the size manually.
465
					$image_meta = wp_get_attachment_metadata( $attachment_id );
466
					$image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
467
					if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
468
						$image_meta['width'] = $image_resized[6];
469
						$image_meta['height'] = $image_resized[7];
470
					}
471
				}
472
473
				$image_args['width']  = $image_meta['width'];
474
				$image_args['height'] = $image_meta['height'];
475
476
				list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
477
478
				// Expose determined arguments to a filter before passing to Photon
479
				$transform = $image_args['crop'] ? 'resize' : 'fit';
480
481
				// Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
482
				if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
483
					if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) {
484
						$photon_args['h'] = $image_args['height'];
485
					} elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) {
486
						$photon_args['w'] = $image_args['width'];
487
					}
488
				} else {
489
					if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) {
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
					} else {
496
						$photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
497
					}
498
499
				}
500
501
502
				/**
503
				 * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string.
504
				 * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
505
				 *
506
				 * @module photon
507
				 *
508
				 * @since 2.0.0
509
				 *
510
				 * @param array $photon_args Array of Photon arguments.
511
				 * @param array $args {
512
				 * 	 Array of image details.
513
				 *
514
				 * 	 @type $image_args Array of Image arguments (width, height, crop).
515
				 * 	 @type $image_url Image URL.
516
				 * 	 @type $attachment_id Attachment ID of the image.
517
				 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
518
				 * 	 @type $transform Value can be resize or fit.
519
				 *                    @see https://developer.wordpress.com/docs/photon/api
520
				 * }
521
				 */
522
				$photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
523
524
				// Generate Photon URL
525
				$image = array(
526
					jetpack_photon_url( $image_url, $photon_args ),
527
					$image_args['width'],
528
					$image_args['height']
529
				);
530
			} elseif ( is_array( $size ) ) {
531
				// Pull width and height values from the provided array, if possible
532
				$width = isset( $size[0] ) ? (int) $size[0] : false;
533
				$height = isset( $size[1] ) ? (int) $size[1] : false;
534
535
				// Don't bother if necessary parameters aren't passed.
536
				if ( ! $width || ! $height ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $width of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $height of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
537
					return $image;
538
				}
539
540
				$image_meta = wp_get_attachment_metadata( $attachment_id );
541
				$image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height );
542
				$width = $image_resized[6];
543
				$height = $image_resized[7];
544
545
				list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
546
547
				// Expose arguments to a filter before passing to Photon
548
				$photon_args = array(
549
					'fit' => $width . ',' . $height
550
				);
551
552
				/**
553
				 * Filter the Photon Arguments added to an image when going through Photon,
554
				 * when the image size is an array of height and width values.
555
				 *
556
				 * @module photon
557
				 *
558
				 * @since 2.0.0
559
				 *
560
				 * @param array $photon_args Array of Photon arguments.
561
				 * @param array $args {
562
				 * 	 Array of image details.
563
				 *
564
				 * 	 @type $width Image width.
565
				 * 	 @type height Image height.
566
				 * 	 @type $image_url Image URL.
567
				 * 	 @type $attachment_id Attachment ID of the image.
568
				 * }
569
				 */
570
				$photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
571
572
				// Generate Photon URL
573
				$image = array(
574
					jetpack_photon_url( $image_url, $photon_args ),
575
					$width,
576
					$height
577
				);
578
			}
579
		}
580
581
		return $image;
582
	}
583
584
	/**
585
	 * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent.
586
	 *
587
	 * @since 3.8.0
588
	 * @param array $sources An array of image urls and widths.
589
	 * @uses self::validate_image_url, jetpack_photon_url
590
	 * @return array An array of Photon image urls and widths.
591
	 */
592
	public function filter_srcset_array( $sources, $size_array, $image_src, $image_meta ) {
593
		$upload_dir = wp_upload_dir();
594
595
		foreach ( $sources as $i => $source ) {
596
			if ( ! self::validate_image_url( $source['url'] ) ) {
597
				continue;
598
			}
599
600
			$url = $source['url'];
601
			list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $url );
602
603
			// It's quicker to get the full size with the data we have already, if available
604
			if ( isset( $image_meta['file'] ) ) {
605
				$url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
606
			} else {
607
				$url = Jetpack_Photon::strip_image_dimensions_maybe( $url );
608
			}
609
610
			$args = array();
611
			if ( 'w' === $source['descriptor'] ) {
612
				if ( $height && ( $source['value'] == $width ) ) {
613
					$args['resize'] = $width . ',' . $height;
614
				} else {
615
					$args['w'] = $source['value'];
616
				}
617
618
			}
619
620
			$sources[ $i ]['url'] = jetpack_photon_url( $url, $args );
621
		}
622
623
		return $sources;
624
	}
625
626
	/**
627
	 ** GENERAL FUNCTIONS
628
	 **/
629
630
	/**
631
	 * Ensure image URL is valid for Photon.
632
	 * 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.
633
	 *
634
	 * @param string $url
635
	 * @uses wp_parse_args
636
	 * @return bool
637
	 */
638
	protected static function validate_image_url( $url ) {
639
		$parsed_url = @parse_url( $url );
640
641
		if ( ! $parsed_url )
642
			return false;
643
644
		// Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
645
		$url_info = wp_parse_args( $parsed_url, array(
646
			'scheme' => null,
647
			'host'   => null,
648
			'port'   => null,
649
			'path'   => null
650
		) );
651
652
		// Bail if scheme isn't http or port is set that isn't port 80
653
		if (
654
			( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) &&
655
			/**
656
			 * Allow Photon to fetch images that are served via HTTPS.
657
			 *
658
			 * @module photon
659
			 *
660
			 * @since 2.4.0
661
			 *
662
			 * @param bool true Should Photon ignore images using the HTTPS scheme. Default to true.
663
			 */
664
			apply_filters( 'jetpack_photon_reject_https', true )
665
		) {
666
			return false;
667
		}
668
669
		// Bail if no host is found
670
		if ( is_null( $url_info['host'] ) )
671
			return false;
672
673
		// Bail if the image alredy went through Photon
674
		if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
675
			return false;
676
677
		// Bail if no path is found
678
		if ( is_null( $url_info['path'] ) )
679
			return false;
680
681
		// Ensure image extension is acceptable
682
		if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
683
			return false;
684
685
		// If we got this far, we should have an acceptable image URL
686
		// But let folks filter to decline if they prefer.
687
		/**
688
		 * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon.
689
		 *
690
		 * @module photon
691
		 *
692
		 * @since 3.0.0
693
		 *
694
		 * @param bool true Is the image URL valid and can it be used by Photon. Default to true.
695
		 * @param string $url Image URL.
696
		 * @param array $parsed_url Array of information about the image.
697
		 */
698
		return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url );
699
	}
700
701
	/**
702
	 * Checks if the file exists before it passes the file to photon
703
	 *
704
	 * @param string $src The image URL
705
	 * @return string
706
	 **/
707
	protected static function strip_image_dimensions_maybe( $src ){
708
		$stripped_src = $src;
0 ignored issues
show
Unused Code introduced by
$stripped_src is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
709
710
		// Build URL, first removing WP's resized string so we pass the original image to Photon
711
		if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) ) {
712
			$stripped_src = str_replace( $src_parts[1], '', $src );
713
			$upload_dir = wp_upload_dir();
714
715
			// Extracts the file path to the image minus the base url
716
			$file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) );
717
718
			if( file_exists( $upload_dir["basedir"] . $file_path ) )
719
				$src = $stripped_src;
720
		}
721
722
		return $src;
723
	}
724
725
	/**
726
	 * Provide an array of available image sizes and corresponding dimensions.
727
	 * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
728
	 *
729
	 * @global $wp_additional_image_sizes
730
	 * @uses get_option
731
	 * @return array
732
	 */
733
	protected static function image_sizes() {
734
		if ( null == self::$image_sizes ) {
735
			global $_wp_additional_image_sizes;
736
737
			// Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
738
			$images = array(
739
				'thumb'  => array(
740
					'width'  => intval( get_option( 'thumbnail_size_w' ) ),
741
					'height' => intval( get_option( 'thumbnail_size_h' ) ),
742
					'crop'   => (bool) get_option( 'thumbnail_crop' )
743
				),
744
				'medium' => array(
745
					'width'  => intval( get_option( 'medium_size_w' ) ),
746
					'height' => intval( get_option( 'medium_size_h' ) ),
747
					'crop'   => false
748
				),
749
				'large'  => array(
750
					'width'  => intval( get_option( 'large_size_w' ) ),
751
					'height' => intval( get_option( 'large_size_h' ) ),
752
					'crop'   => false
753
				),
754
				'full'   => array(
755
					'width'  => null,
756
					'height' => null,
757
					'crop'   => false
758
				)
759
			);
760
761
			// Compatibility mapping as found in wp-includes/media.php
762
			$images['thumbnail'] = $images['thumb'];
763
764
			// Update class variable, merging in $_wp_additional_image_sizes if any are set
765
			if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
766
				self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
767
			else
768
				self::$image_sizes = $images;
769
		}
770
771
		return is_array( self::$image_sizes ) ? self::$image_sizes : array();
772
	}
773
774
	/**
775
	 * Pass og:image URLs through Photon
776
	 *
777
	 * @param array $tags
778
	 * @param array $parameters
779
	 * @uses jetpack_photon_url
780
	 * @return array
781
	 */
782
	function filter_open_graph_tags( $tags, $parameters ) {
783
		if ( empty( $tags['og:image'] ) ) {
784
			return $tags;
785
		}
786
787
		$photon_args = array(
788
			'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
789
		);
790
791
		if ( is_array( $tags['og:image'] ) ) {
792
			$images = array();
793
			foreach ( $tags['og:image'] as $image ) {
794
				$images[] = jetpack_photon_url( $image, $photon_args );
795
			}
796
			$tags['og:image'] = $images;
797
		} else {
798
			$tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
799
		}
800
801
		return $tags;
802
	}
803
804
	/**
805
	 * Enqueue Photon helper script
806
	 *
807
	 * @uses wp_enqueue_script, plugins_url
808
	 * @action wp_enqueue_script
809
	 * @return null
810
	 */
811
	public function action_wp_enqueue_scripts() {
812
		wp_enqueue_script( 'jetpack-photon', plugins_url( 'modules/photon/photon.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), 20130122, true );
813
	}
814
}
815