1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Class for JS optimization. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
7
|
|
|
exit; |
8
|
|
|
} |
9
|
|
|
|
10
|
|
|
class autoptimizeScripts extends autoptimizeBase |
11
|
|
|
{ |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Stores founds scripts. |
15
|
|
|
* |
16
|
|
|
* @var array |
17
|
|
|
*/ |
18
|
|
|
private $scripts = array(); |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Stores to be moved JS. |
22
|
|
|
* |
23
|
|
|
* @var array |
24
|
|
|
*/ |
25
|
|
|
private $move = array( |
26
|
|
|
'first' => array(), |
27
|
|
|
'last' => array(), |
28
|
|
|
); |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* List of not to be moved JS. |
32
|
|
|
* |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
private $dontmove = array( |
36
|
|
|
'document.write', |
37
|
|
|
'html5.js', |
38
|
|
|
'show_ads.js', |
39
|
|
|
'google_ad', |
40
|
|
|
'histats.com/js', |
41
|
|
|
'statcounter.com/counter/counter.js', |
42
|
|
|
'ws.amazon.com/widgets', |
43
|
|
|
'media.fastclick.net', |
44
|
|
|
'/ads/', |
45
|
|
|
'comment-form-quicktags/quicktags.php', |
46
|
|
|
'edToolbar', |
47
|
|
|
'intensedebate.com', |
48
|
|
|
'scripts.chitika.net/', |
49
|
|
|
'_gaq.push', |
50
|
|
|
'jotform.com/', |
51
|
|
|
'admin-bar.min.js', |
52
|
|
|
'GoogleAnalyticsObject', |
53
|
|
|
'plupload.full.min.js', |
54
|
|
|
'syntaxhighlighter', |
55
|
|
|
'adsbygoogle', |
56
|
|
|
'gist.github.com', |
57
|
|
|
'_stq', |
58
|
|
|
'nonce', |
59
|
|
|
'post_id', |
60
|
|
|
'data-noptimize', |
61
|
|
|
'logHuman', |
62
|
|
|
'amp-mobile-version-switcher', |
63
|
|
|
'data-rocketlazyloadscript', |
64
|
|
|
'rocket-browser-checker-js-after', |
65
|
|
|
); |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* List of to be moved JS. |
69
|
|
|
* |
70
|
|
|
* @var array |
71
|
|
|
*/ |
72
|
|
|
private $domove = array( |
73
|
|
|
'gaJsHost', |
74
|
|
|
'load_cmc', |
75
|
|
|
'jd.gallery.transitions.js', |
76
|
|
|
'swfobject.embedSWF(', |
77
|
|
|
'tiny_mce.js', |
78
|
|
|
'tinyMCEPreInit.go', |
79
|
|
|
); |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* List of JS that can be moved last (not used any more). |
83
|
|
|
* |
84
|
|
|
* @var array |
85
|
|
|
*/ |
86
|
|
|
private $domovelast = array( |
87
|
|
|
'addthis.com', |
88
|
|
|
'/afsonline/show_afs_search.js', |
89
|
|
|
'disqus.js', |
90
|
|
|
'networkedblogs.com/getnetworkwidget', |
91
|
|
|
'infolinks.com/js/', |
92
|
|
|
'jd.gallery.js.php', |
93
|
|
|
'jd.gallery.transitions.js', |
94
|
|
|
'swfobject.embedSWF(', |
95
|
|
|
'linkwithin.com/widget.js', |
96
|
|
|
'tiny_mce.js', |
97
|
|
|
'tinyMCEPreInit.go', |
98
|
|
|
); |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Setting CDN base URL. |
102
|
|
|
* |
103
|
|
|
* @var string |
104
|
|
|
*/ |
105
|
|
|
public $cdn_url = ''; |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Setting; aggregate or not. |
109
|
|
|
* |
110
|
|
|
* @var bool |
111
|
|
|
*/ |
112
|
|
|
private $aggregate = true; |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Setting; if not aggregated, should we defer? |
116
|
|
|
* |
117
|
|
|
* @var bool |
118
|
|
|
*/ |
119
|
|
|
private $defer_not_aggregate = false; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Setting; try/catch wrapping or not. |
123
|
|
|
* |
124
|
|
|
* @var bool |
125
|
|
|
*/ |
126
|
|
|
private $trycatch = false; |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* State; is JS already minified. |
130
|
|
|
* |
131
|
|
|
* @var bool |
132
|
|
|
*/ |
133
|
|
|
private $alreadyminified = false; |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Setting; force JS in head or not. |
137
|
|
|
* |
138
|
|
|
* @var bool |
139
|
|
|
*/ |
140
|
|
|
private $forcehead = true; |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Setting; aggregate inline JS or not. |
144
|
|
|
* |
145
|
|
|
* @var bool |
146
|
|
|
*/ |
147
|
|
|
private $include_inline = false; |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* State; holds JS code. |
151
|
|
|
* |
152
|
|
|
* @var string |
153
|
|
|
*/ |
154
|
|
|
private $jscode = ''; |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* State; holds URL of JS-file. |
158
|
|
|
* |
159
|
|
|
* @var string |
160
|
|
|
*/ |
161
|
|
|
private $url = ''; |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* State; stores rest of HTML if (old) option "only in head" is on. |
165
|
|
|
* |
166
|
|
|
* @var string |
167
|
|
|
*/ |
168
|
|
|
private $restofcontent = ''; |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* State; holds md5-hash. |
172
|
|
|
* |
173
|
|
|
* @var string |
174
|
|
|
*/ |
175
|
|
|
private $md5hash = ''; |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Setting (filter); allowlist of to be aggregated JS. |
179
|
|
|
* |
180
|
|
|
* @var string |
181
|
|
|
*/ |
182
|
|
|
private $allowlist = ''; |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Setting (filter); holds JS that should be removed. |
186
|
|
|
* |
187
|
|
|
* @var array |
188
|
|
|
*/ |
189
|
|
|
private $jsremovables = array(); |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Setting (filter); can we inject already minified files after the |
193
|
|
|
* unminified aggregate JS has been minified. |
194
|
|
|
* |
195
|
|
|
* @var bool |
196
|
|
|
*/ |
197
|
|
|
private $inject_min_late = true; |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Setting; should excluded JS be minified (if not already). |
201
|
|
|
* |
202
|
|
|
* @var bool |
203
|
|
|
*/ |
204
|
|
|
private $minify_excluded = true; |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Reads the page and collects script tags. |
208
|
|
|
* |
209
|
|
|
* @param array $options all options. |
210
|
|
|
*/ |
211
|
|
|
public function read( $options ) |
212
|
|
|
{ |
213
|
|
|
$noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content ); |
214
|
|
|
if ( $noptimize_js ) { |
215
|
|
|
return false; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
// only optimize known good JS? |
219
|
|
|
$allowlist_js = apply_filters( 'autoptimize_filter_js_allowlist', '', $this->content ); |
220
|
|
|
$allowlist_js = apply_filters( 'autoptimize_filter_js_whitelist', $allowlist_js, $this->content ); // fixme: to be removed in next version. |
221
|
|
View Code Duplication |
if ( ! empty( $allowlist_js ) ) { |
|
|
|
|
222
|
|
|
$this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_js ) ) ); |
|
|
|
|
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
// is there JS we should simply remove? |
226
|
|
|
$removable_js = apply_filters( 'autoptimize_filter_js_removables', '', $this->content ); |
227
|
|
View Code Duplication |
if ( ! empty( $removable_js ) ) { |
|
|
|
|
228
|
|
|
$this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removable_js ) ) ); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
// only header? |
232
|
|
View Code Duplication |
if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) { |
|
|
|
|
233
|
|
|
$content = explode( '</head>', $this->content, 2 ); |
234
|
|
|
$this->content = $content[0] . '</head>'; |
235
|
|
|
$this->restofcontent = $content[1]; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
// Determine whether we're doing JS-files aggregation or not. |
239
|
|
|
if ( ! $options['aggregate'] ) { |
240
|
|
|
$this->aggregate = false; |
241
|
|
|
} |
242
|
|
|
// Returning true for "dontaggregate" turns off aggregation. |
243
|
|
|
if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) { |
244
|
|
|
$this->aggregate = false; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
// Defer when not aggregating. |
248
|
|
|
if ( false === $this->aggregate && apply_filters( 'autoptimize_js_filter_defer_not_aggregate', $options['defer_not_aggregate'] ) ) { |
249
|
|
|
$this->defer_not_aggregate = true; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
// include inline? |
253
|
|
|
if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) { |
254
|
|
|
$this->include_inline = true; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
// filter to "late inject minified JS", default to true for now (it is faster). |
258
|
|
|
$this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true ); |
259
|
|
|
|
260
|
|
|
// filters to override hardcoded do(nt)move(last) array contents (array in, array out!). |
261
|
|
|
$this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove ); |
262
|
|
|
$this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast ); |
263
|
|
|
$this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove ); |
264
|
|
|
|
265
|
|
|
// Determine whether excluded files should be minified if not yet so. |
266
|
|
|
if ( ! $options['minify_excluded'] && $options['aggregate'] ) { |
267
|
|
|
$this->minify_excluded = false; |
268
|
|
|
} |
269
|
|
|
$this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded, '' ); |
270
|
|
|
|
271
|
|
|
// get extra exclusions settings or filter. |
272
|
|
|
$exclude_js = $options['js_exclude']; |
273
|
|
|
$exclude_js = apply_filters( 'autoptimize_filter_js_exclude', $exclude_js, $this->content ); |
274
|
|
|
|
275
|
|
|
if ( '' !== $exclude_js ) { |
276
|
|
|
if ( is_array( $exclude_js ) ) { |
277
|
|
|
$remove_keys = array_keys( $exclude_js, 'remove' ); |
278
|
|
|
if ( false !== $remove_keys ) { |
279
|
|
|
foreach ( $remove_keys as $remove_key ) { |
280
|
|
|
unset( $exclude_js[ $remove_key ] ); |
281
|
|
|
$this->jsremovables[] = $remove_key; |
282
|
|
|
} |
283
|
|
|
} |
284
|
|
|
$excl_js_arr = array_keys( $exclude_js ); |
285
|
|
|
} else { |
286
|
|
|
$excl_js_arr = array_filter( array_map( 'trim', explode( ',', $exclude_js ) ) ); |
287
|
|
|
} |
288
|
|
|
$this->dontmove = array_merge( $excl_js_arr, $this->dontmove ); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
// Should we add try-catch? |
292
|
|
|
if ( $options['trycatch'] ) { |
293
|
|
|
$this->trycatch = true; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
// force js in head? |
297
|
|
|
if ( $options['forcehead'] ) { |
298
|
|
|
$this->forcehead = true; |
299
|
|
|
} else { |
300
|
|
|
$this->forcehead = false; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
$this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead ); |
304
|
|
|
|
305
|
|
|
// get cdn url. |
306
|
|
|
$this->cdn_url = $options['cdn_url']; |
307
|
|
|
|
308
|
|
|
// noptimize me. |
309
|
|
|
$this->content = $this->hide_noptimize( $this->content ); |
310
|
|
|
|
311
|
|
|
// Save IE hacks. |
312
|
|
|
$this->content = $this->hide_iehacks( $this->content ); |
313
|
|
|
|
314
|
|
|
// comments. |
315
|
|
|
$this->content = $this->hide_comments( $this->content ); |
316
|
|
|
|
317
|
|
|
// Get script files. |
318
|
|
|
if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) { |
319
|
|
|
foreach ( $matches[0] as $tag ) { |
320
|
|
|
// only consider script aggregation for types allowlisted in should_aggregate-function. |
321
|
|
|
$should_aggregate = $this->should_aggregate( $tag ); |
322
|
|
|
if ( ! $should_aggregate ) { |
323
|
|
|
$tag = ''; |
324
|
|
|
continue; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) { |
328
|
|
|
// non-inline script. |
329
|
|
View Code Duplication |
if ( $this->isremovable( $tag, $this->jsremovables ) ) { |
|
|
|
|
330
|
|
|
$this->content = str_replace( $tag, '', $this->content ); |
331
|
|
|
continue; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$orig_tag = null; |
|
|
|
|
335
|
|
|
$url = current( explode( '?', $source[2], 2 ) ); |
336
|
|
|
$path = $this->getpath( $url ); |
337
|
|
|
if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable( $tag ) ) { |
338
|
|
|
// ok to optimize, add to array. |
339
|
|
|
$this->scripts[] = $path; |
340
|
|
|
} else { |
341
|
|
|
$orig_tag = $tag; |
342
|
|
|
$new_tag = $tag; |
343
|
|
|
|
344
|
|
|
// non-mergeable script (excluded or dynamic or external). |
345
|
|
|
if ( is_array( $exclude_js ) ) { |
346
|
|
|
// should we add flags? |
347
|
|
|
foreach ( $exclude_js as $excl_tag => $excl_flags ) { |
348
|
|
|
if ( false !== strpos( $orig_tag, $excl_tag ) && in_array( $excl_flags, array( 'async', 'defer' ) ) ) { |
349
|
|
|
$new_tag = str_replace( '<script ', '<script ' . $excl_flags . ' ', $new_tag ); |
350
|
|
|
} |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
// not aggregating but deferring? |
355
|
|
|
if ( $this->defer_not_aggregate && false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path && strpos( $new_tag, ' defer' ) === false ) { |
356
|
|
|
$new_tag = str_replace( '<script ', '<script defer ', $new_tag ); |
357
|
|
|
// and remove async as async+defer=async while we explicitly want defer. |
358
|
|
|
if ( strpos( $new_tag, ' async' ) !== false && apply_filters( 'autoptimize_filter_js_defer_remove_async', true ) ) { |
359
|
|
|
$new_tag = str_replace( array( ' async', ' async="async"', " async='async'" ), '', $new_tag ); |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
// Should we minify the non-aggregated script? |
364
|
|
|
// -> if aggregate is on and exclude minify is on |
365
|
|
|
// -> if aggregate is off and the file is not in dontmove. |
366
|
|
View Code Duplication |
if ( $path && $this->minify_excluded ) { |
|
|
|
|
367
|
|
|
$consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false ); |
368
|
|
|
if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) { |
369
|
|
|
$minified_url = $this->minify_single( $path ); |
370
|
|
|
if ( ! empty( $minified_url ) ) { |
371
|
|
|
// Replace original URL with minified URL from cache. |
372
|
|
|
$new_tag = str_replace( $url, $minified_url, $new_tag ); |
373
|
|
|
} elseif ( apply_filters( 'autoptimize_filter_ccsjs_remove_empty_minified_url', false ) ) { |
374
|
|
|
// Remove the original script tag, because cache content is empty but only if filter |
375
|
|
|
// is trued because $minified_url is also false if original JS is minified already. |
376
|
|
|
$new_tag = ''; |
377
|
|
|
} |
378
|
|
|
} |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
if ( $this->ismovable( $new_tag ) ) { |
382
|
|
|
// can be moved, flags and all. |
383
|
|
|
if ( $this->movetolast( $new_tag ) ) { |
384
|
|
|
$this->move['last'][] = $new_tag; |
385
|
|
|
} else { |
386
|
|
|
$this->move['first'][] = $new_tag; |
387
|
|
|
} |
388
|
|
View Code Duplication |
} else { |
|
|
|
|
389
|
|
|
// cannot be moved, so if flag was added re-inject altered tag immediately. |
390
|
|
|
if ( ( '' !== $new_tag && $orig_tag !== $new_tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_js_remove_empty_files', false ) ) ) { |
391
|
|
|
$this->content = str_replace( $orig_tag, $new_tag, $this->content ); |
392
|
|
|
$orig_tag = ''; |
|
|
|
|
393
|
|
|
} |
394
|
|
|
// and forget about the $tag (not to be touched any more). |
395
|
|
|
$tag = ''; |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
} else { |
399
|
|
|
// Inline script. |
400
|
|
View Code Duplication |
if ( $this->isremovable( $tag, $this->jsremovables ) ) { |
|
|
|
|
401
|
|
|
$this->content = str_replace( $tag, '', $this->content ); |
402
|
|
|
continue; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
// unhide comments, as javascript may be wrapped in comment-tags for old times' sake. |
406
|
|
|
$tag = $this->restore_comments( $tag ); |
407
|
|
|
if ( $this->ismergeable( $tag ) && $this->include_inline ) { |
408
|
|
|
preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code ); |
409
|
|
|
$code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] ); |
410
|
|
|
$code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code ); |
411
|
|
|
$this->scripts[] = 'INLINE;' . $code; |
412
|
|
|
} else { |
413
|
|
|
// Can we move this? |
414
|
|
|
$autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag ); |
415
|
|
|
if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) { |
416
|
|
|
if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) { |
417
|
|
|
$this->move['last'][] = $tag; |
418
|
|
|
} else { |
419
|
|
|
$this->move['first'][] = $tag; |
420
|
|
|
} |
421
|
|
|
} else { |
422
|
|
|
// We shouldn't touch this. |
423
|
|
|
$tag = ''; |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
// Re-hide comments to be able to do the removal based on tag from $this->content. |
427
|
|
|
$tag = $this->hide_comments( $tag ); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
// Remove the original script tag. |
431
|
|
|
$this->content = str_replace( $tag, '', $this->content ); |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
return true; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
// No script files, great ;-) . |
438
|
|
|
return false; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Determines wheter a certain `<script>` $tag should be aggregated or not. |
443
|
|
|
* |
444
|
|
|
* We consider these as "aggregation-safe" currently: |
445
|
|
|
* - script tags without a `type` attribute |
446
|
|
|
* - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`, |
447
|
|
|
* and `application/ecmascript` |
448
|
|
|
* |
449
|
|
|
* Everything else should return false. |
450
|
|
|
* |
451
|
|
|
* @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type |
452
|
|
|
* |
453
|
|
|
* @param string $tag Script node & child(ren). |
454
|
|
|
* @return bool |
455
|
|
|
*/ |
456
|
|
|
public function should_aggregate( $tag ) |
457
|
|
|
{ |
458
|
|
|
if ( empty( $tag ) ) { |
459
|
|
|
return false; |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
// We're only interested in the type attribute of the <script> tag itself, not any possible |
463
|
|
|
// inline code that might just contain the 'type=' string... |
464
|
|
|
$tag_parts = array(); |
465
|
|
|
preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts ); |
466
|
|
|
$tag_without_contents = null; |
467
|
|
|
if ( ! empty( $tag_parts[1] ) ) { |
468
|
|
|
$tag_without_contents = $tag_parts[1]; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
$has_type = ( strpos( $tag_without_contents, 'type' ) !== false ); |
472
|
|
|
|
473
|
|
|
$type_valid = false; |
474
|
|
|
if ( $has_type ) { |
475
|
|
|
$type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents ); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
$should_aggregate = false; |
479
|
|
|
if ( ! $has_type || $type_valid ) { |
480
|
|
|
$should_aggregate = true; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
return $should_aggregate; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Joins and optimizes JS. |
488
|
|
|
*/ |
489
|
|
|
public function minify() |
490
|
|
|
{ |
491
|
|
|
foreach ( $this->scripts as $script ) { |
492
|
|
|
if ( empty( $script ) ) { |
493
|
|
|
continue; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
// TODO/FIXME: some duplicate code here, can be reduced/simplified. |
497
|
|
|
if ( preg_match( '#^INLINE;#', $script ) ) { |
498
|
|
|
// Inline script. |
499
|
|
|
$script = preg_replace( '#^INLINE;#', '', $script ); |
500
|
|
|
$script = rtrim( $script, ";\n\t\r" ) . ';'; |
501
|
|
|
// Add try-catch? |
502
|
|
|
if ( $this->trycatch ) { |
503
|
|
|
$script = 'try{' . $script . '}catch(e){}'; |
504
|
|
|
} |
505
|
|
|
$tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' ); |
506
|
|
|
if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) { |
507
|
|
|
$script = $tmpscript; |
508
|
|
|
$this->alreadyminified = true; |
509
|
|
|
} |
510
|
|
|
$this->jscode .= "\n" . $script; |
511
|
|
|
} else { |
512
|
|
|
// External script. |
513
|
|
|
if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) { |
514
|
|
|
$scriptsrc = file_get_contents( $script ); |
515
|
|
|
$scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc ); |
516
|
|
|
$scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';'; |
517
|
|
|
// Add try-catch? |
518
|
|
|
if ( $this->trycatch ) { |
519
|
|
|
$scriptsrc = 'try{' . $scriptsrc . '}catch(e){}'; |
520
|
|
|
} |
521
|
|
|
$tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script ); |
522
|
|
View Code Duplication |
if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) { |
|
|
|
|
523
|
|
|
$scriptsrc = $tmpscriptsrc; |
524
|
|
|
$this->alreadyminified = true; |
525
|
|
|
} elseif ( $this->can_inject_late( $script ) ) { |
526
|
|
|
$scriptsrc = self::build_injectlater_marker( $script, md5( $scriptsrc ) ); |
527
|
|
|
} |
528
|
|
|
$this->jscode .= "\n" . $scriptsrc; |
529
|
|
|
} |
530
|
|
|
} |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
// Check for already-minified code. |
534
|
|
|
$this->md5hash = md5( $this->jscode ); |
535
|
|
|
$ccheck = new autoptimizeCache( $this->md5hash, 'js' ); |
536
|
|
|
if ( $ccheck->check() ) { |
537
|
|
|
$this->jscode = $ccheck->retrieve(); |
|
|
|
|
538
|
|
|
return true; |
539
|
|
|
} |
540
|
|
|
unset( $ccheck ); |
541
|
|
|
|
542
|
|
|
// $this->jscode has all the uncompressed code now. |
543
|
|
|
if ( true !== $this->alreadyminified ) { |
544
|
|
|
if ( apply_filters( 'autoptimize_js_do_minify', true ) ) { |
545
|
|
|
$tmp_jscode = trim( JSMin::minify( $this->jscode ) ); |
546
|
|
|
if ( ! empty( $tmp_jscode ) ) { |
547
|
|
|
$this->jscode = $tmp_jscode; |
548
|
|
|
unset( $tmp_jscode ); |
549
|
|
|
} |
550
|
|
|
$this->jscode = $this->inject_minified( $this->jscode ); |
551
|
|
|
$this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode ); |
552
|
|
|
return true; |
553
|
|
|
} else { |
554
|
|
|
$this->jscode = $this->inject_minified( $this->jscode ); |
555
|
|
|
return false; |
556
|
|
|
} |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
$this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode ); |
560
|
|
|
return true; |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Caches the JS in uncompressed, deflated and gzipped form. |
565
|
|
|
*/ |
566
|
|
|
public function cache() |
567
|
|
|
{ |
568
|
|
|
$cache = new autoptimizeCache( $this->md5hash, 'js' ); |
569
|
|
|
if ( ! $cache->check() ) { |
570
|
|
|
// Cache our code. |
571
|
|
|
$cache->cache( $this->jscode, 'text/javascript' ); |
572
|
|
|
} |
573
|
|
|
$this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname(); |
574
|
|
|
$this->url = $this->url_replace_cdn( $this->url ); |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* Returns the content. |
579
|
|
|
*/ |
580
|
|
|
public function getcontent() |
581
|
|
|
{ |
582
|
|
|
// Restore the full content. |
583
|
|
|
if ( ! empty( $this->restofcontent ) ) { |
584
|
|
|
$this->content .= $this->restofcontent; |
585
|
|
|
$this->restofcontent = ''; |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
// Add the scripts taking forcehead/ deferred (default) into account. |
589
|
|
|
if ( $this->forcehead ) { |
590
|
|
|
$replace_tag = array( '</head>', 'before' ); |
591
|
|
|
$defer = ''; |
592
|
|
|
} else { |
593
|
|
|
$replace_tag = array( '</body>', 'before' ); |
594
|
|
|
$defer = 'defer '; |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
$defer = apply_filters( 'autoptimize_filter_js_defer', $defer ); |
598
|
|
|
$type_js = ''; |
599
|
|
|
if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) { |
600
|
|
|
$type_js = 'type="text/javascript" '; |
601
|
|
|
} |
602
|
|
|
|
603
|
|
|
$bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>'; |
604
|
|
|
$bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload ); |
605
|
|
|
|
606
|
|
|
$bodyreplacement = implode( '', $this->move['first'] ); |
607
|
|
|
$bodyreplacement .= $bodyreplacementpayload; |
608
|
|
|
$bodyreplacement .= implode( '', $this->move['last'] ); |
609
|
|
|
|
610
|
|
|
$replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag ); |
611
|
|
|
|
612
|
|
|
if ( strlen( $this->jscode ) > 0 ) { |
613
|
|
|
$this->inject_in_html( $bodyreplacement, $replace_tag ); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
// Restore comments. |
617
|
|
|
$this->content = $this->restore_comments( $this->content ); |
618
|
|
|
|
619
|
|
|
// Restore IE hacks. |
620
|
|
|
$this->content = $this->restore_iehacks( $this->content ); |
621
|
|
|
|
622
|
|
|
// Restore noptimize. |
623
|
|
|
$this->content = $this->restore_noptimize( $this->content ); |
624
|
|
|
|
625
|
|
|
// Return the modified HTML. |
626
|
|
|
return $this->content; |
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
/** |
630
|
|
|
* Checks against the allow- and blocklists. |
631
|
|
|
* |
632
|
|
|
* @param string $tag JS tag. |
633
|
|
|
*/ |
634
|
|
|
private function ismergeable( $tag ) |
635
|
|
|
{ |
636
|
|
|
if ( empty( $tag ) || ! $this->aggregate ) { |
637
|
|
|
return false; |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
if ( ! empty( $this->allowlist ) ) { |
641
|
|
|
foreach ( $this->allowlist as $match ) { |
|
|
|
|
642
|
|
|
if ( false !== strpos( $tag, $match ) ) { |
643
|
|
|
return true; |
644
|
|
|
} |
645
|
|
|
} |
646
|
|
|
// No match with allowlist. |
647
|
|
|
return false; |
648
|
|
|
} else { |
649
|
|
|
foreach ( $this->domove as $match ) { |
650
|
|
|
if ( false !== strpos( $tag, $match ) ) { |
651
|
|
|
// Matched something. |
652
|
|
|
return false; |
653
|
|
|
} |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
if ( $this->movetolast( $tag ) ) { |
657
|
|
|
return false; |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
foreach ( $this->dontmove as $match ) { |
661
|
|
|
if ( false !== strpos( $tag, $match ) ) { |
662
|
|
|
// Matched something. |
663
|
|
|
return false; |
664
|
|
|
} |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
// If we're here it's safe to merge. |
668
|
|
|
return true; |
669
|
|
|
} |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
/** |
673
|
|
|
* Checks agains the blocklist. |
674
|
|
|
* |
675
|
|
|
* @param string $tag tag to check for blocklist (exclusions). |
676
|
|
|
*/ |
677
|
|
|
private function ismovable( $tag ) |
678
|
|
|
{ |
679
|
|
|
if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) { |
680
|
|
|
return false; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
foreach ( $this->domove as $match ) { |
684
|
|
|
if ( false !== strpos( $tag, $match ) ) { |
685
|
|
|
// Matched something. |
686
|
|
|
return true; |
687
|
|
|
} |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
if ( $this->movetolast( $tag ) ) { |
691
|
|
|
return true; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
foreach ( $this->dontmove as $match ) { |
695
|
|
|
if ( false !== strpos( $tag, $match ) ) { |
696
|
|
|
// Matched something. |
697
|
|
|
return false; |
698
|
|
|
} |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
// If we're here it's safe to move. |
702
|
|
|
return true; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
private function movetolast( $tag ) |
706
|
|
|
{ |
707
|
|
|
if ( empty( $tag ) ) { |
708
|
|
|
return false; |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
foreach ( $this->domovelast as $match ) { |
712
|
|
|
if ( false !== strpos( $tag, $match ) ) { |
713
|
|
|
// Matched, return true. |
714
|
|
|
return true; |
715
|
|
|
} |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
// Should be in 'first'. |
719
|
|
|
return false; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* Determines wheter a <script> $tag can be excluded from minification (as already minified) based on: |
724
|
|
|
* - inject_min_late being active |
725
|
|
|
* - filename ending in `min.js` |
726
|
|
|
* - filename matching `js/jquery/jquery.js` (WordPress core jquery, is minified) |
727
|
|
|
* - filename matching one passed in the consider minified filter |
728
|
|
|
* |
729
|
|
|
* @param string $js_path Path to JS file. |
730
|
|
|
* @return bool |
731
|
|
|
*/ |
732
|
|
|
private function can_inject_late( $js_path ) { |
733
|
|
|
$consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false ); |
734
|
|
|
if ( true !== $this->inject_min_late ) { |
735
|
|
|
// late-inject turned off. |
736
|
|
|
return false; |
737
|
|
|
} elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( false === strpos( $js_path, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) { |
738
|
|
|
// file not minified based on filename & filter. |
739
|
|
|
return false; |
740
|
|
|
} else { |
741
|
|
|
// phew, all is safe, we can late-inject. |
742
|
|
|
return true; |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
/** |
747
|
|
|
* Returns whether we're doing aggregation or not. |
748
|
|
|
* |
749
|
|
|
* @return bool |
750
|
|
|
*/ |
751
|
|
|
public function aggregating() |
752
|
|
|
{ |
753
|
|
|
return $this->aggregate; |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Minifies a single local js file and returns its (cached) url. |
758
|
|
|
* |
759
|
|
|
* @param string $filepath Filepath. |
760
|
|
|
* @param bool $cache_miss Optional. Force a cache miss. Default false. |
761
|
|
|
* |
762
|
|
|
* @return bool|string Url pointing to the minified js file or false. |
763
|
|
|
*/ |
764
|
|
|
public function minify_single( $filepath, $cache_miss = false ) |
765
|
|
|
{ |
766
|
|
|
$contents = $this->prepare_minify_single( $filepath ); |
767
|
|
|
|
768
|
|
|
if ( empty( $contents ) ) { |
769
|
|
|
return false; |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
// Check cache. |
773
|
|
|
$hash = 'single_' . md5( $contents ); |
774
|
|
|
$cache = new autoptimizeCache( $hash, 'js' ); |
775
|
|
|
|
776
|
|
|
// If not in cache already, minify... |
777
|
|
|
if ( ! $cache->check() || $cache_miss ) { |
778
|
|
|
$contents = trim( JSMin::minify( $contents ) ); |
779
|
|
|
|
780
|
|
|
// Check if minified cache content is empty. |
781
|
|
|
if ( empty( $contents ) ) { |
782
|
|
|
return false; |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
// Filter contents of excluded minified CSS. |
786
|
|
|
$contents = apply_filters( 'autoptimize_filter_js_single_after_minify', $contents ); |
787
|
|
|
|
788
|
|
|
// Store in cache. |
789
|
|
|
$cache->cache( $contents, 'text/javascript' ); |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
$url = $this->build_minify_single_url( $cache ); |
793
|
|
|
|
794
|
|
|
return $url; |
795
|
|
|
} |
796
|
|
|
} |
797
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.