Permalink
Browse files

Merge branch 'feature/uri_rewriter' into develop

* feature/uri_rewriter:
  Document rewriting algorithms
  Make inlining with non-combined files work
  Rewrite uris when inlining, with combine on.
  Basic functionality: Supports linked files, not inlined ones
  Add skeleton: new rewriter class, and new config option
  • Loading branch information...
canton7 committed Jul 24, 2012
2 parents 0e1aec4 + 7c9ab08 commit 449930fada6f998ddb9df74b685e5dde99c17347
Showing with 176 additions and 8 deletions.
  1. +1 −0 bootstrap.php
  2. +45 −8 classes/casset.php
  3. +68 −0 classes/casset/cssurirewriterrelative.php
  4. +25 −0 config/casset.php
  5. +37 −0 readme.md
View
@@ -19,6 +19,7 @@
'Casset\\Casset_JSMin' => __DIR__.'/classes/casset/jsmin.php',
'Casset\\Casset_Csscompressor' => __DIR__.'/classes/casset/csscompressor.php',
'Casset\\Casset_Cssurirewriter' => __DIR__.'/classes/casset/cssurirewriter.php',
+ 'Casset\\Casset_Cssurirewriterrelative' => __DIR__.'/classes/casset/cssurirewriterrelative.php',
'Casset\\Casset_Addons_Twig' => __DIR__.'/classes/casset/addons/twig.php',
));
View
@@ -116,6 +116,11 @@ class Casset {
*/
protected static $rendered_groups = array('js' => array(), 'css' => array());
+ /**
+ * @var string which css uri rewriter we want to use. Options are 'absolute', 'relative', 'none'
+ */
+ protected static $css_uri_rewriter = 'absolute';
+
/**
* @var bool Wether we've been initialized.
*/
@@ -182,6 +187,8 @@ public static function _init()
static::$filepath_callback = \Config::get('casset.filepath_callback', static::$filepath_callback);
+ static::$css_uri_rewriter = \Config::get('casset.css_uri_rewriter', static::$css_uri_rewriter);
+
static::$initialized = true;
}
@@ -874,6 +881,8 @@ public static function render_css($group = false, $options = array(), $attr_dep
if ($inline)
{
$content = file_get_contents(DOCROOT.static::$cache_path.$filename);
+ // We'll need to fix the uris, unless they were rewritten absolutely to start with
+ $content = static::css_rewrite_uris($content, static::$cache_path.$filename, \Uri::string());
if ($options['gen_tags'])
$ret .= html_tag('style', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
else
@@ -894,7 +903,7 @@ public static function render_css($group = false, $options = array(), $attr_dep
{
if ($inline)
{
- $content = static::load_file($file['file'], 'css');
+ $content = static::load_file($file['file'], 'css', false, \Uri::string());
if ($options['gen_tags'])
$ret .= html_tag('style', $attr, PHP_EOL.$content.PHP_EOL).PHP_EOL;
else
@@ -1063,7 +1072,7 @@ protected static function files_to_render($type, $group)
* @param type $filename
* @return type
*/
- protected static function load_file($filename, $type, $file_group = false)
+ protected static function load_file($filename, $type, $file_group = false, $destination_filename = null)
{
$content = file_get_contents($filename);
if (static::$post_load_callback != null)
@@ -1073,10 +1082,37 @@ protected static function load_file($filename, $type, $file_group = false)
$content = $func($content, $filename, $type, $file_group);
}
if ($type == 'css')
- $content = Casset_Cssurirewriter::rewrite($content, dirname($filename));
+ $content = static::css_rewrite_uris($content, $filename, $destination_filename);
return $content;
}
+ /**
+ * Selects the correct css uri rewriter, and applies it
+ *
+ * @param string $content the contents of the file to rewrite
+ * @param string $filename the original location of the file
+ * @param string $destination_filename the name of the file where the css will be written to
+ * @return string The re-written content
+ */
+ protected static function css_rewrite_uris($content, $filename, $destination_filename) {
+ switch (static::$css_uri_rewriter) {
+ case 'absolute':
+ $rewritten = Casset_Cssurirewriter::rewrite($content, dirname($filename));
+ break;
+ case 'relative':
+ $rewritten = Casset_Cssurirewriterrelative::rewrite_css($content, dirname($filename), dirname($destination_filename));
+ break;
+ case 'none':
+ $rewritten = $content;
+ break;
+ default:
+ throw new Casset_Exception('Unknown CSS URI rewriter: '.static::$css_uri_rewriter);
+ break;
+ }
+
+ return $rewritten;
+ }
+
/**
* Takes a list of files, and combines them into a single minified file.
* Doesn't bother if none of the files have been modified since the cache
@@ -1108,8 +1144,9 @@ protected static function combine($type, $file_group, $minify, $inline)
return $a['file'];
}, $file_group)).($minify ? 'min' : '').$last_mod).'.'.$type;
- $filepath = DOCROOT.static::$cache_path.'/'.$filename;
- $needs_update = (!file_exists($filepath));
+ $rel_filepath = static::$cache_path.'/'.$filename;
+ $abs_filepath = DOCROOT.$rel_filepath;
+ $needs_update = (!file_exists($abs_filepath));
if ($needs_update)
{
@@ -1119,10 +1156,10 @@ protected 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 .= static::load_file($file['file'], $type, $file_group).PHP_EOL;
+ $content .= static::load_file($file['file'], $type, $file_group, $rel_filepath).PHP_EOL;
else
{
- $file_content = static::load_file($file['file'], $type, $file_group);
+ $file_content = static::load_file($file['file'], $type, $file_group, $rel_filepath);
if ($file_content === false)
throw new Casset_Exception("Couldn't not open file {$file['file']}");
if ($type == 'js')
@@ -1135,7 +1172,7 @@ protected static function combine($type, $file_group, $minify, $inline)
}
}
}
- file_put_contents($filepath, $content, LOCK_EX);
+ file_put_contents($abs_filepath, $content, LOCK_EX);
$mtime = time();
}
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * This library is used as part of Casset.
+ *
+ * @package Casset
+ * @version v1.17
+ * @author Antony Male
+ * @license MIT License
+ * @link http://github.com/canton7/fuelphp-casset
+ */
+
+namespace Casset;
+
+class Casset_Cssurirewriterrelative {
+ const PATTERN = '/(url|@import)\s*\(\s*([\'"]?)([^\/\'"][^)\2]+)\2\s*\)/';
+
+ public static function rewrite_css($css, $before_dir, $after_dir) {
+ // Normalise slashes
+ $before_dir = str_replace('\\', '/', $before_dir);
+ $after_dir = str_replace('\\', '/', $after_dir);
+
+ // Make sure before_dir and after_dir have trailing slashes
+ $before_dir = rtrim($before_dir, '/') . '/';
+ $after_dir = rtrim($after_dir, '/') . '/';
+
+ // Trim off common leading prefix
+ $i = 0;
+ for (; $i<strlen($after_dir); $i++) {
+ if ($after_dir[$i] != $before_dir[$i])
+ break;
+ }
+ // If we quit on the first loop, $i holds first index of mismatch, otherwise last index of match
+ $i = max($i-1, 0);
+ $after_dir = substr($after_dir, $i);
+ $before_dir = substr($before_dir, $i);
+
+ // Move back out of the dir that the file ends up in
+ // then back into the dir the file was in before
+ $rel = str_repeat('../', substr_count($after_dir, '/')) . $before_dir;
+
+ // return $css;
+
+ $css = preg_replace_callback(static::PATTERN, function($m) use ($rel) {
+ list($match, $type, $quote, $url) = $m;
+ if (strpos($url, 'data:') === 0 || strpos($url, '://') !== false)
+ return $match;
+ // PHP anonymous function binding fail
+ $rel_url = Casset_Cssurirewriterrelative::tidy_url("$rel$url");
+ return "$type($quote$rel_url$quote)";
+ }, $css);
+
+ return $css;
+ }
+
+ public static function tidy_url($url) {
+ // Get rid of /./ and something/../
+ $url = preg_replace('#(/|^)\./#', '\1', $url);
+
+ do {
+ $url = preg_replace('#(/|^)[^/\.]+/\.\./#', '\1', $url, -1, $changed);
+ } while ($changed);
+
+ return $url;
+ }
+}
+
+/* End of file casset/cssurirewriterrelative.php */
View
@@ -157,6 +157,31 @@
*/
'filepath_callback' => null,
+ /**
+ * Which CSS URI rewriter to use.
+ * When the location of a CSS file is changed (which happens when the file is rewritten,
+ * combined, inlined, etc), all uris inside of that file (e.g. url(....)) have to be rewritten,
+ * otherwise they would break.
+ *
+ * Casset provides a number of different rewriting algorithms, each with their own advantages
+ * and disadvantages.
+ * The options are absolute, relative, and none.
+ *
+ * Absolute:
+ * This algorithm, written by Stephen Clay as part of his JSMin library, rewrites all relative
+ * uris to be absolute, relative to DOCUMENT_ROOT.
+ * However, this algorithm gets confused when you symlinkyour document root.
+ *
+ * Relative:
+ * This algorithm rewrites relative uris to take account of the changed position of the css
+ * file, while keeping the uri relative. This can handle symlinked document roots, but is not
+ * as extensively tested.
+ *
+ * None:
+ * This option turns off all uri rewriting.
+ */
+ 'css_uri_rewriter' => 'absolute',
+
/**
* Groups of scripts.
* You can predefine groups of js and css scripts which can be enabled/disabled
View
@@ -814,6 +814,43 @@ In your .htaccess:
RewriteRule ^(.*)\/(.+)\.([0-9]+)\.(js|css|jpg|jpeg|gif|png)$ $1/$2.$4 [L]
```
+CSS URI Rewriting Algorithms
+----------------------------
+
+You probably only need to read this section if you've noticed URLs in your css files being broken by Casset.
+
+A bit of background: when a css file is rewritten, the rewritten css is stored in a cache file.
+This cache file is (probably) stored in a different location to the original file, so all relative urls will be broken.
+Therefore Casset rewrites all of your urls for you.
+
+Casset supplies a number of algorithms, which are listed below.
+None of them are entirely foolproof.
+
+### Absolute Rewriter
+
+The default algorithm, this rewrites all urls to be absolute, so they start with a /.
+It was written by Stephen Clay as part of his Minify package, is well tested, and works for 99% of cases.
+
+Where it fails is when your document root is a symlink, in which case the algorithm is unable to determine the correct document root, and ends up garbling the urls.
+There is a workaround (providing an array of symlinks) but this is not yet supported by Casset.
+
+### Relative Rewriter
+
+This algorithm takes the current location of the cache file, and the original location of the css file, and constructs a relative url between the two paths.
+It's newer than the absolute rewriter, and hasn't been as extensively tested.
+It therefore might have some corner cases it break in -- if you find one, please create an issue!
+However, it should be able to handle the symlink problem which the absolute rewriter fails at.
+
+### No rewriting
+
+The other option is to turn off rewriting completely.
+Some people may decide they simply don't need it.
+In addition, the post_load callback allows you to do your own rewriting -- or should: I've never tried it, so you might not be given enough information; raise an issue if this is the case.
+If you decide to do this, you'll probably want to turn off Casset's rewriting.
+
+The algorithm is specified using the `css_uri_rewriter` config key.
+This can take values of 'absolute', 'relative', or 'none'.
+
Addons
------

0 comments on commit 449930f

Please sign in to comment.