Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1028 lines (877 sloc) 41.4 KB
<?php
/*------------------------------------------------------------------------------
** File: class.magic-min.php
** Class: MagicMin
** Description: Javascript and CSS minification/merging class to simplify movement from development to production versions of files
** Dependencies: jShrink (https://github.com/tedious/JShrink)
** Version: 3.0.4
** Created: 01-Jun-2013
** Updated: 21-Mar-2015
** Author: Bennett Stone
** Homepage: www.phpdevtips.com
**------------------------------------------------------------------------------
** COPYRIGHT (c) 2015 BENNETT STONE
**
** The source code included in this package is free software; you can
** redistribute it and/or modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation. This license can be
** read at:
**
** http://www.opensource.org/licenses/gpl-license.php
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
**------------------------------------------------------------------------------
**
** Usage:
** $min = new Minifier();
** <script src="<?php $min->minify( '[source filename]', '[output filename (optional)]', '[version (optional)]' ); ?>"></script>
** <link rel="stylesheet" media="all" href="<?php $min->minify( 'css/copy-from.css', 'css/test-prod.minified.css', '1.8' ); ?>">
**
** Usage example for merge and minify:
** $min->merge( 'output filename and location', 'directory', 'type (js or css)', array( of items to exclude ) );
** $min->merge( 'js/one-file-merged.js', 'js', 'js', array( 'js/inline-edit.js', 'js/autogrow.js' ) );
**
** Normalized output example using merge and minify:
** <script src="<?php $min->merge( 'js/production.min.js', 'js', 'js' ); ?>"></script>
**
** Adding gzip, base64 image encoding, or returning rather than echo:
** $vars = array(
** 'echo' => false,
** 'encode' => true,
** 'timer' => true,
** 'gzip' => true
** );
** $minified = new Minifier( $vars );
**
** Using JShrink for js minification as opposed to google closure (default set to google closure)
** $vars = array(
** 'closure' => false,
** 'gzip' => true,
** 'encode' => true
** );
** $minified = new Minifier( $vars );
**
** NEW as of 3.0.0: Output sha1 hashed time-based filenames for new files to break caching without version numbers
** $vars = array(
** 'closure' => false,
** 'gzip' => true,
** 'encode' => true,
** 'remove_comments' => true,
** 'hashed_filenames' => true,
** 'output_log' => true
** );
** $minified = new Minifier( $vars );
**
**------------------------------------------------------------------------------ */
class Minifier {
public $content;
public $output_file;
public $extension;
private $type;
//Max image size for inclusion
const IMAGE_MAX_SIZE = 5;
//For script execution time (src: http://bit.ly/18O3VWw)
private $mtime;
//Sum of output messages
private static $messages = array();
//array of settings to add-to/adjust
private $settings = array();
//List of available config keys that can be set via init
private $config_keys = array(
'echo' => true, //Return or echo the values
'encode' => false, //base64 images from CSS and include as part of the file?
'timer' => true, //Ouput script execution time
'gzip' => false, //Output as php with gzip?
'closure' => true, //Use google closure (utilizes cURL)
'remove_comments' => true, //Remove comments,
'hashed_filenames' => false, //Generate hashbased filenames to break caches,
'output_log' => false, //Output logs automatically at end of file output,
'force_rebuild' => false, //Brute force rebuild of minified assets- USE SPARINGLY!
);
/**
* Construct function
* @access public
* @param array $vars
* @return mixed
*/
public function __construct( $vars = array() )
{
$this->mtime = microtime( true );
foreach( $this->config_keys as $key => $default )
{
if( isset( $vars[$key] ) )
{
self::$messages[]['Minifier Log'] = $key .': '. $vars[$key];
$this->settings[$key] = $vars[$key];
}
else
{
self::$messages[]['Minifier Log'] = $key .': '. $default;
$this->settings[$key] = $default;
}
}
} //end __construct()
/**
* Private function to strip directory names from TOC output
* Used for make_min()
*
* @access private
* @param array $input
* @return array $output
*/
private function strip_directory( $input )
{
return basename( $input );
}
/**
* Private function used to automatically add URI schemes to
* assets in order for them to have their contents retrieved
* ONLY WORKS to add schemes to URI's prefixed with "//" to
* handle protocol irrelevant loading when using either http or https
*
* For example, the following file WOULD work:
* //ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
*
* While, the following would NOT work:
* ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
*
* @access private
* @param string $uri
* @param string $scheme
* @return string
*/
private function add_uri_scheme( $uri, $scheme = 'http:' )
{
if( substr( $uri, 0, 2 ) == '//' )
{
return parse_url( $uri, PHP_URL_SCHEME ) === null ? $scheme . $uri : $uri;
}
else
{
return $uri;
}
}
/**
* Private function to determine if files are local or remote
* Used for merge_images() and minify() to determine if filemtime can be used
*
* @access private
* @param string $file
* @return bool
*/
private function remote_file( $file )
{
//It is a remote file
if( substr( $file, 0, 4 ) == 'http' )
{
return true;
}
//Local file
else
{
return false;
}
}
/**
* Internal function to output everything as a gmdate
* Prevents issues with servertime vs. PHP time settings by
* turning all timestamps into gmt
* @access private
* @param int time
* @return int
*/
private function gmstamp( $time = '' )
{
$time = !empty( $time ) ? $time : time();
return gmdate( 'U', $time );
}
/**
* Function to create or retrieve stored data for cachefiles
* that are generated using hashed filenames
* Cachedfiles store:
** orig filename
** hashed filename
** timestamp
** regenerated timestamp (if the file has been regenerated)
*
* Both minify() and merge() run processes to determine filenames and timestamps
* through this function to unify the placement of hashed_filename checks, and files
* are created/checked using this function
*
* @access private
* @param string $source_file- always the same- name of NONhashed minified file
* @param string $reference_file - mainly used to determine accurate file extensions
* @param bool $regen (wipe to recreate contents of cachefile, defaults to false)
* @return object (for less-array-ey bracket retrieval)
*/
private function minified_filedata( $source_file, $reference_files = array(), $regen = false )
{
if( $this->settings['hashed_filenames'] && is_dir( dirname( $source_file ) ) && is_writable( dirname( $source_file ) ) )
{
//Reference filename to create
$cache_refname = sha1( $source_file ) .'.txt';
$checkfile = dirname( $source_file ) . DIRECTORY_SEPARATOR . $cache_refname;
if( file_exists( $checkfile ) && !$regen )
{
$data = file_get_contents( $checkfile );
return (object)unserialize( $data );
}
else
{
$new_ext = strtolower( pathinfo( $source_file, PATHINFO_EXTENSION ) );
if( $new_ext == 'php' )
{
$source_file = rtrim( strtolower( $source_file ), '.php' );
$new_ext = strtolower( pathinfo( $source_file, PATHINFO_EXTENSION ) ) .'.php';
}
$time = $this->gmstamp();
$data = array(
'files' => $reference_files,
'references' => dirname( $source_file ) . DIRECTORY_SEPARATOR. sha1( $time ) . '.'. $new_ext,
'filemtime' => $time,
'generated' => $time
);
//If we need to regen, just wipe the contents of the file
if( $regen === true )
{
$data['regenerated'] = $this->gmstamp();
}
$handle = fopen( $checkfile, 'w' ) or error_log( 'Cannot open file: '.$checkfile );
fwrite( $handle, serialize( $data ) );
fclose( $handle );
return (object)$data;
}
}
else
{
if( file_exists( $source_file ) )
{
$data = array(
'files' => $reference_files,
'references' => $source_file,
'filemtime' => $this->gmstamp( filemtime( $source_file ) ),
'generated' => $this->gmstamp( filemtime( $source_file ) )
);
}
else
{
$time = $this->gmstamp();
$data = array(
'files' => $reference_files,
'references' => $source_file,
'filemtime' => $time,
'generated' => $time
);
}
return (object)$data;
}
}
/**
* Function to seek out and replace image references within CSS with base64_encoded data streams
* Used in minify_contents function IF global for $this->encode
* This function will retrieve the contents of local OR remote images, and is based on
* Matthias Mullie <minify@mullie.eu>'s function, "importFiles" from the JavaScript and CSS minifier
* http://www.phpclasses.org/package/7519-PHP-Optimize-JavaScript-and-CSS-files.html
*
* @access private
* @param string $source_file (used for location)
* @param string $contents
* @return string $updated_style
*/
private function merge_images( $source_file, $contents )
{
$this->directory = dirname( $source_file ) . DIRECTORY_SEPARATOR;
if( preg_match_all( '/url\((["\']?)((?!["\']?data:).*?\.(gif|png|jpg|jpeg))\\1\)/i', $contents, $this->matches, PREG_SET_ORDER ) )
{
$this->find = array();
$this->replace = array();
foreach( $this->matches as $this->graphic )
{
$this->extension = pathinfo( $this->graphic[2], PATHINFO_EXTENSION );
$this->image_file = '';
//See if the file is remote or local
if( $this->remote_file( $this->graphic[2] ) )
{
//It's remote, and CURL is pretty fast
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $this->graphic[2] );
curl_setopt( $ch, CURLOPT_NOBODY, 1 );
curl_setopt( $ch, CURLOPT_FAILONERROR, 1 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
//And it WAS remote, and it DOES exist
if( curl_exec( $ch ) !== FALSE )
{
//Get the image file
$cd = curl_init( $this->graphic[2] );
curl_setopt( $cd, CURLOPT_HEADER, 0 );
curl_setopt( $cd, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $cd, CURLOPT_BINARYTRANSFER, 1 );
$this->image_file = curl_exec( $cd );
//Get the remote filesize
$this->filesize = curl_getinfo( $cd, CURLINFO_CONTENT_LENGTH_DOWNLOAD );
curl_close( $cd );
if( $this->filesize <= Minifier::IMAGE_MAX_SIZE * 1024 )
{
//Assign the find and replace
$this->find[] = $this->graphic[0];
$this->replace[] = 'url(data:'.$this->extension.';base64,'.base64_encode( $this->image_file ).')';
}
} //End file exists
curl_close( $ch );
} //End remote file
elseif( file_exists( $this->directory . $this->graphic[2] ) )
{
//File DOES exist locally, get the contents
//Check the filesize
$this->filesize = filesize( $this->directory . $this->graphic[2] );
if( $this->filesize <= Minifier::IMAGE_MAX_SIZE * 1024 )
{
//File is within the filesize requirements so add it
$this->image_file = file_get_contents( $this->directory . $this->graphic[2] );
//Assign the find and replace
$this->find[] = $this->graphic[0];
$this->replace[] = 'url(data:'.$this->extension.';base64,'.base64_encode( $this->image_file ).')';
}
} //End local file
}
//Log the number of replacements to the console
self::$messages[]['Minifier Log: merge_images'] = count( $this->replace ) .' files base64_encoded into ' . $source_file;
//Find and replace all the images with the base64 data
$this->updated_style = str_replace( $this->find, $this->replace, $contents );
return $this->updated_style;
} //End if( regex for images)
else
{
//No images found in the sheet, just return the contents
return $contents;
}
} //end merge_images()
/**
* Private function to handle minification of file contents
* Supports CSS and JS files
*
* @access private
* @param string $src_file
* @param bool $run_minification (default true)
* @return string $content
*/
private function minify_contents( $src_file, $run_minification = true )
{
$this->source = @file_get_contents( $src_file );
//Log the error and continue if we can't get the file contents
if( !$this->source )
{
self::$messages[]['Minifier ERROR'] = 'Unable to retrieve the contents of '. $src_file . '. Skipping at '. __LINE__ .' in '. basename( __FILE__ );
//This will cause potential js errors, but allow the script to continue processing while notifying the user via console
$this->source = '';
}
$this->type = strtolower( pathinfo( $src_file, PATHINFO_EXTENSION ) );
$this->output = '';
/**
* If the filename indicates that the contents are already minified, we'll just return the contents
* If the switch is flipped (useful for loading things such as jquery via google cdn)
*/
if( preg_match( '/\.min\./i', $src_file ) || $run_minification === false )
{
return $this->source;
}
else
{
if( !empty( $this->type ) && $this->type == 'css' )
{
$this->content = $this->source;
//If the param is set to merge images into the css before minifying...
if( $this->settings['encode'] )
{
$this->content = $this->merge_images( $src_file, $this->content );
}
/* remove comments */
if( $this->settings['remove_comments'] )
{
$this->content = preg_replace( '!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $this->content );
}
/* remove tabs, spaces, newlines, etc. */
$this->content = preg_replace( '/(\s\s+|\t|\n)/', ' ', $this->content );
/* remove other spaces before/after ; */
$this->content = preg_replace( array('(( )+{)','({( )+)'), '{', $this->content );
$this->content = preg_replace( array('(( )+})','(}( )+)','(;( )*})'), '}', $this->content );
$this->content = preg_replace( array('(;( )+)','(( )+;)'), ';', $this->content );
/* remove spaces after : */
$this->content = preg_replace( '(:( )*)', ':', $this->content );
} //end $this->type == 'css'
if( !empty( $this->type ) && $this->type == 'js' )
{
$this->content = $this->source;
/**
* Migrated preg_replace and str_replace custom minification to use google closure API
* OR jShrink on 15-Jun-2013 due to js minification irregularities with most regex's:
* https://github.com/tedious/JShrink
* https://developers.google.com/closure/compiler/
* https://developers.google.com/closure/compiler/docs/api-ref
* Accomodates lack of local file for JShrink by getting contents from github
* and writing to a local file for the class (just in case)
* If bool is passed for 'closure' => true during class initiation, cURL request processes
*/
if( $this->settings['closure'] )
{
//Build the data array
$data = array(
'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
'output_format' => 'json',
'output_info' => 'compiled_code',
'js_code' => urlencode( $this->content )
);
//Compile it into a post compatible format
$fields_string = '';
foreach( $data as $key => $value )
{
$fields_string .= $key . '=' . $value . '&';
}
rtrim( $fields_string, '&' );
//Initiate and execute the curl request
$h = curl_init();
curl_setopt( $h, CURLOPT_URL, 'http://closure-compiler.appspot.com/compile' );
curl_setopt( $h, CURLOPT_POST, true );
curl_setopt( $h, CURLOPT_POSTFIELDS, $fields_string );
curl_setopt( $h, CURLOPT_HEADER, false );
curl_setopt( $h, CURLOPT_RETURNTRANSFER, 1 );
$result = curl_exec( $h );
$result_raw = json_decode( $result, true );
//If we've made too many requests, or passed bad data, our js will be broken
if( isset( $result_raw['serverErrors'] ) && !empty( $result_raw['serverErrors'] ) )
{
$e_code = $result_raw['serverErrors'][0]['code'];
$e_message = $result_raw['serverErrors'][0]['error'];
self::$messages[]['Minifier ERROR'] = $e_code . ': '. $e_message . ' File: '. basename( $src_file ) . '. Returning unminified contents.';
}
else
{
$this->content = $result_raw['compiledCode'];
}
//close connection
curl_close( $h );
} //end if( $this->settings['closure'] )
else
{
//Not using google closure, default to JShrink but make sure the file exists
if( !file_exists( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'jShrink.php' ) )
{
self::$messages[]['Minifier Log'] = 'jShrink does not exist locally. Retrieving...';
$this->handle = fopen( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'jShrink.php', 'w' );
$this->jshrink = file_get_contents( 'https://raw.github.com/tedivm/JShrink/master/src/JShrink/Minifier.php' );
fwrite( $this->handle, $this->jshrink );
fclose( $this->handle );
}
//Include jShrink
require_once( dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'jShrink.php' );
//Minify the javascript
$this->content = JShrink\Minifier::minify( $this->content, array( 'flaggedComments' => $this->settings['remove_comments'] ) );
} //end if( !$this->settings['closure'] )
} //end $this->type == 'js'
//Add to the output and return it
$this->output .= $this->content;
return $this->output;
}
} //end minify_contents()
/**
* Private function to create file with, or without minified contents
*
* @access private
* @param string path to, and name of source file
* @param string path to, and name of new minified file
* @param bool $do_minify (default is true) (used for remote files)
* @return string new filename/location (same as path to variable)
*/
private function make_min( $src_file, $new_file, $do_minify = true )
{
self::$messages[]['Minifier note'] = 'Writing new file to '. dirname( $new_file );
//Make sure the directory exists and is writable
if( !is_dir( dirname( $new_file ) ) || !is_writeable( dirname( $new_file ) ) )
{
self::$messages[]['Minifier ERROR'] = dirname( $new_file ) . ' is not writable. Cannot create minified file.';
trigger_error( dirname( $new_file ) . ' is not writable. Cannot create minified file.' );
return false;
}
//Output gzip data as needed, but default to none
//Lengthy line usage is intentional to provide cleanly formatted fwrite contents
$this->prequel = '';
if( $this->settings['gzip'] )
{
$this->prequel = '<?php' . PHP_EOL;
$this->prequel .= 'if( extension_loaded( "zlib" ) )' . PHP_EOL;
$this->prequel .= '{' . PHP_EOL;
$this->prequel .= ' ob_start( "ob_gzhandler" );' . PHP_EOL;
$this->prequel .= '}' . PHP_EOL;
$this->prequel .= 'else' . PHP_EOL;
$this->prequel .= '{' . PHP_EOL;
$this->prequel .= ' ob_start();' . PHP_EOL;
$this->prequel .= '}' . PHP_EOL;
//Get the actual file type for header
$this->extension = strtolower( pathinfo( $new_file, PATHINFO_EXTENSION ) );
/**
* If gzip is enabled, the .php extension is added automatically
* and must be accounted for to prevent files from being recreated
*/
if( $this->extension != 'php' )
{
$new_file = $new_file. '.php';
}
//Close out the php row so we can continue with normal content
$offset = 60 * 60 * 24 * 31;
$this->prequel .= 'header( \'Content-Encoding: gzip\' );' . PHP_EOL;
$this->prequel .= 'header( \'Cache-Control: max-age=' . $offset.'\' );' . PHP_EOL;
$this->prequel .= 'header( \'Expires: ' . gmdate( "D, d M Y H:i:s", time() + $offset ) . ' GMT\' );' . PHP_EOL;
$this->prequel .= 'header( \'Last-Modified: ' . gmdate( "D, d M Y H:i:s", filemtime( __FILE__ ) ) . ' GMT\' );' . PHP_EOL;
//Add the header content type output for correct rendering
if( $this->extension == 'css' || ( strpos( $new_file, '.css' ) !== false ) )
{
$this->prequel .= 'header( \'Content-type: text/css; charset: UTF-8\' );' . PHP_EOL;
}
if( $this->extension == 'js' || ( strpos( $new_file, '.js' ) !== false ) )
{
$this->prequel .= 'header( \'Content-type: application/javascript; charset: UTF-8\' );' . PHP_EOL;
}
//Close out the php tag that gets written to the file
$this->prequel .= '?>' . PHP_EOL;
} //End if( $this->gzip )
//Single files
if( !is_array( $src_file ) )
{
$this->filetag = '/**' . PHP_EOL;
$this->filetag .= ' * Filename: '. basename( $src_file ) . PHP_EOL;
$this->filetag .= ' * Generated by MagicMin '.date('Y-m-d'). ' at '. date('h:i:s A') . PHP_EOL;
$this->filetag .= ' */' . PHP_EOL;
$this->content = $this->prequel . $this->filetag . $this->minify_contents( $src_file, $do_minify );
}
else
{
//Strip the directory names from the $src_file array for security
$filenames = array_map( array( $this, 'strip_directory' ), $src_file );
//Make a temporary var to store the data and write a TOC
$this->compiled = '/**' . PHP_EOL;
$this->compiled .= ' * Table of contents: ' . PHP_EOL;
$this->compiled .= ' * '. implode( PHP_EOL. ' * ', $filenames ) . PHP_EOL;
$this->compiled .= ' * Generated by MagicMin: ' . date( 'Y-m-d h:i:s' ). PHP_EOL;
$this->compiled .= ' */' . PHP_EOL;
//Loop through an array of files to write to the new file
foreach( $src_file as $this->new_file )
{
/**
* It's relatively safe to assume that remote files being retrieved
* already have minified contents (ie. Google CDN hosted jquery)
* so prevent re-minification, but default to $do_minify = true;
*/
$do_minify = true;
if( $this->remote_file( $this->new_file ) )
{
//Remote files should not have compressed content
$do_minify = false;
}
$this->compiled .= $this->minify_contents( $this->new_file, $do_minify );
}
//Write the temporary contents to the full contents
$this->content = trim( $this->prequel . $this->compiled );
//Remove the temporary data
unset( $this->compiled );
} //End $src_file is_array
//If the file already exists, open it and empty it
if( file_exists( $new_file ) && is_writeable( $new_file ) )
{
$f = fopen( $new_file, 'w' );
fclose( $f );
}
//Create the new file
$this->handle = fopen( $new_file, 'w' );
//Log any error messages from the new file creation
if( !$this->handle )
{
self::$messages[]['Minifier ERROR'] = 'Unable to open file: '.$new_file;
trigger_error( 'Unable to open file: '.$new_file );
return false;
}
else
{
//Write the minified contents to it
fwrite( $this->handle, $this->content );
fclose( $this->handle );
//Make sure this filemtime syncs up with everything else magicmin does
touch( $new_file, $this->gmstamp() );
//Log to the console
self::$messages[]['Minifier Log: New file'] = 'Successfully created '. $new_file;
//Return filename and location
return $new_file;
}
} //end make_min()
/**
* Get contents of JS or CSS script, create minified version
* Idea and partial adaptation from: http://davidwalsh.name/php-cache-function
* Dependent on "make_min" function
*
* Example usage:
* <script src="<?php $min->minify( 'js/script.dev.js', 'js/script.js', '1.3' ); ?>"></script>
* <link rel="stylesheet" href="<?php $min->minify( 'css/style.css', 'css/styles.min.css', '1.8' ); ?>" />
* $min->minify( 'source file', 'output file', 'version' );
*
* @access public
* @param string $src_file (filename and location for original file)
* @param string $file (filename and location for output file. Empty defaults to [filename].min.[extension])
* @param string $version
* @return string $output_file (includes provided location)
*/
public function minify( $src_file, $file = '', $version = '' )
{
//Handle double slash prefixed remote filenames, as well as checking relative filenames
$src_file = $this->add_uri_scheme( $src_file );
//Since the $file (output) filename is optional, if empty, just add .min.[ext]
if( empty( $file ) )
{
//Get the pathinfo
$ext = pathinfo( $src_file );
//Create a new filename
$file = $ext['dirname'] . DIRECTORY_SEPARATOR . $ext['filename'] . '.min.' . $ext['extension'];
}
//If we have gzip enabled, we must account for the .php extension
if( $this->settings['gzip'] && ( strtolower( pathinfo( $file, PATHINFO_EXTENSION ) ) != '.php' ) )
{
$file .= '.php';
}
$minfile = $this->minified_filedata( $src_file, array( $file ) );
$file = $minfile->references;
//The source file is remote, and we can't check for an updated version anyway
if( $this->remote_file( $src_file ) && file_exists( $file ) )
{
$this->output_file = $file;
}
//The local version doesn't exist, but we don't need to minify
elseif( $this->remote_file( $src_file ) && !file_exists( $file ) )
{
$this->output_file = $this->make_min( $src_file, $file, false );
//Add the filename to the output log
self::$messages[]['Minifier Log: minify'] = 'Retrieving contents of '.$src_file .' to add to '.$file;
}
//The file already exists and doesn't need to be recreated
elseif( ( $this->settings['force_rebuild'] === true ) || ( file_exists( $file ) && file_exists( $src_file ) ) && ( $this->gmstamp( filemtime( $src_file ) ) < $minfile->filemtime ) )
{
//No change, so the output is the same as the input
$this->output_file = $file;
}
//The file exists, but the development version is newer
elseif( ( $this->settings['force_rebuild'] === true ) || ( file_exists( $file ) && file_exists( $src_file ) ) && ( $this->gmstamp( filemtime( $src_file ) ) > $minfile->filemtime ) )
{
//Remove the file so we can do a clean recreate
chmod( $file, 0777 );
unlink( $file );
//Regen cacheref
$minfile = $this->minified_filedata( $src_file, array( $file ), true );
$file = $minfile->references;
//Make the cached version
$this->output_file = $this->make_min( $src_file, $file );
//Add to the console.log output
self::$messages[]['Minifier Log: minify'] = 'Made new version of '.$src_file.' into '.$file;
}
//The minified file doesn't exist, make one
else
{
//Make the cached version
$this->output_file = $this->make_min( $src_file, $file );
//Add to the console.log output if desired
self::$messages[]['Minifier Log: minify'] = 'Made new version of '.$src_file.' into '.$file;
}
//Add the ? params if they exist
if( !empty( $version ) )
{
$this->output_file .= '?v='. $version;
}
//Return the output filename or echo
if( $this->settings['echo'] )
{
echo $this->output_file;
}
else
{
return $this->output_file;
}
} //end minify()
/**
* Get the contents of js or css files, minify, and merge into a single file
*
* Example usage:
* <?php
* require_once( 'class.magic-min.php' );
* $min = new Minifier();
* ?>
* <script src="<?php $min->merge( '[output folder]/[output filename.js]', '[directory]', '[type(js or css)]', array( '[filetoignore]', '[filetoignore]' ) ); ?>"></script>
* <script src="<?php $min->merge( 'js/one-file-merged.js', 'js', 'js', array( 'js/inline-edit.js', 'js/autogrow.js' ) ); ?>"></script>
*
* @access public
* @param string $output_filename
* @param string $directory to loop through
* @param mixed $list_or_type (css, js, selective - default is js)
**** $list_or_type will also accept "selective" array which overrides glob and only includes specified files
**** $list_or_type array passed files are included in order, and no other files will be included
**** files must all be the same type in order to prevent eronious output contents (js and css do not mix)
* @param array $exclude files to exclude
* @param array $order to specify output order
* @return string new filenae
*/
public function merge( $output_filename, $directory, $list_or_type = 'js', $exclude = array(), $order = array() )
{
/**
* Added selective inclusion to override glob and exclusion 13-Jun-2013 ala Ray Beriau
* This assumes the user has passed an array of filenames, in order rather than a file type
* By doing so, we'll set the directory to indicate no contents, and priorize directly into $order
*/
if( is_array( $list_or_type ) && !empty( $list_or_type ) )
{
//Direct the directory to be an empty array
$this->directory = array();
//Utilize the $order variable
$order = $list_or_type;
}
else
{
//Open the directory for looping and seek out files of appropriate type
$this->directory = glob( $directory .'/*.'.$list_or_type );
}
/**
* Reassign the $output_filename if gzip is enabled as we must account for the .php
* extension in order to prevent the file from being recreated
*/
if( $this->settings['gzip'] && ( strtolower( pathinfo( $output_filename, PATHINFO_EXTENSION ) ) != '.php' ) )
{
$output_filename .= '.php';
}
$reference_files = ( is_array( $list_or_type ) && !empty( $list_or_type ) ) ? $list_or_type : array( $output_filename );
$minfile = $this->minified_filedata( $output_filename, $reference_files );
$minified_name = $minfile->references;
$minified_filelist = !empty( $minfile->files ) ? $minfile->files : array();
//Create a bool to determine if a new file needs to be created
$this->create_new = false;
//Allow brute force rebuild from construct param
if( $this->settings['force_rebuild'] === true )
{
self::$messages[]['Minifier Log: Force Rebuild'] = 'Force rebuild set to true';
$this->create_new = true;
}
//Start the array of files to add to the cache
$this->compilation = array();
//Determine if a specific order is needed, if so remove only those files from glob seek
if( !empty( $order ) )
{
self::$messages[]['Minifier Log: Merge order'] = 'Order specified with '. count( $order ) .' files';
foreach( $order as $this->file )
{
//Handle protocol irrelevant URIs such as '//ajax.google...'
$this->file = $this->add_uri_scheme( $this->file );
//Check each file for modification greater than the output file if it exists
if( file_exists( $minified_name ) && ( $this->file != $output_filename ) && ( !$this->remote_file( $this->file ) ) && ( $this->gmstamp( filemtime( $this->file ) ) > $minfile->filemtime ) || !in_array( $this->file, $minified_filelist ) )
{
self::$messages[]['Minifier Log: New File Flagged'] = 'Flagged for update by '. $this->file;
$this->create_new = true;
}
//Add the specified files to the beginning of the use array passed to $this->make_min
$this->compilation[] = $this->file;
}
//Now remove the same files from the glob directory
$this->directory = array_diff( $this->directory, $this->compilation );
} //End !empty( $order )
//Loop through the directory grabbing files along the way
foreach( $this->directory as $this->file )
{
//Handle protocol irrelevant URIs such as '//ajax.google...'
$this->file = $this->add_uri_scheme( $this->file );
//Make sure we didn't want to exclude this file before adding it
if( !in_array( $this->file, $exclude ) && ( $this->file != $minified_name ) )
{
//Check each file for modification greater than the output file if it exists
if( file_exists( $minified_name ) && ( !$this->remote_file( $this->file ) ) && ( $this->gmstamp( filemtime( $this->file ) ) > $minfile->filemtime ) || !in_array( $this->file, $minified_filelist ) )
{
self::$messages[]['Minifier Log: New File Flagged'] = 'Flagged for update by '. $this->file;
$this->create_new = true;
}
$this->compilation[] = $this->file;
}
} //End foreach( $this->directory )
//Check to see that we have the same number of files passed to the function as were stored
if( count( $minified_filelist ) != count( $this->compilation ) )
{
$this->create_new = true;
}
//Only recreate the file as needed
if( file_exists( $minified_name ) && $this->create_new )
{
self::$messages[]['Minifier Log: Creating new'] = $minified_name .' exists but flagged for new';
//Remove the file so we can do a clean recreate
chmod( $minified_name, 0777 );
unlink( $minified_name );
//Regen cacheref
$minfile = $this->minified_filedata( $output_filename, $this->compilation, true );
$output_filename = $minfile->references;
//Group and minify the contents
$this->compressed = $this->make_min( $this->compilation, $output_filename );
}
elseif( !file_exists( $minified_name ) )
{
//Regen cacheref
$minfile = $this->minified_filedata( $output_filename, $this->compilation, true );
$output_filename = $minfile->references;
//Group and minify the contents
$this->compressed = $this->make_min( $this->compilation, $output_filename );
}
else
{
$this->compressed = $minified_name;
}
//Echo or return
if( $this->settings['echo'] )
{
echo $this->compressed;
}
else
{
return $this->compressed;
}
} //end merge()
/**
* Output any return data to the javascript console/source of page
* Usage (assuming minifier is initiated as $minifier):
* <?php $minifier->logs(); ?>
*
* @param none
* @return string
*/
public function logs()
{
//Add the timer the console.log output if desired
if( $this->settings['timer'] )
{
self::$messages[]['Minifier Log: timer'] = 'MagicMin processed and loaded in '. ( microtime( true ) - $this->mtime ) .' seconds';
}
if( !empty( self::$messages ) )
{
echo PHP_EOL . '<script>' . PHP_EOL;
foreach( self::$messages as $this->data )
{
foreach( $this->data as $this->type => $this->output )
{
echo 'console.log("'.$this->type .' : '. $this->output.'");' . PHP_EOL;
}
}
echo '</script>' . PHP_EOL;
} //end !empty( $this-messages )
} //end logs()
/**
* Allow logs to be automatically output at script completion
* Dependent on 'output_log' configuration variable set to true
*
*/
public function __destruct()
{
if( $this->settings['output_log'] === true )
{
$this->logs();
}
} //end __destruct()
} //End class Minifier
You can’t perform that action at this time.