Completed
Push — add/stats-package ( c3aabb...99d182 )
by
unknown
148:14 queued 140:08
created

Jetpack_Photon_Static_Assets_CDN::go()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
1
<?php
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
15
use Automattic\Jetpack\Assets;
16
17
$GLOBALS['concatenate_scripts'] = false;
18
19
Assets::add_resource_hint( '//c0.wp.com', 'dns-prefetch' );
20
21
class Jetpack_Photon_Static_Assets_CDN {
22
	const CDN = 'https://c0.wp.com/';
23
24
	/**
25
	 * Sets up action handlers needed for Jetpack CDN.
26
	 */
27
	public static function go() {
28
		add_action( 'wp_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
29
		add_action( 'wp_print_styles', array( __CLASS__, 'cdnize_assets' ) );
30
		add_action( 'admin_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
31
		add_action( 'admin_print_styles', array( __CLASS__, 'cdnize_assets' ) );
32
		add_action( 'wp_footer', array( __CLASS__, 'cdnize_assets' ) );
33
		add_filter( 'load_script_textdomain_relative_path', array( __CLASS__, 'fix_script_relative_path' ), 10, 2 );
34
		add_filter( 'load_script_translation_file', array( __CLASS__, 'fix_local_script_translation_path' ), 10, 3 );
35
	}
36
37
	/**
38
	 * Sets up CDN URLs for assets that are enqueued by the WordPress Core.
39
	 */
40
	public static function cdnize_assets() {
41
		global $wp_scripts, $wp_styles, $wp_version;
42
43
		/*
44
		 * Short-circuit if AMP since not relevant as custom JS is not allowed and CSS is inlined.
45
		 * Note that it is not suitable to use the jetpack_force_disable_site_accelerator filter for this
46
		 * because it will be applied before the wp action, which is the point at which the queried object
47
		 * is available and we know whether the response will be AMP or not. This is particularly important
48
		 * for AMP-first (native AMP) pages where there are no AMP-specific URLs.
49
		 */
50
		if ( Jetpack_AMP_Support::is_amp_request() ) {
51
			return;
52
		}
53
54
		/**
55
		 * Filters Jetpack CDN's Core version number and locale. Can be used to override the values
56
		 * that Jetpack uses to retrieve assets. Expects the values to be returned in an array.
57
		 *
58
		 * @module photon-cdn
59
		 *
60
		 * @since 6.6.0
61
		 *
62
		 * @param array $values array( $version  = core assets version, i.e. 4.9.8, $locale = desired locale )
63
		 */
64
		list( $version, $locale ) = apply_filters(
0 ignored issues
show
Unused Code introduced by
The assignment to $locale is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
65
			'jetpack_cdn_core_version_and_locale',
66
			array( $wp_version, get_locale() )
67
		);
68
69
		if ( self::is_public_version( $version ) ) {
70
			$site_url = trailingslashit( site_url() );
71 View Code Duplication
			foreach ( $wp_scripts->registered as $handle => $thing ) {
72
				if ( wp_startswith( $thing->src, self::CDN ) ) {
73
					continue;
74
				}
75
				$src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
76
				if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ) ) ) {
77
					$wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
78
					$wp_scripts->registered[ $handle ]->ver = null;
79
				}
80
			}
81 View Code Duplication
			foreach ( $wp_styles->registered as $handle => $thing ) {
82
				if ( wp_startswith( $thing->src, self::CDN ) ) {
83
					continue;
84
				}
85
				$src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
86
				if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ) ) ) {
87
					$wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
88
					$wp_styles->registered[ $handle ]->ver = null;
89
				}
90
			}
91
		}
92
93
		self::cdnize_plugin_assets( 'jetpack', JETPACK__VERSION );
94
		if ( class_exists( 'WooCommerce' ) ) {
95
			self::cdnize_plugin_assets( 'woocommerce', WC_VERSION );
96
		}
97
	}
98
99
	/**
100
	 * Ensure use of the correct relative path when determining the JavaScript file names.
101
	 *
102
	 * @param string $relative The relative path of the script. False if it could not be determined.
103
	 * @param string $src      The full source url of the script.
104
	 * @return string The expected relative path for the CDN-ed URL.
105
	 */
106
	public static function fix_script_relative_path( $relative, $src ) {
107
108
		// Note relevant in AMP responses. See note above.
109
		if ( Jetpack_AMP_Support::is_amp_request() ) {
110
			return $relative;
111
		}
112
113
		$strpos = strpos( $src, '/wp-includes/' );
114
115
		// We only treat URLs that have wp-includes in them. Cases like language textdomains
116
		// can also use this filter, they don't need to be touched because they are local paths.
117
		if ( false !== $strpos ) {
118
			return substr( $src, 1 + $strpos );
119
		}
120
121
		// Get the local path from a URL which was CDN'ed by cdnize_plugin_assets().
122
		if ( preg_match( '#^' . preg_quote( self::CDN, '#' ) . 'p/[^/]+/[^/]+/(.*)$#', $src, $m ) ) {
123
			return $m[1];
124
		}
125
126
		return $relative;
127
	}
128
129
	/**
130
	 * Ensure use of the correct local path when loading the JavaScript translation file for a CDN'ed asset.
131
	 *
132
	 * @param string|false $file   Path to the translation file to load. False if there isn't one.
133
	 * @param string       $handle The script handle.
134
	 * @param string       $domain The text domain.
135
	 *
136
	 * @return string The transformed local languages path.
137
	 */
138
	public static function fix_local_script_translation_path( $file, $handle, $domain ) {
139
		global $wp_scripts;
140
141
		// This is a rewritten plugin URL, so load the language file from the plugins path.
142
		if ( $file && isset( $wp_scripts->registered[ $handle ] ) && wp_startswith( $wp_scripts->registered[ $handle ]->src, self::CDN . 'p' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
143
			return WP_LANG_DIR . '/plugins/' . basename( $file );
144
		}
145
146
		return $file;
147
	}
148
149
	/**
150
	 * Sets up CDN URLs for supported plugin assets.
151
	 *
152
	 * @param String $plugin_slug plugin slug string.
153
	 * @param String $current_version plugin version string.
154
	 * @return null|bool
155
	 */
156
	public static function cdnize_plugin_assets( $plugin_slug, $current_version ) {
157
		global $wp_scripts, $wp_styles;
158
159
		/**
160
		 * Filters Jetpack CDN's plugin slug and version number. Can be used to override the values
161
		 * that Jetpack uses to retrieve assets. For example, when testing a development version of Jetpack
162
		 * the assets are not yet published, so you may need to override the version value to either
163
		 * trunk, or the latest available version. Expects the values to be returned in an array.
164
		 *
165
		 * @module photon-cdn
166
		 *
167
		 * @since 6.6.0
168
		 *
169
		 * @param array $values array( $slug = the plugin repository slug, i.e. jetpack, $version = the plugin version, i.e. 6.6 )
170
		 */
171
		list( $plugin_slug, $current_version ) = apply_filters(
172
			'jetpack_cdn_plugin_slug_and_version',
173
			array( $plugin_slug, $current_version )
174
		);
175
176
		$assets               = self::get_plugin_assets( $plugin_slug, $current_version );
177
		$plugin_directory_url = plugins_url() . '/' . $plugin_slug . '/';
178
179
		if ( is_wp_error( $assets ) || ! is_array( $assets ) ) {
180
			return false;
181
		}
182
183 View Code Duplication
		foreach ( $wp_scripts->registered as $handle => $thing ) {
184
			if ( wp_startswith( $thing->src, self::CDN ) ) {
185
				continue;
186
			}
187
			if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
188
				$local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
189
				if ( in_array( $local_path, $assets, true ) ) {
190
					$wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
191
					$wp_scripts->registered[ $handle ]->ver = null;
192
				}
193
			}
194
		}
195 View Code Duplication
		foreach ( $wp_styles->registered as $handle => $thing ) {
196
			if ( wp_startswith( $thing->src, self::CDN ) ) {
197
				continue;
198
			}
199
			if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
200
				$local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
201
				if ( in_array( $local_path, $assets, true ) ) {
202
					$wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
203
					$wp_styles->registered[ $handle ]->ver = null;
204
				}
205
			}
206
		}
207
	}
208
209
	/**
210
	 * Returns cdn-able assets for a given plugin.
211
	 *
212
	 * @param string $plugin plugin slug string.
213
	 * @param string $version plugin version number string.
214
	 * @return array|bool Will return false if not a public version.
215
	 */
216
	public static function get_plugin_assets( $plugin, $version ) {
217
		if ( 'jetpack' === $plugin && JETPACK__VERSION === $version ) {
218
			if ( ! self::is_public_version( $version ) ) {
219
				return false;
220
			}
221
222
			$assets = array(); // The variable will be redefined in the included file.
223
224
			include JETPACK__PLUGIN_DIR . 'modules/photon-cdn/jetpack-manifest.php';
225
			return $assets;
226
		}
227
228
		/**
229
		 * Used for other plugins to provide their bundled assets via filter to
230
		 * prevent the need of storing them in an option or an external api request
231
		 * to w.org.
232
		 *
233
		 * @module photon-cdn
234
		 *
235
		 * @since 6.6.0
236
		 *
237
		 * @param array $assets The assets array for the plugin.
238
		 * @param string $version The version of the plugin being requested.
239
		 */
240
		$assets = apply_filters( "jetpack_cdn_plugin_assets-{$plugin}", null, $version );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $version.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
241
		if ( is_array( $assets ) ) {
242
			return $assets;
243
		}
244
245
		if ( ! self::is_public_version( $version ) ) {
246
			return false;
247
		}
248
249
		$cache = Jetpack_Options::get_option( 'static_asset_cdn_files', array() );
250
		if ( isset( $cache[ $plugin ][ $version ] ) ) {
251
			if ( is_array( $cache[ $plugin ][ $version ] ) ) {
252
				return $cache[ $plugin ][ $version ];
253
			}
254
			if ( is_numeric( $cache[ $plugin ][ $version ] ) ) {
255
				// Cache an empty result for up to 24h.
256
				if ( intval( $cache[ $plugin ][ $version ] ) + DAY_IN_SECONDS > time() ) {
257
					return array();
258
				}
259
			}
260
		}
261
262
		$url = sprintf( 'http://downloads.wordpress.org/plugin-checksums/%s/%s.json', $plugin, $version );
263
264
		if ( wp_http_supports( array( 'ssl' ) ) ) {
265
			$url = set_url_scheme( $url, 'https' );
266
		}
267
268
		$response = wp_remote_get( $url );
269
270
		$body = trim( wp_remote_retrieve_body( $response ) );
271
		$body = json_decode( $body, true );
272
273
		$return = time();
274
		if ( is_array( $body ) ) {
275
			$return = array_filter( array_keys( $body['files'] ), array( __CLASS__, 'is_js_or_css_file' ) );
276
		}
277
278
		$cache[ $plugin ]             = array();
279
		$cache[ $plugin ][ $version ] = $return;
280
		Jetpack_Options::update_option( 'static_asset_cdn_files', $cache, true );
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
281
282
		return $return;
283
	}
284
285
	/**
286
	 * Checks a path whether it is a JS or CSS file.
287
	 *
288
	 * @param String $path file path.
289
	 * @return Boolean whether the file is a JS or CSS.
290
	 */
291
	public static function is_js_or_css_file( $path ) {
292
		return ( false === strpos( $path, '?' ) ) && in_array( substr( $path, -3 ), array( 'css', '.js' ), true );
293
	}
294
295
	/**
296
	 * Checks whether the version string indicates a production version.
297
	 *
298
	 * @param String  $version the version string.
299
	 * @param Boolean $include_beta_and_rc whether to count beta and RC versions as production.
300
	 * @return Boolean
301
	 */
302
	public static function is_public_version( $version, $include_beta_and_rc = false ) {
303
		if ( preg_match( '/^\d+(\.\d+)+$/', $version ) ) {
304
			// matches `1` `1.2` `1.2.3`.
305
			return true;
306
		} elseif ( $include_beta_and_rc && preg_match( '/^\d+(\.\d+)+(-(beta|rc|pressable)\d?)$/i', $version ) ) {
307
			// matches `1.2.3` `1.2.3-beta` `1.2.3-pressable` `1.2.3-beta1` `1.2.3-rc` `1.2.3-rc2`.
308
			return true;
309
		}
310
		// unrecognized version.
311
		return false;
312
	}
313
}
314
/**
315
 * Allow plugins to short-circuit the Asset CDN, even when the module is on.
316
 *
317
 * @module photon-cdn
318
 *
319
 * @since 6.7.0
320
 *
321
 * @param false bool Should the Asset CDN be blocked? False by default.
322
 */
323
if ( true !== apply_filters( 'jetpack_force_disable_site_accelerator', false ) ) {
324
	Jetpack_Photon_Static_Assets_CDN::go();
325
}
326