Skip to content
Browse files

Merge branch 'release/1.12'

* release/1.12: (37 commits)
  Update changelog
  Bump version to v1.12
  Fix bug in add_deps().
  Update JSmin and CSSmin libraries to the latest stable versions from their master.
  Put the only call to Cssurirewriter inside load_file
  Rewrite CSS URIs when inlined and not minified
  Remove $attr because is not defined yet.
  Any non-string asset names will be ignored
  Add note about using  with add_group() in 'Extra Attributes'
  Another readme typo
  Fix small readme typo
  Remove typo in readme
  Remove trailing whitespace
  Change a ton of private methods to protected
  Add option prep method so group options are passed through to the add_group method.
  If set_group_option is used with all groups ('*'), then change the default.
  Create the global groups on initialisation, if it doesn't exist.
  set_group_option() will throw an exception if the named group doesn't exist.
  Add new function to tell whether a group exists.
  $<param>_default vars have been removed, in favour of an array of default options
  ...
  • Loading branch information...
2 parents 4340edd + 10cf9b1 commit b71cbe6fa582a3d6461a68a04e3f58040e853bce @canton7 canton7 committed Feb 4, 2012
Showing with 1,059 additions and 819 deletions.
  1. +1 −1 bootstrap.php
  2. +13 −0 changelog.md
  3. +203 −105 classes/casset.php
  4. +182 −188 classes/casset/csscompressor.php
  5. +256 −252 classes/casset/cssurirewriter.php
  6. +282 −250 classes/casset/jsmin.php
  7. +23 −5 config/casset.php
  8. +99 −18 readme.md
View
2 bootstrap.php
@@ -4,7 +4,7 @@
* Casset: Convenient asset library for FuelPHP.
*
* @package Casset
- * @version v1.11
+ * @version v1.12
* @author Antony Male
* @license MIT License
* @copyright 2011 Antony Male
View
13 changelog.md
@@ -3,6 +3,19 @@ Changelog
This file lists the important changes between versions. For a list of minor changes, check the log.
+v1.12
+-----
+ - `<link>` and `<script>` tags now respect whether document is HTML5
+ - New hook: filepath_callback. Allows you to modify the URL, as used by Casset, of js/css files, and images. See the readme.
+ - New function: group_exists.
+ - set_group_option changes default group options (for add future groups) when used with '*' as the group name.
+ - Lots of private methods are now protected.
+ - CSS URIs are rewritten when the stylesheet is inlined and not minified.
+ - Update to minification libraries.
+ - Any non-string assets names are ignored.
+ - Fix bug with generating stylesheet tags.
+ - Various documentation additions and fixes.
+
v1.11
-----
- Asset URLs can be obtained, see "Getting asset paths / urls".
View
308 classes/casset.php
@@ -4,7 +4,7 @@
* Casset: Convenient asset library for FuelPHP.
*
* @package Casset
- * @version v1.11
+ * @version v1.12
* @author Antony Male
* @license MIT License
* @copyright 2011 Antony Male
@@ -65,25 +65,17 @@ class Casset {
);
/**
- * @var bool Whether to minfy.
- */
- protected static $min_default = true;
-
- /**
- * @var bool Whether to combine
- */
- protected static $combine_default = true;
-
- /**
- * @var bool Whether to render files inline by default.
- */
- protected static $inline_default = false;
-
- /**
*
- * @var array The default attributes when creating the asset's tag.
+ * @var array Defaults for a group
*/
- protected static $attr_default = array();
+ protected static $default_options = array(
+ 'enabled' => true,
+ 'combine' => true,
+ 'min' => true,
+ 'inline' => false,
+ 'attr' => array(),
+ 'deps' => array(),
+ );
/**
* @var int How deep to go when resolving deps
@@ -110,6 +102,14 @@ class Casset {
*/
protected static $post_load_callback = null;
+ /*
+ * @var function If given, the function to call when we've decided on the name
+ * for a file, but want to allow the user to tweak it before we write it to the
+ * page.
+ * Prototype: callback($filepath, $type, $remote);
+ */
+ protected static $filepath_callback = null;
+
/**
* @var array Keeps a record of which groups have been rendered.
* We then check this when deciding whether to render a dep.
@@ -151,39 +151,60 @@ public static function _init()
static::$cache_path = \Config::get('casset.cache_path', static::$cache_path);
- static::$min_default = \Config::get('casset.min', static::$min_default);
- static::$combine_default = \Config::get('casset.combine', static::$combine_default);
+ static::$default_options['min'] = \Config::get('casset.min', static::$default_options['min']);
+ static::$default_options['combine'] = \Config::get('casset.combine', static::$default_options['combine']);
static::$deps_max_depth = \Config::get('casset.deps_max_depth', static::$deps_max_depth);
-
$group_sets = \Config::get('casset.groups', array());
foreach ($group_sets as $group_type => $groups)
{
foreach ($groups as $group_name => $group)
{
- $options = array(
- 'enabled' => array_key_exists('enabled', $group) ? $group['enabled'] : true,
- 'combine' => array_key_exists('combine', $group) ? $group['combine'] : static::$combine_default,
- 'min' => array_key_exists('min', $group) ? $group['min'] : static::$min_default,
- 'inline' => array_key_exists('inline', $group) ? $group['inline'] : static::$inline_default,
- 'attr' => array_key_exists('attr', $group) ? $group['attr'] : static::$attr_default,
- 'deps' => array_key_exists('deps', $group) ? $group['deps'] : array(),
- );
+ $options = static::prep_new_group_options($group);
static::add_group($group_type, $group_name, $group['files'], $options);
}
}
+ // Add the global group if it doesn't already exist.
+ // This is so that set_group_option() can be used on it. This function will
+ // throw an exception if the named group doesn't exist.
+ if (!static::group_exists('js', 'global'))
+ static::add_group_base('js', 'global');
+ if (!static::group_exists('css', 'global'))
+ static::add_group_base('css', 'global');
+
static::$show_files = \Config::get('casset.show_files', static::$show_files);
static::$show_files_inline = \Config::get('casset.show_files_inline', static::$show_files_inline);
static::$post_load_callback = \Config::get('casset.post_load_callback', static::$post_load_callback);
+ static::$filepath_callback = \Config::get('casset.filepath_callback', static::$filepath_callback);
+
static::$initialized = true;
}
+ /**
+ * Sets up options for new groups setup via casset/config.php.
+ * Abstracts away from _init method. Also easier if options are
+ * added in future as iterates through defaults to do checking.
+ *
+ * @param array $options Options as defined in group in config.php
+ * @return void
+ */
+ protected static function prep_new_group_options($group_options)
+ {
+ $options = array();
+ foreach (static::$default_options as $key => $option_val) {
+ if (array_key_exists($key, $group_options)) {
+ $options[$key] = $group_options[$key];
+ }
+ }
+ return $options;
+ }
+
/**
* Parses oen of the 'paths' config keys into the format used internally.
@@ -245,17 +266,10 @@ public static function set_path($path_key = 'core')
* 'deps' => array(),
* );
*/
- private static function add_group_base($group_type, $group_name, $options = array())
+ protected static function add_group_base($group_type, $group_name, $options = array())
{
// Insert defaults
- $options = array_merge(array(
- 'enabled' => true,
- 'combine' => static::$combine_default,
- 'min' => static::$min_default,
- 'inline' => static::$inline_default,
- 'attr' => static::$attr_default,
- 'deps' => array(),
- ), $options);
+ $options += static::$default_options;
if (!is_array($options['deps']))
$options['deps'] = array($options['deps']);
$options['files'] = array();
@@ -305,6 +319,17 @@ public static function add_group($group_type, $group_name, $files, $options = ar
}
/**
+ * Returns true if the given group exists
+ *
+ * @param string $group_type 'js' or 'css
+ * @param type $group_name the nam eof the group
+ */
+ public static function group_exists($group_type, $group_name)
+ {
+ return array_key_exists($group_name, static::$groups[$group_type]);
+ }
+
+ /**
* Enables both js and css groups of the given name.
*
* @param mixed $group The group to enable, or array of groups
@@ -373,7 +398,7 @@ public static function disable_css($groups)
* @param string $group The group to enable/disable, or array of groups
* @param bool $enabled True to enabel to group, false odisable
*/
- private static function asset_enabled($type, $groups, $enabled)
+ protected static function asset_enabled($type, $groups, $enabled)
{
if (!is_array($groups))
$groups = array($groups);
@@ -400,7 +425,11 @@ public static function set_group_option($type, $group_names, $option_key, $optio
if ($group_names == '')
$group_names = array('global');
else if ($group_names == '*')
+ {
+ // Change the default
+ static::$default_options[$option_key] = $option_value;
$group_names = array_keys(static::$groups[$type]);
+ }
else if (!is_array($group_names))
$group_names = array($group_names);
@@ -409,7 +438,12 @@ public static function set_group_option($type, $group_names, $option_key, $optio
$option_value = array($option_value);
foreach ($group_names as $group_name)
+ {
+ // If the group doesn't exist, throw a fuss
+ if (!static::group_exists($type, $group_name))
+ throw new Casset_Exception("Can't set option for group '$group_name' ($type), as it doesn't exist.");
static::$groups[$type][$group_name][$option_key] = $option_value;
+ }
}
/**
@@ -473,8 +507,13 @@ public static function css($sheet, $sheet_min = false, $group = 'global')
* If omitted, $script will be minified internally
* @param string $group The group to add this asset to
*/
- private static function add_asset($type, $script, $script_min, $group)
+ protected static function add_asset($type, $script, $script_min, $group)
{
+ // Allow the user to specify any non-string value for an asset, and it
+ // will be ignore. This can be handy when using ternary operators
+ // in the groups config.
+ if (!is_string($script))
+ return;
// Don't force the user to remember that 'false' is used when not supplying
// a pre-minified file.
if (!is_string($script_min))
@@ -523,7 +562,7 @@ public static function css_inline($content)
* @param string $type 'css' / 'js'
* @param string $content The css / js to add
*/
- private static function add_asset_inline($type, $content)
+ protected static function add_asset_inline($type, $content)
{
array_push(static::$inline_assets[$type], $content);
}
@@ -586,14 +625,14 @@ public static function get_filepath($filename, $type, $add_url = false, $force_a
if (strpos($filename, '::') === false)
$filename = static::$default_path_key.'::'.$filename;
$files = static::find_files($filename, $type);
- if ($add_url)
+ foreach ($files as &$file)
{
- foreach ($files as &$file)
- {
- if (strpos($file, '//') !== false)
- continue;
+ $remote = (strpos($file, '//') !== false);
+ $file = static::process_filepath($file, $type, $remote);
+ if ($remote)
+ continue;
+ if ($add_url)
$file = static::$asset_url.$file;
- }
}
if (count($files) == 1 && !$force_array)
return $files[0];
@@ -607,13 +646,15 @@ public static function get_filepath($filename, $type, $add_url = false, $force_a
* @param string $group The group name to add deps to
* @param array $deps An array of group names to add as deps.
*/
- public static function add_deps($type, $group, $deps)
+ public static function add_deps($type, $group, $new_deps)
{
- if (!is_array($deps))
- $deps = array($deps);
+ if (!is_array($new_deps))
+ $new_deps = array($new_deps);
if (!array_key_exists($group, static::$groups[$type]))
throw new \Fuel_Exception("Group $group ($type) doesn't exist, so can't add deps to it.");
- array_push(static::$groups[$type][$group]['deps'], $deps);
+ // Avoid duplicates in deps array
+ $deps = &static::$groups[$type][$group]['deps'];
+ $deps = array_unique(array_merge($deps, $new_deps));
}
/**
@@ -636,6 +677,26 @@ public static function add_css_deps($group, $deps)
static::add_deps('css', $group, $deps);
}
+
+ /**
+ * Sticks the given filename through the filepath callback, if given.
+ *
+ * @param string $filepath The filepath to process
+ * @param string $type The type of asset, passed to the callback
+ * @param bool $remote Whether the asset is on another machine, passed to the callback
+ */
+ protected static function process_filepath($filepath, $type, $remote = null)
+ {
+ if (static::$filepath_callback)
+ {
+ if ($remote === null)
+ $remote = (strpos($filepath, '//') !== false);
+ $func = static::$filepath_callback;
+ $filepath = $func($filepath, $type, $remote);
+ }
+ return $filepath;
+ }
+
/**
* Shortcut to render_js() and render_css().
*
@@ -661,13 +722,13 @@ public static function render($group = false, $inline_dep = null, $attr = array(
* a cache file to be written for speed purposes
* @return string The javascript tags to be written to the page
*/
- public static function render_js($group = false, $inline = null, $attr = array())
+ public static function render_js($group = false, $inline_dep = null, $attr_dep = array())
{
// Don't force the user to remember that false is used for ommitted non-bool arguments
if (!is_string($group))
$group = false;
- if (!is_array($attr))
- $attr = array();
+ if (!is_array($attr_dep))
+ $attr_dep = array();
$file_groups = static::files_to_render('js', $group);
@@ -677,11 +738,15 @@ public static function render_js($group = false, $inline = null, $attr = array()
{
// We used to take $inline as 2nd argument. However, we now use a group option.
// It's easiest if we let $inline override this group option, though.
- if ($inline === null)
- $inline = static::$groups['js'][$group_name]['inline'];
+ $inline = ($inline_dep === null) ? static::$groups['js'][$group_name]['inline'] : $inline_dep;
+
// $attr is also deprecated. If specified, entirely overrides the group option.
- if (!count($attr))
- $attr = static::$groups['js'][$group_name]['attr'];
+ $attr = (!count($attr_dep)) ? static::$groups['js'][$group_name]['attr'] : $attr_dep;
+
+ // the type attribute is not required for script elements under html5
+ // @link http://www.w3.org/TR/html5/scripting-1.html#attr-script-type
+ if (!\Html::$html5)
+ $attr = array( 'type' => 'text/javascript' ) + $attr;
if (static::$groups['js'][$group_name]['combine'])
{
@@ -693,25 +758,28 @@ public static function render_js($group = false, $inline = null, $attr = array()
}, $file_group)).'-->'.PHP_EOL;
}
if ($inline)
- $ret .= html_tag('script', array('type' => 'text/javascript')+$attr, PHP_EOL.file_get_contents(DOCROOT.static::$cache_path.$filename).PHP_EOL).PHP_EOL;
+ $ret .= html_tag('script', $attr, PHP_EOL.file_get_contents(DOCROOT.static::$cache_path.$filename).PHP_EOL).PHP_EOL;
else
+ {
+ $filepath = static::process_filepath(static::$cache_path.$filename, 'js');
$ret .= html_tag('script', array(
- 'type' => 'text/javascript',
- 'src' => static::$asset_url.static::$cache_path.$filename,
+ 'src' => static::$asset_url.$filepath,
)+$attr, '').PHP_EOL;
+ }
}
else
{
foreach ($file_group as $file)
{
if ($inline)
- $ret .= html_tag('script', array('type' => 'text/javascript')+$attr, PHP_EOL.file_get_contents($file['file']).PHP_EOL).PHP_EOL;
+ $ret .= html_tag('script', $attr, PHP_EOL.file_get_contents($file['file']).PHP_EOL).PHP_EOL;
else
{
- $base = (strpos($file['file'], '//') === false) ? static::$asset_url : '';
+ $remote = (strpos($file['file'], '//') !== false);
+ $base = ($remote) ? '' : static::$asset_url;
+ $filepath = static::process_filepath($file['file'], 'js', $remote);
$ret .= html_tag('script', array(
- 'type' => 'text/javascript',
- 'src' => $base.$file['file'],
+ 'src' => $base.$filepath,
)+$attr, '').PHP_EOL;
}
}
@@ -729,13 +797,13 @@ public static function render_js($group = false, $inline = null, $attr = array()
* a cache file to be written for speed purposes
* @return string The css tags to be written to the page
*/
- public static function render_css($group = false, $inline = null, $attr = array())
+ public static function render_css($group = false, $inline_dep = null, $attr_dep = array())
{
// Don't force the user to remember that false is used for ommitted non-bool arguments
if (!is_string($group))
$group = false;
- if (!is_array($attr))
- $attr = array();
+ if (!is_array($attr_dep))
+ $attr_dep = array();
$file_groups = static::files_to_render('css', $group);
@@ -745,15 +813,19 @@ public static function render_css($group = false, $inline = null, $attr = array(
{
// We used to take $inline as 2nd argument. However, we now use a group option.
// It's easiest if we let $inline override this group option, though.
- if ($inline === null)
- $inline = static::$groups['css'][$group_name]['inline'];
+ $inline = ($inline_dep === null) ? static::$groups['css'][$group_name]['inline'] : $inline_dep;
+
// $attr is also deprecated. If specified, entirely overrides the group option.
- if (!count($attr))
- $attr = static::$groups['css'][$group_name]['attr'];
+ $attr = (!count($attr_dep)) ? static::$groups['css'][$group_name]['attr'] : $attr_dep;
+
+ // the type attribute is not required for style or link[rel="stylesheet"] elements under html5
+ // @link http://www.w3.org/TR/html5/links.html#link-type-stylesheet
+ // @link http://www.w3.org/TR/html5/semantics.html#attr-style-type
+ if (!\Html::$html5)
+ $attr = array( 'type' => 'text/css' ) + $attr;
if (static::$groups['css'][$group_name]['combine'])
{
-
$filename = static::combine('css', $file_group, static::$groups['css'][$group_name]['min'], $inline);
if (!$inline && static::$show_files)
{
@@ -762,27 +834,33 @@ public static function render_css($group = false, $inline = null, $attr = array(
}, $file_group)).'-->'.PHP_EOL;
}
if ($inline)
- $ret .= html_tag('style', array('type' => 'text/css')+$attr, PHP_EOL.file_get_contents(DOCROOT.static::$cache_path.$filename).PHP_EOL).PHP_EOL;
+ $ret .= html_tag('style', $attr, PHP_EOL.file_get_contents(DOCROOT.static::$cache_path.$filename).PHP_EOL).PHP_EOL;
else
+ {
+ $filepath = static::process_filepath(static::$cache_path.$filename, 'css');
$ret .= html_tag('link', array(
'rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => static::$asset_url.static::$cache_path.$filename,
+ 'href' => static::$asset_url.$filepath,
)+$attr).PHP_EOL;
+ }
}
else
{
foreach ($file_group as $file)
{
if ($inline)
- $ret .= html_tag('style', array('type' => 'text/css')+$attr, PHP_EOL.file_get_contents($file['file']).PHP_EOL).PHP_EOL;
+ {
+ $content = static::load_file($file['file'], 'css');
+ $ret .= html_tag('style', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
+ }
else
{
- $base = (strpos($file['file'], '//') === false) ? static::$asset_url : '';
+ $remote = (strpos($file['file'], '//') !== false);
+ $base = ($remote) ? '' : static::$asset_url;
+ $filepath = static::process_filepath($file['file'], 'css', $remote);
$ret .= html_tag('link', array(
'rel' => 'stylesheet',
- 'type' => 'text/css',
- 'href' => $base.$file['file'],
+ 'href' => $base.$filepath,
)+$attr).PHP_EOL;
}
}
@@ -798,7 +876,7 @@ public static function render_css($group = false, $inline = null, $attr = array(
* @param string $asset_type 'css', 'js' or 'img'
* @return string The path to the asset, relative to $asset_url
*/
- private static function find_files($file, $asset_type)
+ protected static function find_files($file, $asset_type)
{
$parts = explode('::', $file, 2);
if (!array_key_exists($parts[0], static::$asset_paths))
@@ -841,7 +919,7 @@ private static function find_files($file, $asset_type)
* @return array List of group names with deps resolved
*/
- private static function resolve_deps($type, $group_names, $depth=0)
+ protected static function resolve_deps($type, $group_names, $depth=0)
{
if ($depth > static::$deps_max_depth)
{
@@ -877,7 +955,7 @@ private static function resolve_deps($type, $group_names, $depth=0)
* @param array $group The groups to render. If false, takes all groups
* @return array An array of array('file' => file_name, 'minified' => whether_minified)
*/
- private static function files_to_render($type, $group)
+ protected static function files_to_render($type, $group)
{
// If no group specified, print all groups.
if ($group == false)
@@ -938,7 +1016,7 @@ private static function files_to_render($type, $group)
* @param type $filename
* @return type
*/
- private static function load_file($filename, $type, $file_group)
+ protected static function load_file($filename, $type, $file_group = false)
{
$content = file_get_contents($filename);
if (static::$post_load_callback != null)
@@ -947,6 +1025,8 @@ private static function load_file($filename, $type, $file_group)
$func = static::$post_load_callback;
$content = $func($content, $filename, $type, $file_group);
}
+ if ($type == 'css')
+ $content = Casset_Cssurirewriter::rewrite($content, dirname($filename));
return $content;
}
@@ -961,12 +1041,8 @@ private static function load_file($filename, $type, $file_group)
* @param bool $minify whether to minify the files, as well as combining them
* @return string The path to the cache file which was written.
*/
- private static function combine($type, $file_group, $minify, $inline)
+ protected static function combine($type, $file_group, $minify, $inline)
{
- $filename = md5(implode('', array_map(function($a) {
- return $a['file'];
- }, $file_group)).($minify ? 'min' : '')).'.'.$type;
-
// Get the last modified time of all of the component files
$last_mod = 0;
foreach ($file_group as $file)
@@ -981,8 +1057,12 @@ private static function combine($type, $file_group, $minify, $inline)
$last_mod = $mod;
}
+ $filename = md5(implode('', array_map(function($a) {
+ return $a['file'];
+ }, $file_group)).($minify ? 'min' : '').$last_mod).'.'.$type;
+
$filepath = DOCROOT.static::$cache_path.'/'.$filename;
- $needs_update = (!file_exists($filepath) || ($mtime = filemtime($filepath)) < $last_mod);
+ $needs_update = (!file_exists($filepath));
if ($needs_update)
{
@@ -992,13 +1072,7 @@ private static function combine($type, $file_group, $minify, $inline)
if (static::$show_files_inline)
$content .= PHP_EOL.'/* '.$file['file'].' */'.PHP_EOL.PHP_EOL;
if ($file['minified'] || !$minify)
- {
- $content_temp = static::load_file($file['file'], $type, $file_group).PHP_EOL;
- if ($type == 'css')
- $content .= Casset_Cssurirewriter::rewrite($content_temp, dirname($file['file']));
- else
- $content .= $content_temp;
- }
+ $content = static::load_file($file['file'], $type, $file_group).PHP_EOL;
else
{
$file_content = static::load_file($file['file'], $type, $file_group);
@@ -1010,16 +1084,14 @@ private static function combine($type, $file_group, $minify, $inline)
}
elseif ($type == 'css')
{
- $css = Casset_Csscompressor::process($file_content).PHP_EOL;
- $content .= Casset_Cssurirewriter::rewrite($css, dirname($file['file']));
+ $content .= Casset_Csscompressor::process($file_content).PHP_EOL;
}
}
}
file_put_contents($filepath, $content, LOCK_EX);
$mtime = time();
}
- if (!$inline)
- $filename .= '?'.$mtime;
+
return $filename;
}
@@ -1030,10 +1102,18 @@ private static function combine($type, $file_group, $minify, $inline)
*/
public static function render_js_inline()
{
+
+ // the type attribute is not required for script elements under html5
+ // @link http://www.w3.org/TR/html5/scripting-1.html#attr-script-type
+ if (!\Html::$html5)
+ $attr = array( 'type' => 'text/javascript' );
+ else
+ $attr = array();
+
$ret = '';
foreach (static::$inline_assets['js'] as $content)
{
- $ret .= html_tag('script', array('type' => 'text/javascript'), PHP_EOL.$content.PHP_EOL).PHP_EOL;
+ $ret .= html_tag('script', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
}
return $ret;
}
@@ -1045,10 +1125,18 @@ public static function render_js_inline()
*/
public static function render_css_inline()
{
+
+ // the type attribute is not required for style elements under html5
+ // @link http://www.w3.org/TR/html5/semantics.html#attr-style-type
+ if (!\Html::$html5)
+ $attr = array( 'type' => 'text/css' );
+ else
+ $attr = array();
+
$ret = '';
foreach (static::$inline_assets['css'] as $content)
{
- $ret .= html_tag('script', array('type' => 'text/javascript'), PHP_EOL.$content.PHP_EOL).PHP_EOL;
+ $ret .= html_tag('style', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
}
return $ret;
}
@@ -1063,6 +1151,14 @@ public static function set_post_load_callback($callback) {
}
/**
+ * Sets the filepath callback
+ * @param function The function to set
+ */
+ public static function set_filepath_callback($callback) {
+ static::$filepath_callback = $callback;
+ }
+
+ /**
* Locates the given image(s), and returns the resulting <img> tag.
*
* @param mixed $images Image(s) to print. Can be string or array of strings
@@ -1083,7 +1179,9 @@ public static function img($images, $alt, $attr = array())
$image_paths = static::find_files($image, 'img');
foreach ($image_paths as $image_path)
{
- $base = (strpos($image_path, '//') === false) ? static::$asset_url : '';
+ $remote = (strpos($image_path, '//') !== false);
+ $image_path = static::process_filepath($image_path, 'img', $remote);
+ $base = ($remote) ? '' : static::$asset_url;
$attr['src'] = $base.$image_path;
$ret .= html_tag('img', $attr);
}
@@ -1131,7 +1229,7 @@ public static function clear_css_cache($before = 'now')
* @param type $before Time before which to delete files. Defaults to 'now'.
* Uses strtotime.
*/
- private static function clear_cache_base($filter = '*', $before = 'now')
+ protected static function clear_cache_base($filter = '*', $before = 'now')
{
$before = strtotime($before);
$files = glob(DOCROOT.static::$cache_path.$filter);
View
370 classes/casset/csscompressor.php
@@ -1,4 +1,5 @@
<?php
+
/**
* Compress CSS
*
@@ -12,12 +13,11 @@
* @author Stephen Clay <steve@mrclay.org>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
-
/**
* This library is used as part of Casset.
*
* @package Casset
- * @version v1.11
+ * @version v1.12
* @author Antony Male
* @license MIT License
* @link http://github.com/canton7/fuelphp-casset
@@ -27,99 +27,96 @@
class Casset_Csscompressor {
- /**
- * Minify a CSS string
- *
- * @param string $css
- *
- * @param array $options (currently ignored)
- *
- * @return string
- */
- public static function process($css, $options = array())
- {
- $obj = new Casset_Csscompressor($options);
- return $obj->_process($css);
- }
-
- /**
- * @var array options
- */
- protected $_options = null;
-
- /**
- * @var bool Are we "in" a hack?
- *
- * I.e. are some browsers targetted until the next comment?
- */
- protected $_inHack = false;
-
-
- /**
- * Constructor
- *
- * @param array $options (currently ignored)
- *
- * @return null
- */
- private function __construct($options) {
- $this->_options = $options;
- }
-
- /**
- * Minify a CSS string
- *
- * @param string $css
- *
- * @return string
- */
- protected function _process($css)
- {
- $css = str_replace("\r\n", "\n", $css);
-
- // preserve empty comment after '>'
- // http://www.webdevout.net/css-hacks#in_css-selectors
- $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
-
- // preserve empty comment between property and value
- // http://css-discuss.incutio.com/?page=BoxModelHack
- $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
- $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
-
- // apply callback to all valid comments (and strip out surrounding ws
- $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
- ,array($this, '_commentCB'), $css);
-
- // remove ws around { } and last semicolon in declaration block
- $css = preg_replace('/\\s*{\\s*/', '{', $css);
- $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
-
- // remove ws surrounding semicolons
- $css = preg_replace('/\\s*;\\s*/', ';', $css);
-
- // remove ws around urls
- $css = preg_replace('/
+ /**
+ * Minify a CSS string
+ *
+ * @param string $css
+ *
+ * @param array $options (currently ignored)
+ *
+ * @return string
+ */
+ public static function process($css, $options = array()) {
+ $obj = new Casset_Csscompressor($options);
+ return $obj->_process($css);
+ }
+
+ /**
+ * @var array options
+ */
+ protected $_options = null;
+
+ /**
+ * @var bool Are we "in" a hack?
+ *
+ * I.e. are some browsers targetted until the next comment?
+ */
+ protected $_inHack = false;
+
+ /**
+ * Constructor
+ *
+ * @param array $options (currently ignored)
+ *
+ * @return null
+ */
+ private function __construct($options) {
+ $this->_options = $options;
+ }
+
+ /**
+ * Minify a CSS string
+ *
+ * @param string $css
+ *
+ * @return string
+ */
+ protected function _process($css) {
+ $css = str_replace("\r\n", "\n", $css);
+
+ // preserve empty comment after '>'
+ // http://www.webdevout.net/css-hacks#in_css-selectors
+ $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
+
+ // preserve empty comment between property and value
+ // http://css-discuss.incutio.com/?page=BoxModelHack
+ $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
+ $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
+
+ // apply callback to all valid comments (and strip out surrounding ws
+ $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
+ , array($this, '_commentCB'), $css);
+
+ // remove ws around { } and last semicolon in declaration block
+ $css = preg_replace('/\\s*{\\s*/', '{', $css);
+ $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
+
+ // remove ws surrounding semicolons
+ $css = preg_replace('/\\s*;\\s*/', ';', $css);
+
+ // remove ws around urls
+ $css = preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
\\s*
\\) # )
/x', 'url($1)', $css);
- // remove ws between rules and colons
- $css = preg_replace('/
+ // remove ws between rules and colons
+ $css = preg_replace('/
\\s*
([{;]) # 1 = beginning of block or rule separator
\\s*
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
\\s*
:
\\s*
- (\\b|[#\'"]) # 3 = first character of a value
+ (\\b|[#\'"-]) # 3 = first character of a value
/x', '$1$2:$3', $css);
- // remove ws in selectors
- $css = preg_replace_callback('/
+ // remove ws in selectors
+ $css = preg_replace_callback('/
(?: # non-capture
\\s*
[^~>+,\\s]+ # selector part
@@ -130,130 +127,127 @@ protected function _process($css)
[^~>+,\\s]+ # selector part
{ # open declaration block
/x'
- ,array($this, '_selectorsCB'), $css);
+ , array($this, '_selectorsCB'), $css);
- // minimize hex colors
- $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
- , '$1#$2$3$4$5', $css);
+ // minimize hex colors
+ $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
+ , '$1#$2$3$4$5', $css);
- // remove spaces between font families
- $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
- ,array($this, '_fontFamilyCB'), $css);
+ // remove spaces between font families
+ $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
+ , array($this, '_fontFamilyCB'), $css);
- $css = preg_replace('/@import\\s+url/', '@import url', $css);
+ $css = preg_replace('/@import\\s+url/', '@import url', $css);
- // replace any ws involving newlines with a single newline
- $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
+ // replace any ws involving newlines with a single newline
+ $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
- // separate common descendent selectors w/ newlines (to limit line lengths)
- $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
+ // separate common descendent selectors w/ newlines (to limit line lengths)
+ $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
- // Use newline after 1st numeric value (to limit line lengths).
- $css = preg_replace('/
+ // Use newline after 1st numeric value (to limit line lengths).
+ $css = preg_replace('/
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
\\s+
/x'
- ,"$1\n", $css);
-
- // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
- $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
-
- return trim($css);
- }
-
- /**
- * Replace what looks like a set of selectors
- *
- * @param array $m regex matches
- *
- * @return string
- */
- protected function _selectorsCB($m)
- {
- // remove ws around the combinators
- return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
- }
-
- /**
- * Process a comment and return a replacement
- *
- * @param array $m regex matches
- *
- * @return string
- */
- protected function _commentCB($m)
- {
- $hasSurroundingWs = (trim($m[0]) !== $m[1]);
- $m = $m[1];
- // $m is the comment content w/o the surrounding tokens,
- // but the return value will replace the entire comment.
- if ($m === 'keep') {
- return '/**/';
- }
- if ($m === '" "') {
- // component of http://tantek.com/CSS/Examples/midpass.html
- return '/*" "*/';
- }
- if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
- // component of http://tantek.com/CSS/Examples/midpass.html
- return '/*";}}/* */';
- }
- if ($this->_inHack) {
- // inversion: feeding only to one browser
- if (preg_match('@
+ , "$1\n", $css);
+
+ // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
+ $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
+
+ return trim($css);
+ }
+
+ /**
+ * Replace what looks like a set of selectors
+ *
+ * @param array $m regex matches
+ *
+ * @return string
+ */
+ protected function _selectorsCB($m) {
+ // remove ws around the combinators
+ return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
+ }
+
+ /**
+ * Process a comment and return a replacement
+ *
+ * @param array $m regex matches
+ *
+ * @return string
+ */
+ protected function _commentCB($m) {
+ $hasSurroundingWs = (trim($m[0]) !== $m[1]);
+ $m = $m[1];
+ // $m is the comment content w/o the surrounding tokens,
+ // but the return value will replace the entire comment.
+ if ($m === 'keep') {
+ return '/**/';
+ }
+ if ($m === '" "') {
+ // component of http://tantek.com/CSS/Examples/midpass.html
+ return '/*" "*/';
+ }
+ if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
+ // component of http://tantek.com/CSS/Examples/midpass.html
+ return '/*";}}/* */';
+ }
+ if ($this->_inHack) {
+ // inversion: feeding only to one browser
+ if (preg_match('@
^/ # comment started like /*/
\\s*
(\\S[\\s\\S]+?) # has at least some non-ws content
\\s*
/\\* # ends like /*/ or /**/
@x', $m, $n)) {
- // end hack mode after this comment, but preserve the hack and comment content
- $this->_inHack = false;
- return "/*/{$n[1]}/**/";
- }
- }
- if (substr($m, -1) === '\\') { // comment ends like \*/
- // begin hack mode and preserve hack
- $this->_inHack = true;
- return '/*\\*/';
- }
- if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
- // begin hack mode and preserve hack
- $this->_inHack = true;
- return '/*/*/';
- }
- if ($this->_inHack) {
- // a regular comment ends hack mode but should be preserved
- $this->_inHack = false;
- return '/**/';
- }
- // Issue 107: if there's any surrounding whitespace, it may be important, so
- // replace the comment with a single space
- return $hasSurroundingWs // remove all other comments
- ? ' '
- : '';
- }
-
- /**
- * Process a font-family listing and return a replacement
- *
- * @param array $m regex matches
- *
- * @return string
- */
- protected function _fontFamilyCB($m)
- {
- $m[1] = preg_replace('/
- \\s*
- (
- "[^"]+" # 1 = family in double qutoes
- |\'[^\']+\' # or 1 = family in single quotes
- |[\\w\\-]+ # or 1 = unquoted family
- )
- \\s*
- /x', '$1', $m[1]);
- return 'font-family:' . $m[1] . $m[2];
- }
+ // end hack mode after this comment, but preserve the hack and comment content
+ $this->_inHack = false;
+ return "/*/{$n[1]}/**/";
+ }
+ }
+ if (substr($m, -1) === '\\') { // comment ends like \*/
+ // begin hack mode and preserve hack
+ $this->_inHack = true;
+ return '/*\\*/';
+ }
+ if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
+ // begin hack mode and preserve hack
+ $this->_inHack = true;
+ return '/*/*/';
+ }
+ if ($this->_inHack) {
+ // a regular comment ends hack mode but should be preserved
+ $this->_inHack = false;
+ return '/**/';
+ }
+ // Issue 107: if there's any surrounding whitespace, it may be important, so
+ // replace the comment with a single space
+ return $hasSurroundingWs // remove all other comments
+ ? ' ' : '';
+ }
+
+ /**
+ * Process a font-family listing and return a replacement
+ *
+ * @param array $m regex matches
+ *
+ * @return string
+ */
+ protected function _fontFamilyCB($m) {
+ // Issue 210: must not eliminate WS between words in unquoted families
+ $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+ $out = 'font-family:';
+ while (null !== ($piece = array_shift($pieces))) {
+ if ($piece[0] !== '"' && $piece[0] !== "'") {
+ $piece = preg_replace('/\\s+/', ' ', $piece);
+ $piece = preg_replace('/\\s?,\\s?/', ',', $piece);
+ }
+ $out .= $piece;
+ }
+ return $out . $m[2];
+ }
}
/* End of file casset/csscompressor.php */
View
508 classes/casset/cssurirewriter.php
@@ -1,281 +1,285 @@
<?php
+
/**
* Rewrite file-relative URIs as root-relative in CSS files
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
-
/**
* This library is used as part of Casset.
*
* @package Casset
- * @version v1.11
+ * @version v1.12
* @author Antony Male
* @license MIT License
* @link http://github.com/canton7/fuelphp-casset
*/
-
namespace Casset;
class Casset_Cssurirewriter {
- /**
- * Defines which class to call as part of callbacks, change this
- * if you extend Minify_CSS_UriRewriter
- * @var string
- */
- protected static $className = 'Casset_Cssurirewriter';
-
- /**
- * rewrite() and rewriteRelative() append debugging information here
- * @var string
- */
- public static $debugText = '';
-
- /**
- * Rewrite file relative URIs as root relative in CSS files
- *
- * @param string $css
- *
- * @param string $currentDir The directory of the current CSS file.
- *
- * @param string $docRoot The document root of the web site in which
- * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
- *
- * @param array $symlinks (default = array()) If the CSS file is stored in
- * a symlink-ed directory, provide an array of link paths to
- * target paths, where the link paths are within the document root. Because
- * paths need to be normalized for this to work, use "//" to substitute
- * the doc root in the link paths (the array keys). E.g.:
- * <code>
- * array('//symlink' => '/real/target/path') // unix
- * array('//static' => 'D:\\staticStorage') // Windows
- * </code>
- *
- * @return string
- */
- public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
- {
- self::$_docRoot = self::_realpath(
- $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
- );
- self::$_currentDir = self::_realpath($currentDir);
- self::$_symlinks = array();
-
- // normalize symlinks
- foreach ($symlinks as $link => $target) {
- $link = ($link === '//')
- ? self::$_docRoot
- : str_replace('//', self::$_docRoot . '/', $link);
- $link = strtr($link, '/', DIRECTORY_SEPARATOR);
- self::$_symlinks[$link] = self::_realpath($target);
- }
-
- self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
- . "currentDir : " . self::$_currentDir . "\n";
- if (self::$_symlinks) {
- self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
- }
- self::$debugText .= "\n";
-
- $css = self::_trimUrls($css);
-
- // rewrite
- $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
- ,array(self::$className, '_processUriCB'), $css);
- $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
- ,array(self::$className, '_processUriCB'), $css);
-
- return $css;
- }
-
- /**
- * Prepend a path to relative URIs in CSS files
- *
- * @param string $css
- *
- * @param string $path The path to prepend.
- *
- * @return string
- */
- public static function prepend($css, $path)
- {
- self::$_prependPath = $path;
-
- $css = self::_trimUrls($css);
-
- // append
- $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
- ,array(self::$className, '_processUriCB'), $css);
- $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
- ,array(self::$className, '_processUriCB'), $css);
-
- self::$_prependPath = null;
- return $css;
- }
-
-
- /**
- * @var string directory of this stylesheet
- */
- private static $_currentDir = '';
-
- /**
- * @var string DOC_ROOT
- */
- private static $_docRoot = '';
-
- /**
- * @var array directory replacements to map symlink targets back to their
- * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
- */
- private static $_symlinks = array();
-
- /**
- * @var string path to prepend
- */
- private static $_prependPath = null;
-
- private static function _trimUrls($css)
- {
- return preg_replace('/
+ /**
+ * rewrite() and rewriteRelative() append debugging information here
+ * @var string
+ */
+ public static $debugText = '';
+
+ /**
+ * In CSS content, rewrite file relative URIs as root relative
+ *
+ * @param string $css
+ *
+ * @param string $currentDir The directory of the current CSS file.
+ *
+ * @param string $docRoot The document root of the web site in which
+ * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
+ *
+ * @param array $symlinks (default = array()) If the CSS file is stored in
+ * a symlink-ed directory, provide an array of link paths to
+ * target paths, where the link paths are within the document root. Because
+ * paths need to be normalized for this to work, use "//" to substitute
+ * the doc root in the link paths (the array keys). E.g.:
+ * <code>
+ * array('//symlink' => '/real/target/path') // unix
+ * array('//static' => 'D:\\staticStorage') // Windows
+ * </code>
+ *
+ * @return string
+ */
+ public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array()) {
+ self::$_docRoot = self::_realpath(
+ $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
+ );
+ self::$_currentDir = self::_realpath($currentDir);
+ self::$_symlinks = array();
+
+ // normalize symlinks
+ foreach ($symlinks as $link => $target) {
+ $link = ($link === '//') ? self::$_docRoot : str_replace('//', self::$_docRoot . '/', $link);
+ $link = strtr($link, '/', DIRECTORY_SEPARATOR);
+ self::$_symlinks[$link] = self::_realpath($target);
+ }
+
+ self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
+ . "currentDir : " . self::$_currentDir . "\n";
+ if (self::$_symlinks) {
+ self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
+ }
+ self::$debugText .= "\n";
+
+ $css = self::_trimUrls($css);
+
+ // rewrite
+ $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
+ , array(self::$className, '_processUriCB'), $css);
+ $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ , array(self::$className, '_processUriCB'), $css);
+
+ return $css;
+ }
+
+ /**
+ * In CSS content, prepend a path to relative URIs
+ *
+ * @param string $css
+ *
+ * @param string $path The path to prepend.
+ *
+ * @return string
+ */
+ public static function prepend($css, $path) {
+ self::$_prependPath = $path;
+
+ $css = self::_trimUrls($css);
+
+ // append
+ $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
+ , array(self::$className, '_processUriCB'), $css);
+ $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ , array(self::$className, '_processUriCB'), $css);
+
+ self::$_prependPath = null;
+ return $css;
+ }
+
+ /**
+ * Get a root relative URI from a file relative URI
+ *
+ * <code>
+ * Minify_CSS_UriRewriter::rewriteRelative(
+ * '../img/hello.gif'
+ * , '/home/user/www/css' // path of CSS file
+ * , '/home/user/www' // doc root
+ * );
+ * // returns '/img/hello.gif'
+ *
+ * // example where static files are stored in a symlinked directory
+ * Minify_CSS_UriRewriter::rewriteRelative(
+ * 'hello.gif'
+ * , '/var/staticFiles/theme'
+ * , '/home/user/www'
+ * , array('/home/user/www/static' => '/var/staticFiles')
+ * );
+ * // returns '/static/theme/hello.gif'
+ * </code>
+ *
+ * @param string $uri file relative URI
+ *
+ * @param string $realCurrentDir realpath of the current file's directory.
+ *
+ * @param string $realDocRoot realpath of the site document root.
+ *
+ * @param array $symlinks (default = array()) If the file is stored in
+ * a symlink-ed directory, provide an array of link paths to
+ * real target paths, where the link paths "appear" to be within the document
+ * root. E.g.:
+ * <code>
+ * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
+ * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
+ * </code>
+ *
+ * @return string
+ */
+ public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array()) {
+ // prepend path with current dir separator (OS-independent)
+ $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
+ . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
+
+ self::$debugText .= "file-relative URI : {$uri}\n"
+ . "path prepended : {$path}\n";
+
+ // "unresolve" a symlink back to doc root
+ foreach ($symlinks as $link => $target) {
+ if (0 === strpos($path, $target)) {
+ // replace $target with $link
+ $path = $link . substr($path, strlen($target));
+
+ self::$debugText .= "symlink unresolved : {$path}\n";
+
+ break;
+ }
+ }
+ // strip doc root
+ $path = substr($path, strlen($realDocRoot));
+
+ self::$debugText .= "docroot stripped : {$path}\n";
+
+ // fix to root-relative URI
+
+ $uri = strtr($path, '/\\', '//');
+
+ $uri = self::removeDots($uri);
+
+ self::$debugText .= "traversals removed : {$uri}\n\n";
+
+ return $uri;
+ }
+
+ /**
+ * Remove instances of "./" and "../" where possible from a root-relative URI
+ * @param string $uri
+ * @return string
+ */
+ public static function removeDots($uri) {
+ $uri = str_replace('/./', '/', $uri);
+ // inspired by patch from Oleg Cherniy
+ do {
+ $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
+ } while ($changed);
+ return $uri;
+ }
+
+ /**
+ * Defines which class to call as part of callbacks, change this
+ * if you extend Minify_CSS_UriRewriter
+ * @var string
+ */
+ protected static $className = 'Casset_Cssurirewriter';
+
+ /**
+ * Get realpath with any trailing slash removed. If realpath() fails,
+ * just remove the trailing slash.
+ *
+ * @param string $path
+ *
+ * @return mixed path with no trailing slash
+ */
+ protected static function _realpath($path) {
+ $realPath = realpath($path);
+ if ($realPath !== false) {
+ $path = $realPath;
+ }
+ return rtrim($path, '/\\');
+ }
+
+ /**
+ * @var string directory of this stylesheet
+ */
+ private static $_currentDir = '';
+
+ /**
+ * @var string DOC_ROOT
+ */
+ private static $_docRoot = '';
+
+ /**
+ * @var array directory replacements to map symlink targets back to their
+ * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
+ */
+ private static $_symlinks = array();
+
+ /**
+ * @var string path to prepend
+ */
+ private static $_prependPath = null;
+
+ private static function _trimUrls($css) {
+ return preg_replace('/
url\\( # url(
\\s*
([^\\)]+?) # 1 = URI (assuming does not contain ")")
\\s*
\\) # )
/x', 'url($1)', $css);
- }
-
- private static function _processUriCB($m)
- {
- // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
- $isImport = ($m[0][0] === '@');
- // determine URI and the quote character (if any)
- if ($isImport) {
- $quoteChar = $m[1];
- $uri = $m[2];
- } else {
- // $m[1] is either quoted or not
- $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
- ? $m[1][0]
- : '';
- $uri = ($quoteChar === '')
- ? $m[1]
- : substr($m[1], 1, strlen($m[1]) - 2);
- }
- // analyze URI
- if ('/' !== $uri[0] // root-relative
- && false === strpos($uri, '//') // protocol (non-data)
- && 0 !== strpos($uri, 'data:') // data protocol
- ) {
- // URI is file-relative: rewrite depending on options
- $uri = (self::$_prependPath !== null)
- ? (self::$_prependPath . $uri)
- : self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
- }
- return $isImport
- ? "@import {$quoteChar}{$uri}{$quoteChar}"
- : "url({$quoteChar}{$uri}{$quoteChar})";
- }
-
- /**
- * Rewrite a file relative URI as root relative
- *
- * <code>
- * Minify_CSS_UriRewriter::rewriteRelative(
- * '../img/hello.gif'
- * , '/home/user/www/css' // path of CSS file
- * , '/home/user/www' // doc root
- * );
- * // returns '/img/hello.gif'
- *
- * // example where static files are stored in a symlinked directory
- * Minify_CSS_UriRewriter::rewriteRelative(
- * 'hello.gif'
- * , '/var/staticFiles/theme'
- * , '/home/user/www'
- * , array('/home/user/www/static' => '/var/staticFiles')
- * );
- * // returns '/static/theme/hello.gif'
- * </code>
- *
- * @param string $uri file relative URI
- *
- * @param string $realCurrentDir realpath of the current file's directory.
- *
- * @param string $realDocRoot realpath of the site document root.
- *
- * @param array $symlinks (default = array()) If the file is stored in
- * a symlink-ed directory, provide an array of link paths to
- * real target paths, where the link paths "appear" to be within the document
- * root. E.g.:
- * <code>
- * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
- * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
- * </code>
- *
- * @return string
- */
- public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
- {
- // prepend path with current dir separator (OS-independent)
- $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
- . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
-
- self::$debugText .= "file-relative URI : {$uri}\n"
- . "path prepended : {$path}\n";
-
- // "unresolve" a symlink back to doc root
- foreach ($symlinks as $link => $target) {
- if (0 === strpos($path, $target)) {
- // replace $target with $link
- $path = $link . substr($path, strlen($target));
-
- self::$debugText .= "symlink unresolved : {$path}\n";
-
- break;
- }
- }
- // strip doc root
- $path = substr($path, strlen($realDocRoot));
-
- self::$debugText .= "docroot stripped : {$path}\n";
-
- // fix to root-relative URI
-
- $uri = strtr($path, '/\\', '//');
-
- // remove /./ and /../ where possible
- $uri = str_replace('/./', '/', $uri);
- // inspired by patch from Oleg Cherniy
- do {
- $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
- } while ($changed);
-
- self::$debugText .= "traversals removed : {$uri}\n\n";
-
- return $uri;
- }
-
- /**
- * Get realpath with any trailing slash removed. If realpath() fails,
- * just remove the trailing slash.
- *
- * @param string $path
- *
- * @return mixed path with no trailing slash
- */
- protected static function _realpath($path)
- {
- $realPath = realpath($path);
- if ($realPath !== false) {
- $path = $realPath;
- }
- return rtrim($path, '/\\');
- }
+ }
+
+ private static function _processUriCB($m) {
+ // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+ $isImport = ($m[0][0] === '@');
+ // determine URI and the quote character (if any)
+ if ($isImport) {
+ $quoteChar = $m[1];
+ $uri = $m[2];
+ } else {
+ // $m[1] is either quoted or not
+ $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') ? $m[1][0] : '';
+ $uri = ($quoteChar === '') ? $m[1] : substr($m[1], 1, strlen($m[1]) - 2);
+ }
+ // analyze URI
+ if ('/' !== $uri[0] // root-relative
+ && false === strpos($uri, '//') // protocol (non-data)
+ && 0 !== strpos($uri, 'data:') // data protocol
+ ) {
+ // URI is file-relative: rewrite depending on options
+ if (self::$_prependPath === null) {
+ $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
+ } else {
+ $uri = self::$_prependPath . $uri;
+ if ($uri[0] === '/') {
+ $root = '';
+ $rootRelative = $uri;
+ $uri = $root . self::removeDots($rootRelative);
+ } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
+ $root = $m[1];
+ $rootRelative = substr($uri, strlen($root));
+ $uri = $root . self::removeDots($rootRelative);
+ }
+ }
+ }
+ return $isImport ? "@import {$quoteChar}{$uri}{$quoteChar}" : "url({$quoteChar}{$uri}{$quoteChar})";
+ }
}
/* End of file casset/cssurirewriter.php */
View
532 classes/casset/jsmin.php
@@ -1,4 +1,5 @@
<?php
+
/**
* jsmin.php - PHP implementation of Douglas Crockford's JSMin.
*
@@ -48,280 +49,311 @@
* @license http://opensource.org/licenses/mit-license.php MIT License
* @link http://code.google.com/p/jsmin-php/
*/
-
/**
* This library is used as part of Casset.
* @package Casset
- * @version v1.11
+ * @version v1.12
* @author Antony Male
* @license MIT License
* @link http://github.com/canton7/fuelphp-casset
*/
-
namespace Casset;
class Casset_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;
+ 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 Casset_JSmin($js);
+ return $jsmin->min();
+ }
+
+ /**
+ * @param string $input
+ */
+ public function __construct($input) {
+ $this->input = $input;
+ }
+
+ /**
+ * Perform minification, return result
+ */
+ public function min() {
+ if ($this->output !== '') { // min already run
+ return $this->output;
+ }
- protected $a = "\n";
- protected $b = '';
- protected $input = '';
- protected $inputIndex = 0;
- protected $inputLength = 0;
- protected $lookAhead = null;
- protected $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);
- /**
- * Minify Javascript
- *
- * @param string $js Javascript to be minified
- * @return string
- */
- public static function minify($js)
- {
- $jsmin = new Casset_JSMin($js);
- return $jsmin->min();
- }
+ $this->action(self::ACTION_DELETE_A_B);
- /**
- * Setup process
- */
- public function __construct($input)
- {
- $this->input = str_replace("\r\n", "\n", $input);
- $this->inputLength = strlen($this->input);
- }
+ 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);
- /**
- * Perform minification, return result
- */
- public function min()
- {
- if ($this->output !== '') { // min already run
- return $this->output;
- }
- $this->action(self::ACTION_DELETE_A_B);
+ if ($mbIntEnc !== null) {
+ mb_internal_encoding($mbIntEnc);
+ }
+ return $this->output;
+ }
- while ($this->a !== null) {
- // determine next command
- $command = self::ACTION_KEEP_A; // default
- if ($this->a === ' ') {
- if (! $this->isAlphaNum($this->b)) {
- $command = self::ACTION_DELETE_A;
- }
- } elseif ($this->a === "\n") {
- if ($this->b === ' ') {
- $command = self::ACTION_DELETE_A_B;
- } elseif (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);
- 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.
+ */
+ 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;
- /**
- * 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.
- */
- protected function action($command)
- {
- switch ($command) {
- case self::ACTION_KEEP_A:
- $this->output .= $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->a = $this->get();
- if ($this->a === $this->b) { // end quote
- break;
- }
- if (ord($this->a) <= self::ORD_LF) {
- throw new JSMin_UnterminatedStringException(
- 'Unterminated String: ' . var_export($str, true));
- }
- $str .= $this->a;
- if ($this->a === '\\') {
- $this->output .= $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(
- 'Unterminated RegExp: '. var_export($pattern, true));
- }
- $this->output .= $this->a;
- }
- $this->b = $this->next();
- }
- // end case ACTION_DELETE_A_B
- }
- }
+ // 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;
- 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;
- }
+ $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;