PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 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 / jetpack_vendor / automattic / jetpack-image-cdn / src / class-image-cdn-core.php
jetpack / jetpack_vendor / automattic / jetpack-image-cdn / src Last commit date
compatibility 8 months ago class-image-cdn-core.php 8 months ago class-image-cdn-image-sizes.php 1 month ago class-image-cdn-image.php 1 month ago class-image-cdn-setup.php 1 year ago class-image-cdn.php 1 month ago
class-image-cdn-core.php
448 lines
1 <?php
2 /**
3 * Core Image CDN functionality.
4 *
5 * It should be available even if Image CDN is not active.
6 *
7 * @package automattic/jetpack-image-cdn
8 */
9
10 namespace Automattic\Jetpack\Image_CDN;
11
12 use Automattic\Jetpack\Status;
13 use Automattic\Jetpack\Status\Host;
14
15 /**
16 * A static class that provides core Image CDN functionality.
17 */
18 class Image_CDN_Core {
19 /**
20 * Register hooks.
21 */
22 public static function setup() {
23 // Add photon compatibility.
24 require_once __DIR__ . '/compatibility/photon.php';
25
26 // Add ActivityPub compatibility.
27 require_once __DIR__ . '/compatibility/activitypub.php';
28
29 // Add Breakdance compatibility.
30 require_once __DIR__ . '/compatibility/breakdance.php';
31
32 /**
33 * Add an easy way to photon-ize a URL that is safe to call even if Jetpack isn't active.
34 *
35 * See: https://jetpack.com/2013/07/11/photon-and-themes/
36 */
37 add_filter( 'jetpack_photon_url', array( __CLASS__, 'cdn_url' ), 10, 3 );
38
39 /**
40 * WordPress.com
41 *
42 * If a cropped WP.com-hosted image is the source image, have Photon replicate the crop.
43 */
44 add_filter( 'jetpack_photon_pre_args', array( __CLASS__, 'parse_wpcom_query_args' ), 10, 2 );
45
46 add_filter( 'jetpack_photon_skip_for_url', array( __CLASS__, 'banned_domains' ), 9, 2 );
47 }
48
49 /**
50 * Generates a Photon URL.
51 *
52 * @see https://developer.wordpress.com/docs/photon/
53 *
54 * @param string $image_url URL to the publicly accessible image you want to manipulate.
55 * @param array|string $args An array of arguments, e.g. array( 'w' => '300', 'resize' => array( 123, 456 ) ), or in string form (w=123&h=456).
56 * @param string|null $scheme URL protocol.
57 * @return string The raw final URL. You should run this through esc_url() before displaying it.
58 */
59 public static function cdn_url( $image_url, $args = array(), $scheme = null ) {
60 if ( ! is_string( $image_url ) || empty( $image_url ) ) {
61 return '';
62 }
63 $image_url = trim( $image_url );
64
65 if ( ! defined( 'IS_WPCOM' ) || ! \IS_WPCOM ) {
66 /**
67 * Disables Photon URL processing for local development
68 *
69 * @module photon
70 *
71 * @since 4.1.0
72 *
73 * @param bool false Result of Automattic\Jetpack\Status->is_offline_mode().
74 */
75 if ( true === apply_filters( 'jetpack_photon_development_mode', ( new Status() )->is_offline_mode() ) ) {
76 return $image_url;
77 }
78 }
79
80 /**
81 * Allow specific image URls to avoid going through Photon.
82 *
83 * @module photon
84 *
85 * @since 3.2.0
86 *
87 * @param bool false Should the image be returned as is, without going through Photon. Default to false.
88 * @param string $image_url Image URL.
89 * @param array|string $args Array of Photon arguments.
90 * @param string|null $scheme Image scheme. Default to null.
91 */
92 if ( false !== apply_filters( 'jetpack_photon_skip_for_url', false, $image_url, $args, $scheme ) ) {
93 return $image_url;
94 }
95
96 /**
97 * Filter the original image URL before it goes through Photon.
98 *
99 * @module photon
100 *
101 * @since 1.9.0
102 *
103 * @param string $image_url Image URL.
104 * @param array|string $args Array of Photon arguments.
105 * @param string|null $scheme Image scheme. Default to null.
106 */
107 $image_url = apply_filters( 'jetpack_photon_pre_image_url', $image_url, $args, $scheme );
108 /**
109 * Filter the original Photon image parameters before Photon is applied to an image.
110 *
111 * @module photon
112 *
113 * @since 1.9.0
114 *
115 * @param array|string $args Array of Photon arguments.
116 * @param string $image_url Image URL.
117 * @param string|null $scheme Image scheme. Default to null.
118 */
119 $args = apply_filters( 'jetpack_photon_pre_args', $args, $image_url, $scheme );
120
121 if ( empty( $image_url ) ) {
122 return $image_url;
123 }
124
125 $image_url_parts = wp_parse_url( $image_url );
126
127 // Unable to parse.
128 if ( ! is_array( $image_url_parts ) || empty( $image_url_parts['host'] ) || empty( $image_url_parts['path'] ) ) {
129 return $image_url;
130 }
131
132 // Ensure image extension is acceptable.
133 if (
134 ! in_array(
135 strtolower( pathinfo( $image_url_parts['path'], PATHINFO_EXTENSION ) ),
136 Image_CDN::get_supported_extensions(),
137 true
138 )
139 ) {
140 return $image_url;
141 }
142
143 if ( is_array( $args ) ) {
144 // Convert values that are arrays into strings.
145 foreach ( $args as $arg => $value ) {
146 if ( is_array( $value ) ) {
147 $args[ $arg ] = implode( ',', $value );
148 }
149 }
150
151 // Encode values.
152 // See https://core.trac.wordpress.org/ticket/17923 .
153 $args = rawurlencode_deep( $args );
154 }
155
156 // Don't photon-ize WPCOM hosted images -- we can serve them up from wpcom directly.
157 $is_wpcom_image = false;
158 if ( self::ends_with( strtolower( $image_url_parts['host'] ), '.files.wordpress.com' ) ) {
159 $is_wpcom_image = true;
160 if ( isset( $args['ssl'] ) ) {
161 // Do not send the ssl argument to prevent caching issues.
162 unset( $args['ssl'] );
163 }
164 }
165
166 $is_wpcom_private_site = false;
167 if ( ( new Host() )->is_wpcom_platform() && ( new Status() )->is_private_site() ) {
168 $is_wpcom_private_site = true;
169 if ( isset( $args['ssl'] ) ) {
170 // Do not send the ssl argument to prevent caching issues.
171 unset( $args['ssl'] );
172 }
173 }
174
175 // You can't run a Photon URL through Photon again because query strings are stripped.
176 // So if the image is already a Photon URL, append the new arguments to the existing URL.
177 // Alternately, if it's a *.files.wordpress.com url or an image on a private WordPress.com Simple site,
178 // then keep the domain as is.
179 if (
180 self::is_cdn_url( $image_url )
181 || $is_wpcom_image
182 || $is_wpcom_private_site
183 ) {
184 $photon_url = add_query_arg( $args, $image_url );
185 return self::cdn_url_scheme( $photon_url, $scheme );
186 }
187
188 /**
189 * Allow Photon to use query strings as well.
190 * By default, Photon doesn't support query strings so we ignore them and look only at the path.
191 * This setting is Photon Server dependent.
192 *
193 * @module photon
194 *
195 * @since 1.9.0
196 *
197 * @param bool false Should images using query strings go through Photon. Default is false.
198 * @param string $image_url_parts['host'] Image URL's host.
199 */
200 if ( ! apply_filters( 'jetpack_photon_any_extension_for_domain', false, $image_url_parts['host'] ) ) {
201 // Photon doesn't support query strings so we ignore them and look only at the path.
202 // However some source images are served via PHP so check the no-query-string extension.
203 // For future proofing, this is an excluded list of common issues rather than an allow list.
204 $extension = pathinfo( $image_url_parts['path'], PATHINFO_EXTENSION );
205 if ( empty( $extension ) || in_array( $extension, array( 'php', 'ashx' ), true ) ) {
206 return $image_url;
207 }
208 }
209
210 $image_host_path = $image_url_parts['host'] . static::escape_path( $image_url_parts['path'] );
211 /**
212 * Filters the domain used by the Photon module.
213 *
214 * @module photon
215 *
216 * @since 3.4.2
217 *
218 * @param string https://i0.wp.com Domain used by Photon.
219 * @param string $image_url URL of the image to be photonized.
220 */
221 $photon_domain = apply_filters( 'jetpack_photon_domain', 'https://i0.wp.com', $image_url );
222 $photon_domain = trailingslashit( esc_url( $photon_domain ) );
223 $photon_url = $photon_domain . $image_host_path;
224
225 /**
226 * Add query strings to Photon URL.
227 * By default, Photon doesn't support query strings so we ignore them.
228 * This setting is Photon Server dependent.
229 *
230 * @module photon
231 *
232 * @since 1.9.0
233 *
234 * @param bool false Should query strings be added to the image URL. Default is false.
235 * @param string $image_url_parts['host'] Image URL's host.
236 */
237 if ( isset( $image_url_parts['query'] ) && apply_filters( 'jetpack_photon_add_query_string_to_domain', false, $image_url_parts['host'] ) ) {
238 $photon_url .= '?q=' . rawurlencode( $image_url_parts['query'] );
239 }
240
241 if ( $args ) {
242 if ( is_array( $args ) ) {
243 $photon_url = add_query_arg( $args, $photon_url );
244 } elseif ( strpos( $photon_url, '?' ) !== false ) {
245 $photon_url .= '&' . $args;
246 } else {
247 $photon_url .= '?' . $args;
248 }
249 }
250
251 if ( isset( $image_url_parts['scheme'] ) && 'https' === $image_url_parts['scheme'] ) {
252 $photon_url = add_query_arg( array( 'ssl' => 1 ), $photon_url );
253 }
254
255 return self::cdn_url_scheme( $photon_url, $scheme );
256 }
257
258 /**
259 * Checks if a given URL is a Photon URL.
260 *
261 * @since 0.5.0
262 * @param string $url The URL to check.
263 * @return bool True if the URL is a Photon URL, false otherwise.
264 */
265 public static function is_cdn_url( $url ) {
266 $parsed_url = wp_parse_url( $url );
267
268 if ( ! $parsed_url ) {
269 return false;
270 }
271
272 // See usage in ::cdn_url for documentation of this filter
273 $custom_photon_url = apply_filters( 'jetpack_photon_domain', '', $url );
274 $custom_photon_url = esc_url( $custom_photon_url );
275
276 return in_array( $parsed_url['host'], array( 'i0.wp.com', 'i1.wp.com', 'i2.wp.com' ), true )
277 || wp_parse_url( $custom_photon_url, PHP_URL_HOST ) === $parsed_url['host'];
278 }
279
280 /**
281 * URL-encodes each path component.
282 *
283 * Example:
284 * Input: "foo/bar baz/baz"
285 * Output: "foo/bar%20baz/baz"
286 *
287 * @param string $path The path to escape.
288 * @return string The escaped path.
289 */
290 private static function escape_path( $path ) {
291 $parts = explode( '/', $path );
292 $parts = array_map( 'rawurldecode', $parts );
293 $parts = array_map( 'rawurlencode', $parts );
294 return implode( '/', $parts );
295 }
296
297 /**
298 * Parses WP.com-hosted image args to replicate the crop.
299 *
300 * @param mixed $args Args set during Photon's processing.
301 * @param string $image_url URL of the image.
302 * @return array|string Args for Photon to use for the URL.
303 */
304 public static function parse_wpcom_query_args( $args, $image_url ) {
305 $parsed_url = wp_parse_url( $image_url );
306
307 if ( ! $parsed_url ) {
308 return $args;
309 }
310
311 $image_url_parts = wp_parse_args(
312 $parsed_url,
313 array(
314 'host' => '',
315 'query' => '',
316 )
317 );
318
319 if ( ! str_ends_with( $image_url_parts['host'], '.files.wordpress.com' ) ) {
320 return $args;
321 }
322
323 if ( empty( $image_url_parts['query'] ) ) {
324 return $args;
325 }
326
327 $wpcom_args = wp_parse_args( $image_url_parts['query'] );
328
329 if ( empty( $wpcom_args['w'] ) || empty( $wpcom_args['h'] ) ) {
330 return $args;
331 }
332
333 // Keep the crop by using "resize".
334 if ( ! empty( $wpcom_args['crop'] ) ) {
335 if ( is_array( $args ) ) {
336 $args = array_merge( array( 'resize' => array( $wpcom_args['w'], $wpcom_args['h'] ) ), $args );
337 } else {
338 $args = 'resize=' . rawurlencode( absint( $wpcom_args['w'] ) . ',' . absint( $wpcom_args['h'] ) ) . '&' . $args;
339 }
340 } elseif ( is_array( $args ) ) {
341 $args = array_merge( array( 'fit' => array( $wpcom_args['w'], $wpcom_args['h'] ) ), $args );
342 } else {
343 $args = 'fit=' . rawurlencode( absint( $wpcom_args['w'] ) . ',' . absint( $wpcom_args['h'] ) ) . '&' . $args;
344 }
345
346 return $args;
347 }
348
349 /**
350 * Sets the scheme for a URL
351 *
352 * @param string $url URL to set scheme.
353 * @param string $scheme Scheme to use. Accepts http, https, network_path.
354 *
355 * @return string URL.
356 */
357 public static function cdn_url_scheme( $url, $scheme ) {
358 if ( ! in_array( $scheme, array( 'http', 'https', 'network_path' ), true ) ) {
359 if ( preg_match( '#^(https?:)?//#', $url ) ) {
360 return $url;
361 }
362
363 $scheme = 'http';
364 }
365
366 if ( 'network_path' === $scheme ) {
367 $scheme_slashes = '//';
368 } else {
369 $scheme_slashes = "$scheme://";
370 }
371
372 return preg_replace( '#^([a-z:]+)?//#i', $scheme_slashes, $url );
373 }
374
375 /**
376 * Check to skip Photon for a known domain that shouldn't be Photonized.
377 *
378 * @param bool $skip If the image should be skipped by Photon.
379 * @param string $image_url URL of the image.
380 *
381 * @return bool Should the image be skipped by Photon.
382 */
383 public static function banned_domains( $skip, $image_url ) {
384 $banned_host_patterns = array(
385 '/^chart\.googleapis\.com$/',
386 '/^chart\.apis\.google\.com$/',
387 '/^graph\.facebook\.com$/',
388 '/\.fbcdn\.net$/',
389 '/\.paypalobjects\.com$/',
390 '/\.dropbox\.com$/',
391 '/\.cdninstagram\.com$/',
392 '/^(commons|upload)\.wikimedia\.org$/',
393 '/\.wikipedia\.org$/',
394 '/^m\.media-amazon\.com$/',
395 '/^covers\.openlibrary\.org$/',
396 );
397
398 $host = wp_parse_url( $image_url, PHP_URL_HOST );
399 if ( ! $host ) {
400 return $skip;
401 }
402
403 foreach ( $banned_host_patterns as $banned_host_pattern ) {
404 if ( 1 === preg_match( $banned_host_pattern, $host ) ) {
405 return true;
406 }
407 }
408
409 return $skip;
410 }
411
412 /**
413 * Check whether a string ends with a specific substring.
414 *
415 * @param string $haystack String we are filtering.
416 * @param string $needle The substring we are looking for.
417 * @return bool
418 */
419 public static function ends_with( $haystack, $needle ) {
420 if ( ! $haystack || ! $needle || ! is_scalar( $haystack ) || ! is_scalar( $needle ) ) {
421 return false;
422 }
423
424 $haystack = (string) $haystack;
425 $needle = (string) $needle;
426
427 return str_ends_with( $haystack, $needle );
428 }
429
430 /**
431 * This is a copy of Jetpack::get_content_width()
432 * for backwards compatibility.
433 */
434 public static function get_jetpack_content_width() {
435 $content_width = ( isset( $GLOBALS['content_width'] ) && is_numeric( $GLOBALS['content_width'] ) )
436 ? $GLOBALS['content_width']
437 : false;
438 /**
439 * Filter the Content Width value.
440 *
441 * @since 2.2.3
442 *
443 * @param string $content_width Content Width value.
444 */
445 return apply_filters( 'jetpack_content_width', $content_width );
446 }
447 }
448