root = $_SERVER["DOCUMENT_ROOT"];
$this->wordpressdir = parse_url(get_site_url(), PHP_URL_PATH);
if(is_admin()) {
add_action( 'admin_menu', array($this,'admin_menu') );
add_action( 'admin_enqueue_scripts', array($this,'load_admin_jscss') );
add_action( 'wp_ajax_mmr_files', array($this,'mmr_files_callback') );
add_action( 'admin_init', array($this,'mmr_register_settings') );
register_deactivation_hook( __FILE__, array($this, 'plugin_deactivate') );
} else if($this->should_mmr()) {
$this->host = $_SERVER['HTTP_HOST'];
//php < 5.4.7 returns null if host without scheme entered
if(mb_substr($this->host, 0, 4) !== 'http') $this->host = 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 's' : '').'://' . $this->host;
$this->host = parse_url( $this->host, PHP_URL_HOST );
$this->mergecss = !get_option('mmr-nomergecss');
$this->mergejs = !get_option('mmr-nomergejs');
$this->cssmin = !get_option('mmr-nocssmin');
$this->jsmin = !get_option('mmr-nojsmin');
$this->http2push = get_option('mmr-http2push');
$this->outputbuffering = get_option('mmr-outputbuffering');
$this->gzip = get_option('mmr-gzip');
$this->ignore = array_map('trim',explode(PHP_EOL,get_option('mmr-ignore')));
add_action( 'compress_css',array($this,'compress_css_action'), 10, 1 );
add_action( 'compress_js', array($this,'compress_js_action'), 10, 1 );
if($this->outputbuffering) {
add_action( 'init', array($this,'start_buffer'), PHP_INT_MIN );
}
add_action( 'wp_print_scripts', array($this,'inspect_scripts'), PHP_INT_MAX );
add_action( 'wp_print_styles', array($this,'inspect_styles'), PHP_INT_MAX );
add_filter( 'style_loader_src', array($this,'remove_cssjs_ver'), 10, 2 );
add_filter( 'script_loader_src', array($this,'remove_cssjs_ver'), 10, 2 );
add_action( 'wp_print_footer_scripts', array($this,'inspect_stylescripts_footer'), 9.999999 ); //10 = Internal WordPress Output
}
}
private function should_mmr() {
if ( class_exists( 'Vc_Manager' ) && isset($_GET['vc_editable'] )) { // disable mrr when visual composer in use and in edit mode
return false;
}
return true;
}
public function mmr_files_callback() {
if(isset($_POST['purge']) && $_POST['purge'] == 'all') {
$this->clear_scheduled();
$this->rrmdir(MMR_CACHE_DIR);
} else if(isset($_POST['purge'])) {
$this->clear_scheduled($_POST['purge']);
array_map('unlink', glob(MMR_CACHE_DIR.'/'.$_POST['purge'].'*'));
}
$return = array('js'=>array(),'css'=>array(),'stamp'=>$_POST['stamp']);
$files = glob(MMR_CACHE_DIR.'/*.log', GLOB_BRACE);
if(count($files) > 0) {
foreach($files as $file) {
$script_path = substr($file, 0, -4);
$ext = pathinfo($script_path, PATHINFO_EXTENSION);
$scheduled = false;
if(wp_next_scheduled( 'compress_'.$ext, array($script_path) ) !== false) {
$scheduled = true;
}
$log = file_get_contents($file);
$error = false;
if(strpos($log,'COMPRESSION FAILED') !== false) {
$error = true;
}
$filename = basename($script_path);
switch($ext) {
case 'css':
$minpath = substr($script_path,0,-4).'.min.css';
break;
case 'js':
$minpath = substr($script_path,0,-3).'.min.js';
break;
}
if(file_exists($minpath)) {
$filename = basename($minpath);
}
$hash = substr($filename,0,strpos($filename,'-'));
$accessed = 'Unknown';
if( file_exists($script_path.'.accessed'))
{
$accessed = file_get_contents($script_path.'.accessed');
if(strtotime('today') <= $accessed) {
$accessed = 'Today';
} else if(strtotime('yesterday') <= $accessed) {
$accessed = 'Yesterday';
} else if(strtotime('this week') <= $accessed) {
$accessed = 'This Week';
} else if(strtotime('this month') <= $accessed) {
$accessed = 'This Month';
} else {
$accessed = date(get_option('date_format'), $accessed);
}
}
array_push($return[$ext], array('hash'=>$hash,'filename'=>$filename,'scheduled'=>$scheduled,'log'=>$log, 'error'=>$error, 'accessed'=>$accessed) );
}
}
header('Content-Type: application/json');
echo json_encode($return);
wp_die(); // this is required to terminate immediately and return a proper response
}
private function clear_scheduled($hash = null) {
if($hash != null) {
$files = glob(MMR_CACHE_DIR.'/'.$hash.'*.log', GLOB_BRACE);
} else {
$files = glob(MMR_CACHE_DIR.'/*.log', GLOB_BRACE);
}
if(count($files) > 0) {
foreach($files as $file) {
$script_path = substr($file, 0, -4);
$ext = pathinfo($script_path, PATHINFO_EXTENSION);
wp_clear_scheduled_hook( 'compress_'.$ext, array($script_path) );
}
}
}
public function plugin_deactivate() {
$this->clear_scheduled();
if(is_dir(MMR_CACHE_DIR)) {
$this->rrmdir(MMR_CACHE_DIR);
}
}
private function rrmdir($dir) {
foreach(glob($dir.'/{,.}*', GLOB_BRACE) as $file) {
if(basename($file) != '.' && basename($file) != '..') {
if(is_dir($file)) $this->rrmdir($file); else unlink($file);
}
}
rmdir($dir);
}
public function load_admin_jscss($hook) {
if ( 'settings_page_merge-minify-refresh' != $hook ) {
return;
}
wp_enqueue_style( 'merge-minify-refresh', plugins_url('admin.css', __FILE__) );
wp_enqueue_script( 'merge-minify-refresh', plugins_url('admin.js', __FILE__), array(), false, true );
}
public function admin_menu() {
add_options_page( 'Merge + Minify + Refresh Settings', 'Merge + Minify + Refresh', 'manage_options', 'merge-minify-refresh', array($this,'merge_minify_refresh_settings') );
}
public function mmr_register_settings() {
register_setting( 'mmr-group', 'mmr-nomergecss' );
register_setting( 'mmr-group', 'mmr-nomergejs' );
register_setting( 'mmr-group', 'mmr-nocssmin' );
register_setting( 'mmr-group', 'mmr-nojsmin' );
register_setting( 'mmr-group', 'mmr-http2push' );
register_setting( 'mmr-group', 'mmr-outputbuffering' );
register_setting( 'mmr-group', 'mmr-gzip' );
register_setting( 'mmr-group', 'mmr-ignore' );
}
public function merge_minify_refresh_settings() {
if ( !current_user_can( 'manage_options' ) ) {
wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
}
//echo '
';var_dump(_get_cron_array()); echo '
';
$files = glob(MMR_CACHE_DIR.'/*.{js,css}', GLOB_BRACE);
echo '
Merge + Minify + Refresh Settings
When a CSS or JS file is modified MMR will automatically re-process the files. However, when a dependancy changes these files may become stale.
Purge All
The following Javascript files have been processed:
The following CSS files have been processed:
No files have been processed
';
echo '';
}
public function remove_cssjs_ver( $src ) {
if( strpos( $src, '?ver=' ) )
$src = remove_query_arg( 'ver', $src );
return $src;
}
public function http2push_reseource( $url, $type = '' ) {
if( !$this->http2push || headers_sent() ) {
return FALSE;
}
$http_link_header = array( "Link: <{$url}>; rel=preload" );
if ( $type != '' ) {
$http_link_header[] = "as={$type}";
}
if( isset($_SERVER['HTTP_REFERER']) &&
stristr($_SERVER['HTTP_REFERER'], get_site_url()) !== FALSE ) {
$http_link_header[] = 'nopush';
}
header( implode('; ', $http_link_header), false);
}
private function host_match( $url ) {
if( empty($url) ) {
return false;
}
$url = $this->ensure_scheme($url);
$url_host = parse_url( $url, PHP_URL_HOST );
if( !$url_host || $url_host == $this->host ) {
return true;
} else {
return false;
}
}
//php < 5.4.7 parse_url returns null if host without scheme entered
private function ensure_scheme($url) {
return preg_replace("/(http(s)?:\/\/|\/\/)(.*)/i", "http$2://$3", $url);
}
private function remove_scheme($url) {
return preg_replace("/(http(s)?:\/\/|\/\/)(.*)/i", "//$3", $url);
}
public function start_buffer() {
ob_start();
}
private function fix_wp_subfolder($file_path) {
if(!is_main_site() && defined('SUBDOMAIN_INSTALL') && !SUBDOMAIN_INSTALL) { //is a subfolder site
$details = get_blog_details();
$file_path = preg_replace('|^'.$details->path.'|', '/', $file_path);
}
if($this->wordpressdir != '' && substr($file_path, 0, strlen($this->wordpressdir) + 1) != $this->wordpressdir . '/') {
$file_path = $this->wordpressdir . $file_path;
}
return $file_path;
}
public function inspect_styles() {
wp_styles(); //ensure styles is initialised
global $wp_styles;
$this->process_scripts($wp_styles, 'css');
}
public function inspect_scripts() {
wp_scripts(); //ensure scripts is initialised
global $wp_scripts;
$this->process_scripts($wp_scripts, 'js');
}
public function inspect_stylescripts_footer() {
global $wp_scripts;
$this->process_scripts($wp_scripts, 'js', true);
global $wp_styles;
$this->process_scripts($wp_styles, 'css', true);
if($this->outputbuffering) {
ob_end_flush();
}
}
/**
* process_scripts function.
*
* @access public
* @param mixed &$script_list - copy of the global wp list
* @param mixed $ext - type of script to check 'css' or 'js'
* @param bool $in_footer (default: false)
* @return void
*/
public function process_scripts(&$script_list, $ext, $in_footer = false) {
if($script_list) {
$script_line_end = "\n";
if($ext == 'js') {
$script_line_end = ";\n";
}
$scripts = clone $script_list;
$scripts->all_deps($scripts->queue);
$handles = $this->get_handles($ext, $scripts, !$in_footer);
$done = $scripts->done;
//loop through header scripts and merge + schedule wpcron
for($i=0,$l=count($handles);$i<$l;$i++) {
if(!isset($handles[$i]['handle'])) {
$done = array_merge($done, $handles[$i]['handles']);
$hash = hash('adler32', get_home_url() . implode('', $handles[$i]['handles'])); //get_home_url() prevents multisite hash collisions
$file_path = '/'.$hash.'-'.$handles[$i]['modified'].'.' . $ext;
$full_path = MMR_CACHE_DIR.$file_path;
$min_path = '/'.$hash.'-'.$handles[$i]['modified'].'.min.' . $ext;
$min_exists = file_exists(MMR_CACHE_DIR.$min_path);
if(!file_exists($full_path) && !$min_exists) {
$output = '';
$log = "";
$should_minify = true;
foreach( $handles[$i]['handles'] as $handle ) {
$log .= " - ".$handle." - ".$scripts->registered[$handle]->src;
$script_path = parse_url($this->ensure_scheme($scripts->registered[$handle]->src), PHP_URL_PATH);
$script_path = $this->fix_wp_subfolder($script_path);
if(substr($script_path, -7) == '.min.' . $ext) {
if(count($handles[$i]['handles']) > 1) { //multiple files default to not minified
$nomin_path = substr($script_path, 0, -7) . '.' . $ext;
if(is_file($this->root.$nomin_path)) {
$script_path = $nomin_path;
$log .= " - unminified version used";
}
} else {
$should_minify = false; // single file is already minified
}
}
$contents = '';
if($ext == 'js' && isset($scripts->registered[$handle]->extra['before']) && count($scripts->registered[$handle]->extra['before']) > 0) {
$contents .= implode($script_line_end,$scripts->registered[$handle]->extra['before']) . $script_line_end;
}
// Remove the BOM
$contents .= preg_replace("/^\xEF\xBB\xBF/", '', file_get_contents($this->root.$script_path)) . $script_line_end;
if(isset($scripts->registered[$handle]->extra['after']) && count($scripts->registered[$handle]->extra['after']) > 0) {
$contents .= implode($script_line_end,$scripts->registered[$handle]->extra['after']) . $script_line_end;
}
if($ext == 'css') {
//convert relative paths to absolute & ignore data: or absolute paths (starts with /)
$contents = preg_replace("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/i", "url(".dirname($script_path)."/$1)", $contents);
}
$output .= $contents;
$log .= "\n";
}
//remove existing expired files
array_map('unlink', glob(MMR_CACHE_DIR.'/'.$hash.'-*.' . $ext));
if($should_minify) {
file_put_contents($full_path , $output);
if(count($handles[$i]['handles']) > 1) {
file_put_contents($full_path.'.log', date('c')." - MERGED:\n".$log);
} else {
file_put_contents($full_path.'.log', date('c')."\n".$log);
}
wp_clear_scheduled_hook('compress_'. $ext, array($full_path) );
if($this->{$ext . 'min'}) {
wp_schedule_single_event( time(), 'compress_' . $ext, array($full_path) );
}
} else {
file_put_contents(substr($full_path, 0, -2) . 'min.' . $ext , $output);
file_put_contents($full_path.'.log', date('c')." - ORIGINAL FILE USED:\n".$log);
$min_exists = true;
}
} else {
file_put_contents($full_path.'.accessed', current_time('timestamp'));
}
if($ext == 'js') {
$data = '';
foreach( $handles[$i]['handles'] as $handle ) {
if(isset($scripts->registered[$handle]->extra['data'])) {
$data .= $scripts->registered[$handle]->extra['data'];
}
}
if($min_exists) {
$this->http2push_reseource(MMR_JS_CACHE_URL.$min_path, 'script');
wp_register_script('js-'.$this->scriptcount, MMR_JS_CACHE_URL.$min_path, array(), false, $in_footer);
} else {
$this->http2push_reseource(MMR_JS_CACHE_URL.$file_path, 'script');
wp_register_script('js-'.$this->scriptcount, MMR_JS_CACHE_URL.$file_path, array(), false, $in_footer);
}
//set any existing data that was added with wp_localize_script
if($data != '') {
$script_list->registered['js-'.$this->scriptcount]->extra['data'] = $data;
}
wp_enqueue_script('js-'.$this->scriptcount);
} else {
if($min_exists) {
$this->http2push_reseource(MMR_CSS_CACHE_URL.$min_path, 'style');
wp_register_style('css-'.$this->scriptcount, MMR_CSS_CACHE_URL.$min_path,false,false,$handles[$i]['media']);
} else {
$this->http2push_reseource(MMR_CSS_CACHE_URL.$file_path, 'style');
wp_register_style('css-'.$this->scriptcount, MMR_CSS_CACHE_URL.$file_path,false,false,$handles[$i]['media']);
}
wp_enqueue_style('css-'.$this->scriptcount);
}
$this->scriptcount++;
} else { //external
if($ext == 'js') {
wp_dequeue_script($handles[$i]['handle']); //need to do this so the order of scripts is retained
wp_enqueue_script($handles[$i]['handle']);
} else {
wp_dequeue_style($handles[$i]['handle']); //need to do this so the order of scripts is retained
wp_enqueue_style($handles[$i]['handle']);
}
}
}
$script_list->done = $done;
}
}
/**
* get_handles function.
* Returns a list of the handles in $ourList in the order and grouping that mmr will need to merge them
* @access private
* @param mixed $type - type of script to check 'css' or 'js'
* @param mixed &$ourList - copy of the global wp list
* @param bool $ignoreFooterScripts (default: false) - whether to ignore scripts marked for the footer
* @return array() - MMR script handles list
*/
private function get_handles($type, &$ourList, $ignoreFooterScripts = false)
{
switch($type)
{
case 'js':
$ext = 'js';
$dontMerge = !$this->mergejs;
$srcFilter = 'script_loader_src';
$checkMedia = false;
break;
case 'css':
$ext = 'css';
$dontMerge = !$this->mergecss;
$srcFilter = 'style_loader_src';
$checkMedia = true;
break;
default:
return array();
}
$handles = array();
$currentHandle = -1;
foreach( $ourList->to_do as $handle ) {
if(apply_filters( $srcFilter, $ourList->registered[$handle]->src, $handle) !== false) { //is valid src
if($ignoreFooterScripts)
{
$is_footer = isset($ourList->registered[$handle]->extra['group']);
if($is_footer)
{
//ignore this script, so go on to the next one
continue;
}
}
$script_path = parse_url($this->ensure_scheme($ourList->registered[$handle]->src), PHP_URL_PATH);
$script_path = $this->fix_wp_subfolder($script_path);
$extension = pathinfo($script_path, PATHINFO_EXTENSION);
if($extension == $ext && $this->host_match($ourList->registered[$handle]->src) && !in_array($ourList->registered[$handle]->src, $this->ignore) && !isset($ourList->registered[$handle]->extra["conditional"])) { //is a local script
$mediaMatches = true;
if($checkMedia)
{
$media = isset($ourList->registered[$handle]->args) ? $ourList->registered[$handle]->args : 'all';
$mediaMatches = $currentHandle != -1 && isset($handles[$currentHandle]['media']) && $handles[$currentHandle]['media'] == $media;
}
if($dontMerge || $currentHandle == -1 || isset($handles[$currentHandle]['handle']) || !$mediaMatches) {
if($checkMedia)
{
array_push($handles, array('modified'=>0,'handles'=>array(),'media'=>$media));
}
else
{
array_push($handles, array('modified'=>0,'handles'=>array()));
}
$currentHandle++;
}
$modified = 0;
if(is_file($this->root.$script_path)) {
$modified = filemtime($this->root.$script_path);
}
array_push($handles[$currentHandle]['handles'], $handle);
if($modified > $handles[$currentHandle]['modified']) {
$handles[$currentHandle]['modified'] = $modified;
}
} else { //external script or not able to be processed
array_push($handles, array('handle'=>$handle));
$currentHandle++;
}
}
}
return $handles;
}
public function compress_css_action($full_path) {
if(is_file($full_path)) {
require_once('Minify/src/Minify.php');
require_once('Minify/src/CSS.php');
require_once('Minify/ConverterInterface.php');
require_once('Minify/Converter.php');
require_once('Minify/src/Exception.php');
file_put_contents($full_path.'.log', date('c')." - COMPRESSING CSS\n",FILE_APPEND);
$file_size_before = filesize($full_path);
$minifier = new MatthiasMullie\Minify\CSS($full_path);
$min_path = str_replace('.css','.min.css',$full_path);
$minifier->minify($min_path);
if($this->gzip_file($min_path)) {
file_put_contents($full_path.'.log', date('c')." - GZIPPED - $min_path.gz\n",FILE_APPEND);
}
$file_size_after = filesize($min_path);
file_put_contents($full_path.'.log', date('c')." - COMPRESSION COMPLETE - ".$this->human_filesize($file_size_before-$file_size_after)." saved\n",FILE_APPEND);
}
}
public function compress_js_action($full_path) {
if(is_file($full_path)) {
$file_size_before = filesize($full_path);
if(function_exists('exec') && exec('command -v java >/dev/null && echo "yes" || echo "no"') == 'yes' && exec('java -version 2>&1',$jvoutput) && preg_match("/version\ \"(1\.[7-9]{1}+|[7-9]|[0-9]{2,})/", $jvoutput[0])) {
file_put_contents($full_path.'.log', date('c')." - COMPRESSING JS WITH CLOSURE\n",FILE_APPEND);
$cmd = 'java -jar \''.WP_PLUGIN_DIR.'/merge-minify-refresh/closure-compiler.jar\' --language_in ECMASCRIPT5 --warning_level QUIET --js \''.$full_path.'\' --js_output_file \''.$full_path.'.tmp\'';
exec($cmd . ' 2>&1', $output);
if(count($output) == 0) {
$min_path = str_replace('.js','.min.js',$full_path);
rename($full_path.'.tmp',$min_path);
if($this->gzip_file($min_path)) {
file_put_contents($full_path.'.log', date('c')." - GZIPPED - $min_path.gz\n",FILE_APPEND);
}
$file_size_after = filesize($min_path);
file_put_contents($full_path.'.log', date('c')." - COMPRESSION COMPLETE - ".$this->human_filesize($file_size_before-$file_size_after)." saved\n",FILE_APPEND);
} else {
ob_start();
var_dump($output);
$error=ob_get_contents();
ob_end_clean();
file_put_contents($full_path.'.log', date('c')." - COMPRESSION FAILED\n".$error,FILE_APPEND);
unlink($full_path.'.tmp');
}
} else {
require_once('Minify/src/Minify.php');
require_once('Minify/src/JS.php');
file_put_contents($full_path.'.log', date('c')." - COMPRESSING WITH MINIFY (PHP exec not available or java not found)\n",FILE_APPEND);
$minifier = new MatthiasMullie\Minify\JS($full_path);
$min_path = str_replace('.js','.min.js',$full_path);
$minifier->minify($min_path);
if($this->gzip_file($min_path)) {
file_put_contents($full_path.'.log', date('c')." - GZIPPED - $min_path.gz\n",FILE_APPEND);
}
$file_size_after = filesize($min_path);
file_put_contents($full_path.'.log', date('c')." - COMPRESSION COMPLETE - ".$this->human_filesize($file_size_before-$file_size_after)." saved\n",FILE_APPEND);
}
}
}
//thanks to http://php.net/manual/en/function.filesize.php#106569
private function human_filesize($bytes, $decimals = 2) {
$sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}
//thanks to Marcus Svensson
private function gzip_file($path) {
$gzipped = false;
if ($this->gzip && function_exists('exec') && exec('command -V gzip >/dev/null && echo "yes" || echo "no"') == 'yes') {
exec("gzip -9 < '$path' > '$path.gz'", $output, $return);
if($return == 0) {//gzip worked
$gzipped = true;
}
}
return $gzipped;
}
}
$mergeminifyrefresh = new MergeMinifyRefresh();