Completed
Push — add/pwa ( 3e657d...bbbc99 )
by
unknown
07:39
created

should_inline_asset()   D

Complexity

Conditions 10
Paths 30

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 14
nc 30
nop 2
dl 0
loc 25
rs 4.8196
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 = 10 * 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 = false;//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
		add_filter( 'script_loader_src', array( $this, 'filter_inline_scripts' ), -100, 2 );
100
		add_filter( 'script_loader_tag', array( $this, 'print_inline_scripts' ), -100, 3 );
101
		add_filter( 'style_loader_src', array( $this, 'filter_inline_styles' ), -100, 2 );
102
		add_filter( 'style_loader_tag', array( $this, 'print_inline_styles' ), -100, 4 );
103
104
		add_action( 'init', array( $this, 'set_first_load_cookie' ) );
105
106
		/**
107
		 * Feature, theme and plugin-specific hacks
108
		 */
109
110
		// 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...
111
		if ( $this->remove_core_emojis ) {
112
			add_action( 'init', array( $this, 'disable_emojis' ) );
113
		}
114
115
		// inline/defer/async stuff for Jetpack
116
		add_action( 'init', array( $this, 'optimize_jetpack' ) );
117
	}
118
119
	/** Disabling Emojis **/
120
	// improves page load performance
121
122
	function disable_emojis() {
123
		remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
124
		remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
125
		remove_action( 'embed_head', 'print_emoji_detection_script', 7 );
126
127
		remove_action( 'wp_print_styles', 'print_emoji_styles' );
128
		remove_action( 'admin_print_styles', 'print_emoji_styles' );
129
130
		remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
131
		remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
132
		remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
133
134
		add_filter( 'tiny_mce_plugins', array( $this, 'disable_emojis_tinymce' ) );
135
		add_filter( 'wp_resource_hints', array( $this, 'disable_emojis_remove_dns_prefetch' ), 10, 2 );
136
	}
137
138
	function optimize_jetpack() {
139
140
	}
141
142
	/**
143
	 * Filter function used to remove the tinymce emoji plugin.
144
	 *
145
	 * @param array $plugins
146
	 * @return array Difference betwen the two arrays
147
	 */
148
	function disable_emojis_tinymce( $plugins ) {
149
		if ( is_array( $plugins ) ) {
150
			return array_diff( $plugins, array( 'wpemoji' ) );
151
		} else {
152
			return array();
153
		}
154
	}
155
156
	/**
157
	 * Remove emoji CDN hostname from DNS prefetching hints.
158
	 *
159
	 * @param array $urls URLs to print for resource hints.
160
	 * @param string $relation_type The relation type the URLs are printed for.
161
	 * @return array Difference betwen the two arrays.
162
	 */
163
	function disable_emojis_remove_dns_prefetch( $urls, $relation_type ) {
164
		if ( 'dns-prefetch' == $relation_type ) {
165
			/** This filter is documented in wp-includes/formatting.php */
166
			$emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2/svg/' );
167
168
			$urls = array_diff( $urls, array( $emoji_svg_url ) );
169
		}
170
171
		return $urls;
172
	}
173
174
	// preload scripts we expect to find in the footer
175
	function preload_scripts() {
176
		global $wp_scripts;
177
		$wp_scripts->all_deps( $wp_scripts->queue, false );
178
                foreach( $wp_scripts->to_do as $handle ) {
179
                        $registration = $wp_scripts->registered[$handle];
180
			// preload anything not async'd, since these scripts are likely to be a higher priority
181
			$is_footer_script = isset( $registration->extra['group'] ) && 1 == $registration->extra['group'];
182
			error_log("$handle is footer: $is_footer_script");
183
			if ( ! $this->should_async_script( $registration ) && $is_footer_script ) {
184
				echo '<link rel="preload" as="script" href="'. esc_attr( $registration->src )  .'" />';
185
			}
186
                }	
187
	}
188
189
	function render_critical_css() {
190
		echo '<!-- critical.css --><style type="text/css">' . file_get_contents( plugin_dir_path( __FILE__ ) . 'critical.css' ) . '</style>';
191
	}
192
193
	// by default we only inline scripts+styles on first page load for a given user
194
	function set_first_load_cookie() {
195 View Code Duplication
		if ( ! isset( $_COOKIE['jetpack_perf_loaded'] ) ) {
196
			setcookie( 'jetpack_perf_loaded', '1', time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
197
		}
198
	}
199
200
	// this code essentially sets the default asset location to the footer rather than the head
201
	function relocate_assets() {
202
		global $wp_scripts;
203
204
		//error_log(print_r($wp_scripts,1));
205
206
		// fetch all deps for head
207
		$wp_scripts->all_deps( $wp_scripts->queue, false );
208 View Code Duplication
		foreach( $wp_scripts->to_do as $handle ) {
209
			$registration = $wp_scripts->registered[$handle];
210
			$asset_group = apply_filters( 'jetpack_perf_script_group', $registration->args, $handle );
211
212
			if ( $asset_group !== $registration->args ) {
213
				$registration->args = $asset_group;
214
				$wp_scripts->groups[$handle] = $asset_group;
215
			} 
216
		}
217
218
		//$wp_scripts->done = array();
219
220
		global $wp_styles;
221
222
                // fetch all deps for head
223
                $wp_styles->all_deps( $wp_styles->queue, false,1 );
224 View Code Duplication
                foreach( $wp_styles->to_do as $handle ) {
225
                        $registration = $wp_styles->registered[$handle];
226
                        $asset_group = apply_filters( 'jetpack_perf_style_group', $wp_styles->groups[$handle], $handle );
227
228
                        if ( $asset_group !== $wp_styles->groups[$handle] ) {
229
                                $registration->args = $asset_group;
230
                                $wp_styles->groups[$handle] = $asset_group;
231
                        }
232
                }
233
234
	}
235
236
	// scripts that run after CSS <link>s in the header block waiting for the CSS to load
237
	// so we move styles as late as possible in the wp_head action to maximise the chance
238
	// of non-blocking rendering
239
	function move_styles_to_bottom_of_header() {
240
		remove_action( 'wp_head', 'wp_print_styles', 8 );
241
		add_action( 'wp_head', 'wp_print_styles', 999 );
242
	}
243
244
	function set_script_groups( $group, $handle ) {
245
		//error_log("set script for $handle in group $group");
246
		// force jquery into header, everything else can go in footer unless filtered elsewhere
247
		if ( in_array( $handle, array( 'jquery-core', 'jquery-migrate', 'jquery' ) ) ) {
248
			return 0;
249
		}
250
251
		// force scripts with deps into header
252
		if ( $this->script_has_deps( $handle ) ) {
253
			return 0;
254
		}
255
256
		if ( $group === NULL ) {
257
		//	error_log("force set $handle to 0");
258
			// set default location to header
259
			return 1;
260
		}
261
262
		//error_log("set $handle to $group");
263
264
		return $group;
265
	}
266
267
	function set_style_groups( $group, $handle ) {
268
		if ( in_array( $handle, array( 'genericons' ) ) ) {
269
			return 0;
270
		}
271
		
272
		if ( $group === NULL ) {
273
			return 1;
274
		}
275
276
		return $group;
277
	}
278
279
	// this is wild: inline scripts in the footer can block rendering, but we don't have a chance to intercept them in the header
280
	// so let's mark them as defer (which guarantees order) - which also requires adding deps with the localize script base64-encoded
281
282
	// so, I don't think this is a performance win - stuff in the footer is already "deferred" and forcing the defer attribute
283
	// only makes them load one after the other, rather than allowing the browser to do paralellisation
284
	function encode_footer_scripts() {
285
		global $wp_scripts;
286
		$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...
287
288
                foreach( $wp_scripts->to_do as $handle ) {
289
                        $registration = $wp_scripts->registered[$handle];
290
			//error_log(print_r($registration,1));
291
			if ( isset( $registration->extra['data'] ) && $registration->extra['data'] ) {
292
				//register artificial dependency with param of defer
293
				$encoded_src = 'data:text/javascript;base64,' . base64_encode( $registration->extra['data'] );
294
				$data_handle = $handle . '_jp_data';
295
				$wp_scripts->add( $data_handle, $encoded_src, array(), false );
296
				$wp_scripts->add_data( $data_handle, 'jetpack-defer', true );
297
				$registration->deps[] = $data_handle;
298
				unset( $registration->extra['data'] );
299
			}
300
301
			$registration->extra['jetpack-defer'] = true;
302
			$registration->extra['jetpack-async'] = false;
303
                }
304
		$wp_scripts->to_do = array();
305
	}	
306
307
	/** FILTERS **/
308
	public function remove_external_font_scripts( $should_remove, $handle, $asset_url ) {
309
		$font_script_url = 'http://use.typekit.com/';
310
		return strncmp( $asset_url, $font_script_url, strlen( $font_script_url ) ) === 0;
311
	}
312
313
	public function remove_external_font_styles( $should_remove, $handle, $asset_url ) {
314
		$font_url = 'https://fonts.googleapis.com';
315
		return strncmp( $asset_url, $font_url, strlen( $font_url ) ) === 0;
316
	}
317
318
	/** SCRIPTS **/
319 View Code Duplication
	public function filter_inline_scripts( $src, $handle ) {
320
		global $wp_scripts;
321
322
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
323
			return $src;
324
		}
325
326
		$script = $wp_scripts->registered[$handle];
327
328
		// reset src to empty - can't return empty string though because then it skips rendering the tag
329
		if ( $this->should_inline_script( $script ) ) {
330
			return '#';
331
		}
332
333
		return $src;
334
	}
335
336
	public function print_inline_scripts( $tag, $handle, $src ) {
337
		global $wp_scripts;
338
339
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
340
			return $tag;
341
		}
342
343
		$script = $wp_scripts->registered[$handle];
344
345
		if ( $this->should_remove_script( $script ) ) {
346
			return '';
347
		}
348
349
		if ( $this->should_inline_script( $script ) ) {
350
			$label = '<!-- ' . $script->src . ' -->';
351
			// base64-encoding a script into the src URL only makes sense if we intend to async or defer it
352
			if ( $this->should_defer_script( $script ) ) {
353
				$tag = $label . '<script defer type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
354
			} elseif ( $this->should_async_script( $script ) ) {
355
				$tag = $label . '<script async type="text/javascript" src="data:text/javascript;base64,' . base64_encode( file_get_contents( $script->extra['jetpack-inline-file'] ) ) . '"></script>';
356
			} else {
357
				$tag = $label . '<script type="text/javascript">' . file_get_contents( $script->extra['jetpack-inline-file'] ) . '</script>';
358
			}
359
		} else {
360
			if ( $this->should_defer_script( $script ) ) {
361
				$tag = preg_replace( '/<script /', '<script defer ', $tag );
362
			} elseif ( $this->should_async_script( $script ) ) {
363
				$tag = preg_replace( '/<script /', '<script async ', $tag );
364
			}
365
		}
366
367
		return $tag;
368
	}
369
370
	private function should_async_script( $script ) {
371
		// this could be dangerous if scripts have undeclared dependencies
372
		// only make scripts async if nothing depends on them
373
		// turns out this is a problem - lots of plugins import (say) jquery extensions and then use them in the page from inline scripts. Bah.	
374
		// but if a script has been declared in the footer? hm maybe that's ok........
375
		$should_async_script = isset( $script->extra['group'] ) && $script->extra['group'] === 1; // ! $this->script_has_deps( $script->handle );
376
377
		// you can override this logic by setting jetpack-async
378
		$should_async_script = $should_async_script || ( isset( $script->extra['jetpack-async'] ) && $script->extra['jetpack-async'] );
379
		return $this->async_scripts && apply_filters( 'jetpack_perf_async_script', $should_async_script, $script->handle, $script->src );
380
	}
381
382
	private function script_has_deps( $handle ) {
383
		global $wp_scripts;
384 View Code Duplication
                foreach ( $wp_scripts->to_do as $other_script_handle ) {
385
                        $other_script = $wp_scripts->registered[ $other_script_handle ];
386
                        if ( in_array( $handle, $other_script->deps ) ) {
387
				return true;
388
                        }
389
                }
390
391
		return false;
392
	}
393
394
	private function should_defer_script( $script ) {
395
		global $wp_scripts;
396
397
		// if it's explicitly not in the footer, or we have Jetpack Defer set, and has no dependencies
398
		$should_defer_script = $script->args === 0;
399
400
		// only make scripts deferred if nothing depends on them
401 View Code Duplication
		foreach ( $wp_scripts->to_do as $other_script_handle ) {
402
			$other_script = $wp_scripts->registered[ $other_script_handle ];
403
			if ( in_array( $script->handle, $other_script->deps ) ) {
404
				$should_defer_script = false;
405
				break;
406
			}
407
		}
408
409
		$should_defer_script = $should_defer_script || ( isset( $script->extra['jetpack-defer'] ) && $script->extra['jetpack-defer'] );
410
		return $this->defer_scripts && apply_filters( 'jetpack_perf_defer_script', $should_defer_script, $script->handle, $script->src );
411
	}
412
413
	private function should_remove_script( $script ) {
414
		return $this->should_remove_asset( 'jetpack_perf_remove_script', $script );
415
	}
416
417
	private function should_inline_script( $script ) {
418
		return ( $this->inline_scripts_and_styles || $this->inline_always ) && $this->should_inline_asset( 'jetpack_perf_inline_script', $script );
419
	}
420
421
	/** STYLES **/
422 View Code Duplication
	public function filter_inline_styles( $src, $handle ) {
423
		global $wp_scripts;
424
425
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
426
			return $src;
427
		}
428
429
		$style = $wp_scripts->registered[$handle];
430
431
		if ( $this->should_inline_style( $style ) ) {
432
			return '#';
433
		}
434
435
		return $src;
436
	}
437
438
	public function print_inline_styles( $tag, $handle, $href, $media ) {
439
		global $wp_styles;
440
441
		if ( is_admin() || ! isset( $wp_styles->registered[$handle] ) ) {
442
			return $tag;
443
		}
444
445
		$style = $wp_styles->registered[$handle];
446
447
		if ( $this->should_async_style( $style ) ) {
448
			// we async all styles if inline-critical-css is enabled
449
			$existing_tag = $tag;
450
			$tag = preg_replace( '/rel=[\'"]stylesheet[\'"]/', 'rel="preload"', $tag );
451
			$tag = preg_replace( '/<link /', '<link onload="this.rel=\'stylesheet\'" as="style" ', $tag );
452
			$tag .= '<noscript>' . $existing_tag . '</noscript>';
453
			return $tag;
454
		}
455
456
		if ( $this->should_inline_style( $style ) ) {
457
			$label = '<!-- ' . $style->src . ' -->';
458
			$css = $this->fix_css_urls( file_get_contents( $style->extra['jetpack-inline-file'] ), $style->src );
459
			return "$label<style type='text/css' media='$media'>$css</style>";
460
		}
461
462
		if ( $this->should_remove_style( $style ) ) {
463
			return '';
464
		}
465
466
		return $tag;
467
	}
468
469
	// we can async styles if we load critical CSS in the header
470
	private function should_async_style( $style ) {
471
		return $this->inject_critical_css;
472
	}
473
474
	public function fix_css_urls( $css, $css_url ) {
475
		$base = trailingslashit( dirname( $css_url ) );
476
		$base = str_replace( site_url(), '', $base );
477
478
		// reject absolute site_url
479
		if ( 'http' === substr( $base, 0, 4 ) ) {
480
			return $css;
481
		}
482
483
		return preg_replace_callback( '/url[\s]*\([\s]*["\']?[\s]*(?!https?:\/\/)(?!data:)(?!#)([^\)"\']*)["\']?\)/i', function( $matches ) use ( $base ) {
484
			// 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...
485
			$url = $this->rel2abspath( $matches[1], $base );
486
487
			// sneaky - see if it's small enough that it should be encoded and placed inline
488
			$local_path = $this->local_url_to_file_path( $url );
489
			if ( file_exists( $local_path ) && filesize( $local_path ) < self::INLINE_CSS_URL_MAX_SIZE_BYTES && ( $mime_type = wp_check_filetype( $url )['type'] ) ) {
490
				$url = 'data:' . $mime_type . ';base64,' . base64_encode( file_get_contents( $local_path ) );
491
			} 
492
493
			return 'url('.$url.')';
494
		}, $css );
495
	}
496
497
	// see: http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php
498
	private function rel2abspath( $rel, $path) {
499
		/* remove non-directory element from path */
500
		$path = preg_replace( '#/[^/]*$#', '', $path );
501
502
		/* destroy path if relative url points to root */
503
		if( $rel[0] == '/' )
504
			$path = '';
505
506
		/* dirty absolute URL */
507
		$abs = '';
508
509
		$abs .= $path . '/' . $rel;
510
511
		/* replace '//' or '/./' or '/foo/../' with '/' */
512
		$re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
513
		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...
514
515
		/* absolute path is ready! */
516
		return $abs;
517
	}
518
519
	private function should_inline_style( $style ) {
520
		return ( $this->inline_scripts_and_styles || $this->inline_always ) && $this->should_inline_asset( 'jetpack_perf_inline_style', $style );
521
	}
522
523
	private function should_remove_style( $style ) {
524
		return $this->should_remove_asset( 'jetpack_perf_remove_style', $style );
525
	}
526
527
	/** shared code **/
528
529
	private function should_inline_asset( $filter, $dependency ) {
530
		// inline anything local, with a src starting with /, or starting with site_url
531
		$site_url = site_url();
532
533
		$is_local_url = ( strncmp( $dependency->src, '/', 1 ) === 0 && strncmp( $dependency->src, '//', 2 ) !== 0 )
534
			|| strpos( $dependency->src, $site_url ) === 0;
535
536
		if ( $is_local_url && ! isset( $dependency->extra['jetpack-inline'] ) ) {
537
			$dependency->extra['jetpack-inline'] = true;
538
			$dependency->extra['jetpack-inline-file'] = $this->local_url_to_file_path( $dependency->src );
539
		}
540
541
		// early exit if the file doesn't exist or is too large
542
		if ( ! isset( $dependency->extra['jetpack-inline-file'] ) || ! file_exists( $dependency->extra['jetpack-inline-file'] ) ) {
543
			return false;
544
		}
545
546
		// only inline if we don't have a conditional
547
		$should_inline = ! isset( $dependency->extra['conditional'] )
548
			&& isset( $dependency->extra['jetpack-inline'] )
549
			&& $dependency->extra['jetpack-inline']
550
			&& filesize( $dependency->extra['jetpack-inline-file'] ) < self::INLINE_ASSET_MAX_SIZE_BYTES;
551
552
		return apply_filters( $filter, $should_inline, $dependency->handle, $dependency->src );
553
	}
554
555
	private function local_url_to_file_path( $url ) {
556
		$path = untrailingslashit( ABSPATH ) . parse_url( $url )['path'];
557
		if ( '/' !== DIRECTORY_SEPARATOR )
558
	             	$path = str_replace( '/', DIRECTORY_SEPARATOR, $path );
559
		return $path;
560
	}
561
562
	private function should_remove_asset( $filter, $dependency ) {
563
		return apply_filters( $filter, false, $dependency->handle, $dependency->src );
564
	}
565
566
	/**
567
	 * if inline assets are enabled, renders inline
568
	 * 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...
569
	 * This is actually currently unused
570
	 */
571
	 public function register_inline_script( $handle, $file, $plugin_file, $deps = false, $ver = false, $in_footer = false ) {
572
		$registered = wp_register_script( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $in_footer );
573
574 View Code Duplication
		if ( $registered ) {
575
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
576
			wp_script_add_data( $handle, 'jetpack-inline', true );
577
			wp_script_add_data( $handle, 'jetpack-inline-file', $file_full_path );
578
		}
579
580
		return $registered;
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_style( $handle, $file, $plugin_file, $deps = array(), $ver = false, $media = 'all' ) {
589
		$registered = wp_register_style( $handle, plugins_url( $file, $plugin_file ), $deps, $ver, $media );
590
591 View Code Duplication
		if ( $registered ) {
592
			$file_full_path = dirname( $plugin_file ) . '/' . $file;
593
			wp_style_add_data( $handle, 'jetpack-inline', true );
594
			wp_style_add_data( $handle, 'jetpack-inline-file', $file_full_path );
595
		}
596
	}
597
}
598