Repository: slav123/CodeIgniter-minify Branch: master Commit: 76c03757bd04 Files: 33 Total size: 325.2 KB Directory structure: gitextract_uq89ngc_/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── application/ │ ├── config/ │ │ ├── autoload.php │ │ ├── config.php │ │ ├── constants.php │ │ ├── mimes.php │ │ ├── minify.php │ │ └── routes.php │ ├── controllers/ │ │ ├── index.html │ │ └── welcome.php │ ├── errors/ │ │ ├── error_404.php │ │ ├── error_db.php │ │ ├── error_general.php │ │ ├── error_php.php │ │ └── index.html │ ├── libraries/ │ │ ├── Minify.php │ │ └── minify/ │ │ ├── JSMin.php │ │ ├── JSMinPlus.php │ │ ├── cssmin-v3.0.1.php │ │ └── cssminify.php │ └── views/ │ ├── index.html │ └── welcome_message.php ├── assets/ │ ├── css/ │ │ ├── browser-specific.css │ │ └── style.css │ └── js/ │ ├── helpers.js │ └── jqModal.js ├── composer.json ├── index.php ├── phpunit.xml └── tests/ ├── MinifyTest.php └── bootstrap.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ ================================================ FILE: .travis.yml ================================================ language: php php: - 5.6 - 7.0 - 7.1 - 7.2 before_script: - travis_retry composer install --dev ================================================ FILE: LICENSE ================================================ Copyright (c) 2015 Slawomir Jasinski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # CodeIgniter - minify [![Build Status](https://travis-ci.org/slav123/CodeIgniter-minify.svg?branch=master)](https://travis-ci.org/slav123/CodeIgniter-minify) Simple CodeIgniter library to compress **CSS and JavaScript** files on the fly. Library is based on few other scripts like or to minify CSS and it uses [Google Closure compiler](https://developers.google.com/closure/compiler/) to compress JavaScript ## Installation Just put `Minify.php` file in libraries path, and create `minify.php` config file on config directory. ## Using the library #### Configure the library: All directories needs to be writable. Next you can set your own values for config file. ```php // enable/disable library (default value: 'TRUE') $config['enabled'] = TRUE; // output path where the compiled files will be stored (default value: 'assets') $config['assets_dir'] = 'assets'; // optional - path where the compiled css files will be stored (default value: '' - for backward compatibility) $config['assets_dir_css'] = ''; // optional - path where the compiled js files will be stored (default value: '' - for backward compatibility) $config['assets_dir_js'] = ''; // optional - handy when your assets are in a different domain than main website (default value: '') $config['base_url'] = ''; // where to look for css files (default value: 'assets/css') $config['css_dir'] = 'assets/css'; // where to look for js files (default value: 'assets/js') $config['js_dir'] = 'assets/js'; // default file name for css (default value: 'style.css') $config['css_file'] = 'styles.css'; // default file name for js (default value: 'scripts.js') $config['js_file'] = 'scripts.js'; // default tag for css (default value: '') $config['css_tag'] = ''; // default tag for js (default value: '') $config['js_tag'] = ''; // use html tags on output and return as a string (default value: 'TRUE') // if html_tags === FALSE - array with links to assets is returned $config['html_tags'] = TRUE; // use automatic file names (default value: 'FALSE') $config['auto_names'] = FALSE; // use to enable versioning your assets (default value: 'FALSE') $config['versioning'] = FALSE; // automatically deploy when there are any changes in files (default value: 'TRUE') $config['deploy_on_change'] = TRUE; // compress files or not (default value: 'TRUE') $config['compress'] = TRUE; // compression engine setting (default values: 'minify' and 'closurecompiler') $config['compression_engine'] = array( 'css' => 'minify', // minify || cssmin 'js' => 'closurecompiler' // closurecompiler || jsmin || jsminplus ); // when you use closurecompiler as compression engine you can choose compression level (default value: 'SIMPLE_OPTIMIZATIONS') // avaliable options: "WHITESPACE_ONLY", "SIMPLE_OPTIMIZATIONS" or "ADVANCED_OPTIMIZATIONS" $config['closurecompiler']['compilation_level'] = 'SIMPLE_OPTIMIZATIONS'; ``` #### Available engines * CSS - `minify` or `cssmin` - both of them are local, just try out which one is better for you, * JS - `closurecompiler` makes API call to external server, it's slower then regular inline engine, but it's super efficient with compression, `jsmin` and `jsminplus` are local #### Run the library In the controller: ```php // load the library $this->load->library('minify'); // or load and assign custom config (will override values from config file) $this->load->library('minify', $config); // by default library's functionality is enabled, but in some cases you would like to return // assets without compilation and compression - when debugging or in development environment // in that case you can use config variable to disable it $config['enabled'] = FALSE; // or $this->minify->enabled = FALSE; ``` In controller or view: ```php // set css files - you can use array or string with commas // when using this method, you replaces previously added files $this->minify->css(array('reset.css', 'style.css', 'tinybox.css')); $this->minify->css('reset.css, style.css, tinybox.css'); // add css files - you can use array or string with commas // when using this method, you're adding new files to previous ones $this->minify->add_css(array('reset.css'))->add_css('style.css, tinybox.css'); // set js files - you can use array or string with commas // when using this method, you replaces previously added files $this->minify->js(array('html5.js', 'main.js')); $this->minify->js('html5.js, main.js'); // set js files - you can use array or string with commas // when using this method, you're adding new files to previous ones $this->minify->add_js(array('html5.js'))->add_js('main.js'); // with methods: css(), js(), add_css() and add_js() // you can pass group name for given files as second parameter // default group name is "default" $this->minify->js(array('html5.js', 'main.js'), 'extra'); $this->minify->add_css('style.css, tinybox.css', 'another'); // deploy css // bool argument for rebuild css - false means skip rebuilding (default value: TRUE) echo $this->minify->deploy_css(TRUE); //Output: '' // deploy js // bool argument for rebuild js - false means skip rebuilding (default value: FALSE) echo $this->minify->deploy_js(); //Output: ''. // you can use automatic file name for particular deploy when you have $config['auto_names'] set to FALSE // to do so, you must set file name to 'auto' during deploy echo $this->minify->deploy_css(TRUE, 'auto'); echo $this->minify->deploy_js(TRUE, 'auto'); //Output: '' //Output: ''. // you can deploy only particular group of files echo $this->minify->deploy_css(TRUE, NULL, 'another'); echo $this->minify->deploy_js(TRUE, 'auto', 'extra'); //Output: '' //Output: ''. // you can enable versioning your your assets via config variable `$config['versioning']` or manually $this->minify->versioning = TRUE; echo $this->minify->deploy_js(); //Output: ''. ``` ## Changelog 01 Mar 2021 * comments fixing * config checks only when deploy 26 Jul 2019 * added option to manually change version number for assets (thanks [screamingjungle](https://github.com/screamingjungle)) 11 Feb 2019 * fixed an issue where not all config variables from the constructor were taken into account 02 Feb 2019 * new config variable to allow of use a custom domain/subdomain for your assets: `$config['base_url']` (default to '') 25 Jul 2018 * new config variable to disable default behavior - deploy when any file is changed: `$config['change_on_deploy']` (default to TRUE) 24 Jul 2018 * handle errors for closurecompiler engine 26 Feb 2018 * new config variable to determine if we want to return html tags (as string result) or only links to the assets (as array): `$config['html_tags']` (default to TRUE) * we can now specify what HTML tag will be used for CSS and JS through `$config['css_tag']` and `$config['js_tag']` 17 Jun 2017 * new config variable to enable versioning assets `$config['versioning']` (default to FALSE) * new config variable to enable/disable library - useful for debugging: `$config['enabled']` (default to TRUE) 29 Dec 2016 * introduce option to save compiled css and js files in different folders - new config variables: `$config['assets_dir_css']` and `$config['assets_dir_js']`. 29 Apr 2015 * allow using automatic file name for particular deploy when you have `$config['auto_names']` set to `FALSE` * documentation update 20 Apr 2015 * Closure compiler configuration extracted to config file 22 Mar 2015 * method chaining support * new methods: `add_css()` and `add_js()` - gives ability for adding files to existing files arrays * added support to run library with custom array config, assigned as second parameter (during loading) `$this->load->library('minify', $config);` * added support for *groups* in files arrays - as second (optional) parameter in methods: `css()`, `js()`, `add_css()` and `add_js()` (i.e. `$this->minify->js(array('script.js'), 'extra');` - default group name is *default*) * added support for strings as first parameter in methods: `css()`, `js()`, `add_css()` and `add_js()` (i.e. `$this->minify->js('first.js, second.js');`) * added support for automatic files names: `$config['auto_names'] = TRUE;` * external compression classes moved to *minify* folder * unit tests for new features 10 Feb 2015 * Unit testing 09 Feb 2015 * 2 new engines to compress JS files * documentation update 13 Oct 2014 * changed way of generating JS file 14 July 2014 * small bug fixes in JS compression 4 July 2014 * sample JavaScript files to see how it works * detection of empty JS file causes force refresh 23 May 2014 * you can chose your compression engine library in config file (CSS only) * speed optimisations * force CSS rewrite using $this->minify->deploy_css(TRUE); 11 Mar 2014 * completely rewrite CSS parser - uses cssmin compress CSS, * detects file modification time no longer force rewrites, * example usage now included withing app ## Any questions? Report theme here: ================================================ FILE: application/config/autoload.php ================================================ 'application/mac-binhex40', 'cpt' => 'application/mac-compactpro', 'csv' => array('text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream', 'application/vnd.ms-excel', 'application/x-csv', 'text/x-csv', 'text/csv', 'application/csv', 'application/excel', 'application/vnd.msexcel'), 'bin' => 'application/macbinary', 'dms' => 'application/octet-stream', 'lha' => 'application/octet-stream', 'lzh' => 'application/octet-stream', 'exe' => array('application/octet-stream', 'application/x-msdownload'), 'class' => 'application/octet-stream', 'psd' => 'application/x-photoshop', 'so' => 'application/octet-stream', 'sea' => 'application/octet-stream', 'dll' => 'application/octet-stream', 'oda' => 'application/oda', 'pdf' => array('application/pdf', 'application/x-download'), 'ai' => 'application/postscript', 'eps' => 'application/postscript', 'ps' => 'application/postscript', 'smi' => 'application/smil', 'smil' => 'application/smil', 'mif' => 'application/vnd.mif', 'xls' => array('application/excel', 'application/vnd.ms-excel', 'application/msexcel'), 'ppt' => array('application/powerpoint', 'application/vnd.ms-powerpoint'), 'wbxml' => 'application/wbxml', 'wmlc' => 'application/wmlc', 'dcr' => 'application/x-director', 'dir' => 'application/x-director', 'dxr' => 'application/x-director', 'dvi' => 'application/x-dvi', 'gtar' => 'application/x-gtar', 'gz' => 'application/x-gzip', 'php' => 'application/x-httpd-php', 'php4' => 'application/x-httpd-php', 'php3' => 'application/x-httpd-php', 'phtml' => 'application/x-httpd-php', 'phps' => 'application/x-httpd-php-source', 'js' => 'application/x-javascript', 'swf' => 'application/x-shockwave-flash', 'sit' => 'application/x-stuffit', 'tar' => 'application/x-tar', 'tgz' => array('application/x-tar', 'application/x-gzip-compressed'), 'xhtml' => 'application/xhtml+xml', 'xht' => 'application/xhtml+xml', 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed'), 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mpga' => 'audio/mpeg', 'mp2' => 'audio/mpeg', 'mp3' => array('audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'), 'aif' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'ram' => 'audio/x-pn-realaudio', 'rm' => 'audio/x-pn-realaudio', 'rpm' => 'audio/x-pn-realaudio-plugin', 'ra' => 'audio/x-realaudio', 'rv' => 'video/vnd.rn-realvideo', 'wav' => array('audio/x-wav', 'audio/wave', 'audio/wav'), 'bmp' => array('image/bmp', 'image/x-windows-bmp'), 'gif' => 'image/gif', 'jpeg' => array('image/jpeg', 'image/pjpeg'), 'jpg' => array('image/jpeg', 'image/pjpeg'), 'jpe' => array('image/jpeg', 'image/pjpeg'), 'png' => array('image/png', 'image/x-png'), 'tiff' => 'image/tiff', 'tif' => 'image/tiff', 'css' => 'text/css', 'html' => 'text/html', 'htm' => 'text/html', 'shtml' => 'text/html', 'txt' => 'text/plain', 'text' => 'text/plain', 'log' => array('text/plain', 'text/x-log'), 'rtx' => 'text/richtext', 'rtf' => 'text/rtf', 'xml' => 'text/xml', 'xsl' => 'text/xml', 'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpe' => 'video/mpeg', 'qt' => 'video/quicktime', 'mov' => 'video/quicktime', 'avi' => 'video/x-msvideo', 'movie' => 'video/x-sgi-movie', 'doc' => 'application/msword', 'docx' => array('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'), 'xlsx' => array('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip'), 'word' => array('application/msword', 'application/octet-stream'), 'xl' => 'application/excel', 'eml' => 'message/rfc822', 'json' => array('application/json', 'text/json') ); /* End of file mimes.php */ /* Location: ./application/config/mimes.php */ ================================================ FILE: application/config/minify.php ================================================ * @copyright 2015 All Rights Reserved SpiderSoft * @license Copyright 2015 All Rights Reserved SpiderSoft * @link http://www.spidersoft.com.au/projects/codeigniter-minify/ */ defined('BASEPATH') OR exit('No direct script access allowed'); /** * Minify config file * * @category PHP * @package Controller * @author Slawomir Jasinski * @copyright 2015 All Rights Reserved SpiderSoft * @license Copyright 2015 All Rights Reserved SpiderSoft * @link http://www.spidersoft.com.au/projects/codeigniter-minify/ */ // enable/disable library (default value: 'TRUE') // when enabled === FALSE library return assets without compilation and compression // usefull when debugging or in development environment $config['enabled'] = TRUE; // output path where the compiled files will be stored (default value: 'assets') $config['assets_dir'] = 'assets'; // optional - path where the compiled css files will be stored (default value: '' - for backward compatibility) $config['assets_dir_css'] = ''; // optional - path where the compiled js files will be stored (default value: '' - for backward compatibility) $config['assets_dir_js'] = ''; // optional - handy when your assets are in a different domain than main website (default value: '') $config['base_url'] = ''; // where to look for css files (default value: 'assets/css') $config['css_dir'] = 'assets/css'; // where to look for js files (default value: 'assets/js') $config['js_dir'] = 'assets/js'; // default file name for css (default value: 'style.css') $config['css_file'] = 'styles.css'; // default file name for js (default value: 'scripts.js') $config['js_file'] = 'scripts.js'; // default tag for css (default value: '') $config['css_tag'] = ''; // default tag for js (default value: '') $config['js_tag'] = ''; // use html tags on output and return as a string (default value: 'TRUE') // if html_tags === FALSE - array with links to assets is returned $config['html_tags'] = TRUE; // use automatic file names (default value: 'FALSE') $config['auto_names'] = FALSE; // use to enable versioning your assets (default value: 'FALSE') $config['versioning'] = FALSE; // override version md5 with a number (default value: 'NULL') $config['version_number'] = NULL; // automatically deploy when there are any changes in files (default value: 'TRUE') $config['deploy_on_change'] = TRUE; // compress files or not (default value: 'TRUE') $config['compress'] = TRUE; // compression engine setting (default values: 'minify' and 'closurecompiler') $config['compression_engine'] = array( 'css' => 'cssmin', // minify || cssmin 'js' => 'closurecompiler' // closurecompiler || jsmin || jsminplus ); // when you use closurecompiler as compression engine you can choose compression level (default value: 'SIMPLE_OPTIMIZATIONS') // avaliable options: "WHITESPACE_ONLY", "SIMPLE_OPTIMIZATIONS" or "ADVANCED_OPTIMIZATIONS" $config['closurecompiler']['compilation_level'] = 'SIMPLE_OPTIMIZATIONS'; // End of file minify.php // Location: ./application/config/minify.php ================================================ FILE: application/config/routes.php ================================================ 403 Forbidden

Directory access is forbidden.

================================================ FILE: application/controllers/welcome.php ================================================ * @see http://codeigniter.com/user_guide/general/urls.html */ public function index() { $this->load->library('minify'); $this->load->helper('url'); $this->load->view('welcome_message'); } } /* End of file welcome.php */ /* Location: ./application/controllers/welcome.php */ ================================================ FILE: application/errors/error_404.php ================================================ 404 Page Not Found

================================================ FILE: application/errors/error_db.php ================================================ Database Error

================================================ FILE: application/errors/error_general.php ================================================ Error

================================================ FILE: application/errors/error_php.php ================================================

A PHP Error was encountered

Severity:

Message:

Filename:

Line Number:

================================================ FILE: application/errors/index.html ================================================ 403 Forbidden

Directory access is forbidden.

================================================ FILE: application/libraries/Minify.php ================================================ * @copyright 2015 All Rights Reserved SpiderSoft * @license Copyright 2015 All Rights Reserved SpiderSoft * @link Location: http://github.com/slav123/CodeIgniter-Minify */ defined('BASEPATH') or exit('No direct script access allowed'); /** * the Minify LibraryClass * * @category PHP * @package Controller * @author Slawomir Jasinski * @copyright 2016 All Rights Reserved SpiderSoft * @license Copyright 2015 All Rights Reserved SpiderSoft * @link http://www.spidersoft.com.au */ class Minify { /** * CodeIgniter global. * * @var object */ protected $ci; /** * Css files array. * * @var array */ protected $css_array = array(); /** * Js files array. * * @var array */ protected $js_array = array(); /** * Enable/disable. * * @var bool */ public $enabled = TRUE; /** * Assets dir. * * @var string */ public $assets_dir = 'assets'; /** * Assets dir for css (optional). * * @var string */ public $assets_dir_css = ''; /** * Assets dir for js (optional). * * @var string */ public $assets_dir_js = ''; /** * Base URL. * * @var string */ public $base_url = ''; /** * Css dir. * * @var string */ public $css_dir = 'assets/css'; /** * Js dir. * * @var string */ public $js_dir = 'assets/js'; /** * Output css file name. * * @var string */ public $css_file = 'styles.css'; /** * Output js file name. * * @var string */ public $js_file = 'scripts.js'; /** * Output css tag template. * * @var string */ public $css_tag = ''; /** * Output js tag template. * * @var string */ public $js_tag = ''; /** * Use html tags on output. * * @var string */ public $html_tags = TRUE; /** * Automatic file names. * * @var bool */ public $auto_names = FALSE; /** * Automatic deploy on change. * * @var bool */ public $deploy_on_change = TRUE; /** * File versioning. * * @var bool */ public $versioning = FALSE; /** * File version number override. * * @var string */ public $version_number = NULL; /** * Compress files or not. * * @var bool */ public $compress = TRUE; /** * Compression engines. * * @var array */ public $compression_engine = array('css' => 'minify', 'js' => 'closurecompiler'); /** * Closurecompiler settings. * * @var array */ public $closurecompiler = array('compilation_level' => 'SIMPLE_OPTIMIZATIONS'); /** * Css file name with path. * * @var string */ private $_css_file = ''; /** * Js file name with path. * * @var string */ private $_js_file = ''; /** * Last modification. * * @var array */ private $_lmod = array('css' => 0, 'js' => 0); /** * Constructor * * @param array $config Config array */ public function __construct($config = array()) { $this->ci = get_instance(); $this->ci->load->config('minify', TRUE, TRUE); // user specified settings from config file $this->enabled = $this->ci->config->item('enabled', 'minify') ?: $this->enabled; $this->assets_dir = $this->ci->config->item('assets_dir', 'minify') ?: $this->assets_dir; $this->assets_dir_css = $this->ci->config->item('assets_dir_css', 'minify') ?: $this->assets_dir_css; $this->assets_dir_js = $this->ci->config->item('assets_dir_js', 'minify') ?: $this->assets_dir_js; $this->base_url = $this->ci->config->item('base_url', 'minify') ?: $this->base_url; $this->css_dir = $this->ci->config->item('css_dir', 'minify') ?: $this->css_dir; $this->js_dir = $this->ci->config->item('js_dir', 'minify') ?: $this->js_dir; $this->css_file = $this->ci->config->item('css_file', 'minify') ?: $this->css_file; $this->js_file = $this->ci->config->item('js_file', 'minify') ?: $this->js_file; $this->css_tag = $this->ci->config->item('css_tag', 'minify') ?: $this->css_tag; $this->js_tag = $this->ci->config->item('js_tag', 'minify') ?: $this->js_tag; $this->html_tags = $this->ci->config->item('html_tags', 'minify') ?: $this->html_tags; $this->auto_names = $this->ci->config->item('auto_names', 'minify') ?: $this->auto_names; $this->deploy_on_change = $this->ci->config->item('deploy_on_change', 'minify') ?: $this->deploy_on_change; $this->versioning = $this->ci->config->item('versioning', 'minify') ?: $this->versioning; $this->version_number = $this->ci->config->item('version_number', 'minify') ?: $this->version_number; $this->compress = $this->ci->config->item('compress', 'minify') ?: $this->compress; $this->compression_engine = $this->ci->config->item('compression_engine', 'minify') ?: $this->compression_engine; $this->closurecompiler = $this->ci->config->item('closurecompiler', 'minify') ?: $this->closurecompiler; if (count($config) > 0) { // custom config array foreach ($config as $key => $val) { if (isset($this->$key)) { $this->$key = $val; } } } // save default names for later use/reset $this->css_file_default = $this->css_file; $this->js_file_default = $this->js_file; log_message('debug', "Minify Class Initialized"); } /** * Declare css files list * * @param mixed $css File or files names * @param bool $group Set group for files * * @return Minify */ public function css($css, $group = 'default') { if (is_array($css)) { $this->css_array[$group] = $css; } else { $this->css_array[$group] = array_map('trim', explode(',', $css)); } return $this; } /** * Declare js files list * * @param mixed $js File or files names * @param bool $group Set group for files * * @return Minify */ public function js($js, $group = 'default') { if (is_array($js)) { $this->js_array[$group] = $js; } else { $this->js_array[$group] = array_map('trim', explode(',', $js)); } return $this; } /** * Declare css files list * * @param mixed $css File or files names * @param bool $group Set group for files * * @return Minify */ public function add_css($css, $group = 'default') { if ( ! isset($this->css_array[$group])) { $this->css_array[$group] = array(); } if (is_array($css)) { $this->css_array[$group] = array_unique(array_merge($this->css_array[$group], $css)); } else { $this->css_array[$group] = array_unique(array_merge($this->css_array[$group], array_map('trim', explode(',', $css)))); } return $this; } /** * Declare js files list * * @param mixed $js File or files names * @param bool $group Set group for files * * @return Minify */ public function add_js($js, $group = 'default') { if ( ! isset($this->js_array[$group])) { $this->js_array[$group] = array(); } if (is_array($js)) { $this->js_array[$group] = array_unique(array_merge($this->js_array[$group], $js)); } else { $this->js_array[$group] = array_unique(array_merge($this->js_array[$group], array_map('trim', explode(',', $js)))); } return $this; } /** * Deploy and minify CSS * * @param bool $force Force to rewrite file * @param null $file_name File name to create * @param null $group Group name * * @return string|array */ public function deploy_css($force = TRUE, $file_name = NULL, $group = NULL) { // perform checks $this->_config_checks('css'); $return = array(); if (is_null($file_name)) { $file_name = $this->css_file_default; } if (is_null($group)) { foreach ($this->css_array as $group_name => $group_array) { $return = array_merge($return, $this->_deploy_css($force, $file_name, $group_name)); } } else { $return = array_merge($return, $this->_deploy_css($force, $file_name, $group)); } return $this->_output($return, 'css'); } /** * Deploy and minify js * * @param bool $force Force rewriting js file * @param null $file_name File name * @param null $group Group name * * @return string|array */ public function deploy_js($force = FALSE, $file_name = NULL, $group = NULL) { // perform checks $this->_config_checks('js'); $return = array(); if (is_null($file_name)) { $file_name = $this->js_file_default; } if (is_null($group)) { foreach ($this->js_array as $group_name => $group_array) { $return = array_merge($return, $this->_deploy_js($force, $file_name, $group_name)); } } else { $return = array_merge($return, $this->_deploy_js($force, $file_name, $group)); } return $this->_output($return, 'js'); } /** * Build and minify CSS * * @param bool $force Force to rewrite file * @param null $file_name File name to create * @param null $group Group name * * @return array */ private function _deploy_css($force = TRUE, $file_name = NULL, $group = NULL) { if ($this->enabled === FALSE) { return $this->_simple_output('css', $group); } if ($this->auto_names or $file_name === 'auto') { $file_name = md5(serialize($this->css_array[$group])) . '.css'; } else { $file_name = ($group === 'default') ? $file_name : $group . '_' . $file_name; } $this->_set('css_file', $file_name); $this->_scan_files('css', $force, $group); if ($this->versioning) { $this->_css_file = $this->_css_file . '?v=' . $this->_version_number($this->_css_file); } return [$this->_css_file]; } /** * Build and minify js * * @param bool $force Force rewriting js file * @param null $file_name File name * @param null $group Group name * * @return array */ private function _deploy_js($force = FALSE, $file_name = NULL, $group = NULL) { if ($this->enabled === FALSE) { return $this->_simple_output('js', $group); } if ($this->auto_names or $file_name === 'auto') { $file_name = md5(serialize($this->js_array[$group])) . '.js'; } else { $file_name = ($group === 'default') ? $file_name : $group . '_' . $file_name; } $this->_set('js_file', $file_name); $this->_scan_files('js', $force, $group); if ($this->versioning) { $this->_js_file = $this->_js_file . '?v=' . $this->_version_number($this->_js_file); } return [$this->_js_file]; } /** * construct js_file and css_file * * @param string $name File type * @param string $value File name * * @return void */ private function _set($name, $value) { switch ($name) { case 'js_file': if ($this->compress) { if ( ! preg_match("/\.min\.js$/", $value)) { $value = str_replace('.js', '.min.js', $value); } $this->js_file = $value; } // determine if we have special dir for js specified $assets_dir = empty($this->assets_dir_js) ? $this->assets_dir : $this->assets_dir_js; $this->_js_file = $assets_dir . '/' . $value; if ( ! file_exists($this->_js_file) && ! touch($this->_js_file)) { throw new Exception('Can not create file ' . $this->_js_file); } else { $this->_lmod['js'] = filemtime($this->_js_file); } break; case 'css_file': if ($this->compress) { if ( ! preg_match("/\.min\.css$/", $value)) { $value = str_replace('.css', '.min.css', $value); } $this->css_file = $value; } // determine if we have special dir for css specified $assets_dir = empty($this->assets_dir_css) ? $this->assets_dir : $this->assets_dir_css; $this->_css_file = $assets_dir . '/' . $value; if ( ! file_exists($this->_css_file) && ! touch($this->_css_file)) { throw new Exception('Can not create file ' . $this->_css_file); } else { $this->_lmod['css'] = filemtime($this->_css_file); } break; } } /** * scan CSS directory and look for changes * * @param string $type Type (css | js) * @param bool $force Rewrite no mather what * @param string $group Group name */ private function _scan_files($type, $force, $group) { switch ($type) { case 'css': $files_array = $this->css_array[$group]; $directory = $this->css_dir; $out_file = $this->_css_file; break; case 'js': $files_array = $this->js_array[$group]; $directory = $this->js_dir; $out_file = $this->_js_file; } // if multiple files if (is_array($files_array)) { $compile = FALSE; foreach ($files_array as $file) { $filename = $directory . '/' . $file; if (file_exists($filename)) { if ($this->deploy_on_change && filemtime($filename) > $this->_lmod[$type]) { $compile = TRUE; } } else { throw new Exception('File ' . $filename . ' is missing'); } } // check if this is init build if (file_exists($out_file) && filesize($out_file) === 0) { $force = TRUE; } if ($compile or $force) { $this->_concat_files($files_array, $directory, $out_file); } } } /** * output files with proper template for html_tags * or without as array * * @param array $files Files array * @param string $type Type (css | js) * * @return string|array */ private function _output($files, $type) { switch ($type) { case 'css': $template = $this->css_tag; break; case 'js': $template = $this->js_tag; } $output = array(); foreach ($files as $file) { $output[] = $this->html_tags ? sprintf($template, $this->_base_url($file)) : $this->_base_url($file); } if ( ! empty($output)) { return $this->html_tags ? implode(PHP_EOL, $output) : $output; } return $this->html_tags ? '' : array(); } /** * simple output files - no compress, no compile (files in = files out) * good for debugging or development env * * @param string $type Type (css | js) * @param string $group Group name * * @return array */ private function _simple_output($type, $group) { switch ($type) { case 'css': $files = $this->css_array[$group]; $directory = $this->css_dir; $template = $this->css_tag; break; case 'js': $files = $this->js_array[$group]; $directory = $this->js_dir; $template = $this->js_tag; } $output = array(); foreach ($files as $file) { $filename = $directory . '/' . $file; if ($this->versioning) { $filename .= '?v=' . $this->_version_number($filename); } $output[] = $filename; } return $output; } /** * add merge files * * @param string $file_array Input file array * @param string $directory Directory * @param string $out_file Output file * * @return void */ private function _concat_files($file_array, $directory, $out_file) { if ($fh = fopen($out_file, 'w')) { foreach ($file_array as $file_name) { $file_name = $directory . '/' . $file_name; $contents = file_get_contents($file_name); // if this is javascript file, check if we have ; at the end if (preg_match("/.js$/i", $out_file)) { if (substr(rtrim($contents), - 1) !== ';') { $contents .= ';'; } $contents .= "\n"; } fwrite($fh, $contents); } fclose($fh); } else { throw new Exception('Can\'t write to ' . $out_file); } if ($this->compress) { // read output file contest (already concated) $contents = file_get_contents($out_file); // recreate file $handle = fopen($out_file, 'w'); if (preg_match("/.css$/i", $out_file)) { $engine = '_' . $this->compression_engine['css']; } if (preg_match("/.js$/i", $out_file)) { $engine = '_' . $this->compression_engine['js']; } // call function name to compress file fwrite($handle, call_user_func(array($this, $engine), $contents)); fclose($handle); } } /** * Compress javascript using closure compiler service * * @param string $data Source to compress * * @return mixed */ private function _closurecompiler($data) { $config = $this->closurecompiler; $ch = curl_init('https://closure-compiler.appspot.com/compile'); //if server is not https if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off') { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); } curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, 'output_info=compiled_code&output_info=errors&output_format=text&compilation_level=' . $config['compilation_level'] . '&js_code=' . urlencode($data)); $output = curl_exec($ch); curl_close($ch); if (preg_match('/Input_0:[0-9]+: ERROR/', $output)) { throw new Exception('Closure Compiler error: ' . $output); } return $output; } /** * Implements jsmin as alternative to closure compiler * * @param string $data Source to compress * * @return string */ private function _jsmin($data) { require_once(APPPATH . 'libraries/minify/JSMin.php'); return JSMin::minify($data); } /** * Implements jsminplus as alternative to closure compiler * * @param string $data Source to compress * * @return string */ private function _jsminplus($data) { require_once(APPPATH . 'libraries/minify/JSMinPlus.php'); return JSMinPlus::minify($data); } /** * Implements cssmin compression engine * * @param string $data Source to compress * * @return string */ private function _cssmin($data) { require_once(APPPATH . 'libraries/minify/cssmin-v3.0.1.php'); return CssMin::minify($data); } /** * Implements cssminify compression engine * * @param string $data Source to compress * * @return string */ private function _minify($data) { require_once(APPPATH . 'libraries/minify/cssminify.php'); $cssminify = new cssminify(); return $cssminify->compress($data); } /** * Build correct URL for file * * @param string $file File with path * * @return string */ private function _base_url($file) { if ($this->base_url === '') { return base_url($file); } return rtrim($this->base_url, '/') . '/' . $file; } /** * Perform config checks * * @param $type string CSS / JS check * * @return void * @throws Exception */ private function _config_checks($type) { switch ($type) { case 'css': if (empty($this->assets_dir_css) && ! is_writable($this->assets_dir)) { throw new Exception('Assets directory ' . $this->assets_dir . ' is not writable'); } if ( ! empty($this->assets_dir_css) && ! is_writable($this->assets_dir_css)) { throw new Exception('Assets directory for css ' . $this->assets_dir_css . ' is not writable'); } if (empty($this->css_dir)) { throw new Exception('CSS directory must be set'); } if ($this->html_tags === TRUE && empty($this->css_tag)) { throw new Exception('CSS tag template must be set'); } if ( ! $this->auto_names) { if (empty($this->css_file)) { throw new Exception('CSS file name can\'t be empty'); } } if ($this->compress) { if ( ! isset($this->compression_engine['css']) or empty($this->compression_engine['css'])) { throw new Exception('Compression engine for CSS is required'); } } break; case 'js': if (empty($this->assets_dir_js) && ! is_writable($this->assets_dir)) { throw new Exception('Assets directory ' . $this->assets_dir . ' is not writable'); } if ( ! empty($this->assets_dir_js) && ! is_writable($this->assets_dir_js)) { throw new Exception('Assets directory for js ' . $this->assets_dir_js . ' is not writable'); } if (empty($this->js_dir)) { throw new Exception('JS directory must be set'); } if ($this->html_tags === TRUE && empty($this->js_tag)) { throw new Exception('JS tag template must be set'); } if ( ! $this->auto_names) { if (empty($this->js_file)) { throw new Exception('JS file name can\'t be empty'); } } if ($this->compress) { if ( ! isset($this->compression_engine['js']) or empty($this->compression_engine['js'])) { throw new Exception('Compression engine for JS is required'); } if ($this->compression_engine['js'] === 'closurecompiler' && ( ! isset($this->closurecompiler['compilation_level']) or empty($this->closurecompiler['compilation_level']))) { throw new Exception('Compilation level for closurecompiler is needed'); } } break; } } /** * Get Version Number for file * * @param string $file File with path * * @return string */ private function _version_number($file) { if ( ! empty($this->version_number)) { return $this->version_number; } return md5_file($file); } } /* End of file Minify.php */ /* Location: ./libraries/Minify.php */ ================================================ FILE: application/libraries/minify/JSMin.php ================================================ * $minifiedJs = JSMin::minify($js); * * * This is a modified port of jsmin.c. Improvements: * * Does not choke on some regexp literals containing quote characters. E.g. /'/ * * Spaces are preserved after some add/sub operators, so they are not mistakenly * converted to post-inc/dec. E.g. a + ++b -> a+ ++b * * Preserves multi-line comments that begin with /*! * * PHP 5 or higher is required. * * Permission is hereby granted to use this version of the library under the * same terms as jsmin.c, which has the following license: * * -- * Copyright (c) 2002 Douglas Crockford (www.crockford.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * The Software shall be used for Good, not Evil. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * -- * * @package JSMin * @author Ryan Grove (PHP port) * @author Steve Clay (modifications + cleanup) * @author Andrea Giammarchi (spaceBeforeRegExp) * @copyright 2002 Douglas Crockford (jsmin.c) * @copyright 2008 Ryan Grove (PHP port) * @license http://opensource.org/licenses/mit-license.php MIT License * @link http://code.google.com/p/jsmin-php/ */ class JSMin { const ORD_LF = 10; const ORD_SPACE = 32; const ACTION_KEEP_A = 1; const ACTION_DELETE_A = 2; const ACTION_DELETE_A_B = 3; protected $a = "\n"; protected $b = ''; protected $input = ''; protected $inputIndex = 0; protected $inputLength = 0; protected $lookAhead = null; protected $output = ''; protected $lastByteOut = ''; protected $keptComment = ''; /** * Minify Javascript. * * @param string $js Javascript to be minified * * @return string */ public static function minify($js) { $jsmin = new JSMin($js); return $jsmin->min(); } /** * @param string $input */ public function __construct($input) { $this->input = $input; } /** * Perform minification, return result * * @return string */ public function min() { if ($this->output !== '') { // min already run return $this->output; } $mbIntEnc = null; if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { $mbIntEnc = mb_internal_encoding(); mb_internal_encoding('8bit'); } $this->input = str_replace("\r\n", "\n", $this->input); $this->inputLength = strlen($this->input); $this->action(self::ACTION_DELETE_A_B); while ($this->a !== null) { // determine next command $command = self::ACTION_KEEP_A; // default if ($this->a === ' ') { if (($this->lastByteOut === '+' || $this->lastByteOut === '-') && ($this->b === $this->lastByteOut)) { // Don't delete this space. If we do, the addition/subtraction // could be parsed as a post-increment } elseif (! $this->isAlphaNum($this->b)) { $command = self::ACTION_DELETE_A; } } elseif ($this->a === "\n") { if ($this->b === ' ') { $command = self::ACTION_DELETE_A_B; // in case of mbstring.func_overload & 2, must check for null b, // otherwise mb_strpos will give WARNING } elseif ($this->b === null || (false === strpos('{[(+-!~', $this->b) && ! $this->isAlphaNum($this->b))) { $command = self::ACTION_DELETE_A; } } elseif (! $this->isAlphaNum($this->a)) { if ($this->b === ' ' || ($this->b === "\n" && (false === strpos('}])+-"\'', $this->a)))) { $command = self::ACTION_DELETE_A_B; } } $this->action($command); } $this->output = trim($this->output); if ($mbIntEnc !== null) { mb_internal_encoding($mbIntEnc); } return $this->output; } /** * ACTION_KEEP_A = Output A. Copy B to A. Get the next B. * ACTION_DELETE_A = Copy B to A. Get the next B. * ACTION_DELETE_A_B = Get the next B. * * @param int $command * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException */ protected function action($command) { // make sure we don't compress "a + ++b" to "a+++b", etc. if ($command === self::ACTION_DELETE_A_B && $this->b === ' ' && ($this->a === '+' || $this->a === '-')) { // Note: we're at an addition/substraction operator; the inputIndex // will certainly be a valid index if ($this->input[$this->inputIndex] === $this->a) { // This is "+ +" or "- -". Don't delete the space. $command = self::ACTION_KEEP_A; } } switch ($command) { case self::ACTION_KEEP_A: // 1 $this->output .= $this->a; if ($this->keptComment) { $this->output = rtrim($this->output, "\n"); $this->output .= $this->keptComment; $this->keptComment = ''; } $this->lastByteOut = $this->a; // fallthrough intentional case self::ACTION_DELETE_A: // 2 $this->a = $this->b; if ($this->a === "'" || $this->a === '"') { // string literal $str = $this->a; // in case needed for exception for(;;) { $this->output .= $this->a; $this->lastByteOut = $this->a; $this->a = $this->get(); if ($this->a === $this->b) { // end quote break; } if ($this->isEOF($this->a)) { throw new JSMin_UnterminatedStringException( "JSMin: Unterminated String at byte {$this->inputIndex}: {$str}"); } $str .= $this->a; if ($this->a === '\\') { $this->output .= $this->a; $this->lastByteOut = $this->a; $this->a = $this->get(); $str .= $this->a; } } } // fallthrough intentional case self::ACTION_DELETE_A_B: // 3 $this->b = $this->next(); if ($this->b === '/' && $this->isRegexpLiteral()) { $this->output .= $this->a . $this->b; $pattern = '/'; // keep entire pattern in case we need to report it in the exception for(;;) { $this->a = $this->get(); $pattern .= $this->a; if ($this->a === '[') { for(;;) { $this->output .= $this->a; $this->a = $this->get(); $pattern .= $this->a; if ($this->a === ']') { break; } if ($this->a === '\\') { $this->output .= $this->a; $this->a = $this->get(); $pattern .= $this->a; } if ($this->isEOF($this->a)) { throw new JSMin_UnterminatedRegExpException( "JSMin: Unterminated set in RegExp at byte " . $this->inputIndex .": {$pattern}"); } } } if ($this->a === '/') { // end pattern break; // while (true) } elseif ($this->a === '\\') { $this->output .= $this->a; $this->a = $this->get(); $pattern .= $this->a; } elseif ($this->isEOF($this->a)) { throw new JSMin_UnterminatedRegExpException( "JSMin: Unterminated RegExp at byte {$this->inputIndex}: {$pattern}"); } $this->output .= $this->a; $this->lastByteOut = $this->a; } $this->b = $this->next(); } // end case ACTION_DELETE_A_B } } /** * @return bool */ protected function isRegexpLiteral() { if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { // we obviously aren't dividing return true; } if ($this->a === ' ' || $this->a === "\n") { $length = strlen($this->output); if ($length < 2) { // weird edge case return true; } // you can't divide a keyword if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) { if ($this->output === $m[0]) { // odd but could happen return true; } // make sure it's a keyword, not end of an identifier $charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1); if (! $this->isAlphaNum($charBeforeKeyword)) { return true; } } } return false; } /** * Return the next character from stdin. Watch out for lookahead. If the character is a control character, * translate it to a space or linefeed. * * @return string */ protected function get() { $c = $this->lookAhead; $this->lookAhead = null; if ($c === null) { // getc(stdin) if ($this->inputIndex < $this->inputLength) { $c = $this->input[$this->inputIndex]; $this->inputIndex += 1; } else { $c = null; } } if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { return $c; } if ($c === "\r") { return "\n"; } return ' '; } /** * Does $a indicate end of input? * * @param string $a * @return bool */ protected function isEOF($a) { return ord($a) <= self::ORD_LF; } /** * Get next char (without getting it). If is ctrl character, translate to a space or newline. * * @return string */ protected function peek() { $this->lookAhead = $this->get(); return $this->lookAhead; } /** * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. * * @param string $c * * @return bool */ protected function isAlphaNum($c) { return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126); } /** * Consume a single line comment from input (possibly retaining it) */ protected function consumeSingleLineComment() { $comment = ''; while (true) { $get = $this->get(); $comment .= $get; if (ord($get) <= self::ORD_LF) { // end of line reached // if IE conditional comment if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { $this->keptComment .= "/{$comment}"; } return; } } } /** * Consume a multiple line comment from input (possibly retaining it) * * @throws JSMin_UnterminatedCommentException */ protected function consumeMultipleLineComment() { $this->get(); $comment = ''; for(;;) { $get = $this->get(); if ($get === '*') { if ($this->peek() === '/') { // end of comment reached $this->get(); if (0 === strpos($comment, '!')) { // preserved by YUI Compressor if (!$this->keptComment) { // don't prepend a newline if two comments right after one another $this->keptComment = "\n"; } $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { // IE conditional $this->keptComment .= "/*{$comment}*/"; } return; } } elseif ($get === null) { throw new JSMin_UnterminatedCommentException( "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); } $comment .= $get; } } /** * Get the next character, skipping over comments. Some comments may be preserved. * * @return string */ protected function next() { $get = $this->get(); if ($get === '/') { switch ($this->peek()) { case '/': $this->consumeSingleLineComment(); $get = "\n"; break; case '*': $this->consumeMultipleLineComment(); $get = ' '; break; } } return $get; } } class JSMin_UnterminatedStringException extends Exception {} class JSMin_UnterminatedCommentException extends Exception {} class JSMin_UnterminatedRegExpException extends Exception {} ================================================ FILE: application/libraries/minify/JSMinPlus.php ================================================ * * Usage: $minified = JSMinPlus::minify($script [, $filename]) * * Versionlog (see also changelog.txt): * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top * reduce memory footprint by minifying by block-scope * some small byte-saving and performance improvements * 12-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes * 12-04-2009 - some small bugfixes and performance improvements * 09-04-2009 - initial open sourced version 1.0 * * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip * */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Narcissus JavaScript engine. * * The Initial Developer of the Original Code is * Brendan Eich . * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): Tino Zijdel * PHP port, modifications and minifier routine are (C) 2009-2011 * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ define('TOKEN_END', 1); define('TOKEN_NUMBER', 2); define('TOKEN_IDENTIFIER', 3); define('TOKEN_STRING', 4); define('TOKEN_REGEXP', 5); define('TOKEN_NEWLINE', 6); define('TOKEN_CONDCOMMENT_START', 7); define('TOKEN_CONDCOMMENT_END', 8); define('JS_SCRIPT', 100); define('JS_BLOCK', 101); define('JS_LABEL', 102); define('JS_FOR_IN', 103); define('JS_CALL', 104); define('JS_NEW_WITH_ARGS', 105); define('JS_INDEX', 106); define('JS_ARRAY_INIT', 107); define('JS_OBJECT_INIT', 108); define('JS_PROPERTY_INIT', 109); define('JS_GETTER', 110); define('JS_SETTER', 111); define('JS_GROUP', 112); define('JS_LIST', 113); define('JS_MINIFIED', 999); define('DECLARED_FORM', 0); define('EXPRESSED_FORM', 1); define('STATEMENT_FORM', 2); /* Operators */ define('OP_SEMICOLON', ';'); define('OP_COMMA', ','); define('OP_HOOK', '?'); define('OP_COLON', ':'); define('OP_OR', '||'); define('OP_AND', '&&'); define('OP_BITWISE_OR', '|'); define('OP_BITWISE_XOR', '^'); define('OP_BITWISE_AND', '&'); define('OP_STRICT_EQ', '==='); define('OP_EQ', '=='); define('OP_ASSIGN', '='); define('OP_STRICT_NE', '!=='); define('OP_NE', '!='); define('OP_LSH', '<<'); define('OP_LE', '<='); define('OP_LT', '<'); define('OP_URSH', '>>>'); define('OP_RSH', '>>'); define('OP_GE', '>='); define('OP_GT', '>'); define('OP_INCREMENT', '++'); define('OP_DECREMENT', '--'); define('OP_PLUS', '+'); define('OP_MINUS', '-'); define('OP_MUL', '*'); define('OP_DIV', '/'); define('OP_MOD', '%'); define('OP_NOT', '!'); define('OP_BITWISE_NOT', '~'); define('OP_DOT', '.'); define('OP_LEFT_BRACKET', '['); define('OP_RIGHT_BRACKET', ']'); define('OP_LEFT_CURLY', '{'); define('OP_RIGHT_CURLY', '}'); define('OP_LEFT_PAREN', '('); define('OP_RIGHT_PAREN', ')'); define('OP_CONDCOMMENT_END', '@*/'); define('OP_UNARY_PLUS', 'U+'); define('OP_UNARY_MINUS', 'U-'); /* Keywords */ define('KEYWORD_BREAK', 'break'); define('KEYWORD_CASE', 'case'); define('KEYWORD_CATCH', 'catch'); define('KEYWORD_CONST', 'const'); define('KEYWORD_CONTINUE', 'continue'); define('KEYWORD_DEBUGGER', 'debugger'); define('KEYWORD_DEFAULT', 'default'); define('KEYWORD_DELETE', 'delete'); define('KEYWORD_DO', 'do'); define('KEYWORD_ELSE', 'else'); define('KEYWORD_ENUM', 'enum'); define('KEYWORD_FALSE', 'false'); define('KEYWORD_FINALLY', 'finally'); define('KEYWORD_FOR', 'for'); define('KEYWORD_FUNCTION', 'function'); define('KEYWORD_IF', 'if'); define('KEYWORD_IN', 'in'); define('KEYWORD_INSTANCEOF', 'instanceof'); define('KEYWORD_NEW', 'new'); define('KEYWORD_NULL', 'null'); define('KEYWORD_RETURN', 'return'); define('KEYWORD_SWITCH', 'switch'); define('KEYWORD_THIS', 'this'); define('KEYWORD_THROW', 'throw'); define('KEYWORD_TRUE', 'true'); define('KEYWORD_TRY', 'try'); define('KEYWORD_TYPEOF', 'typeof'); define('KEYWORD_VAR', 'var'); define('KEYWORD_VOID', 'void'); define('KEYWORD_WHILE', 'while'); define('KEYWORD_WITH', 'with'); class JSMinPlus { private $parser; private $reserved = array( 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', // Words reserved for future use 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger', 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto', 'implements', 'import', 'int', 'interface', 'long', 'native', 'package', 'private', 'protected', 'public', 'short', 'static', 'super', 'synchronized', 'throws', 'transient', 'volatile', // These are not reserved, but should be taken into account // in isValidIdentifier (See jslint source code) 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined' ); private function __construct() { $this->parser = new JSParser($this); } public static function minify($js, $filename='') { static $instance; // this is a singleton if(!$instance) $instance = new JSMinPlus(); return $instance->min($js, $filename); } private function min($js, $filename) { try { $n = $this->parser->parse($js, $filename, 1); return $this->parseTree($n); } catch(Exception $e) { echo $e->getMessage() . "\n"; } return false; } public function parseTree($n, $noBlockGrouping = false) { $s = ''; switch ($n->type) { case JS_MINIFIED: $s = $n->value; break; case JS_SCRIPT: // we do nothing yet with funDecls or varDecls $noBlockGrouping = true; // FALL THROUGH case JS_BLOCK: $childs = $n->treeNodes; $lastType = 0; for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++) { $type = $childs[$i]->type; $t = $this->parseTree($childs[$i]); if (strlen($t)) { if ($c) { $s = rtrim($s, ';'); if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM) { // put declared functions on a new line $s .= "\n"; } elseif ($type == KEYWORD_VAR && $type == $lastType) { // mutiple var-statements can go into one $t = ',' . substr($t, 4); } else { // add terminator $s .= ';'; } } $s .= $t; $c++; $lastType = $type; } } if ($c > 1 && !$noBlockGrouping) { $s = '{' . $s . '}'; } break; case KEYWORD_FUNCTION: $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '('; $params = $n->params; for ($i = 0, $j = count($params); $i < $j; $i++) $s .= ($i ? ',' : '') . $params[$i]; $s .= '){' . $this->parseTree($n->body, true) . '}'; break; case KEYWORD_IF: $s = 'if(' . $this->parseTree($n->condition) . ')'; $thenPart = $this->parseTree($n->thenPart); $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null; // empty if-statement if ($thenPart == '') $thenPart = ';'; if ($elsePart) { // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble if ($thenPart != ';' && $thenPart[0] != '{') $thenPart = '{' . $thenPart . '}'; $s .= $thenPart . 'else'; // we could check for more, but that hardly ever applies so go for performance if ($elsePart[0] != '{') $s .= ' '; $s .= $elsePart; } else { $s .= $thenPart; } break; case KEYWORD_SWITCH: $s = 'switch(' . $this->parseTree($n->discriminant) . '){'; $cases = $n->cases; for ($i = 0, $j = count($cases); $i < $j; $i++) { $case = $cases[$i]; if ($case->type == KEYWORD_CASE) $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':'; else $s .= 'default:'; $statement = $this->parseTree($case->statements, true); if ($statement) { $s .= $statement; // no terminator for last statement if ($i + 1 < $j) $s .= ';'; } } $s .= '}'; break; case KEYWORD_FOR: $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '') . ';' . ($n->condition ? $this->parseTree($n->condition) : '') . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')'; $body = $this->parseTree($n->body); if ($body == '') $body = ';'; $s .= $body; break; case KEYWORD_WHILE: $s = 'while(' . $this->parseTree($n->condition) . ')'; $body = $this->parseTree($n->body); if ($body == '') $body = ';'; $s .= $body; break; case JS_FOR_IN: $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')'; $body = $this->parseTree($n->body); if ($body == '') $body = ';'; $s .= $body; break; case KEYWORD_DO: $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')'; break; case KEYWORD_BREAK: case KEYWORD_CONTINUE: $s = $n->value . ($n->label ? ' ' . $n->label : ''); break; case KEYWORD_TRY: $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}'; $catchClauses = $n->catchClauses; for ($i = 0, $j = count($catchClauses); $i < $j; $i++) { $t = $catchClauses[$i]; $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}'; } if ($n->finallyBlock) $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}'; break; case KEYWORD_THROW: case KEYWORD_RETURN: $s = $n->type; if ($n->value) { $t = $this->parseTree($n->value); if (strlen($t)) { if ($this->isWordChar($t[0]) || $t[0] == '\\') $s .= ' '; $s .= $t; } } break; case KEYWORD_WITH: $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body); break; case KEYWORD_VAR: case KEYWORD_CONST: $s = $n->value . ' '; $childs = $n->treeNodes; for ($i = 0, $j = count($childs); $i < $j; $i++) { $t = $childs[$i]; $s .= ($i ? ',' : '') . $t->name; $u = $t->initializer; if ($u) $s .= '=' . $this->parseTree($u); } break; case KEYWORD_IN: case KEYWORD_INSTANCEOF: $left = $this->parseTree($n->treeNodes[0]); $right = $this->parseTree($n->treeNodes[1]); $s = $left; if ($this->isWordChar(substr($left, -1))) $s .= ' '; $s .= $n->type; if ($this->isWordChar($right[0]) || $right[0] == '\\') $s .= ' '; $s .= $right; break; case KEYWORD_DELETE: case KEYWORD_TYPEOF: $right = $this->parseTree($n->treeNodes[0]); $s = $n->type; if ($this->isWordChar($right[0]) || $right[0] == '\\') $s .= ' '; $s .= $right; break; case KEYWORD_VOID: $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')'; break; case KEYWORD_DEBUGGER: throw new Exception('NOT IMPLEMENTED: DEBUGGER'); break; case TOKEN_CONDCOMMENT_START: case TOKEN_CONDCOMMENT_END: $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : ''); $childs = $n->treeNodes; for ($i = 0, $j = count($childs); $i < $j; $i++) $s .= $this->parseTree($childs[$i]); break; case OP_SEMICOLON: if ($expression = $n->expression) $s = $this->parseTree($expression); break; case JS_LABEL: $s = $n->label . ':' . $this->parseTree($n->statement); break; case OP_COMMA: $childs = $n->treeNodes; for ($i = 0, $j = count($childs); $i < $j; $i++) $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); break; case OP_ASSIGN: $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]); break; case OP_HOOK: $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]); break; case OP_OR: case OP_AND: case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND: case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE: case OP_LT: case OP_LE: case OP_GE: case OP_GT: case OP_LSH: case OP_RSH: case OP_URSH: case OP_MUL: case OP_DIV: case OP_MOD: $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]); break; case OP_PLUS: case OP_MINUS: $left = $this->parseTree($n->treeNodes[0]); $right = $this->parseTree($n->treeNodes[1]); switch ($n->treeNodes[1]->type) { case OP_PLUS: case OP_MINUS: case OP_INCREMENT: case OP_DECREMENT: case OP_UNARY_PLUS: case OP_UNARY_MINUS: $s = $left . $n->type . ' ' . $right; break; case TOKEN_STRING: //combine concatted strings with same quotestyle if ($n->type == OP_PLUS && substr($left, -1) == $right[0]) { $s = substr($left, 0, -1) . substr($right, 1); break; } // FALL THROUGH default: $s = $left . $n->type . $right; } break; case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS: $s = $n->value . $this->parseTree($n->treeNodes[0]); break; case OP_INCREMENT: case OP_DECREMENT: if ($n->postfix) $s = $this->parseTree($n->treeNodes[0]) . $n->value; else $s = $n->value . $this->parseTree($n->treeNodes[0]); break; case OP_DOT: $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]); break; case JS_INDEX: $s = $this->parseTree($n->treeNodes[0]); // See if we can replace named index with a dot saving 3 bytes if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER && $n->treeNodes[1]->type == TOKEN_STRING && $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1)) ) $s .= '.' . substr($n->treeNodes[1]->value, 1, -1); else $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']'; break; case JS_LIST: $childs = $n->treeNodes; for ($i = 0, $j = count($childs); $i < $j; $i++) $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); break; case JS_CALL: $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')'; break; case KEYWORD_NEW: case JS_NEW_WITH_ARGS: $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')'; break; case JS_ARRAY_INIT: $s = '['; $childs = $n->treeNodes; for ($i = 0, $j = count($childs); $i < $j; $i++) { $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]); } $s .= ']'; break; case JS_OBJECT_INIT: $s = '{'; $childs = $n->treeNodes; for ($i = 0, $j = count($childs); $i < $j; $i++) { $t = $childs[$i]; if ($i) $s .= ','; if ($t->type == JS_PROPERTY_INIT) { // Ditch the quotes when the index is a valid identifier if ( $t->treeNodes[0]->type == TOKEN_STRING && $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1)) ) $s .= substr($t->treeNodes[0]->value, 1, -1); else $s .= $t->treeNodes[0]->value; $s .= ':' . $this->parseTree($t->treeNodes[1]); } else { $s .= $t->type == JS_GETTER ? 'get' : 'set'; $s .= ' ' . $t->name . '('; $params = $t->params; for ($i = 0, $j = count($params); $i < $j; $i++) $s .= ($i ? ',' : '') . $params[$i]; $s .= '){' . $this->parseTree($t->body, true) . '}'; } } $s .= '}'; break; case TOKEN_NUMBER: $s = $n->value; if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m)) $s = $m[1] . 'e' . strlen($m[2]); break; case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE: case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP: $s = $n->value; break; case JS_GROUP: if (in_array( $n->treeNodes[0]->type, array( JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP, TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER, KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE ) )) { $s = $this->parseTree($n->treeNodes[0]); } else { $s = '(' . $this->parseTree($n->treeNodes[0]) . ')'; } break; default: throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type); } return $s; } private function isValidIdentifier($string) { return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved); } private function isWordChar($char) { return $char == '_' || $char == '$' || ctype_alnum($char); } } class JSParser { private $t; private $minifier; private $opPrecedence = array( ';' => 0, ',' => 1, '=' => 2, '?' => 2, ':' => 2, // The above all have to have the same precedence, see bug 330975 '||' => 4, '&&' => 5, '|' => 6, '^' => 7, '&' => 8, '==' => 9, '!=' => 9, '===' => 9, '!==' => 9, '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10, '<<' => 11, '>>' => 11, '>>>' => 11, '+' => 12, '-' => 12, '*' => 13, '/' => 13, '%' => 13, 'delete' => 14, 'void' => 14, 'typeof' => 14, '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14, '++' => 15, '--' => 15, 'new' => 16, '.' => 17, JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0, JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0 ); private $opArity = array( ',' => -2, '=' => 2, '?' => 3, '||' => 2, '&&' => 2, '|' => 2, '^' => 2, '&' => 2, '==' => 2, '!=' => 2, '===' => 2, '!==' => 2, '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2, '<<' => 2, '>>' => 2, '>>>' => 2, '+' => 2, '-' => 2, '*' => 2, '/' => 2, '%' => 2, 'delete' => 1, 'void' => 1, 'typeof' => 1, '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1, '++' => 1, '--' => 1, 'new' => 1, '.' => 2, JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2, JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1, TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1 ); public function __construct($minifier=null) { $this->minifier = $minifier; $this->t = new JSTokenizer(); } public function parse($s, $f, $l) { // initialize tokenizer $this->t->init($s, $f, $l); $x = new JSCompilerContext(false); $n = $this->Script($x); if (!$this->t->isDone()) throw $this->t->newSyntaxError('Syntax error'); return $n; } private function Script($x) { $n = $this->Statements($x); $n->type = JS_SCRIPT; $n->funDecls = $x->funDecls; $n->varDecls = $x->varDecls; // minify by scope if ($this->minifier) { $n->value = $this->minifier->parseTree($n); // clear tree from node to save memory $n->treeNodes = null; $n->funDecls = null; $n->varDecls = null; $n->type = JS_MINIFIED; } return $n; } private function Statements($x) { $n = new JSNode($this->t, JS_BLOCK); array_push($x->stmtStack, $n); while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY) $n->addNode($this->Statement($x)); array_pop($x->stmtStack); return $n; } private function Block($x) { $this->t->mustMatch(OP_LEFT_CURLY); $n = $this->Statements($x); $this->t->mustMatch(OP_RIGHT_CURLY); return $n; } private function Statement($x) { $tt = $this->t->get(); $n2 = null; // Cases for statements ending in a right curly return early, avoiding the // common semicolon insertion magic after this switch. switch ($tt) { case KEYWORD_FUNCTION: return $this->FunctionDefinition( $x, true, count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM ); break; case OP_LEFT_CURLY: $n = $this->Statements($x); $this->t->mustMatch(OP_RIGHT_CURLY); return $n; case KEYWORD_IF: $n = new JSNode($this->t); $n->condition = $this->ParenExpression($x); array_push($x->stmtStack, $n); $n->thenPart = $this->Statement($x); $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null; array_pop($x->stmtStack); return $n; case KEYWORD_SWITCH: $n = new JSNode($this->t); $this->t->mustMatch(OP_LEFT_PAREN); $n->discriminant = $this->Expression($x); $this->t->mustMatch(OP_RIGHT_PAREN); $n->cases = array(); $n->defaultIndex = -1; array_push($x->stmtStack, $n); $this->t->mustMatch(OP_LEFT_CURLY); while (($tt = $this->t->get()) != OP_RIGHT_CURLY) { switch ($tt) { case KEYWORD_DEFAULT: if ($n->defaultIndex >= 0) throw $this->t->newSyntaxError('More than one switch default'); // FALL THROUGH case KEYWORD_CASE: $n2 = new JSNode($this->t); if ($tt == KEYWORD_DEFAULT) $n->defaultIndex = count($n->cases); else $n2->caseLabel = $this->Expression($x, OP_COLON); break; default: throw $this->t->newSyntaxError('Invalid switch case'); } $this->t->mustMatch(OP_COLON); $n2->statements = new JSNode($this->t, JS_BLOCK); while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY) $n2->statements->addNode($this->Statement($x)); array_push($n->cases, $n2); } array_pop($x->stmtStack); return $n; case KEYWORD_FOR: $n = new JSNode($this->t); $n->isLoop = true; $this->t->mustMatch(OP_LEFT_PAREN); if (($tt = $this->t->peek()) != OP_SEMICOLON) { $x->inForLoopInit = true; if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST) { $this->t->get(); $n2 = $this->Variables($x); } else { $n2 = $this->Expression($x); } $x->inForLoopInit = false; } if ($n2 && $this->t->match(KEYWORD_IN)) { $n->type = JS_FOR_IN; if ($n2->type == KEYWORD_VAR) { if (count($n2->treeNodes) != 1) { throw $this->t->SyntaxError( 'Invalid for..in left-hand side', $this->t->filename, $n2->lineno ); } // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name. $n->iterator = $n2->treeNodes[0]; $n->varDecl = $n2; } else { $n->iterator = $n2; $n->varDecl = null; } $n->object = $this->Expression($x); } else { $n->setup = $n2 ? $n2 : null; $this->t->mustMatch(OP_SEMICOLON); $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x); $this->t->mustMatch(OP_SEMICOLON); $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x); } $this->t->mustMatch(OP_RIGHT_PAREN); $n->body = $this->nest($x, $n); return $n; case KEYWORD_WHILE: $n = new JSNode($this->t); $n->isLoop = true; $n->condition = $this->ParenExpression($x); $n->body = $this->nest($x, $n); return $n; case KEYWORD_DO: $n = new JSNode($this->t); $n->isLoop = true; $n->body = $this->nest($x, $n, KEYWORD_WHILE); $n->condition = $this->ParenExpression($x); if (!$x->ecmaStrictMode) { // '. PHP_EOL.'', $result, 'output js with disabled library\'s functionality'); } // test css when functionality is disabled public function testCssDisabled() { $this->minify = new Minify(); $this->minify->enabled = FALSE; $this->minify->assets_dir_css = 'assets/css'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_string($result), 'deploy with add_css'); $this->assertEquals(''. PHP_EOL.'', $result, 'output css with disabled library\'s functionality'); } // test js when no html tags public function testJsNoHtmlTagsWithDisabled() { $this->minify = new Minify(); $this->minify->enabled = FALSE; $this->minify->html_tags = FALSE; $this->minify->assets_dir_js = 'assets/js'; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_array($result), 'deploy with add_js'); $this->assertEquals(array('http://minify.localhost/assets/js/helpers.js', 'http://minify.localhost/assets/js/jqModal.js'), $result, 'output js with no html tags'); } // test css when no html tags public function testCssNoHtmlTagsWithDisabled() { $this->minify = new Minify(); $this->minify->enabled = FALSE; $this->minify->html_tags = FALSE; $this->minify->assets_dir_css = 'assets/css'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_array($result), 'deploy with add_css'); $this->assertEquals(array('http://minify.localhost/assets/css/style.css', 'http://minify.localhost/assets/css/browser-specific.css'), $result, 'output css with no html tags'); } // test js when no html tags public function testJsNoHtmlTagsWithEnabled() { $this->minify = new Minify(); $this->minify->html_tags = FALSE; $this->minify->assets_dir_js = 'assets/js'; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_array($result), 'deploy with add_js'); $this->assertEquals(array('http://minify.localhost/assets/js/scripts.min.js'), $result, 'output js with no html tags'); } // test css when no html tags public function testCssNoHtmlTagsWithEnabled() { $this->minify = new Minify(); $this->minify->html_tags = FALSE; $this->minify->assets_dir_css = 'assets/css'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_array($result), 'deploy with add_css'); $this->assertEquals(array('http://minify.localhost/assets/css/styles.min.css'), $result, 'output css with no html tags'); } // check js compression public function testJsCompress() { $this->minify = new Minify(); $this->minify->js_dir = 'assets/js'; $this->minify->js(array('helpers.js')); $result = $this->minify->deploy_js(FALSE, 'ut.js'); $this->assertTrue(is_string($result), 'deploy js with name'); $this->assertEquals($this->minify->js_file, 'ut.min.js', 'output js file name'); } // check js compression with closule compiler public function testJsCompressWithClosureCompiler() { $this->minify = new Minify(); $this->minify->js_dir = 'assets/js'; $this->minify->compression_engine['js'] = 'closurecompiler'; $this->minify->js(array('helpers.js')); $result = $this->minify->deploy_js(TRUE, 'ut.js'); $this->assertTrue(is_string($result), 'deploy js with closurecompiler'); $this->assertEquals($this->minify->js_file, 'ut.min.js', 'output js file name'); $file_content = file_get_contents($this->minify->assets_dir.DIRECTORY_SEPARATOR.$this->minify->js_file); $this->assertNotContains('Error', $file_content, 'output file has errors'); $this->assertTrue( ! empty($file_content), 'output is not empty'); } // test css compresion public function testCssCompress() { $this->minify = new Minify(); $this->minify->css_dir = 'assets/css'; $this->minify->css(array('style.css')); $result = $this->minify->deploy_css(TRUE, 'ut.css'); $this->assertTrue(is_string($result), 'deploy css with name'); $this->assertEquals($this->minify->css_file, 'ut.min.css', 'output css file name'); } // test js with auto names public function testJsCompressWithAutoNames() { $this->minify = new Minify(); $this->minify->auto_names = TRUE; $this->minify->js_dir = 'assets/js'; $this->minify->js(array('helpers.js')); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_string($result), 'deploy js with auto name'); $this->assertEquals($this->minify->js_file, '91e30b9b77dc616476b94acf4dbb25c1.min.js', 'output js auto file name'); } // test css with auto names public function testCssCompressWithAutoNames() { $this->minify = new Minify(); $this->minify->auto_names = TRUE; $this->minify->css_dir = 'assets/css'; $this->minify->css(array('style.css')); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_string($result), 'deploy css with auto name'); $this->assertEquals($this->minify->css_file, '72ac8bfd7cb9dd0f9df9ef4aafe0c714.min.css', 'output css auto file name'); } // test js add public function testJsCompressWithAdd() { $this->minify = new Minify(); $this->minify->js_dir = 'assets/js'; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_string($result), 'deploy with add_js'); $this->assertEquals($this->minify->js_file, 'scripts.min.js', 'output js default file name'); } // tetst css with cadd public function testCssCompressWithAdd() { $this->minify = new Minify(); $this->minify->css_dir = 'assets/css'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_string($result), 'deploy with add_css'); $this->assertEquals($this->minify->css_file, 'styles.min.css', 'output css default file name'); } public function testJsCompressWithIndividualAutoName() { $this->minify = new Minify(); $this->minify->js_dir = 'assets/js'; $this->minify->js(array('helpers.js')); $result = $this->minify->deploy_js(FALSE, 'auto'); $this->assertTrue(is_string($result), 'deploy js with individual auto name'); $this->assertEquals($this->minify->js_file, '91e30b9b77dc616476b94acf4dbb25c1.min.js', 'output js auto file name'); } public function testCssCompressWithIndividualAutoName() { $this->minify = new Minify(); $this->minify->css_dir = 'assets/css'; $this->minify->css(array('style.css')); $result = $this->minify->deploy_css(TRUE, 'auto'); $this->assertTrue(is_string($result), 'deploy css with individual auto name'); $this->assertEquals($this->minify->css_file, '72ac8bfd7cb9dd0f9df9ef4aafe0c714.min.css', 'output css auto file name'); } // feature/custom_folders_for_compiled_css_and_js public function testCustomJsPathForAssets() { $this->minify = new Minify(); $this->minify->assets_dir_js = 'assets/js'; $this->minify->js_dir = 'assets/js'; $this->minify->js(array('helpers.js')); $result = $this->minify->deploy_js(TRUE, 'auto'); $this->assertEquals('', $result, 'deploy js with custom save path'); } // public function testCustomCssPathForAssets() { $this->minify = new Minify(); $this->minify->assets_dir_css = 'assets/css'; $this->minify->css_dir = 'assets/css'; $this->minify->css(array('style.css')); $result = $this->minify->deploy_css(TRUE, 'auto'); $this->assertEquals('', $result, 'deploy css with custom save path'); } public function testCssCompressWithGroupNames() { $this->minify = new Minify(); $this->minify->css_dir = 'assets/css'; $this->minify->add_css(array('style.css'), 'sample1')->add_css('browser-specific.css', 'sample2'); $result = $this->minify->deploy_css(TRUE, NULL, 'sample1'); $this->assertTrue(is_string($result), 'deploy with group name: sample1'); $this->assertEquals($this->minify->css_file, 'sample1_styles.min.css', 'output css default file name for group: sample1'); $result = $this->minify->deploy_css(TRUE, NULL, 'sample2'); $this->assertTrue(is_string($result), 'deploy with group name: sample2'); $this->assertEquals($this->minify->css_file, 'sample2_styles.min.css', 'output css default file name for group: sample2'); } public function testJsCompressWithGroupNames() { $this->minify = new Minify(); $this->minify->css_dir = 'assets/js'; $this->minify->add_js(array('helpers.js'), 'sample1')->add_js('jqModal.js', 'sample2'); $result = $this->minify->deploy_js(TRUE, NULL, 'sample1'); $this->assertTrue(is_string($result), 'deploy with group name: sample1'); $this->assertEquals($this->minify->js_file, 'sample1_scripts.min.js', 'output js default file name for group: sample1'); $result = $this->minify->deploy_js(TRUE, NULL, 'sample2'); $this->assertTrue(is_string($result), 'deploy with group name: sample2'); $this->assertEquals($this->minify->js_file, 'sample2_scripts.min.js', 'output js default file name for group: sample2'); } // test versioning js public function testJsVersioning() { $this->minify = new Minify(); unlink(APPPATH.'../assets/js/scripts.min.js'); $this->minify->assets_dir_js = 'assets/js'; $this->minify->versioning = TRUE; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_string($result), 'deploy js with versioning'); $this->assertEquals('', $result, 'output js default file name with version'); } // test versioning css public function testCssVersioning() { $this->minify = new Minify(); unlink(APPPATH.'../assets/css/styles.min.css'); $this->minify->assets_dir_css = 'assets/css'; $this->minify->versioning = TRUE; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_string($result), 'deploy css with versioning'); $this->assertEquals('', $result, 'output css default file name with versioning'); } // test versioning js with custom version number public function testJsVersioningCustomNumber() { $this->minify = new Minify(); unlink(APPPATH.'../assets/js/scripts.min.js'); $this->minify->assets_dir_js = 'assets/js'; $this->minify->versioning = TRUE; $this->minify->version_number = '12345'; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_string($result), 'deploy js with versioning'); $this->assertEquals('', $result, 'output js default file name with custom version number'); } // test versioning css with custom version number public function testCssVersioningCustomNumber() { $this->minify = new Minify(); unlink(APPPATH.'../assets/css/styles.min.css'); $this->minify->assets_dir_css = 'assets/css'; $this->minify->versioning = TRUE; $this->minify->version_number = '12345'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_string($result), 'deploy css with versioning'); $this->assertEquals('', $result, 'output css default file name with custom version number'); } // test deploy on change js public function testJsDeployOnChangeFalse() { $this->minify = new Minify(); $this->minify->assets_dir_js = 'assets/js'; $this->minify->versioning = TRUE; // deploy on change turned off $this->minify->deploy_on_change = FALSE; $this->minify->js(array('helpers.js')); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_string($result), 'deploy js with deploy_on_change = FALSE'); $this->assertEquals('', $result, 'output js default file name without deploy_on_change'); } // test deploy on change css public function testCssDeployOnChangeFalse() { $this->minify = new Minify(); $this->minify->assets_dir_css = 'assets/css'; $this->minify->versioning = TRUE; // deploy on change turned off $this->minify->deploy_on_change = FALSE; $this->minify->css(array('style.css')); $result = $this->minify->deploy_css(FALSE); $this->assertTrue(is_string($result), 'deploy css with deploy_on_change = FALSE'); $this->assertEquals('', $result, 'output css default file name without deploy_on_change'); } // test js with custom base url public function testJsWithChangedBaseUrl() { $this->minify = new Minify(); $this->minify->html_tags = FALSE; $this->minify->base_url = 'http://cdn1.minify.localhost/'; $this->minify->assets_dir_js = 'assets/js'; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_array($result), 'deploy with add_js'); $this->assertEquals(array('http://cdn1.minify.localhost/assets/js/scripts.min.js'), $result, 'output js with no html tags and custom base_url'); } // test css with custom base url public function testCssWithChangedBaseUrl() { $this->minify = new Minify(); $this->minify->html_tags = FALSE; $this->minify->base_url = 'http://cdn2.minify.localhost'; $this->minify->assets_dir_css = 'assets/css'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_array($result), 'deploy with add_css'); $this->assertEquals(array('http://cdn2.minify.localhost/assets/css/styles.min.css'), $result, 'output css with no html tags and custom base_url'); } // test js with custom config in constructor public function testJsWithCustomConstructorConfig() { $config = array('js_file' => 'changed_scripts.js'); $this->minify = new Minify($config); $this->minify->html_tags = FALSE; $this->minify->assets_dir_js = 'assets/js'; $this->minify->add_js(array('helpers.js'))->add_js('jqModal.js'); $result = $this->minify->deploy_js(FALSE); $this->assertTrue(is_array($result), 'deploy with add_js'); $this->assertEquals(array('http://minify.localhost/assets/js/changed_scripts.min.js'), $result, 'output js with no html tags'); } // test css with custom config in constructor public function testCssWithCustomConstructorConfig() { $config = array('css_file' => 'changed_styles.css'); $this->minify = new Minify($config); $this->minify->html_tags = FALSE; $this->minify->assets_dir_css = 'assets/css'; $this->minify->add_css(array('style.css'))->add_css('browser-specific.css'); $result = $this->minify->deploy_css(TRUE); $this->assertTrue(is_array($result), 'deploy with add_css'); $this->assertEquals(array('http://minify.localhost/assets/css/changed_styles.min.css'), $result, 'output css with no html tags'); } } ================================================ FILE: tests/bootstrap.php ================================================ load = new CI_Loader(); $this->config = new CI_Config(); } public function config() { } } /** * The loads happen in the constructor (before we can mock anything out), * so instead we'll fakeify the Loader */ class CI_Loader { public function __construct() { $this->item = new CI_Config(); } public function __call($method, $params = array()) { } } class CI_Config { public function __call($method, $params = array()) { switch ($params[0]) { case 'assets_dir': return sys_get_temp_dir(); break; case 'compression_engine': return array('js' => 'jsmin', 'css' => 'minify'); break; } } }