<?php

use MatthiasMullie\Minify;

/*
 *  Based on some work of autoptimize plugin
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly

class Breeze_MinificationScripts extends Breeze_MinificationBase {
	private $head_scripts        = array();
	private $footer_scripts      = array();
	private $dontmove            = array(
		'gtag',
		'document.write',
		'html5.js',
		'show_ads.js',
		'google_ad',
		'blogcatalog.com/w',
		'tweetmeme.com/i',
		'mybloglog.com/',
		'histats.com/js',
		'ads.smowtion.com/ad.js',
		'statcounter.com/counter/counter.js',
		'widgets.amung.us',
		'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',
		'googletagmanager',
	);
	private $donotmove_exception = array( 'jQuery' );

	private $domove                   = array( 'gaJsHost', 'load_cmc', 'jd.gallery.transitions.js', 'swfobject.embedSWF(', 'tiny_mce.js', 'tinyMCEPreInit.go' );
	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',
		'post_id',
		'googletagmanager',
	);
	private $trycatch                 = false;
	private $alreadyminified          = false;
	private $forcehead                = true;
	private $include_inline           = false;
	private $jscode                   = '';
	private $url                      = '';
	private $move                     = array(
		'first' => array(),
		'last'  => array(),
	);
	private $restofcontent            = '';
	private $file_name                = '';
	private $whitelist                = '';
	private $jsremovables             = array();
	private $inject_min_late          = '';
	private $group_js                 = false;
	private $custom_js_exclude        = array();
	private $js_head_group            = array();
	private $js_footer_group          = array();
	private $js_min_head              = array();
	private $js_min_footer            = array();
	private $url_group_head           = array();
	private $url_group_footer         = array();
	private $jscode_inline_head       = array();
	private $jscode_inline_footer     = array();
	private $move_to_footer_js        = array();
	private $move_to_footer           = array();
	private $defer_js                 = array();
	private $full_script              = '';
	private $uncompressed_inline      = array();
	private $inline_increment         = 1;
	private $original_content         = '';
	private $show_original_content    = 0;
	private $do_process               = false;
	private $inline_counter           = 0;
	private $group_hash_changed       = true;
	public $breeze_minified_js_hashes = array();
	/**
	 * We need to keep track of filenames to make sure
	 * every filename is unique otherwise if the same
	 * name found only one file will get generated.
	 * the structure is like this:
	 *
	 * [ 'file_name' => 'level' ]
	 */
	private $url_scripts_file_names = array();

	/**
	 * Extract level determines how many directories will be included in the filename.
	 */
	private $extract_level = -3;

	/**
	 * Holds the 3rd party defer links
	 *
	 * @var array[]
	 */
	private $do_defer_action = array(
		'head'   => array(),
		'footer' => array(),
	);
	/**
	 * Holds the 3rd party defer tags
	 *
	 * @var array[]
	 */
	private $do_defer_tag = array();

	/**
	 * Defer/Delay the inline scripts.
	 *
	 * @var array
	 */
	private $no_delay_js = array();

	/**
	 * Delay the Javascript
	 *
	 * @var array
	 */
	private $delay_javascript = false;

	/**
	 * Defer/Delay the inline scripts.
	 *
	 * @var array
	 */
	private $delay_inline_js = array();

	/**
	 * If Inline JS delay is on/off.
	 *
	 * @var array
	 */
	private $is_inline_delay_on = false;

	/**
	 * Contains all the scripts that will be delayed.
	 *
	 * @var array
	 */
	private $delay_scripts = array(
		'header' => array(),
		'footer' => array(),
	);

	// Reads the page and collects script tags
	public function read( $options ) {

		$this_path_url                   = $this->get_cache_file_url( 'js' );
		$this->breeze_minified_js_hashes = $options['breeze_minified_js_hashes'];
		if ( false === breeze_is_process_locked( $this_path_url ) ) {
			$this->do_process = breeze_lock_cache_process( $this_path_url );
		} else {
			$this->original_content = $this->content;

			return true;
		}

		$noptimizeJS = apply_filters( 'breeze_filter_js_noptimize', false, $this->content );
		if ( $noptimizeJS ) {
			return false;
		}

		if ( false === $this->group_js && false === $this->include_inline ) {
			$this->donotmove_exception[] = '/wp-includes/js/dist/i18n.js';
			$this->donotmove_exception[] = '/wp-includes/js/dist/i18n.min.js';
			$this->dontmove[]            = '/wp-includes/js/dist/i18n.js';
			$this->dontmove[]            = '/wp-includes/js/dist/i18n.min.js';
		}

		$this->delay_javascript   = $options['delay_javascript'];
		$this->is_inline_delay_on = $options['is_inline_delay_on'];
		// only optimize known good JS?
		$whitelistJS = apply_filters( 'breeze_filter_js_whitelist', '' );
		if ( ! empty( $whitelistJS ) ) {
			$this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistJS ) ) );
		}

		// is there JS we should simply remove
		$removableJS = apply_filters( 'breeze_filter_js_removables', '' );
		if ( ! empty( $removableJS ) ) {
			$this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removableJS ) ) );
		}

		// only header?
		if ( apply_filters( 'breeze_filter_js_justhead', $options['justhead'] ) == true ) {
			$content             = explode( '</head>', $this->content, 2 );
			$this->content       = $content[0] . '</head>';
			$this->restofcontent = $content[1];
		}

		// include inline?
		if ( apply_filters( 'breeze_js_include_inline', $options['include_inline'] ) == true ) {
			$this->include_inline = true;
		}

		// group js?
		if ( apply_filters( 'breeze_js_group_js', $options['group_js'] ) == true ) {
			$this->group_js = true;
		}

		// custom js exclude
		if ( ! empty( $options['custom_js_exclude'] ) ) {
			$this->custom_js_exclude = $options['custom_js_exclude'];
		}

		// JS files will move to footer
		if ( ! empty( $options['move_to_footer_js'] ) ) {
			$this->move_to_footer_js = $options['move_to_footer_js'];
		}

		// JS files will move to footer
		if ( ! empty( $options['defer_js'] ) ) {
			$this->defer_js = $options['defer_js'];
		}

		// Inline delay scripts
		if ( ! empty( $options['delay_inline_js'] ) ) {
			$this->delay_inline_js = $options['delay_inline_js'];
		}

		if ( ! empty( $options['no_delay_js'] ) ) {
			$this->no_delay_js = $options['no_delay_js'];
		}

		// filter to "late inject minified JS", default to true for now (it is faster)
		$this->inject_min_late = apply_filters( 'breeze_filter_js_inject_min_late', true );

		// filters to override hardcoded do(nt)move(last) array contents (array in, array out!)
		$this->dontmove   = apply_filters( 'breeze_js_dontmove', $this->dontmove );
		$this->domovelast = apply_filters( 'breeze_filter_js_movelast', $this->domovelast );
		$this->domove     = apply_filters( 'breeze_filter_js_domove', $this->domove );

		// get extra exclusions settings or filter
		$excludeJS = $options['js_exclude'];
		$excludeJS = apply_filters( 'breeze_filter_js_exclude', $excludeJS );
		if ( $excludeJS !== '' ) {
			$exclJSArr      = array_filter( array_map( 'trim', explode( ',', $excludeJS ) ) );
			$this->dontmove = array_merge( $exclJSArr, $this->dontmove );
		}

		// Should we add try-catch?
		if ( $options['trycatch'] == true ) {
			$this->trycatch = true;
		}

		// force js in head?
		if ( $options['forcehead'] == true ) {
			$this->forcehead = true;
		} else {
			$this->forcehead = false;
		}
		$this->forcehead = apply_filters( 'breeze_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
		$exploded_content = explode( '</head>', $this->content, 2 );
		$this->getJS( $exploded_content[0] );
		$this->getJS( $exploded_content[1], false );

		if ( ! empty( $this->head_scripts ) || ! empty( $this->footer_scripts ) ) {
			// Re-order moving to footer JS files
			$ordered_moving_js    = array_intersect_key( $this->move_to_footer_js, $this->move_to_footer );
			$ordered_moving_js    = array_map( array( $this, 'getpath' ), $ordered_moving_js );
			$this->footer_scripts = array_merge( $ordered_moving_js, $this->footer_scripts );

			return true;
		}

		// No script files, great!
		return false;
	}

	// Get all JS in page
	private function getJS( $content, $head = true ) {

		if ( preg_match_all( '#<script.*</script>#Usmi', $content, $matches ) ) {

			if ( wp_script_is( 'spai-sniper', 'enqueued' ) ) {
				$jquery_local_path         = home_url( '/wp-includes/js/jquery/jquery.js' );
				$this->custom_js_exclude[] = $jquery_local_path;
			}

			if ( isset( $matches[0] ) && ! empty( $matches[0] ) ) {
				$matches[0] = $this->delay_script_loading( $matches[0] );
			}

			foreach ( $matches[0] as $tag ) {

				if ( false !== strpos( $tag, 'ga(' ) ||
					false !== strpos( $tag, 'google-analytics.com/analytics.js' ) ||
					false !== strpos( $tag, '/breeze-extra/' ) ||
					false !== strpos( $tag, "gtag('js'" )
				) {
					$tag = '';
					continue;
				}

				// only consider aggregation whitelisted in should_aggregate-function
				if ( ! $this->should_aggregate( $tag ) ) {
					$tag = '';
					continue;
				}

				if ( preg_match( '/\ssrc=("|\')?(.*(\ |\>))("|\')?/Usmi', $tag, $source ) ) {
					$source[2] = substr( $source[2], 0, - 1 );
					if ( $this->isremovable( $tag, $this->jsremovables ) ) {
						$content = str_replace( $tag, '', $content );
						continue;
					}

					// Get the script version.
					$script_version = '';
					$explode_url    = explode( '?', $source[2] );
					if ( isset( $explode_url[1] ) ) {
						$script_version = $explode_url[1];
					}

					// External script
					$url = current( explode( '?', $source[2], 2 ) );
					if ( $url[0] == "'" || $url[0] == '"' ) {
						$url = substr( $url, 1 );
					}
					if ( $url[ strlen( $url ) - 1 ] == '"' || $url[ strlen( $url ) - 1 ] == "'" ) {
						$url = substr( $url, 0, - 1 );
					}

					// Let's check if this file is in the excluded list.
					$is_excluded = breeze_is_string_in_array_values( $url, $this->custom_js_exclude );

					// exclude js
					if ( ! empty( $is_excluded ) ) {
						continue;
					}

					if ( false !== strpos( $tag, '.php' ) ) {
						continue;
					}

					$path = $this->getpath( $url );

					if ( $path !== false && preg_match( '#\.js$#', $path ) ) {
						// Inline
						if ( $this->ismergeable( $tag ) ) {
							// We can merge it
							if ( $head ) {
								// If this file will be move to footer
								if ( in_array( $url, $this->move_to_footer_js ) ) {
									$this->move_to_footer[ $url ] = $path;
								} else {
									$this->head_scripts[ $url ] = $path;
								}
							} else {
								$this->footer_scripts[ $url ] = $path;
							}
						} else {
							// No merge, but maybe we can move it
							if ( $this->ismovable( $tag ) ) {
								// Yeah, move it
								if ( $this->movetolast( $tag ) ) {
									$this->move['last'][] = $tag;
								} else {
									$this->move['first'][] = $tag;
								}
							} else {
								// We shouldn't touch this
								$tag = '';
							}
						}
					} else {
						// External script (example: google analytics)
						// OR Script is dynamic (.php etc)
						if ( // Defer 3rd party scripts.
							breeze_validate_url_via_regexp( $url ) &&
							! empty( $this->defer_js ) &&
							true === $this->is_in_defer_is( $url, $this->defer_js )
						) {

							if ( true === $head ) {
								$this->do_defer_action['head'][ $url ] = $url;
							} else {
								$this->do_defer_action['footer'][ $url ] = $url;
							}
							$this->do_defer_tag[ $url ] = $tag;

							// Remove the original script tag
							// $content = str_replace( $tag, '', $content );

						} elseif ( $this->ismovable( $tag ) ) {
							if ( $this->movetolast( $tag ) ) {
								$this->move['last'][] = $tag;
							} else {
								$this->move['first'][] = $tag;
							}
						} else {
							$is_delayed = false;
							if ( true === $this->is_inline_delay_on ) {
								$is_delayed = $this->is_inline_delay( $tag );
							}
							if ( true === $is_delayed ) {
								if ( true === $head ) {
									$this->delay_scripts['header'][ $url ] = array(
										'path'    => $path,
										'version' => $script_version,
									);
								} else {
									$this->delay_scripts['footer'][ $url ] = array(
										'path'    => $path,
										'version' => $script_version,
									);
								}
							} else {
								// We shouldn't touch this
								$tag = '';
							}
						}
					}
				} else {
					// Inline script
					if ( $this->isremovable( $tag, $this->jsremovables ) ) {
						$content = str_replace( $tag, '', $content );
						continue;
					}

					// unhide comments, as javascript may be wrapped in comment-tags for old times' sake
					$tag = $this->restore_comments( $tag );
					if ( $this->ismergeable( $tag ) && ( $this->include_inline || $this->group_js ) ) {

						preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
						$code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
						$code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );

						if ( $head ) {
							$this->head_scripts[] = 'INLINE;' . $code;
						} else {
							$this->footer_scripts[] = 'INLINE;' . $code;
						}
					} else {
						// Can we move this?
						if ( $this->ismovable( $tag ) ) {
							if ( $this->movetolast( $tag ) ) {
								$this->move['last'][] = $tag;
							} else {
								$this->move['first'][] = $tag;
							}
						} else {
							$is_delayed = false;
							if ( true === $this->is_inline_delay_on ) {
								$is_delayed = $this->is_inline_delay( $tag );
							}
							if ( true === $is_delayed ) {
								preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
								$code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
								$code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
								if ( true === $head ) {
									$this->delay_scripts['header'][] = $code;
								} else {
									$this->delay_scripts['footer'][] = $code;
								}
							} 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
				$content = str_replace( $tag, '', $content );
			}
		}

		if ( $head ) {
			$this->content = $content;
		} else {
			$this->content .= '</head>' . $content;
		}

		return true;
	}

	public function minify() {
		$this->runMinify( $this->head_scripts );
		$this->runMinify( $this->footer_scripts, false );

		if ( ! empty( $this->group_js ) && empty( $this->include_inline ) && ! empty( $this->uncompressed_inline ) ) {
			foreach ( $this->uncompressed_inline as $index_inline => $uncompressed_js ) {
				// add the uncompressed inline
				$this->full_script = str_replace( $index_inline, $uncompressed_js, $this->full_script );
			}
		}

		return true;
	}

	/**
	 * Generates file name that will be stored in breeze minification directory.
	 */
	private function breeze_get_minified_file_name( $url, $extract_level ) {
		if ( is_int( $url ) ) {
			++$this->inline_counter;
			$file_name = 'inline_script_' . $this->inline_counter;
		} else {
			$parts     = array_filter( explode( '/', $url ) );
			$file_path = array_slice( $parts, $extract_level );
			$file_name = substr( implode( '-', $file_path ), 0, -3 );
			if ( isset( $this->url_scripts_file_names[ $file_name ] ) ) {
				$extract_level = $this->url_scripts_file_names[ $file_name ];
				--$extract_level;
				return $this->breeze_get_minified_file_name( $url, $extract_level );
			}
			$this->url_scripts_file_names[ $file_name ] = $extract_level;
		}
		return $file_name;
	}

	// Joins and optimizes JS
	private function runMinify( $scripts, $head = true ) {
		if ( false === $this->do_process ) {
			return true;
		}

		foreach ( $scripts as $url => $script ) {

			if ( preg_match( '#^INLINE;#', $script ) ) {
				// Inline script
				$script = preg_replace( '#^INLINE;#', '', $script );
				$script = rtrim( $script, ";\n\t\r" ) . ';';
				// Add try-catch?
				if ( $this->trycatch ) {
					$script = 'try{' . $script . '}catch(e){}';
				}
				$tmpscript = apply_filters( 'breeze_js_individual_script', $script, '' );
				if ( has_filter( 'breeze_js_individual_script' ) && ! empty( $tmpscript ) ) {
					$script                = $tmpscript;
					$this->alreadyminified = true;
				}

				if ( empty( $this->group_js ) ) {

					if ( $head ) {
						$this->js_head_group[] = $script;
					} else {
						$this->js_footer_group[] = $script;
					}
				}

				if ( empty( $this->include_inline ) ) {

					$index_inline                               = "/*!IS_UNCOMPRESSED_INLINE_{$this->inline_increment}*/";
					$this->full_script                         .= $index_inline;
					$this->uncompressed_inline[ $index_inline ] = $script;
					++$this->inline_increment;

				} else {

					$this->full_script .= $script;
				}
			} else {

				// External script
				if ( $script !== false && file_exists( $script ) && is_readable( $script ) ) {

					$scriptsrc = file_get_contents( $script );
					$scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
					if ( empty( $scriptsrc ) ) {
						$scriptsrc = '';
					}
					$scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';

					// Add try-catch?
					if ( $this->trycatch ) {
						$scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
					}
					$tmpscriptsrc = apply_filters( 'breeze_js_individual_script', $scriptsrc, $script );
					if ( has_filter( 'breeze_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
						$scriptsrc             = $tmpscriptsrc;
						$this->alreadyminified = true;

					} elseif ( apply_filters( 'breeze_js_ignore_minify', $script ) || ( strpos( $script, 'min.js' ) !== false ) && ( $this->inject_min_late === true ) || $this->breeze_js_files_exceptions( $url ) ) {
						$scriptsrc = '%%INJECTLATER' . breeze_HASH . '%%' . base64_encode( $script ) . '|' . hash( 'sha512', $scriptsrc ) . '%%INJECTLATER%%';

					}

					if ( $this->group_js == true ) {
						$this->jscode      .= "\n" . $scriptsrc;
						$this->full_script .= "\n" . $scriptsrc;
					} elseif ( $head ) {
							$this->js_head_group[ $url ] = $scriptsrc;
					} else {
						$this->js_footer_group[ $url ] = $scriptsrc;
					}
				}/*
				else{
					//Couldn't read JS. Maybe getpath isn't working?
				}*/
			}
		}

		// Minify JS
		// When using group JS
		if ( ! $head && ! empty( $this->jscode ) ) {
			// Check for already-minified code
			#$this->file_name = 'combined-scripts';
			$this->file_name = $this->create_cache_file_name();
			$jscode_hash     = hash( 'sha512', $this->jscode );
			$ccheck          = new Breeze_MinificationCache( $this->file_name, 'js' );
			if ( isset( $this->breeze_minified_js_hashes[ $this->file_name ] )
			&& $this->breeze_minified_js_hashes[ $this->file_name ] === $jscode_hash
			) {
				$this->group_hash_changed = false;
			}
			if ( $ccheck->check() && ! $this->group_hash_changed ) {
				$this->full_script     = $ccheck->retrieve();
				$this->alreadyminified = true;
			}
			unset( $ccheck );

			// $this->jscode has all the uncompressed code now.

			if ( $this->alreadyminified !== true ) {

				if ( apply_filters( 'breeze_js_do_minify', true ) && ! empty( $this->full_script ) ) {
					$minifier = new Minify\JS();
					$minifier->add( $this->full_script );
					$tmp_jscode = $minifier->minify();

					if ( ! empty( $tmp_jscode ) ) {
						$this->full_script = trim( $tmp_jscode );
						unset( $tmp_jscode );
					}
				}
				$this->breeze_minified_js_hashes[ $this->file_name ] = $jscode_hash;
				$this->full_script                                   = $this->inject_minified( $this->full_script );
			}

			// Get the inline JS and minify
			if ( ! empty( $this->js_head_group ) || ! empty( $this->js_footer_group ) ) {
				if ( ! empty( $this->js_head_group ) ) {
					foreach ( $this->js_head_group as $jscode ) {
						// $this->jscode has all the uncompressed code now.
						if ( apply_filters( 'breeze_js_do_minify', true ) ) {
							$minifier = new Minify\JS();
							$minifier->add( $jscode );
							$tmp_jscode = $minifier->minify();

							if ( ! empty( $tmp_jscode ) ) {
								$jscode = $tmp_jscode;
								unset( $tmp_jscode );
							}
						}

						$this->jscode_inline_head[] = $this->inject_minified( $jscode );
					}
				}

				if ( ! empty( $this->js_footer_group ) ) {
					foreach ( $this->js_footer_group as $jscode ) {
						// $this->jscode has all the uncompressed code now.
						if ( apply_filters( 'breeze_js_do_minify', true ) ) {
							$minifier = new Minify\JS();
							$minifier->add( $jscode );
							$tmp_jscode = $minifier->minify();

							if ( ! empty( $tmp_jscode ) ) {
								$jscode = $tmp_jscode;
								unset( $tmp_jscode );
							}
						}

						$this->jscode_inline_footer[] = $this->inject_minified( $jscode );
					}
				}
			}

			return true;
		}

		// Not using group JS
		if ( ! empty( $this->js_head_group ) || ! empty( $this->js_footer_group ) ) {
			if ( $head && ! empty( $this->js_head_group ) ) {
				foreach ( $this->js_head_group as $url => $jscode ) {

					$this->file_name = $this->breeze_get_minified_file_name( $url, $this->extract_level );
					$jscode_hash     = hash( 'sha512', $jscode );
					$ccheck          = new Breeze_MinificationCache( $this->file_name, 'js' );
					$hash_changed    = true;
					if ( isset( $this->breeze_minified_js_hashes[ $this->file_name ] )
					&& $this->breeze_minified_js_hashes[ $this->file_name ] === $jscode_hash
					) {
						$hash_changed = false;
					}

					if ( $ccheck->check() && ! $hash_changed ) {
						$js_exist                  = $ccheck->retrieve();
						$this->js_min_head[ $url ] = $this->file_name . '_breezejsgroup_' . $js_exist . '_hashchanged_' . (string) $hash_changed;
						continue;
					}
					unset( $ccheck );

					// $this->jscode has all the uncompressed code now.
					if ( apply_filters( 'breeze_js_do_minify', true ) ) {
						$minifier = new Minify\JS();
						$minifier->add( $jscode );
						$tmp_jscode = $minifier->minify();

						if ( ! empty( $tmp_jscode ) ) {
							$jscode = $tmp_jscode;
							unset( $tmp_jscode );
						}
					}

					$jscode = $this->inject_minified( $jscode );
					$this->breeze_minified_js_hashes[ $this->file_name ] = $jscode_hash;
					$this->js_min_head[ $url ]                           = $this->file_name . '_breezejsgroup_' . $jscode . '_hashchanged_' . (string) $hash_changed;
				}
			}

			if ( ! $head && ! empty( $this->js_footer_group ) ) {
				foreach ( $this->js_footer_group as $url => $jscode ) {
					$this->file_name = $this->breeze_get_minified_file_name( $url, $this->extract_level );
					$jscode_hash     = hash( 'sha512', $jscode );
					$ccheck          = new Breeze_MinificationCache( $this->file_name, 'js' );
					$hash_changed    = true;
					if ( isset( $this->breeze_minified_js_hashes[ $this->file_name ] )
					&& $this->breeze_minified_js_hashes[ $this->file_name ] === $jscode_hash
					) {
						$hash_changed = false;
					}
					$ccheck = new Breeze_MinificationCache( $this->file_name, 'js' );
					if ( $ccheck->check() && ! $hash_changed ) {
						$js_exist                    = $ccheck->retrieve();
						$this->js_min_footer[ $url ] = $this->file_name . '_breezejsgroup_' . $js_exist . '_hashchanged_' . (string) $hash_changed;

						continue;
					}
					unset( $ccheck );

					// $this->jscode has all the uncompressed code now.
					if ( apply_filters( 'breeze_js_do_minify', true ) ) {
						$minifier = new Minify\JS();
						$minifier->add( $jscode );
						$tmp_jscode = $minifier->minify();

						if ( ! empty( $tmp_jscode ) ) {
							$jscode = $tmp_jscode;
							unset( $tmp_jscode );
						}
					}

					$jscode = $this->inject_minified( $jscode );
					$this->breeze_minified_js_hashes[ $this->file_name ] = $jscode_hash;
					$this->js_min_footer[ $url ]                         = $this->file_name . '_breezejsgroup_' . $jscode . '_hashchanged_' . (string) $hash_changed;

				}
			}
		}
		return true;
	}

	public function update_minified_hashes() {
		$breeze_minified_hashes       = get_option( 'breeze_minified_hashes', array() );
		$breeze_minified_hashes['js'] = $this->breeze_minified_js_hashes;
		update_option( 'breeze_minified_hashes', $breeze_minified_hashes );
	}


	// Caches the JS in uncompressed, deflated and gzipped form.
	public function cache() {
		if ( false === $this->do_process ) {
			return true;
		}

		if ( $this->group_js == true ) {
			// If inline is also included
			$cache = new Breeze_MinificationCache( $this->file_name, 'js' );

			if ( ! $cache->check() || $this->group_hash_changed ) {
				// Cache our code
				$cache->cache( $this->full_script, 'text/javascript' );
			}

			$cache_directory = $cache->get_cache_dir();
			if ( $this->is_cache_file_present( $cache_directory . $cache->get_file_name() ) ) {
				$this->url = breeze_CACHE_URL . breeze_current_user_type() . $cache->getname() . '?ver=' . time();
				$this->url = $this->url_replace_cdn( $this->url );
			} else {
				$this->show_original_content = 1;
				$this->clear_cache_data();
			}
		} else {
			$url_exists = true;

			foreach ( $this->js_min_head as $old_url => $js_min ) {
				$minifier_info = explode( '_breezejsgroup_', $js_min, 2 );
				$file_name     = $this->create_cache_file_name( '', 0, 50 ) . '-' . $minifier_info[0];   // e.g., "photo.css"
				$remaining     = explode( '_hashchanged_', $minifier_info[1], 2 );
				$js_code       = $remaining[0];
				$hash_changed  = (bool) $remaining[1];

				$cache = new Breeze_MinificationCache( $file_name, 'js' );
				if ( ! $cache->check() || $hash_changed ) {
					// Cache our code
					$cache->cache( $js_code, 'text/javascript' );
				}

				$cache_directory = $cache->get_cache_dir();
				if ( ! file_exists( $cache_directory . $cache->get_file_name() ) ) {
					$url_exists = false;
				} else {
					$url = breeze_CACHE_URL . breeze_current_user_type() . $cache->getname() . '?ver=' . time();
					if ( true === $this->delay_javascript && is_numeric( $old_url ) && $this->ignore_from_delay( $js_code ) ) {
						$this->url_group_head['defer'] = $this->url_replace_cdn( $url );
					} elseif ( true === $this->is_inline_delay_on && is_numeric( $old_url ) && $this->is_inline_delay( $js_code ) ) {
						$this->url_group_head['defer'] = $this->url_replace_cdn( $url );
					} else {
						$this->url_group_head[ $old_url ] = $this->url_replace_cdn( $url );
					}
				}
			}

			foreach ( $this->js_min_footer as $old_url => $js_min ) {
				$minifier_info = explode( '_breezejsgroup_', $js_min, 2 );
				$file_name     = $this->create_cache_file_name( '', 0, 50 ) . '-' . $minifier_info[0];   // e.g., "photo.css"
				$remaining     = explode( '_hashchanged_', $minifier_info[1], 2 );
				$js_code       = $remaining[0];
				$hash_changed  = (bool) $remaining[1];
				$cache         = new Breeze_MinificationCache( $file_name, 'js' );
				if ( ! $cache->check() || $hash_changed ) {
					// Cache our code
					$cache->cache( $js_code, 'text/javascript' );
				}

				$cache_directory = $cache->get_cache_dir();
				if ( ! file_exists( $cache_directory . $cache->get_file_name() ) ) {
					$url_exists = false;
				} else {
					$url = breeze_CACHE_URL . breeze_current_user_type() . $cache->getname() . '?ver=' . time();
					if ( true === $this->delay_javascript && is_numeric( $old_url ) && $this->ignore_from_delay( $js_code ) ) {
						$this->url_group_footer['defer'] = $this->url_replace_cdn( $url );
					} elseif ( true === $this->is_inline_delay_on && is_numeric( $old_url ) && $this->is_inline_delay( $js_code ) ) {
						$this->url_group_footer['defer'] = $this->url_replace_cdn( $url );
					} else {
						$this->url_group_footer[ $old_url ] = $this->url_replace_cdn( $url );
					}
				}
			}

			if ( false === $url_exists ) {
				$this->show_original_content = 1;
				$this->clear_cache_data();
			}
		}
		$this->update_minified_hashes();
	}

	// Returns the content
	public function getcontent() {
		if ( ! empty( $this->show_original_content ) ) {
			return $this->original_content;
		}

		// Restore the full content
		if ( ! empty( $this->restofcontent ) ) {
			$this->content      .= $this->restofcontent;
			$this->restofcontent = '';
		}

		// Load inline JS to html
		if ( ! empty( $this->jscode_inline_head ) && empty( $this->group_js ) ) {

			$replaceTag = array( '</head>', 'before' );

			foreach ( $this->jscode_inline_head as $js ) {
				if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js ) ) {
					$jsHead[] = '<div class="breeze-scripts-load" data-file="0" data-async="false" data-locate="head" data-defer="false" style="display:none">' . htmlspecialchars( $js, ENT_QUOTES ) . '</div>' . "\n";
				} elseif ( $this->is_inline_delay_on && $this->is_inline_delay( $js ) ) {
					$jsHead[] = '<script type="module">' . $js . '</script>';
				} else {
					$jsHead[] = '<script type="text/javascript">' . $js . '</script>';
				}
			}
			$jsReplacement  = '';
			$jsReplacement .= implode( '', $jsHead );
			$this->inject_in_html( $jsReplacement, $replaceTag );
		}

		if ( ! empty( $this->jscode_inline_footer ) && empty( $this->group_js ) ) {
			$replaceTag = array( '</body>', 'before' );

			foreach ( $this->jscode_inline_footer as $js ) {
				if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js ) ) {
					$jsFooter[] = '<div class="breeze-scripts-load" data-file="0" data-async="false" data-locate="footer" data-defer="false" style="display:none">' . htmlspecialchars( $js, ENT_QUOTES ) . '</div>' . "\n";
				} elseif ( $this->is_inline_delay_on && $this->is_inline_delay( $js ) ) {
					$jsFooter[] = '<script type="module">' . $js . '</script>';
				} else {
					$jsFooter[] = '<script type="text/javascript">' . $js . '</script>';
				}
			}
			$jsReplacement  = '';
			$jsReplacement .= implode( '', $jsFooter );
			$this->inject_in_html( $jsReplacement, $replaceTag );
		}

		// $defer = apply_filters('breeze_filter_js_defer', $defer);

		if ( $this->group_js == true ) {
			$replaceTag = array( '</body>', 'before' );
			if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $this->url ) ) {
				$bodyreplacementpayload = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="footer" data-defer="true"  style="display:none">' . $this->url . '</div>' . "\n";
			} elseif ( $this->is_inline_delay_on && $this->is_inline_delay( $this->url ) ) { // Might add specific changes later.
				$bodyreplacementpayload = '<script type="text/javascript" defer src="' . $this->url . '"></script>';
			} else {
				$bodyreplacementpayload = '<script type="text/javascript" defer src="' . $this->url . '"></script>';
			}
			$bodyreplacementpayload = apply_filters( 'breeze_filter_js_bodyreplacementpayload', $bodyreplacementpayload );

			$bodyreplacement  = implode( '', $this->move['first'] );
			$bodyreplacement .= $bodyreplacementpayload;
			$bodyreplacement .= implode( '', $this->move['last'] );

			$replaceTag = apply_filters( 'breeze_filter_js_replacetag', $replaceTag );

			if ( strlen( $this->full_script ) > 0 ) {
				$this->inject_in_html( $bodyreplacement, $replaceTag );
			}
		} else {

			// handle the 3rd party defer scripts in header.
			if ( ! empty( $this->delay_scripts ) && ! empty( $this->delay_scripts['header'] ) ) {
				$replace_tag   = array( '</head>', 'before' );
				$js_head_defer = array();
				$defer         = 'defer ';
				foreach ( $this->delay_scripts['header'] as $js_url => $js_script ) {
					if ( is_string( $js_url ) ) {
						$js_url = trim( $js_url, '”' );
					}

					if ( filter_var( $js_url, FILTER_VALIDATE_URL ) ) {
						if ( ! empty( $js_script['version'] ) ) {
							$js_url .= '?' . $js_script['version'];
						}
						if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js_url ) ) {
							$js_head_defer[] = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="head" data-defer="true" style="display:none">' . $js_url . '</div>' . "\n";
						} else {
							$js_head_defer[] = "<script type='application/javascript' {$defer}src='{$js_url}'></script>\n";
						}
					} elseif ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js_script ) ) {
                        $js_head_defer[] = '<div class="breeze-scripts-load" data-file="0" data-async="false" data-locate="head" data-defer="true" style="display:none">' . htmlspecialchars( $js_script, ENT_QUOTES ) . '</div>' . "\n";
					} else {
						$js_head_defer[] = "<script type='module'>{$js_script}</script>\n";
					}
				}
				$js_replacement  = '';
				$js_replacement .= implode( '', $js_head_defer );
				$this->inject_in_html( $js_replacement, $replace_tag );
			}

			// handle the 3rd party defer scripts in footer.
			if ( ! empty( $this->delay_scripts ) && ! empty( $this->delay_scripts['footer'] ) ) {
				$replace_tag     = array( '</body>', 'before' );
				$js_footer_defer = array();
				$defer           = 'defer ';
				foreach ( $this->delay_scripts['footer'] as $js_url => $js_script ) {
					if ( is_string( $js_url ) ) {
						$js_url = trim( $js_url, '”' );
					}

					if ( filter_var( $js_url, FILTER_VALIDATE_URL ) ) {
						if ( ! empty( $js_script['version'] ) ) {
							$js_url .= '?' . $js_script['version'];
						}

						if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js_url ) ) {
							$js_footer_defer[] = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="footer" data-defer="true" style="display:none">' . $js_url . '</div>' . "\n";
						} else {
							$js_footer_defer[] = "<script type='application/javascript' {$defer}src='{$js_url}'></script>\n";
						}
					} elseif ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js_script ) ) {
                        $js_footer_defer[] = '<div class="breeze-scripts-load" data-file="0" data-async="false" data-locate="footer" data-defer="true" style="display:none">' . htmlspecialchars( $js_script, ENT_QUOTES ) . '</div>' . "\n";
					} else {
						$js_footer_defer[] = "<script type='module'>{$js_script}</script>\n";
					}
				}

				$js_replacement  = '';
				$js_replacement .= implode( '', $js_footer_defer );
				$this->inject_in_html( $js_replacement, $replace_tag );
			}

			$headScript   = array();
			$footerScript = array();

			if ( ! empty( $this->url_group_head ) ) {
				$replaceTag = array( '</head>', 'before' );

				foreach ( $this->url_group_head as $old_url => $url ) {
					$defer = '';
					if (
						gettype( $old_url ) == 'string' &&
						(
							in_array( $old_url, $this->defer_js ) ||
							$this->is_inline_delay( $url ) ||
							'defer' === $old_url
						)
					) {
						$defer = 'defer ';
					}

					if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $url ) ) {
						if ( empty( $defer ) ) {
							$defer = 'false';
						} else {
							$defer = 'true';
						}
						$headScript[] = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="head" data-defer="' . $defer . '" style="display:none">' . $url . '</div>' . "\n";
					} else {
						$headScript[] = '<script type="text/javascript" ' . $defer . 'src="' . $url . '"></script>';
					}
				}
				$jsReplacementPayload = implode( '', $headScript );

				$jsReplacement  = implode( '', $this->move['first'] );
				$jsReplacement .= $jsReplacementPayload;

				$replaceTag = apply_filters( 'breeze_filter_js_replacetag', $replaceTag );

				if ( ! empty( $this->js_min_head ) ) {
					$this->inject_in_html( $jsReplacement, $replaceTag );
				}
			}

			if ( ! empty( $this->url_group_footer ) ) {
				$replaceTag = array( '</body>', 'before' );

				foreach ( $this->url_group_footer as $old_url => $url ) {
					$defer = '';
					if ( gettype( $old_url ) == 'string' &&
						(
							in_array( $old_url, $this->defer_js ) ||
							$this->is_inline_delay( $url ) ||
							'defer' === $old_url
						)
					) {
						$defer = 'defer ';
					}
					if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $url ) ) {
						if ( empty( $defer ) ) {
							$defer = 'false';
						} else {
							$defer = 'true';
						}
						$footerScript[] = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="footer" data-defer="' . $defer . '" style="display:none">' . $url . '</div>' . "\n";
					} else {
						$footerScript[] = '<script type="text/javascript" ' . $defer . 'src="' . $url . '"></script>';
					}
				}
				$jsReplacementPayload = implode( '', $footerScript );

				$jsReplacement  = $jsReplacementPayload;
				$jsReplacement .= implode( '', $this->move['last'] );

				$replaceTag = apply_filters( 'breeze_filter_js_replacetag', $replaceTag );

				if ( ! empty( $this->js_min_footer ) ) {
					$this->inject_in_html( $jsReplacement, $replaceTag );
				}
			}
		}

		if ( ! empty( $this->do_defer_action['head'] ) || ! empty( $this->do_defer_action['footer'] ) ) {

			if ( ! empty( $this->do_defer_action['head'] ) ) {
				$replaceTag  = array( '</head>', 'before' );
				$js_head     = array();
				$defer       = 'defer ';
				$delay_defer = 'true';

				foreach ( $this->do_defer_action['head'] as $js_url => $js_path ) {
					if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js_url ) ) {
						$js_head[] = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="footer" data-defer="' . $delay_defer . '" style="display:none">' . $js_url . '</div>' . "\n";
					} else {
						$js_head[] = "<script type='application/javascript' {$defer}src='{$js_url}'></script>\n";
					}

					if ( isset( $this->do_defer_tag[ $js_url ] ) ) {
						$this->content = str_replace( $this->do_defer_tag[ $js_url ], '', $this->content );
					}
				}

				$js_replacement  = '';
				$js_replacement .= implode( '', $js_head );
				$this->inject_in_html( $js_replacement, $replaceTag );
			}

			if ( ! empty( $this->do_defer_action['footer'] ) ) {
				$replaceTag  = array( '</body>', 'before' );
				$js_footer   = array();
				$defer       = 'defer ';
				$delay_defer = 'true';

				foreach ( $this->do_defer_action['footer'] as $js_url => $js_path ) {
					if ( true === $this->delay_javascript && false === $this->ignore_from_delay( $js_url ) ) {
						$js_footer[] = '<div class="breeze-scripts-load" data-file="1" data-async="false" data-locate="footer" data-defer="' . $delay_defer . '" style="display:none">' . $js_url . '</div>' . "\n";
					} else {
						$js_footer[] = "<script type='application/javascript' {$defer}src='{$js_url}'></script>\n";
					}

					if ( isset( $this->do_defer_tag[ $js_url ] ) ) {
						$this->content = str_replace( $this->do_defer_tag[ $js_url ], '', $this->content );
					}
				}

				$js_replacement  = '';
				$js_replacement .= implode( '', $js_footer );
				$this->inject_in_html( $js_replacement, $replaceTag );
			}
		}

		if ( true === $this->delay_javascript ) {
			$delay_script_js = breeze_load_delay_script();
			$replace_tag     = array( '</body>', 'before' );
			$this->inject_in_html( $delay_script_js, $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 );

		if ( true === $this->do_process ) {
			$this_path_url = $this->get_cache_file_url( 'js' );
			breeze_unlock_process( $this_path_url );

			return $this->content;
		} else {
			return $this->original_content;
		}
		// Return the modified HTML
		// return $this->content;
	}

	// Checks against the white- and blacklists
	private function ismergeable( $tag ) {
		if ( ! empty( $this->whitelist ) ) {
			foreach ( $this->whitelist as $match ) {
				if ( strpos( $tag, $match ) !== false ) {
					return true;
				}
			}

			// no match with whitelist
			return false;
		} else {
			foreach ( $this->domove as $match ) {
				if ( strpos( $tag, $match ) !== false ) {
					// Matched something
					return false;
				}
			}

			if ( $this->movetolast( $tag ) ) {
				return false;
			}

			$return_value = true;
			foreach ( $this->dontmove as $match ) {
				if ( strpos( $tag, $match ) !== false ) {
					// Matched something
					$return_value = false;

				}
			}

			if ( false === $return_value ) {

				foreach ( $this->donotmove_exception as $match ) {
					if ( strpos( $tag, $match ) !== false ) {
						// Matched something

						return true;

					}
				}
			}

			// If we're here it's safe to merge
			return true;
		}
	}

	// Checks agains the blacklist
	private function ismovable( $tag ) {
		if ( $this->include_inline !== true || apply_filters( 'breeze_filter_js_unmovable', true ) ) {
			return false;
		}

		foreach ( $this->domove as $match ) {
			if ( strpos( $tag, $match ) !== false ) {
				// Matched something
				return true;
			}
		}

		if ( $this->movetolast( $tag ) ) {
			return true;
		}

		foreach ( $this->dontmove as $match ) {
			if ( strpos( $tag, $match ) !== false ) {
				// Matched something
				return false;
			}
		}

		// If we're here it's safe to move
		return true;
	}

	private function movetolast( $tag ) {
		$return_value = false;
		foreach ( $this->domovelast as $match ) {
			if ( strpos( $tag, $match ) !== false ) {
				// Matched, return true
				$return_value = true;
			}
		}

		if ( true === $return_value ) {

			foreach ( $this->donotmove_exception as $match ) {
				if ( strpos( $tag, $match ) !== false ) {
					// Matched something

					return false;

				}
			}
		}

		// Should be in 'first'
		return false;
	}

	/**
	 * Determines wheter a <script> $tag should be aggregated or not.
	 *
	 * We consider these as "aggregation-safe" currently:
	 * - script tags without a `type` attribute
	 * - script tags with an explicit `type` of `text/javascript`, 'text/ecmascript',
	 *   'application/javascript' or 'application/ecmascript'
	 *
	 * Everything else should return false.
	 *
	 * @param string $tag
	 *
	 * @return bool
	 *
	 * original function by https://github.com/zytzagoo/ on his AO fork, thanks Tomas!
	 */
	public function should_aggregate( $tag ) {
		preg_match( '#<(script[^>]*)>#i', $tag, $scripttag );
		if ( strpos( $scripttag[1], 'type=' ) === false ) {
			return true;
		} elseif ( preg_match( '/type=["\']?(?:text|application)\/(?:javascript|ecmascript)["\']?/i', $scripttag[1] ) ) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Search for specific exceptions.
	 * Files that should not be minified twice or included in grouping..
	 *
	 * @param $needle
	 *
	 * @return bool
	 * @since 1.1.3
	 */
	private function breeze_js_files_exceptions( $needle ) {
		$search_patterns = array(
			'cs-vendor\.[a-zA-Z0-9]*\.js',
			'cs\.[a-zA-Z0-9]*\.js',
			// 'woocommerce-currency-switcher/js/front.js',
		);

		$needle = trim( $needle );
		foreach ( $search_patterns as $pattern ) {
			preg_match( '/(' . $pattern . ')/i', $needle, $output_array );
			if ( ! empty( $output_array ) ) { // is found ?
				return true;
			}
		}

		return false;
	}

	/**
	 * Check if the inline script is in the list of delay.
	 *
	 * @param $tag
	 *
	 * @return bool
	 */
	private function ignore_from_delay( $tag ) {

		foreach ( $this->no_delay_js as $match ) {
			if ( strpos( $tag, $match ) !== false ) {
				// Matched something
				return true;
			}
		}

		return false;
	}

	/**
	 * Check if the inline script is in the list of delay.
	 *
	 * @param $tag
	 *
	 * @return bool
	 */
	private function is_inline_delay( $tag ) {
		foreach ( $this->delay_inline_js as $match ) {
			if ( strpos( $tag, $match ) !== false ) {
				// Matched something
				return true;
			}
		}

		return false;
	}

	/**
	 * This is an exception for bad written plugins.
	 * Where they use jQuery but do not load their script with jQuery dependency.
	 * We will delay these scripts. But if jQuery is not loaded the issue will still persist.
	 *
	 * @since 1.2.4
	 * @access private
	 */
	private function delay_script_loading( $scripts = array() ) {

		if ( ! is_array( $scripts ) || empty( $scripts ) ) {
			return $scripts;
		}

		$to_move_last = apply_filters(
			'breeze_delay_bag_scripts',
			array(
				'sp-scripts.min.js',
				'js/tubular.js',
			)
		);

		$return_scripts = array();
		$add_last       = array();

		foreach ( $scripts as $index => $script ) {
			$add_return = false;
			$add_later  = false;
			foreach ( $to_move_last as $script_to_delay ) {
				if ( false === strpos( $script, $script_to_delay ) ) {
					$add_return = true;
					break;
				} else {
					$add_later = true;
				}
			}

			if ( false === $add_later && true === $add_return ) {
				$return_scripts[] = $script;
			} elseif ( true === $add_later ) {
				$add_last[] = $script;
			}
		}

		if ( ! empty( $add_last ) ) {
			foreach ( $add_last as $delayed_js_script ) {
				$return_scripts[] = $delayed_js_script;
			}
		}

		return $return_scripts;
	}
}
