PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 7.1.3
Jetpack – WP Security, Backup, Speed, & Growth v7.1.3
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.photon.php
jetpack Last commit date
3rd-party 7 years ago _inc 6 years ago bin 7 years ago css 7 years ago extensions 6 years ago images 6 years ago json-endpoints 7 years ago languages 7 years ago modules 5 years ago sal 7 years ago scss 7 years ago sync 7 years ago views 7 years ago .svnignore 12 years ago CODE-OF-CONDUCT.md 9 years ago changelog.txt 7 years ago class.frame-nonce-preview.php 9 years ago class.jetpack-admin.php 7 years ago class.jetpack-affiliate.php 7 years ago class.jetpack-autoupdate.php 8 years ago class.jetpack-bbpress-json-api-compat.php 9 years ago class.jetpack-cli.php 7 years ago class.jetpack-client-server.php 8 years ago class.jetpack-client.php 7 years ago class.jetpack-connection-banner.php 7 years ago class.jetpack-constants.php 8 years ago class.jetpack-data.php 7 years ago class.jetpack-debugger.php 7 years ago class.jetpack-error.php 10 years ago class.jetpack-gutenberg.php 7 years ago class.jetpack-heartbeat.php 7 years ago class.jetpack-idc.php 8 years ago class.jetpack-ixr-client.php 10 years ago class.jetpack-jitm.php 7 years ago class.jetpack-modules-list-table.php 8 years ago class.jetpack-network-sites-list-table.php 9 years ago class.jetpack-network.php 7 years ago class.jetpack-options.php 7 years ago class.jetpack-post-images.php 7 years ago class.jetpack-signature.php 7 years ago class.jetpack-tracks.php 7 years ago class.jetpack-twitter-cards.php 7 years ago class.jetpack-user-agent.php 8 years ago class.jetpack-xmlrpc-server.php 7 years ago class.jetpack.php 7 years ago class.json-api-endpoints.php 7 years ago class.json-api.php 7 years ago class.photon.php 7 years ago composer.json 7 years ago functions.compat.php 7 years ago functions.gallery.php 8 years ago functions.global.php 7 years ago functions.opengraph.php 7 years ago functions.photon.php 7 years ago jetpack.php 5 years ago json-api-config.php 10 years ago json-endpoints.php 7 years ago locales.php 7 years ago readme.txt 5 years ago require-lib.php 7 years ago uninstall.php 8 years ago wpml-config.xml 10 years ago
class.photon.php
1266 lines
1 <?php
2
3 class Jetpack_Photon {
4 /**
5 * Class variables
6 */
7 // Oh look, a singleton
8 private static $__instance = null;
9
10 // Allowed extensions must match http://code.trac.wordpress.org/browser/photon/index.php#L31
11 protected static $extensions = array(
12 'gif',
13 'jpg',
14 'jpeg',
15 'png'
16 );
17
18 // Don't access this directly. Instead, use self::image_sizes() so it's actually populated with something.
19 protected static $image_sizes = null;
20
21 /**
22 * Singleton implementation
23 *
24 * @return object
25 */
26 public static function instance() {
27 if ( ! is_a( self::$__instance, 'Jetpack_Photon' ) ) {
28 self::$__instance = new Jetpack_Photon;
29 self::$__instance->setup();
30 }
31
32 return self::$__instance;
33 }
34
35 /**
36 * Silence is golden.
37 */
38 private function __construct() {}
39
40 /**
41 * Register actions and filters, but only if basic Photon functions are available.
42 * The basic functions are found in ./functions.photon.php.
43 *
44 * @uses add_action, add_filter
45 * @return null
46 */
47 private function setup() {
48 if ( ! function_exists( 'jetpack_photon_url' ) ) {
49 return;
50 }
51
52 // Images in post content and galleries
53 add_filter( 'the_content', array( __CLASS__, 'filter_the_content' ), 999999 );
54 add_filter( 'get_post_galleries', array( __CLASS__, 'filter_the_galleries' ), 999999 );
55 add_filter( 'widget_media_image_instance', array( __CLASS__, 'filter_the_image_widget' ), 999999 );
56
57 // Core image retrieval
58 add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 );
59 add_filter( 'rest_request_before_callbacks', array( $this, 'should_rest_photon_image_downsize' ), 10, 3 );
60 add_filter( 'rest_request_after_callbacks', array( $this, 'cleanup_rest_photon_image_downsize' ) );
61
62 // Responsive image srcset substitution
63 add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ), 10, 5 );
64 add_filter( 'wp_calculate_image_sizes', array( $this, 'filter_sizes' ), 1, 2 ); // Early so themes can still easily filter.
65
66 // Helpers for maniuplated images
67 add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 );
68
69 /**
70 * Allow Photon to disable uploaded images resizing and use its own resize capabilities instead.
71 *
72 * @module photon
73 *
74 * @since 7.1.0
75 *
76 * @param bool false Should Photon enable noresize mode. Default to false.
77 */
78 if ( apply_filters( 'jetpack_photon_noresize_mode', false ) ) {
79 $this->enable_noresize_mode();
80 }
81 }
82
83 /**
84 * Enables the noresize mode for Photon, allowing to avoid intermediate size files generation.
85 */
86 private function enable_noresize_mode() {
87 jetpack_require_lib( 'class.jetpack-photon-image-sizes' );
88
89 // The main objective of noresize mode is to disable additional resized image versions creation.
90 // This filter handles removal of additional sizes.
91 add_filter( 'intermediate_image_sizes_advanced', array( __CLASS__, 'filter_photon_noresize_intermediate_sizes' ) );
92
93 // Load the noresize srcset solution on priority of 20, allowing other plugins to set sizes earlier.
94 add_filter( 'wp_get_attachment_metadata', array( __CLASS__, 'filter_photon_norezise_maybe_inject_sizes' ), 20, 2 );
95
96 // Photonize thumbnail URLs in the API response.
97 add_filter( 'rest_api_thumbnail_size_urls', array( __CLASS__, 'filter_photon_noresize_thumbnail_urls' ) );
98
99 // This allows to assign the Photon domain to images that normally use the home URL as base.
100 add_filter( 'jetpack_photon_domain', array( __CLASS__, 'filter_photon_norezise_domain' ), 10, 2 );
101
102 add_filter( 'the_content', array( __CLASS__, 'filter_content_add' ), 0 );
103
104 // Jetpack hooks in at six nines (999999) so this filter does at seven.
105 add_filter( 'the_content', array( __CLASS__, 'filter_content_remove' ), 9999999 );
106
107 // Regular Photon operation mode filter doesn't run when is_admin(), so we need an additional filter.
108 // This is temporary until Jetpack allows more easily running these filters for is_admin().
109 if ( is_admin() ) {
110 add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 5, 3 );
111
112 // Allows any image that gets passed to Photon to be resized via Photon.
113 add_filter( 'jetpack_photon_admin_allow_image_downsize', '__return_true' );
114 }
115 }
116
117 /**
118 * This is our catch-all to strip dimensions from intermediate images in content.
119 * Since this primarily only impacts post_content we do a little dance to add the filter early
120 * to `the_content` and then remove it later on in the same hook.
121 *
122 * @param String $content the post content.
123 * @return String the post content unchanged.
124 */
125 public static function filter_content_add( $content ) {
126 add_filter( 'jetpack_photon_pre_image_url', array( __CLASS__, 'strip_image_dimensions_maybe' ) );
127 return $content;
128 }
129
130 /**
131 * Removing the content filter that was set previously.
132 *
133 * @param String $content the post content.
134 * @return String the post content unchanged.
135 */
136 public static function filter_content_remove( $content ) {
137 remove_filter( 'jetpack_photon_pre_image_url', array( __CLASS__, 'strip_image_dimensions_maybe' ) );
138 return $content;
139 }
140
141 /**
142 * Short circuits the Photon filter to enable Photon processing for any URL.
143 *
144 * @param String $photon_url a proposed Photon URL for the media file.
145 * @param String $image_url the original media URL.
146 * @return String an URL to be used for the media file.
147 */
148 public static function filter_photon_norezise_domain( $photon_url, $image_url ) {
149 return $photon_url;
150 }
151
152 /**
153 * Disables intermediate sizes to disallow resizing.
154 *
155 * @param Array $sizes an array containing image sizes.
156 * @return Boolean
157 */
158 public static function filter_photon_noresize_intermediate_sizes( $sizes ) {
159 return array();
160 }
161
162 public static function filter_photon_noresize_thumbnail_urls( $sizes ) {
163 foreach ( $sizes as $size => $url ) {
164 $parts = explode( '?', $url );
165 $arguments = isset( $parts[1] ) ? $parts[1] : array();
166
167 $sizes[ $size ] = jetpack_photon_url( $url, wp_parse_args( $arguments ) );
168 }
169
170 return $sizes;
171 }
172
173 /**
174 * Inject image sizes to attachment metadata.
175 *
176 * @param array $data Attachment metadata.
177 * @param int $attachment_id Attachment's post ID.
178 *
179 * @return array Attachment metadata.
180 */
181 public static function filter_photon_norezise_maybe_inject_sizes( $data, $attachment_id ) {
182 // Can't do much if data is empty.
183 if ( empty( $data ) ) {
184 return $data;
185 }
186 $sizes_already_exist = (
187 true === is_array( $data )
188 && true === array_key_exists( 'sizes', $data )
189 && true === is_array( $data['sizes'] )
190 && false === empty( $data['sizes'] )
191 );
192 if ( $sizes_already_exist ) {
193 return $data;
194 }
195 // Missing some critical data we need to determine sizes, not processing.
196 if ( ! isset( $data['file'] )
197 || ! isset( $data['width'] )
198 || ! isset( $data['height'] )
199 ) {
200 return $data;
201 }
202
203 $mime_type = get_post_mime_type( $attachment_id );
204 $attachment_is_image = preg_match( '!^image/!', $mime_type );
205
206 if ( 1 === $attachment_is_image ) {
207 $image_sizes = new Jetpack_Photon_ImageSizes( $attachment_id, $data );
208 $data['sizes'] = $image_sizes->generate_sizes_meta();
209 }
210 return $data;
211 }
212
213 /**
214 * Inject image sizes to Jetpack REST API responses. This wraps the filter_photon_norezise_maybe_inject_sizes function.
215 *
216 * @param array $data Attachment sizes data.
217 * @param int $attachment_id Attachment's post ID.
218 *
219 * @return array Attachment sizes array.
220 */
221 public static function filter_photon_norezise_maybe_inject_sizes_api( $sizes, $attachment_id ) {
222 return self::filter_photon_norezise_maybe_inject_sizes( wp_get_attachment_metadata( $attachment_id ), $attachment_id );
223 }
224
225 /**
226 ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS
227 **/
228
229 /**
230 * Match all images and any relevant <a> tags in a block of HTML.
231 *
232 * @param string $content Some HTML.
233 * @return array An array of $images matches, where $images[0] is
234 * an array of full matches, and the link_url, img_tag,
235 * and img_url keys are arrays of those matches.
236 */
237 public static function parse_images_from_html( $content ) {
238 $images = array();
239
240 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 ) ) {
241 foreach ( $images as $key => $unused ) {
242 // Simplify the output as much as possible, mostly for confirming test results.
243 if ( is_numeric( $key ) && $key > 0 )
244 unset( $images[$key] );
245 }
246
247 return $images;
248 }
249
250 return array();
251 }
252
253 /**
254 * Try to determine height and width from strings WP appends to resized image filenames.
255 *
256 * @param string $src The image URL.
257 * @return array An array consisting of width and height.
258 */
259 public static function parse_dimensions_from_filename( $src ) {
260 $width_height_string = array();
261
262 if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . '){1}$#i', $src, $width_height_string ) ) {
263 $width = (int) $width_height_string[1];
264 $height = (int) $width_height_string[2];
265
266 if ( $width && $height )
267 return array( $width, $height );
268 }
269
270 return array( false, false );
271 }
272
273 /**
274 * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon.
275 *
276 * @param string $content
277 * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url
278 * @filter the_content
279 * @return string
280 */
281 public static function filter_the_content( $content ) {
282 $images = Jetpack_Photon::parse_images_from_html( $content );
283
284 if ( ! empty( $images ) ) {
285 $content_width = Jetpack::get_content_width();
286
287 $image_sizes = self::image_sizes();
288 $upload_dir = wp_get_upload_dir();
289
290 foreach ( $images[0] as $index => $tag ) {
291 // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained
292 $transform = 'resize';
293
294 // Start with a clean attachment ID each time
295 $attachment_id = false;
296
297 // Flag if we need to munge a fullsize URL
298 $fullsize_url = false;
299
300 // Identify image source
301 $src = $src_orig = $images['img_url'][ $index ];
302
303 /**
304 * Allow specific images to be skipped by Photon.
305 *
306 * @module photon
307 *
308 * @since 2.0.3
309 *
310 * @param bool false Should Photon ignore this image. Default to false.
311 * @param string $src Image URL.
312 * @param string $tag Image Tag (Image HTML output).
313 */
314 if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) )
315 continue;
316
317 // Support Automattic's Lazy Load plugin
318 // Can't modify $tag yet as we need unadulterated version later
319 if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
320 $placeholder_src = $placeholder_src_orig = $src;
321 $src = $src_orig = $lazy_load_src[1];
322 } elseif ( preg_match( '#data-lazy-original=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) {
323 $placeholder_src = $placeholder_src_orig = $src;
324 $src = $src_orig = $lazy_load_src[1];
325 }
326
327 // Check if image URL should be used with Photon
328 if ( self::validate_image_url( $src ) ) {
329 // Find the width and height attributes
330 $width = $height = false;
331
332 // First, check the image tag
333 if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) )
334 $width = $width_string[1];
335
336 if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) )
337 $height = $height_string[1];
338
339 // Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout.
340 if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) )
341 $width = $height = false;
342
343 // Detect WP registered image size from HTML class
344 if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) {
345 $size = array_pop( $size );
346
347 if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) {
348 $width = (int) $image_sizes[ $size ]['width'];
349 $height = (int) $image_sizes[ $size ]['height'];
350 $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
351 }
352 } else {
353 unset( $size );
354 }
355
356 // WP Attachment ID, if uploaded to this site
357 if (
358 preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) &&
359 0 === strpos( $src, $upload_dir['baseurl'] ) &&
360 /**
361 * Filter whether an image using an attachment ID in its class has to be uploaded to the local site to go through Photon.
362 *
363 * @module photon
364 *
365 * @since 2.0.3
366 *
367 * @param bool false Was the image uploaded to the local site. Default to false.
368 * @param array $args {
369 * Array of image details.
370 *
371 * @type $src Image URL.
372 * @type tag Image tag (Image HTML output).
373 * @type $images Array of information about the image.
374 * @type $index Image index.
375 * }
376 */
377 apply_filters( 'jetpack_photon_image_is_local', false, compact( 'src', 'tag', 'images', 'index' ) )
378 ) {
379 $attachment_id = intval( array_pop( $attachment_id ) );
380
381 if ( $attachment_id ) {
382 $attachment = get_post( $attachment_id );
383
384 // Basic check on returned post object
385 if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) {
386 $src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' );
387
388 if ( self::validate_image_url( $src_per_wp[0] ) ) {
389 $src = $src_per_wp[0];
390 $fullsize_url = true;
391
392 // Prevent image distortion if a detected dimension exceeds the image's natural dimensions
393 if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) {
394 $width = false === $width ? false : min( $width, $src_per_wp[1] );
395 $height = false === $height ? false : min( $height, $src_per_wp[2] );
396 }
397
398 // If no width and height are found, max out at source image's natural dimensions
399 // Otherwise, respect registered image sizes' cropping setting
400 if ( false === $width && false === $height ) {
401 $width = $src_per_wp[1];
402 $height = $src_per_wp[2];
403 $transform = 'fit';
404 } elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) {
405 $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit';
406 }
407 }
408 } else {
409 unset( $attachment_id );
410 unset( $attachment );
411 }
412 }
413 }
414
415 // If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames.
416 if ( false === $width && false === $height ) {
417 list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src );
418 }
419
420 // If width is available, constrain to $content_width
421 if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) {
422 if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) {
423 $height = round( ( $content_width * $height ) / $width );
424 $width = $content_width;
425 } elseif ( $width > $content_width ) {
426 $width = $content_width;
427 }
428 }
429
430 // Set a width if none is found and $content_width is available
431 // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing
432 if ( false === $width && is_numeric( $content_width ) ) {
433 $width = (int) $content_width;
434
435 if ( false !== $height )
436 $transform = 'fit';
437 }
438
439 // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation.
440 if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . '){1}$#i', basename( $src ), $filename ) )
441 $fullsize_url = true;
442
443 // Build URL, first maybe removing WP's resized string so we pass the original image to Photon
444 if ( ! $fullsize_url ) {
445 $src = self::strip_image_dimensions_maybe( $src );
446 }
447
448 // Build array of Photon args and expose to filter before passing to Photon URL function
449 $args = array();
450
451 if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) )
452 $args[ $transform ] = $width . ',' . $height;
453 elseif ( false !== $width )
454 $args['w'] = $width;
455 elseif ( false !== $height )
456 $args['h'] = $height;
457
458 /**
459 * Filter the array of Photon arguments added to an image when it goes through Photon.
460 * By default, only includes width and height values.
461 * @see https://developer.wordpress.com/docs/photon/api/
462 *
463 * @module photon
464 *
465 * @since 2.0.0
466 *
467 * @param array $args Array of Photon Arguments.
468 * @param array $args {
469 * Array of image details.
470 *
471 * @type $tag Image tag (Image HTML output).
472 * @type $src Image URL.
473 * @type $src_orig Original Image URL.
474 * @type $width Image width.
475 * @type $height Image height.
476 * }
477 */
478 $args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) );
479
480 $photon_url = jetpack_photon_url( $src, $args );
481
482 // Modify image tag if Photon function provides a URL
483 // 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.
484 if ( $src != $photon_url ) {
485 $new_tag = $tag;
486
487 // If present, replace the link href with a Photoned URL for the full-size image.
488 if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) )
489 $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 );
490
491 // Supplant the original source value with our Photon URL
492 $photon_url = esc_url( $photon_url );
493 $new_tag = str_replace( $src_orig, $photon_url, $new_tag );
494
495 // If Lazy Load is in use, pass placeholder image through Photon
496 if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) {
497 $placeholder_src = jetpack_photon_url( $placeholder_src );
498
499 if ( $placeholder_src != $placeholder_src_orig )
500 $new_tag = str_replace( $placeholder_src_orig, esc_url( $placeholder_src ), $new_tag );
501
502 unset( $placeholder_src );
503 }
504
505 // If we are not transforming the image with resize, fit, or letterbox (lb), then we should remove
506 // the width and height arguments from the image to prevent distortion. Even if $args['w'] and $args['h']
507 // are present, Photon does not crop to those dimensions. Instead, it appears to favor height.
508 //
509 // If we are transforming the image via one of those methods, let's update the width and height attributes.
510 if ( empty( $args['resize'] ) && empty( $args['fit'] ) && empty( $args['lb'] ) ) {
511 $new_tag = preg_replace( '#(?<=\s)(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag );
512 } else {
513 $resize_args = isset( $args['resize'] ) ? $args['resize'] : false;
514 if ( false == $resize_args ) {
515 $resize_args = ( ! $resize_args && isset( $args['fit'] ) )
516 ? $args['fit']
517 : false;
518 }
519 if ( false == $resize_args ) {
520 $resize_args = ( ! $resize_args && isset( $args['lb'] ) )
521 ? $args['lb']
522 : false;
523 }
524
525 $resize_args = array_map( 'trim', explode( ',', $resize_args ) );
526
527 // (?<=\s) - Ensure width or height attribute is preceded by a space
528 // (width=["|\']?) - Matches, and captures, width=, width=", or width='
529 // [\d%]+ - Matches 1 or more digits
530 // (["|\']?) - Matches, and captures, ", ', or empty string
531 // \s - Ensures there's a space after the attribute
532 $new_tag = preg_replace( '#(?<=\s)(width=["|\']?)[\d%]+(["|\']?)\s?#i', sprintf( '${1}%d${2} ', $resize_args[0] ), $new_tag );
533 $new_tag = preg_replace( '#(?<=\s)(height=["|\']?)[\d%]+(["|\']?)\s?#i', sprintf( '${1}%d${2} ', $resize_args[1] ), $new_tag );
534 }
535
536 // Tag an image for dimension checking
537 $new_tag = preg_replace( '#(\s?/)?>(\s*</a>)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag );
538
539 // Replace original tag with modified version
540 $content = str_replace( $tag, $new_tag, $content );
541 }
542 } elseif ( preg_match( '#^http(s)?://i[\d]{1}.wp.com#', $src ) && ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) {
543 $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $tag, 1 );
544
545 $content = str_replace( $tag, $new_tag, $content );
546 }
547 }
548 }
549
550 return $content;
551 }
552
553 public static function filter_the_galleries( $galleries ) {
554 if ( empty( $galleries ) || ! is_array( $galleries ) ) {
555 return $galleries;
556 }
557
558 // Pass by reference, so we can modify them in place.
559 foreach ( $galleries as &$this_gallery ) {
560 if ( is_string( $this_gallery ) ) {
561 $this_gallery = self::filter_the_content( $this_gallery );
562 // LEAVING COMMENTED OUT as for the moment it doesn't seem
563 // necessary and I'm not sure how it would propagate through.
564 // } elseif ( is_array( $this_gallery )
565 // && ! empty( $this_gallery['src'] )
566 // && ! empty( $this_gallery['type'] )
567 // && in_array( $this_gallery['type'], array( 'rectangle', 'square', 'circle' ) ) ) {
568 // $this_gallery['src'] = array_map( 'jetpack_photon_url', $this_gallery['src'] );
569 }
570 }
571 unset( $this_gallery ); // break the reference.
572
573 return $galleries;
574 }
575
576
577 /**
578 * Runs the image widget through photon.
579 *
580 * @param array $instance Image widget instance data.
581 * @return array
582 */
583 public static function filter_the_image_widget( $instance ) {
584 if ( Jetpack::is_module_active( 'photon' ) && ! $instance['attachment_id'] && $instance['url'] ) {
585 jetpack_photon_url( $instance['url'], array(
586 'w' => $instance['width'],
587 'h' => $instance['height'],
588 ) );
589 }
590
591 return $instance;
592 }
593
594 /**
595 ** CORE IMAGE RETRIEVAL
596 **/
597
598 /**
599 * Filter post thumbnail image retrieval, passing images through Photon
600 *
601 * @param string|bool $image
602 * @param int $attachment_id
603 * @param string|array $size
604 * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url
605 * @filter image_downsize
606 * @return string|bool
607 */
608 public function filter_image_downsize( $image, $attachment_id, $size ) {
609 // Don't foul up the admin side of things, unless a plugin wants to.
610 if ( is_admin() &&
611 /**
612 * Provide plugins a way of running Photon for images in the WordPress Dashboard (wp-admin).
613 *
614 * 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.
615 *
616 * @module photon
617 *
618 * @since 4.8.0
619 *
620 * @param bool false Stop Photon from being run on the Dashboard. Default to false.
621 * @param array $args {
622 * Array of image details.
623 *
624 * @type $image Image URL.
625 * @type $attachment_id Attachment ID of the image.
626 * @type $size Image size. Can be a string (name of the image size, e.g. full) or an array of width and height.
627 * }
628 */
629 false === apply_filters( 'jetpack_photon_admin_allow_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) )
630 ) {
631 return $image;
632 }
633
634 /**
635 * Provide plugins a way of preventing Photon from being applied to images retrieved from WordPress Core.
636 *
637 * @module photon
638 *
639 * @since 2.0.0
640 *
641 * @param bool false Stop Photon from being applied to the image. Default to false.
642 * @param array $args {
643 * Array of image details.
644 *
645 * @type $image Image URL.
646 * @type $attachment_id Attachment ID of the image.
647 * @type $size Image size. Can be a string (name of the image size, e.g. full) or an array of width and height.
648 * }
649 */
650 if ( apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) ) {
651 return $image;
652 }
653
654 // Get the image URL and proceed with Photon-ification if successful
655 $image_url = wp_get_attachment_url( $attachment_id );
656
657 // Set this to true later when we know we have size meta.
658 $has_size_meta = false;
659
660 if ( $image_url ) {
661 // Check if image URL should be used with Photon
662 if ( ! self::validate_image_url( $image_url ) ) {
663 return $image;
664 }
665
666 $intermediate = true; // For the fourth array item returned by the image_downsize filter.
667
668 // If an image is requested with a size known to WordPress, use that size's settings with Photon.
669 // WP states that `add_image_size()` should use a string for the name, but doesn't enforce that.
670 // Due to differences in how Core and Photon check for the registered image size, we check both types.
671 if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
672 $image_args = self::image_sizes();
673 $image_args = $image_args[ $size ];
674
675 $photon_args = array();
676
677 $image_meta = image_get_intermediate_size( $attachment_id, $size );
678
679 // 'full' is a special case: We need consistent data regardless of the requested size.
680 if ( 'full' == $size ) {
681 $image_meta = wp_get_attachment_metadata( $attachment_id );
682 $intermediate = false;
683 } elseif ( ! $image_meta ) {
684 // If we still don't have any image meta at this point, it's probably from a custom thumbnail size
685 // for an image that was uploaded before the custom image was added to the theme. Try to determine the size manually.
686 $image_meta = wp_get_attachment_metadata( $attachment_id );
687
688 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
689 $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
690 if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
691 $image_meta['width'] = $image_resized[6];
692 $image_meta['height'] = $image_resized[7];
693 }
694 }
695 }
696
697 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
698 $image_args['width'] = $image_meta['width'];
699 $image_args['height'] = $image_meta['height'];
700
701 list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
702 $has_size_meta = true;
703 }
704
705 // Expose determined arguments to a filter before passing to Photon
706 $transform = $image_args['crop'] ? 'resize' : 'fit';
707
708 // Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
709 if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
710 if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) {
711 $photon_args['h'] = $image_args['height'];
712 } elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) {
713 $photon_args['w'] = $image_args['width'];
714 }
715 } else {
716 if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) {
717 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
718 // Lets make sure that we don't upscale images since wp never upscales them as well
719 $smaller_width = ( ( $image_meta['width'] < $image_args['width'] ) ? $image_meta['width'] : $image_args['width'] );
720 $smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
721
722 $photon_args[ $transform ] = $smaller_width . ',' . $smaller_height;
723 }
724 } else {
725 $photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
726 }
727
728 }
729
730
731 /**
732 * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string.
733 * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
734 *
735 * @module photon
736 *
737 * @since 2.0.0
738 *
739 * @param array $photon_args Array of Photon arguments.
740 * @param array $args {
741 * Array of image details.
742 *
743 * @type $image_args Array of Image arguments (width, height, crop).
744 * @type $image_url Image URL.
745 * @type $attachment_id Attachment ID of the image.
746 * @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
747 * @type $transform Value can be resize or fit.
748 * @see https://developer.wordpress.com/docs/photon/api
749 * }
750 */
751 $photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
752
753 // Generate Photon URL
754 $image = array(
755 jetpack_photon_url( $image_url, $photon_args ),
756 $has_size_meta ? $image_args['width'] : false,
757 $has_size_meta ? $image_args['height'] : false,
758 $intermediate
759 );
760 } elseif ( is_array( $size ) ) {
761 // Pull width and height values from the provided array, if possible
762 $width = isset( $size[0] ) ? (int) $size[0] : false;
763 $height = isset( $size[1] ) ? (int) $size[1] : false;
764
765 // Don't bother if necessary parameters aren't passed.
766 if ( ! $width || ! $height ) {
767 return $image;
768 }
769
770 $image_meta = wp_get_attachment_metadata( $attachment_id );
771 if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
772 $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height );
773
774 if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
775 $width = $image_resized[6];
776 $height = $image_resized[7];
777 } else {
778 $width = $image_meta['width'];
779 $height = $image_meta['height'];
780 }
781
782 $has_size_meta = true;
783 }
784
785 list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
786
787 // Expose arguments to a filter before passing to Photon
788 $photon_args = array(
789 'fit' => $width . ',' . $height
790 );
791
792 /**
793 * Filter the Photon Arguments added to an image when going through Photon,
794 * when the image size is an array of height and width values.
795 *
796 * @module photon
797 *
798 * @since 2.0.0
799 *
800 * @param array $photon_args Array of Photon arguments.
801 * @param array $args {
802 * Array of image details.
803 *
804 * @type $width Image width.
805 * @type height Image height.
806 * @type $image_url Image URL.
807 * @type $attachment_id Attachment ID of the image.
808 * }
809 */
810 $photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
811
812 // Generate Photon URL
813 $image = array(
814 jetpack_photon_url( $image_url, $photon_args ),
815 $has_size_meta ? $width : false,
816 $has_size_meta ? $height : false,
817 $intermediate
818 );
819 }
820 }
821
822 return $image;
823 }
824
825 /**
826 * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent.
827 *
828 * @since 3.8.0
829 * @since 4.0.4 Added automatically additional sizes beyond declared image sizes.
830 * @param array $sources An array of image urls and widths.
831 * @uses self::validate_image_url, jetpack_photon_url, Jetpack_Photon::parse_from_filename
832 * @uses Jetpack_Photon::strip_image_dimensions_maybe, Jetpack::get_content_width
833 * @return array An array of Photon image urls and widths.
834 */
835 public function filter_srcset_array( $sources = array(), $size_array = array(), $image_src = array(), $image_meta = array(), $attachment_id = 0 ) {
836 if ( ! is_array( $sources ) ) {
837 return $sources;
838 }
839 $upload_dir = wp_get_upload_dir();
840
841 foreach ( $sources as $i => $source ) {
842 if ( ! self::validate_image_url( $source['url'] ) ) {
843 continue;
844 }
845
846 /** This filter is already documented in class.photon.php */
847 if ( apply_filters( 'jetpack_photon_skip_image', false, $source['url'], $source ) ) {
848 continue;
849 }
850
851 $url = $source['url'];
852 list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $url );
853
854 // It's quicker to get the full size with the data we have already, if available
855 if ( ! empty( $attachment_id ) ) {
856 $url = wp_get_attachment_url( $attachment_id );
857 } else {
858 $url = Jetpack_Photon::strip_image_dimensions_maybe( $url );
859 }
860
861 $args = array();
862 if ( 'w' === $source['descriptor'] ) {
863 if ( $height && ( $source['value'] == $width ) ) {
864 $args['resize'] = $width . ',' . $height;
865 } else {
866 $args['w'] = $source['value'];
867 }
868
869 }
870
871 $sources[ $i ]['url'] = jetpack_photon_url( $url, $args );
872 }
873
874 /**
875 * At this point, $sources is the original srcset with Photonized URLs.
876 * Now, we're going to construct additional sizes based on multiples of the content_width.
877 * This will reduce the gap between the largest defined size and the original image.
878 */
879
880 /**
881 * Filter the multiplier Photon uses to create new srcset items.
882 * Return false to short-circuit and bypass auto-generation.
883 *
884 * @module photon
885 *
886 * @since 4.0.4
887 *
888 * @param array|bool $multipliers Array of multipliers to use or false to bypass.
889 */
890 $multipliers = apply_filters( 'jetpack_photon_srcset_multipliers', array( 2, 3 ) );
891 $url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
892
893 if (
894 /** Short-circuit via jetpack_photon_srcset_multipliers filter. */
895 is_array( $multipliers )
896 /** This filter is already documented in class.photon.php */
897 && ! apply_filters( 'jetpack_photon_skip_image', false, $url, null )
898 /** Verify basic meta is intact. */
899 && isset( $image_meta['width'] ) && isset( $image_meta['height'] ) && isset( $image_meta['file'] )
900 /** Verify we have the requested width/height. */
901 && isset( $size_array[0] ) && isset( $size_array[1] )
902 ) {
903
904 $fullwidth = $image_meta['width'];
905 $fullheight = $image_meta['height'];
906 $reqwidth = $size_array[0];
907 $reqheight = $size_array[1];
908
909 $constrained_size = wp_constrain_dimensions( $fullwidth, $fullheight, $reqwidth );
910 $expected_size = array( $reqwidth, $reqheight );
911
912 if ( abs( $constrained_size[0] - $expected_size[0] ) <= 1 && abs( $constrained_size[1] - $expected_size[1] ) <= 1 ) {
913 $crop = 'soft';
914 $base = Jetpack::get_content_width() ? Jetpack::get_content_width() : 1000; // Provide a default width if none set by the theme.
915 } else {
916 $crop = 'hard';
917 $base = $reqwidth;
918 }
919
920
921 $currentwidths = array_keys( $sources );
922 $newsources = null;
923
924 foreach ( $multipliers as $multiplier ) {
925
926 $newwidth = $base * $multiplier;
927 foreach ( $currentwidths as $currentwidth ){
928 // If a new width would be within 100 pixes of an existing one or larger than the full size image, skip.
929 if ( abs( $currentwidth - $newwidth ) < 50 || ( $newwidth > $fullwidth ) ) {
930 continue 2; // Back to the foreach ( $multipliers as $multiplier )
931 }
932 } // foreach ( $currentwidths as $currentwidth ){
933
934 if ( 'soft' == $crop ) {
935 $args = array(
936 'w' => $newwidth,
937 );
938 } else { // hard crop, e.g. add_image_size( 'example', 200, 200, true );
939 $args = array(
940 'zoom' => $multiplier,
941 'resize' => $reqwidth . ',' . $reqheight,
942 );
943 }
944
945 $newsources[ $newwidth ] = array(
946 'url' => jetpack_photon_url( $url, $args ),
947 'descriptor' => 'w',
948 'value' => $newwidth,
949 );
950 } // foreach ( $multipliers as $multiplier )
951 if ( is_array( $newsources ) ) {
952 if ( function_exists( 'array_replace' ) ) { // PHP 5.3+, preferred
953 // phpcs:disable
954 $sources = array_replace( $sources, $newsources );
955 // phpcs:enable
956 } else { // For PHP 5.2 using WP shim function
957 $sources = array_replace_recursive( $sources, $newsources );
958 }
959 }
960 } // if ( isset( $image_meta['width'] ) && isset( $image_meta['file'] ) )
961
962 return $sources;
963 }
964
965 /**
966 * Filters an array of image `sizes` values, using $content_width instead of image's full size.
967 *
968 * @since 4.0.4
969 * @since 4.1.0 Returns early for images not within the_content.
970 * @param array $sizes An array of media query breakpoints.
971 * @param array $size Width and height of the image
972 * @uses Jetpack::get_content_width
973 * @return array An array of media query breakpoints.
974 */
975 public function filter_sizes( $sizes, $size ) {
976 if ( ! doing_filter( 'the_content' ) ){
977 return $sizes;
978 }
979 $content_width = Jetpack::get_content_width();
980 if ( ! $content_width ) {
981 $content_width = 1000;
982 }
983
984 if ( ( is_array( $size ) && $size[0] < $content_width ) ) {
985 return $sizes;
986 }
987
988 return sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $content_width );
989 }
990
991 /**
992 ** GENERAL FUNCTIONS
993 **/
994
995 /**
996 * Ensure image URL is valid for Photon.
997 * 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.
998 *
999 * @param string $url
1000 * @uses wp_parse_args
1001 * @return bool
1002 */
1003 protected static function validate_image_url( $url ) {
1004 $parsed_url = @parse_url( $url );
1005
1006 if ( ! $parsed_url )
1007 return false;
1008
1009 // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
1010 $url_info = wp_parse_args( $parsed_url, array(
1011 'scheme' => null,
1012 'host' => null,
1013 'port' => null,
1014 'path' => null
1015 ) );
1016
1017 // Bail if scheme isn't http or port is set that isn't port 80
1018 if (
1019 ( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) &&
1020 /**
1021 * Allow Photon to fetch images that are served via HTTPS.
1022 *
1023 * @module photon
1024 *
1025 * @since 2.4.0
1026 * @since 3.9.0 Default to false.
1027 *
1028 * @param bool $reject_https Should Photon ignore images using the HTTPS scheme. Default to false.
1029 */
1030 apply_filters( 'jetpack_photon_reject_https', false )
1031 ) {
1032 return false;
1033 }
1034
1035 // Bail if no host is found
1036 if ( is_null( $url_info['host'] ) )
1037 return false;
1038
1039 // Bail if the image alredy went through Photon
1040 if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
1041 return false;
1042
1043 // Bail if no path is found
1044 if ( is_null( $url_info['path'] ) )
1045 return false;
1046
1047 // Ensure image extension is acceptable
1048 if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
1049 return false;
1050
1051 // If we got this far, we should have an acceptable image URL
1052 // But let folks filter to decline if they prefer.
1053 /**
1054 * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon.
1055 *
1056 * @module photon
1057 *
1058 * @since 3.0.0
1059 *
1060 * @param bool true Is the image URL valid and can it be used by Photon. Default to true.
1061 * @param string $url Image URL.
1062 * @param array $parsed_url Array of information about the image.
1063 */
1064 return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url );
1065 }
1066
1067 /**
1068 * Checks if the file exists before it passes the file to photon
1069 *
1070 * @param string $src The image URL
1071 * @return string
1072 **/
1073 public static function strip_image_dimensions_maybe( $src ){
1074 $stripped_src = $src;
1075
1076 // Build URL, first removing WP's resized string so we pass the original image to Photon
1077 if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) ) {
1078 $stripped_src = str_replace( $src_parts[1], '', $src );
1079 $upload_dir = wp_get_upload_dir();
1080
1081 // Extracts the file path to the image minus the base url
1082 $file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) );
1083
1084 if( file_exists( $upload_dir["basedir"] . $file_path ) )
1085 $src = $stripped_src;
1086 }
1087
1088 return $src;
1089 }
1090
1091 /**
1092 * Provide an array of available image sizes and corresponding dimensions.
1093 * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
1094 *
1095 * @global $wp_additional_image_sizes
1096 * @uses get_option
1097 * @return array
1098 */
1099 protected static function image_sizes() {
1100 if ( null == self::$image_sizes ) {
1101 global $_wp_additional_image_sizes;
1102
1103 // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
1104 $images = array(
1105 'thumb' => array(
1106 'width' => intval( get_option( 'thumbnail_size_w' ) ),
1107 'height' => intval( get_option( 'thumbnail_size_h' ) ),
1108 'crop' => (bool) get_option( 'thumbnail_crop' )
1109 ),
1110 'medium' => array(
1111 'width' => intval( get_option( 'medium_size_w' ) ),
1112 'height' => intval( get_option( 'medium_size_h' ) ),
1113 'crop' => false
1114 ),
1115 'large' => array(
1116 'width' => intval( get_option( 'large_size_w' ) ),
1117 'height' => intval( get_option( 'large_size_h' ) ),
1118 'crop' => false
1119 ),
1120 'full' => array(
1121 'width' => null,
1122 'height' => null,
1123 'crop' => false
1124 )
1125 );
1126
1127 // Compatibility mapping as found in wp-includes/media.php
1128 $images['thumbnail'] = $images['thumb'];
1129
1130 // Update class variable, merging in $_wp_additional_image_sizes if any are set
1131 if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
1132 self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
1133 else
1134 self::$image_sizes = $images;
1135 }
1136
1137 return is_array( self::$image_sizes ) ? self::$image_sizes : array();
1138 }
1139
1140 /**
1141 * Pass og:image URLs through Photon
1142 *
1143 * @param array $tags
1144 * @param array $parameters
1145 * @uses jetpack_photon_url
1146 * @return array
1147 */
1148 function filter_open_graph_tags( $tags, $parameters ) {
1149 if ( empty( $tags['og:image'] ) ) {
1150 return $tags;
1151 }
1152
1153 $photon_args = array(
1154 'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
1155 );
1156
1157 if ( is_array( $tags['og:image'] ) ) {
1158 $images = array();
1159 foreach ( $tags['og:image'] as $image ) {
1160 $images[] = jetpack_photon_url( $image, $photon_args );
1161 }
1162 $tags['og:image'] = $images;
1163 } else {
1164 $tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
1165 }
1166
1167 return $tags;
1168 }
1169
1170 public function noresize_intermediate_sizes( $sizes ) {
1171 return __return_empty_array();
1172 }
1173
1174 /**
1175 * Enqueue Photon helper script
1176 *
1177 * @uses wp_enqueue_script, plugins_url
1178 * @action wp_enqueue_script
1179 * @return null
1180 */
1181 public function action_wp_enqueue_scripts() {
1182 if ( Jetpack_AMP_Support::is_amp_request() ) {
1183 return;
1184 }
1185 wp_enqueue_script(
1186 'jetpack-photon',
1187 Jetpack::get_file_url_for_environment(
1188 '_inc/build/photon/photon.min.js',
1189 'modules/photon/photon.js'
1190 ),
1191 array( 'jquery' ),
1192 20130122,
1193 true
1194 );
1195 }
1196
1197 /**
1198 * Determine if image_downsize should utilize Photon via REST API.
1199 *
1200 * The WordPress Block Editor (Gutenberg) and other REST API consumers using the wp/v2/media endpoint, especially in the "edit"
1201 * context is more akin to the is_admin usage of Photon (see filter_image_downsize). Since consumers are trying to edit content in posts,
1202 * Photon should not fire as it will fire later on display. By aborting an attempt to Photonize an image here, we
1203 * prevents issues like https://github.com/Automattic/jetpack/issues/10580 .
1204 *
1205 * To determine if we're using the wp/v2/media endpoint, we hook onto the `rest_request_before_callbacks` filter and
1206 * if determined we are using it in the edit context, we'll false out the `jetpack_photon_override_image_downsize` filter.
1207 *
1208 * @see Jetpack_Photon::filter_image_downsize()
1209 *
1210 * @param null|WP_Error $response
1211 * @param array $endpoint_data
1212 * @param WP_REST_Request $request Request used to generate the response.
1213 *
1214 * @return null|WP_Error The original response object without modification.
1215 */
1216 public function should_rest_photon_image_downsize( $response, $endpoint_data, $request ) {
1217 if ( ! is_a( $request , 'WP_REST_Request' ) ) {
1218 return $response; // Something odd is happening. Do nothing and return the response.
1219 }
1220
1221 if ( is_wp_error( $response ) ) {
1222 // If we're going to return an error, we don't need to do anything with Photon.
1223 return $response;
1224 }
1225
1226 $route = $request->get_route();
1227
1228 if ( false !== strpos( $route, 'wp/v2/media' ) && 'edit' === $request['context'] ) {
1229 // Don't use `__return_true()`: Use something unique. See ::_override_image_downsize_in_rest_edit_context()
1230 // Late execution to avoid conflict with other plugins as we really don't want to run in this situation.
1231 add_filter( 'jetpack_photon_override_image_downsize', array( $this, '_override_image_downsize_in_rest_edit_context' ), 999999 );
1232 }
1233
1234 return $response;
1235
1236 }
1237
1238 /**
1239 * Remove the override we may have added in ::should_rest_photon_image_downsize()
1240 * Since ::_override_image_downsize_in_rest_edit_context() is only
1241 * every used here, we can always remove it without ever worrying
1242 * about breaking any other configuration.
1243 *
1244 * @param mixed $response
1245 * @return mixed Unchanged $response
1246 */
1247 public function cleanup_rest_photon_image_downsize( $response ) {
1248 remove_filter( 'jetpack_photon_override_image_downsize', array( $this, '_override_image_downsize_in_rest_edit_context' ), 999999 );
1249 return $response;
1250 }
1251
1252 /**
1253 * Used internally by ::should_rest_photon_image_downsize() to not photonize
1254 * image URLs in ?context=edit REST requests.
1255 * MUST NOT be used anywhere else.
1256 * We use a unique function instead of __return_true so that we can clean up
1257 * after ourselves without breaking anyone else's filters.
1258 *
1259 * @internal
1260 * @return true
1261 */
1262 public function _override_image_downsize_in_rest_edit_context() {
1263 return true;
1264 }
1265 }
1266