Development

Changeset 10089

You must first sign up to be able to contribute.

Changeset 10089

Show
Ignore:
Timestamp:
07/03/08 15:34:47 (5 months ago)
Author:
KRavEN
Message:
  • Moved cache generation/management into the filter
  • Removed .htaccess modification requirements
  • added compress_js and compress_css options to the filter
  • added client side caching for the packed files and improved the request name so it will cache properly
  • added routing rules to route to the sfCombineFilter module
  • added non-harmfull css compressor as JSMin can mess up the css for some javascript libraries
  • fixed a bug where the Asset helper was not being initialized
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • plugins/sfCombineFilterPlugin/branches/kraven/README

    r6025 r10089  
    33The `sfCombineFilter` is an implementation of the [http://rakaz.nl/extra/code/combine CSS and Javascript Combiner] by Niels Leenheer. It makes your pages load faster by combining and compressing javascript and css files. 
    44 
    5 This filter provides means to automatically combine your included javascripts and stylesheets into one request each. All of the included files are combined into a single large file and compressed using gzip. The resulting file is cached and used every time that particular combination of files is used, until any of the files are changed at which point a new cache is automatically created.  
     5This filter provides means to automatically combine your included javascripts and stylesheets into one request each. All of the included files are combined into a single large file and compressed using gzip. The resulting file is cached and used every time that particular combination of files is used, until any of the files are changed at which point a new cache is automatically created. 
    66 
    77The symfony filter looks at all of the javascripts and stylesheets resources specified in the current response, and automatically creates a string that combines all of the names together. When your visitors request this special file, they will get one large file, gzipped and ready to go. 
     
    3737 
    3838 * In your app/config/filters.yml, add the sfCombineFilter. This must come AFTER "common": 
    39   
     39 
    4040{{{ 
    4141rendering: ~ 
     
    5353    javascripts:    true 
    5454    stylesheets:    true 
     55    compress_js:    true 
     56    compress_css:   true 
    5557    root_js_only:   false 
    5658    root_css_only:  true 
     
    5961}}} 
    6062 
    61 By default all javascripts and stylesheets requested in '/js-packed' or '/css-packed' respectively are combined into one request each, whereas files requested outside of that path are ignored. You can turn off the filter all together for javascript or css by setting those parameters to "false". If you want to include files requested from any path and not just those inside /js or /css, then set the parameters "root_js_only" and "root_css_only" to false.  
    62  
    63 === Modify .htaccess === 
    64 Add the following to your .htaccess rewrite rules: 
     63 * In your app/config/routing.yml 
    6564{{{ 
    66   RewriteRule ^css/packed/(.*\.css) /sfCombineFilterPlugin/combine.php?type=css&files=$1 
    67   RewriteRule ^js/packed/(.*\.js) /sfCombineFilterPlugin/combine.php?type=javascript&files=$1 
     65download_packed_files: 
     66  url:  /packed/:type/:cachefilename/packed.* 
     67  param: { module: sfCombineFilter, action: download } 
    6868}}} 
    6969 
    70 This will capture any requests made to /js/packed/* or /css/packed/* and route them through the compression script. 
     70 * In your app/config/settings.yml 
     71 
     72add sfCombineFilter to enabled_modules: [] 
     73 
     74By default all javascripts and stylesheets requested in /packed/js/ or /packed/css/ respectively are combined into one request each, whereas files requested outside of that path are ignored. You can turn off the filter all together for javascript or css by setting those parameters to "false". If you want to include files requested from any path and not just those inside /js or /css, then set the parameters "root_js_only" and "root_css_only" to false. 
    7175 
    7276== Changelog == 
     
    8286 - Fixes issues when included files outside of root css and js directories. 
    8387 - New: Option to minify the js using JSMin. 
     88 
     89 === 2008-07-03 | 0.1.2 beta === 
     90Changes by Benjamin Runnels: 
     91 - Moved cache generation/management into the filter 
     92 - Removed .htaccess modification requirements 
     93 - added compress_js and compress_css options to the filter 
     94 - added client side caching for the packed files and improved the request name so it will cache properly 
     95 - added routing rules to route to the sfCombineFilter module 
     96 - added non-harmfull css compressor as JSMin can mess up the css for some javascript libraries 
  • plugins/sfCombineFilterPlugin/branches/kraven/lib/sfCombineFilter.class.php

    r6025 r10089  
    44 * 
    55 * sfCombineFilter.class.php (c) 2007 Scott Meves. 
    6  * Combine.php Copyright (c) 2006 by Niels Leenheer 
     6 * sfCombineFilter.class.php modifications (c) 2008 by Benjamin Runnels * 
    77 * 
    88 * For the full copyright and license information, please view the LICENSE 
     
    2020class sfCombineFilter extends sfFilter 
    2121{ 
     22  private $response,$sf_relative_url_root,$type,$files,$lastmodified; 
    2223 
    2324  public function execute ($filterChain) 
     
    2526    $filterChain->execute(); 
    2627 
    27     if ($this->getParameter('javascripts', true)) { 
     28    sfLoader::loadHelpers('Asset'); 
     29    $this->response = $this->getContext()->getResponse(); 
     30    $this->sf_relative_url_root = $this->getContext()->getRequest()->getRelativeUrlRoot(). $this->getContext()->getRequest()->getScriptName(); 
     31 
     32    if ($this->getParameter('javascripts', true)) 
     33    { 
     34      $this->type = 'javascript'; 
     35      $this->files = array(); 
     36      $this->lastmodified = 0; 
    2837      $this->getCombinedJavascripts(); 
    2938    } 
    3039 
    31     if ($this->getParameter('stylesheets', true)) { 
     40    if ($this->getParameter('stylesheets', true)) 
     41    { 
     42      $this->type = 'css'; 
     43      $this->files = array(); 
     44      $this->lastmodified = 0; 
    3245      $this->getCombinedStylesheets(); 
    3346    } 
     
    3649  protected function getCombinedJavascripts() 
    3750  { 
    38     $response = $this->getContext()->getResponse(); 
    39     $sf_relative_url_root = $this->getContext()->getRequest()->getRelativeUrlRoot(); 
    4051    $root_js_only = $this->getParameter('root_js_only', true); 
    4152 
     
    4556    foreach (array('first', '', 'last') as $position) 
    4657    { 
    47       foreach ($response->getJavascripts($position) as $files => $options) 
     58      foreach ($this->response->getJavascripts($position) as $files => $options) 
    4859      { 
    4960        if (!is_array($files)) 
     
    6576          $path = _compute_public_path($file, 'js', 'js'); 
    6677 
    67           if ((!$root_js_only && !strpos($path, '://')) || ($root_js_only && strpos($path, $sf_relative_url_root.'/js/') === 0)) { 
    68             $combined_sources[] = ($root_js_only ? preg_replace("/^".str_replace('/', '\/', $sf_relative_url_root.'/js/')."/i", '', $path) : $path); 
    69             $response->getParameterHolder()->remove($file, 'helper/asset/auto/javascript'.($position ? '/'.$position : '')); 
     78          if ((!$root_js_only && !strpos($path, '://')&& strpos($path, '.js')) || ($root_js_only && strpos($path, $this->sf_relative_url_root.'/js/') === 0)) { 
     79            $element = ($root_js_only ? preg_replace("/^".str_replace('/', '\/', $this->sf_relative_url_root.'/js/')."/i", '', $path) : $path); 
     80            if($this->checkFile($element)) 
     81            { 
     82              $combined_sources[] = $element; 
     83              $this->response->getParameterHolder()->remove($file, 'helper/asset/auto/javascript'.($position ? '/'.$position : '')); 
     84            } 
     85 
    7086          } 
    7187        } 
     
    7490 
    7591    if (count($combined_sources)) { 
    76       $combined_sources_str = $sf_relative_url_root . '/js/packed/' . implode(',', $combined_sources); 
    77       $response->addJavascript($combined_sources_str, ''); 
    78     } 
    79  
     92      $cacheFileName = $this->lastmodified . '-' . md5(implode(',', $combined_sources)); 
     93      if($this->cacheFile($cacheFileName)) 
     94      { 
     95        $combined_sources_str = $this->sf_relative_url_root ."/packed/js/$cacheFileName/packed.js"; 
     96        //TODO: keep track if there is a dynamic file in the middle of static files and create multiple packed files 
     97        // if required to keep the proper order 
     98        $this->response->addJavascript($combined_sources_str, 'first'); 
     99      } 
     100    } 
     101  } 
     102 
     103  protected function cacheFile($cacheFileName) 
     104  { 
     105    //get an instance of the file cache object. We grab the web root then get the name of the cache folder 
     106    //we don't want to use sf_cache_dir because that is application and environment specific 
     107    //we don't want to a path relative to sf_web_dir because the sf_root_dir can be changed, better to start from there 
     108    $cache = new sfFileCache(sfConfig::get('sf_root_dir').DIRECTORY_SEPARATOR.sfConfig::get('sf_cache_dir_name')); 
     109 
     110    //cached files are in the 'packed_files' name space 
     111 
     112    //Next we see if we can pull the file from the cache. 
     113    if($cache->has($cacheFileName, 'packed_files')) 
     114    { 
     115      //Ok! We have a cached copy of the file! 
     116      return true; 
     117    } 
     118    else 
     119    { 
     120      // Get contents of the files 
     121      $contents = ''; 
     122      foreach ($this->files as $path) 
     123      { 
     124        if($this->type=='css') 
     125        { 
     126          //TODO: may need more replacements in the path but this covers everything I've come across so far 
     127          $cssPath = str_replace(array(sfConfig::get('sf_root_dir'),'plugins/','web/'),'',$path); 
     128          $con = $this->fixCssPaths(file_get_contents($path),$cssPath); 
     129        } 
     130        else 
     131        { 
     132          $con = file_get_contents($path); 
     133        } 
     134 
     135        $contents .= "\n\n" . $con; 
     136      } 
     137 
     138      if ($this->type=='javascript'&& $this->getParameter('compress_js')) 
     139      { 
     140        $contents = JSMin::minify($contents); 
     141      } 
     142      elseif($this->type=='css'&& $this->getParameter('compress_css')) 
     143      { 
     144        $contents = $this->compressCss($contents); 
     145      } 
     146 
     147      //Write the file data to the cache 
     148      $cache->set($cacheFileName, 'packed_files', $contents); 
     149      return true; 
     150    } 
     151 
     152    return false; 
     153  } 
     154 
     155  private function compressCss($content) 
     156  { 
     157    // remove comments 
     158    $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content); 
     159    // remove tabs, spaces, newlines, etc. 
     160    $content = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $content); 
     161    return $content; 
     162  } 
     163 
     164  private function fixCssPaths($content,$path) 
     165  { 
     166    if (preg_match_all("/url\(\s?[\'|\"]?(.+)[\'|\"]?\s?\)/ix", $content, $urlMatches) ) 
     167    { 
     168      $urlMatches = array_unique( $urlMatches[1] ); 
     169      $cssPathArray = explode(DIRECTORY_SEPARATOR, $path); 
     170 
     171      // pop the css file name 
     172      array_pop( $cssPathArray ); 
     173      $cssPathCount   = count( $cssPathArray ); 
     174      foreach( $urlMatches as $match ) 
     175      { 
     176        $match = str_replace( array('"', "'"), '', $match ); 
     177        $relativeCount = substr_count( $match, '../' ); 
     178        // replace path if it is realtive 
     179        if ( $match[0] !== '/' and strpos( $match, 'http:' ) === false ) 
     180        { 
     181          $cssPathSlice = $relativeCount === 0 ? $cssPathArray : array_slice( $cssPathArray  , 0, $cssPathCount - $relativeCount  ); 
     182          $newMatchPath = implode('/', $cssPathSlice) . '/' . str_replace('../', '', $match); 
     183          $content = str_replace( $match, $newMatchPath, $content ); 
     184        } 
     185      } 
     186    } 
     187    return $content; 
     188  } 
     189 
     190  private function checkFile($element) 
     191  { 
     192    $sf_symfony_data_dir = sfConfig::get('sf_symfony_data_dir'); 
     193 
     194    $cachedir  = sfConfig::get('sf_cache_dir'); 
     195    $webdir    = sfConfig::get('sf_web_dir'); 
     196    $cssdir    = $webdir.DIRECTORY_SEPARATOR.'css'; 
     197    $jsdir     = $webdir.DIRECTORY_SEPARATOR.'js'; 
     198 
     199    // Determine the directory and type we should use 
     200    switch ($this->type) 
     201    { 
     202      case 'css': 
     203        $dir = $cssdir; 
     204        break; 
     205      case 'javascript': 
     206        $dir = $jsdir; 
     207        break; 
     208    } 
     209 
     210    $path = null; 
     211    if (substr($element, 0, 4) == '/sf/') 
     212    { 
     213      $path = $sf_symfony_data_dir.DIRECTORY_SEPARATOR.'web'.$element; 
     214    } 
     215    else if (substr($element, 0, 3) == 'sf/') 
     216    { 
     217      $path = $sf_symfony_data_dir.DIRECTORY_SEPARATOR.'web'.DIRECTORY_SEPARATOR.$element; 
     218    } 
     219    else if (0 === strpos($element, '/')) 
     220    { 
     221      $path = realpath($webdir.$element); 
     222    } 
     223    else 
     224    { 
     225      $path = realpath($dir.DIRECTORY_SEPARATOR.$element); 
     226    } 
     227 
     228    if (!file_exists($path)) return false; 
     229 
     230    $this->files[] = $path; 
     231    $this->lastmodified = max($this->lastmodified, filemtime($path)); 
     232    return true; 
    80233  } 
    81234 
    82235  protected function getCombinedStylesheets() 
    83236  { 
    84     $response = $this->getContext()->getResponse(); 
    85     $sf_relative_url_root = $this->getContext()->getRequest()->getRelativeUrlRoot(); 
    86237    $root_css_only = $this->getParameter('root_css_only', true); 
    87  
    88238    $already_seen = array(); 
    89239    $combined_sources = array(); 
     
    91241    foreach (array('first', '', 'last') as $position) 
    92242    { 
    93       foreach ($response->getStylesheets($position) as $files => $options) 
     243      foreach ($this->response->getStylesheets($position) as $files => $options) 
    94244      { 
    95245        if (!is_array($files)) 
     
    111261          $path = _compute_public_path($file, 'css', 'css'); 
    112262 
    113           if ((!$root_css_only && !strpos($path, '://')) || ($root_css_only && strpos($path, $sf_relative_url_root.'/css/') === 0)) { 
    114             $combined_sources[] = (!$root_css_only ? preg_replace("/^".str_replace('/', '\/', $sf_relative_url_root.'/css/')."/i", '', $path) : $path); 
    115             $response->getParameterHolder()->remove($file, 'helper/asset/auto/stylesheet'.($position ? '/'.$position : '')); 
    116           } 
    117         } 
    118       } 
    119     } 
    120  
    121     if (count($combined_sources)) { 
    122       $combined_sources_str = $sf_relative_url_root . '/css/packed/' . implode(',', $combined_sources); 
    123       $response->addStylesheet($combined_sources_str, '', array('raw_name'=>true)); 
    124     } 
    125   } 
    126  
    127   protected function isInvalidMediaType($options) { 
     263          if ((!$root_css_only && !strpos($path, '://')&& strpos($path, '.css')) || ($root_css_only && strpos($path, $this->sf_relative_url_root.'/css/') === 0)) { 
     264            $element = (!$root_css_only ? preg_replace("/^".str_replace('/', '\/', $this->sf_relative_url_root.'/css/')."/i", '', $path) : $path); 
     265            if($this->checkFile($element)) 
     266            { 
     267              $combined_sources[] = $element; 
     268              $this->response->getParameterHolder()->remove($file, 'helper/asset/auto/stylesheet'.($position ? '/'.$position : '')); 
     269            } 
     270          } 
     271        } 
     272      } 
     273    } 
     274 
     275    if (count($combined_sources)) 
     276    { 
     277      $cacheFileName = $this->lastmodified . '-' . md5(implode(',', $combined_sources)); 
     278      if($this->cacheFile($cacheFileName)) 
     279      { 
     280        $combined_sources_str = $this->sf_relative_url_root ."/packed/css/$cacheFileName/packed.css"; 
     281        //TODO: keep track if there is a dynamic file in the middle of static files and create multiple packed files 
     282        // if required to keep the proper order 
     283        $this->response->addStylesheet($combined_sources_str, 'first'); 
     284      } 
     285    } 
     286  } 
     287 
     288  protected function isInvalidMediaType($options) 
     289  { 
    128290    return isset($options['media']) && !in_array($options['media'], array('', 'all', 'screen')); 
    129291  } 
    130292 
    131   protected function isAbsolutePath($options) { 
     293  protected function isAbsolutePath($options) 
     294  { 
    132295    return isset($options['absolute']) && $options['absolute'] == true; 
    133296  }