PluginProbe
ezCache / 1.3
ezCache v1.3
2.6.5 2.6.4 2.6.2 2.6.3 2.6.1 2.6.0 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5 2.2.1 2.2.2 trunk 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.3 1.3.1 1.3.10 1.3.11 All 49 releases
ezcache / includes / FileOptimizer / JsCombiner.php

JsCombiner.php in ezCache 1.3, at includes/FileOptimizer/JsCombiner.php

384 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Upress\EzCache\FileOptimizer;
3
4 use MatthiasMullie\Minify\JS;
5
6 class JsCombiner extends BaseFileOptimizer {
7 static $FILE_TYPE = 'js';
8
9 protected $minifier;
10 protected $jquery_url;
11 protected $minify_base_path;
12 protected $minify_base_url;
13 protected $in_location;
14 private $scripts = [];
15
16 function __construct( $cache_dir, $cache_url, $location='all' ) {
17 $this->minifier = new JS();
18 $this->jquery_url = $this->get_jquery_url();
19 $this->minify_base_path = $cache_dir;
20 $this->minify_base_url = $cache_url;
21 $this->in_location = $location;
22 }
23
24 /**
25 * Gets jQuery URL if defer JS safe mode is active
26 *
27 * @return bool|string
28 */
29 protected function get_jquery_url() {
30 global $wp_scripts;
31
32 if ( ! isset( $wp_scripts->registered['jquery-core']->src ) ) {
33 return false;
34 }
35
36 if ( '' === $this->get_url_component( $wp_scripts->registered['jquery-core']->src, PHP_URL_HOST ) ) {
37 return wp_parse_url( site_url( $wp_scripts->registered['jquery-core']->src ), PHP_URL_PATH );
38 }
39
40 return $wp_scripts->registered['jquery-core']->src;
41 }
42
43 /**
44 * Minifies and combines JavaScripts into one
45 *
46 * @param string $html HTML content.
47 * @return string
48 */
49 function optimize( $html ) {
50 $html_buff = $html;
51 if ( 'head' === $this->in_location ) {
52 $html_buff = substr( $html_buff, 0, strpos( $html_buff, '</head>' ) );
53 } else if ( 'body' === $this->in_location ) {
54 $html_buff = substr( $html_buff, strpos( $html_buff, '</head>' ) );
55 }
56 $scripts = $this->find( '<script.*<\/script>', $html_buff );
57
58 if ( ! $scripts ) {
59 return $html;
60 }
61
62 // Parses found nodes to keep only the ones to combine
63 $combine_scripts = $this->parse( $scripts );
64
65 if ( empty( $combine_scripts ) ) {
66 return $html;
67 }
68
69 // Gets content for each script either from inline or from src
70 $content = $this->get_content();
71
72 if ( empty( $content ) ) {
73 return $html;
74 }
75
76 // Creates the minify URL if the minification is successful
77 $minify_url = $this->combine( $content );
78
79 if ( ! $minify_url ) {
80 return $html;
81 }
82
83 if ( 'head' === $this->in_location ) {
84 $html = str_replace( '</head>', '<script src="' . esc_url( $minify_url ) . '" data-minify="1"></script></head>', $html );
85 } else {
86 $html = str_replace( '</body>', '<script src="' . esc_url( $minify_url ) . '" data-minify="1"></script></body>', $html );
87 }
88
89 $combine_scripts = array_filter( $combine_scripts );
90 foreach ( $combine_scripts as $script ) {
91 $html = str_replace( $script[0], '', $html );
92 }
93
94 return $html;
95 }
96
97 /**
98 * Parses found nodes to keep only the ones to combine
99 *
100 * @param array $scripts scripts corresponding to JS file or content.
101 *
102 * @return array
103 */
104 function parse( $scripts ) {
105 $scripts = array_map( function( $script ) {
106 preg_match( '/<script\s+([^>]+[\s\'"])?src\s*=\s*[\'"]\s*?([^\'"]+\.js(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>/Umsi', $script[0], $matches );
107
108 if ( isset( $matches[2] ) ) {
109 if ( $this->is_external_file( $matches[2] ) ) {
110 foreach ( $this->get_excluded_external_file_path() as $excluded_file ) {
111 if ( false !== strpos( $matches[2], $excluded_file ) ) {
112 return [];
113 }
114 }
115
116 $this->scripts[] = [
117 'type' => 'url',
118 'content' => $matches[2],
119 ];
120
121 return $script;
122 }
123
124 if ( $this->is_minify_excluded_file( $matches ) ) {
125 return [];
126 }
127
128 if ( $this->jquery_url && false !== strpos( $matches[2], $this->jquery_url ) ) {
129 return [];
130 }
131
132 $file_path = $this->get_file_path( $matches[2] );
133
134 if ( ! $file_path ) {
135 return [];
136 }
137
138 $this->scripts[] = [
139 'type' => 'file',
140 'content' => $file_path,
141 ];
142 } elseif ( ! isset( $matches[2] ) ) {
143 preg_match( '/<script\b([^>]*)>(?:\/\*\s*<!\[CDATA\[\s*\*\/)?\s*([\s\S]*?)\s*(?:\/\*\s*\]\]>\s*\*\/)?<\/script>/msi', $script[0], $matches_inline );
144
145 if ( strpos( $matches_inline[1], 'type' ) !== false && ! preg_match( '/type\s*=\s*["\']?(?:text|application)\/(?:(?:x\-)?javascript|ecmascript)["\']?/i', $matches_inline[1] ) ) {
146 return [];
147 }
148
149 if ( false !== strpos( $matches_inline[1], 'src=' ) ) {
150 return [];
151 }
152
153 $test_localize_script = str_replace( array( "\r", "\n" ), '', $matches_inline[2] );
154
155 if ( in_array( $test_localize_script, $this->get_localized_scripts(), true ) ) {
156 return [];
157 }
158
159 foreach ( $this->get_excluded_inline_content() as $excluded_content ) {
160 if ( false !== strpos( $matches_inline[2], $excluded_content ) ) {
161 return [];
162 }
163 }
164
165 $this->scripts[] = [
166 'type' => 'inline',
167 'content' => $matches_inline[2],
168 ];
169 }
170
171 return $script;
172 }, $scripts );
173
174 return array_filter( $scripts );
175 }
176
177 /**
178 * Gets content for each script either from inline or from src
179 *
180 * @return string
181 */
182 protected function get_content() {
183 $content = '';
184
185 foreach ( $this->scripts as $script ) {
186 if ( 'file' === $script['type'] ) {
187 $file_content = file_get_contents( $script['content'] );
188 $content .= $file_content;
189
190 $this->minifier->add( $file_content );
191 } elseif ( 'url' === $script['type'] ) {
192 $file_content = file_get_contents( esc_url( $script['content'] ) );
193 $content .= $file_content;
194
195 $this->minifier->add( $file_content );
196 } elseif ( 'inline' === $script['type'] ) {
197 $inline_js = rtrim( $script['content'], ";\n\t\r" ) . ';';
198 $content .= $inline_js;
199
200 $this->minifier->add( $inline_js );
201 }
202 }
203
204 return $content;
205 }
206
207 /**
208 * Creates the minify URL if the minification is successful
209 *
210 * @param string $content Content to minify & combine.
211
212 * @return string|bool The minify URL if successful, false otherwise
213 */
214 protected function combine( $content ) {
215 if ( empty( $content ) ) {
216 return false;
217 }
218
219 $filename = md5( $content ) . '.js';
220 $minified_file = $this->minify_base_path . $filename;
221
222 if ( file_exists( $minified_file ) ) {
223 touch( $minified_file );
224 } else {
225 $minified_content = $this->minifier->minify();
226
227 if ( ! $minified_content ) {
228 return false;
229 }
230
231 wp_mkdir_p( dirname( $minified_file ) );
232 $minify_filepath = file_put_contents( $minified_file, $minified_content );
233
234 if ( ! $minify_filepath ) {
235 return false;
236 }
237 }
238
239 return $this->minify_base_url . $filename;
240 }
241
242 /**
243 * Patterns in content excluded from being combined
244 *
245 * @return array
246 */
247 protected function get_excluded_inline_content() {
248 return [
249 'document.write',
250 'google_ad',
251 'edToolbar',
252 'gtag',
253 '_gaq.push',
254 '_gaLt',
255 'GoogleAnalyticsObject',
256 'syntaxhighlighter',
257 'adsbygoogle',
258 'ci_cap_',
259 '_stq',
260 'nonce',
261 'post_id',
262 'LogHuman',
263 'idcomments_acct',
264 'ch_client',
265 'sc_online_t',
266 '_stq',
267 'bannersnack_embed',
268 'vtn_player_type',
269 'ven_video_key',
270 'ANS_customer_id',
271 'tdBlock',
272 'tdLocalCache',
273 '"url":',
274 'td_live_css_uid',
275 'tdAjaxCount',
276 'lazyLoadOptions',
277 'adthrive',
278 'loadCSS',
279 'google_tag_params',
280 'clicky_custom',
281 'clicky_site_ids',
282 'NSLPopupCenter',
283 '_paq',
284 'gtm',
285 'dataLayer',
286 'RecaptchaLoad',
287 'recaptcha',
288 'WPCOM_sharing_counts',
289 'jetpack_remote_comment',
290 'scrapeazon',
291 'subscribe-field',
292 'contextly',
293 ];
294 }
295
296 /**
297 * Patterns in URL excluded from being combined
298 *
299 * @return array
300 */
301 protected function get_excluded_external_file_path() {
302 return [
303 'html5.js',
304 'show_ads.js',
305 'histats.com/js',
306 'ws.amazon.com/widgets',
307 '/ads/',
308 'intensedebate.com',
309 'scripts.chitika.net/',
310 'jotform.com/',
311 'gist.github.com',
312 'forms.aweber.com',
313 'video.unrulymedia.com',
314 'stats.wp.com',
315 'stats.wordpress.com',
316 'widget.rafflecopter.com',
317 'widget-prime.rafflecopter.com',
318 'releases.flowplayer.org',
319 'c.ad6media.fr',
320 'cdn.stickyadstv.com',
321 'www.smava.de',
322 'contextual.media.net',
323 'app.getresponse.com',
324 'adserver.reklamstore.com',
325 's0.wp.com',
326 'wprp.zemanta.com',
327 'files.bannersnack.com',
328 'smarticon.geotrust.com',
329 'js.gleam.io',
330 'ir-na.amazon-adsystem.com',
331 'web.ventunotech.com',
332 'verify.authorize.net',
333 'ads.themoneytizer.com',
334 'embed.finanzcheck.de',
335 'imagesrv.adition.com',
336 'js.juicyads.com',
337 'form.jotformeu.com',
338 'speakerdeck.com',
339 'content.jwplatform.com',
340 'ads.investingchannel.com',
341 'app.ecwid.com',
342 'www.industriejobs.de',
343 's.gravatar.com',
344 'googlesyndication.com',
345 'a.optmstr.com',
346 'a.optmnstr.com',
347 'adthrive.com',
348 'mediavine.com',
349 'js.hsforms.net',
350 'googleadservices.com',
351 'f.convertkit.com',
352 'recaptcha/api.js',
353 ];
354 }
355
356 /**
357 * Gets all localized scripts data to exclude them from combine.
358 *
359 * @return array
360 */
361 protected function get_localized_scripts() {
362 static $localized_scripts;
363
364 if ( isset( $localized_scripts ) ) {
365 return $localized_scripts;
366 }
367
368 $localized_scripts = [];
369
370 foreach ( array_unique( wp_scripts()->queue ) as $item ) {
371 $data = wp_scripts()->print_extra_script( $item, false );
372
373 if ( empty( $data ) ) {
374 continue;
375 }
376
377 $localized_scripts[] = '/* <![CDATA[ */' . $data . '/* ]]> */';
378 }
379
380 return $localized_scripts;
381 }
382
383 }
384