Completed
Push — add/asset-cdn ( 63bddc...40b93e )
by
unknown
08:50
created

Jetpack_Asset_CDN::should_concat_script()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Plugin Name: Asset CDN
4
 * Description: Speed up Javascript and CSS
5
 * Plugin URI: https://github.com/automattic/jetpack
6
 * Author: Automattic
7
 * Author URI: https://automattic.com
8
 * Version: 0.1.0
9
 * Text Domain: asset-cdn
10
 * Domain Path: /languages/
11
 * License: GPLv2 or later
12
 */
13
14
/**
15
 * TODO
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
16
 * - versioning (combine ver hashes) and cachebusting
17
 * - concat/minify/serve JS too
18
 * - asset inlining for smaller styles?
19
 * - critical CSS support?
20
 * - non-enqueued assets?
21
 */
22
23
class Jetpack_Asset_CDN {
24
	private static $__instance = null;
25
26
	private $cdn_server;
27
	private $concat_style_groups = array();
28
	private $concat_script_groups = array();
29
	private $inject_critical_css = false;
30
	private $include_external_assets = false;
31
32
	/**
33
	 * Singleton implementation
34
	 *
35
	 * @return object
36
	 */
37
	public static function instance() {
38
		if ( ! is_a( self::$__instance, 'Jetpack_Asset_CDN' ) ) {
39
			self::$__instance = new Jetpack_Asset_CDN();
40
		}
41
42
		return self::$__instance;
43
	}
44
45
	public static function reset() {
46
		if ( null === self::$__instance ) {
47
			return;
48
		}
49
50
		// allow smaller CSS by only minifying assets on the page
51
		remove_filter( 'jetpack_implode_frontend_css', '__return_false' );
52
53
		// buffer selected CSS and JS tags
54
		remove_filter( 'script_loader_tag', array( self::$__instance, 'register_concat_scripts' ), -100 );
55
		remove_filter( 'style_loader_tag', array( self::$__instance, 'register_concat_styles' ), -100 );
56
57
		// render buffered assets
58
		remove_action( 'wp_head', array( self::$__instance, 'render_concatenated_styles_head' ), PHP_INT_MAX );
59
		remove_action( 'wp_head', array( self::$__instance, 'render_concatenated_scripts_head' ), PHP_INT_MAX );
60
		remove_action( 'wp_footer', array( self::$__instance, 'render_concatenated_styles_footer' ), PHP_INT_MAX );
61
		remove_action( 'wp_footer', array( self::$__instance, 'render_concatenated_scripts_footer' ), PHP_INT_MAX );
62
63
		self::$__instance = null;
64
	}
65
66
	private function __construct() {
67
		$this->cdn_server = apply_filters( 'jetpack_asset_cdn_url', 'https://cdn.wpvm.io' );
68
		$this->include_external_assets = apply_filters( 'jetpack_asset_cdn_external_assets', false );
69
70
		// allow smaller CSS by only minifying assets on the page
71
		add_filter( 'jetpack_implode_frontend_css', '__return_false' );
72
73
		// buffer selected CSS and JS tags
74
		add_filter( 'script_loader_tag', array( $this, 'register_concat_scripts' ), -100, 3 );
75
		add_filter( 'style_loader_tag', array( $this, 'register_concat_styles' ), -100, 4 );
76
77
		// render buffered assets
78
		add_action( 'wp_head', array( $this, 'render_concatenated_styles_head' ), PHP_INT_MAX );
79
		add_action( 'wp_head', array( $this, 'render_concatenated_scripts_head' ), PHP_INT_MAX );
80
		add_action( 'wp_footer', array( $this, 'render_concatenated_styles_footer' ), PHP_INT_MAX );
81
		add_action( 'wp_footer', array( $this, 'render_concatenated_scripts_footer' ), PHP_INT_MAX );
82
	}
83
84
	/**
85
	 * Render functions
86
	 */
87
88
	function render_concatenated_styles_head() {
89
		$this->flush_concatenated_styles(0);
90
	}
91
92
	function render_concatenated_styles_footer() {
93
		$this->flush_concatenated_styles(0);
94
		$this->flush_concatenated_styles(1);
95
	}
96
97
	private function flush_concatenated_styles( $group ) {
98
		if ( ! isset( $this->concat_style_groups[ $group ] ) ) {
99
			return;
100
		}
101
102
		$style_groups = $this->concat_style_groups[ $group ];
103
104
		if ( empty( $style_groups ) ) {
105
			return;
106
		}
107
108
		// special URL to concatenation service
109
		global $wp_styles;
110
		$site_url = site_url();
111
		foreach( $style_groups as $media => $styles ) {
112
			$urls = array();
113
			$vers = array();
114
115
			foreach( $styles as $style ) {
116
				$urls[] = str_replace( untrailingslashit( $site_url ), '', $style->src );
117
				$vers[] = $style->ver ? $style->ver : $wp_styles->default_version;
118
			}
119
120
			$cdn_url = $this->cdn_server . '/css?b=' .
121
				urlencode( $site_url ) . '&' .
122
				http_build_query( array( 'f' => $urls ) ) . '&' .
123
				http_build_query( array( 'v' => $vers ) );
124
125
			// if we are injecting critical CSS, load the full CSS async
126
			if ( $this->inject_critical_css ) {
127
				echo '<link rel="preload" onload="this.rel=\'stylesheet\'" as="style" type="text/css" media="' . $media . '" href="' . esc_attr( $cdn_url ) . '"/>';
128
			} else {
129
				echo '<link rel="stylesheet" type="text/css" media="' . $media . '" href="' . esc_attr( $cdn_url ) . '"/>';
130
			}
131
132
			foreach( $styles as $style ) {
133
				if ( isset( $style->extra['concat-after'] ) && $style->extra['concat-after'] ) {
134
					printf( "<style id='%s-inline-css' type='text/css'>\n%s\n</style>\n", esc_attr( $style->handle ), implode( "\n", $style->extra['concat-after'] ) );
135
				}
136
			}
137
		}
138
139
		$this->concat_style_groups[ $group ] = array();
140
	}
141
142
	function render_concatenated_scripts_head() {
143
		$this->flush_concatenated_scripts( 0 );
144
	}
145
146
	function render_concatenated_scripts_footer() {
147
		$this->flush_concatenated_scripts( 0 ); // in case of late-enqueud header styles
148
		$this->flush_concatenated_scripts( 1 );
149
	}
150
151
	private function flush_concatenated_scripts( $group ) {
152
		if ( ! isset( $this->concat_script_groups[ $group ] ) ) {
153
			return;
154
		}
155
156
		$scripts = $this->concat_script_groups[ $group ];
157
158
		if ( empty( $scripts ) ) {
159
			return;
160
		}
161
162
		// special URL to concatenation service
163
		global $wp_scripts;
164
		$site_url = site_url();
165
		$urls = array();
166
		$vers = array();
167
168
		foreach( $scripts as $script ) {
169
			$urls[] = str_replace( untrailingslashit( $site_url ), '', $script->src );
170
			$vers[] = $script->ver ? $script->ver : $wp_scripts->default_version;
171
			if ( isset( $script->extra['before'] ) && $script->extra['before'] ) {
172
				echo sprintf( "<script type='text/javascript'>\n%s\n</script>\n", $script->extra['before'] );
173
			}
174
		}
175
176
		$cdn_url = $this->cdn_server . '/js?b=' .
177
			urlencode( $site_url ) . '&' .
178
			http_build_query( array( 'f' => $urls ) ) . '&' .
179
			http_build_query( array( 'v' => $vers ) );
180
181
		// TODO: if there is NO inline or external script tags in the body, render async (maybe?)
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...
182
		echo '<script type="text/javascript" src="' . esc_attr( $cdn_url ) . '"></script>';
183
184
		foreach( $scripts as $script ) {
185
			if ( isset( $script->extra['after'] ) && $script->extra['after'] ) {
186
				echo sprintf( "<script type='text/javascript'>\n%s\n</script>\n", $script->extra['after'] );
187
			}
188
		}
189
190
		$this->concat_script_groups[ $group ] = array();
191
	}
192
193
	/**
194
	 * Asset modification functions
195
	 */
196
197
	/**
198
	 * Scripts
199
	 */
200
201
	public function register_concat_scripts( $tag, $handle, $src ) {
202
		global $wp_scripts;
203
204
		// don't do admin for now
205
		if ( is_admin() || ! isset( $wp_scripts->registered[$handle] ) ) {
206
			return $tag;
207
		}
208
209
		$script = $wp_scripts->registered[$handle];
210
211
		if ( $this->should_concat_script( $script ) ) {
212
			$this->buffer_script( $script );
213
			return '';
214
		}
215
216
		// if this is a non-CDN script, and there are existing CDN scripts in this group, print them and reset the
217
		// array
218
		$group = isset( $script->extra['group'] ) ? $script->extra['group'] : 0;
219
		$this->flush_concatenated_scripts( $group );
220
221
		return $tag;
222
	}
223
224
	private function should_concat_script( $script ) {
225
		$should_concat =
226
			( $this->include_external_assets || $this->is_local_url( $script->src ) )
227
			&& ! isset( $script->extra['conditional'] );
228
		return apply_filters( 'jetpack_perf_concat_script', $should_concat, $script->handle, $script->src );
229
	}
230
231
	private function buffer_script( $script ) {
232
		$group = isset( $script->extra['group'] ) ? $script->extra['group'] : 0;
233
		if ( ! isset( $this->concat_script_groups[$group] ) ) {
234
			$this->concat_script_groups[$group] = array();
235
		}
236
		$this->concat_script_groups[$group][] = $script;
237
	}
238
239
	/**
240
	 * Styles
241
	 */
242
243
	public function register_concat_styles( $tag, $handle, $href, $media ) {
244
		global $wp_styles;
245
246
		// don't do admin for now
247
		if ( is_admin() || ! isset( $wp_styles->registered[$handle] ) ) {
248
			return $tag;
249
		}
250
251
		$style = $wp_styles->registered[$handle];
252
253
		if ( $this->should_concat_style( $style ) ) {
254
			$this->buffer_style( $style );
255
			return '';
256
		}
257
258
		return $tag;
259
	}
260
261
	private function buffer_style( $style ) {
262
		$group = isset( $style->extra['group'] ) ? $style->extra['group'] : 0;
263
		$media = $style->args;
264
265
		// rename the 'after' code so that we can output it separately
266
		if ( isset( $style->extra['after'] ) ) {
267
			$style->extra['concat-after'] = $style->extra['after'];
268
			unset( $style->extra['after'] );
269
		}
270
271
		if ( ! $media ) {
272
			$media = 'all';
273
		}
274
275
		if ( ! isset( $this->concat_style_groups[$group] ) ) {
276
			$this->concat_style_groups[$group] = array();
277
		}
278
279
		if ( ! isset( $this->concat_style_groups[$group][$media] ) ) {
280
			$this->concat_style_groups[$group][$media] = array();
281
		}
282
283
		$this->concat_style_groups[$group][$media][] = $style;
284
	}
285
286
	private function should_concat_style( $style ) {
287
		$should_concat =
288
		( $this->include_external_assets || $this->is_local_url( $style->src ) )
289
		&& ! isset( $style->extra['conditional'] );
290
291
		return apply_filters( 'jetpack_perf_concat_style', $should_concat, $style->handle, $style->src );
292
	}
293
294
	private function is_local_url( $url ) {
295
		$site_url = site_url();
296
		return ( strncmp( $url, '/', 1 ) === 0 && strncmp( $url, '//', 2 ) !== 0 )
297
			|| strpos( $url, $site_url ) === 0;
298
	}
299
}