Completed
Push — branch-6.3-built ( 8655db...179047 )
by
unknown
10:38 queued 51s
created

class.photon.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
		add_filter( 'widget_media_image_instance', array( __CLASS__, 'filter_the_image_widget' ), 999999 );
55
56
		// Core image retrieval
57
		add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
58
59
		// Responsive image srcset substitution
60
		add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 10, 5 );
61
		add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still easily filter.
62
63
		// Helpers for maniuplated images
64
		add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
65
	}
66
67
	/**
68
	 ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS
69
	 **/
70
71
	/**
72
	 * Match all images and any relevant <a> tags in a block of HTML.
73
	 *
74
	 * @param string $content Some HTML.
75
	 * @return array An array of $images matches, where $images[0] is
76
	 *         an array of full matches, and the link_url, img_tag,
77
	 *         and img_url keys are arrays of those matches.
78
	 */
79
	public static function parse_images_from_html( $content ) {
80
		$images = array();
81
82
		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 ) ) {
83
			foreach ( $images as $key => $unused ) {
84
				// Simplify the output as much as possible, mostly for confirming test results.
85
				if ( is_numeric( $key ) && $key > 0 )
86
					unset( $images[$key] );
87
			}
88
89
			return $images;
90
		}
91
92
		return array();
93
	}
94
95
	/**
96
	 * Try to determine height and width from strings WP appends to resized image filenames.
97
	 *
98
	 * @param string $src The image URL.
99
	 * @return array An array consisting of width and height.
100
	 */
101
	public static function parse_dimensions_from_filename( $src ) {
102
		$width_height_string = array();
103
104
		if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . '){1}$#i', $src, $width_height_string ) ) {
105
			$width = (int) $width_height_string[1];
106
			$height = (int) $width_height_string[2];
107
108
			if ( $width && $height )
109
				return array( $width, $height );
110
		}
111
112
		return array( false, false );
113
	}
114
115
	/**
116
	 * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon.
117
	 *
118
	 * @param string $content
119
	 * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url
120
	 * @filter the_content
121
	 * @return string
122
	 */
123
	public static function filter_the_content( $content ) {
124
		$images = Jetpack_Photon::parse_images_from_html( $content );
125
126
		if ( ! empty( $images ) ) {
127
			$content_width = Jetpack::get_content_width();
128
129
			$image_sizes = self::image_sizes();
130
			$upload_dir = wp_get_upload_dir();
131
132
			foreach ( $images[0] as $index => $tag ) {
133
				// Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained
134
				$transform = 'resize';
135
136
				// Start with a clean attachment ID each time
137
				$attachment_id = false;
138
139
				// Flag if we need to munge a fullsize URL
140
				$fullsize_url = false;
141
142
				// Identify image source
143
				$src = $src_orig = $images['img_url'][ $index ];
144
145
				/**
146
				 * Allow specific images to be skipped by Photon.
147
				 *
148
				 * @module photon
149
				 *
150
				 * @since 2.0.3
151
				 *
152
				 * @param bool false Should Photon ignore this image. Default to false.
153
				 * @param string $src Image URL.
154
				 * @param string $tag Image Tag (Image HTML output).
155
				 */
156
				if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) )
157
					continue;
158
159
				// Support Automattic's Lazy Load plugin
160
				// Can't modify $tag yet as we need unadulterated version later
161
				if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
162
					$placeholder_src = $placeholder_src_orig = $src;
163
					$src = $src_orig = $lazy_load_src[1];
164
				} elseif ( preg_match( '#data-lazy-original=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
165
					$placeholder_src = $placeholder_src_orig = $src;
166
					$src = $src_orig = $lazy_load_src[1];
167
				}
168
169
				// Check if image URL should be used with Photon
170
				if ( self::validate_image_url( $src ) ) {
171
					// Find the width and height attributes
172
					$width = $height = false;
173
174
					// First, check the image tag
175
					if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) )
176
						$width = $width_string[1];
177
178
					if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) )
179
						$height = $height_string[1];
180
181
					// Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout.
182
					if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) )
183
						$width = $height = false;
184
185
					// Detect WP registered image size from HTML class
186
					if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
187
						$size = array_pop( $size );
188
189
						if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) {
190
							$width = (int) $image_sizes[ $size ]['width'];
191
							$height = (int) $image_sizes[ $size ]['height'];
192
							$transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
193
						}
194
					} else {
195
						unset( $size );
196
					}
197
198
					// WP Attachment ID, if uploaded to this site
199
					if (
200
						preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) &&
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
						$attachment_id = intval( array_pop( $attachment_id ) );
222
223
						if ( $attachment_id ) {
224
							$attachment = get_post( $attachment_id );
225
226
							// Basic check on returned post object
227
							if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) {
228
								$src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' );
229
230
								if ( self::validate_image_url( $src_per_wp[0] ) ) {
231
									$src = $src_per_wp[0];
232
									$fullsize_url = true;
233
234
									// Prevent image distortion if a detected dimension exceeds the image's natural dimensions
235
									if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
236
										$width = false === $width ? false : min( $width, $src_per_wp[1] );
237
										$height = false === $height ? false : min( $height, $src_per_wp[2] );
238
									}
239
240
									// If no width and height are found, max out at source image's natural dimensions
241
									// Otherwise, respect registered image sizes' cropping setting
242
									if ( false === $width && false === $height ) {
243
										$width = $src_per_wp[1];
244
										$height = $src_per_wp[2];
245
										$transform = 'fit';
246
									} elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
247
										$transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
248
									}
249
								}
250
							} else {
251
								unset( $attachment_id );
252
								unset( $attachment );
253
							}
254
						}
255
					}
256
257
					// If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames.
258
					if ( false === $width && false === $height ) {
259
						list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src );
260
					}
261
262
					// If width is available, constrain to $content_width
263
					if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) {
264
						if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) {
265
							$height = round( ( $content_width * $height ) / $width );
266
							$width = $content_width;
267
						} elseif ( $width > $content_width ) {
268
							$width = $content_width;
269
						}
270
					}
271
272
					// Set a width if none is found and $content_width is available
273
					// If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing
274
					if ( false === $width && is_numeric( $content_width ) ) {
275
						$width = (int) $content_width;
276
277
						if ( false !== $height )
278
							$transform = 'fit';
279
					}
280
281
					// Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
282
					if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . '){1}$#i', basename( $src ), $filename ) )
283
						$fullsize_url = true;
284
285
					// Build URL, first maybe removing WP's resized string so we pass the original image to Photon
286
					if ( ! $fullsize_url ) {
287
						$src = self::strip_image_dimensions_maybe( $src );
288
					}
289
290
					// Build array of Photon args and expose to filter before passing to Photon URL function
291
					$args = array();
292
293
					if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) )
294
						$args[ $transform ] = $width . ',' . $height;
295
					elseif ( false !== $width )
296
						$args['w'] = $width;
297
					elseif ( false !== $height )
298
						$args['h'] = $height;
299
300
					/**
301
					 * Filter the array of Photon arguments added to an image when it goes through Photon.
302
					 * By default, only includes width and height values.
303
					 * @see https://developer.wordpress.com/docs/photon/api/
304
					 *
305
					 * @module photon
306
					 *
307
					 * @since 2.0.0
308
					 *
309
					 * @param array $args Array of Photon Arguments.
310
					 * @param array $args {
311
					 * 	 Array of image details.
312
					 *
313
					 * 	 @type $tag Image tag (Image HTML output).
314
					 * 	 @type $src Image URL.
315
					 * 	 @type $src_orig Original Image URL.
316
					 * 	 @type $width Image width.
317
					 * 	 @type $height Image height.
318
					 * }
319
					 */
320
					$args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
321
322
					$photon_url = jetpack_photon_url( $src, $args );
323
324
					// Modify image tag if Photon function provides a URL
325
					// 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.
326
					if ( $src != $photon_url ) {
327
						$new_tag = $tag;
328
329
						// If present, replace the link href with a Photoned URL for the full-size image.
330
						if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) )
331
							$new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
332
333
						// Supplant the original source value with our Photon URL
334
						$photon_url = esc_url( $photon_url );
335
						$new_tag = str_replace( $src_orig, $photon_url, $new_tag );
336
337
						// If Lazy Load is in use, pass placeholder image through Photon
338
						if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) {
339
							$placeholder_src = jetpack_photon_url( $placeholder_src );
340
341
							if ( $placeholder_src != $placeholder_src_orig )
342
								$new_tag = str_replace( $placeholder_src_orig, esc_url( $placeholder_src ), $new_tag );
0 ignored issues
show
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...
343
344
							unset( $placeholder_src );
345
						}
346
347
						// If we are not transforming the image with resize, fit, or letterbox (lb), then we should remove
348
						// the width and height arguments from the image to prevent distortion. Even if $args['w'] and $args['h']
349
						// are present, Photon does not crop to those dimensions. Instead, it appears to favor height.
350
						//
351
						// If we are transforming the image via one of those methods, let's update the width and height attributes.
352
						if ( empty( $args['resize'] ) && empty( $args['fit'] ) && empty( $args['lb'] ) ) {
353
							$new_tag = preg_replace( '#(?<=\s)(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag );
354
						} else {
355
							$resize_args = isset( $args['resize'] ) ? $args['resize'] : false;
356 View Code Duplication
							if ( false == $resize_args ) {
357
								$resize_args = ( ! $resize_args && isset( $args['fit'] ) )
358
									? $args['fit']
359
									: false;
360
							}
361 View Code Duplication
							if ( false == $resize_args ) {
362
								$resize_args = ( ! $resize_args && isset( $args['lb'] ) )
363
									? $args['lb']
364
									: false;
365
							}
366
367
							$resize_args = array_map( 'trim', explode( ',', $resize_args ) );
368
369
							// (?<=\s)         - Ensure width or height attribute is preceded by a space
370
							// (width=["|\']?) - Matches, and captures, width=, width=", or width='
371
							// [\d%]+          - Matches 1 or more digits
372
							// (["|\']?)       - Matches, and captures, ", ', or empty string
373
							// \s              - Ensures there's a space after the attribute
374
							$new_tag = preg_replace( '#(?<=\s)(width=["|\']?)[\d%]+(["|\']?)\s?#i', sprintf( '${1}%d${2} ', $resize_args[0] ), $new_tag );
375
							$new_tag = preg_replace( '#(?<=\s)(height=["|\']?)[\d%]+(["|\']?)\s?#i', sprintf( '${1}%d${2} ', $resize_args[1] ), $new_tag );
376
						}
377
378
						// Tag an image for dimension checking
379
						$new_tag = preg_replace( '#(\s?/)?>(\s*</a>)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag );
380
381
						// Replace original tag with modified version
382
						$content = str_replace( $tag, $new_tag, $content );
383
					}
384
				} elseif ( preg_match( '#^http(s)?://i[\d]{1}.wp.com#', $src ) && ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) {
385
					$new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $tag, 1 );
386
387
					$content = str_replace( $tag, $new_tag, $content );
388
				}
389
			}
390
		}
391
392
		return $content;
393
	}
394
395
	public static function filter_the_galleries( $galleries ) {
396
		if ( empty( $galleries ) || ! is_array( $galleries ) ) {
397
			return $galleries;
398
		}
399
400
		// Pass by reference, so we can modify them in place.
401
		foreach ( $galleries as &$this_gallery ) {
402
			if ( is_string( $this_gallery ) ) {
403
				$this_gallery = self::filter_the_content( $this_gallery );
404
		// LEAVING COMMENTED OUT as for the moment it doesn't seem
405
		// necessary and I'm not sure how it would propagate through.
406
		//	} elseif ( is_array( $this_gallery )
407
		//	           && ! empty( $this_gallery['src'] )
408
		//	           && ! empty( $this_gallery['type'] )
409
		//	           && in_array( $this_gallery['type'], array( 'rectangle', 'square', 'circle' ) ) ) {
410
		//		$this_gallery['src'] = array_map( 'jetpack_photon_url', $this_gallery['src'] );
411
			}
412
		}
413
		unset( $this_gallery ); // break the reference.
414
415
		return $galleries;
416
	}
417
418
419
	/**
420
	 * Runs the image widget through photon.
421
	 *
422
	 * @param array $instance Image widget instance data.
423
	 * @return array
424
	 */
425
	public static function filter_the_image_widget( $instance ) {
426
		if ( Jetpack::is_module_active( 'photon' ) && ! $instance['attachment_id'] && $instance['url'] ) {
427
			jetpack_photon_url( $instance['url'], array(
428
				'w' => $instance['width'],
429
				'h' => $instance['height'],
430
			) );
431
		}
432
433
		return $instance;
434
	}
435
436
	/**
437
	 ** CORE IMAGE RETRIEVAL
438
	 **/
439
440
	/**
441
	 * Filter post thumbnail image retrieval, passing images through Photon
442
	 *
443
	 * @param string|bool $image
444
	 * @param int $attachment_id
445
	 * @param string|array $size
446
	 * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url
447
	 * @filter image_downsize
448
	 * @return string|bool
449
	 */
450
	public function filter_image_downsize( $image, $attachment_id, $size ) {
451
		// Don't foul up the admin side of things, unless a plugin wants to.
452
		if ( is_admin() &&
453
			/**
454
			 * Provide plugins a way of running Photon for images in the WordPress Dashboard (wp-admin).
455
			 *
456
			 * Note: enabling this will result in Photon URLs added to your post content, which could make migrations across domains (and off Photon) a bit more challenging.
457
			 *
458
			 * @module photon
459
			 *
460
			 * @since 4.8.0
461
			 *
462
			 * @param bool false Stop Photon from being run on the Dashboard. Default to false.
463
			 * @param array $args {
464
			 * 	 Array of image details.
465
			 *
466
			 * 	 @type $image Image URL.
467
			 * 	 @type $attachment_id Attachment ID of the image.
468
			 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an array of width and height.
469
			 * }
470
			 */
471
			false === apply_filters( 'jetpack_photon_admin_allow_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
472
		) {
473
			return $image;
474
		}
475
476
		/**
477
		 * Provide plugins a way of preventing Photon from being applied to images retrieved from WordPress Core.
478
		 *
479
		 * @module photon
480
		 *
481
		 * @since 2.0.0
482
		 *
483
		 * @param bool false Stop Photon from being applied to the image. Default to false.
484
		 * @param array $args {
485
		 * 	 Array of image details.
486
		 *
487
		 * 	 @type $image Image URL.
488
		 * 	 @type $attachment_id Attachment ID of the image.
489
		 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an array of width and height.
490
		 * }
491
		 */
492
		if ( apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) ) {
493
			return $image;
494
		}
495
496
		// Get the image URL and proceed with Photon-ification if successful
497
		$image_url = wp_get_attachment_url( $attachment_id );
498
499
		// Set this to true later when we know we have size meta.
500
		$has_size_meta = false;
501
502
		if ( $image_url ) {
503
			// Check if image URL should be used with Photon
504
			if ( ! self::validate_image_url( $image_url ) )
505
				return $image;
506
507
			$intermediate = true; // For the fourth array item returned by the image_downsize filter.
508
509
			// If an image is requested with a size known to WordPress, use that size's settings with Photon.
510
			// WP states that `add_image_size()` should use a string for the name, but doesn't enforce that.
511
			// Due to differences in how Core and Photon check for the registered image size, we check both types.
512
			if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
513
				$image_args = self::image_sizes();
514
				$image_args = $image_args[ $size ];
515
516
				$photon_args = array();
517
518
				$image_meta = image_get_intermediate_size( $attachment_id, $size );
519
520
				// 'full' is a special case: We need consistent data regardless of the requested size.
521
				if ( 'full' == $size ) {
522
					$image_meta = wp_get_attachment_metadata( $attachment_id );
523
					$intermediate = false;
524
				} elseif ( ! $image_meta ) {
525
					// If we still don't have any image meta at this point, it's probably from a custom thumbnail size
526
					// for an image that was uploaded before the custom image was added to the theme.  Try to determine the size manually.
527
					$image_meta = wp_get_attachment_metadata( $attachment_id );
528
529
					if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
530
						$image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
531
						if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
532
							$image_meta['width'] = $image_resized[6];
533
							$image_meta['height'] = $image_resized[7];
534
						}
535
					}
536
				}
537
538
				if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
539
					$image_args['width']  = $image_meta['width'];
540
					$image_args['height'] = $image_meta['height'];
541
542
					list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
543
					$has_size_meta = true;
544
				}
545
546
				// Expose determined arguments to a filter before passing to Photon
547
				$transform = $image_args['crop'] ? 'resize' : 'fit';
548
549
				// Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
550
				if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
551
					if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) {
552
						$photon_args['h'] = $image_args['height'];
553
					} elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) {
554
						$photon_args['w'] = $image_args['width'];
555
					}
556
				} else {
557
					if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) {
558
						if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
559
							// Lets make sure that we don't upscale images since wp never upscales them as well
560
							$smaller_width  = ( ( $image_meta['width']  < $image_args['width']  ) ? $image_meta['width']  : $image_args['width']  );
561
							$smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
562
563
							$photon_args[ $transform ] = $smaller_width . ',' . $smaller_height;
564
						}
565
					} else {
566
						$photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
567
					}
568
569
				}
570
571
572
				/**
573
				 * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string.
574
				 * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
575
				 *
576
				 * @module photon
577
				 *
578
				 * @since 2.0.0
579
				 *
580
				 * @param array $photon_args Array of Photon arguments.
581
				 * @param array $args {
582
				 * 	 Array of image details.
583
				 *
584
				 * 	 @type $image_args Array of Image arguments (width, height, crop).
585
				 * 	 @type $image_url Image URL.
586
				 * 	 @type $attachment_id Attachment ID of the image.
587
				 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
588
				 * 	 @type $transform Value can be resize or fit.
589
				 *                    @see https://developer.wordpress.com/docs/photon/api
590
				 * }
591
				 */
592
				$photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
593
594
				// Generate Photon URL
595
				$image = array(
596
					jetpack_photon_url( $image_url, $photon_args ),
597
					$has_size_meta ? $image_args['width'] : false,
598
					$has_size_meta ? $image_args['height'] : false,
599
					$intermediate
600
				);
601
			} elseif ( is_array( $size ) ) {
602
				// Pull width and height values from the provided array, if possible
603
				$width = isset( $size[0] ) ? (int) $size[0] : false;
604
				$height = isset( $size[1] ) ? (int) $size[1] : false;
605
606
				// Don't bother if necessary parameters aren't passed.
607
				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...
608
					return $image;
609
				}
610
611
				$image_meta = wp_get_attachment_metadata( $attachment_id );
612
				if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
613
					$image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height );
614
615
					if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
616
						$width = $image_resized[6];
617
						$height = $image_resized[7];
618
					} else {
619
						$width = $image_meta['width'];
620
						$height = $image_meta['height'];
621
					}
622
623
					$has_size_meta = true;
624
				}
625
626
				list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
627
628
				// Expose arguments to a filter before passing to Photon
629
				$photon_args = array(
630
					'fit' => $width . ',' . $height
631
				);
632
633
				/**
634
				 * Filter the Photon Arguments added to an image when going through Photon,
635
				 * when the image size is an array of height and width values.
636
				 *
637
				 * @module photon
638
				 *
639
				 * @since 2.0.0
640
				 *
641
				 * @param array $photon_args Array of Photon arguments.
642
				 * @param array $args {
643
				 * 	 Array of image details.
644
				 *
645
				 * 	 @type $width Image width.
646
				 * 	 @type height Image height.
647
				 * 	 @type $image_url Image URL.
648
				 * 	 @type $attachment_id Attachment ID of the image.
649
				 * }
650
				 */
651
				$photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
652
653
				// Generate Photon URL
654
				$image = array(
655
					jetpack_photon_url( $image_url, $photon_args ),
656
					$has_size_meta ? $width : false,
657
					$has_size_meta ? $height : false,
658
					$intermediate
659
				);
660
			}
661
		}
662
663
		return $image;
664
	}
665
666
	/**
667
	 * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent.
668
	 *
669
	 * @since 3.8.0
670
	 * @since 4.0.4 Added automatically additional sizes beyond declared image sizes.
671
	 * @param array $sources An array of image urls and widths.
672
	 * @uses self::validate_image_url, jetpack_photon_url, Jetpack_Photon::parse_from_filename
673
	 * @uses Jetpack_Photon::strip_image_dimensions_maybe, Jetpack::get_content_width
674
	 * @return array An array of Photon image urls and widths.
675
	 */
676
	public function filter_srcset_array( $sources = array(), $size_array = array(), $image_src = array(), $image_meta = array(), $attachment_id = 0 ) {
677
		if ( ! is_array( $sources ) ) {
678
			return $sources;
679
		}
680
		$upload_dir = wp_get_upload_dir();
681
682
		foreach ( $sources as $i => $source ) {
683
			if ( ! self::validate_image_url( $source['url'] ) ) {
684
				continue;
685
			}
686
687
			/** This filter is already documented in class.photon.php */
688
			if ( apply_filters( 'jetpack_photon_skip_image', false, $source['url'], $source ) ) {
689
				continue;
690
			}
691
692
			$url = $source['url'];
693
			list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $url );
694
695
			// It's quicker to get the full size with the data we have already, if available
696
			if ( ! empty( $attachment_id ) ) {
697
				$url = wp_get_attachment_url( $attachment_id );
698
			} else {
699
				$url = Jetpack_Photon::strip_image_dimensions_maybe( $url );
700
			}
701
702
			$args = array();
703
			if ( 'w' === $source['descriptor'] ) {
704
				if ( $height && ( $source['value'] == $width ) ) {
705
					$args['resize'] = $width . ',' . $height;
706
				} else {
707
					$args['w'] = $source['value'];
708
				}
709
710
			}
711
712
			$sources[ $i ]['url'] = jetpack_photon_url( $url, $args );
713
		}
714
715
		/**
716
		 * At this point, $sources is the original srcset with Photonized URLs.
717
		 * Now, we're going to construct additional sizes based on multiples of the content_width.
718
		 * This will reduce the gap between the largest defined size and the original image.
719
		 */
720
721
		/**
722
		 * Filter the multiplier Photon uses to create new srcset items.
723
		 * Return false to short-circuit and bypass auto-generation.
724
		 *
725
		 * @module photon
726
		 *
727
		 * @since 4.0.4
728
		 *
729
		 * @param array|bool $multipliers Array of multipliers to use or false to bypass.
730
		 */
731
		$multipliers = apply_filters( 'jetpack_photon_srcset_multipliers', array( 2, 3 ) );
732
		$url         = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
733
734
		if (
735
			/** Short-circuit via jetpack_photon_srcset_multipliers filter. */
736
			is_array( $multipliers )
737
			/** This filter is already documented in class.photon.php */
738
			&& ! apply_filters( 'jetpack_photon_skip_image', false, $url, null )
739
			/** Verify basic meta is intact. */
740
			&& isset( $image_meta['width'] ) && isset( $image_meta['height'] ) && isset( $image_meta['file'] )
741
			/** Verify we have the requested width/height. */
742
			&& isset( $size_array[0] ) && isset( $size_array[1] )
743
			) {
744
745
			$fullwidth  = $image_meta['width'];
746
			$fullheight = $image_meta['height'];
747
			$reqwidth   = $size_array[0];
748
			$reqheight  = $size_array[1];
749
750
			$constrained_size = wp_constrain_dimensions( $fullwidth, $fullheight, $reqwidth );
751
			$expected_size = array( $reqwidth, $reqheight );
752
753
			if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) {
754
				$crop = 'soft';
755
				$base = Jetpack::get_content_width() ? Jetpack::get_content_width() : 1000; // Provide a default width if none set by the theme.
756
			} else {
757
				$crop = 'hard';
758
				$base = $reqwidth;
759
			}
760
761
762
			$currentwidths = array_keys( $sources );
763
			$newsources = null;
764
765
			foreach ( $multipliers as $multiplier ) {
766
767
				$newwidth = $base * $multiplier;
768
				foreach ( $currentwidths as $currentwidth ){
769
					// If a new width would be within 100 pixes of an existing one or larger than the full size image, skip.
770
					if ( abs( $currentwidth - $newwidth ) < 50 || ( $newwidth > $fullwidth ) ) {
771
						continue 2; // Back to the foreach ( $multipliers as $multiplier )
772
					}
773
				} // foreach ( $currentwidths as $currentwidth ){
774
775
				if ( 'soft' == $crop ) {
776
					$args = array(
777
						'w' => $newwidth,
778
					);
779
				} else { // hard crop, e.g. add_image_size( 'example', 200, 200, true );
780
					$args = array(
781
						'zoom'   => $multiplier,
782
						'resize' => $reqwidth . ',' . $reqheight,
783
					);
784
				}
785
786
				$newsources[ $newwidth ] = array(
787
					'url'         => jetpack_photon_url( $url, $args ),
788
					'descriptor'  => 'w',
789
					'value'       => $newwidth,
790
					);
791
			} // foreach ( $multipliers as $multiplier )
792
			if ( is_array( $newsources ) ) {
793
				if ( function_exists( 'array_replace' ) ) { // PHP 5.3+, preferred
794
					$sources = array_replace( $sources, $newsources );
795
				} else { // For PHP 5.2 using WP shim function
796
					$sources = array_replace_recursive( $sources, $newsources );
797
				}
798
			}
799
		} // if ( isset( $image_meta['width'] ) && isset( $image_meta['file'] ) )
800
801
		return $sources;
802
	}
803
804
	/**
805
	 * Filters an array of image `sizes` values, using $content_width instead of image's full size.
806
	 *
807
	 * @since 4.0.4
808
	 * @since 4.1.0 Returns early for images not within the_content.
809
	 * @param array $sizes An array of media query breakpoints.
810
	 * @param array $size  Width and height of the image
811
	 * @uses Jetpack::get_content_width
812
	 * @return array An array of media query breakpoints.
813
	 */
814
	public function filter_sizes( $sizes, $size ) {
815
		if ( ! doing_filter( 'the_content' ) ){
816
			return $sizes;
817
		}
818
		$content_width = Jetpack::get_content_width();
819
		if ( ! $content_width ) {
820
			$content_width = 1000;
821
		}
822
823
		if ( ( is_array( $size ) && $size[0] < $content_width ) ) {
824
			return $sizes;
825
		}
826
827
		return sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $content_width );
828
	}
829
830
	/**
831
	 ** GENERAL FUNCTIONS
832
	 **/
833
834
	/**
835
	 * Ensure image URL is valid for Photon.
836
	 * 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.
837
	 *
838
	 * @param string $url
839
	 * @uses wp_parse_args
840
	 * @return bool
841
	 */
842
	protected static function validate_image_url( $url ) {
843
		$parsed_url = @parse_url( $url );
844
845
		if ( ! $parsed_url )
846
			return false;
847
848
		// Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
849
		$url_info = wp_parse_args( $parsed_url, array(
850
			'scheme' => null,
851
			'host'   => null,
852
			'port'   => null,
853
			'path'   => null
854
		) );
855
856
		// Bail if scheme isn't http or port is set that isn't port 80
857
		if (
858
			( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) &&
859
			/**
860
			 * Allow Photon to fetch images that are served via HTTPS.
861
			 *
862
			 * @module photon
863
			 *
864
			 * @since 2.4.0
865
			 * @since 3.9.0 Default to false.
866
			 *
867
			 * @param bool $reject_https Should Photon ignore images using the HTTPS scheme. Default to false.
868
			 */
869
			apply_filters( 'jetpack_photon_reject_https', false )
870
		) {
871
			return false;
872
		}
873
874
		// Bail if no host is found
875
		if ( is_null( $url_info['host'] ) )
876
			return false;
877
878
		// Bail if the image alredy went through Photon
879
		if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
880
			return false;
881
882
		// Bail if no path is found
883
		if ( is_null( $url_info['path'] ) )
884
			return false;
885
886
		// Ensure image extension is acceptable
887
		if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
888
			return false;
889
890
		// If we got this far, we should have an acceptable image URL
891
		// But let folks filter to decline if they prefer.
892
		/**
893
		 * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon.
894
		 *
895
		 * @module photon
896
		 *
897
		 * @since 3.0.0
898
		 *
899
		 * @param bool true Is the image URL valid and can it be used by Photon. Default to true.
900
		 * @param string $url Image URL.
901
		 * @param array $parsed_url Array of information about the image.
902
		 */
903
		return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url );
904
	}
905
906
	/**
907
	 * Checks if the file exists before it passes the file to photon
908
	 *
909
	 * @param string $src The image URL
910
	 * @return string
911
	 **/
912
	protected static function strip_image_dimensions_maybe( $src ){
913
		$stripped_src = $src;
0 ignored issues
show
$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...
914
915
		// Build URL, first removing WP's resized string so we pass the original image to Photon
916
		if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) ) {
917
			$stripped_src = str_replace( $src_parts[1], '', $src );
918
			$upload_dir = wp_get_upload_dir();
919
920
			// Extracts the file path to the image minus the base url
921
			$file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) );
922
923
			if( file_exists( $upload_dir["basedir"] . $file_path ) )
924
				$src = $stripped_src;
925
		}
926
927
		return $src;
928
	}
929
930
	/**
931
	 * Provide an array of available image sizes and corresponding dimensions.
932
	 * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
933
	 *
934
	 * @global $wp_additional_image_sizes
935
	 * @uses get_option
936
	 * @return array
937
	 */
938
	protected static function image_sizes() {
939
		if ( null == self::$image_sizes ) {
940
			global $_wp_additional_image_sizes;
941
942
			// Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
943
			$images = array(
944
				'thumb'  => array(
945
					'width'  => intval( get_option( 'thumbnail_size_w' ) ),
946
					'height' => intval( get_option( 'thumbnail_size_h' ) ),
947
					'crop'   => (bool) get_option( 'thumbnail_crop' )
948
				),
949
				'medium' => array(
950
					'width'  => intval( get_option( 'medium_size_w' ) ),
951
					'height' => intval( get_option( 'medium_size_h' ) ),
952
					'crop'   => false
953
				),
954
				'large'  => array(
955
					'width'  => intval( get_option( 'large_size_w' ) ),
956
					'height' => intval( get_option( 'large_size_h' ) ),
957
					'crop'   => false
958
				),
959
				'full'   => array(
960
					'width'  => null,
961
					'height' => null,
962
					'crop'   => false
963
				)
964
			);
965
966
			// Compatibility mapping as found in wp-includes/media.php
967
			$images['thumbnail'] = $images['thumb'];
968
969
			// Update class variable, merging in $_wp_additional_image_sizes if any are set
970
			if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
971
				self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
972
			else
973
				self::$image_sizes = $images;
974
		}
975
976
		return is_array( self::$image_sizes ) ? self::$image_sizes : array();
977
	}
978
979
	/**
980
	 * Pass og:image URLs through Photon
981
	 *
982
	 * @param array $tags
983
	 * @param array $parameters
984
	 * @uses jetpack_photon_url
985
	 * @return array
986
	 */
987
	function filter_open_graph_tags( $tags, $parameters ) {
988
		if ( empty( $tags['og:image'] ) ) {
989
			return $tags;
990
		}
991
992
		$photon_args = array(
993
			'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
994
		);
995
996
		if ( is_array( $tags['og:image'] ) ) {
997
			$images = array();
998
			foreach ( $tags['og:image'] as $image ) {
999
				$images[] = jetpack_photon_url( $image, $photon_args );
1000
			}
1001
			$tags['og:image'] = $images;
1002
		} else {
1003
			$tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
1004
		}
1005
1006
		return $tags;
1007
	}
1008
1009
	/**
1010
	 * Enqueue Photon helper script
1011
	 *
1012
	 * @uses wp_enqueue_script, plugins_url
1013
	 * @action wp_enqueue_script
1014
	 * @return null
1015
	 */
1016 View Code Duplication
	public function action_wp_enqueue_scripts() {
1017
		if ( Jetpack_AMP_Support::is_amp_request() ) {
1018
			return;
1019
		}
1020
		wp_enqueue_script(
1021
			'jetpack-photon',
1022
			Jetpack::get_file_url_for_environment(
1023
				'_inc/build/photon/photon.min.js',
1024
				'modules/photon/photon.js'
1025
			),
1026
			array( 'jquery' ),
1027
			20130122,
1028
			true
1029
		);
1030
	}
1031
}
1032