developer.jelix.org is not used any more and exists only for history. Post new tickets on the Github account.
developer.jelix.org n'est plus utilisée, et existe uniquement pour son historique. Postez les nouveaux tickets sur le compte github.

Ticket #860: ticket_860.diff

File ticket_860.diff, 25.1 KB (added by bricet, 11 years ago)

Yet another bug about jsUniqueUrlId and cssUniqueUrlId. Really, really sorry ...

  • build/manifests/jelix-lib.mn

    diff -r 9ffb4169618e build/manifests/jelix-lib.mn
    a b  
    254254  jLog.class.php
    255255  jMailer.class.php
    256256  jMessage.class.php
     257  jMinifier.class.php
    257258  jSmtp.class.php
    258259  jTcpdf.class.php
    259260  jVersionComparator.class.php
     
    555557! WSDLStruct.class.php
    556558! WSException.class.php
    557559
     560cd lib/minify/min/lib
     561! JSMinPlus.php
     562! Minify.php
     563! JSMin.php
     564! FirePHP.php
     565
     566cd lib/minify/min/lib/Minify
     567! YUICompressor.php
     568! ImportProcessor.php
     569! Lines.php
     570! CommentPreserver.php
     571! Build.php
     572! CSS.php
     573! Source.php
     574! Logger.php
     575! HTML.php
     576! Packer.php
     577
     578cd lib/minify/min/lib/Minify/CSS
     579! UriRewriter.php
     580! Compressor.php
     581
     582cd lib/minify/min/lib/Minify/Controller
     583! Version1.php
     584! Groups.php
     585! Base.php
     586! Files.php
     587! Page.php
     588! MinApp.php
     589
     590
     591
    558592cd lib/phpMailer
    559593! class.phpmailer.php
    560594! class.pop3.php
  • lib/jelix/CREDITS

    diff -r 9ffb4169618e lib/jelix/CREDITS
    a b  
    144144 - fixed little bugs in jForms (#789, #790)
    145145 - little improvement in number_format modifier (#875)
    146146
     147Brice Tence (aka brayce)
     148 - introduced optional jMinifier for jResponseHtml for JS/CSS concatenation and minification (#860)
     149
    147150Contributors
    148151------------
    149152
  • lib/jelix/core/defaultconfig.ini.php

    diff -r 9ffb4169618e lib/jelix/core/defaultconfig.ini.php
    a b  
    9292htmlfragment = jResponseHtmlFragment
    9393htmlauth = jResponseHtml
    9494
     95[jResponseHtml]
     96;concatenate and minify CSS and/or JS files :
     97minifyCSS = off
     98minifyJS = off
     99; check all filemtime() of source files to check if minify's cache should be generated again. Should be set to "off" on production servers :
     100minifyCheckCacheFiletime = on
     101; list of filenames (no path) which shouldn't be minified :
     102minifyExcludeCSS = ""
     103minifyExcludeJS = ""
     104; add a unique ID to CSS and/or JS files URLs ( this gives for exemple /file.js?1267704635 ). This ID is actually the filemtime of each served file :
     105jsUniqueUrlId = off
     106cssUniqueUrlId = off
     107
     108
    95109[error_handling]
    96110messageLogFormat = "%date%\t[%code%]\t%msg%\t%file%\t%line%\n"
    97111logFile = error.log
  • lib/jelix/core/response/jResponseHtml.class.php

    diff -r 9ffb4169618e lib/jelix/core/response/jResponseHtml.class.php
    a b  
    1717*
    1818*/
    1919require_once(JELIX_LIB_PATH.'tpl/jTpl.class.php');
     20require_once(JELIX_LIB_PATH.'utils/jMinifier.class.php');
    2021
    2122/**
    2223* HTML response
     
    426427        }
    427428    }
    428429
     430    final protected function outputJsScriptTag( $fileUrl, $scriptParams, $filePath = null ) {
     431        global $gJConfig;
     432
     433        $params = '';
     434        if( is_array($scriptParams) ) {
     435            foreach ($scriptParams as $param_name=>$param_value){
     436                $params .= $param_name.'="'. htmlspecialchars($param_value).'" ';
     437            }
     438        } else {
     439            $params = $scriptParams;
     440        }
     441
     442        $jsFilemtime = '';
     443        if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['jsUniqueUrlId']
     444            && $filePath !== null
     445            && (strpos($fileUrl,'http://')===FALSE) //path is not absolute
     446          ) {
     447            $jsFilemtime = "?".filemtime($filePath);
     448        }
     449        echo '<script type="text/javascript" src="',htmlspecialchars($fileUrl),$jsFilemtime,'" ',$params,'></script>',"\n";
     450    }
     451
     452
     453
     454    final protected function outputCssLinkTag( $fileUrl, $cssParams, $filePath = null ) {
     455        global $gJConfig;
     456
     457        $params = '';
     458        if( is_array($cssParams) ) {
     459            foreach ($cssParams as $param_name=>$param_value){
     460                $params .= $param_name.'="'. htmlspecialchars($param_value).'" ';
     461            }
     462        } else {
     463            $params = $cssParams;
     464        }
     465
     466        $cssFilemtime = '';
     467        if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['cssUniqueUrlId']
     468            && $filePath !== null
     469            && (strpos($fileUrl,'http://')===FALSE) //path is not absolute
     470          ) {
     471            $cssFilemtime = "?".filemtime($filePath);
     472        }
     473        echo '<link type="text/css" href="',htmlspecialchars($fileUrl),$cssFilemtime,'" ',$params,$this->_endTag,"\n";
     474    }
     475
     476
     477
     478
     479    final protected function outputJsScriptTags( &$scriptList ) {
     480        global $gJConfig;
     481
     482        $minifyJsByParams = array();
     483        $minifyExcludeJS = array();
     484
     485        if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['minifyExcludeJS'] ) {
     486            $minifyExcludeJS = explode( ',', $gJConfig->jResponseHtml['minifyExcludeJS'] );
     487        }
     488
     489        foreach ($scriptList as $src=>$params){
     490            //the extra params we may found in there.
     491            $scriptParams = '';
     492
     493            $pathSrc = $src;
     494            if ( $gJConfig->urlengine['basePath'] != '/' &&
     495                $gJConfig->urlengine['basePath'] != '' ) {
     496                    $res = explode($gJConfig->urlengine['basePath'],$src);
     497                    if ( count($res) > 1 )
     498                        list(,$pathSrc) = $res;
     499                }
     500
     501            $pathIsAbsolute = (strpos($pathSrc,'http://')!==FALSE);
     502
     503            if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['minifyJS'] &&
     504                ! $pathIsAbsolute && ! in_array(basename($pathSrc), $minifyExcludeJS) ) {
     505                //this file should be minified
     506                $sparams=$params;
     507                ksort($sparams); //sort to avoid duplicity just because of params order
     508                foreach ($sparams as $param_name=>$param_value){
     509                    $scriptParams .= $param_name.'="'. htmlspecialchars($param_value).'" ';
     510                }
     511                $minifyJsByParams[$scriptParams][] = "$src";
     512            } else {
     513                // current script should not be minified
     514                // thus to preserve scripts order we should apply previous pending minifications and generate its script tag
     515                // ex: a.js, b.js, c.js, d.js where c should not be minified. script tag generated must be min_a_+_b.js, c.js, min_d.js
     516                foreach ($minifyJsByParams as $param_value=>$js_files) {
     517                    foreach (jMinifier::minify( $js_files, 'js' ) as $minifiedJs ) {
     518                        $this->outputJsScriptTag( $gJConfig->urlengine['basePath'].$minifiedJs, $param_value, JELIX_APP_WWW_PATH.$minifiedJs);
     519                    }
     520                }
     521                // minified operation finished on pending scripts. thus clear js array of scripts to minify :
     522                $minifyJsByParams = array();
     523
     524                $this->outputJsScriptTag( $src, $params, $pathSrc );
     525            }
     526        }
     527        foreach ($minifyJsByParams as $param_value=>$js_files) {
     528            foreach (jMinifier::minify( $js_files, 'js' ) as $minifiedJs ) {
     529                $this->outputJsScriptTag( $gJConfig->urlengine['basePath'].$minifiedJs, $param_value, JELIX_APP_WWW_PATH.$minifiedJs);
     530            }
     531        }
     532    }
     533
     534
     535
     536    final protected function outputCSSLinkTags( &$linkList ) {
     537        global $gJConfig;
     538
     539        $minifyCssByParams = array();
     540        $minifyExcludeCSS = array();
     541
     542        if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['minifyExcludeCSS'] ) {
     543            $minifyExcludeCSS = explode( ',', $gJConfig->jResponseHtml['minifyExcludeCSS'] );
     544        }
     545
     546        foreach ($linkList as $src=>$params){
     547            //the extra params we may found in there.
     548            $cssParams = '';
     549           
     550            $pathSrc = $src;
     551            if ( $gJConfig->urlengine['basePath'] != '/' &&
     552                $gJConfig->urlengine['basePath'] != '' ) {
     553                    $res = explode($gJConfig->urlengine['basePath'],$src);
     554                    if ( count($res) > 1 )
     555                        list(,$pathSrc) = $res;
     556                }
     557
     558            $pathIsAbsolute = (strpos($pathSrc,'http://')!==FALSE);
     559
     560            if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['minifyCSS'] &&
     561                ! $pathIsAbsolute && ! in_array(basename($pathSrc), $minifyExcludeCSS) ) {
     562                //this file should be minified
     563                $sparams=$params;
     564                ksort($sparams); //sort to avoid duplicity just because of params order
     565                foreach ($sparams as $param_name=>$param_value){
     566                    if( $param_name != "media" ) {
     567                        $cssParams .= $param_name.'="'. htmlspecialchars($param_value).'" ';
     568                    }
     569                }
     570                if(!isset($params['rel']))
     571                    $cssParams .='rel="stylesheet" ';
     572                if( isset($params['media'] ) ) {
     573                    //split for each media if specified
     574                    foreach ( explode(',', $params['media']) as $medium) {
     575                        $myCssParams = $cssParams . 'media="' . $medium . '" ';
     576                        $minifyCssByParams[$myCssParams][] = "$src";
     577                    }
     578                } else {
     579                    $minifyCssByParams[$cssParams][] = "$src";
     580                }
     581            } else {
     582                // current stylesheet should not be minified
     583                // thus to preserve stylesheets order we should apply previous pending minifications and generate its link tag
     584                // ex: a.css, b.css, c.css, d.css where c should not be minified. script tag genrated must be min_a_+_b.css, c.js, min_d.js
     585                foreach ($minifyCssByParams as $param_value=>$css_files) {
     586                    foreach (jMinifier::minify( $css_files, 'css' ) as $minifiedCss ) {
     587                        $this->outputCssLinkTag( $gJConfig->urlengine['basePath'].$minifiedCss, $param_value, JELIX_APP_WWW_PATH.$minifiedCss);
     588                    }
     589                }
     590                $minifyCssByParams = array();
     591
     592                if(!isset($params['rel']))
     593                    $params['rel'] ='stylesheet';
     594               
     595                $this->outputCssLinkTag( $src, $params, $pathSrc);
     596            }
     597        }
     598        foreach ($minifyCssByParams as $param_value=>$css_files) {
     599            foreach (jMinifier::minify( $css_files, 'css' ) as $minifiedCss ) {
     600                $this->outputCssLinkTag( $gJConfig->urlengine['basePath'].$minifiedCss, $param_value, JELIX_APP_WWW_PATH.$minifiedCss);
     601            }
     602        }
     603    }
     604
     605
     606
     607
     608
    429609    /**
    430610     * generate the content of the <head> content
    431611     */
    432612    final protected function outputHtmlHeader (){
     613        global $gJConfig;
     614
     615        $minifyExcludeCSS = array();
     616
     617        if( isset($gJConfig->jResponseHtml) && $gJConfig->jResponseHtml['minifyExcludeCSS'] ) {
     618            $minifyExcludeCSS = explode( ',', $gJConfig->jResponseHtml['minifyExcludeCSS'] );
     619        }
     620
    433621        echo '<head>'."\n";
    434622        if($this->_isXhtml && $this->xhtmlContentType && strstr($_SERVER['HTTP_ACCEPT'],'application/xhtml+xml')){     
    435623            echo '<meta content="application/xhtml+xml; charset='.$this->_charset.'" http-equiv="content-type"'.$this->_endTag;
     
    455637        if (!empty($this->_MetaAuthor)) {
    456638            echo '<meta name="author" content="'.htmlspecialchars($this->_MetaAuthor).'" '.$this->_endTag;
    457639        }
    458         // css link
    459         foreach ($this->_CSSLink as $src=>$params){
    460             //the extra params we may found in there.
    461             $more = '';
    462             foreach ($params as $param_name=>$param_value){
    463                 $more .= $param_name.'="'. htmlspecialchars($param_value).'" ';
    464             }
    465             if(!isset($params['rel']))
    466                 $more .='rel="stylesheet" ';
    467             echo  '<link type="text/css" href="',htmlspecialchars($src),'" ',$more,$this->_endTag;
    468         }
     640
     641        $this->outputCSSLinkTags( $this->_CSSLink );
    469642
    470643        foreach ($this->_CSSIELink as $src=>$params){
    471644            // special params for conditions on IE versions
    472645            if (!isset($params['_ieCondition']))
    473               $params['_ieCondition'] = 'IE' ;
     646                $params['_ieCondition'] = 'IE' ;
    474647            echo '<!--[if '.$params['_ieCondition'].' ]>';
    475             //the extra params we may found in there.
    476             $more = '';
    477             foreach ($params as $param_name=>$param_value){
    478                 if ($param_name=='_ieCondition')
    479                   continue ;
    480                 $more .= $param_name.'="'. htmlspecialchars($param_value).'" ';
    481             }
    482             if(!isset($params['rel']))
    483                 $more .='rel="stylesheet" ';
    484             echo  '<link type="text/css" href="',htmlspecialchars($src),'" ',$more,$this->_endTag;
     648
     649            unset($params['_ieCondition']);
     650            $cssIeLink = array($src=>$params); //make a var to pass it by ref
     651            $this->outputCSSLinkTags( $cssIeLink );
     652
    485653            echo '<![endif]-->';
    486654        }
    487655
     
    499667            echo '<link rel="',$params[0],'" type="',$params[1],'" href="',htmlspecialchars($href),'" ',$more,$this->_endTag;
    500668        }
    501669
    502         // js link
    503         foreach ($this->_JSLink as $src=>$params){
    504             //the extra params we may found in there.
    505             $more = '';
    506             foreach ($params as $param_name=>$param_value){
    507                 $more .= $param_name.'="'. htmlspecialchars($param_value).'" ';
    508             }
    509             echo '<script type="text/javascript" src="',htmlspecialchars($src),'" ',$more,'></script>',"\n";
    510         }
     670        $this->outputJsScriptTags( $this->_JSLink );
     671
    511672        if(count($this->_JSIELink)){
    512673            echo '<!--[if IE]>';
    513             foreach ($this->_JSIELink as $src=>$params){
    514                 //the extra params we may found in there.
    515                 $more = '';
    516                 foreach ($params as $param_name=>$param_value){
    517                     $more .= $param_name.'="'. htmlspecialchars($param_value).'" ';
    518                 }
    519                 echo '<script type="text/javascript" src="',htmlspecialchars($src),'" ',$more,'></script>',"\n";
    520             }
     674
     675            $this->outputJsScriptTags( $this->_JSIELink );
     676
    521677            echo '<![endif]-->';
    522678        }
    523679
  • new file lib/jelix/utils/jMinifier.class.php

    diff -r 9ffb4169618e lib/jelix/utils/jMinifier.class.php
    - +  
     1<?php
     2/**
     3 * @package    jelix
     4 * @subpackage core
     5 * @author     Brice Tence
     6 * @copyright  2010 Brice Tence
     7 *   Idea of this class was picked from the Minify project ( Minify 2.1.3, http://code.google.com/p/minify )
     8 * @link       http://www.jelix.org
     9 * @licence    GNU Lesser General Public Licence see LICENCE file or http://www.gnu.org/licenses/lgpl.html
     10 */
     11
     12/**
     13 * This object is responsible to concatenate and minify CSS or JS files.
     14 * There is a cache system so that previously minified files are served directly.
     15 * Otherwise, files ares concatenated, minified and stored in cache.
     16 * We also check if cache is up to date and needs to be refreshed.
     17 * @package  jelix
     18 * @subpackage core
     19 * @author     Brice Tence
     20 * @copyright  2010 Brice Tence
     21 * @licence    GNU Lesser General Public Licence see LICENCE file or http://www.gnu.org/licenses/lgpl.html .
     22 * initial author : Brice Tence
     23 */
     24
     25
     26
     27define('MINIFY_MIN_DIR', LIB_PATH.'minify/min/');
     28
     29// setup include path
     30set_include_path(MINIFY_MIN_DIR.'/lib' . PATH_SEPARATOR . get_include_path());
     31
     32require_once "Minify/Controller/MinApp.php";
     33require_once 'Minify/Source.php';
     34require_once 'Minify.php';
     35
     36
     37
     38
     39class jMinifier {
     40
     41    const TYPE_CSS = 'text/css';
     42    const TYPE_HTML = 'text/html';
     43    // there is some debate over the ideal JS Content-Type, but this is the
     44    // Apache default and what Yahoo! uses..
     45    const TYPE_JS = 'application/x-javascript';
     46
     47
     48    /**
     49     * @var Minify_Controller active controller for current request
     50     */
     51    protected static $_controller = null;
     52
     53    /**
     54     * @var array options for current request
     55     */
     56    protected static $_options = null;
     57
     58    /**
     59     * Cache file locking. Set to false of filesystem is NFS. On at least one
     60     * NFS system flock-ing attempts stalled PHP for 30 seconds!
     61     */
     62    protected static $min_cacheFileLocking = true;
     63
     64    /**
     65     * If this string is not empty AND the serve() option 'bubbleCssImports' is
     66     * NOT set, then serve() will check CSS files for @import declarations that
     67     * appear too late in the combined stylesheet. If found, serve() will prepend
     68     * the output with this warning.
     69     *
     70     * @var string $importWarning
     71     */
     72    public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n";
     73
     74
     75    /**
     76     * This is a static class, so private constructor
     77     */
     78    private function __construct(){
     79    }
     80
     81    /**
     82     * @param    array   $fileList    Array of file URLs to include
     83     * @return array of file path to minify's www cached file. A further improvment could be to output several files (size limit for iPhone). That's why this is an array.
     84     */
     85    public static function minify( $fileList, $fileType ){
     86        global $gJConfig,$gJCoord;
     87
     88        $cachePathCSS = 'cache/minify/css/';
     89        jFile::createDir(JELIX_APP_WWW_PATH.$cachePathCSS);
     90        $cachePathJS = 'cache/minify/js/';
     91        jFile::createDir(JELIX_APP_WWW_PATH.$cachePathJS);
     92
     93        $minifiedFiles = array();
     94
     95        $minAppMaxFiles = count($fileList);
     96
     97        //compute a hash of source files to manage cache
     98        $sourcesHash = md5(implode(';', $fileList));
     99
     100        $options = array();
     101        $options['MinApp']['maxFiles'] = $minAppMaxFiles;
     102        $cachePath = '';
     103        $cacheExt = '';
     104        switch ($fileType) {
     105        case 'js':
     106            $options['contentType'] = self::TYPE_JS;
     107            $cachePath = $cachePathJS;
     108            $cacheExt = 'js';
     109            break;
     110        case 'css':
     111            $options['contentType'] = self::TYPE_CSS;
     112            $cachePath = $cachePathCSS;
     113            $cacheExt = 'css';
     114            break;
     115        default:
     116            return;
     117        }
     118
     119        $cacheFilepath = $cachePath . $sourcesHash . '.' . $cacheExt;
     120
     121
     122        $cacheFilepathFilemtime = null;
     123        if( is_file( JELIX_APP_WWW_PATH.$cacheFilepath ) ) {
     124            $cacheFilepathFilemtime = filemtime( JELIX_APP_WWW_PATH.$cacheFilepath );
     125        }
     126
     127        //If we should not check filemtime of source files, let's see if we have the result in our cache
     128        //We assume minifyCheckCacheFiletime is "on" if not set
     129        if( isset($GLOBALS['gJConfig']->responseHtml) &&
     130            $GLOBALS['gJConfig']->responseHtml['minifyCheckCacheFiletime'] === false &&
     131            $cacheFilepathFilemtime !== null ) {
     132                $minifiedFiles[] = $cacheFilepath;
     133                return $minifiedFiles;
     134            }
     135
     136
     137        $sources = array();
     138        //add source files
     139        foreach ($fileList as $file) {
     140            $minifySource = new Minify_Source(array(
     141                'filepath' => realpath($_SERVER['DOCUMENT_ROOT'] . $file)
     142            ));
     143            $sources[] = $minifySource;
     144        }
     145
     146        $controller = new Minify_Controller_MinApp();
     147        $controller->sources = $sources;
     148        $options = $controller->analyzeSources($options);
     149        $options = $controller->mixInDefaultOptions($options);
     150
     151        self::$_options = $options;
     152        self::$_controller = $controller;
     153
     154        if( $cacheFilepathFilemtime === null ||
     155            $cacheFilepathFilemtime < self::$_options['lastModifiedTime'] ) {
     156                //cache does not exist or is to old. Let's refresh it :
     157
     158                //rewrite URL in CSS files
     159                if (self::$_options['contentType'] === self::TYPE_CSS && self::$_options['rewriteCssUris']) {
     160                    reset($controller->sources);
     161                    while (list($key, $source) = each(self::$_controller->sources)) {
     162                        if ($source->filepath
     163                            && !isset($source->minifyOptions['currentDir'])
     164                            && !isset($source->minifyOptions['prependRelativePath'])
     165                        ) {
     166                            $source->minifyOptions['currentDir'] = dirname($source->filepath);
     167                        }
     168                    }
     169                }
     170
     171                $cacheData = self::combineAndMinify();
     172
     173                $flag = self::$min_cacheFileLocking
     174                    ? LOCK_EX
     175                    : null;
     176
     177                if (is_file(JELIX_APP_WWW_PATH.$cacheFilepath)) {
     178                    @unlink(JELIX_APP_WWW_PATH.$cacheFilepath);
     179                }
     180
     181                if (! @file_put_contents(JELIX_APP_WWW_PATH.$cacheFilepath, $cacheData, $flag)) {
     182                    return false;
     183                }
     184            }
     185
     186        $minifiedFiles[] = $cacheFilepath;
     187
     188        return $minifiedFiles;
     189    }
     190
     191
     192
     193    /**
     194     * Combines sources and minifies the result.
     195     *
     196     * @return string
     197     */
     198    protected static function combineAndMinify()
     199    {
     200        $type = self::$_options['contentType']; // ease readability
     201
     202        // when combining scripts, make sure all statements separated and
     203        // trailing single line comment is terminated
     204        $implodeSeparator = ($type === self::TYPE_JS)
     205            ? "\n;"
     206            : '';
     207        // allow the user to pass a particular array of options to each
     208        // minifier (designated by type). source objects may still override
     209        // these
     210        $defaultOptions = isset(self::$_options['minifierOptions'][$type])
     211            ? self::$_options['minifierOptions'][$type]
     212            : array();
     213        // if minifier not set, default is no minification. source objects
     214        // may still override this
     215        $defaultMinifier = isset(self::$_options['minifiers'][$type])
     216            ? self::$_options['minifiers'][$type]
     217            : false;
     218
     219        if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) {
     220            // all source have same options/minifier, better performance
     221            // to combine, then minify once
     222            foreach (self::$_controller->sources as $source) {
     223                $pieces[] = $source->getContent();
     224            }
     225            $content = implode($implodeSeparator, $pieces);
     226            if ($defaultMinifier) {
     227                self::$_controller->loadMinifier($defaultMinifier);
     228                $content = call_user_func($defaultMinifier, $content, $defaultOptions);   
     229            }
     230        } else {
     231            // minify each source with its own options and minifier, then combine
     232            foreach (self::$_controller->sources as $source) {
     233                // allow the source to override our minifier and options
     234                $minifier = (null !== $source->minifier)
     235                    ? $source->minifier
     236                    : $defaultMinifier;
     237                $options = (null !== $source->minifyOptions)
     238                    ? array_merge($defaultOptions, $source->minifyOptions)
     239                    : $defaultOptions;
     240                if ($minifier) {
     241                    self::$_controller->loadMinifier($minifier);
     242                    // get source content and minify it
     243                    $pieces[] = call_user_func($minifier, $source->getContent(), $options);     
     244                } else {
     245                    $pieces[] = $source->getContent();     
     246                }
     247            }
     248            $content = implode($implodeSeparator, $pieces);
     249        }
     250
     251        if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
     252            $content = self::_handleCssImports($content);
     253        }
     254
     255        // do any post-processing (esp. for editing build URIs)
     256        if (self::$_options['postprocessorRequire']) {
     257            require_once self::$_options['postprocessorRequire'];
     258        }
     259        if (self::$_options['postprocessor']) {
     260            $content = call_user_func(self::$_options['postprocessor'], $content, $type);
     261        }
     262        return $content;
     263    }
     264
     265    /**
     266     * Bubble CSS @imports to the top or prepend a warning if an
     267     * @import is detected not at the top.
     268     */
     269    protected static function _handleCssImports($css)
     270    {
     271        if (self::$_options['bubbleCssImports']) {
     272            // bubble CSS imports
     273            preg_match_all('/@import.*?;/', $css, $imports);
     274            $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
     275        } else if ('' !== self::$importWarning) {
     276            // remove comments so we don't mistake { in a comment as a block
     277            $noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
     278            $lastImportPos = strrpos($noCommentCss, '@import');
     279            $firstBlockPos = strpos($noCommentCss, '{');
     280            if (false !== $lastImportPos
     281                && false !== $firstBlockPos
     282                && $firstBlockPos < $lastImportPos
     283            ) {
     284                // { appears before @import : prepend warning
     285                $css = self::$importWarning . $css;
     286            }
     287        }
     288        return $css;
     289    }
     290
     291}