PluginProbe
ezCache / 1.3.11
ezCache v1.3.11
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.11, at includes/FileOptimizer/JsCombiner.php

388 lines 9.5 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 ( preg_match( '/^\/\//ui', $matches[2] ) ) {
110 $matches[2] = 'http' . (is_ssl() ? 's' : '') . ':' . $matches[2];
111 }
112
113 if ( $this->is_external_file( $matches[2] ) ) {
114 foreach ( $this->get_excluded_external_file_path() as $excluded_file ) {
115 if ( false !== strpos( $matches[2], $excluded_file ) ) {
116 return [];
117 }
118 }
119
120 $this->scripts[] = [
121 'type' => 'url',
122 'content' => $matches[2],
123 ];
124
125 return $script;
126 }
127
128 if ( $this->is_minify_excluded_file( $matches ) ) {
129 return [];
130 }
131
132 if ( $this->jquery_url && false !== strpos( $matches[2], $this->jquery_url ) ) {
133 return [];
134 }
135
136 $file_path = $this->get_file_path( $matches[2] );
137
138 if ( ! $file_path ) {
139 return [];
140 }
141
142 $this->scripts[] = [
143 'type' => 'file',
144 'content' => $file_path,
145 ];
146 } elseif ( ! isset( $matches[2] ) ) {
147 preg_match( '/<script\b([^>]*)>(?:\/\*\s*<!\[CDATA\[\s*\*\/)?\s*([\s\S]*?)\s*(?:\/\*\s*\]\]>\s*\*\/)?<\/script>/msi', $script[0], $matches_inline );
148
149 if ( strpos( $matches_inline[1], 'type' ) !== false && ! preg_match( '/type\s*=\s*["\']?(?:text|application)\/(?:(?:x\-)?javascript|ecmascript)["\']?/i', $matches_inline[1] ) ) {
150 return [];
151 }
152
153 if ( false !== strpos( $matches_inline[1], 'src=' ) ) {
154 return [];
155 }
156
157 $test_localize_script = str_replace( array( "\r", "\n" ), '', $matches_inline[2] );
158
159 if ( in_array( $test_localize_script, $this->get_localized_scripts(), true ) ) {
160 return [];
161 }
162
163 foreach ( $this->get_excluded_inline_content() as $excluded_content ) {
164 if ( false !== strpos( $matches_inline[2], $excluded_content ) ) {
165 return [];
166 }
167 }
168
169 $this->scripts[] = [
170 'type' => 'inline',
171 'content' => $matches_inline[2],
172 ];
173 }
174
175 return $script;
176 }, $scripts );
177
178 return array_filter( $scripts );
179 }
180
181 /**
182 * Gets content for each script either from inline or from src
183 *
184 * @return string
185 */
186 protected function get_content() {
187 $content = '';
188
189 foreach ( $this->scripts as $script ) {
190 if ( 'file' === $script['type'] ) {
191 $file_content = file_get_contents( $script['content'] );
192 $content .= $file_content;
193
194 $this->minifier->add( $file_content );
195 } elseif ( 'url' === $script['type'] ) {
196 $file_content = file_get_contents( esc_url( $script['content'] ) );
197 $content .= $file_content;
198
199 $this->minifier->add( $file_content );
200 } elseif ( 'inline' === $script['type'] ) {
201 $inline_js = rtrim( $script['content'], ";\n\t\r" ) . ';';
202 $content .= $inline_js;
203
204 $this->minifier->add( $inline_js );
205 }
206 }
207
208 return $content;
209 }
210
211 /**
212 * Creates the minify URL if the minification is successful
213 *
214 * @param string $content Content to minify & combine.
215
216 * @return string|bool The minify URL if successful, false otherwise
217 */
218 protected function combine( $content ) {
219 if ( empty( $content ) ) {
220 return false;
221 }
222
223 $filename = md5( $content ) . '.js';
224 $minified_file = $this->minify_base_path . $filename;
225
226 if ( file_exists( $minified_file ) ) {
227 touch( $minified_file );
228 } else {
229 $minified_content = $this->minifier->minify();
230
231 if ( ! $minified_content ) {
232 return false;
233 }
234
235 wp_mkdir_p( dirname( $minified_file ) );
236 $minify_filepath = file_put_contents( $minified_file, $minified_content );
237
238 if ( ! $minify_filepath ) {
239 return false;
240 }
241 }
242
243 return $this->minify_base_url . $filename;
244 }
245
246 /**
247 * Patterns in content excluded from being combined
248 *
249 * @return array
250 */
251 protected function get_excluded_inline_content() {
252 return [
253 'document.write',
254 'google_ad',
255 'edToolbar',
256 'gtag',
257 '_gaq.push',
258 '_gaLt',
259 'GoogleAnalyticsObject',
260 'syntaxhighlighter',
261 'adsbygoogle',
262 'ci_cap_',
263 '_stq',
264 'nonce',
265 'post_id',
266 'LogHuman',
267 'idcomments_acct',
268 'ch_client',
269 'sc_online_t',
270 '_stq',
271 'bannersnack_embed',
272 'vtn_player_type',
273 'ven_video_key',
274 'ANS_customer_id',
275 'tdBlock',
276 'tdLocalCache',
277 '"url":',
278 'td_live_css_uid',
279 'tdAjaxCount',
280 'lazyLoadOptions',
281 'adthrive',
282 'loadCSS',
283 'google_tag_params',
284 'clicky_custom',
285 'clicky_site_ids',
286 'NSLPopupCenter',
287 '_paq',
288 'gtm',
289 'dataLayer',
290 'RecaptchaLoad',
291 'recaptcha',
292 'WPCOM_sharing_counts',
293 'jetpack_remote_comment',
294 'scrapeazon',
295 'subscribe-field',
296 'contextly',
297 ];
298 }
299
300 /**
301 * Patterns in URL excluded from being combined
302 *
303 * @return array
304 */
305 protected function get_excluded_external_file_path() {
306 return [
307 'html5.js',
308 'show_ads.js',
309 'histats.com/js',
310 'ws.amazon.com/widgets',
311 '/ads/',
312 'intensedebate.com',
313 'scripts.chitika.net/',
314 'jotform.com/',
315 'gist.github.com',
316 'forms.aweber.com',
317 'video.unrulymedia.com',
318 'stats.wp.com',
319 'stats.wordpress.com',
320 'widget.rafflecopter.com',
321 'widget-prime.rafflecopter.com',
322 'releases.flowplayer.org',
323 'c.ad6media.fr',
324 'cdn.stickyadstv.com',
325 'www.smava.de',
326 'contextual.media.net',
327 'app.getresponse.com',
328 'adserver.reklamstore.com',
329 's0.wp.com',
330 'wprp.zemanta.com',
331 'files.bannersnack.com',
332 'smarticon.geotrust.com',
333 'js.gleam.io',
334 'ir-na.amazon-adsystem.com',
335 'web.ventunotech.com',
336 'verify.authorize.net',
337 'ads.themoneytizer.com',
338 'embed.finanzcheck.de',
339 'imagesrv.adition.com',
340 'js.juicyads.com',
341 'form.jotformeu.com',
342 'speakerdeck.com',
343 'content.jwplatform.com',
344 'ads.investingchannel.com',
345 'app.ecwid.com',
346 'www.industriejobs.de',
347 's.gravatar.com',
348 'googlesyndication.com',
349 'a.optmstr.com',
350 'a.optmnstr.com',
351 'adthrive.com',
352 'mediavine.com',
353 'js.hsforms.net',
354 'googleadservices.com',
355 'f.convertkit.com',
356 'recaptcha/api.js',
357 ];
358 }
359
360 /**
361 * Gets all localized scripts data to exclude them from combine.
362 *
363 * @return array
364 */
365 protected function get_localized_scripts() {
366 static $localized_scripts;
367
368 if ( isset( $localized_scripts ) ) {
369 return $localized_scripts;
370 }
371
372 $localized_scripts = [];
373
374 foreach ( array_unique( wp_scripts()->queue ) as $item ) {
375 $data = wp_scripts()->print_extra_script( $item, false );
376
377 if ( empty( $data ) ) {
378 continue;
379 }
380
381 $localized_scripts[] = '/* <![CDATA[ */' . $data . '/* ]]> */';
382 }
383
384 return $localized_scripts;
385 }
386
387 }
388