PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.2
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / classes / Optimizer / JS.php

JS.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.2, at includes/classes/Optimizer/JS.php

342 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * JS Optimizer
4 * Credits: https://github.com/Automattic/nginx-http-concat/blob/master/jsconcat.php
5 *
6 * @package PoweredCache\Optimizer
7 */
8
9 namespace PoweredCache\Optimizer;
10
11 use \WP_Scripts as WP_Scripts;
12
13 //phpcs:disable Squiz.Commenting.VariableComment.Missing
14 //phpcs:disable Generic.Commenting.DocComment.MissingShort
15 //phpcs:disable Squiz.Commenting.FunctionComment.MissingParamName
16 //phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag
17 //phpcs:disable Squiz.Commenting.FunctionComment.MissingParamComment
18
19
20 /**
21 * Class JS
22 */
23 class JS extends WP_Scripts {
24 private $old_scripts;
25 public $allow_gzip_compression;
26 public $do_minify;
27 public $excluded_files = [];
28
29 /**
30 * JS constructor.
31 *
32 * @param object $scripts \WP_Scripts
33 */
34 public function __construct( $scripts ) {
35 if ( empty( $scripts ) || ! ( $scripts instanceof WP_Scripts ) ) {
36 $this->old_scripts = new WP_Scripts();
37 } else {
38 $this->old_scripts = $scripts;
39 }
40
41 // Unset all the object properties except our private copy of the scripts object.
42 // We have to unset everything so that the overload methods talk to $this->old_scripts->whatever
43 // instead of $this->whatever.
44 foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
45 if ( 'old_scripts' === $key ) {
46 continue;
47 }
48 unset( $this->$key );
49 }
50 }
51
52 /**
53 * Check given handle has inline JS
54 *
55 * @param string $handle JS handle
56 *
57 * @return bool
58 */
59 protected function has_inline_content( $handle ) {
60 $before_output = $this->get_data( $handle, 'before' );
61 if ( ! empty( $before_output ) ) {
62 return true;
63 }
64
65 $after_output = $this->get_data( $handle, 'after' );
66 if ( ! empty( $after_output ) ) {
67 return true;
68 }
69
70 // JavaScript translations
71 $has_translations = ! empty( $this->registered[ $handle ]->textdomain );
72 if ( $has_translations ) {
73 return true;
74 }
75
76 return false;
77 }
78
79 /**
80 * Prep items for concat
81 *
82 * @param bool $handles
83 * @param bool $group
84 *
85 * @return array|string[]
86 */
87 public function do_items( $handles = false, $group = false ) {
88 $abspath = wp_normalize_path( ABSPATH );
89 $handles = false === $handles ? $this->queue : (array) $handles;
90 $javascripts = array();
91 $siteurl = apply_filters( 'powered_cache_fo_site_url', $this->base_url );
92
93 $this->all_deps( $handles );
94 $level = 0;
95
96 foreach ( $this->to_do as $key => $handle ) {
97 if ( in_array( $handle, $this->done, true ) || ! isset( $this->registered[ $handle ] ) ) {
98 continue;
99 }
100
101 if ( 0 === $group && $this->groups[ $handle ] > 0 ) {
102 $this->in_footer[] = $handle;
103 unset( $this->to_do[ $key ] );
104 continue;
105 }
106
107 if ( ! $this->registered[ $handle ]->src ) { // Defines a group.
108 // if there are localized items, echo them
109 $this->print_extra_script( $handle );
110 $this->done[] = $handle;
111 continue;
112 }
113
114 if ( false === $group && in_array( $handle, $this->in_footer, true ) ) {
115 $this->in_footer = array_diff( $this->in_footer, (array) $handle );
116 }
117
118 $obj = $this->registered[ $handle ];
119 $js_url = $obj->src;
120 $js_url_parsed = wp_parse_url( $js_url );
121 $extra = $obj->extra;
122
123 // Don't concat by default
124 $do_concat = false;
125
126 // Only try to concat static js files
127 if ( false !== strpos( $js_url_parsed['path'], '.js' ) ) {
128 $do_concat = true;
129 }
130
131 // Don't try to concat externally hosted scripts
132 $is_internal_url = Helper::is_internal_url( $js_url, $siteurl );
133 if ( ! $is_internal_url ) {
134 $do_concat = false;
135 }
136
137 // Concat and canonicalize the paths only for
138 // existing scripts that aren't outside ABSPATH
139
140 $js_realpath = Helper::realpath( $js_url, $siteurl );
141 if ( ! $js_realpath || 0 !== strpos( $js_realpath, $abspath ) ) {
142 $do_concat = false;
143 } else {
144 $js_url_parsed['path'] = substr( $js_realpath, strlen( $abspath ) - 1 );
145 }
146
147 if ( $this->has_inline_content( $handle ) ) {
148 $do_concat = false;
149 }
150
151 // Skip core scripts that use Strict Mode
152 if ( 'react' === $handle || 'react-dom' === $handle ) {
153 $do_concat = false;
154 }
155
156 if ( Helper::is_excluded_js( $js_url ) ) {
157 $do_concat = false;
158 }
159
160 /**
161 * Allow plugins to disable concatenation of certain scripts.
162 *
163 * @hook powered_cache_fo_js_do_concat
164 *
165 * @param {boolean} $do_concat Contact status.
166 * @param {string} $handle Handle of script.
167 *
168 * @return {boolean} New value.
169 * @since 2.0
170 */
171 $do_concat = apply_filters( 'powered_cache_fo_js_do_concat', $do_concat, $handle );
172
173 if ( true === $do_concat ) {
174 if ( ! isset( $javascripts[ $level ] ) ) {
175 $javascripts[ $level ]['type'] = 'concat';
176 }
177
178 $javascripts[ $level ]['paths'][] = $js_url_parsed['path'];
179 $javascripts[ $level ]['handles'][] = $handle;
180
181 } else {
182 $level ++;
183 $javascripts[ $level ]['type'] = 'do_item';
184 $javascripts[ $level ]['handle'] = $handle;
185 $level ++;
186 }
187 unset( $this->to_do[ $key ] );
188 }
189
190 if ( empty( $javascripts ) ) {
191 return $this->done;
192 }
193
194 foreach ( $javascripts as $js_array ) {
195 if ( 'do_item' === $js_array['type'] ) {
196 if ( $this->do_item( $js_array['handle'], $group ) ) {
197 $this->done[] = $js_array['handle'];
198 }
199 } elseif ( 'concat' === $js_array['type'] ) {
200 array_map( array( $this, 'print_extra_script' ), $js_array['handles'] );
201
202 if ( isset( $js_array['paths'] ) && count( $js_array['paths'] ) > 1 ) {
203 $paths = array_map(
204 function ( $url ) use ( $abspath ) {
205 return $abspath . $url;
206 },
207 $js_array['paths']
208 );
209 $mtime = max( array_map( 'filemtime', $paths ) );
210 $path_str = implode( ',', $js_array['paths'] ) . "?m=${mtime}j";
211
212 if ( $this->allow_gzip_compression ) {
213 $path_64 = base64_encode( gzcompress( $path_str ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
214 if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) {
215 $path_str = '-' . $path_64;
216 }
217 }
218
219 $href = Helper::get_optimized_url( $path_str, $this->do_minify );
220 } elseif ( isset( $js_array['paths'] ) && is_array( $js_array['paths'] ) ) {
221 $href = $this->cache_bust_mtime( $siteurl . $js_array['paths'][0], $siteurl );
222 }
223
224 $this->done = array_merge( $this->done, $js_array['handles'] );
225
226 // Print before/after scripts from wp_inline_scripts() and concatenated script tag
227 if ( isset( $js_array['extras']['before'] ) ) {
228 foreach ( $js_array['extras']['before'] as $inline_before ) {
229 echo $inline_before; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
230 }
231 }
232 if ( isset( $href ) ) {
233 /**
234 * Filters combined script loader tags.
235 *
236 * @hook powered_cache_fo_script_loader_tag
237 *
238 * @param {string} $js_tag JS tag.
239 *
240 * @return {string} New value.
241 * @since 2.0
242 */
243 echo apply_filters( 'powered_cache_fo_script_loader_tag', "<script type='text/javascript' src='$href'></script>", $href ); // phpcs:ignore
244 echo PHP_EOL;
245 }
246 if ( isset( $js_array['extras']['after'] ) ) {
247 foreach ( $js_array['extras']['after'] as $inline_after ) {
248 echo $inline_after; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
249 }
250 }
251 }
252 }
253
254 /**
255 * Fires after combine JS
256 *
257 * @hook powered_cache_fo_js_concat_did_items
258 *
259 * @param {array} $javascripts JS resources.
260 *
261 * @since 2.0
262 */
263 do_action( 'powered_cache_fo_js_concat_did_items', $javascripts );
264
265 return $this->done;
266 }
267
268 /**
269 * Bust cache
270 *
271 * @param string $url URL
272 * @param string $siteurl Site URL
273 *
274 * @return string
275 */
276 public function cache_bust_mtime( $url, $siteurl ) {
277 if ( strpos( $url, '?m=' ) ) {
278 return $url;
279 }
280
281 $parts = wp_parse_url( $url );
282 if ( ! isset( $parts['path'] ) || empty( $parts['path'] ) ) {
283 return $url;
284 }
285
286 $file = Helper::realpath( $url, $siteurl );
287
288 $mtime = false;
289 if ( file_exists( $file ) ) {
290 $mtime = filemtime( $file );
291 }
292
293 if ( ! $mtime ) {
294 return $url;
295 }
296
297 if ( false === strpos( $url, '?' ) ) {
298 $q = '';
299 } else {
300 list( $url, $q ) = explode( '?', $url, 2 );
301 if ( strlen( $q ) ) {
302 $q = '&amp;' . $q;
303 }
304 }
305
306 return "$url?m={$mtime}g{$q}";
307 }
308
309 /**
310 * @param $key
311 *
312 * @return bool
313 */
314 public function __isset( $key ) {
315 return isset( $this->old_scripts->$key );
316 }
317
318 /**
319 * @param $key
320 */
321 public function __unset( $key ) {
322 unset( $this->old_scripts->$key );
323 }
324
325 /**
326 * @param $key
327 *
328 * @return mixed
329 */
330 public function &__get( $key ) {
331 return $this->old_scripts->$key;
332 }
333
334 /**
335 * @param $key
336 * @param $value
337 */
338 public function __set( $key, $value ) {
339 $this->old_scripts->$key = $value;
340 }
341 }
342