diff --git a/classes/Media.php b/classes/Media.php index ec29ba2c7..04697ee85 100755 --- a/classes/Media.php +++ b/classes/Media.php @@ -129,20 +129,24 @@ class MediaCore } - public static function packJS($js_content) + public static function packJS($js_files) { - if (!empty($js_content)) + if (count($js_files)) { - require_once(_PS_TOOL_DIR_.'js_minify/jsmin.php'); - try { - $js_content = JSMin::minify($js_content); - } catch (Exception $e) { - if (_PS_MODE_DEV_) + try + { + require_once(_PS_TOOL_DIR_.'closure/closure.php'); + $closure = new PhpClosure($js_files); + return $closure->getCompiledCode(); + } + catch (Exception $e) + { + if (_PS_MODE_DEV_ === true) echo $e->getMessage(); - return $js_content; + return false; } } - return $js_content; + return null; } public static function minifyCSS($css_content, $fileuri = false, &$import_url = array()) @@ -535,16 +539,10 @@ class MediaCore // aggregate and compress js files content, write new caches files if ($js_files_date > $compressed_js_file_date) { - $content = ''; - foreach ($js_files_infos as $file_infos) - { - if (file_exists($file_infos['path'])) - $content .= file_get_contents($file_infos['path']).';'; - else - $compressed_js_files_not_found[] = $file_infos['path']; - } - $content = Media::packJS($content); - + $content = Media::packJS($js_files_infos); + if ($content === false) + return false; + if (!empty($compressed_js_files_not_found)) $content = '/* WARNING ! file(s) not found : "'. implode(',', $compressed_js_files_not_found). diff --git a/classes/controller/FrontController.php b/classes/controller/FrontController.php index 11181cc43..8b460c8ca 100755 --- a/classes/controller/FrontController.php +++ b/classes/controller/FrontController.php @@ -452,7 +452,8 @@ class FrontControllerCore extends Controller $this->css_files = Media::cccCSS($this->css_files); //JS compressor management if (Configuration::get('PS_JS_THEME_CACHE')) - $this->js_files = Media::cccJs($this->js_files); + if ($js_files = Media::cccJs($this->js_files)) + $this->js_files = $js_files; } // Call hook before assign of css_files and js_files in order to include correctly all css and javascript files @@ -515,7 +516,8 @@ class FrontControllerCore extends Controller $this->css_files = Media::cccCSS($this->css_files); //JS compressor management if (Configuration::get('PS_JS_THEME_CACHE')) - $this->js_files = Media::cccJs($this->js_files); + if ($js_files = Media::cccJs($this->js_files)) + $this->js_files = $js_files; } $this->context->smarty->assign('css_files', $this->css_files); diff --git a/tools/closure/closure.php b/tools/closure/closure.php new file mode 100644 index 000000000..45bd4e47a --- /dev/null +++ b/tools/closure/closure.php @@ -0,0 +1,86 @@ + +* @copyright 2007-2013 PrestaShop SA +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class PhpClosure +{ + private $_js_files = false; + private $_output_format = 'json'; + private $_output_info = 'compiled_code'; + private $_optimization_level = 'SIMPLE_OPTIMIZATIONS'; + private $compiler_uri = 'http://closure-compiler.appspot.com/compile'; + + public function __construct($js_files = array()) + { + $this->_js_files = $js_files; + } + + public function getCompiledCode() + { + $data = $this->getData(); + + $options = array( + 'http'=>array( + 'method' => "POST", + 'header' => + "Content-type: application/x-www-form-urlencoded\r\n". + "Content-length: ". strlen($data) ."\r\n", + 'content' => $data, + ) + ); + + $context = stream_context_create($options); + $json_response = file_get_contents($this->_compiler_uri, null, $context); + $response = json_decode($json_response); + + if (isset($response->compiledCode)) + return $response->compiledCode; + elseif (isset($response->serverErrors)) + { + $server_errors = array_pop($response->serverErrors); + throw new Exception($server_errors->error); + } + } + + protected function getData() + { + $params = array( + 'compilation_level' => $this->_optimization_level, + 'output_format' => $this->_output_format, + 'output_info' => $this->_output_info, + ); + + $index = 0; + foreach ($this->_js_files as $js_file) + $params['code_url_'.$index++] = _PS_BASE_URL_.$js_file['uri']; + + foreach ($params as $key => $value) + $data[] = preg_replace('/_[0-9]*$/', '', $key).'='.urlencode($value); + + return implode('&', $data); + } + + +} diff --git a/tools/js_minify/index.php b/tools/closure/index.php similarity index 100% rename from tools/js_minify/index.php rename to tools/closure/index.php diff --git a/tools/js_minify/jsmin.php b/tools/js_minify/jsmin.php deleted file mode 100644 index b6879f371..000000000 --- a/tools/js_minify/jsmin.php +++ /dev/null @@ -1,385 +0,0 @@ - - * $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 = ''; - - /** - * 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) - { - 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: - $this->output .= $this->a; - $this->lastByteOut = $this->a; - - // fallthrough - case self::ACTION_DELETE_A: - $this->a = $this->b; - if ($this->a === "'" || $this->a === '"') { // string literal - $str = $this->a; // in case needed for exception - while (true) { - $this->output .= $this->a; - $this->lastByteOut = $this->a; - - $this->a = $this->get(); - if ($this->a === $this->b) { // end quote - break; - } - if (ord($this->a) <= self::ORD_LF) { - 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 - case self::ACTION_DELETE_A_B: - $this->b = $this->next(); - if ($this->b === '/' && $this->isRegexpLiteral()) { // RegExp literal - $this->output .= $this->a . $this->b; - $pattern = '/'; // in case needed for exception - while (true) { - $this->a = $this->get(); - $pattern .= $this->a; - if ($this->a === '/') { // end pattern - break; // while (true) - } elseif ($this->a === '\\') { - $this->output .= $this->a; - $this->a = $this->get(); - $pattern .= $this->a; - } elseif (ord($this->a) <= self::ORD_LF) { - 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("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing - return true; - } - if (' ' === $this->a) { - $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; - } - - /** - * Get next char. Convert ctrl char to space. - * - * @return string - */ - protected function get() - { - $c = $this->lookAhead; - $this->lookAhead = null; - if ($c === null) { - if ($this->inputIndex < $this->inputLength) { - $c = $this->input[$this->inputIndex]; - $this->inputIndex += 1; - } else { - return null; - } - } - if ($c === "\r" || $c === "\n") { - return "\n"; - } - if (ord($c) < self::ORD_SPACE) { // control char - return ' '; - } - return $c; - } - - /** - * Get next char. If is ctrl character, translate to a space or newline. - * - * @return string - */ - protected function peek() - { - $this->lookAhead = $this->get(); - return $this->lookAhead; - } - - /** - * Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII? - * - * @param string $c - * - * @return bool - */ - protected function isAlphaNum($c) - { - return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126); - } - - /** - * @return string - */ - protected function singleLineComment() - { - $comment = ''; - while (true) { - $get = $this->get(); - $comment .= $get; - if (ord($get) <= self::ORD_LF) { // EOL reached - // if IE conditional comment - if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { - return "/{$comment}"; - } - return $get; - } - } - } - - /** - * @return string - * @throws JSMin_UnterminatedCommentException - */ - protected function multipleLineComment() - { - $this->get(); - $comment = ''; - while (true) { - $get = $this->get(); - if ($get === '*') { - if ($this->peek() === '/') { // end of comment reached - $this->get(); - // if comment preserved by YUI Compressor - if (0 === strpos($comment, '!')) { - return "\n/*!" . substr($comment, 1) . "*/\n"; - } - // if IE conditional comment - if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { - return "/*{$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 !== '/') { - return $get; - } - switch ($this->peek()) { - case '/': return $this->singleLineComment(); - case '*': return $this->multipleLineComment(); - default: return $get; - } - } -} - -class JSMin_UnterminatedStringException extends Exception {} -class JSMin_UnterminatedCommentException extends Exception {} -class JSMin_UnterminatedRegExpException extends Exception {}