PluginProbe
ezCache / 2.5
ezCache v2.5
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 2.5, at includes/FileOptimizer/JsCombiner.php

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