Completed
Push — add/pwa ( 78a5c3...f00215 )
by
unknown
07:23
created

should_inline_asset()   C

Complexity

Conditions 8
Paths 10

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 12
nc 10
nop 2
dl 0
loc 23
rs 6.1403
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
	private $concat_local_styles = false;
20
	private $concat_style_groups = array();
21
22
	const INLINE_ASSET_MAX_SIZE_BYTES = 50 * 1024; // 10kb
23
	const INLINE_CSS_URL_MAX_SIZE_BYTES = 5 * 1024; // 5kb
24
25
	/**
26
	 * Singleton implementation
27
	 *
28
	 * @return object
29
	 */
30
	public static function instance() {
31
		if ( ! is_a( self::$__instance, 'Jetpack_Perf_Optimize_Assets' ) ) {
32
			self::$__instance = new Jetpack_Perf_Optimize_Assets();
33
		}
34
35
		return self::$__instance;
36
	}
37
38
	public function disable_for_request() {
39
		$this->remove_remote_fonts = false;
40
		$this->inline_scripts_and_styles = false;
41
		$this->async_scripts = false;
42
		$this->defer_scripts = false;
43
	}
44
45
	/**
46
	 * 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...
47
	 */
48
49
	/**
50
	 * Registers actions
51
	 */
52
	private function __construct() {
53
//		global $concatenate_scripts;
54
//		$concatenate_scripts = true;
55
		$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...
56
		$this->remove_remote_fonts       = get_option( 'perf_remove_remote_fonts', true );
57
		$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...
58
		$this->inline_scripts_and_styles = get_option( 'perf_inline_scripts_and_styles', true ) && ( $this->is_first_load || $this->inline_always );
59
		$this->async_scripts             = get_option( 'perf_async_scripts', true );
60
		$this->defer_scripts             = get_option( 'perf_defer_scripts', true );
61
		$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...
62
		$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...
63
		$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...
64
		$this->inject_critical_css       = true;
65
		$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...
66
		$this->minify_html               = false;
67
		$this->concat_local_styles       = true;
68
		$this->cdn_server                = 'https://wordpress.com';
0 ignored issues
show
Bug introduced by
The property cdn_server 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...
69
70
		if ( $this->minify_html ) {
71
			require_once dirname( __FILE__ ) . '/class.jetpack-perf-optimize-html.php';
72
			Jetpack_Perf_Optimize_HTML::instance();
73
		}
74
75
		if ( $this->remove_remote_fonts ) {
76
			add_filter( 'jetpack_perf_remove_script', array( $this, 'remove_external_font_scripts' ), 10, 3 );
77
			add_filter( 'jetpack_perf_remove_style', array( $this, 'remove_external_font_styles' ), 10, 3 );
78
		}
79
80
		add_action( 'wp_enqueue_scripts', array( $this, 'relocate_assets' ), PHP_INT_MAX );
81
82
		// relocate assets
83
//		add_filter( 'jetpack_perf_style_group', array( $this, 'set_style_groups' ), 10, 2 );
84
//		add_filter( 'jetpack_perf_script_group', array( $this, 'set_script_groups' ), 10, 2 );
85
86
		if ( $this->inject_critical_css ) {
87
			add_action( 'wp_head', array( $this, 'render_critical_css' ), 0 );
88
		}
89
90
		if ( $this->concat_local_styles ) {
91
			//hooo boy
92
			add_filter( 'jetpack_perf_concat_style', '__return_true', 10  );
93
			add_action( 'wp_head', array( $this, 'render_concatenated_styles_head' ), PHP_INT_MAX );
94
			add_action( 'wp_footer', array( $this, 'render_concatenated_styles_footer' ), PHP_INT_MAX );
95
		}
96
97
		if ( $this->preload_scripts ) {
98
			add_action( 'wp_print_scripts', array( $this, 'preload_scripts' ) );
99
		}
100
101
102
		// munge footer scripts
103
		//add_filter( 'wp_footer', array( $this, 'encode_footer_scripts' ), -PHP_INT_MAX );
104
105
		// necessary to catch some woocommerce scripts that get localized inside wp_print_scripts at pri 5
106
		//add_filter( 'wp_print_footer_scripts', array( $this, 'encode_footer_scripts' ), 9 );
107
108
		// in modern browsers (post-2008) there is a small performance win from
109
		// loading CSS after external scripts in the header, rather than before
110
		if ( $this->move_scripts_above_css_in_header ) {
111
			add_action( 'init', array( $this, 'move_styles_to_bottom_of_header' ), PHP_INT_MAX );
112
		}
113
114
		if ( $this->prevent_jetpack_implode_css ) {
115
			add_filter( 'jetpack_implode_frontend_css', '__return_false' );
116
		}
117
118
		// ensure dashicons is loading inline
119
		add_filter( 'jetpack_perf_async_style', function( $should_async, $handle, $src ) {
120
			if ( 'dashicons' === $handle ) {
121
				return false;
122
			}
123
			return $should_async;
124
		}, 10, 3 );
125
126
		add_filter( 'jetpack_perf_inline_style', function( $should_inline, $handle, $src ) {
127
			if ( 'dashicons' === $handle ) {
128
					return true;
129
			}
130
			return $should_inline;
131
		}, 10, 3 );
132
133
		add_filter( 'script_loader_src', array( $this, 'filter_inline_scripts' ), -100, 2 );
134
		add_filter( 'script_loader_tag', array( $this, 'print_inline_scripts' ), -100, 3 );
135
		add_filter( 'style_loader_src', array( $this, 'filter_inline_styles' ), -100, 2 );
136
		add_filter( 'style_loader_tag', array( $this, 'print_inline_styles' ), -100, 4 );
137
138
		add_action( 'init', array( $this, 'set_first_load_cookie' ) );
139
140
		/**
141
		 * Feature, theme and plugin-specific hacks
142
		 */
143
144
		// 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...
145
		if ( $this->remove_core_emojis ) {
146
			add_action( 'init', array( $this, 'disable_emojis' ) );
147
		}
148
149
		// inline/defer/async stuff for Jetpack
150
		add_action( 'init', array( $this, 'optimize_jetpack' ) );
151
	}
152
153
	/** Disabling Emojis **/
154
	// improves page load performance
155
156
	function disable_emojis() {
157
		remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
158
		remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
159
		remove_action( 'embed_head', 'print_emoji_detection_script', 7 );
160
161
		remove_action( 'wp_print_styles', 'print_emoji_styles' );
162
		remove_action( 'admin_print_styles', 'print_emoji_styles' );
163
164
		remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
165
		remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
166
		remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
167
168
		add_filter( 'tiny_mce_plugins', array( $this, 'disable_emojis_tinymce' ) );
169
		add_filter( 'wp_resource_hints', array( $this, 'disable_emojis_remove_dns_prefetch' ), 10, 2 );
170
	}
171
172
	function optimize_jetpack() {
173
174
	}
175
176
	function render_concatenated_styles_head() {
177
		if ( isset( $this->concat_style_groups[0] ) ) {
178
			$this->render_concatenated_styles( $this->concat_style_groups[0] );
179
		}
180
	}
181
182
	function render_concatenated_styles_footer() {
183
		if ( isset( $this->concat_style_groups[1] ) ) {
184
			$this->render_concatenated_styles( $this->concat_style_groups[1] );
185
		}
186
	}
187
188
	private function render_concatenated_styles( $styles ) {
189
		// special URL to concatenation service
190
		foreach( $styles as $media => $urls ) {
191
			$cdn_url = $this->cdn_server . '/css?files=' . implode( ',', array_map( 'urlencode', $urls ) );
192
			if ( $this->inject_critical_css ) {
193
				echo '<!-- jetpack concat --><link rel="preload" onload="this.rel=\'stylesheet\'" as="style" type="text/css" media="' . $media . '" src="' . $cdn_url . '"/>';
194
			} else {
195
				echo '<!-- jetpack concat --><link rel="stylesheet" type="text/css" media="' . $media . '" src="' . $cdn_url . '"/>';
196
			}
197
		}
198
	}
199
200
	/**
201
	 * Filter function used to remove the tinymce emoji plugin.
202
	 *
203
	 * @param array $plugins
204
	 * @return array Difference betwen the two arrays
205
	 */
206
	function disable_emojis_tinymce( $plugins ) {
207
		if ( is_array( $plugins ) ) {
208
			return array_diff( $plugins, array( 'wpemoji' ) );
209
		} else {
210
			return array();
211
		}
212
	}
213
214
	/**
215
	 * Remove emoji CDN hostname from DNS prefetching hints.
216
	 *
217
	 * @param array $urls URLs to print for resource hints.
218
	 * @param string $relation_type The relation type the URLs are printed for.
219
	 * @return array Difference betwen the two arrays.
220
	 */
221
	function disable_emojis_remove_dns_prefetch( $urls, $relation_type ) {
222
		if ( 'dns-prefetch' == $relation_type ) {
223
			/** This filter is documented in wp-includes/formatting.php */
224
			$emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2/svg/' );
225
226
			$urls = array_diff( $urls, array( $emoji_svg_url ) );
227
		}
228
229
		return $urls;
230
	}
231
232
	// preload scripts we expect to find in the footer
233
	function preload_scripts() {
234
		global $wp_scripts;
235
		$wp_scripts->all_deps( $wp_scripts->queue, false );
236
				foreach( $wp_scripts->to_do as $handle ) {
237
						$registration = $wp_scripts->registered[$handle];
238
			// preload anything not async'd, since these scripts are likely to be a higher priority
239
			$is_footer_script = isset( $registration->extra['group'] ) && 1 == $registration->extra['group'];
240
			// 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...
241
			if ( ! $this->should_async_script( $registration ) && $is_footer_script && ! $this->should_inline_script( $registration ) ) {
242
				echo '<link rel="preload" as="script" href="'. esc_attr( $registration->src )  .'" />';
243
			}
244
		}
245
	}
246
247
	function render_critical_css() {
248
		echo '<!-- critical.css --><style type="text/css">' . file_get_contents( plugin_dir_path( __FILE__ ) . 'critical.css' ) . '</style>';
249
	}
250
251
	// by default we only inline scripts+styles on first page load for a given user
252
	function set_first_load_cookie() {
253 View Code Duplication
		if ( ! isset( $_COOKIE['jetpack_perf_loaded'] ) ) {
254
			setcookie( 'jetpack_perf_loaded', '1', time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
255
		}
256
	}
257
258
	// this code essentially sets the default asset location to the footer rather than the head
259
	function relocate_assets() {
260
		global $wp_scripts;
261
262
		//error_log(print_r($wp_scripts,1));
263
264
		// fetch all deps for head
265
		$wp_scripts->all_deps( $wp_scripts->queue, false );
266 View Code Duplication
		foreach( $wp_scripts->to_do as $handle ) {
267
			$registration = $wp_scripts->registered[$handle];
268
			$asset_group = apply_filters( 'jetpack_perf_script_group', $registration->args, $handle );
269
270
			if ( $asset_group !== $registration->args ) {
271
				$registration->args = $asset_group;
272
				$wp_scripts->groups[$handle] = $asset_group;
273
			}
274
		}
275
276
		//$wp_scripts->done = array();
277
278
		global $wp_styles;
279
280
				// fetch all deps for head
281
				$wp_styles->all_deps( $wp_styles->queue, false,1 );
282 View Code Duplication
				foreach( $wp_styles->to_do as $handle ) {
283
						$registration = $wp_styles->registered[$handle];
284
						$asset_group = apply_filters( 'jetpack_perf_style_group', $wp_styles->groups[$handle], $handle );
285
286
						if ( $asset_group !== $wp_styles->groups[$handle] ) {
287
								$registration->args = $asset_group;
288
								$wp_styles->groups[$handle] = $asset_group;
289
						}
290
				}
291
292
	}
293
294
	// scripts that run after CSS <link>s in the header block waiting for the CSS to load
295
	// so we move styles as late as possible in the wp_head action to maximise the chance
296
	// of non-blocking rendering
297
	function move_styles_to_bottom_of_header() {
298
		remove_action( 'wp_head', 'wp_print_styles', 8 );
299
		add_action( 'wp_head', 'wp_print_styles', 999 );
300
	}
301
302
	function set_script_groups( $group, $handle ) {
303
		return $group;
304
		// move everything to the footer
305
		//error_log("set script for $handle in group $group");
306
		// force jquery into header, everything else can go in footer unless filtered elsewhere
307
		if ( in_array( $handle, array( 'jquery-core', 'jquery-migrate', 'jquery' ) ) ) {
0 ignored issues
show
Unused Code introduced by
// move everything to th...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...
308
			return 0;
309
		}
310
311
		// force scripts with deps into header
312
		if ( $this->script_has_deps( $handle ) ) {
313
			return 0;
314
		}
315
316
		if ( $group === NULL ) {
317
		//	error_log("force set $handle to 0");
318
			// set default location to header
319
			return 1;
320
		}
321
322
		//error_log("set $handle to $group");
323
324
		return $group;
325
	}
326
327
	function set_style_groups( $group, $handle ) {
328
		if ( in_array( $handle, array( 'genericons' ) ) ) {
329
			return 0;
330
		}
331
332
		if ( $group === NULL ) {
333
			return 1;
334
		}
335
336
		return $group;
337
	}
338
339
	// this is wild: inline scripts in the footer can block rendering, but we don't have a chance to intercept them in the header
340
	// so let's mark them as defer (which guarantees order) - which also requires adding deps with the localize script base64-encoded
341
342
	// so, I don't think this is a performance win - stuff in the footer is already "deferred" and forcing the defer attribute
343
	// only makes them load one after the other, rather than allowing the browser to do paralellisation
344
345
	// we're better off concatenating
346
	function encode_footer_scripts() {
347
		global $wp_scripts;
348
		$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...
349
350
				foreach( $wp_scripts->to_do as $handle ) {
351
						$registration = $wp_scripts->registered[$handle];
352
			//error_log(print_r($registration,1));
353
			if ( isset( $registration->extra['data'] ) && $registration->extra['data'] ) {
354
				//register artificial dependency with param of defer
355
				$encoded_src = 'data:text/javascript;base64,' . base64_encode( $registration->extra['data'] );
356
				$data_handle = $handle . '_jp_data';
357
				$wp_scripts->add( $data_handle, $encoded_src, array(), false );
358
				$wp_scripts->add_data( $data_handle, 'jetpack-defer', true );
359
				$registration->deps[] = $data_handle;
360
				unset( $registration->extra['data'] );
361
			}
362
363
			$registration->extra['jetpack-defer'] = true;
364
			$registration->extra['jetpack-async'] = false;
365
				}
366
		$wp_scripts->to_do = array();
367
	}
368
369
	/** FILTERS **/
370
	public function remove_external_font_scripts( $should_remove, $handle, $asset_url ) {
371
		$font_script_url = 'http://use.typekit.com/';
372
		return strncmp( $asset_url, $font_script_url, strlen( $font_script_url ) ) === 0;
373
	}
374
375
	public function remove_external_font_styles( $should_remove, $handle, $asset_url ) {
376
		$font_url = 'https://fonts.googleapis.com';
377
		return strncmp( $asset_url, $font_url, strlen( $font_url ) ) === 0;
378
	}
379
380
	/** SCRIPTS **/
381 View Code Duplication
	public function filter_inline_scripts( $src, $handle ) {
382
		global $wp_scripts;
383
384
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
385
			return $src;
386
		}
387
388
		$script = $wp_scripts->registered[$handle];
389
390
		// reset src to empty - can't return empty string though because then it skips rendering the tag
391
		if ( $this->should_inline_script( $script ) ) {
392
			return '#';
393
		}
394
395
		return $src;
396
	}
397
398
	public function print_inline_scripts( $tag, $handle, $src ) {
399
		global $wp_scripts;
400
401
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
402
			return $tag;
403
		}
404
405
		$script = $wp_scripts->registered[$handle];
406
407
		// if ( $this->should_concat_script( $script ) ) {
408
		// 	$this->buffer_script( $script );
409
		// 	return '';
410
		// }
411
412
		if ( $this->should_remove_script( $script ) ) {
413
			return '';
414
		}
415
416
		if ( $this->should_inline_script( $script ) ) {
417
			$label = '<!-- ' . $script->src . ' -->';
418
			// base64-encoding a script into the src URL only makes sense if we intend to async or defer it
419
			if ( $this->should_defer_script( $script ) ) {
420
				$tag = $label . '<script defer type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
421
			} elseif ( $this->should_async_script( $script ) ) {
422
				$tag = $label . '<script async type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
423
			} else {
424
				$tag = $label . '<script type="text/javascript">' . file_get_contents( $script->extra['jetpack-inline-file'] ) . '</script>';
425
			}
426
		} else {
427
			if ( $this->should_defer_script( $script ) ) {
428
				$tag = preg_replace( '/<script /', '<script defer ', $tag );
429
			} elseif ( $this->should_async_script( $script ) ) {
430
				$tag = preg_replace( '/<script /', '<script async ', $tag );
431
			}
432
		}
433
434
		return $tag;
435
	}
436
437
	private function should_async_script( $script ) {
438
		// this could be dangerous if scripts have undeclared dependencies
439
		// only make scripts async if nothing depends on them
440
		// turns out this is a problem - lots of plugins import (say) jquery extensions and then use them in the page from inline scripts. Bah.
441
		// but if a script has been declared in the footer? hm maybe that's ok........
442
		$should_async_script = ! $this->script_has_deps( $script->handle ) && isset( $script->extra['group'] ) && $script->extra['group'] === 1;
443
444
		// you can override this logic by setting jetpack-async
445
		$should_async_script = $should_async_script || ( isset( $script->extra['jetpack-async'] ) && $script->extra['jetpack-async'] );
446
		return $this->async_scripts && apply_filters( 'jetpack_perf_async_script', $should_async_script, $script->handle, $script->src );
447
	}
448
449
	private function script_has_deps( $handle ) {
450
		global $wp_scripts;
451 View Code Duplication
				foreach ( $wp_scripts->to_do as $other_script_handle ) {
452
						$other_script = $wp_scripts->registered[ $other_script_handle ];
453
						if ( in_array( $handle, $other_script->deps ) ) {
454
				return true;
455
						}
456
				}
457
458
		return false;
459
	}
460
461
	private function should_defer_script( $script ) {
462
		global $wp_scripts;
463
464
		// if it's explicitly not in the footer, or we have Jetpack Defer set, and has no dependencies
465
		$should_defer_script = $script->args === 0;
466
467
		// only make scripts deferred if nothing depends on them
468 View Code Duplication
		foreach ( $wp_scripts->to_do as $other_script_handle ) {
469
			$other_script = $wp_scripts->registered[ $other_script_handle ];
470
			if ( in_array( $script->handle, $other_script->deps ) ) {
471
				$should_defer_script = false;
472
				break;
473
			}
474
		}
475
476
		$should_defer_script = $should_defer_script || ( isset( $script->extra['jetpack-defer'] ) && $script->extra['jetpack-defer'] );
477
		return $this->defer_scripts && apply_filters( 'jetpack_perf_defer_script', $should_defer_script, $script->handle, $script->src );
478
	}
479
480
	private function should_remove_script( $script ) {
481
		return $this->should_remove_asset( 'jetpack_perf_remove_script', $script );
482
	}
483
484
	private function should_inline_script( $script ) {
485
		return $this->inline_scripts_and_styles && $this->should_inline_asset( 'jetpack_perf_inline_script', $script );
486
	}
487
488
	/** STYLES **/
489 View Code Duplication
	public function filter_inline_styles( $src, $handle ) {
490
		global $wp_scripts;
491
492
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
493
			return $src;
494
		}
495
496
		$style = $wp_scripts->registered[$handle];
497
498
		if ( $this->should_inline_style( $style ) ) {
499
			return '#';
500
		}
501
502
		return $src;
503
	}
504
505
	public function print_inline_styles( $tag, $handle, $href, $media ) {
506
		global $wp_styles;
507
508
		if ( is_admin() || ! isset( $wp_styles->registered[$handle] ) ) {
509
			return $tag;
510
		}
511
512
		$style = $wp_styles->registered[$handle];
513
514
		if ( $this->should_concat_style( $style ) ) {
515
			$this->buffer_style( $style );
516
			return '';
517
		}
518
519
		// async styles use the new(-ish) preload syntax - should only be done if
520
		// critical CSS is enabled, since otherwise we'll end up with flash of unstyled content (FOUC)
521
		if ( $this->should_async_style( $style ) ) {
522
			// we async all styles if inline-critical-css is enabled
523
			$existing_tag = $tag;
524
			$tag = preg_replace( '/rel=[\'"]stylesheet[\'"]/', 'rel="preload"', $tag );
525
			$tag = preg_replace( '/<link /', '<link onload="this.rel=\'stylesheet\'" as="style" ', $tag );
526
			$tag .= '<noscript>' . $existing_tag . '</noscript>';
527
			return $tag;
528
		}
529
530
		if ( $this->should_inline_style( $style ) ) {
531
			$label = '<!-- ' . $style->src . ' -->';
532
			$css = $this->fix_css_urls( file_get_contents( $style->extra['jetpack-inline-file'] ), $style->src );
533
			return "$label<style type='text/css' media='$media'>$css</style>";
534
		}
535
536
		if ( $this->should_remove_style( $style ) ) {
537
			return '';
538
		}
539
540
		return $tag;
541
	}
542
543
	private function buffer_style( $style ) {
544
		$group = isset( $style->extra['group'] ) ? $style->extra['group'] : 0;
545
		$media = $style->args;
546
547
		if ( ! $media ) {
548
			$media = 'all';
549
		}
550
551
		if ( ! isset( $this->concat_style_groups[$group] ) ) {
552
			$this->concat_style_groups[$group] = array();
553
		}
554
555
		if ( ! isset( $this->concat_style_groups[$group][$media] ) ) {
556
			$this->concat_style_groups[$group][$media] = array();
557
		}
558
559
		$local_url = str_replace( untrailingslashit( site_url() ), '', $style->src );
560
		$this->concat_style_groups[$group][$media][] = $local_url;
561
	}
562
563
	private function should_concat_style( $style ) {
564
		// only concat local styles
565
		$is_local       = $this->is_local_url( $style->src );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 7 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
566
		// don't concat conditional styles
567
		$is_conditional = isset( $style->extra['conditional'] );
568
		return apply_filters( 'jetpack_perf_concat_style', $this->concat_local_styles && $is_local && ! $is_conditional, $style->handle, $style->src );
569
	}
570
571
	// we can async styles if we load critical CSS in the header
572
	private function should_async_style( $style ) {
573
		return apply_filters( 'jetpack_perf_async_style', $this->inject_critical_css, $style->handle, $style->src );
574
	}
575
576
	// for CSS urls()s, replace with base64-encoded content if smaller than a certain size
577
	public function fix_css_urls( $css, $css_url ) {
578
		$base = trailingslashit( dirname( $css_url ) );
579
		$base = str_replace( site_url(), '', $base );
580
581
		// reject absolute site_url
582
		if ( 'http' === substr( $base, 0, 4 ) ) {
583
			return $css;
584
		}
585
586
		return preg_replace_callback( '/url[\s]*\([\s]*["\']?[\s]*(?!https?:\/\/)(?!data:)(?!#)([^\)"\']*)["\']?\)/i', function( $matches ) use ( $base ) {
587
			// 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...
588
			$url = $this->rel2abspath( $matches[1], $base );
589
590
			// sneaky - see if it's small enough that it should be encoded and placed inline
591
			$local_path = $this->local_url_to_file_path( $url );
592
			if ( file_exists( $local_path ) && filesize( $local_path ) < self::INLINE_CSS_URL_MAX_SIZE_BYTES && ( $mime_type = wp_check_filetype( $url )['type'] ) ) {
593
				$url = 'data:' . $mime_type . ';base64,' . base64_encode( file_get_contents( $local_path ) );
594
			}
595
596
			return 'url('.$url.')';
597
		}, $css );
598
	}
599
600
	// see: http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php
601
	private function rel2abspath( $rel, $path) {
602
		/* remove non-directory element from path */
603
		$path = preg_replace( '#/[^/]*$#', '', $path );
604
605
		/* destroy path if relative url points to root */
606
		if( $rel[0] == '/' )
607
			$path = '';
608
609
		/* dirty absolute URL */
610
		$abs = '';
611
612
		$abs .= $path . '/' . $rel;
613
614
		/* replace '//' or '/./' or '/foo/../' with '/' */
615
		$re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
616
		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...
617
618
		/* absolute path is ready! */
619
		return $abs;
620
	}
621
622
	private function should_inline_style( $style ) {
623
		return $this->inline_scripts_and_styles && $this->should_inline_asset( 'jetpack_perf_inline_style', $style );
624
	}
625
626
	private function should_remove_style( $style ) {
627
		return $this->should_remove_asset( 'jetpack_perf_remove_style', $style );
628
	}
629
630
	/** shared code **/
631
632
	private function is_local_url( $url ) {
633
		$site_url = site_url();
634
		return ( strncmp( $url, '/', 1 ) === 0 && strncmp( $url, '//', 2 ) !== 0 )
635
			|| strpos( $url, $site_url ) === 0;
636
	}
637
638
	private function should_inline_asset( $filter, $dependency ) {
639
		// inline anything local, with a src starting with /, or starting with site_url
640
641
		$is_local_url = $this->is_local_url( $dependency->src );
642
643
		if ( $is_local_url && ! isset( $dependency->extra['jetpack-inline'] ) ) {
644
			$dependency->extra['jetpack-inline'] = true;
645
			$dependency->extra['jetpack-inline-file'] = $this->local_url_to_file_path( $dependency->src );
646
		}
647
648
		// early exit if the file doesn't exist or is too large
649
		if ( ! isset( $dependency->extra['jetpack-inline-file'] ) || ! file_exists( $dependency->extra['jetpack-inline-file'] ) ) {
650
			return false;
651
		}
652
653
		// only inline if we don't have a conditional
654
		$should_inline = ! isset( $dependency->extra['conditional'] )
655
			&& isset( $dependency->extra['jetpack-inline'] )
656
			&& $dependency->extra['jetpack-inline']
657
			&& filesize( $dependency->extra['jetpack-inline-file'] ) < self::INLINE_ASSET_MAX_SIZE_BYTES;
658
659
		return apply_filters( $filter, $should_inline, $dependency->handle, $dependency->src );
660
	}
661
662
	private function local_url_to_file_path( $url ) {
663
		$path = untrailingslashit( ABSPATH ) . parse_url( $url )['path'];
664
		if ( '/' !== DIRECTORY_SEPARATOR )
665
				 	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
666
		return $path;
667
	}
668
669
	private function should_remove_asset( $filter, $dependency ) {
670
		return apply_filters( $filter, false, $dependency->handle, $dependency->src );
671
	}
672
673
	/**
674
	 * if inline assets are enabled, renders inline
675
	 * 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...
676
	 * This is actually currently unused
677
	 */
678
	 public function register_inline_script( $handle, $file, $plugin_file, $deps = false, $ver = false, $in_footer = false ) {
679
		$registered = wp_register_script( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $in_footer );
680
681 View Code Duplication
		if ( $registered ) {
682
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
683
			wp_script_add_data( $handle, 'jetpack-inline', true );
684
			wp_script_add_data( $handle, 'jetpack-inline-file', $file_full_path );
685
		}
686
687
		return $registered;
688
	}
689
690
	/**
691
	 * if inline assets are enabled, renders inline
692
	 * 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...
693
	 * This is actually currently unused
694
	 */
695
	public function register_inline_style( $handle, $file, $plugin_file, $deps = array(), $ver = false, $media = 'all' ) {
696
		$registered = wp_register_style( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $media );
697
698 View Code Duplication
		if ( $registered ) {
699
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
700
			wp_style_add_data( $handle, 'jetpack-inline', true );
701
			wp_style_add_data( $handle, 'jetpack-inline-file', $file_full_path );
702
		}
703
	}
704
}
705