Completed
Push — add/pwa ( d2805e...9c2880 )
by
unknown
08:14
created

should_async_script()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 6
nop 1
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Optimizes page assets for unreliable networks and fast rendering, particularly with empty caches
5
 * - inline scripts and styles
6
 * - async external JS
7
 * - remove references to external fonts
8
 * - move CSS links below scripts in head (scripts after CSS blocks render until script finishes downloading)
9
 */
10
11
class Jetpack_Perf_Optimize_Assets {
12
	private static $__instance = null;
13
	private $remove_remote_fonts = false;
14
	private $inline_scripts_and_styles = false;
15
	private $async_scripts = false;
16
	private $defer_scripts = false;
17
	private $inject_critical_css = false;
18
	private $minify_html = false;
19
	const INLINE_ASSET_MAX_SIZE_BYTES = 50 * 1024; // 10kb
20
	const INLINE_CSS_URL_MAX_SIZE_BYTES = 5 * 1024; // 5kb
21
22
	/**
23
	 * Singleton implementation
24
	 *
25
	 * @return object
26
	 */
27
	public static function instance() {
28
		if ( ! is_a( self::$__instance, 'Jetpack_Perf_Optimize_Assets' ) ) {
29
			self::$__instance = new Jetpack_Perf_Optimize_Assets();
30
		}
31
32
		return self::$__instance;
33
	}
34
35
	public function disable_for_request() {
36
		$this->remove_remote_fonts = false;
37
		$this->inline_scripts_and_styles = false;
38
		$this->async_scripts = false;
39
		$this->defer_scripts = false;
40
	}
41
42
	/**
43
	 * TODO: detect if this is worth doing for wp-admin?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
44
	 */
45
46
	/**
47
	 * Registers actions
48
	 */
49
	private function __construct() {
50
//		global $concatenate_scripts;
51
//		$concatenate_scripts = true;
52
		$this->is_first_load             = ! isset( $_COOKIE['jetpack_perf_loaded'] );
0 ignored issues
show
Bug introduced by
The property is_first_load does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
53
		$this->remove_remote_fonts       = get_option( 'perf_remove_remote_fonts', true );
54
		$this->inline_always             = get_option( 'perf_inline_on_every_request', false );
0 ignored issues
show
Bug introduced by
The property inline_always does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
55
		$this->inline_scripts_and_styles = get_option( 'perf_inline_scripts_and_styles', true ) && ( $this->is_first_load || $this->inline_always );
56
		$this->async_scripts             = get_option( 'perf_async_scripts', true );
57
		$this->defer_scripts             = get_option( 'perf_defer_scripts', true );
58
		$this->move_scripts_above_css_in_header = true;
0 ignored issues
show
Bug introduced by
The property move_scripts_above_css_in_header does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
59
		$this->remove_core_emojis        = true;
0 ignored issues
show
Bug introduced by
The property remove_core_emojis does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
60
		$this->prevent_jetpack_implode_css = true;
0 ignored issues
show
Bug introduced by
The property prevent_jetpack_implode_css does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
61
		$this->inject_critical_css       = true;
62
		$this->preload_scripts           = true;
0 ignored issues
show
Bug introduced by
The property preload_scripts does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
63
		$this->minify_html               = true;
64
65
		if ( $this->minify_html ) {
66
			require_once dirname( __FILE__ ) . '/class.jetpack-perf-optimize-html.php';
67
			Jetpack_Perf_Optimize_HTML::instance();
68
		}
69
70
		if ( $this->remove_remote_fonts ) {
71
			add_filter( 'jetpack_perf_remove_script', array( $this, 'remove_external_font_scripts' ), 10, 3 );
72
			add_filter( 'jetpack_perf_remove_style', array( $this, 'remove_external_font_styles' ), 10, 3 );
73
		}
74
75
		add_action( 'wp_enqueue_scripts', array( $this, 'relocate_assets' ), PHP_INT_MAX );
76
77
		// relocate assets
78
//		add_filter( 'jetpack_perf_style_group', array( $this, 'set_style_groups' ), 10, 2 );
79
		add_filter( 'jetpack_perf_script_group', array( $this, 'set_script_groups' ), 10, 2 );
80
81
		if ( $this->inject_critical_css ) {
82
			add_action( 'wp_head', array( $this, 'render_critical_css' ), 0 );
83
		}
84
85
		if ( $this->preload_scripts ) {
86
			add_action( 'wp_print_scripts', array( $this, 'preload_scripts' ) );
87
		}
88
89
90
		// munge footer scripts
91
		//add_filter( 'wp_footer', array( $this, 'encode_footer_scripts' ), -PHP_INT_MAX );
92
93
		// necessary to catch some woocommerce scripts that get localized inside wp_print_scripts at pri 5
94
		//add_filter( 'wp_print_footer_scripts', array( $this, 'encode_footer_scripts' ), 9 );
95
96
		// in modern browsers (post-2008) there is a small performance win from
97
		// loading CSS after external scripts in the header, rather than before
98
		if ( $this->move_scripts_above_css_in_header ) {
99
			add_action( 'init', array( $this, 'move_styles_to_bottom_of_header' ), PHP_INT_MAX );
100
		}
101
102
		if ( $this->prevent_jetpack_implode_css ) {
103
			add_filter( 'jetpack_implode_frontend_css', '__return_false' );
104
		}
105
106
		// ensure dashicons is loading inline
107
		add_filter( 'jetpack_perf_async_style', function( $should_async, $handle, $src ) {
108
			if ( 'dashicons' === $handle ) {
109
				return false;
110
			}
111
			return $should_async;
112
		}, 10, 3 );
113
114
		add_filter( 'jetpack_perf_inline_style', function( $should_inline, $handle, $src ) {
115
			if ( 'dashicons' === $handle ) {
116
					return true;
117
			}
118
			return $should_inline;
119
		}, 10, 3 );
120
121
		add_filter( 'script_loader_src', array( $this, 'filter_inline_scripts' ), -100, 2 );
122
		add_filter( 'script_loader_tag', array( $this, 'print_inline_scripts' ), -100, 3 );
123
		add_filter( 'style_loader_src', array( $this, 'filter_inline_styles' ), -100, 2 );
124
		add_filter( 'style_loader_tag', array( $this, 'print_inline_styles' ), -100, 4 );
125
126
		add_action( 'init', array( $this, 'set_first_load_cookie' ) );
127
128
		/**
129
		 * Feature, theme and plugin-specific hacks
130
		 */
131
132
		// remove emoji detection - TODO a setting for this
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
133
		if ( $this->remove_core_emojis ) {
134
			add_action( 'init', array( $this, 'disable_emojis' ) );
135
		}
136
137
		// inline/defer/async stuff for Jetpack
138
		add_action( 'init', array( $this, 'optimize_jetpack' ) );
139
	}
140
141
	/** Disabling Emojis **/
142
	// improves page load performance
143
144
	function disable_emojis() {
145
		remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
146
		remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
147
		remove_action( 'embed_head', 'print_emoji_detection_script', 7 );
148
149
		remove_action( 'wp_print_styles', 'print_emoji_styles' );
150
		remove_action( 'admin_print_styles', 'print_emoji_styles' );
151
152
		remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
153
		remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
154
		remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
155
156
		add_filter( 'tiny_mce_plugins', array( $this, 'disable_emojis_tinymce' ) );
157
		add_filter( 'wp_resource_hints', array( $this, 'disable_emojis_remove_dns_prefetch' ), 10, 2 );
158
	}
159
160
	function optimize_jetpack() {
161
162
	}
163
164
	/**
165
	 * Filter function used to remove the tinymce emoji plugin.
166
	 *
167
	 * @param array $plugins
168
	 * @return array Difference betwen the two arrays
169
	 */
170
	function disable_emojis_tinymce( $plugins ) {
171
		if ( is_array( $plugins ) ) {
172
			return array_diff( $plugins, array( 'wpemoji' ) );
173
		} else {
174
			return array();
175
		}
176
	}
177
178
	/**
179
	 * Remove emoji CDN hostname from DNS prefetching hints.
180
	 *
181
	 * @param array $urls URLs to print for resource hints.
182
	 * @param string $relation_type The relation type the URLs are printed for.
183
	 * @return array Difference betwen the two arrays.
184
	 */
185
	function disable_emojis_remove_dns_prefetch( $urls, $relation_type ) {
186
		if ( 'dns-prefetch' == $relation_type ) {
187
			/** This filter is documented in wp-includes/formatting.php */
188
			$emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2/svg/' );
189
190
			$urls = array_diff( $urls, array( $emoji_svg_url ) );
191
		}
192
193
		return $urls;
194
	}
195
196
	// preload scripts we expect to find in the footer
197
	function preload_scripts() {
198
		global $wp_scripts;
199
		$wp_scripts->all_deps( $wp_scripts->queue, false );
200
				foreach( $wp_scripts->to_do as $handle ) {
201
						$registration = $wp_scripts->registered[$handle];
202
			// preload anything not async'd, since these scripts are likely to be a higher priority
203
			$is_footer_script = isset( $registration->extra['group'] ) && 1 == $registration->extra['group'];
204
			// TODO: this doesn't currently affect any scripts - will it ever?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
205
			if ( ! $this->should_async_script( $registration ) && $is_footer_script ) {
206
				echo '<link rel="preload" as="script" href="'. esc_attr( $registration->src )  .'" />';
207
			}
208
		}
209
	}
210
211
	function render_critical_css() {
212
		echo '<!-- critical.css --><style type="text/css">' . file_get_contents( plugin_dir_path( __FILE__ ) . 'critical.css' ) . '</style>';
213
	}
214
215
	// by default we only inline scripts+styles on first page load for a given user
216
	function set_first_load_cookie() {
217 View Code Duplication
		if ( ! isset( $_COOKIE['jetpack_perf_loaded'] ) ) {
218
			setcookie( 'jetpack_perf_loaded', '1', time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
219
		}
220
	}
221
222
	// this code essentially sets the default asset location to the footer rather than the head
223
	function relocate_assets() {
224
		global $wp_scripts;
225
226
		//error_log(print_r($wp_scripts,1));
227
228
		// fetch all deps for head
229
		$wp_scripts->all_deps( $wp_scripts->queue, false );
230 View Code Duplication
		foreach( $wp_scripts->to_do as $handle ) {
231
			$registration = $wp_scripts->registered[$handle];
232
			$asset_group = apply_filters( 'jetpack_perf_script_group', $registration->args, $handle );
233
234
			if ( $asset_group !== $registration->args ) {
235
				$registration->args = $asset_group;
236
				$wp_scripts->groups[$handle] = $asset_group;
237
			}
238
		}
239
240
		//$wp_scripts->done = array();
241
242
		global $wp_styles;
243
244
				// fetch all deps for head
245
				$wp_styles->all_deps( $wp_styles->queue, false,1 );
246 View Code Duplication
				foreach( $wp_styles->to_do as $handle ) {
247
						$registration = $wp_styles->registered[$handle];
248
						$asset_group = apply_filters( 'jetpack_perf_style_group', $wp_styles->groups[$handle], $handle );
249
250
						if ( $asset_group !== $wp_styles->groups[$handle] ) {
251
								$registration->args = $asset_group;
252
								$wp_styles->groups[$handle] = $asset_group;
253
						}
254
				}
255
256
	}
257
258
	// scripts that run after CSS <link>s in the header block waiting for the CSS to load
259
	// so we move styles as late as possible in the wp_head action to maximise the chance
260
	// of non-blocking rendering
261
	function move_styles_to_bottom_of_header() {
262
		remove_action( 'wp_head', 'wp_print_styles', 8 );
263
		add_action( 'wp_head', 'wp_print_styles', 999 );
264
	}
265
266
	function set_script_groups( $group, $handle ) {
267
		// move everything to the footer
268
		return 1;
269
		//error_log("set script for $handle in group $group");
270
		// force jquery into header, everything else can go in footer unless filtered elsewhere
271
		if ( in_array( $handle, array( 'jquery-core', 'jquery-migrate', 'jquery' ) ) ) {
0 ignored issues
show
Unused Code introduced by
//error_log("set script ...y'))) { return 0; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
272
			return 0;
273
		}
274
275
		// force scripts with deps into header
276
		if ( $this->script_has_deps( $handle ) ) {
277
			return 0;
278
		}
279
280
		if ( $group === NULL ) {
281
		//	error_log("force set $handle to 0");
282
			// set default location to header
283
			return 1;
284
		}
285
286
		//error_log("set $handle to $group");
287
288
		return $group;
289
	}
290
291
	function set_style_groups( $group, $handle ) {
292
		if ( in_array( $handle, array( 'genericons' ) ) ) {
293
			return 0;
294
		}
295
296
		if ( $group === NULL ) {
297
			return 1;
298
		}
299
300
		return $group;
301
	}
302
303
	// this is wild: inline scripts in the footer can block rendering, but we don't have a chance to intercept them in the header
304
	// so let's mark them as defer (which guarantees order) - which also requires adding deps with the localize script base64-encoded
305
306
	// so, I don't think this is a performance win - stuff in the footer is already "deferred" and forcing the defer attribute
307
	// only makes them load one after the other, rather than allowing the browser to do paralellisation
308
309
	// we're better off concatenating
310
	function encode_footer_scripts() {
311
		global $wp_scripts;
312
		$queued = $wp_scripts->all_deps( $wp_scripts->queue, false );
0 ignored issues
show
Unused Code introduced by
$queued is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
313
314
				foreach( $wp_scripts->to_do as $handle ) {
315
						$registration = $wp_scripts->registered[$handle];
316
			//error_log(print_r($registration,1));
317
			if ( isset( $registration->extra['data'] ) && $registration->extra['data'] ) {
318
				//register artificial dependency with param of defer
319
				$encoded_src = 'data:text/javascript;base64,' . base64_encode( $registration->extra['data'] );
320
				$data_handle = $handle . '_jp_data';
321
				$wp_scripts->add( $data_handle, $encoded_src, array(), false );
322
				$wp_scripts->add_data( $data_handle, 'jetpack-defer', true );
323
				$registration->deps[] = $data_handle;
324
				unset( $registration->extra['data'] );
325
			}
326
327
			$registration->extra['jetpack-defer'] = true;
328
			$registration->extra['jetpack-async'] = false;
329
				}
330
		$wp_scripts->to_do = array();
331
	}
332
333
	/** FILTERS **/
334
	public function remove_external_font_scripts( $should_remove, $handle, $asset_url ) {
335
		$font_script_url = 'http://use.typekit.com/';
336
		return strncmp( $asset_url, $font_script_url, strlen( $font_script_url ) ) === 0;
337
	}
338
339
	public function remove_external_font_styles( $should_remove, $handle, $asset_url ) {
340
		$font_url = 'https://fonts.googleapis.com';
341
		return strncmp( $asset_url, $font_url, strlen( $font_url ) ) === 0;
342
	}
343
344
	/** SCRIPTS **/
345 View Code Duplication
	public function filter_inline_scripts( $src, $handle ) {
346
		global $wp_scripts;
347
348
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
349
			return $src;
350
		}
351
352
		$script = $wp_scripts->registered[$handle];
353
354
		// reset src to empty - can't return empty string though because then it skips rendering the tag
355
		if ( $this->should_inline_script( $script ) ) {
356
			return '#';
357
		}
358
359
		return $src;
360
	}
361
362
	public function print_inline_scripts( $tag, $handle, $src ) {
363
		global $wp_scripts;
364
365
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
366
			return $tag;
367
		}
368
369
		$script = $wp_scripts->registered[$handle];
370
371
		if ( $this->should_remove_script( $script ) ) {
372
			return '';
373
		}
374
375
		if ( $this->should_inline_script( $script ) ) {
376
			$label = '<!-- ' . $script->src . ' -->';
377
			// base64-encoding a script into the src URL only makes sense if we intend to async or defer it
378
			if ( $this->should_defer_script( $script ) ) {
379
				$tag = $label . '<script defer type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
380
			} elseif ( $this->should_async_script( $script ) ) {
381
				$tag = $label . '<script async type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
382
			} else {
383
				$tag = $label . '<script type="text/javascript">' . file_get_contents( $script->extra['jetpack-inline-file'] ) . '</script>';
384
			}
385
		} else {
386
			if ( $this->should_defer_script( $script ) ) {
387
				$tag = preg_replace( '/<script /', '<script defer ', $tag );
388
			} elseif ( $this->should_async_script( $script ) ) {
389
				$tag = preg_replace( '/<script /', '<script async ', $tag );
390
			}
391
		}
392
393
		return $tag;
394
	}
395
396
	private function should_async_script( $script ) {
397
		// this could be dangerous if scripts have undeclared dependencies
398
		// only make scripts async if nothing depends on them
399
		// turns out this is a problem - lots of plugins import (say) jquery extensions and then use them in the page from inline scripts. Bah.
400
		// but if a script has been declared in the footer? hm maybe that's ok........
401
		$should_async_script = ! $this->script_has_deps( $script->handle ); //isset( $script->extra['group'] ) && $script->extra['group'] === 1;
402
403
		// you can override this logic by setting jetpack-async
404
		$should_async_script = $should_async_script || ( isset( $script->extra['jetpack-async'] ) && $script->extra['jetpack-async'] );
405
		return $this->async_scripts && apply_filters( 'jetpack_perf_async_script', $should_async_script, $script->handle, $script->src );
406
	}
407
408
	private function script_has_deps( $handle ) {
409
		global $wp_scripts;
410 View Code Duplication
				foreach ( $wp_scripts->to_do as $other_script_handle ) {
411
						$other_script = $wp_scripts->registered[ $other_script_handle ];
412
						if ( in_array( $handle, $other_script->deps ) ) {
413
				return true;
414
						}
415
				}
416
417
		return false;
418
	}
419
420
	private function should_defer_script( $script ) {
421
		global $wp_scripts;
422
423
		// if it's explicitly not in the footer, or we have Jetpack Defer set, and has no dependencies
424
		$should_defer_script = $script->args === 0;
425
426
		// only make scripts deferred if nothing depends on them
427 View Code Duplication
		foreach ( $wp_scripts->to_do as $other_script_handle ) {
428
			$other_script = $wp_scripts->registered[ $other_script_handle ];
429
			if ( in_array( $script->handle, $other_script->deps ) ) {
430
				$should_defer_script = false;
431
				break;
432
			}
433
		}
434
435
		$should_defer_script = $should_defer_script || ( isset( $script->extra['jetpack-defer'] ) && $script->extra['jetpack-defer'] );
436
		return $this->defer_scripts && apply_filters( 'jetpack_perf_defer_script', $should_defer_script, $script->handle, $script->src );
437
	}
438
439
	private function should_remove_script( $script ) {
440
		return $this->should_remove_asset( 'jetpack_perf_remove_script', $script );
441
	}
442
443
	private function should_inline_script( $script ) {
444
		return $this->inline_scripts_and_styles && $this->should_inline_asset( 'jetpack_perf_inline_script', $script );
445
	}
446
447
	/** STYLES **/
448 View Code Duplication
	public function filter_inline_styles( $src, $handle ) {
449
		global $wp_scripts;
450
451
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
452
			return $src;
453
		}
454
455
		$style = $wp_scripts->registered[$handle];
456
457
		if ( $this->should_inline_style( $style ) ) {
458
			return '#';
459
		}
460
461
		return $src;
462
	}
463
464
	public function print_inline_styles( $tag, $handle, $href, $media ) {
465
		global $wp_styles;
466
467
		if ( is_admin() || ! isset( $wp_styles->registered[$handle] ) ) {
468
			return $tag;
469
		}
470
471
		$style = $wp_styles->registered[$handle];
472
473
		// async styles use the new(-ish) preload syntax - should only be done if
474
		// critical CSS is enabled, since otherwise we'll end up with flash of unstyled content (FOUC)
475
		if ( $this->should_async_style( $style ) ) {
476
			// we async all styles if inline-critical-css is enabled
477
			$existing_tag = $tag;
478
			$tag = preg_replace( '/rel=[\'"]stylesheet[\'"]/', 'rel="preload"', $tag );
479
			$tag = preg_replace( '/<link /', '<link onload="this.rel=\'stylesheet\'" as="style" ', $tag );
480
			$tag .= '<noscript>' . $existing_tag . '</noscript>';
481
			return $tag;
482
		}
483
484
		if ( $this->should_inline_style( $style ) ) {
485
			$label = '<!-- ' . $style->src . ' -->';
486
			$css = $this->fix_css_urls( file_get_contents( $style->extra['jetpack-inline-file'] ), $style->src );
487
			return "$label<style type='text/css' media='$media'>$css</style>";
488
		}
489
490
		if ( $this->should_remove_style( $style ) ) {
491
			return '';
492
		}
493
494
		return $tag;
495
	}
496
497
	// we can async styles if we load critical CSS in the header
498
	private function should_async_style( $style ) {
499
		return apply_filters( 'jetpack_perf_async_style', $this->inject_critical_css, $style->handle, $style->src );
500
	}
501
502
	// for CSS urls()s, replace with base64-encoded content if smaller than a certain size
503
	public function fix_css_urls( $css, $css_url ) {
504
		$base = trailingslashit( dirname( $css_url ) );
505
		$base = str_replace( site_url(), '', $base );
506
507
		// reject absolute site_url
508
		if ( 'http' === substr( $base, 0, 4 ) ) {
509
			return $css;
510
		}
511
512
		return preg_replace_callback( '/url[\s]*\([\s]*["\']?[\s]*(?!https?:\/\/)(?!data:)(?!#)([^\)"\']*)["\']?\)/i', function( $matches ) use ( $base ) {
513
			// TODO: embed data-encoded file, for files smaller than certain size?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
514
			$url = $this->rel2abspath( $matches[1], $base );
515
516
			// sneaky - see if it's small enough that it should be encoded and placed inline
517
			$local_path = $this->local_url_to_file_path( $url );
518
			if ( file_exists( $local_path ) && filesize( $local_path ) < self::INLINE_CSS_URL_MAX_SIZE_BYTES && ( $mime_type = wp_check_filetype( $url )['type'] ) ) {
519
				$url = 'data:' . $mime_type . ';base64,' . base64_encode( file_get_contents( $local_path ) );
520
			}
521
522
			return 'url('.$url.')';
523
		}, $css );
524
	}
525
526
	// see: http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php
527
	private function rel2abspath( $rel, $path) {
528
		/* remove non-directory element from path */
529
		$path = preg_replace( '#/[^/]*$#', '', $path );
530
531
		/* destroy path if relative url points to root */
532
		if( $rel[0] == '/' )
533
			$path = '';
534
535
		/* dirty absolute URL */
536
		$abs = '';
537
538
		$abs .= $path . '/' . $rel;
539
540
		/* replace '//' or '/./' or '/foo/../' with '/' */
541
		$re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
542
		for( $n=1; $n>0; $abs = preg_replace( $re, '/', $abs, -1, $n ) ) {}
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
543
544
		/* absolute path is ready! */
545
		return $abs;
546
	}
547
548
	private function should_inline_style( $style ) {
549
		return $this->inline_scripts_and_styles && $this->should_inline_asset( 'jetpack_perf_inline_style', $style );
550
	}
551
552
	private function should_remove_style( $style ) {
553
		return $this->should_remove_asset( 'jetpack_perf_remove_style', $style );
554
	}
555
556
	/** shared code **/
557
558
	private function should_inline_asset( $filter, $dependency ) {
559
		// inline anything local, with a src starting with /, or starting with site_url
560
		$site_url = site_url();
561
562
		$is_local_url = ( strncmp( $dependency->src, '/', 1 ) === 0 && strncmp( $dependency->src, '//', 2 ) !== 0 )
563
			|| strpos( $dependency->src, $site_url ) === 0;
564
565
		if ( $is_local_url && ! isset( $dependency->extra['jetpack-inline'] ) ) {
566
			$dependency->extra['jetpack-inline'] = true;
567
			$dependency->extra['jetpack-inline-file'] = $this->local_url_to_file_path( $dependency->src );
568
		}
569
570
		// early exit if the file doesn't exist or is too large
571
		if ( ! isset( $dependency->extra['jetpack-inline-file'] ) || ! file_exists( $dependency->extra['jetpack-inline-file'] ) ) {
572
			return false;
573
		}
574
575
		// only inline if we don't have a conditional
576
		$should_inline = ! isset( $dependency->extra['conditional'] )
577
			&& isset( $dependency->extra['jetpack-inline'] )
578
			&& $dependency->extra['jetpack-inline']
579
			&& filesize( $dependency->extra['jetpack-inline-file'] ) < self::INLINE_ASSET_MAX_SIZE_BYTES;
580
581
		return apply_filters( $filter, $should_inline, $dependency->handle, $dependency->src );
582
	}
583
584
	private function local_url_to_file_path( $url ) {
585
		$path = untrailingslashit( ABSPATH ) . parse_url( $url )['path'];
586
		if ( '/' !== DIRECTORY_SEPARATOR )
587
				 	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
588
		return $path;
589
	}
590
591
	private function should_remove_asset( $filter, $dependency ) {
592
		return apply_filters( $filter, false, $dependency->handle, $dependency->src );
593
	}
594
595
	/**
596
	 * if inline assets are enabled, renders inline
597
	 * TODO: enable this just for certain paths/patterns/filetypes
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
598
	 * This is actually currently unused
599
	 */
600
	 public function register_inline_script( $handle, $file, $plugin_file, $deps = false, $ver = false, $in_footer = false ) {
601
		$registered = wp_register_script( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $in_footer );
602
603 View Code Duplication
		if ( $registered ) {
604
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
605
			wp_script_add_data( $handle, 'jetpack-inline', true );
606
			wp_script_add_data( $handle, 'jetpack-inline-file', $file_full_path );
607
		}
608
609
		return $registered;
610
	}
611
612
	/**
613
	 * if inline assets are enabled, renders inline
614
	 * TODO: enable this just for certain paths/patterns/filetypes
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
615
	 * This is actually currently unused
616
	 */
617
	public function register_inline_style( $handle, $file, $plugin_file, $deps = array(), $ver = false, $media = 'all' ) {
618
		$registered = wp_register_style( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $media );
619
620 View Code Duplication
		if ( $registered ) {
621
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
622
			wp_style_add_data( $handle, 'jetpack-inline', true );
623
			wp_style_add_data( $handle, 'jetpack-inline-file', $file_full_path );
624
		}
625
	}
626
}
627