Completed
Push — fix/fatal-ad-block ( 03f2b3 )
by Jeremy
07:14
created

class.photon.php (5 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
use Automattic\Jetpack\Assets;
4
5
class Jetpack_Photon {
6
	/**
7
	 * Class variables
8
	 */
9
	// Oh look, a singleton
10
	private static $__instance = null;
11
12
	// Allowed extensions must match https://code.trac.wordpress.org/browser/photon/index.php#L31
13
	protected static $extensions = array(
14
		'gif',
15
		'jpg',
16
		'jpeg',
17
		'png'
18
	);
19
20
	// Don't access this directly. Instead, use self::image_sizes() so it's actually populated with something.
21
	protected static $image_sizes = null;
22
23
	/**
24
	 * Singleton implementation
25
	 *
26
	 * @return object
27
	 */
28
	public static function instance() {
29
		if ( ! is_a( self::$__instance, 'Jetpack_Photon' ) ) {
30
			self::$__instance = new Jetpack_Photon;
31
			self::$__instance->setup();
32
		}
33
34
		return self::$__instance;
35
	}
36
37
	/**
38
	 * Silence is golden.
39
	 */
40
	private function __construct() {}
41
42
	/**
43
	 * Register actions and filters, but only if basic Photon functions are available.
44
	 * The basic functions are found in ./functions.photon.php.
45
	 *
46
	 * @uses add_action, add_filter
47
	 * @return null
48
	 */
49
	private function setup() {
50
		if ( ! function_exists( 'jetpack_photon_url' ) ) {
51
			return;
52
		}
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
		add_filter( 'widget_media_image_instance', array( __CLASS__, 'filter_the_image_widget' ), 999999 );
58
59
		// Core image retrieval
60
		add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
61
		add_filter( 'rest_request_before_callbacks', array( $this, 'should_rest_photon_image_downsize' ), 10, 3 );
62
		add_filter( 'rest_request_after_callbacks', array( $this, 'cleanup_rest_photon_image_downsize' ) );
63
64
		// Responsive image srcset substitution
65
		add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 10, 5 );
66
		add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still easily filter.
67
68
		// Helpers for maniuplated images
69
		add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
70
71
		/**
72
		 * Allow Photon to disable uploaded images resizing and use its own resize capabilities instead.
73
		 *
74
		 * @module photon
75
		 *
76
		 * @since 7.1.0
77
		 *
78
		 * @param bool false Should Photon enable noresize mode. Default to false.
79
		 */
80
		if ( apply_filters( 'jetpack_photon_noresize_mode', false ) ) {
81
			$this->enable_noresize_mode();
82
		}
83
	}
84
85
	/**
86
	 * Enables the noresize mode for Photon, allowing to avoid intermediate size files generation.
87
	 */
88
	private function enable_noresize_mode() {
89
		jetpack_require_lib( 'class.jetpack-photon-image-sizes' );
90
91
		// The main objective of noresize mode is to disable additional resized image versions creation.
92
		// This filter handles removal of additional sizes.
93
		add_filter( 'intermediate_image_sizes_advanced', array( __CLASS__, 'filter_photon_noresize_intermediate_sizes' ) );
94
95
		// Load the noresize srcset solution on priority of 20, allowing other plugins to set sizes earlier.
96
		add_filter( 'wp_get_attachment_metadata', array( __CLASS__, 'filter_photon_norezise_maybe_inject_sizes' ), 20, 2 );
97
98
		// Photonize thumbnail URLs in the API response.
99
		add_filter( 'rest_api_thumbnail_size_urls', array( __CLASS__, 'filter_photon_noresize_thumbnail_urls' ) );
100
101
		// This allows to assign the Photon domain to images that normally use the home URL as base.
102
		add_filter( 'jetpack_photon_domain', array( __CLASS__, 'filter_photon_norezise_domain' ), 10, 2 );
103
104
		add_filter( 'the_content', array( __CLASS__, 'filter_content_add' ), 0 );
105
106
		// Jetpack hooks in at six nines (999999) so this filter does at seven.
107
		add_filter( 'the_content', array( __CLASS__, 'filter_content_remove' ), 9999999 );
108
109
		// Regular Photon operation mode filter doesn't run when is_admin(), so we need an additional filter.
110
		// This is temporary until Jetpack allows more easily running these filters for is_admin().
111
		if ( is_admin() ) {
112
			add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 5, 3 );
113
114
			// Allows any image that gets passed to Photon to be resized via Photon.
115
			add_filter( 'jetpack_photon_admin_allow_image_downsize', '__return_true' );
116
		}
117
	}
118
119
	/**
120
	 * This is our catch-all to strip dimensions from intermediate images in content.
121
	 * Since this primarily only impacts post_content we do a little dance to add the filter early
122
	 * to `the_content` and then remove it later on in the same hook.
123
	 *
124
	 * @param String $content the post content.
125
	 * @return String the post content unchanged.
126
	 */
127
	public static function filter_content_add( $content ) {
128
		add_filter( 'jetpack_photon_pre_image_url', array( __CLASS__, 'strip_image_dimensions_maybe' ) );
129
		return $content;
130
	}
131
132
	/**
133
	 * Removing the content filter that was set previously.
134
	 *
135
	 * @param String $content the post content.
136
	 * @return String the post content unchanged.
137
	 */
138
	public static function filter_content_remove( $content ) {
139
		remove_filter( 'jetpack_photon_pre_image_url', array( __CLASS__, 'strip_image_dimensions_maybe' ) );
140
		return $content;
141
	}
142
143
	/**
144
	 * Short circuits the Photon filter to enable Photon processing for any URL.
145
	 *
146
	 * @param String $photon_url a proposed Photon URL for the media file.
147
	 * @param String $image_url the original media URL.
148
	 * @return String an URL to be used for the media file.
149
	 */
150
	public static function filter_photon_norezise_domain( $photon_url, $image_url ) {
151
		return $photon_url;
152
	}
153
154
	/**
155
	 * Disables intermediate sizes to disallow resizing.
156
	 *
157
	 * @param Array $sizes an array containing image sizes.
158
	 * @return Boolean
159
	 */
160
	public static function filter_photon_noresize_intermediate_sizes( $sizes ) {
161
		return array();
162
	}
163
164
	public static function filter_photon_noresize_thumbnail_urls( $sizes ) {
165
		foreach ( $sizes as $size => $url ) {
166
			$parts = explode( '?', $url );
167
			$arguments = isset( $parts[1] ) ? $parts[1] : array();
168
169
			$sizes[ $size ] = jetpack_photon_url( $url, wp_parse_args( $arguments ) );
170
		}
171
172
		return $sizes;
173
	}
174
175
	/**
176
	 * Inject image sizes to attachment metadata.
177
	 *
178
	 * @param array $data          Attachment metadata.
179
	 * @param int   $attachment_id Attachment's post ID.
180
	 *
181
	 * @return array Attachment metadata.
182
	 */
183
	public static function filter_photon_norezise_maybe_inject_sizes( $data, $attachment_id ) {
184
		// Can't do much if data is empty.
185
		if ( empty( $data ) ) {
186
			return $data;
187
		}
188
		$sizes_already_exist = (
189
			true === is_array( $data )
190
			&& true === array_key_exists( 'sizes', $data )
191
			&& true === is_array( $data['sizes'] )
192
			&& false === empty( $data['sizes'] )
193
		);
194
		if ( $sizes_already_exist ) {
195
			return $data;
196
		}
197
		// Missing some critical data we need to determine sizes, not processing.
198
		if ( ! isset( $data['file'] )
199
			|| ! isset( $data['width'] )
200
			|| ! isset( $data['height'] )
201
		) {
202
			return $data;
203
		}
204
205
		$mime_type           = get_post_mime_type( $attachment_id );
206
		$attachment_is_image = preg_match( '!^image/!', $mime_type );
207
208
		if ( 1 === $attachment_is_image ) {
209
			$image_sizes   = new Jetpack_Photon_ImageSizes( $attachment_id, $data );
210
			$data['sizes'] = $image_sizes->generate_sizes_meta();
211
		}
212
		return $data;
213
	}
214
215
	/**
216
	 * Inject image sizes to Jetpack REST API responses. This wraps the filter_photon_norezise_maybe_inject_sizes function.
217
	 *
218
	 * @param array $data          Attachment sizes data.
0 ignored issues
show
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
219
	 * @param int   $attachment_id Attachment's post ID.
220
	 *
221
	 * @return array Attachment sizes array.
222
	 */
223
	public static function filter_photon_norezise_maybe_inject_sizes_api( $sizes, $attachment_id ) {
224
		return self::filter_photon_norezise_maybe_inject_sizes( wp_get_attachment_metadata( $attachment_id ), $attachment_id );
225
	}
226
227
	/**
228
	 ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS
229
	 **/
230
231
	/**
232
	 * Match all images and any relevant <a> tags in a block of HTML.
233
	 *
234
	 * @param string $content Some HTML.
235
	 * @return array An array of $images matches, where $images[0] is
236
	 *         an array of full matches, and the link_url, img_tag,
237
	 *         and img_url keys are arrays of those matches.
238
	 */
239
	public static function parse_images_from_html( $content ) {
240
		$images = array();
241
242
		if ( preg_match_all( '#(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag><(?:img|amp-img|amp-anim)[^>]*?\s+?src=["|\'](?P<img_url>[^\s]+?)["|\'].*?>){1}(?:\s*</a>)?#is', $content, $images ) ) {
243
			foreach ( $images as $key => $unused ) {
244
				// Simplify the output as much as possible, mostly for confirming test results.
245
				if ( is_numeric( $key ) && $key > 0 )
246
					unset( $images[$key] );
247
			}
248
249
			return $images;
250
		}
251
252
		return array();
253
	}
254
255
	/**
256
	 * Try to determine height and width from strings WP appends to resized image filenames.
257
	 *
258
	 * @param string $src The image URL.
259
	 * @return array An array consisting of width and height.
260
	 */
261
	public static function parse_dimensions_from_filename( $src ) {
262
		$width_height_string = array();
263
264
		if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . '){1}$#i', $src, $width_height_string ) ) {
265
			$width = (int) $width_height_string[1];
266
			$height = (int) $width_height_string[2];
267
268
			if ( $width && $height )
269
				return array( $width, $height );
270
		}
271
272
		return array( false, false );
273
	}
274
275
	/**
276
	 * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon.
277
	 *
278
	 * @param string $content
279
	 * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url
280
	 * @filter the_content
281
	 * @return string
282
	 */
283
	public static function filter_the_content( $content ) {
284
		$images = Jetpack_Photon::parse_images_from_html( $content );
285
286
		if ( ! empty( $images ) ) {
287
			$content_width = Jetpack::get_content_width();
288
289
			$image_sizes = self::image_sizes();
290
291
			$upload_dir = wp_get_upload_dir();
292
293
			foreach ( $images[0] as $index => $tag ) {
294
				// Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained
295
				$transform = 'resize';
296
297
				// Start with a clean attachment ID each time
298
				$attachment_id = false;
299
300
				// Flag if we need to munge a fullsize URL
301
				$fullsize_url = false;
302
303
				// Identify image source
304
				$src = $src_orig = $images['img_url'][ $index ];
305
306
				/**
307
				 * Allow specific images to be skipped by Photon.
308
				 *
309
				 * @module photon
310
				 *
311
				 * @since 2.0.3
312
				 *
313
				 * @param bool false Should Photon ignore this image. Default to false.
314
				 * @param string $src Image URL.
315
				 * @param string $tag Image Tag (Image HTML output).
316
				 */
317
				if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) )
318
					continue;
319
320
				// Support Automattic's Lazy Load plugin
321
				// Can't modify $tag yet as we need unadulterated version later
322
				if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
323
					$placeholder_src = $placeholder_src_orig = $src;
324
					$src = $src_orig = $lazy_load_src[1];
325
				} elseif ( preg_match( '#data-lazy-original=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
326
					$placeholder_src = $placeholder_src_orig = $src;
327
					$src = $src_orig = $lazy_load_src[1];
328
				}
329
330
				// Check if image URL should be used with Photon
331
				if ( self::validate_image_url( $src ) ) {
332
					// Find the width and height attributes
333
					$width = $height = false;
334
335
					// First, check the image tag
336
					if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) )
337
						$width = $width_string[1];
338
339
					if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) )
340
						$height = $height_string[1];
341
342
					// Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout.
343
					if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) )
344
						$width = $height = false;
345
346
					// Detect WP registered image size from HTML class
347
					if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
348
						$size = array_pop( $size );
349
350
						if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) {
351
							$width = (int) $image_sizes[ $size ]['width'];
352
							$height = (int) $image_sizes[ $size ]['height'];
353
							$transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
354
						}
355
					} else {
356
						unset( $size );
357
					}
358
359
					// WP Attachment ID, if uploaded to this site
360
					if (
361
						preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) &&
362
						0 === strpos( $src, $upload_dir['baseurl'] ) &&
363
						/**
364
						 * Filter whether an image using an attachment ID in its class has to be uploaded to the local site to go through Photon.
365
						 *
366
						 * @module photon
367
						 *
368
						 * @since 2.0.3
369
						 *
370
						 * @param bool false Was the image uploaded to the local site. Default to false.
371
						 * @param array $args {
372
						 * 	 Array of image details.
373
						 *
374
						 * 	 @type $src Image URL.
375
						 * 	 @type tag Image tag (Image HTML output).
376
						 * 	 @type $images Array of information about the image.
377
						 * 	 @type $index Image index.
378
						 * }
379
						 */
380
						apply_filters( 'jetpack_photon_image_is_local', false, compact( 'src', 'tag', 'images', 'index' ) )
381
					) {
382
						$attachment_id = intval( array_pop( $attachment_id ) );
383
384
						if ( $attachment_id ) {
385
							$attachment = get_post( $attachment_id );
386
387
							// Basic check on returned post object
388
							if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) {
389
								$src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' );
390
391
								if ( self::validate_image_url( $src_per_wp[0] ) ) {
392
									$src = $src_per_wp[0];
393
									$fullsize_url = true;
394
395
									// Prevent image distortion if a detected dimension exceeds the image's natural dimensions
396
									if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
397
										$width = false === $width ? false : min( $width, $src_per_wp[1] );
398
										$height = false === $height ? false : min( $height, $src_per_wp[2] );
399
									}
400
401
									// If no width and height are found, max out at source image's natural dimensions
402
									// Otherwise, respect registered image sizes' cropping setting
403
									if ( false === $width && false === $height ) {
404
										$width = $src_per_wp[1];
405
										$height = $src_per_wp[2];
406
										$transform = 'fit';
407
									} elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
408
										$transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
409
									}
410
								}
411
							} else {
412
								unset( $attachment_id );
413
								unset( $attachment );
414
							}
415
						}
416
					}
417
418
					// If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames.
419
					if ( false === $width && false === $height ) {
420
						list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src );
421
					}
422
423
					$width_orig     = $width;
424
					$height_orig    = $height;
425
					$transform_orig = $transform;
426
427
					// If width is available, constrain to $content_width
428
					if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) {
429
						if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) {
430
							$height = round( ( $content_width * $height ) / $width );
431
							$width = $content_width;
432
						} elseif ( $width > $content_width ) {
433
							$width = $content_width;
434
						}
435
					}
436
437
					// Set a width if none is found and $content_width is available
438
					// If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing
439
					if ( false === $width && is_numeric( $content_width ) ) {
440
						$width = (int) $content_width;
441
442
						if ( false !== $height )
443
							$transform = 'fit';
444
					}
445
446
					// Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
447
					if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . '){1}$#i', basename( $src ), $filename ) )
448
						$fullsize_url = true;
449
450
					// Build URL, first maybe removing WP's resized string so we pass the original image to Photon
451
					if ( ! $fullsize_url && 0 === strpos( $src, $upload_dir['baseurl'] ) ) {
452
						$src = self::strip_image_dimensions_maybe( $src );
453
					}
454
455
					// Build array of Photon args and expose to filter before passing to Photon URL function
456
					$args = array();
457
458
					if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) )
459
						$args[ $transform ] = $width . ',' . $height;
460
					elseif ( false !== $width )
461
						$args['w'] = $width;
462
					elseif ( false !== $height )
463
						$args['h'] = $height;
464
465
					/**
466
					 * Filter the array of Photon arguments added to an image when it goes through Photon.
467
					 * By default, only includes width and height values.
468
					 * @see https://developer.wordpress.com/docs/photon/api/
469
					 *
470
					 * @module photon
471
					 *
472
					 * @since 2.0.0
473
					 *
474
					 * @param array $args Array of Photon Arguments.
475
					 * @param array $details {
476
					 *     Array of image details.
477
					 *
478
					 *     @type string    $tag            Image tag (Image HTML output).
479
					 *     @type string    $src            Image URL.
480
					 *     @type string    $src_orig       Original Image URL.
481
					 *     @type int|false $width          Image width.
482
					 *     @type int|false $height         Image height.
483
					 *     @type int|false $width_orig     Original image width before constrained by content_width.
484
					 *     @type int|false $height_orig    Original Image height before constrained by content_width.
485
					 *     @type string    $transform      Transform.
486
					 *     @type string    $transform_orig Original transform before constrained by content_width.
487
					 * }
488
					 */
489
					$args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height', 'width_orig', 'height_orig', 'transform', 'transform_orig' ) );
490
491
					$photon_url = jetpack_photon_url( $src, $args );
492
493
					// Modify image tag if Photon function provides a URL
494
					// 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.
495
					if ( $src != $photon_url ) {
496
						$new_tag = $tag;
497
498
						// If present, replace the link href with a Photoned URL for the full-size image.
499
						if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) )
500
							$new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
501
502
						// Supplant the original source value with our Photon URL
503
						$photon_url = esc_url( $photon_url );
504
						$new_tag = str_replace( $src_orig, $photon_url, $new_tag );
505
506
						// If Lazy Load is in use, pass placeholder image through Photon
507
						if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) {
508
							$placeholder_src = jetpack_photon_url( $placeholder_src );
509
510
							if ( $placeholder_src != $placeholder_src_orig )
511
								$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...
512
513
							unset( $placeholder_src );
514
						}
515
516
						// If we are not transforming the image with resize, fit, or letterbox (lb), then we should remove
517
						// the width and height arguments from the image to prevent distortion. Even if $args['w'] and $args['h']
518
						// are present, Photon does not crop to those dimensions. Instead, it appears to favor height.
519
						//
520
						// If we are transforming the image via one of those methods, let's update the width and height attributes.
521
						if ( empty( $args['resize'] ) && empty( $args['fit'] ) && empty( $args['lb'] ) ) {
522
							$new_tag = preg_replace( '#(?<=\s)(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag );
523
						} else {
524
							$resize_args = isset( $args['resize'] ) ? $args['resize'] : false;
525 View Code Duplication
							if ( false == $resize_args ) {
526
								$resize_args = ( ! $resize_args && isset( $args['fit'] ) )
527
									? $args['fit']
528
									: false;
529
							}
530 View Code Duplication
							if ( false == $resize_args ) {
531
								$resize_args = ( ! $resize_args && isset( $args['lb'] ) )
532
									? $args['lb']
533
									: false;
534
							}
535
536
							$resize_args = array_map( 'trim', explode( ',', $resize_args ) );
537
538
							// (?<=\s)         - Ensure width or height attribute is preceded by a space
539
							// (width=["|\']?) - Matches, and captures, width=, width=", or width='
540
							// [\d%]+          - Matches 1 or more digits
541
							// (["|\']?)       - Matches, and captures, ", ', or empty string
542
							// \s              - Ensures there's a space after the attribute
543
							$new_tag = preg_replace( '#(?<=\s)(width=["|\']?)[\d%]+(["|\']?)\s?#i', sprintf( '${1}%d${2} ', $resize_args[0] ), $new_tag );
544
							$new_tag = preg_replace( '#(?<=\s)(height=["|\']?)[\d%]+(["|\']?)\s?#i', sprintf( '${1}%d${2} ', $resize_args[1] ), $new_tag );
545
						}
546
547
						// Tag an image for dimension checking
548
						if ( ! self::is_amp_endpoint() ) {
549
							$new_tag = preg_replace( '#(\s?/)?>(\s*</a>)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag );
550
						}
551
552
						// Replace original tag with modified version
553
						$content = str_replace( $tag, $new_tag, $content );
554
					}
555
				} elseif ( preg_match( '#^http(s)?://i[\d]{1}.wp.com#', $src ) && ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) {
556
					$new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $tag, 1 );
557
558
					$content = str_replace( $tag, $new_tag, $content );
559
				}
560
			}
561
		}
562
563
		return $content;
564
	}
565
566
	public static function filter_the_galleries( $galleries ) {
567
		if ( empty( $galleries ) || ! is_array( $galleries ) ) {
568
			return $galleries;
569
		}
570
571
		// Pass by reference, so we can modify them in place.
572
		foreach ( $galleries as &$this_gallery ) {
573
			if ( is_string( $this_gallery ) ) {
574
				$this_gallery = self::filter_the_content( $this_gallery );
575
		// LEAVING COMMENTED OUT as for the moment it doesn't seem
576
		// necessary and I'm not sure how it would propagate through.
577
		//	} elseif ( is_array( $this_gallery )
578
		//	           && ! empty( $this_gallery['src'] )
579
		//	           && ! empty( $this_gallery['type'] )
580
		//	           && in_array( $this_gallery['type'], array( 'rectangle', 'square', 'circle' ) ) ) {
581
		//		$this_gallery['src'] = array_map( 'jetpack_photon_url', $this_gallery['src'] );
582
			}
583
		}
584
		unset( $this_gallery ); // break the reference.
585
586
		return $galleries;
587
	}
588
589
590
	/**
591
	 * Runs the image widget through photon.
592
	 *
593
	 * @param array $instance Image widget instance data.
594
	 * @return array
595
	 */
596
	public static function filter_the_image_widget( $instance ) {
597
		if ( Jetpack::is_module_active( 'photon' ) && ! $instance['attachment_id'] && $instance['url'] ) {
598
			jetpack_photon_url( $instance['url'], array(
599
				'w' => $instance['width'],
600
				'h' => $instance['height'],
601
			) );
602
		}
603
604
		return $instance;
605
	}
606
607
	/**
608
	 ** CORE IMAGE RETRIEVAL
609
	 **/
610
611
	/**
612
	 * Filter post thumbnail image retrieval, passing images through Photon
613
	 *
614
	 * @param string|bool $image
615
	 * @param int $attachment_id
616
	 * @param string|array $size
617
	 * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url
618
	 * @filter image_downsize
619
	 * @return string|bool
620
	 */
621
	public function filter_image_downsize( $image, $attachment_id, $size ) {
622
		// Don't foul up the admin side of things, unless a plugin wants to.
623
		if ( is_admin() &&
624
			/**
625
			 * Provide plugins a way of running Photon for images in the WordPress Dashboard (wp-admin).
626
			 *
627
			 * 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.
628
			 *
629
			 * @module photon
630
			 *
631
			 * @since 4.8.0
632
			 *
633
			 * @param bool false Stop Photon from being run on the Dashboard. Default to false.
634
			 * @param array $args {
635
			 * 	 Array of image details.
636
			 *
637
			 * 	 @type $image Image URL.
638
			 * 	 @type $attachment_id Attachment ID of the image.
639
			 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an array of width and height.
640
			 * }
641
			 */
642
			false === apply_filters( 'jetpack_photon_admin_allow_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
643
		) {
644
			return $image;
645
		}
646
647
		/**
648
		 * Provide plugins a way of preventing Photon from being applied to images retrieved from WordPress Core.
649
		 *
650
		 * @module photon
651
		 *
652
		 * @since 2.0.0
653
		 *
654
		 * @param bool false Stop Photon from being applied to the image. Default to false.
655
		 * @param array $args {
656
		 * 	 Array of image details.
657
		 *
658
		 * 	 @type $image Image URL.
659
		 * 	 @type $attachment_id Attachment ID of the image.
660
		 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an array of width and height.
661
		 * }
662
		 */
663
		if ( apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) ) {
664
			return $image;
665
		}
666
667
		// Get the image URL and proceed with Photon-ification if successful
668
		$image_url = wp_get_attachment_url( $attachment_id );
669
670
		// Set this to true later when we know we have size meta.
671
		$has_size_meta = false;
672
673
		if ( $image_url ) {
674
			// Check if image URL should be used with Photon
675
			if ( ! self::validate_image_url( $image_url ) ) {
676
				return $image;
677
			}
678
679
			$intermediate = true; // For the fourth array item returned by the image_downsize filter.
680
681
			// If an image is requested with a size known to WordPress, use that size's settings with Photon.
682
			// WP states that `add_image_size()` should use a string for the name, but doesn't enforce that.
683
			// Due to differences in how Core and Photon check for the registered image size, we check both types.
684
			if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
685
				$image_args = self::image_sizes();
686
				$image_args = $image_args[ $size ];
687
688
				$photon_args = array();
689
690
				$image_meta = image_get_intermediate_size( $attachment_id, $size );
691
692
				// 'full' is a special case: We need consistent data regardless of the requested size.
693
				if ( 'full' == $size ) {
694
					$image_meta = wp_get_attachment_metadata( $attachment_id );
695
					$intermediate = false;
696
				} elseif ( ! $image_meta ) {
697
					// If we still don't have any image meta at this point, it's probably from a custom thumbnail size
698
					// for an image that was uploaded before the custom image was added to the theme.  Try to determine the size manually.
699
					$image_meta = wp_get_attachment_metadata( $attachment_id );
700
701
					if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
702
						$image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
703
						if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
704
							$image_meta['width'] = $image_resized[6];
705
							$image_meta['height'] = $image_resized[7];
706
						}
707
					}
708
				}
709
710
				if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
711
					$image_args['width']  = $image_meta['width'];
712
					$image_args['height'] = $image_meta['height'];
713
714
					list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
715
					$has_size_meta = true;
716
				}
717
718
				// Expose determined arguments to a filter before passing to Photon
719
				$transform = $image_args['crop'] ? 'resize' : 'fit';
720
721
				// Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
722
				if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
723
					if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) {
724
						$photon_args['h'] = $image_args['height'];
725
					} elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) {
726
						$photon_args['w'] = $image_args['width'];
727
					}
728
				} else {
729
					if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) {
730
						if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
731
							// Lets make sure that we don't upscale images since wp never upscales them as well
732
							$smaller_width  = ( ( $image_meta['width']  < $image_args['width']  ) ? $image_meta['width']  : $image_args['width']  );
733
							$smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
734
735
							$photon_args[ $transform ] = $smaller_width . ',' . $smaller_height;
736
						}
737
					} else {
738
						$photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
739
					}
740
741
				}
742
743
744
				/**
745
				 * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string.
746
				 * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
747
				 *
748
				 * @module photon
749
				 *
750
				 * @since 2.0.0
751
				 *
752
				 * @param array $photon_args Array of Photon arguments.
753
				 * @param array $args {
754
				 * 	 Array of image details.
755
				 *
756
				 * 	 @type $image_args Array of Image arguments (width, height, crop).
757
				 * 	 @type $image_url Image URL.
758
				 * 	 @type $attachment_id Attachment ID of the image.
759
				 * 	 @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
760
				 * 	 @type $transform Value can be resize or fit.
761
				 *                    @see https://developer.wordpress.com/docs/photon/api
762
				 * }
763
				 */
764
				$photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
765
766
				// Generate Photon URL
767
				$image = array(
768
					jetpack_photon_url( $image_url, $photon_args ),
769
					$has_size_meta ? $image_args['width'] : false,
770
					$has_size_meta ? $image_args['height'] : false,
771
					$intermediate
772
				);
773
			} elseif ( is_array( $size ) ) {
774
				// Pull width and height values from the provided array, if possible
775
				$width = isset( $size[0] ) ? (int) $size[0] : false;
776
				$height = isset( $size[1] ) ? (int) $size[1] : false;
777
778
				// Don't bother if necessary parameters aren't passed.
779
				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...
780
					return $image;
781
				}
782
783
				$image_meta = wp_get_attachment_metadata( $attachment_id );
784
				if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
785
					$image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height );
786
787
					if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
788
						$width = $image_resized[6];
789
						$height = $image_resized[7];
790
					} else {
791
						$width = $image_meta['width'];
792
						$height = $image_meta['height'];
793
					}
794
795
					$has_size_meta = true;
796
				}
797
798
				list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
799
800
				// Expose arguments to a filter before passing to Photon
801
				$photon_args = array(
802
					'fit' => $width . ',' . $height
803
				);
804
805
				/**
806
				 * Filter the Photon Arguments added to an image when going through Photon,
807
				 * when the image size is an array of height and width values.
808
				 *
809
				 * @module photon
810
				 *
811
				 * @since 2.0.0
812
				 *
813
				 * @param array $photon_args Array of Photon arguments.
814
				 * @param array $args {
815
				 * 	 Array of image details.
816
				 *
817
				 * 	 @type $width Image width.
818
				 * 	 @type height Image height.
819
				 * 	 @type $image_url Image URL.
820
				 * 	 @type $attachment_id Attachment ID of the image.
821
				 * }
822
				 */
823
				$photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
824
825
				// Generate Photon URL
826
				$image = array(
827
					jetpack_photon_url( $image_url, $photon_args ),
828
					$has_size_meta ? $width : false,
829
					$has_size_meta ? $height : false,
830
					$intermediate
831
				);
832
			}
833
		}
834
835
		return $image;
836
	}
837
838
	/**
839
	 * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent.
840
	 *
841
	 * @since 3.8.0
842
	 * @since 4.0.4 Added automatically additional sizes beyond declared image sizes.
843
	 * @param array $sources An array of image urls and widths.
844
	 * @uses self::validate_image_url, jetpack_photon_url, Jetpack_Photon::parse_from_filename
845
	 * @uses Jetpack_Photon::strip_image_dimensions_maybe, Jetpack::get_content_width
846
	 * @return array An array of Photon image urls and widths.
847
	 */
848
	public function filter_srcset_array( $sources = array(), $size_array = array(), $image_src = array(), $image_meta = array(), $attachment_id = 0 ) {
849
		if ( ! is_array( $sources ) ) {
850
			return $sources;
851
		}
852
		$upload_dir = wp_get_upload_dir();
853
854
		foreach ( $sources as $i => $source ) {
855
			if ( ! self::validate_image_url( $source['url'] ) ) {
856
				continue;
857
			}
858
859
			/** This filter is already documented in class.photon.php */
860
			if ( apply_filters( 'jetpack_photon_skip_image', false, $source['url'], $source ) ) {
861
				continue;
862
			}
863
864
			$url = $source['url'];
865
			list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $url );
866
867
			// It's quicker to get the full size with the data we have already, if available
868
			if ( ! empty( $attachment_id ) ) {
869
				$url = wp_get_attachment_url( $attachment_id );
870
			} else {
871
				$url = Jetpack_Photon::strip_image_dimensions_maybe( $url );
872
			}
873
874
			$args = array();
875
			if ( 'w' === $source['descriptor'] ) {
876
				if ( $height && ( $source['value'] == $width ) ) {
877
					$args['resize'] = $width . ',' . $height;
878
				} else {
879
					$args['w'] = $source['value'];
880
				}
881
882
			}
883
884
			$sources[ $i ]['url'] = jetpack_photon_url( $url, $args );
885
		}
886
887
		/**
888
		 * At this point, $sources is the original srcset with Photonized URLs.
889
		 * Now, we're going to construct additional sizes based on multiples of the content_width.
890
		 * This will reduce the gap between the largest defined size and the original image.
891
		 */
892
893
		/**
894
		 * Filter the multiplier Photon uses to create new srcset items.
895
		 * Return false to short-circuit and bypass auto-generation.
896
		 *
897
		 * @module photon
898
		 *
899
		 * @since 4.0.4
900
		 *
901
		 * @param array|bool $multipliers Array of multipliers to use or false to bypass.
902
		 */
903
		$multipliers = apply_filters( 'jetpack_photon_srcset_multipliers', array( 2, 3 ) );
904
		$url         = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
905
906
		if (
907
			/** Short-circuit via jetpack_photon_srcset_multipliers filter. */
908
			is_array( $multipliers )
909
			/** This filter is already documented in class.photon.php */
910
			&& ! apply_filters( 'jetpack_photon_skip_image', false, $url, null )
911
			/** Verify basic meta is intact. */
912
			&& isset( $image_meta['width'] ) && isset( $image_meta['height'] ) && isset( $image_meta['file'] )
913
			/** Verify we have the requested width/height. */
914
			&& isset( $size_array[0] ) && isset( $size_array[1] )
915
			) {
916
917
			$fullwidth  = $image_meta['width'];
918
			$fullheight = $image_meta['height'];
919
			$reqwidth   = $size_array[0];
920
			$reqheight  = $size_array[1];
921
922
			$constrained_size = wp_constrain_dimensions( $fullwidth, $fullheight, $reqwidth );
923
			$expected_size = array( $reqwidth, $reqheight );
924
925
			if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) {
926
				$crop = 'soft';
927
				$base = Jetpack::get_content_width() ? Jetpack::get_content_width() : 1000; // Provide a default width if none set by the theme.
928
			} else {
929
				$crop = 'hard';
930
				$base = $reqwidth;
931
			}
932
933
934
			$currentwidths = array_keys( $sources );
935
			$newsources = null;
936
937
			foreach ( $multipliers as $multiplier ) {
938
939
				$newwidth = $base * $multiplier;
940
				foreach ( $currentwidths as $currentwidth ){
941
					// If a new width would be within 100 pixes of an existing one or larger than the full size image, skip.
942
					if ( abs( $currentwidth - $newwidth ) < 50 || ( $newwidth > $fullwidth ) ) {
943
						continue 2; // Back to the foreach ( $multipliers as $multiplier )
944
					}
945
				} // foreach ( $currentwidths as $currentwidth ){
946
947
				if ( 'soft' == $crop ) {
948
					$args = array(
949
						'w' => $newwidth,
950
					);
951
				} else { // hard crop, e.g. add_image_size( 'example', 200, 200, true );
952
					$args = array(
953
						'zoom'   => $multiplier,
954
						'resize' => $reqwidth . ',' . $reqheight,
955
					);
956
				}
957
958
				$newsources[ $newwidth ] = array(
959
					'url'         => jetpack_photon_url( $url, $args ),
960
					'descriptor'  => 'w',
961
					'value'       => $newwidth,
962
					);
963
			} // foreach ( $multipliers as $multiplier )
964
			if ( is_array( $newsources ) ) {
965
				if ( function_exists( 'array_replace' ) ) { // PHP 5.3+, preferred
966
					// phpcs:disable
967
					$sources = array_replace( $sources, $newsources );
968
					// phpcs:enable
969
				} else { // For PHP 5.2 using WP shim function
970
					$sources = array_replace_recursive( $sources, $newsources );
971
				}
972
			}
973
		} // if ( isset( $image_meta['width'] ) && isset( $image_meta['file'] ) )
974
975
		return $sources;
976
	}
977
978
	/**
979
	 * Filters an array of image `sizes` values, using $content_width instead of image's full size.
980
	 *
981
	 * @since 4.0.4
982
	 * @since 4.1.0 Returns early for images not within the_content.
983
	 * @param array $sizes An array of media query breakpoints.
984
	 * @param array $size  Width and height of the image
985
	 * @uses Jetpack::get_content_width
986
	 * @return array An array of media query breakpoints.
987
	 */
988
	public function filter_sizes( $sizes, $size ) {
989
		if ( ! doing_filter( 'the_content' ) ){
990
			return $sizes;
991
		}
992
		$content_width = Jetpack::get_content_width();
993
		if ( ! $content_width ) {
994
			$content_width = 1000;
995
		}
996
997
		if ( ( is_array( $size ) && $size[0] < $content_width ) ) {
998
			return $sizes;
999
		}
1000
1001
		return sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $content_width );
1002
	}
1003
1004
	/**
1005
	 ** GENERAL FUNCTIONS
1006
	 **/
1007
1008
	/**
1009
	 * Ensure image URL is valid for Photon.
1010
	 * 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.
1011
	 *
1012
	 * @param string $url
1013
	 * @uses wp_parse_args
1014
	 * @return bool
1015
	 */
1016
	protected static function validate_image_url( $url ) {
1017
		$parsed_url = @parse_url( $url );
1018
1019
		if ( ! $parsed_url )
1020
			return false;
1021
1022
		// Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
1023
		$url_info = wp_parse_args( $parsed_url, array(
1024
			'scheme' => null,
1025
			'host'   => null,
1026
			'port'   => null,
1027
			'path'   => null
1028
		) );
1029
1030
		// Bail if scheme isn't http or port is set that isn't port 80
1031
		if (
1032
			( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) &&
1033
			/**
1034
			 * Allow Photon to fetch images that are served via HTTPS.
1035
			 *
1036
			 * @module photon
1037
			 *
1038
			 * @since 2.4.0
1039
			 * @since 3.9.0 Default to false.
1040
			 *
1041
			 * @param bool $reject_https Should Photon ignore images using the HTTPS scheme. Default to false.
1042
			 */
1043
			apply_filters( 'jetpack_photon_reject_https', false )
1044
		) {
1045
			return false;
1046
		}
1047
1048
		// Bail if no host is found
1049
		if ( is_null( $url_info['host'] ) )
1050
			return false;
1051
1052
		// Bail if the image alredy went through Photon
1053
		if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
1054
			return false;
1055
1056
		// Bail if no path is found
1057
		if ( is_null( $url_info['path'] ) )
1058
			return false;
1059
1060
		// Ensure image extension is acceptable
1061
		if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
1062
			return false;
1063
1064
		// If we got this far, we should have an acceptable image URL
1065
		// But let folks filter to decline if they prefer.
1066
		/**
1067
		 * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon.
1068
		 *
1069
		 * @module photon
1070
		 *
1071
		 * @since 3.0.0
1072
		 *
1073
		 * @param bool true Is the image URL valid and can it be used by Photon. Default to true.
1074
		 * @param string $url Image URL.
1075
		 * @param array $parsed_url Array of information about the image.
1076
		 */
1077
		return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url );
1078
	}
1079
1080
	/**
1081
	 * Checks if the file exists before it passes the file to photon
1082
	 *
1083
	 * @param string $src The image URL
1084
	 * @return string
1085
	 **/
1086
	public static function strip_image_dimensions_maybe( $src ){
1087
		$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...
1088
1089
		// Build URL, first removing WP's resized string so we pass the original image to Photon
1090
		if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) ) {
1091
			$stripped_src = str_replace( $src_parts[1], '', $src );
1092
			$upload_dir = wp_get_upload_dir();
1093
1094
			// Extracts the file path to the image minus the base url
1095
			$file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) );
1096
1097
			if( file_exists( $upload_dir["basedir"] . $file_path ) )
1098
				$src = $stripped_src;
1099
		}
1100
1101
		return $src;
1102
	}
1103
1104
	/**
1105
	 * Provide an array of available image sizes and corresponding dimensions.
1106
	 * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
1107
	 *
1108
	 * @global $wp_additional_image_sizes
1109
	 * @uses get_option
1110
	 * @return array
1111
	 */
1112
	protected static function image_sizes() {
1113
		if ( null == self::$image_sizes ) {
1114
			global $_wp_additional_image_sizes;
1115
1116
			// Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
1117
			$images = array(
1118
				'thumb'  => array(
1119
					'width'  => intval( get_option( 'thumbnail_size_w' ) ),
1120
					'height' => intval( get_option( 'thumbnail_size_h' ) ),
1121
					'crop'   => (bool) get_option( 'thumbnail_crop' )
1122
				),
1123
				'medium' => array(
1124
					'width'  => intval( get_option( 'medium_size_w' ) ),
1125
					'height' => intval( get_option( 'medium_size_h' ) ),
1126
					'crop'   => false
1127
				),
1128
				'medium_large' => array(
1129
						'width'  => intval( get_option( 'medium_large_size_w' ) ),
1130
						'height' => intval( get_option( 'medium_large_size_h' ) ),
1131
						'crop'   => false
1132
				),
1133
				'large'  => array(
1134
					'width'  => intval( get_option( 'large_size_w' ) ),
1135
					'height' => intval( get_option( 'large_size_h' ) ),
1136
					'crop'   => false
1137
				),
1138
				'full'   => array(
1139
					'width'  => null,
1140
					'height' => null,
1141
					'crop'   => false
1142
				)
1143
			);
1144
1145
			// Compatibility mapping as found in wp-includes/media.php
1146
			$images['thumbnail'] = $images['thumb'];
1147
1148
			// Update class variable, merging in $_wp_additional_image_sizes if any are set
1149
			if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
1150
				self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
1151
			else
1152
				self::$image_sizes = $images;
1153
		}
1154
1155
		return is_array( self::$image_sizes ) ? self::$image_sizes : array();
1156
	}
1157
1158
	/**
1159
	 * Pass og:image URLs through Photon
1160
	 *
1161
	 * @param array $tags
1162
	 * @param array $parameters
1163
	 * @uses jetpack_photon_url
1164
	 * @return array
1165
	 */
1166
	function filter_open_graph_tags( $tags, $parameters ) {
1167
		if ( empty( $tags['og:image'] ) ) {
1168
			return $tags;
1169
		}
1170
1171
		$photon_args = array(
1172
			'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
1173
		);
1174
1175
		if ( is_array( $tags['og:image'] ) ) {
1176
			$images = array();
1177
			foreach ( $tags['og:image'] as $image ) {
1178
				$images[] = jetpack_photon_url( $image, $photon_args );
1179
			}
1180
			$tags['og:image'] = $images;
1181
		} else {
1182
			$tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
1183
		}
1184
1185
		return $tags;
1186
	}
1187
1188
	public function noresize_intermediate_sizes( $sizes ) {
1189
		return __return_empty_array();
1190
	}
1191
1192
	/**
1193
	 * Enqueue Photon helper script
1194
	 *
1195
	 * @uses wp_enqueue_script, plugins_url
1196
	 * @action wp_enqueue_script
1197
	 * @return null
1198
	 */
1199
	public function action_wp_enqueue_scripts() {
1200
		if ( self::is_amp_endpoint() ) {
1201
			return;
1202
		}
1203
		wp_enqueue_script(
1204
			'jetpack-photon',
1205
			Assets::get_file_url_for_environment(
1206
				'_inc/build/photon/photon.min.js',
1207
				'modules/photon/photon.js'
1208
			),
1209
			array( 'wp-dom-ready' ),
1210
			20190901,
1211
			true
1212
		);
1213
	}
1214
1215
	/**
1216
	 * Determine if image_downsize should utilize Photon via REST API.
1217
	 *
1218
	 * The WordPress Block Editor (Gutenberg) and other REST API consumers using the wp/v2/media endpoint, especially in the "edit"
1219
	 * context is more akin to the is_admin usage of Photon (see filter_image_downsize). Since consumers are trying to edit content in posts,
1220
	 * Photon should not fire as it will fire later on display. By aborting an attempt to Photonize an image here, we
1221
	 * prevents issues like https://github.com/Automattic/jetpack/issues/10580 .
1222
	 *
1223
	 * To determine if we're using the wp/v2/media endpoint, we hook onto the `rest_request_before_callbacks` filter and
1224
	 * if determined we are using it in the edit context, we'll false out the `jetpack_photon_override_image_downsize` filter.
1225
	 *
1226
	 * @see Jetpack_Photon::filter_image_downsize()
1227
	 *
1228
	 * @param null|WP_Error   $response
1229
	 * @param array           $endpoint_data
1230
	 * @param WP_REST_Request $request  Request used to generate the response.
1231
	 *
1232
	 * @return null|WP_Error The original response object without modification.
1233
	 */
1234
	public function should_rest_photon_image_downsize( $response, $endpoint_data, $request ) {
1235
		if ( ! is_a( $request , 'WP_REST_Request' ) ) {
1236
			return $response; // Something odd is happening. Do nothing and return the response.
1237
		}
1238
1239
		if ( is_wp_error( $response ) ) {
1240
			// If we're going to return an error, we don't need to do anything with Photon.
1241
			return $response;
1242
		}
1243
1244
		$route = $request->get_route();
1245
1246
		if ( false !== strpos( $route, 'wp/v2/media' ) && 'edit' === $request['context'] ) {
1247
			// Don't use `__return_true()`: Use something unique. See ::_override_image_downsize_in_rest_edit_context()
1248
			// Late execution to avoid conflict with other plugins as we really don't want to run in this situation.
1249
			add_filter( 'jetpack_photon_override_image_downsize', array( $this, '_override_image_downsize_in_rest_edit_context' ), 999999 );
1250
		}
1251
1252
		return $response;
1253
1254
	}
1255
1256
	/**
1257
	 * Remove the override we may have added in ::should_rest_photon_image_downsize()
1258
	 * Since ::_override_image_downsize_in_rest_edit_context() is only
1259
	 * every used here, we can always remove it without ever worrying
1260
	 * about breaking any other configuration.
1261
	 *
1262
	 * @param mixed $response
1263
	 * @return mixed Unchanged $response
1264
	 */
1265
	public function cleanup_rest_photon_image_downsize( $response ) {
1266
		remove_filter( 'jetpack_photon_override_image_downsize', array( $this, '_override_image_downsize_in_rest_edit_context' ), 999999 );
1267
		return $response;
1268
	}
1269
1270
	/**
1271
	 * Used internally by ::should_rest_photon_image_downsize() to not photonize
1272
	 * image URLs in ?context=edit REST requests.
1273
	 * MUST NOT be used anywhere else.
1274
	 * We use a unique function instead of __return_true so that we can clean up
1275
	 * after ourselves without breaking anyone else's filters.
1276
	 *
1277
	 * @internal
1278
	 * @return true
1279
	 */
1280
	public function _override_image_downsize_in_rest_edit_context() {
1281
		return true;
1282
	}
1283
1284
	/**
1285
	 * Return whether the current page is AMP.
1286
	 *
1287
	 * This is only present for the sake of WordPress.com where the Jetpack_AMP_Support
1288
	 * class does not yet exist. This mehod may only be called at the wp action or later.
1289
	 *
1290
	 * @return bool Whether AMP page.
1291
	 */
1292
	private static function is_amp_endpoint() {
1293
		return class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request();
1294
	}
1295
}
1296