array(), 'last' => array(), ); /** * List of not to be moved JS. * * @var array */ private $dontmove = array( 'document.write', 'html5.js', 'show_ads.js', 'google_ad', 'histats.com/js', 'statcounter.com/counter/counter.js', 'ws.amazon.com/widgets', 'media.fastclick.net', '/ads/', 'comment-form-quicktags/quicktags.php', 'edToolbar', 'intensedebate.com', 'scripts.chitika.net/', '_gaq.push', 'jotform.com/', 'admin-bar.min.js', 'GoogleAnalyticsObject', 'plupload.full.min.js', 'syntaxhighlighter', 'adsbygoogle', 'gist.github.com', '_stq', 'nonce', 'post_id', 'data-noptimize', 'logHuman', 'amp-mobile-version-switcher', ); /** * List of to be moved JS. * * @var array */ private $domove = array( 'gaJsHost', 'load_cmc', 'jd.gallery.transitions.js', 'swfobject.embedSWF(', 'tiny_mce.js', 'tinyMCEPreInit.go', ); /** * List of JS that can be moved last (not used any more). * * @var array */ private $domovelast = array( 'addthis.com', '/afsonline/show_afs_search.js', 'disqus.js', 'networkedblogs.com/getnetworkwidget', 'infolinks.com/js/', 'jd.gallery.js.php', 'jd.gallery.transitions.js', 'swfobject.embedSWF(', 'linkwithin.com/widget.js', 'tiny_mce.js', 'tinyMCEPreInit.go', ); /** * Setting CDN base URL. * * @var string */ public $cdn_url = ''; /** * Setting; aggregate or not. * * @var bool */ private $aggregate = true; /** * Setting; if not aggregated, should we defer? * * @var bool */ private $defer_not_aggregate = false; /** * Setting; try/catch wrapping or not. * * @var bool */ private $trycatch = false; /** * State; is JS already minified. * * @var bool */ private $alreadyminified = false; /** * Setting; force JS in head or not. * * @var bool */ private $forcehead = true; /** * Setting; aggregate inline JS or not. * * @var bool */ private $include_inline = false; /** * State; holds JS code. * * @var string */ private $jscode = ''; /** * State; holds URL of JS-file. * * @var string */ private $url = ''; /** * State; stores rest of HTML if (old) option "only in head" is on. * * @var string */ private $restofcontent = ''; /** * State; holds md5-hash. * * @var string */ private $md5hash = ''; /** * Setting (filter); allowlist of to be aggregated JS. * * @var string */ private $allowlist = ''; /** * Setting (filter); holds JS that should be removed. * * @var array */ private $jsremovables = array(); /** * Setting (filter); can we inject already minified files after the * unminified aggregate JS has been minified. * * @var bool */ private $inject_min_late = true; /** * Setting; should excluded JS be minified (if not already). * * @var bool */ private $minify_excluded = true; /** * Reads the page and collects script tags. * * @param array $options all options. */ public function read( $options ) { $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content ); if ( $noptimize_js ) { return false; } // only optimize known good JS? $allowlist_js = apply_filters( 'autoptimize_filter_js_allowlist', '', $this->content ); $allowlist_js = apply_filters( 'autoptimize_filter_js_whitelist', $allowlist_js, $this->content ); // fixme: to be removed in next version. if ( ! empty( $allowlist_js ) ) { $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_js ) ) ); } // is there JS we should simply remove? $removable_js = apply_filters( 'autoptimize_filter_js_removables', '', $this->content ); if ( ! empty( $removable_js ) ) { $this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removable_js ) ) ); } // only header? if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) { $content = explode( '', $this->content, 2 ); $this->content = $content[0] . ''; $this->restofcontent = $content[1]; } // Determine whether we're doing JS-files aggregation or not. if ( ! $options['aggregate'] ) { $this->aggregate = false; } // Returning true for "dontaggregate" turns off aggregation. if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) { $this->aggregate = false; } // Defer when not aggregating. if ( false === $this->aggregate && apply_filters( 'autoptimize_js_filter_defer_not_aggregate', $options['defer_not_aggregate'] ) ) { $this->defer_not_aggregate = true; } // include inline? if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) { $this->include_inline = true; } // filter to "late inject minified JS", default to true for now (it is faster). $this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true ); // filters to override hardcoded do(nt)move(last) array contents (array in, array out!). $this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove ); $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast ); $this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove ); // Determine whether excluded files should be minified if not yet so. if ( ! $options['minify_excluded'] && $options['aggregate'] ) { $this->minify_excluded = false; } $this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded, '' ); // get extra exclusions settings or filter. $exclude_js = $options['js_exclude']; $exclude_js = apply_filters( 'autoptimize_filter_js_exclude', $exclude_js, $this->content ); if ( '' !== $exclude_js ) { if ( is_array( $exclude_js ) ) { $remove_keys = array_keys( $exclude_js, 'remove' ); if ( false !== $remove_keys ) { foreach ( $remove_keys as $remove_key ) { unset( $exclude_js[ $remove_key ] ); $this->jsremovables[] = $remove_key; } } $excl_js_arr = array_keys( $exclude_js ); } else { $excl_js_arr = array_filter( array_map( 'trim', explode( ',', $exclude_js ) ) ); } $this->dontmove = array_merge( $excl_js_arr, $this->dontmove ); } // Should we add try-catch? if ( $options['trycatch'] ) { $this->trycatch = true; } // force js in head? if ( $options['forcehead'] ) { $this->forcehead = true; } else { $this->forcehead = false; } $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead ); // get cdn url. $this->cdn_url = $options['cdn_url']; // noptimize me. $this->content = $this->hide_noptimize( $this->content ); // Save IE hacks. $this->content = $this->hide_iehacks( $this->content ); // comments. $this->content = $this->hide_comments( $this->content ); // Get script files. if ( preg_match_all( '##Usmi', $this->content, $matches ) ) { foreach ( $matches[0] as $tag ) { // only consider script aggregation for types allowlisted in should_aggregate-function. $should_aggregate = $this->should_aggregate( $tag ); if ( ! $should_aggregate ) { $tag = ''; continue; } if ( preg_match( '#]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) { // non-inline script. if ( $this->isremovable( $tag, $this->jsremovables ) ) { $this->content = str_replace( $tag, '', $this->content ); continue; } $orig_tag = null; $url = current( explode( '?', $source[2], 2 ) ); $path = $this->getpath( $url ); if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable( $tag ) ) { // ok to optimize, add to array. $this->scripts[] = $path; } else { $orig_tag = $tag; $new_tag = $tag; // non-mergeable script (excluded or dynamic or external). if ( is_array( $exclude_js ) ) { // should we add flags? foreach ( $exclude_js as $excl_tag => $excl_flags ) { if ( false !== strpos( $orig_tag, $excl_tag ) && in_array( $excl_flags, array( 'async', 'defer' ) ) ) { $new_tag = str_replace( '#Usmi', $tag, $code ); $code = preg_replace( '#.*.*#sm', '$1', $code[1] ); $code = preg_replace( '/(?:^\\s*\\s*$)/', '', $code ); $this->scripts[] = 'INLINE;' . $code; } else { // Can we move this? $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag ); if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) { if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) { $this->move['last'][] = $tag; } else { $this->move['first'][] = $tag; } } else { // We shouldn't touch this. $tag = ''; } } // Re-hide comments to be able to do the removal based on tag from $this->content. $tag = $this->hide_comments( $tag ); } // Remove the original script tag. $this->content = str_replace( $tag, '', $this->content ); } return true; } // No script files, great ;-) . return false; } /** * Determines wheter a certain `'; $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload ); $bodyreplacement = implode( '', $this->move['first'] ); $bodyreplacement .= $bodyreplacementpayload; $bodyreplacement .= implode( '', $this->move['last'] ); $replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag ); if ( strlen( $this->jscode ) > 0 ) { $this->inject_in_html( $bodyreplacement, $replace_tag ); } // Restore comments. $this->content = $this->restore_comments( $this->content ); // Restore IE hacks. $this->content = $this->restore_iehacks( $this->content ); // Restore noptimize. $this->content = $this->restore_noptimize( $this->content ); // Return the modified HTML. return $this->content; } /** * Checks against the allow- and blocklists. * * @param string $tag JS tag. */ private function ismergeable( $tag ) { if ( empty( $tag ) || ! $this->aggregate ) { return false; } if ( ! empty( $this->allowlist ) ) { foreach ( $this->allowlist as $match ) { if ( false !== strpos( $tag, $match ) ) { return true; } } // No match with allowlist. return false; } else { foreach ( $this->domove as $match ) { if ( false !== strpos( $tag, $match ) ) { // Matched something. return false; } } if ( $this->movetolast( $tag ) ) { return false; } foreach ( $this->dontmove as $match ) { if ( false !== strpos( $tag, $match ) ) { // Matched something. return false; } } // If we're here it's safe to merge. return true; } } /** * Checks agains the blocklist. * * @param string $tag tag to check for blocklist (exclusions). */ private function ismovable( $tag ) { if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) { return false; } foreach ( $this->domove as $match ) { if ( false !== strpos( $tag, $match ) ) { // Matched something. return true; } } if ( $this->movetolast( $tag ) ) { return true; } foreach ( $this->dontmove as $match ) { if ( false !== strpos( $tag, $match ) ) { // Matched something. return false; } } // If we're here it's safe to move. return true; } private function movetolast( $tag ) { if ( empty( $tag ) ) { return false; } foreach ( $this->domovelast as $match ) { if ( false !== strpos( $tag, $match ) ) { // Matched, return true. return true; } } // Should be in 'first'. return false; } /** * Determines wheter a