PluginProbe ʕ •ᴥ•ʔ
10Web Booster – Website speed optimization, Cache & Page Speed optimizer / trunk
10Web Booster – Website speed optimization, Cache & Page Speed optimizer vtrunk
2.33.0 2.30.5 2.30.7 2.30.9 2.31.10 2.31.8 2.32.11 2.32.21 2.32.3 2.32.4 2.32.7 2.6.31 2.6.40 2.6.42 2.6.7 2.7.37 2.7.44 2.7.47 2.8.18 2.8.19 2.8.32 2.8.34 2.8.35 2.9.23 2.9.24 2.9.25 2.9.27 v2.27.4 trunk 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.17 2.0.18 2.0.21 2.0.22 2.0.25 2.0.26 2.0.27 2.0.3 2.0.7 2.0.9 2.10.46 2.10.65 2.10.66 2.10.68 2.11.41 2.11.42 2.11.43 2.12.15 2.12.21 2.12.22 2.12.23 2.12.26 2.13.37 2.13.40 2.13.41 2.13.42 2.13.44 2.13.45 2.13.47 2.14.49 2.14.50 2.15.18 2.17.21 2.17.23 2.18.17 2.19.44 2.19.45 2.19.46 2.19.49 2.2.12 2.2.15 2.2.16 2.2.18 2.2.8 2.20.31 2.20.32 2.20.33 2.21.11 2.21.12 2.21.16 2.21.25 2.22.32 2.23.13 2.23.15 2.23.16 2.23.18 2.24.12 2.24.14 2.24.18 2.25.14 2.26.6 2.28.10 2.28.13 2.28.14 2.28.7 2.29.1 2.29.2 2.29.3 2.3.0 2.3.1 2.3.2 2.3.3 2.30.18
tenweb-speed-optimizer / includes / OptimizerScripts.php
tenweb-speed-optimizer / includes Last commit date
WebPageCache 7 months ago external 2 months ago ExcludeJsFromDelay.php 2 years ago OptimizerBanner.php 11 months ago OptimizerBase.php 2 years ago OptimizerCSSMin.php 2 years ago OptimizerCache.php 1 year ago OptimizerCacheStructure.php 2 years ago OptimizerCriticalCss.php 1 year ago OptimizerElementor.php 2 years ago OptimizerFonts.php 2 years ago OptimizerImages.php 1 year ago OptimizerLogger.php 1 year ago OptimizerMain.php 2 months ago OptimizerOnInit.php 11 months ago OptimizerScripts.php 2 years ago OptimizerSettings.php 2 months ago OptimizerStyles.php 2 years ago OptimizerUrl.php 1 year ago OptimizerUtils.php 2 months ago OptimizerWhiteLabel.php 2 years ago
OptimizerScripts.php
1191 lines
1 <?php
2
3 namespace TenWebOptimizer;
4
5 use Exception;
6 use JSMin\JSMin;
7 use MatthiasMullie\Minify\JS;
8
9 if (!defined('ABSPATH')) {
10 exit;
11 }
12
13 class OptimizerScripts extends OptimizerBase
14 {
15 const TWO_DELAYED_JS_ATTRIBUTE = 'data-twodelayedjs';
16
17 const TWO_NO_DELAYED_JS_ATTRIBUTE = 'data-two-no-delay';
18
19 const TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE = 'data-pagespeed-no-defer';
20
21 private $scripts = [];
22
23 private $move = [
24 'first' => [],
25 'last' => [],
26 ];
27
28 private $dontmove = [
29 'document.write',
30 'html5.js',
31 'show_ads.js',
32 'google_ad',
33 'histats.com/js',
34 'statcounter.com/counter/counter.js',
35 'ws.amazon.com/widgets',
36 'media.fastclick.net',
37 '/ads/',
38 'comment-form-quicktags/quicktags.php',
39 'edToolbar',
40 'intensedebate.com',
41 'scripts.chitika.net/',
42 '_gaq.push',
43 'jotform.com/',
44 'admin-bar.min.js',
45 'GoogleAnalyticsObject',
46 'plupload.full.min.js',
47 'syntaxhighlighter',
48 'adsbygoogle',
49 'gist.github.com',
50 '_stq',
51 'nonce',
52 'post_id',
53 'data-noptimize',
54 'logHuman',
55 self::TWO_NO_DELAYED_JS_ATTRIBUTE,
56 'two_exclude_inline',
57 'data-two_exclude'
58 ];
59
60 private $domove = [
61 'gaJsHost',
62 'load_cmc',
63 'jd.gallery.transitions.js',
64 'swfobject.embedSWF(',
65 'tiny_mce.js',
66 'tinyMCEPreInit.go',
67 ];
68
69 private $domovelast = [
70 'addthis.com',
71 '/afsonline/show_afs_search.js',
72 'disqus.js',
73 'networkedblogs.com/getnetworkwidget',
74 'infolinks.com/js/',
75 'jd.gallery.js.php',
76 'jd.gallery.transitions.js',
77 'swfobject.embedSWF(',
78 'linkwithin.com/widget.js',
79 'tiny_mce.js',
80 'tinyMCEPreInit.go',
81 ];
82
83 private $dontmoveExtended = [
84 'document.write',
85 'google_ad',
86 'edToolbar',
87 'gtag',
88 '_gaq.push',
89 '_gaLt',
90 'GoogleAnalyticsObject',
91 'syntaxhighlighter',
92 'adsbygoogle',
93 'ci_cap_',
94 '_stq',
95 'nonce',
96 'post_id',
97 'LogHuman',
98 'idcomments_acct',
99 'ch_client',
100 'sc_online_t',
101 '_stq',
102 'bannersnack_embed',
103 'vtn_player_type',
104 'ven_video_key',
105 'ANS_customer_id',
106 'tdBlock',
107 'tdLocalCache',
108 'wpRestNonce',
109 '"url":',
110 'lazyLoadOptions',
111 'adthrive',
112 'loadCSS',
113 'google_tag_params',
114 'clicky_custom',
115 'clicky_site_ids',
116 'NSLPopupCenter',
117 '_paq',
118 'gtm',
119 'dataLayer',
120 'RecaptchaLoad',
121 'WPCOM_sharing_counts',
122 'jetpack_remote_comment',
123 'subscribe-field',
124 'contextly',
125 '_mmunch',
126 'gt_request_uri',
127 'doGTranslate',
128 'docTitle',
129 'bs_ajax_paginate_',
130 'bs_deferred_loading_',
131 'theChampRedirectionUrl',
132 'theChampFBCommentUrl',
133 'theChampTwitterRedirect',
134 'theChampRegRedirectionUrl',
135 'ESSB_CACHE_URL',
136 'oneall_social_login_providers_',
137 'betterads_screen_width',
138 'woocommerce_wishlist_add_to_wishlist_url',
139 'arf_conditional_logic',
140 'heateorSsHorSharingShortUrl',
141 'TL_Const',
142 'bimber_front_microshare',
143 'setAttribute("id"',
144 'setAttribute( "id"',
145 'TribeEventsPro',
146 'peepsotimedata',
147 'wphc_data',
148 'hc_rand_id',
149 'RBL_ADD',
150 'AfsAnalyticsObject',
151 '_thriveCurrentPost',
152 'esc_login_url',
153 'fwduvpMainPlaylist',
154 'Bibblio.initRelatedContent',
155 'showUFC()',
156 '#iphorm-',
157 '#fancy-',
158 'ult-carousel-',
159 'theChampLJAuthUrl',
160 'f._fbq',
161 'Insticator',
162 'w2dc_js_objects',
163 'cherry_ajax',
164 'ad_block_',
165 'elementorFrontendConfig',
166 'zeen_',
167 'disqusIdentifier',
168 'currentAjaxUrl',
169 'geodir_event_call_calendar_',
170 'atatags-',
171 'hbspt.forms.create',
172 'function(c,h,i,m,p)',
173 'dataTable({',
174 'rankMath = {',
175 '_atrk_opts',
176 'quicklinkOptions',
177 'ct_checkjs_',
178 'WP_Statistics_http',
179 'penci_block_',
180 'omapi_localized',
181 'omapi_data',
182 'OptinMonsterApp',
183 'tminusnow',
184 'nfForms',
185 'galleries.gallery_',
186 'wcj_evt.prodID',
187 'advads_tracking_ads',
188 'advadsGATracking.postContext',
189 'woopack_config',
190 'ulp_content_id',
191 'wp-cumulus/tagcloud.swf?r=',
192 'ctSetCookie(\'ct_checkjs\'',
193 'woof_really_curr_tax',
194 'uLogin.customInit',
195 'i18n_no_matching_variations_text',
196 'alsp_map_markers_attrs',
197 'var inc_opt =',
198 'iworks_upprev',
199 'yith_wcevti_tickets',
200 'window.metrilo.ensure_cbuid',
201 'metrilo.event',
202 'wordpress_page_root',
203 'wcct_info',
204 'Springbot.product_id',
205 'pysWooProductData',
206 'dfd-heading',
207 'owl=$("#',
208 'penci_megamenu',
209 'fts_security',
210 'algoliaAutocomplete',
211 'avia_framework_globals',
212 'tabs.easyResponsiveTabs',
213 'searchlocationHeader',
214 'yithautocomplete',
215 'data-parallax-speed',
216 'currency_data=',
217 'cedexisData',
218 'function reenableButton',
219 '#wpnbio-show',
220 'e.Newsletter2GoTrackingObject',
221 'var categories_',
222 '"+nRemaining+"',
223 'cartsguru_cart_token',
224 'after_share_easyoptin',
225 'location_data.push',
226 'thirstyFunctions.isThirstyLink',
227 'styles: \' #custom-menu-',
228 'function svc_center_',
229 '#svc_carousel2_container_',
230 'advads.move',
231 'elementid',
232 'advads_has_ads',
233 'wpseo_map_init',
234 'mdf_current_page_url',
235 'tptn_tracker',
236 'dpsp_pin_button_data',
237 'searchwp_live_search_params',
238 'wpp_params',
239 'top.location,thispage',
240 'selection+pagelink',
241 'ic_window_resolution',
242 'PHP.wp_p_id',
243 'ShopifyBuy.UI.onReady(client)',
244 'orig_request_uri',
245 'gie.widgets.load',
246 'Adman.Flash',
247 'PHP.wp_p_id',
248 'window.broadstreetKeywords',
249 'var productId =',
250 'var flatsomeVars',
251 'wc_product_block_data',
252 'static.mailerlite.com',
253 'amzn_assoc',
254 '_bs_getParameterByName',
255 '_stq.push',
256 'h._remove',
257 'var FlowFlowOpts',
258 'var WCPFData =',
259 'var _beeketing',
260 'var _statcounter',
261 'var actions =',
262 'var current_url',
263 'var object_name',
264 'var the_ajax_script',
265 'var wc_cart_fragments_params',
266 'var woocommerce_params',
267 'var wpml_cookies',
268 'wc_add_to_cart_params',
269 'window.broadstreetKeywords',
270 'window.wc_ga_pro.available_gateways',
271 'xa.prototype',
272 'HOUZEZ_ajaxcalls_vars',
273 'w2dc_maps_objects',
274 'w2dc_controller_args_array',
275 'w2dc_map_markers_attrs',
276 'YT.Player',
277 'WPFC.data',
278 'function current_video_',
279 'var videodiv',
280 'var slider_wppasrotate',
281 'wppas_ga',
282 'var blockClass',
283 'tarteaucitron',
284 'pw_brand_product_list',
285 'tminusCountDown',
286 'pysWooSelectContentData',
287 'wpvq_ans89733',
288 '_isp_version',
289 'price_range_data',
290 'window.FeedbackCompanyWidgets',
291 'woocs_current_currency',
292 'woo_variation_swatches_options',
293 'woocommerce_price_slider_params',
294 'scriptParams',
295 'form-adv-pagination',
296 'borlabsCookiePrioritize',
297 'urls_wpwidgetpolylang',
298 'quickViewNonce',
299 'frontendscripts_params',
300 'nj-facebook-messenger',
301 'var fb_mess_position',
302 'init_particles_row_background_script',
303 'setREVStartSize',
304 'fl-node',
305 'PPAccordion',
306 'soliloquy_',
307 'wprevpublicjs_script_vars',
308 'DTGS_NONCE_FRONTEND',
309 'et_animation_data',
310 'archives-dropdown',
311 'loftloaderCache',
312 'SmartSliderSimple',
313 'var nectarLove',
314 'var incOpt',
315 'RocketBrowserCompatibilityChecker',
316 'RocketPreloadLinksConfig',
317 'placementVersionId',
318 'var useEdit',
319 'var DTGS_NONCE_FRONTEND',
320 'n2jQuery',
321 'et_core_api_spam_recaptcha',
322 'cnArgs',
323 '__CF$cv$params',
324 'trustbox_settings',
325 'aepro',
326 'cdn.jst.ai',
327 'w2dc_fields_in_categories',
328 'aepc_pixel',
329 'avadaWooCommerceVars',
330 'var isb',
331 'fcaPcPost',
332 'csrf_token',
333 'icwp_wpsf_vars_lpantibot',
334 'wpvViewHead',
335 'ed_school_plugin',
336 'aps_comp_',
337 'guaven_woos',
338 '__lm_redirect_to',
339 '__wpdm_view_count',
340 'bookacti.booking_system',
341 'nfFrontEnd',
342 'view_quote_cart_link',
343 '__eae_decode_emails',
344 'divioverlays_ajaxurl',
345 'var _EPYT_',
346 '#ins-heading-',
347 '#ins-button-',
348 'tve_frontend_options',
349 'lb24.src',
350 'amazon_Login_accessToken',
351 'porto_infinite_scroll',
352 '.adace-loader-',
353 'adace_load_',
354 'tagGroupsAccordiontaggroupscloudaccordion',
355 'tagGroupsTabstaggroupscloudtabs',
356 'jrRelatedWidgets',
357 ];
358
359 private $delayed_js = [];
360
361 public $cdn_url = '';
362
363 private $aggregate = false;
364
365 private $trycatch = false;
366
367 private $alreadyminified = false;
368
369 private $forcehead = true;
370
371 private $include_inline = false;
372
373 private $jscode = '';
374
375 private $hashes = [];
376
377 private $url = '';
378
379 private $restofcontent = '';
380
381 private $md5hash = '';
382
383 private $whitelist = '';
384
385 private $jsremovables = [];
386
387 private $inject_min_late = '';
388
389 private $minify_excluded = true;
390
391 /**
392 * @var bool
393 */
394 private $delay_js_execution = false;
395
396 /**
397 * @var OptimizerCacheStructure
398 */
399 private $cacheStructure;
400
401 /**
402 * OptimizerStyles constructor.
403 *
404 * @param string $content
405 * @param OptimizerCacheStructure $cacheStructure
406 */
407 private $TwoSettings;
408
409 /**
410 * @var bool
411 */
412 private $delay_all_js_execution;
413
414 public $two_js_list = [];
415
416 public $two_js_list_excluded = [];
417
418 private $exclude_blob_list = [
419 'adsbygoogle.js'
420 ];
421
422 public function __construct($content, $cacheStructure)
423 {
424 global $TwoSettings;
425 $this->TwoSettings = $TwoSettings;
426 parent::__construct($content);
427 $this->cacheStructure = $cacheStructure;
428 }
429
430 // Reads the page and collects script tags.
431 public function read($options)
432 {
433 $exclJSArr = [];
434
435 // Determine whether we're doing JS-files aggregation or not.
436 if (isset($options['aggregate'])) {
437 $this->aggregate = $options['aggregate'];
438 }
439
440 // Returning true for "dontaggregate" turns off aggregation.
441 // include inline?
442 if ($options['include_inline'] && $this->aggregate) {
443 $this->include_inline = true;
444 }
445 $this->inject_min_late = true;
446 // Determine whether excluded files should be minified if not yet so.
447 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
448 /*if ( !$options['minify_excluded'] && $options['aggregate'] ) {
449 $this->minify_excluded = FALSE;
450 }*/
451 $this->minify_excluded = $options['minify_excluded'];
452
453 if ($this->aggregate) {
454 $this->minify_excluded = false;
455 }
456
457 // get extra exclusions settings or filter.
458 $excludeJS = $options['js_exclude'];
459
460 if ('' !== $excludeJS) {
461 if (is_array($excludeJS)) {
462 if (($removeKeys = array_keys($excludeJS, 'remove')) !== false) {
463 foreach ($removeKeys as $removeKey) {
464 unset($excludeJS[$removeKey]);
465 $this->jsremovables[] = $removeKey;
466 }
467 }
468 $exclJSArr = array_keys($excludeJS);
469 } else {
470 $exclJSArr = array_filter(array_map('trim', explode(',', $excludeJS)));
471 }
472
473 $this->dontmove = array_merge($exclJSArr, $this->dontmove);
474 }
475 $arrayFromHook = apply_filters('two_modify_exclude_js_from_aggregation', $this->dontmove, 10, 1);
476
477 if (is_array($arrayFromHook)) {
478 $this->dontmove = $arrayFromHook;
479 }
480
481 if ($options['use_extended_exception_list_js']) {
482 $this->dontmove = array_unique(array_merge($this->dontmoveExtended, $this->dontmove));
483 }
484
485 // Should we add delay execution
486 if ($options['delay_js_execution']) {
487 $this->delay_js_execution = true;
488 }
489
490 // Should we add delay execution
491 if ($options['delay_all_js_execution']) {
492 $this->delay_all_js_execution = true;
493 }
494
495 // delay js execution.
496 $delayJSExecutionList = $options['delayed_js_execution_list'];
497
498 if ('' !== $delayJSExecutionList) {
499 $delayJSExecution = array_filter(array_map('trim', explode(',', $delayJSExecutionList)));
500 $this->delayed_js = array_merge($delayJSExecution, $this->delayed_js);
501 }
502
503 $delayJSExecutionFromHook = apply_filters('two_modify_delay_execution', $this->delayed_js, 10, 1);
504
505 if (is_array($delayJSExecutionFromHook)) {
506 $this->delayed_js = $delayJSExecutionFromHook;
507 }
508
509 // Should we add try-catch?
510 if ($options['trycatch']) {
511 $this->trycatch = true;
512 }
513
514 // force js in head?
515 if ($options['forcehead']) {
516 $this->forcehead = true;
517 } else {
518 $this->forcehead = false;
519 }
520 // get cdn url.
521 $this->cdn_url = $options['cdn_url'];
522 // noptimize me.
523 $this->content = $this->hide_noptimize($this->content);
524 // Save IE hacks.
525 $this->content = $this->hide_iehacks($this->content);
526 // comments.
527 $this->content = $this->hide_comments($this->content);
528
529 // Get script files.
530 if (preg_match_all('#<script.*</script>#Usmi', $this->content, $matches)) {
531 if ($this->delay_all_js_execution) {
532 $exclude_delay_js = ExcludeJsFromDelay::get_exclusion_list($options, $this->content);
533 $excludeJSLoadNormally = array_merge(explode(',', $options['load_excluded_js_normally']), ExcludeJsFromDelay::EXCLUDE_LIST['types']); //do not make blob templates, schema.org and other excluded types
534
535 foreach ($matches[0] as $tag) {
536 $delay_uid = uniqid('two_', false);
537
538 if (false !== strpos($tag, OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE)) {
539 continue;
540 }
541
542 $excluded_script = false;
543
544 foreach ($exclude_delay_js as $type) {
545 if (!empty($type) && preg_match('~' . $type . '~', $tag)) {
546 $excluded_script = true;
547 break;
548 }
549 }
550 $load_excluded_script_normally = false;
551
552 foreach ($excludeJSLoadNormally as $type) {
553 if (!empty($type) && false !== strpos($tag, $type)) {
554 $load_excluded_script_normally = true;
555 break;
556 }
557 }
558
559 if ($excluded_script && ($this->TwoSettings->get_settings('two_load_excluded_js_via_worker') != 'on' || $load_excluded_script_normally)) {
560 continue;
561 }
562
563 preg_match('#<script[^>]*id=("|\')([^>]*)("|\')#Usmi', $tag, $source);
564 $script_id = '';
565
566 if (isset($source[2])) {
567 $script_id = $source[2];
568 }
569
570 if (preg_match('#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source)) {
571 if (isset($source[2])) {
572 $dealy_script_data = [
573 'inline' => false,
574 'url' => $source[2],
575 'id' => $script_id,
576 'uid' => $delay_uid,
577 'exclude_blob' => false,
578 ];
579
580 foreach ($this->exclude_blob_list as $exclude_blob) {
581 if (!empty($exclude_blob) && false !== strpos($source[2], $exclude_blob)) {
582 $dealy_script_data['exclude_blob'] = true;
583 break;
584 }
585 }
586
587 if ($excluded_script) {
588 $dealy_script_data['excluded_from_delay'] = true;
589 $this->two_js_list_excluded[] = $dealy_script_data;
590 } else {
591 $dealy_script_data['excluded_from_delay'] = false;
592 $this->two_js_list[] = $dealy_script_data;
593 }
594 }
595 $new_tag = str_replace(['src', '<script'], ['data-two_delay_src', '<script data-two_delay_id="' . $delay_uid . '"'], $tag);
596 } else {
597 preg_match('#<script.*>(.*)</script>#Usmi', $tag, $code);
598
599 if (isset($code[1])) {
600 // Restore comments to fix inline js containing html comment.
601 $js_code = $this->restore_comments($code[1]);
602 // Encode the js to keep unicode characters after decode.
603 $inline_code = base64_encode(rawurlencode($js_code));
604 $dealy_script_data = [
605 'inline' => true,
606 'code' => $inline_code,
607 'id' => $script_id,
608 'uid' => $delay_uid,
609 'exclude_blob' => false,
610 ];
611
612 if ($excluded_script) {
613 $dealy_script_data['excluded_from_delay'] = true;
614 $this->two_js_list_excluded[] = $dealy_script_data;
615 } else {
616 $dealy_script_data['excluded_from_delay'] = false;
617 $this->two_js_list[] = $dealy_script_data;
618 }
619 }
620 $new_tag = str_replace(['<script', $code[1]], ["<script data-two_delay_src='inline' data-two_delay_id=\"" . $delay_uid . '"', ''], $tag);
621 }
622
623 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
624 //$this->content = str_replace($tag , $new_tag, $this->content);
625 $pos = strpos($this->content, $tag);
626
627 if ($pos !== false) {
628 $this->content = substr_replace($this->content, $new_tag, $pos, strlen($tag));
629 }
630 $this->cacheStructure->addToTagsToReplace($tag, $new_tag);
631 }
632
633 return true;
634 }
635
636 foreach ($matches[0] as $tag) {
637 // only consider script aggregation for types whitelisted in should_aggregate-function or if it is for delayed
638 $should_aggregate = $this->should_aggregate($tag);
639
640 if (! ($this->delay_js_execution && $this->isfordelay($tag, $this->delayed_js)) && !$should_aggregate) {
641 $tag = '';
642 continue;
643 }
644
645 if (preg_match('#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source)) {
646 // non-inline script.
647 if ($this->isremovable($tag, $this->jsremovables)) {
648 $this->content = str_replace($tag, '', $this->content);
649 $this->cacheStructure->addToTagsToReplace($tag, '');
650 continue;
651 }
652 $origTag = null;
653 $url = current(explode('?', $source[2], 2));
654
655 if ($this->delay_js_execution && $this->isfordelay($url, $this->delayed_js)) {
656 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
657 //$newTag = '<script type="text/javascript" '.self::TWO_DELAYED_JS_ATTRIBUTE.'="'.$url.'"></script>';
658 $newTag = str_replace('src=', self::TWO_DELAYED_JS_ATTRIBUTE . '=', $tag);
659
660 $this->content = str_replace($tag, $newTag, $this->content);
661 $this->cacheStructure->addToTagsToReplace($tag, $newTag);
662
663 continue;
664 }
665 $path = $this->getpath($url);
666
667 if (false !== $path && preg_match('#\.js$#', $path) && $this->ismergeable($tag)) {
668 // ok to optimize, add to array.
669 $this->scripts[md5($path)] = $path;
670 } else {
671 $origTag = $tag;
672 $newTag = $tag;
673
674 // non-mergeable script (excluded or dynamic or external).
675 if (is_array($exclJSArr)) {
676 if (OptimizerUtils::is_pagespeed_js_defer_enabled()) {
677 // should we add flags to disable async loading?
678 foreach ($exclJSArr as $exclTag) {
679 if (false !== strpos($origTag, $exclTag)) {
680 $newTag = str_replace('<script ', '<script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ', $newTag);
681 }
682 }
683 } else {
684 //todo check this, there has to be a bug
685 // should we add flags to enable async loading?
686 if (is_array($excludeJS)) {
687 foreach ($excludeJS as $exclTag => $exclFlags) {
688 if (false !== strpos($origTag, $exclTag) && in_array($exclFlags, ['async', 'defer'])) {
689 $newTag = str_replace('<script ', '<script ' . $exclFlags . ' ', $newTag);
690 }
691 }
692 }
693 }
694 }
695
696 // Should we minify the non-aggregated script?
697 // -> if aggregate is on and exclude minify is on
698 // -> if aggregate is off and the file is not in dontmove.
699 if ($path && $this->minify_excluded) {
700 $consider_minified_array = false;
701
702 if ((false === $this->aggregate && str_replace($this->dontmove, '', $path) === $path) || (true === $this->aggregate && (false === $consider_minified_array || str_replace($consider_minified_array, '', $path) === $path))) {
703 $minified_url = $this->minify_single($path);
704
705 // replace orig URL with minified URL from cache if so.
706 if (!empty($minified_url)) {
707 $newTag = str_replace($url, $minified_url, $newTag);
708 }
709 }
710 }
711
712 if ($this->ismovable($newTag)) {
713 // can be moved, flags and all.
714 if ($this->movetolast($newTag)) {
715 $this->move['last'][] = $newTag;
716 } else {
717 $this->move['first'][] = $newTag;
718 }
719 } else {
720 // cannot be moved, so if flag was added re-inject altered tag immediately.
721 if ($origTag !== $newTag) {
722 $this->content = str_replace($origTag, $newTag, $this->content);
723 $this->cacheStructure->addToTagsToReplace($origTag, $newTag);
724
725 $origTag = '';
726 }
727 // and forget about the $tag (not to be touched any more).
728 $tag = '';
729 }
730 }
731 } else {
732 //this tag is for delay
733 if ($this->delay_js_execution && $this->isfordelay($tag, $this->delayed_js)) {
734 $type = OptimizerUtils::get_javascipt_type($tag);
735 preg_match('#<script.*>(.*)</script>#Usmi', $tag, $code);
736
737 if (isset($code[1])) {
738 $newTag = '<script ' . self::TWO_DELAYED_JS_ATTRIBUTE . '="data:' . $type . ';base64,' . base64_encode($code[1]) . '"></script>';
739 $this->content = str_replace($tag, $newTag, $this->content);
740 $this->cacheStructure->addToTagsToReplace($tag, $newTag);
741
742 continue;
743 }
744 }
745
746 // Inline script.
747 if ($this->isremovable($tag, $this->jsremovables)) {
748 $this->content = str_replace($tag, '', $this->content);
749 $this->cacheStructure->addToTagsToReplace($tag, '');
750
751 continue;
752 }
753 // unhide comments, as javascript may be wrapped in comment-tags for old times' sake.
754 $tag = $this->restore_comments($tag);
755
756 if ($this->include_inline && $this->ismergeable($tag)) {
757 preg_match('#<script.*>(.*)</script>#Usmi', $tag, $code);
758 $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1]);
759 $code = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code);
760 $this->scripts[md5($code)] = 'INLINE;' . $code;
761 } else {
762 // Can we move this?
763 $twoptimize_js_moveable = '';
764
765 //todo refactor this
766 if ($this->ismovable($tag) || '' !== $twoptimize_js_moveable) {
767 if ($this->movetolast($tag) || 'last' === $twoptimize_js_moveable) {
768 $this->move['last'][] = $tag;
769 } else {
770 $this->move['first'][] = $tag;
771 }
772 } else {
773 // We shouldn't touch this.
774 $tag = '';
775 }
776 }
777 // Re-hide comments to be able to do the removal based on tag from $this->content.
778 $tag = $this->hide_comments($tag);
779 }
780 //Remove the original script tag.
781 $this->content = str_replace($tag, '', $this->content);
782 $this->cacheStructure->addToTagsToReplace($tag, '');
783 }
784
785 return true;
786 }
787
788 // No script files, great ;-)
789 return false;
790 }
791
792 /**
793 * Determines wheter a certain `<script>` $tag should be aggregated or not.
794 * We consider these as "aggregation-safe" currently:
795 * - script tags without a `type` attribute
796 * - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
797 * - it is not delayed script
798 * and `application/ecmascript`
799 * Everything else should return false.
800 *
801 * @see https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
802 *
803 * @param string $tag
804 *
805 * @return bool
806 */
807 public function should_aggregate($tag)
808 {
809 // We're only interested in the type attribute of the <script> tag itself, not any possible
810 // inline code that might just contain the 'type=' string...
811 if (false !== strpos($tag, OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE)) {
812 return false;
813 }
814 $tag_parts = [];
815 preg_match('#<(script[^>]*)>#i', $tag, $tag_parts);
816 $tag_without_contents = null;
817
818 if (!empty($tag_parts[1])) {
819 $tag_without_contents = $tag_parts[1];
820 }
821 $has_type = (strpos($tag_without_contents, 'type') !== false);
822 $type_valid = false;
823
824 if ($has_type) {
825 $type_valid = (bool) preg_match('/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents);
826 }
827 $should_aggregate = false;
828
829 if (!$has_type || $type_valid) {
830 $should_aggregate = true;
831 }
832
833 return $should_aggregate;
834 }
835
836 //Joins and optimizes JS
837 public function optimize()
838 {
839 $two_change_minify = $this->TwoSettings->get_settings('two_change_minify');
840
841 if (!empty($this->scripts)) {
842 foreach ($this->scripts as $hash => $script) {
843 if (preg_match('#^INLINE;#', $script)) {
844 // Inline script
845 $script = preg_replace('#^INLINE;#', '', $script);
846 $script = rtrim($script, ";\n\t\r") . ';';
847
848 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
849 // Add try catch
850 if ($this->trycatch) {
851 $script = 'try{' . $script . '}catch(e){}';
852 }
853 $tmpscript = $this->js_snippetcacher($script, '', $hash);
854
855 if (!empty($tmpscript)) {
856 $script = $tmpscript;
857 $this->alreadyminified = true;
858 }
859 $this->jscode .= "\n" . $script;
860 $this->hashes[] = $hash;
861 } else {
862 // External script
863 if (false !== $script && file_exists($script) && is_readable($script)) {
864 $scriptsrc = file_get_contents($script); // phpcs:ignore
865 $scriptsrc = preg_replace('/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc);
866 $scriptsrc = rtrim($scriptsrc, ";\n\t\r") . ';';
867
868 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
869 // Add try catch
870 if ($this->trycatch) {
871 $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
872 }
873 $tmpscriptsrc = $this->js_snippetcacher($scriptsrc, '', $hash);
874
875 if (!empty($tmpscriptsrc)) {
876 $scriptsrc = $tmpscriptsrc;
877 $this->alreadyminified = true;
878 } else {
879 if ($this->can_inject_late($script)) {
880 $scriptsrc = self::build_injectlater_marker($script, md5($scriptsrc));
881 }
882 }
883 $this->jscode .= "\n" . $scriptsrc;
884 $this->hashes[] = $hash;
885 }
886 }
887 }
888 }
889
890 // Check for already-minified code
891 $this->md5hash = md5($this->jscode);
892
893 if (true !== $this->alreadyminified) {
894 if ($two_change_minify == false || $two_change_minify == 'JSMin') {
895 try {
896 $tmp_jscode = trim(JSMin::minify($this->jscode));
897 } catch (Exception $e) {
898 $tmp_jscode = $this->jscode;
899 }
900 } else {
901 $minifier = new JS($this->jscode);
902 $tmp_jscode = $minifier->minify();
903 }
904
905 if (!empty($tmp_jscode)) {
906 $this->jscode = $tmp_jscode;
907 unset($tmp_jscode);
908 }
909 $this->jscode = $this->inject_minified($this->jscode);
910
911 return true;
912 }
913
914 return true;
915 }
916
917 // Caches the JS in uncompressed, deflated and gzipped form.
918 public function cache()
919 {
920 $cache = new OptimizerCache(null, 'js');
921
922 if (!empty($this->jscode)) {
923 // Cache our code
924 $cache->cache($this->jscode, 'text/javascript');
925 }
926 $this->url = TWO_CACHE_URL . $cache->getname();
927 $this->url = $this->url_replace_cdn($this->url);
928 }
929
930 // Returns the content
931 public function getcontent()
932 {
933 // Restore the full content
934 if (!empty($this->restofcontent)) {
935 $this->content .= $this->restofcontent;
936 $this->restofcontent = '';
937 }
938
939 // Add the scripts taking forcehead/ deferred (default) into account
940 if ($this->forcehead) {
941 $replaceTag = [ '</head>', 'before' ];
942 $defer = '';
943 } else {
944 $replaceTag = [ '</body>', 'before' ];
945 $defer = 'defer ';
946 }
947
948 $bodyreplacementpayload = '<script type="text/javascript" ' . $defer . 'src="' . $this->url . '"></script>';
949 $bodyreplacement = implode('', $this->move['first']);
950 $bodyreplacement .= $bodyreplacementpayload;
951 $bodyreplacement .= implode('', $this->move['last']);
952
953 if (strlen($this->jscode) > 0) {
954 $this->content = OptimizerUtils::inject_in_html($this->content, $bodyreplacement, $replaceTag);
955 $this->cacheStructure->addToTagsToAdd($bodyreplacement, $replaceTag);
956 }
957 // Restore comments.
958 $this->content = $this->restore_comments($this->content);
959 // Restore IE hacks.
960 $this->content = $this->restore_iehacks($this->content);
961 // Restore noptimize.
962 $this->content = $this->restore_noptimize($this->content);
963
964 if (!empty($this->two_js_list) && $this->delay_all_js_execution) {
965 $two_events_after_load = $this->TwoSettings->get_settings('two_events_after_load', []);
966 $dispatchEvents = '';
967
968 if (is_array($two_events_after_load)) {
969 if (in_array('DOMContentLoaded', $two_events_after_load)) {
970 $dispatchEvents .= 'console.log("Dispatching DOMContentLoaded event");';
971 $dispatchEvents .= 'document.dispatchEvent(new Event("DOMContentLoaded"));';
972 }
973
974 if (in_array('Load', $two_events_after_load)) {
975 $dispatchEvents .= 'console.log("Dispatching Load event");';
976 $dispatchEvents .= 'window.dispatchEvent(new Event("load"));';
977 }
978
979 if (in_array('Click', $two_events_after_load)) {
980 $dispatchEvents .= 'console.log("Dispatching ClickOrTouch event");';
981 $dispatchEvents .= 'two_loading_events(two_event);';
982 }
983 }
984
985 $this->two_js_list[] = [
986 'code' => base64_encode('
987
988 if (window.two_page_loaded) {
989 console.log("dispatching events");' .
990 $dispatchEvents . '
991 }
992 else {
993 console.log("waiting for page to load");
994 window.addEventListener("load", function() {
995 console.log("dispatching events");
996 document.dispatchEvent(new Event("DOMContentLoaded"));
997 });
998 }
999 '),
1000 'inline' => true,
1001 'uid' => 'two_dispatchEvent_script',
1002 ];
1003 }
1004 $two_dispatchEvent_script = '<script data-two_delay_id="two_dispatchEvent_script"></script>';
1005 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
1006 $this->content = OptimizerUtils::inject_in_html($this->content, $two_dispatchEvent_script, [ '</body>', 'before' ]);
1007 $this->cacheStructure->addToTagsToAdd($two_dispatchEvent_script, [ '</body>', 'before' ]);
1008
1009 // Return the modified HTML.
1010 return $this->content;
1011 }
1012
1013 // Checks against the white- and blacklists
1014 private function ismergeable($tag)
1015 {
1016 if (!$this->aggregate) {
1017 return false;
1018 }
1019
1020 if (!empty($this->whitelist)) {
1021 foreach ($this->whitelist as $match) {
1022 if (false !== strpos($tag, $match)) {
1023 return true;
1024 }
1025 }
1026
1027 // no match with whitelist
1028 return false;
1029 } else {
1030 foreach ($this->domove as $match) {
1031 if (false !== strpos($tag, $match)) {
1032 // Matched something
1033 return false;
1034 }
1035 }
1036
1037 if ($this->movetolast($tag)) {
1038 return false;
1039 }
1040
1041 foreach ($this->dontmove as $match) {
1042 if (false !== strpos($tag, $match)) {
1043 // Matched something
1044 return false;
1045 }
1046 }
1047
1048 // If we're here it's safe to merge
1049 return true;
1050 }
1051 }
1052
1053 // Checks agains the blacklist
1054 private function ismovable($tag)
1055 {
1056 if (true !== $this->include_inline) {
1057 return false;
1058 }
1059
1060 foreach ($this->domove as $match) {
1061 if (false !== strpos($tag, $match)) {
1062 // Matched something
1063 return true;
1064 }
1065 }
1066
1067 if ($this->movetolast($tag)) {
1068 return true;
1069 }
1070
1071 foreach ($this->dontmove as $match) {
1072 if (false !== strpos($tag, $match)) {
1073 // Matched something
1074 return false;
1075 }
1076 }
1077
1078 // If we're here it's safe to move
1079 return true;
1080 }
1081
1082 private function movetolast($tag)
1083 {
1084 foreach ($this->domovelast as $match) {
1085 if (false !== strpos($tag, $match)) {
1086 // Matched. return true
1087 return true;
1088 }
1089 }
1090
1091 // Should be in 'first'
1092 return false;
1093 }
1094
1095 /**
1096 * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
1097 * - inject_min_late being active
1098 * - filename ending in `min.js`
1099 * - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
1100 * - filename matching one passed in the consider minified filter
1101 *
1102 * @param string $jsPath
1103 *
1104 * @return bool
1105 */
1106 private function can_inject_late($jsPath)
1107 {
1108 $consider_minified_array = false;
1109
1110 if (true !== $this->inject_min_late) {
1111 // late-inject turned off
1112 return false;
1113 } else {
1114 if ((false === strpos($jsPath, 'min.js')) && (false === strpos($jsPath, 'wp-includes/js/jquery/jquery.js')) && (str_replace($consider_minified_array, '', $jsPath) === $jsPath)) {
1115 // file not minified based on filename & filter
1116 return false;
1117 } else {
1118 // phew, all is safe, we can late-inject
1119 return true;
1120 }
1121 }
1122 }
1123
1124 /**
1125 * Minifies a single local js file and returns its (cached) url.
1126 *
1127 * @param string $filepath filepath
1128 * @param bool $cache_miss Optional. Force a cache miss. Default false.
1129 *
1130 * @return bool|string url pointing to the minified js file or false
1131 */
1132 public function minify_single($filepath, $cache_miss = false)
1133 {
1134 $two_change_minify = $this->TwoSettings->get_settings('two_change_minify');
1135 $contents = $this->prepare_minify_single($filepath);
1136
1137 if (empty($contents)) {
1138 return false;
1139 }
1140 // Check cache.
1141 $name_prefix = 'minified_' . str_replace('.js', '', basename($filepath));
1142 $cache = new OptimizerCache(null, 'js', 'all', $name_prefix);
1143
1144 if ($two_change_minify == false || $two_change_minify == 'JSMin') {
1145 try {
1146 $contents = trim(JSMin::minify($contents));
1147 } catch (Exception $e) {
1148 $contents = $contents;
1149 }
1150 } else {
1151 $minifier = new JS($contents);
1152 $contents = $minifier->minify();
1153 }
1154 // Store in cache.
1155 $cache->cache($contents, 'text/javascript');
1156 $url = $this->build_minify_single_url($cache);
1157
1158 return $url;
1159 }
1160
1161 public function js_snippetcacher($jsin, $jsfilename, $hash)
1162 {
1163 $two_change_minify = $this->TwoSettings->get_settings('two_change_minify');
1164
1165 if ($two_change_minify == false || $two_change_minify == 'JSMin') {
1166 try {
1167 $tmp_jscode = trim(JSMin::minify($jsin));
1168 } catch (Exception $e) {
1169 $tmp_jscode = $jsin;
1170 }
1171 } else {
1172 $minifier = new JS($jsin);
1173 $tmp_jscode = $minifier->minify();
1174 }
1175
1176 if (! empty($tmp_jscode)) {
1177 $scriptsrc = $tmp_jscode;
1178 unset($tmp_jscode);
1179 } else {
1180 $scriptsrc = $jsin;
1181 }
1182 $last_char = substr($scriptsrc, -1, 1);
1183
1184 if (';' !== $last_char && '}' !== $last_char) {
1185 $scriptsrc .= ';';
1186 }
1187
1188 return $scriptsrc;
1189 }
1190 }
1191