Completed
Push — add/pwa ( bbbc99...d2805e )
by
unknown
07:38
created

should_inline_asset()   C

Complexity

Conditions 11
Paths 36

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 15
nc 36
nop 3
dl 0
loc 26
rs 5.2653
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	const INLINE_ASSET_MAX_SIZE_BYTES = 50 * 1024; // 10kb
19
	const INLINE_CSS_URL_MAX_SIZE_BYTES = 5 * 1024; // 5kb
20
21
	/**
22
	 * Singleton implementation
23
	 *
24
	 * @return object
25
	 */
26
	public static function instance() {
27
		if ( ! is_a( self::$__instance, 'Jetpack_Perf_Optimize_Assets' ) ) {
28
			self::$__instance = new Jetpack_Perf_Optimize_Assets();
29
		}
30
31
		return self::$__instance;
32
	}
33
34
	public function disable_for_request() {
35
		$this->remove_remote_fonts = false;
36
		$this->inline_scripts_and_styles = false;
37
		$this->async_scripts = false;
38
		$this->defer_scripts = false;
39
	}
40
41
	/**
42
	 * 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...
43
	 */
44
45
	/**
46
	 * Registers actions
47
	 */
48
	private function __construct() {
49
//		global $concatenate_scripts;
50
//		$concatenate_scripts = true;
51
		$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...
52
		$this->remove_remote_fonts       = get_option( 'perf_remove_remote_fonts', true );
53
		$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...
54
		$this->inline_scripts_and_styles = get_option( 'perf_inline_scripts_and_styles', true ) && ( $this->is_first_load || $this->inline_always );
55
		$this->async_scripts             = get_option( 'perf_async_scripts', true );
56
		$this->defer_scripts             = get_option( 'perf_defer_scripts', true );
57
		$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...
58
		$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...
59
		$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...
60
		$this->inject_critical_css       = true;
61
		$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...
62
63
		if ( $this->remove_remote_fonts ) {
64
			add_filter( 'jetpack_perf_remove_script', array( $this, 'remove_external_font_scripts' ), 10, 3 );
65
			add_filter( 'jetpack_perf_remove_style', array( $this, 'remove_external_font_styles' ), 10, 3 );
66
		}
67
68
		add_action( 'wp_enqueue_scripts', array( $this, 'relocate_assets' ), PHP_INT_MAX );
69
70
		// relocate assets
71
//		add_filter( 'jetpack_perf_style_group', array( $this, 'set_style_groups' ), 10, 2 );
72
//		add_filter( 'jetpack_perf_script_group', array( $this, 'set_script_groups' ), 10, 2 );
73
74
		if ( $this->inject_critical_css ) {
75
			add_action( 'wp_head', array( $this, 'render_critical_css' ), 0 );
76
		}
77
78
		if ( $this->preload_scripts ) {
79
			add_action( 'wp_print_scripts', array( $this, 'preload_scripts' ) );	
80
		}
81
82
83
		// munge footer scripts
84
		//add_filter( 'wp_footer', array( $this, 'encode_footer_scripts' ), -PHP_INT_MAX );
85
		
86
		// necessary to catch some woocommerce scripts that get localized inside wp_print_scripts at pri 5
87
		//add_filter( 'wp_print_footer_scripts', array( $this, 'encode_footer_scripts' ), 9 );
88
89
		// in modern browsers (post-2008) there is a small performance win from
90
		// loading CSS after external scripts in the header, rather than before
91
		if ( $this->move_scripts_above_css_in_header ) {
92
			add_action( 'init', array( $this, 'move_styles_to_bottom_of_header' ), PHP_INT_MAX );
93
		}
94
95
		if ( $this->prevent_jetpack_implode_css ) {
96
			add_filter( 'jetpack_implode_frontend_css', '__return_false' );
97
		}
98
99
		// ensure dashicons is loading inline
100
		add_filter( 'jetpack_perf_async_style', function( $should_async, $style ) { 
101
			if ( 'dashicons' === $style->handle ) {
102
				return false;
103
			}
104
			return $should_async;
105
		}, 10, 2 );
106
107
		add_filter( 'jetpack_perf_inline_style', function( $should_inline, $style) {
108
			if ( 'dashicons' === $style->handle ) {
109
                                return true;
110
                        }
111
                        return $should_inline;
112
		}, 10, 2 );
113
114
		add_filter( 'script_loader_src', array( $this, 'filter_inline_scripts' ), -100, 2 );
115
		add_filter( 'script_loader_tag', array( $this, 'print_inline_scripts' ), -100, 3 );
116
		add_filter( 'style_loader_src', array( $this, 'filter_inline_styles' ), -100, 2 );
117
		add_filter( 'style_loader_tag', array( $this, 'print_inline_styles' ), -100, 4 );
118
119
		add_action( 'init', array( $this, 'set_first_load_cookie' ) );
120
121
		/**
122
		 * Feature, theme and plugin-specific hacks
123
		 */
124
125
		// 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...
126
		if ( $this->remove_core_emojis ) {
127
			add_action( 'init', array( $this, 'disable_emojis' ) );
128
		}
129
130
		// inline/defer/async stuff for Jetpack
131
		add_action( 'init', array( $this, 'optimize_jetpack' ) );
132
	}
133
134
	/** Disabling Emojis **/
135
	// improves page load performance
136
137
	function disable_emojis() {
138
		remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
139
		remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
140
		remove_action( 'embed_head', 'print_emoji_detection_script', 7 );
141
142
		remove_action( 'wp_print_styles', 'print_emoji_styles' );
143
		remove_action( 'admin_print_styles', 'print_emoji_styles' );
144
145
		remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
146
		remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
147
		remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
148
149
		add_filter( 'tiny_mce_plugins', array( $this, 'disable_emojis_tinymce' ) );
150
		add_filter( 'wp_resource_hints', array( $this, 'disable_emojis_remove_dns_prefetch' ), 10, 2 );
151
	}
152
153
	function optimize_jetpack() {
154
155
	}
156
157
	/**
158
	 * Filter function used to remove the tinymce emoji plugin.
159
	 *
160
	 * @param array $plugins
161
	 * @return array Difference betwen the two arrays
162
	 */
163
	function disable_emojis_tinymce( $plugins ) {
164
		if ( is_array( $plugins ) ) {
165
			return array_diff( $plugins, array( 'wpemoji' ) );
166
		} else {
167
			return array();
168
		}
169
	}
170
171
	/**
172
	 * Remove emoji CDN hostname from DNS prefetching hints.
173
	 *
174
	 * @param array $urls URLs to print for resource hints.
175
	 * @param string $relation_type The relation type the URLs are printed for.
176
	 * @return array Difference betwen the two arrays.
177
	 */
178
	function disable_emojis_remove_dns_prefetch( $urls, $relation_type ) {
179
		if ( 'dns-prefetch' == $relation_type ) {
180
			/** This filter is documented in wp-includes/formatting.php */
181
			$emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2/svg/' );
182
183
			$urls = array_diff( $urls, array( $emoji_svg_url ) );
184
		}
185
186
		return $urls;
187
	}
188
189
	// preload scripts we expect to find in the footer
190
	function preload_scripts() {
191
		global $wp_scripts;
192
		$wp_scripts->all_deps( $wp_scripts->queue, false );
193
                foreach( $wp_scripts->to_do as $handle ) {
194
                        $registration = $wp_scripts->registered[$handle];
195
			// preload anything not async'd, since these scripts are likely to be a higher priority
196
			$is_footer_script = isset( $registration->extra['group'] ) && 1 == $registration->extra['group'];
197
			error_log("$handle is footer: $is_footer_script");
198
			// 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...
199
			if ( ! $this->should_async_script( $registration ) && $is_footer_script ) {
200
				echo '<link rel="preload" as="script" href="'. esc_attr( $registration->src )  .'" />';
201
			}
202
                }	
203
	}
204
205
	function render_critical_css() {
206
		echo '<!-- critical.css --><style type="text/css">' . file_get_contents( plugin_dir_path( __FILE__ ) . 'critical.css' ) . '</style>';
207
	}
208
209
	// by default we only inline scripts+styles on first page load for a given user
210
	function set_first_load_cookie() {
211 View Code Duplication
		if ( ! isset( $_COOKIE['jetpack_perf_loaded'] ) ) {
212
			setcookie( 'jetpack_perf_loaded', '1', time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
213
		}
214
	}
215
216
	// this code essentially sets the default asset location to the footer rather than the head
217
	function relocate_assets() {
218
		global $wp_scripts;
219
220
		//error_log(print_r($wp_scripts,1));
221
222
		// fetch all deps for head
223
		$wp_scripts->all_deps( $wp_scripts->queue, false );
224 View Code Duplication
		foreach( $wp_scripts->to_do as $handle ) {
225
			$registration = $wp_scripts->registered[$handle];
226
			$asset_group = apply_filters( 'jetpack_perf_script_group', $registration->args, $handle );
227
228
			if ( $asset_group !== $registration->args ) {
229
				$registration->args = $asset_group;
230
				$wp_scripts->groups[$handle] = $asset_group;
231
			} 
232
		}
233
234
		//$wp_scripts->done = array();
235
236
		global $wp_styles;
237
238
                // fetch all deps for head
239
                $wp_styles->all_deps( $wp_styles->queue, false,1 );
240 View Code Duplication
                foreach( $wp_styles->to_do as $handle ) {
241
                        $registration = $wp_styles->registered[$handle];
242
                        $asset_group = apply_filters( 'jetpack_perf_style_group', $wp_styles->groups[$handle], $handle );
243
244
                        if ( $asset_group !== $wp_styles->groups[$handle] ) {
245
                                $registration->args = $asset_group;
246
                                $wp_styles->groups[$handle] = $asset_group;
247
                        }
248
                }
249
250
	}
251
252
	// scripts that run after CSS <link>s in the header block waiting for the CSS to load
253
	// so we move styles as late as possible in the wp_head action to maximise the chance
254
	// of non-blocking rendering
255
	function move_styles_to_bottom_of_header() {
256
		remove_action( 'wp_head', 'wp_print_styles', 8 );
257
		add_action( 'wp_head', 'wp_print_styles', 999 );
258
	}
259
260
	function set_script_groups( $group, $handle ) {
261
		//error_log("set script for $handle in group $group");
262
		// force jquery into header, everything else can go in footer unless filtered elsewhere
263
		if ( in_array( $handle, array( 'jquery-core', 'jquery-migrate', 'jquery' ) ) ) {
264
			return 0;
265
		}
266
267
		// force scripts with deps into header
268
		if ( $this->script_has_deps( $handle ) ) {
269
			return 0;
270
		}
271
272
		if ( $group === NULL ) {
273
		//	error_log("force set $handle to 0");
274
			// set default location to header
275
			return 1;
276
		}
277
278
		//error_log("set $handle to $group");
279
280
		return $group;
281
	}
282
283
	function set_style_groups( $group, $handle ) {
284
		if ( in_array( $handle, array( 'genericons' ) ) ) {
285
			return 0;
286
		}
287
		
288
		if ( $group === NULL ) {
289
			return 1;
290
		}
291
292
		return $group;
293
	}
294
295
	// this is wild: inline scripts in the footer can block rendering, but we don't have a chance to intercept them in the header
296
	// so let's mark them as defer (which guarantees order) - which also requires adding deps with the localize script base64-encoded
297
298
	// so, I don't think this is a performance win - stuff in the footer is already "deferred" and forcing the defer attribute
299
	// only makes them load one after the other, rather than allowing the browser to do paralellisation
300
	function encode_footer_scripts() {
301
		global $wp_scripts;
302
		$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...
303
304
                foreach( $wp_scripts->to_do as $handle ) {
305
                        $registration = $wp_scripts->registered[$handle];
306
			//error_log(print_r($registration,1));
307
			if ( isset( $registration->extra['data'] ) && $registration->extra['data'] ) {
308
				//register artificial dependency with param of defer
309
				$encoded_src = 'data:text/javascript;base64,' . base64_encode( $registration->extra['data'] );
310
				$data_handle = $handle . '_jp_data';
311
				$wp_scripts->add( $data_handle, $encoded_src, array(), false );
312
				$wp_scripts->add_data( $data_handle, 'jetpack-defer', true );
313
				$registration->deps[] = $data_handle;
314
				unset( $registration->extra['data'] );
315
			}
316
317
			$registration->extra['jetpack-defer'] = true;
318
			$registration->extra['jetpack-async'] = false;
319
                }
320
		$wp_scripts->to_do = array();
321
	}	
322
323
	/** FILTERS **/
324
	public function remove_external_font_scripts( $should_remove, $handle, $asset_url ) {
325
		$font_script_url = 'http://use.typekit.com/';
326
		return strncmp( $asset_url, $font_script_url, strlen( $font_script_url ) ) === 0;
327
	}
328
329
	public function remove_external_font_styles( $should_remove, $handle, $asset_url ) {
330
		$font_url = 'https://fonts.googleapis.com';
331
		return strncmp( $asset_url, $font_url, strlen( $font_url ) ) === 0;
332
	}
333
334
	/** SCRIPTS **/
335 View Code Duplication
	public function filter_inline_scripts( $src, $handle ) {
336
		global $wp_scripts;
337
338
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
339
			return $src;
340
		}
341
342
		$script = $wp_scripts->registered[$handle];
343
344
		// reset src to empty - can't return empty string though because then it skips rendering the tag
345
		if ( $this->should_inline_script( $script ) ) {
346
			return '#';
347
		}
348
349
		return $src;
350
	}
351
352
	public function print_inline_scripts( $tag, $handle, $src ) {
353
		global $wp_scripts;
354
355
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
356
			return $tag;
357
		}
358
359
		$script = $wp_scripts->registered[$handle];
360
361
		if ( $this->should_remove_script( $script ) ) {
362
			return '';
363
		}
364
365
		if ( $this->should_inline_script( $script ) ) {
366
			$label = '<!-- ' . $script->src . ' -->';
367
			// base64-encoding a script into the src URL only makes sense if we intend to async or defer it
368
			if ( $this->should_defer_script( $script ) ) {
369
				$tag = $label . '<script defer type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
370
			} elseif ( $this->should_async_script( $script ) ) {
371
				$tag = $label . '<script async type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
372
			} else {
373
				$tag = $label . '<script type="text/javascript">' . file_get_contents( $script->extra['jetpack-inline-file'] ) . '</script>';
374
			}
375
		} else {
376
			if ( $this->should_defer_script( $script ) ) {
377
				$tag = preg_replace( '/<script /', '<script defer ', $tag );
378
			} elseif ( $this->should_async_script( $script ) ) {
379
				$tag = preg_replace( '/<script /', '<script async ', $tag );
380
			}
381
		}
382
383
		return $tag;
384
	}
385
386
	private function should_async_script( $script ) {
387
		// this could be dangerous if scripts have undeclared dependencies
388
		// only make scripts async if nothing depends on them
389
		// turns out this is a problem - lots of plugins import (say) jquery extensions and then use them in the page from inline scripts. Bah.	
390
		// but if a script has been declared in the footer? hm maybe that's ok........
391
		$should_async_script = isset( $script->extra['group'] ) && $script->extra['group'] === 1; // ! $this->script_has_deps( $script->handle );
392
393
		// you can override this logic by setting jetpack-async
394
		$should_async_script = $should_async_script || ( isset( $script->extra['jetpack-async'] ) && $script->extra['jetpack-async'] );
395
		return $this->async_scripts && apply_filters( 'jetpack_perf_async_script', $should_async_script, $script->handle, $script->src );
396
	}
397
398
	private function script_has_deps( $handle ) {
399
		global $wp_scripts;
400 View Code Duplication
                foreach ( $wp_scripts->to_do as $other_script_handle ) {
401
                        $other_script = $wp_scripts->registered[ $other_script_handle ];
402
                        if ( in_array( $handle, $other_script->deps ) ) {
403
				return true;
404
                        }
405
                }
406
407
		return false;
408
	}
409
410
	private function should_defer_script( $script ) {
411
		global $wp_scripts;
412
413
		// if it's explicitly not in the footer, or we have Jetpack Defer set, and has no dependencies
414
		$should_defer_script = $script->args === 0;
415
416
		// only make scripts deferred if nothing depends on them
417 View Code Duplication
		foreach ( $wp_scripts->to_do as $other_script_handle ) {
418
			$other_script = $wp_scripts->registered[ $other_script_handle ];
419
			if ( in_array( $script->handle, $other_script->deps ) ) {
420
				$should_defer_script = false;
421
				break;
422
			}
423
		}
424
425
		$should_defer_script = $should_defer_script || ( isset( $script->extra['jetpack-defer'] ) && $script->extra['jetpack-defer'] );
426
		return $this->defer_scripts && apply_filters( 'jetpack_perf_defer_script', $should_defer_script, $script->handle, $script->src );
427
	}
428
429
	private function should_remove_script( $script ) {
430
		return $this->should_remove_asset( 'jetpack_perf_remove_script', $script );
431
	}
432
433
	private function should_inline_script( $script ) {
434
		return $this->inline_scripts_and_styles && $this->should_inline_asset( 'jetpack_perf_inline_script', $script );
0 ignored issues
show
Bug introduced by
The call to should_inline_asset() misses a required argument $default.

This check looks for function calls that miss required arguments.

Loading history...
435
	}
436
437
	/** STYLES **/
438 View Code Duplication
	public function filter_inline_styles( $src, $handle ) {
439
		global $wp_scripts;
440
441
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
442
			return $src;
443
		}
444
445
		$style = $wp_scripts->registered[$handle];
446
447
		if ( $this->should_inline_style( $style ) ) {
448
			return '#';
449
		}
450
451
		return $src;
452
	}
453
454
	public function print_inline_styles( $tag, $handle, $href, $media ) {
455
		global $wp_styles;
456
457
		if ( is_admin() || ! isset( $wp_styles->registered[$handle] ) ) {
458
			return $tag;
459
		}
460
461
		$style = $wp_styles->registered[$handle];
462
463
		if ( $this->should_async_style( $style ) ) {
464
			// we async all styles if inline-critical-css is enabled
465
			$existing_tag = $tag;
466
			$tag = preg_replace( '/rel=[\'"]stylesheet[\'"]/', 'rel="preload"', $tag );
467
			$tag = preg_replace( '/<link /', '<link onload="this.rel=\'stylesheet\'" as="style" ', $tag );
468
			$tag .= '<noscript>' . $existing_tag . '</noscript>';
469
			return $tag;
470
		}
471
472
		if ( $this->should_inline_style( $style ) ) {
473
			$label = '<!-- ' . $style->src . ' -->';
474
			$css = $this->fix_css_urls( file_get_contents( $style->extra['jetpack-inline-file'] ), $style->src );
475
			return "$label<style type='text/css' media='$media'>$css</style>";
476
		}
477
478
		if ( $this->should_remove_style( $style ) ) {
479
			return '';
480
		}
481
482
		return $tag;
483
	}
484
485
	// we can async styles if we load critical CSS in the header
486
	private function should_async_style( $style ) {
487
		return apply_filters( 'jetpack_perf_async_style', $this->inject_critical_css, $style );
488
	}
489
490
	public function fix_css_urls( $css, $css_url ) {
491
		$base = trailingslashit( dirname( $css_url ) );
492
		$base = str_replace( site_url(), '', $base );
493
494
		// reject absolute site_url
495
		if ( 'http' === substr( $base, 0, 4 ) ) {
496
			return $css;
497
		}
498
499
		return preg_replace_callback( '/url[\s]*\([\s]*["\']?[\s]*(?!https?:\/\/)(?!data:)(?!#)([^\)"\']*)["\']?\)/i', function( $matches ) use ( $base ) {
500
			// 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...
501
			$url = $this->rel2abspath( $matches[1], $base );
502
503
			// sneaky - see if it's small enough that it should be encoded and placed inline
504
			$local_path = $this->local_url_to_file_path( $url );
505
			if ( file_exists( $local_path ) && filesize( $local_path ) < self::INLINE_CSS_URL_MAX_SIZE_BYTES && ( $mime_type = wp_check_filetype( $url )['type'] ) ) {
506
				$url = 'data:' . $mime_type . ';base64,' . base64_encode( file_get_contents( $local_path ) );
507
			} 
508
509
			return 'url('.$url.')';
510
		}, $css );
511
	}
512
513
	// see: http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php
514
	private function rel2abspath( $rel, $path) {
515
		/* remove non-directory element from path */
516
		$path = preg_replace( '#/[^/]*$#', '', $path );
517
518
		/* destroy path if relative url points to root */
519
		if( $rel[0] == '/' )
520
			$path = '';
521
522
		/* dirty absolute URL */
523
		$abs = '';
524
525
		$abs .= $path . '/' . $rel;
526
527
		/* replace '//' or '/./' or '/foo/../' with '/' */
528
		$re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
529
		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...
530
531
		/* absolute path is ready! */
532
		return $abs;
533
	}
534
535
	private function should_inline_style( $style ) {
536
		return $this->inline_scripts_and_styles && $this->should_inline_asset( 'jetpack_perf_inline_style', $style );
0 ignored issues
show
Bug introduced by
The call to should_inline_asset() misses a required argument $default.

This check looks for function calls that miss required arguments.

Loading history...
537
	}
538
539
	private function should_remove_style( $style ) {
540
		return $this->should_remove_asset( 'jetpack_perf_remove_style', $style );
541
	}
542
543
	/** shared code **/
544
545
	private function should_inline_asset( $filter, $dependency, $default ) {
546
		// inline anything local, with a src starting with /, or starting with site_url
547
		$site_url = site_url();
548
549
		$is_local_url = ( strncmp( $dependency->src, '/', 1 ) === 0 && strncmp( $dependency->src, '//', 2 ) !== 0 )
550
			|| strpos( $dependency->src, $site_url ) === 0;
551
552
		if ( $is_local_url && ! isset( $dependency->extra['jetpack-inline'] ) ) {
553
			$dependency->extra['jetpack-inline'] = true;
554
			$dependency->extra['jetpack-inline-file'] = $this->local_url_to_file_path( $dependency->src );
555
		}
556
557
		// early exit if the file doesn't exist or is too large
558
		if ( ! isset( $dependency->extra['jetpack-inline-file'] ) || ! file_exists( $dependency->extra['jetpack-inline-file'] ) ) {
559
			return false;
560
		}
561
562
		// only inline if we don't have a conditional
563
		$should_inline = $default 
564
			&& ! isset( $dependency->extra['conditional'] )
565
			&& isset( $dependency->extra['jetpack-inline'] )
566
			&& $dependency->extra['jetpack-inline']
567
			&& filesize( $dependency->extra['jetpack-inline-file'] ) < self::INLINE_ASSET_MAX_SIZE_BYTES;
568
569
		return apply_filters( $filter, $should_inline, $dependency->handle, $dependency->src );
570
	}
571
572
	private function local_url_to_file_path( $url ) {
573
		$path = untrailingslashit( ABSPATH ) . parse_url( $url )['path'];
574
		if ( '/' !== DIRECTORY_SEPARATOR )
575
	             	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
576
		return $path;
577
	}
578
579
	private function should_remove_asset( $filter, $dependency ) {
580
		return apply_filters( $filter, false, $dependency->handle, $dependency->src );
581
	}
582
583
	/**
584
	 * if inline assets are enabled, renders inline
585
	 * 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...
586
	 * This is actually currently unused
587
	 */
588
	 public function register_inline_script( $handle, $file, $plugin_file, $deps = false, $ver = false, $in_footer = false ) {
589
		$registered = wp_register_script( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $in_footer );
590
591 View Code Duplication
		if ( $registered ) {
592
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
593
			wp_script_add_data( $handle, 'jetpack-inline', true );
594
			wp_script_add_data( $handle, 'jetpack-inline-file', $file_full_path );
595
		}
596
597
		return $registered;
598
	}
599
600
	/**
601
	 * if inline assets are enabled, renders inline
602
	 * 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...
603
	 * This is actually currently unused
604
	 */
605
	public function register_inline_style( $handle, $file, $plugin_file, $deps = array(), $ver = false, $media = 'all' ) {
606
		$registered = wp_register_style( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $media );
607
608 View Code Duplication
		if ( $registered ) {
609
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
610
			wp_style_add_data( $handle, 'jetpack-inline', true );
611
			wp_style_add_data( $handle, 'jetpack-inline-file', $file_full_path );
612
		}
613
	}
614
}
615