@website www.terresquall.com @version 3.1.0 @dated 27/06/2018 @notes - Fixed a bug with the 'ignore_async_and_defer_tags' option. They behaved opposite of what they should have. - Fixed a bug causing '; if(is_array($head)) $head[count($head)-1] .= $new_style; else $head .= $new_style; } } return $source; } // This is just a common chunk of code used in process_css_options() and process_script_options(). // Returns true if the checked condition is in wrapped conditionals. private static function _is_in_wrapped_conditionals(&$tag,&$wrapped_conditionals,&$source) { // Does $m belong inside IE conditional tags? If so, wrap it around the conditional tag it belongs to. foreach($wrapped_conditionals[2] as $k => $w) { // If we find that our tag is inside a conditional statement, wrap it up in the condition before moving it up. if(strpos($w,$tag) !== false) { $wrapped_conditionals[2][$k] = self::replace($tag,'',$wrapped_conditionals[2][$k]); $tag = $wrapped_conditionals[1][$k] . $tag . $wrapped_conditionals[3][$k]; // Remove this conditional from the source if there is nothing else inside it. // Otherwise reform the match array. if(trim($wrapped_conditionals[2][$k])) $wrapped_conditionals[0][$k] = $wrapped_conditionals[1][$k] . $wrapped_conditionals[2][$k] . $wrapped_conditionals[3][$k]; else { $source = self::replace($wrapped_conditionals[3][$k],'',self::replace($wrapped_conditionals[1][$k],'',$source)); unset($wrapped_conditionals[0][$k],$wrapped_conditionals[1][$k],$wrapped_conditionals[2][$k],$wrapped_conditionals[3][$k]); } // In case there is an error, here is an error for a backtrace. if($source === false) trigger_error('There seems to be a problem with HTMLMinifier.'); return true; } } return false; } // Extracts the numbering inside a comment from remove_html_comments(). private static function _extract_comment_tag($comment_statement) { if(preg_match('@^" . PHP_EOL; // Adds this to the end of the document. } } else trigger_error('It seems like there is some error in your HTML conditional: ' . $if_cond[0],E_USER_ERROR); } } } else { $attrb = self::get_tag_attributes($scripts[1][$k]); // If this tag is not Javascript, let's ignore it and move on. if(isset($attrb['type']) && !preg_match('@(text|application)/(x-)?javascript@i',$attrb['type'])) continue; // Wrap scripts in conditionals with respective conditionals. $is_wrapped = self::_is_in_wrapped_conditionals($scripts[0][$k],$wrapped_conditionals,$source); if($is_wrapped){ $scripts[1][$k] = substr($scripts[0][$k],0,strpos($scripts[0][$k],$scripts[1][$k])+strlen($scripts[1][$k])); $scripts[3][$k] = substr($scripts[0][$k],strpos($scripts[0][$k],$scripts[3][$k])); } // If this is an inline script, try and clean its comments. if($options['clean_js_comments'] && trim($scripts[2][$k])) { $scripts[2][$k] = self::remove_comments($scripts[2][$k],'js',$ignore_cdata_comments); $scripts[0][$k] = $scripts[1][$k] . $scripts[2][$k] . $scripts[3][$k]; } if($options['shift_script_tags_to_bottom']) { // Don't move this tag if it has the async or defer attribute. if( $ignore_async_defer && (isset($attrb['async']) || isset($attrb['defer'])) ) continue; // Figure out if this is a piece of script we should combine or just append at the end. if(!isset($attrb['id']) && !$is_wrapped && trim($scripts[2][$k]) && $combine_javascript) { // We are moving the script to the end of the page. $source = self::replace($s,'',$source); $script_combine .= $scripts[2][$k] . PHP_EOL; continue; } // We are just going to append this piece of script at the end. $source = self::replace($s,'',$source); $appendix .= $scripts[0][$k] . PHP_EOL; } else { $source = self::replace($s,$scripts[0][$k],$source); } } } // Grab the body tag(s) in the document. $body_tags = self::get_tags('body',$source); $body_count = count($body_tags); $body = null; if($body_count > 1) { if($options['merge_multiple_body_tags']) { $body = ''; foreach($body_tags[2] as $b) { $source = self::replace($b,'',$source); $body .= $b; } } else { $body = array(); foreach($body_tags[2] as $b) { $source = self::replace($b,'',$source); array_push($body,$b); } } } elseif($body_count === 1) { $body = $body_tags[2][0]; $source = self::replace($body,'',$source); } if($options['shift_script_tags_to_bottom']) { // Stuff $appendix and $script_combine into the end of the body tag. if(isset($script_combine)) $appendix .= ''; if($body) { if(is_array($body)) $body[$body_count-1] .= $appendix; else $body .= $appendix; } else { // We should try and append the appendix to a HTML tag first, before adding it directly to source. $source .= $appendix; } } // Put
back. if($body) { $empty_bodies = self::get_tags('body',$source); if(count($empty_bodies[0]) > 0) { if(is_array($body)) { foreach($empty_bodies[0] as $i => $m) { if(empty($body[$i])) $source = self::replace($m,'',$source); // Remove this tag if there are no contents for it. else $source = self::replace($m,$empty_bodies[1][$i] . $body[$i] . $empty_bodies[3][$i],$source); } } else { $source = self::replace($empty_bodies[0][0],$empty_bodies[1][0] . $body . $empty_bodies[3][0],$source); unset($empty_bodies[0][0],$empty_bodies[1][0],$empty_bodies[2][0],$empty_bodies[3][0]); // Remove these matches as they have been used. foreach($empty_bodies[0] as $m) $source = self::replace($m,'',$source); } } else trigger_error('Trying to put back <body> tag contents, but we cannot any empty <body> tag anymore!',E_USER_ERROR); } return $source; } // Shortcut for using substring replace. private static function replace($search,$replacement,$subject) { $idx = strpos($subject,$search); if($idx !== false) return substr_replace($subject,$replacement,$idx,strlen($search)); return $idx; } // Finds tags of a certain name and returns an array of matches. private static function get_tags($tagName,$source,$disallowNesting = true) { switch(gettype($tagName)) { case 'string': $preg_tag = $tagName; break; case 'array': foreach($tagName as $k => $t) $tagName[$k] = preg_quote($t); $preg_tag = implode('|',$tagName); } $src_end = strlen($source)-1; // Calculate last index of $source. $result = array(array(),array(),array(),array()); // Simulate a preg_match() array; 0: full result, 1: opening tag, 2: contents, 3: closing tag. $source_pointer = 0; // For tracking how far down the source we have been searching. $regex = sprintf('@<(%s)(?:\\s[\\s\\S]*?)?/?>@i',$preg_tag); $k = 0; // Counter for number of matches in loop below. // Keep finding tags until we reach the end, or have no more matches. // We are not using preg_match_all because we don't want to match the same content multiple times. // e.g. If there is a