PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 11.4.1
Jetpack – WP Security, Backup, Speed, & Growth v11.4.1
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 14.4.2 All 500 releases
jetpack / modules / photon-cdn.php
photon-cdn.php
331 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Module Name: Asset CDN
4 * Module Description: Jetpack’s Site Accelerator loads your site faster by optimizing your images and serving your images and static files from our global network of servers.
5 * Sort Order: 26
6 * Recommendation Order: 1
7 * First Introduced: 6.6
8 * Requires Connection: No
9 * Auto Activate: No
10 * Module Tags: Photos and Videos, Appearance, Recommended
11 * Feature: Recommended, Appearance
12 * Additional Search Queries: site accelerator, accelerate, static, assets, javascript, css, files, performance, cdn, bandwidth, content delivery network, pagespeed, combine js, optimize css
13 *
14 * @package automattic/jetpack
15 */
16
17 use Automattic\Jetpack\Assets;
18
19 $GLOBALS['concatenate_scripts'] = false; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
20
21 Assets::add_resource_hint( '//c0.wp.com', 'dns-prefetch' );
22
23 /**
24 * Asset CDN module main class file.
25 */
26 class Jetpack_Photon_Static_Assets_CDN {
27 const CDN = 'https://c0.wp.com/';
28
29 /**
30 * Sets up action handlers needed for Jetpack CDN.
31 */
32 public static function go() {
33 add_action( 'wp_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
34 add_action( 'wp_print_styles', array( __CLASS__, 'cdnize_assets' ) );
35 add_action( 'admin_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
36 add_action( 'admin_print_styles', array( __CLASS__, 'cdnize_assets' ) );
37 add_action( 'wp_footer', array( __CLASS__, 'cdnize_assets' ) );
38 add_filter( 'load_script_textdomain_relative_path', array( __CLASS__, 'fix_script_relative_path' ), 10, 2 );
39 add_filter( 'load_script_translation_file', array( __CLASS__, 'fix_local_script_translation_path' ), 10, 3 );
40 }
41
42 /**
43 * Sets up CDN URLs for assets that are enqueued by the WordPress Core.
44 */
45 public static function cdnize_assets() {
46 global $wp_scripts, $wp_styles, $wp_version;
47
48 /*
49 * Short-circuit if AMP since not relevant as custom JS is not allowed and CSS is inlined.
50 * Note that it is not suitable to use the jetpack_force_disable_site_accelerator filter for this
51 * because it will be applied before the wp action, which is the point at which the queried object
52 * is available and we know whether the response will be AMP or not. This is particularly important
53 * for AMP-first (native AMP) pages where there are no AMP-specific URLs.
54 */
55 if ( Jetpack_AMP_Support::is_amp_request() ) {
56 return;
57 }
58
59 /**
60 * Filters Jetpack CDN's Core version number and locale. Can be used to override the values
61 * that Jetpack uses to retrieve assets. Expects the values to be returned in an array.
62 *
63 * @module photon-cdn
64 *
65 * @since 6.6.0
66 *
67 * @param array $values array( $version = core assets version, i.e. 4.9.8, $locale = desired locale )
68 */
69 list( $version, $locale ) = apply_filters( // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
70 'jetpack_cdn_core_version_and_locale',
71 array( $wp_version, get_locale() )
72 );
73
74 if ( self::is_public_version( $version ) ) {
75 $site_url = trailingslashit( site_url() );
76 foreach ( $wp_scripts->registered as $handle => $thing ) {
77 if ( wp_startswith( $thing->src, self::CDN ) ) {
78 continue;
79 }
80 $src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
81 if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ), true ) ) {
82 $wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
83 $wp_scripts->registered[ $handle ]->ver = null;
84 }
85 }
86 foreach ( $wp_styles->registered as $handle => $thing ) {
87 if ( wp_startswith( $thing->src, self::CDN ) ) {
88 continue;
89 }
90 $src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
91 if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ), true ) ) {
92 $wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
93 $wp_styles->registered[ $handle ]->ver = null;
94 }
95 }
96 }
97
98 self::cdnize_plugin_assets( 'jetpack', JETPACK__VERSION );
99 if ( class_exists( 'WooCommerce' ) ) {
100 self::cdnize_plugin_assets( 'woocommerce', WC_VERSION );
101 }
102 }
103
104 /**
105 * Ensure use of the correct relative path when determining the JavaScript file names.
106 *
107 * @param string $relative The relative path of the script. False if it could not be determined.
108 * @param string $src The full source url of the script.
109 * @return string The expected relative path for the CDN-ed URL.
110 */
111 public static function fix_script_relative_path( $relative, $src ) {
112
113 // Note relevant in AMP responses. See note above.
114 if ( Jetpack_AMP_Support::is_amp_request() ) {
115 return $relative;
116 }
117
118 $strpos = strpos( $src, '/wp-includes/' );
119
120 // We only treat URLs that have wp-includes in them. Cases like language textdomains
121 // can also use this filter, they don't need to be touched because they are local paths.
122 if ( false !== $strpos ) {
123 return substr( $src, 1 + $strpos );
124 }
125
126 // Get the local path from a URL which was CDN'ed by cdnize_plugin_assets().
127 if ( preg_match( '#^' . preg_quote( self::CDN, '#' ) . 'p/[^/]+/[^/]+/(.*)$#', $src, $m ) ) {
128 return $m[1];
129 }
130
131 return $relative;
132 }
133
134 /**
135 * Ensure use of the correct local path when loading the JavaScript translation file for a CDN'ed asset.
136 *
137 * @param string|false $file Path to the translation file to load. False if there isn't one.
138 * @param string $handle The script handle.
139 * @param string $domain The text domain.
140 *
141 * @return string The transformed local languages path.
142 */
143 public static function fix_local_script_translation_path( $file, $handle, $domain ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
144 global $wp_scripts;
145
146 // This is a rewritten plugin URL, so load the language file from the plugins path.
147 if ( $file && isset( $wp_scripts->registered[ $handle ] ) && wp_startswith( $wp_scripts->registered[ $handle ]->src, self::CDN . 'p' ) ) {
148 return WP_LANG_DIR . '/plugins/' . basename( $file );
149 }
150
151 return $file;
152 }
153
154 /**
155 * Sets up CDN URLs for supported plugin assets.
156 *
157 * @param String $plugin_slug plugin slug string.
158 * @param String $current_version plugin version string.
159 * @return null|bool
160 */
161 public static function cdnize_plugin_assets( $plugin_slug, $current_version ) {
162 global $wp_scripts, $wp_styles;
163
164 /**
165 * Filters Jetpack CDN's plugin slug and version number. Can be used to override the values
166 * that Jetpack uses to retrieve assets. For example, when testing a development version of Jetpack
167 * the assets are not yet published, so you may need to override the version value to either
168 * trunk, or the latest available version. Expects the values to be returned in an array.
169 *
170 * @module photon-cdn
171 *
172 * @since 6.6.0
173 *
174 * @param array $values array( $slug = the plugin repository slug, i.e. jetpack, $version = the plugin version, i.e. 6.6 )
175 */
176 list( $plugin_slug, $current_version ) = apply_filters(
177 'jetpack_cdn_plugin_slug_and_version',
178 array( $plugin_slug, $current_version )
179 );
180
181 $assets = self::get_plugin_assets( $plugin_slug, $current_version );
182 $plugin_directory_url = plugins_url() . '/' . $plugin_slug . '/';
183
184 if ( is_wp_error( $assets ) || ! is_array( $assets ) ) {
185 return false;
186 }
187
188 foreach ( $wp_scripts->registered as $handle => $thing ) {
189 if ( wp_startswith( $thing->src, self::CDN ) ) {
190 continue;
191 }
192 if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
193 $local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
194 if ( in_array( $local_path, $assets, true ) ) {
195 $wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
196 $wp_scripts->registered[ $handle ]->ver = null;
197 }
198 }
199 }
200 foreach ( $wp_styles->registered as $handle => $thing ) {
201 if ( wp_startswith( $thing->src, self::CDN ) ) {
202 continue;
203 }
204 if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
205 $local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
206 if ( in_array( $local_path, $assets, true ) ) {
207 $wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
208 $wp_styles->registered[ $handle ]->ver = null;
209 }
210 }
211 }
212 }
213
214 /**
215 * Returns cdn-able assets for a given plugin.
216 *
217 * @param string $plugin plugin slug string.
218 * @param string $version plugin version number string.
219 * @return array|bool Will return false if not a public version.
220 */
221 public static function get_plugin_assets( $plugin, $version ) {
222 if ( 'jetpack' === $plugin && JETPACK__VERSION === $version ) {
223 if ( ! self::is_public_version( $version ) ) {
224 return false;
225 }
226
227 $assets = array(); // The variable will be redefined in the included file.
228
229 include JETPACK__PLUGIN_DIR . 'modules/photon-cdn/jetpack-manifest.php';
230 return $assets;
231 }
232
233 /**
234 * Used for other plugins to provide their bundled assets via filter to
235 * prevent the need of storing them in an option or an external api request
236 * to w.org.
237 *
238 * @module photon-cdn
239 *
240 * @since 6.6.0
241 *
242 * @param array $assets The assets array for the plugin.
243 * @param string $version The version of the plugin being requested.
244 */
245 $assets = apply_filters( "jetpack_cdn_plugin_assets-{$plugin}", null, $version ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
246 if ( is_array( $assets ) ) {
247 return $assets;
248 }
249
250 if ( ! self::is_public_version( $version ) ) {
251 return false;
252 }
253
254 $cache = Jetpack_Options::get_option( 'static_asset_cdn_files', array() );
255 if ( isset( $cache[ $plugin ][ $version ] ) ) {
256 if ( is_array( $cache[ $plugin ][ $version ] ) ) {
257 return $cache[ $plugin ][ $version ];
258 }
259 if ( is_numeric( $cache[ $plugin ][ $version ] ) ) {
260 // Cache an empty result for up to 24h.
261 if ( (int) $cache[ $plugin ][ $version ] + DAY_IN_SECONDS > time() ) {
262 return array();
263 }
264 }
265 }
266
267 $url = sprintf( 'http://downloads.wordpress.org/plugin-checksums/%s/%s.json', $plugin, $version );
268
269 if ( wp_http_supports( array( 'ssl' ) ) ) {
270 $url = set_url_scheme( $url, 'https' );
271 }
272
273 $response = wp_remote_get( $url );
274
275 $body = trim( wp_remote_retrieve_body( $response ) );
276 $body = json_decode( $body, true );
277
278 $return = time();
279 if ( is_array( $body ) ) {
280 $return = array_filter( array_keys( $body['files'] ), array( __CLASS__, 'is_js_or_css_file' ) );
281 }
282
283 $cache[ $plugin ] = array();
284 $cache[ $plugin ][ $version ] = $return;
285 Jetpack_Options::update_option( 'static_asset_cdn_files', $cache, true );
286
287 return $return;
288 }
289
290 /**
291 * Checks a path whether it is a JS or CSS file.
292 *
293 * @param String $path file path.
294 * @return Boolean whether the file is a JS or CSS.
295 */
296 public static function is_js_or_css_file( $path ) {
297 return ( false === strpos( $path, '?' ) ) && in_array( substr( $path, -3 ), array( 'css', '.js' ), true );
298 }
299
300 /**
301 * Checks whether the version string indicates a production version.
302 *
303 * @param String $version the version string.
304 * @param Boolean $include_beta_and_rc whether to count beta and RC versions as production.
305 * @return Boolean
306 */
307 public static function is_public_version( $version, $include_beta_and_rc = false ) {
308 if ( preg_match( '/^\d+(\.\d+)+$/', $version ) ) {
309 /** Example matches: `1`, `1.2`, `1.2.3`. */
310 return true;
311 } elseif ( $include_beta_and_rc && preg_match( '/^\d+(\.\d+)+(-(beta|rc|pressable)\d?)$/i', $version ) ) {
312 /** Example matches: `1.2.3`, `1.2.3-beta`, `1.2.3-pressable`, `1.2.3-beta1`, `1.2.3-rc`, `1.2.3-rc2`. */
313 return true;
314 }
315 // Unrecognized version.
316 return false;
317 }
318 }
319 /**
320 * Allow plugins to short-circuit the Asset CDN, even when the module is on.
321 *
322 * @module photon-cdn
323 *
324 * @since 6.7.0
325 *
326 * @param false bool Should the Asset CDN be blocked? False by default.
327 */
328 if ( true !== apply_filters( 'jetpack_force_disable_site_accelerator', false ) ) {
329 Jetpack_Photon_Static_Assets_CDN::go();
330 }
331