PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 8.8
Jetpack – WP Security, Backup, Speed, & Growth v8.8
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / functions.photon.php

functions.photon.php in Jetpack – WP Security, Backup, Speed, & Growth 8.8, at functions.photon.php

384 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Automattic\Jetpack\Status;
4
5 /**
6 * Generic functions using the Photon service.
7 *
8 * Some are used outside of the Photon module being active, so intentionally not within the module.
9 *
10 * @package jetpack
11 */
12
13 /**
14 * Generates a Photon URL.
15 *
16 * @see https://developer.wordpress.com/docs/photon/
17 *
18 * @param string $image_url URL to the publicly accessible image you want to manipulate.
19 * @param array|string $args An array of arguments, i.e. array( 'w' => '300', 'resize' => array( 123, 456 ) ), or in string form (w=123&h=456).
20 * @param string|null $scheme URL protocol.
21 * @return string The raw final URL. You should run this through esc_url() before displaying it.
22 */
23 function jetpack_photon_url( $image_url, $args = array(), $scheme = null ) {
24 $image_url = trim( $image_url );
25
26 if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
27 /**
28 * Disables Photon URL processing for local development
29 *
30 * @module photon
31 *
32 * @since 4.1.0
33 *
34 * @param bool false Result of Automattic\Jetpack\Status->is_offline_mode().
35 */
36 if ( true === apply_filters( 'jetpack_photon_development_mode', ( new Status() )->is_offline_mode() ) ) {
37 return $image_url;
38 }
39 }
40
41 /**
42 * Allow specific image URls to avoid going through Photon.
43 *
44 * @module photon
45 *
46 * @since 3.2.0
47 *
48 * @param bool false Should the image be returned as is, without going through Photon. Default to false.
49 * @param string $image_url Image URL.
50 * @param array|string $args Array of Photon arguments.
51 * @param string|null $scheme Image scheme. Default to null.
52 */
53 if ( false !== apply_filters( 'jetpack_photon_skip_for_url', false, $image_url, $args, $scheme ) ) {
54 return $image_url;
55 }
56
57 /**
58 * Filter the original image URL before it goes through Photon.
59 *
60 * @module photon
61 *
62 * @since 1.9.0
63 *
64 * @param string $image_url Image URL.
65 * @param array|string $args Array of Photon arguments.
66 * @param string|null $scheme Image scheme. Default to null.
67 */
68 $image_url = apply_filters( 'jetpack_photon_pre_image_url', $image_url, $args, $scheme );
69 /**
70 * Filter the original Photon image parameters before Photon is applied to an image.
71 *
72 * @module photon
73 *
74 * @since 1.9.0
75 *
76 * @param array|string $args Array of Photon arguments.
77 * @param string $image_url Image URL.
78 * @param string|null $scheme Image scheme. Default to null.
79 */
80 $args = apply_filters( 'jetpack_photon_pre_args', $args, $image_url, $scheme );
81
82 if ( empty( $image_url ) ) {
83 return $image_url;
84 }
85
86 $image_url_parts = wp_parse_url( $image_url );
87
88 // Unable to parse.
89 if ( ! is_array( $image_url_parts ) || empty( $image_url_parts['host'] ) || empty( $image_url_parts['path'] ) ) {
90 return $image_url;
91 }
92
93 if ( is_array( $args ) ) {
94 // Convert values that are arrays into strings.
95 foreach ( $args as $arg => $value ) {
96 if ( is_array( $value ) ) {
97 $args[ $arg ] = implode( ',', $value );
98 }
99 }
100
101 // Encode values.
102 // See https://core.trac.wordpress.org/ticket/17923 .
103 $args = rawurlencode_deep( $args );
104 }
105
106 // Don't photon-ize WPCOM hosted images -- we can serve them up from wpcom directly.
107 $is_wpcom_image = false;
108 if ( wp_endswith( strtolower( $image_url_parts['host'] ), '.files.wordpress.com' ) ) {
109 $is_wpcom_image = true;
110 if ( isset( $args['ssl'] ) ) {
111 // Do not send the ssl argument to prevent caching issues.
112 unset( $args['ssl'] );
113 }
114 }
115
116 /** This filter is documented below. */
117 $custom_photon_url = apply_filters( 'jetpack_photon_domain', '', $image_url );
118 $custom_photon_url = esc_url( $custom_photon_url );
119
120 // You can't run a Photon URL through Photon again because query strings are stripped.
121 // So if the image is already a Photon URL, append the new arguments to the existing URL.
122 // Alternately, if it's a *.files.wordpress.com url, then keep the domain as is.
123 if (
124 in_array( $image_url_parts['host'], array( 'i0.wp.com', 'i1.wp.com', 'i2.wp.com' ), true )
125 || wp_parse_url( $custom_photon_url, PHP_URL_HOST ) === $image_url_parts['host']
126 || $is_wpcom_image
127 ) {
128 /*
129 * VideoPress Poster images should only keep one param, ssl.
130 */
131 if (
132 is_array( $args )
133 && 'videos.files.wordpress.com' === strtolower( $image_url_parts['host'] )
134 ) {
135 $args = array_intersect_key( array( 'ssl' => 1 ), $args );
136 }
137
138 $photon_url = add_query_arg( $args, $image_url );
139 return jetpack_photon_url_scheme( $photon_url, $scheme );
140 }
141
142 /**
143 * Allow Photon to use query strings as well.
144 * By default, Photon doesn't support query strings so we ignore them and look only at the path.
145 * This setting is Photon Server dependent.
146 *
147 * @module photon
148 *
149 * @since 1.9.0
150 *
151 * @param bool false Should images using query strings go through Photon. Default is false.
152 * @param string $image_url_parts['host'] Image URL's host.
153 */
154 if ( ! apply_filters( 'jetpack_photon_any_extension_for_domain', false, $image_url_parts['host'] ) ) {
155 // Photon doesn't support query strings so we ignore them and look only at the path.
156 // However some source images are served via PHP so check the no-query-string extension.
157 // For future proofing, this is an excluded list of common issues rather than an allow list.
158 $extension = pathinfo( $image_url_parts['path'], PATHINFO_EXTENSION );
159 if ( empty( $extension ) || in_array( $extension, array( 'php', 'ashx' ), true ) ) {
160 return $image_url;
161 }
162 }
163
164 $image_host_path = $image_url_parts['host'] . $image_url_parts['path'];
165
166 /*
167 * Figure out which CDN subdomain to use.
168 *
169 * The goal is to have the same subdomain for any particular image to prevent multiple runs resulting in multiple
170 * images needing to be downloaded by the browser.
171 *
172 * We are providing our own generated value by taking the modulus of the crc32 value of the URL.
173 *
174 * Valid values are 0, 1, and 2.
175 */
176 $subdomain = abs( crc32( $image_host_path ) % 3 );
177
178 /**
179 * Filters the domain used by the Photon module.
180 *
181 * @module photon
182 *
183 * @since 3.4.2
184 *
185 * @param string https://i{$subdomain}.wp.com Domain used by Photon. $subdomain is a random number between 0 and 2.
186 * @param string $image_url URL of the image to be photonized.
187 */
188 $photon_domain = apply_filters( 'jetpack_photon_domain', "https://i{$subdomain}.wp.com", $image_url );
189 $photon_domain = trailingslashit( esc_url( $photon_domain ) );
190 $photon_url = $photon_domain . $image_host_path;
191
192 /**
193 * Add query strings to Photon URL.
194 * By default, Photon doesn't support query strings so we ignore them.
195 * This setting is Photon Server dependent.
196 *
197 * @module photon
198 *
199 * @since 1.9.0
200 *
201 * @param bool false Should query strings be added to the image URL. Default is false.
202 * @param string $image_url_parts['host'] Image URL's host.
203 */
204 if ( isset( $image_url_parts['query'] ) && apply_filters( 'jetpack_photon_add_query_string_to_domain', false, $image_url_parts['host'] ) ) {
205 $photon_url .= '?q=' . rawurlencode( $image_url_parts['query'] );
206 }
207
208 if ( $args ) {
209 if ( is_array( $args ) ) {
210 $photon_url = add_query_arg( $args, $photon_url );
211 } else {
212 // You can pass a query string for complicated requests but where you still want CDN subdomain help, etc.
213 $photon_url .= '?' . $args;
214 }
215 }
216
217 if ( isset( $image_url_parts['scheme'] ) && 'https' === $image_url_parts['scheme'] ) {
218 $photon_url = add_query_arg( array( 'ssl' => 1 ), $photon_url );
219 }
220
221 return jetpack_photon_url_scheme( $photon_url, $scheme );
222 }
223
224 /**
225 * Add an easy way to photon-ize a URL that is safe to call even if Jetpack isn't active.
226 *
227 * See: https://jetpack.com/2013/07/11/photon-and-themes/
228 */
229 add_filter( 'jetpack_photon_url', 'jetpack_photon_url', 10, 3 );
230
231 /**
232 * WordPress.com
233 *
234 * If a cropped WP.com-hosted image is the source image, have Photon replicate the crop.
235 */
236 add_filter( 'jetpack_photon_pre_args', 'jetpack_photon_parse_wpcom_query_args', 10, 2 );
237
238 /**
239 * Parses WP.com-hosted image args to replicate the crop.
240 *
241 * @param mixed $args Args set during Photon's processing.
242 * @param string $image_url URL of the image.
243 * @return array|string Args for Photon to use for the URL.
244 */
245 function jetpack_photon_parse_wpcom_query_args( $args, $image_url ) {
246 $parsed_url = wp_parse_url( $image_url );
247
248 if ( ! $parsed_url ) {
249 return $args;
250 }
251
252 $image_url_parts = wp_parse_args(
253 $parsed_url,
254 array(
255 'host' => '',
256 'query' => '',
257 )
258 );
259
260 if ( '.files.wordpress.com' !== substr( $image_url_parts['host'], -20 ) ) {
261 return $args;
262 }
263
264 if ( empty( $image_url_parts['query'] ) ) {
265 return $args;
266 }
267
268 $wpcom_args = wp_parse_args( $image_url_parts['query'] );
269
270 if ( empty( $wpcom_args['w'] ) || empty( $wpcom_args['h'] ) ) {
271 return $args;
272 }
273
274 // Keep the crop by using "resize".
275 if ( ! empty( $wpcom_args['crop'] ) ) {
276 if ( is_array( $args ) ) {
277 $args = array_merge( array( 'resize' => array( $wpcom_args['w'], $wpcom_args['h'] ) ), $args );
278 } else {
279 $args = 'resize=' . rawurlencode( absint( $wpcom_args['w'] ) . ',' . absint( $wpcom_args['h'] ) ) . '&' . $args;
280 }
281 } else {
282 if ( is_array( $args ) ) {
283 $args = array_merge( array( 'fit' => array( $wpcom_args['w'], $wpcom_args['h'] ) ), $args );
284 } else {
285 $args = 'fit=' . rawurlencode( absint( $wpcom_args['w'] ) . ',' . absint( $wpcom_args['h'] ) ) . '&' . $args;
286 }
287 }
288
289 return $args;
290 }
291
292 /**
293 * Sets the scheme for a URL
294 *
295 * @param string $url URL to set scheme.
296 * @param string $scheme Scheme to use. Accepts http, https, network_path.
297 *
298 * @return string URL.
299 */
300 function jetpack_photon_url_scheme( $url, $scheme ) {
301 if ( ! in_array( $scheme, array( 'http', 'https', 'network_path' ), true ) ) {
302 if ( preg_match( '#^(https?:)?//#', $url ) ) {
303 return $url;
304 }
305
306 $scheme = 'http';
307 }
308
309 if ( 'network_path' === $scheme ) {
310 $scheme_slashes = '//';
311 } else {
312 $scheme_slashes = "$scheme://";
313 }
314
315 return preg_replace( '#^([a-z:]+)?//#i', $scheme_slashes, $url );
316 }
317
318 /**
319 * A wrapper for PHP's parse_url, prepending assumed scheme for network path
320 * URLs. PHP versions 5.4.6 and earlier do not correctly parse without scheme.
321 *
322 * WP ships with a wrapper for parse_url, wp_parse_url, that should be used instead.
323 *
324 * @see https://php.net/manual/en/function.parse-url.php#refsect1-function.parse-url-changelog
325 * @deprecated 7.8.0 Use wp_parse_url instead.
326 *
327 * @param string $url The URL to parse.
328 * @param integer $component Retrieve specific URL component.
329 * @return mixed Result of parse_url
330 */
331 function jetpack_photon_parse_url( $url, $component = -1 ) {
332 _deprecated_function( 'jetpack_photon_parse_url', 'jetpack-7.8.0', 'wp_parse_url' );
333 return wp_parse_url( $url, $component );
334 }
335
336 add_filter( 'jetpack_photon_skip_for_url', 'jetpack_photon_banned_domains', 9, 2 );
337
338 /**
339 * Check to skip Photon for a known domain that shouldn't be Photonized.
340 *
341 * @param bool $skip If the image should be skipped by Photon.
342 * @param string $image_url URL of the image.
343 *
344 * @return bool Should the image be skipped by Photon.
345 */
346 function jetpack_photon_banned_domains( $skip, $image_url ) {
347 $banned_host_patterns = array(
348 '/^chart\.googleapis\.com$/',
349 '/^chart\.apis\.google\.com$/',
350 '/^graph\.facebook\.com$/',
351 '/\.fbcdn\.net$/',
352 '/\.paypalobjects\.com$/',
353 '/\.dropbox\.com$/',
354 '/\.cdninstagram\.com$/',
355 '/\.wikimedia\.org$/',
356 );
357
358 $host = wp_parse_url( $image_url, PHP_URL_HOST );
359
360 foreach ( $banned_host_patterns as $banned_host_pattern ) {
361 if ( 1 === preg_match( $banned_host_pattern, $host ) ) {
362 return true;
363 }
364 }
365
366 return $skip;
367 }
368
369
370 /**
371 * Jetpack Photon - Support Text Widgets.
372 *
373 * @access public
374 * @param string $content Content from text widget.
375 * @return string
376 */
377 function jetpack_photon_support_text_widgets( $content ) {
378 if ( class_exists( 'Jetpack_Photon' ) && Jetpack::is_module_active( 'photon' ) ) {
379 return Jetpack_Photon::filter_the_content( $content );
380 }
381 return $content;
382 }
383 add_filter( 'widget_text', 'jetpack_photon_support_text_widgets' );
384