diff --git a/app/config/minify_config.php b/app/config/minify_config.php index 9fa30251..6e3d9cd0 100644 --- a/app/config/minify_config.php +++ b/app/config/minify_config.php @@ -4,56 +4,78 @@ * * An API client for Hummingbird to manage anime and manga watch lists * - * @package HummingbirdAnimeClient - * @author Timothy J. Warren + * @package HummingbirdAnimeClient + * @author Timothy J. Warren * @copyright Copyright (c) 2015 - 2016 - * @link https://github.com/timw4mail/HummingBirdAnimeClient - * @license MIT + * @link https://github.com/timw4mail/HummingBirdAnimeClient + * @license MIT */ // -------------------------------------------------------------------------- /* $config = */require 'config.php'; -// Should we use myth to preprocess? -$use_myth = FALSE; +return [ -/* -|-------------------------------------------------------------------------- -| CSS Folder -|-------------------------------------------------------------------------- -| -| The folder where css files exist, in relation to the document root -| -*/ -$css_root = $config['asset_dir'] . '/css/'; + /* + |-------------------------------------------------------------------------- + | CSS Folder + |-------------------------------------------------------------------------- + | + | The folder where css files exist, in relation to the document root + | + */ + 'css_root' => $config['asset_dir'] . '/css/', -/* -|-------------------------------------------------------------------------- -| Path from -|-------------------------------------------------------------------------- -| -| Path fragment to rewrite in css files -| -*/ -$path_from = ''; + /* + |-------------------------------------------------------------------------- + | Path from + |-------------------------------------------------------------------------- + | + | Path fragment to rewrite in css files + | + */ + 'path_from' => '', -/* -|-------------------------------------------------------------------------- -| Path to -|-------------------------------------------------------------------------- -| -| The path fragment replacement for the css files -| -*/ -$path_to = ''; + /* + |-------------------------------------------------------------------------- + | Path to + |-------------------------------------------------------------------------- + | + | The path fragment replacement for the css files + | + */ + 'path_to' => '', -/* -|-------------------------------------------------------------------------- -| JS Folder -|-------------------------------------------------------------------------- -| -| The folder where javascript files exist, in relation to the document root -| -*/ -$js_root = $config['asset_dir'] . '/js/'; \ No newline at end of file + /* + |-------------------------------------------------------------------------- + | CSS Groups file + |-------------------------------------------------------------------------- + | + | The file where the css groups are configured + | + */ + 'css_groups_file' => realpath(__DIR__ . '/minify_css_groups.php'), + + /* + |-------------------------------------------------------------------------- + | JS Folder + |-------------------------------------------------------------------------- + | + | The folder where javascript files exist, in relation to the document root + | + */ + 'js_root' => $config['asset_dir'] . '/js/', + + /* + |-------------------------------------------------------------------------- + | JS Groups file + |-------------------------------------------------------------------------- + | + | The file where the javascript groups are configured + | + */ + 'js_groups_file' => realpath(__DIR__ . '/minify_js_groups.php'), + +]; +// End of minify_config.php \ No newline at end of file diff --git a/public/css.php b/public/css.php index 15aa615f..72eaf1b2 100644 --- a/public/css.php +++ b/public/css.php @@ -4,134 +4,181 @@ * * An API client for Hummingbird to manage anime and manga watch lists * - * @package HummingbirdAnimeClient - * @author Timothy J. Warren + * @package HummingbirdAnimeClient + * @author Timothy J. Warren * @copyright Copyright (c) 2015 - 2016 - * @link https://github.com/timw4mail/HummingBirdAnimeClient - * @license MIT + * @link https://github.com/timw4mail/HummingBirdAnimeClient + * @license MIT */ +namespace Aviat\EasyMin; + +/** + * Simple CSS Minifier + */ +class CSSMin { + + protected $css_root; + protected $path_from; + protected $path_to; + protected $group; + protected $last_modified; + + public function __construct(array $config, array $groups) + { + $group = $_GET['g']; + $this->css_root = $config['css_root']; + $this->path_from = $config['path_from']; + $this->path_to = $config['path_to']; + $this->group = $groups[$group]; + $this->last_modified = $this->get_last_modified(); + + $this->send(); + } + + /** + * Send the CSS + * + * @return void + */ + protected function send() + { + $requested_time=(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) + : 0; + + // Send 304 when not modified for faster response + if($this->last_modified === $requested_time) + { + header("HTTP/1.1 304 Not Modified"); + exit(); + } + + $css = ( ! array_key_exists('debug', $_GET)) + ? $this->compress($this->get_css()) + : $this->get_css(); + + $this->output($css); + } + + /** + * Function for compressing the CSS as tightly as possible + * + * @param string $buffer + * @return string + */ + public function compress($buffer) + { + + //Remove CSS comments + $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer); + + //Remove tabs, spaces, newlines, etc. + $buffer = preg_replace('`\s+`', ' ', $buffer); + $replace = array( + ' )' => ')', + ') ' => ')', + ' }' => '}', + '} ' => '}', + ' {' => '{', + '{ ' => '{', + ', ' => ',', + ': ' => ':', + '; ' => ';', + ); + + //Eradicate every last space! + $buffer = trim(strtr($buffer, $replace)); + $buffer = str_replace('{ ', '{', $buffer); + $buffer = str_replace('} ', '}', $buffer); + + return $buffer; + } + + /** + * Get the most recent file modification date + * + * @return int + */ + protected function get_last_modified() + { + $modified = array(); + + // Get all the css files, and concatenate them together + if(isset($this->group)) + { + foreach($this->group as $file) + { + $new_file = realpath("{$this->css_root}{$file}"); + $modified[] = filemtime($new_file); + } + } + + //Add this page for last modified check + $modified[] = filemtime(__FILE__); + + //Get the latest modified date + rsort($modified); + $last_modified = $modified[0]; + + return $last_modified; + } + + /** + * Get the css to display + * + * @return string + */ + protected function get_css() + { + $css = ''; + + if(isset($this->group)) + { + foreach($this->group as $file) + { + $new_file = realpath("{$this->css_root}{$file}"); + $css .= file_get_contents($new_file); + } + } + + // Correct paths that have changed due to concatenation + // based on rules in the config file + $css = str_replace($this->path_from, $this->path_to, $css); + + return $css; + } + + /** + * Output the CSS + * + * @return void + */ + public function output($css) + { + //This GZIPs the CSS for transmission to the user + //making file size smaller and transfer rate quicker + ob_start("ob_gzhandler"); + + header("Content-Type: text/css; charset=utf8"); + header("Cache-control: public, max-age=691200, must-revalidate"); + header("Last-Modified: ".gmdate('D, d M Y H:i:s', $this->last_modified)." GMT"); + header("Expires: ".gmdate('D, d M Y H:i:s', (filemtime(basename(__FILE__)) + 691200))." GMT"); + + echo $css; + + ob_end_flush(); + } +} + +// -------------------------------------------------------------------------- +// ! Start Minifying // -------------------------------------------------------------------------- //Get config files -require('../app/config/minify_config.php'); +$config = require('../app/config/minify_config.php'); +$groups = require($config['css_groups_file']); -//Include the css groups -$groups = require("../app/config/minify_css_groups.php"); +new CSSMin($config, $groups); -//Function for compressing the CSS as tightly as possible -/** - * @param string $buffer - */ -function compress($buffer) { - - //Remove CSS comments - $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer); - - //Remove tabs, spaces, newlines, etc. - $buffer = preg_replace('`\s+`', ' ', $buffer); - $replace = array( - ' )' => ')', - ') ' => ')', - ' }' => '}', - '} ' => '}', - ' {' => '{', - '{ ' => '{', - ', ' => ',', - ': ' => ':', - '; ' => ';', - ); - - //Eradicate every last space! - $buffer = trim(strtr($buffer, $replace)); - $buffer = str_replace('{ ', '{', $buffer); - $buffer = str_replace('} ', '}', $buffer); - - return $buffer; -} - -function get_last_modifed() -{ - global $groups, $css_root; - - $modified = array(); - - // Get all the css files, and concatenate them together - if(isset($groups[$_GET['g']])) - { - foreach($groups[$_GET['g']] as $file) - { - $new_file = realpath($css_root.$file); - $modified[] = filemtime($new_file); - } - } - - //Add myth css file for last modified check - $modified[] = filemtime(realpath("css/base.myth.css")); - - //Add this page for last modified check - $modified[] = filemtime(__FILE__); - - //Get the latest modified date - rsort($modified); - $last_modified = $modified[0]; - - return $last_modified; -} - -function get_css() -{ - global $groups, $path_from, $path_to, $css_root; - - $css = ''; - - if(isset($groups[$_GET['g']])) - { - foreach($groups[$_GET['g']] as $file) - { - $new_file = realpath($css_root.$file); - $css .= file_get_contents($new_file); - $modified[] = filemtime($new_file); - } - } - - // If not in debug mode, minify the css - if( ! isset($_GET['debug'])) - { - $css = compress($css); - } - - // Correct paths that have changed due to concatenation - // based on rules in the config file - $css = strtr($css, $path_from, $path_to); - - return $css; -} - -// -------------------------------------------------------------------------- -$last_modified = get_last_modifed(); - -$requested_time=(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) - ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) - : 0; - -// Send 304 when not modified for faster response -if($last_modified === $requested_time) -{ - header("HTTP/1.1 304 Not Modified"); - exit(); -} - -//This GZIPs the CSS for transmission to the user -//making file size smaller and transfer rate quicker -ob_start("ob_gzhandler"); - -header("Content-Type: text/css; charset=utf8"); -header("Cache-control: public, max-age=691200, must-revalidate"); -header("Last-Modified: ".gmdate('D, d M Y H:i:s', $last_modified)." GMT"); -header("Expires: ".gmdate('D, d M Y H:i:s', (filemtime(basename(__FILE__)) + 691200))." GMT"); - -echo get_css(); - -ob_end_flush(); //End of css.php \ No newline at end of file diff --git a/public/js.php b/public/js.php index eb74f0b9..00936d91 100644 --- a/public/js.php +++ b/public/js.php @@ -4,226 +4,254 @@ * * An API client for Hummingbird to manage anime and manga watch lists * - * @package HummingbirdAnimeClient - * @author Timothy J. Warren + * @package HummingbirdAnimeClient + * @author Timothy J. Warren * @copyright Copyright (c) 2015 - 2016 - * @link https://github.com/timw4mail/HummingBirdAnimeClient - * @license MIT + * @link https://github.com/timw4mail/HummingBirdAnimeClient + * @license MIT */ -// -------------------------------------------------------------------------- +namespace Aviat\EasyMin; + use GuzzleHttp\Client; use GuzzleHttp\Psr7\Request; -//Get config files -require_once('../app/config/minify_config.php'); - -//Include the js groups -$groups_file = '../app/config/minify_js_groups.php'; -$groups = require_once($groups_file); - // Include guzzle require_once('../vendor/autoload.php'); -//The name of this file -$this_file = __FILE__; - -// -------------------------------------------------------------------------- - /** - * Get Files - * - * Concatenates the javascript files for the current - * group as a string - * @return string + * Simple Javascript minfier, using google closure compiler */ -function get_files() -{ - global $groups, $js_root; +class JSMin { - $js = ''; + protected $js_root; + protected $js_group; + protected $js_groups_file; + protected $cache_file; - foreach($groups[$_GET['g']] as $file) + protected $last_modified; + protected $requested_time; + protected $cache_modified; + + public function __construct(array $config, array $groups) { - $new_file = realpath($js_root.$file); - $js .= file_get_contents($new_file) . "\n\n"; + $group = $_GET['g']; + + $this->js_root = $config['js_root']; + $this->js_group = $groups[$group]; + $this->js_groups_file = $config['js_groups_file']; + $this->cache_file = "{$this->js_root}cache/{$group}"; + $this->last_modified = $this->get_last_modified(); + + $this->requested_time = (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) + : time(); + + $this->cache_modified = (is_file($this->cache_file)) + ? filemtime($this->cache_file) + : 0; + + // Output some JS! + $this->send(); } - return $js; -} - -// -------------------------------------------------------------------------- - -/** - * Google Min - * - * Minifies javascript using google's closure compiler - * @param string $new_file - * @return string - */ -function google_min($new_file) -{ - $options = [ - 'output_info' => 'errors', - 'output_format' => 'json', - 'compilation_level' => 'SIMPLE_OPTIMIZATIONS', - //'compilation_level' => 'ADVANCED_OPTIMIZATIONS', - 'js_code' => $new_file, - 'language' => 'ECMASCRIPT6_STRICT', - 'language_out' => 'ECMASCRIPT5_STRICT' - ]; - - // First check for errors - $error_client = new Client(); - $error_res = $error_client->post('http://closure-compiler.appspot.com/compile', [ - 'headers' => [ - 'Accept-Encoding' => 'gzip', - 'Content-type' => 'application/x-www-form-urlencoded' - ], - 'form_params' => $options - ]); - - $error_json = $error_res->getBody(); - $error_obj = json_decode($error_json) ?: (object)[]; - - // Show error if exists - if ( ! empty($error_obj->errors)) + protected function send() { - $error_json = json_encode($error_obj, JSON_PRETTY_PRINT); - echo "console.error(${error_json});"; - die(); + // If the browser's cached version is up to date, + // don't resend the file + if($this->last_modified === $this->requested_time) + { + header('HTTP/1.1 304 Not Modified'); + exit(); + } + + //Determine what to do: rebuild cache, send files as is, or send cache. + // If debug is set, just concatenate + if(array_key_exists('debug', $_GET)) + { + return $this->output($this->get_files()); + } + else if($this->cache_modified < $this->last_modified) + { + $js = $this->minify($this->get_files()); + + //Make sure cache file gets created/updated + if(file_put_contents($this->cache_file, $js) === FALSE) + { + die('Cache file was not created. Make sure you have the correct folder permissions.'); + } + + return $this->output($js); + } + // Otherwise, send the cached file + else + { + return $this->output(file_get_contents($cache_file)); + } } - // Now actually retrieve the compiled code - $options['output_info'] = 'compiled_code'; - $client = new Client(); - $res = $client->post('http://closure-compiler.appspot.com/compile', [ - 'headers' => [ - 'Accept-Encoding' => 'gzip', - 'Content-type' => 'application/x-www-form-urlencoded' - ], - 'form_params' => $options - ]); - - $json = $res->getBody(); - $obj = json_decode($json); - - return $obj->compiledCode; -} - -// -------------------------------------------------------------------------- - -//Creative rewriting of /g/groupname to ?g=groupname -$pi = $_SERVER['PATH_INFO']; -$pia = explode('/', $pi); - -$pia_len = count($pia); -$i = 1; - -while($i < $pia_len) -{ - $j = $i+1; - $j = (isset($pia[$j])) ? $j : $i; - - $_GET[$pia[$i]] = $pia[$j]; - - $i = $j + 1; -}; - -// -------------------------------------------------------------------------- - -$js = ''; -$modified = array(); - -// -------------------------------------------------------------------------- - -//Aggregate the last modified times of the files -if(isset($groups[$_GET['g']])) -{ - if ( ! is_dir($js_root . 'cache')) + /** + * Makes a call to google closure compiler service + * + * @param array $options - Form parameters + * @return object + */ + protected function closure_call(array $options) { - mkdir($js_root . 'cache'); - } - $cache_file = $js_root.'cache/'.$_GET['g']; + $client = new Client(); + $response = $client->post('http://closure-compiler.appspot.com/compile', [ + 'headers' => [ + 'Accept-Encoding' => 'gzip', + 'Content-type' => 'application/x-www-form-urlencoded' + ], + 'form_params' => $options + ]); - foreach($groups[$_GET['g']] as $file) - { - $new_file = realpath($js_root.$file); - $modified[] = filemtime($new_file); + return $response; } - //Add this page too, as well as the groups file - $modified[] = filemtime($this_file); - $modified[] = filemtime($groups_file); - - $cache_modified = 0; - - //Add the cache file - if(is_file($cache_file)) + /** + * Do a call to the closure compiler to check for compilation errors + * + * @param array $options + * @return void + */ + protected function check_minify_errors($options) { - $cache_modified = filemtime($cache_file); + $error_res = $this->closure_call($options); + $error_json = $error_res->getBody(); + $error_obj = json_decode($error_json) ?: (object)[]; + + // Show error if exists + if ( ! empty($error_obj->errors)) + { + $error_json = json_encode($error_obj, JSON_PRETTY_PRINT); + echo "console.error(${error_json});"; + die(); + } + } + + /** + * Get Files + * + * Concatenates the javascript files for the current + * group as a string + * + * @return string + */ + protected function get_files() + { + $js = ''; + + foreach($this->js_group as $file) + { + $new_file = realpath("{$this->js_root}{$file}"); + $js .= file_get_contents($new_file) . "\n\n"; + } + + return $js; + } + + /** + * Get the most recent modified date + * + * @return int + */ + protected function get_last_modified() + { + $modified = array(); + + foreach($this->js_group as $file) + { + $new_file = realpath("{$this->js_root}{$file}"); + $modified[] = filemtime($new_file); + } + + //Add this page too, as well as the groups file + $modified[] = filemtime(__FILE__); + $modified[] = filemtime($this->js_groups_file); + + rsort($modified); + $last_modified = $modified[0]; + + return $last_modified; + } + + /** + * Minifies javascript using google's closure compiler + * + * @param string $js + * @return string + */ + public function minify($js) + { + $options = [ + 'output_info' => 'errors', + 'output_format' => 'json', + 'compilation_level' => 'SIMPLE_OPTIMIZATIONS', + //'compilation_level' => 'ADVANCED_OPTIMIZATIONS', + 'js_code' => $js, + 'language' => 'ECMASCRIPT6_STRICT', + 'language_out' => 'ECMASCRIPT5_STRICT' + ]; + + // Check for errors + $this->check_minify_errors($options); + + // Now actually retrieve the compiled code + $options['output_info'] = 'compiled_code'; + $res = $this->closure_call($options); + $json = $res->getBody(); + $obj = json_decode($json); + + return $obj->compiledCode; + } + + /** + * Output the minified javascript + * + * @param int $last_modified + * @param string $js + * @return void + */ + protected function output($js) + { + //This GZIPs the js for transmission to the user + //making file size smaller and transfer rate quicker + ob_start('ob_gzhandler'); + + // Set important caching headers + header('Content-Type: application/javascript; charset=utf8'); + header('Cache-control: public, max-age=691200, must-revalidate'); + header('Last-Modified: '.gmdate('D, d M Y H:i:s', $this->last_modified).' GMT'); + header('Expires: '.gmdate('D, d M Y H:i:s', (filemtime(__FILE__) + 691200)).' GMT'); + + echo $js; + + ob_end_flush(); } } -else //Nothing to display? Just exit -{ - die('You must specify a group that exists'); -} // -------------------------------------------------------------------------- - -//Get the latest modified date -rsort($modified); -$last_modified = $modified[0]; - -$requested_time=(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) - ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) - : time(); - -// If the browser's cached version is up to date, -// don't resend the file -if($last_modified === $requested_time) -{ - header('HTTP/1.1 304 Not Modified'); - exit(); -} - +// ! Start Minifying // -------------------------------------------------------------------------- -//Determine what to do: rebuild cache, send files as is, or send cache. -// If debug is set, just concatenate -if(array_key_exists('debug', $_GET)) -{ - $js = get_files(); -} -else if($cache_modified < $last_modified) -{ - $js = google_min(get_files()); +$config = require_once('../app/config/minify_config.php'); +$groups = require_once($config['js_groups_file']); +$cache_dir = "{$config['js_root']}cache"; - //Make sure cache file gets created/updated - if(file_put_contents($cache_file, $js) === FALSE) - { - die('Cache file was not created. Make sure you have the correct folder permissions.'); - } -} -// Otherwise, send the cached file -else +if ( ! is_dir($cache_dir)) { - $js = file_get_contents($cache_file); + mkdir($cache_dir); } -// -------------------------------------------------------------------------- +if ( ! array_key_exists($_GET['g'], $groups)) +{ + header('Content-Type: application/javascript; charset=utf8'); + echo '// You must specify a group that exists'; + die(); +} -//This GZIPs the js for transmission to the user -//making file size smaller and transfer rate quicker -ob_start('ob_gzhandler'); +new JSMin($config, $groups); -// Set important caching headers -header('Content-Type: application/javascript; charset=utf8'); -header('Cache-control: public, max-age=691200, must-revalidate'); -header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_modified).' GMT'); -header('Expires: '.gmdate('D, d M Y H:i:s', (filemtime($this_file) + 691200)).' GMT'); - -echo $js; - -ob_end_flush(); //end of js.php \ No newline at end of file