PageRenderTime 373ms CodeModel.GetById 100ms app.highlight 113ms RepoModel.GetById 109ms app.codeStats 1ms

/plugins/system/jch_optimize/jch_optimize.php

https://bitbucket.org/organicdevelopment/joomla-2.5
PHP | 677 lines | 436 code | 70 blank | 171 comment | 82 complexity | 0174009d3ec2b5412227c769b9b89af2 MD5 | raw file
  1<?php
  2
  3/**
  4 * JCH Optimize - Joomla! plugin to aggregate and minify external resources for
  5 *   optmized downloads
  6 * @author Samuel Marshall <sdmarshall73@gmail.com>
  7 * @copyright Copyright (c) 2010 Samuel Marshall
  8 * @license GNU/GPLv3, See LICENSE file
  9 * This program is free software: you can redistribute it and/or modify
 10 * it under the terms of the GNU General Public License as published by
 11 * the Free Software Foundation, either version 3 of the License, or
 12 * (at your option) any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
 20 *
 21 * This plugin, inspired by CssJsCompress <http://www.joomlatags.org>, was
 22 * created in March 2010 and includes other copyrighted works. See individual
 23 * files for details.
 24 */
 25/**
 26 * Modified for Joomla 1.6 by Branislav Maksin - www.maksin.ms
 27 */
 28// no direct access
 29defined('_JEXEC') or die('Restricted access');
 30
 31jimport('joomla.plugin.plugin');
 32require_once ( dirname(__FILE__) . DS . 'cache' . DS . 'CSS.php' );
 33require_once ( dirname(__FILE__) . DS . 'cache' . DS . 'HTML.php' );
 34require_once ( dirname(__FILE__) . DS . 'cache' . DS . 'jsmin.php' );
 35
 36class plgSystemJCH_Optimize extends JPlugin {
 37
 38    /** @var object   Holds reference to JCACHE object */
 39    protected $oCache = '';
 40    /** @var string   Head section of html */
 41    protected $sHead = '';
 42    /** @var string   Html of full page */
 43    protected $sBody = '';
 44    /** @var array    Array of css or js urls taken from head */
 45    protected $aLinks = array();
 46    /** @var array    Array of css media types */
 47    protected $aMedia = array();
 48    /** @var array    Array of ordered js files */
 49    protected $aOrder = array();
 50    /** @var integer  First index for $alinks */
 51    protected $iCnt = 2000;
 52    /** @var array    Array of arguments to be used in callback functions */
 53    protected $aCallbackArgs = array();
 54    /** @var object   Holds reference to JURI object */
 55    protected $oUri = '';
 56
 57    /**
 58     * Triggered by onAfterRender event; will remove all urls of css and js
 59     * files in head tags (except those excluded) and aggregate in single css or
 60     * js file
 61     *
 62     */
 63    public function onAfterRender() {
 64        $iCss = $this->params->get('css', 1);
 65        $iJavaScript = $this->params->get('javascript', 1);
 66        $iHtmlMin = $this->params->get('html_minify', 0);
 67        $sExComp = $this->params->get('excludeComponents', '');
 68        $iExExtensions = $this->params->get('excludeAllExtensions', '1');
 69
 70        if (!$iCss && !$iJavaScript && !$iHtmlMin) {
 71            return true;
 72        }
 73
 74        $oApplication = JFactory::getApplication();
 75        if ($oApplication->getName() != 'site') {
 76            return true;
 77        }
 78
 79        $oDocument = JFactory::getDocument();
 80        $sDocType = $oDocument->getType();
 81        $sLnEnd = $oDocument->_getLineEnd();
 82        $sTab = $oDocument->_getTab();
 83
 84        if ($sDocType != 'html') {
 85            return;
 86        }
 87        $sExCompRegex = '';
 88        $sExExtensionsRegex = '';
 89
 90        if ($iExExtensions) {
 91            $sExExtensionsRegex = '|(?:/components/)|(?:/modules/)|(?:/plugins/)';
 92        }
 93        if (isset($sExComp) && $sExComp) {
 94            $aComponents = $this->getArray($sExComp);
 95            foreach ($aComponents as $sComponent) {
 96                $sExCompRegex .= '|(?:/' . $sComponent . '/)';
 97            }
 98        }
 99
100        $this->sBody = JResponse::getBody();
101        $sHeadRegex = '~<head>.*?</head>~msi';
102        preg_match($sHeadRegex, $this->sBody, $aHeadMatches);
103        $this->sHead = $aHeadMatches[0];
104
105        $this->oUri = clone JURI::getInstance();
106
107        $sCacheGroup = 'plg_jch_optimize';
108        $this->oCache = JFactory::getCache($sCacheGroup, 'callback', 'file');
109        $this->oCache->setCaching(1);
110        $this->oCache->setLifetime((int) $this->params->get('lifetime', '30') * 24 * 60 * 60);
111
112        if ($iJavaScript) {
113            $this->aOrder = $this->getArray($this->params->get('customOrder', ''));
114            $this->aCallbackArgs['excludes'] = $this->getArray($this->params->get('excludeJs', ''));
115            unset($this->aLinks);
116            $sType = 'js';
117
118            $this->excludeIf($sType);
119
120            $iDefer = $this->params->get('defer_js', 0);
121            $iJsPosition = $this->params->get('bottom_js', 1);
122
123            $this->aCallbackArgs['type'] = $sType;
124            $this->aCallbackArgs['counter'] = 0;
125            $sJsRegexStart = '~<script
126                               (?=[^>]+?src\s?=\s?["\']([^"\']+?/([^/]+\.js)(?:\?[^"\']*?)?)["\'])
127                               (?=[^>]+?type\s?=\s?["\']text/javascript["\'])
128                               (?:(?!(?:\.php)|(?:/editors/)';
129            $sJsRegexEnd = ')[^>])+>(?:(?:[^<]*?)</script>)?~ix';
130            $sJsRegex = $sJsRegexStart . $sExExtensionsRegex . $sExCompRegex . $sJsRegexEnd;
131            $this->sHead = preg_replace_callback($sJsRegex, array($this, 'replaceScripts'), $this->sHead);
132
133            $sLink = '<script type="text/javascript" src="URL"';
134            $sLink .= $iDefer ? ' defer="defer"' : '';
135            $sLink .= '></script>';
136            $sNewJsLink = $iJsPosition == 1 ? $sTab . $sLink . $sLnEnd . '</body>' : $sLink;
137            $iCnt = $this->aCallbackArgs['counter'];
138            if (!empty($this->aLinks)) {
139                $iJsId = $this->processLink($sType, $sCacheGroup, $sNewJsLink, $sLnEnd, $iCnt);
140            }
141        }
142
143        if ($iCss || $this->params->get('csg_enable', 0)) {
144            $this->aCallbackArgs['excludes'] = $this->getArray($this->params->get('excludeCss', ''));
145            unset($this->aLinks);
146            $sType = 'css';
147            $this->excludeIf($sType);
148            $this->aCallbackArgs['type'] = $sType;
149            $this->aCallbackArgs['counter'] = 0;
150            $sCssRegexStart = '~<link
151                                    (?=[^>]+?href\s?=\s?["\']([^"\']+?/([^/]+\.css)(?:\?[^"\']*?)?)["\'])
152                                    (?=[^>]+?type\s?=\s?["\']text/css["\'])
153                                    (?:(?!(?:\.php)|(?:title\s?=\s?["\'])';
154            $sCssRegexEnd = ')[^>])+>(?:(?:[^<]*?)</[^>]+>)?~ix';
155            $sCssRegex = $sCssRegexStart . $sExExtensionsRegex . $sExCompRegex . $sCssRegexEnd;
156            $this->sHead = preg_replace_callback($sCssRegex, array($this, 'replaceScripts'), $this->sHead);
157            //print_r($this->aLinks);
158            $sNewCssLink = '</title>' . $sLnEnd . $sTab . '<link rel="stylesheet" type="text/css" ';
159            $sNewCssLink .= 'href="URL"/>';
160
161            if (!empty($this->aLinks)) {
162                $iCssId = $this->processLink('css', $sCacheGroup, $sNewCssLink, $sLnEnd);
163            }
164        }
165
166        $sBody = preg_replace($sHeadRegex, $this->sHead, $this->sBody);
167        $aOptions = array();
168        if ($this->params->get('css_minify', 0)) {
169            $aOptions['cssMinifier'] = array('Minify_CSS', 'process');
170        }
171        if ($this->params->get('js_minify', 0)) {
172            $aOptions['jsMinifier'] = array('JSMin', 'minify');
173        }
174        if ($iHtmlMin) {
175            $sBody = Minify_HTML::minify($sBody, $aOptions);
176        }
177        JResponse::setBody($sBody);
178    }
179
180    /**
181     * Add js and css urls in conditional tags to excludes list
182     *
183     * @param string $sType   css or js
184     */
185    protected function excludeIf($sType) {
186        if (preg_match_all('~<\!--.*?-->~is', $this->sHead, $aMatches)) {
187            foreach ($aMatches[0] as $sMatch) {
188                preg_match_all('~.*?/([^/]+\.' . $sType . ').*?~', $sMatch, $aExcludesMatches);
189                $this->aCallbackArgs['excludes'][] = @$aExcludesMatches[1][0];
190            }
191        }
192    }
193
194    /**
195     * Use generated id to cache aggregated file
196     *
197     * @param string $sType           css or js
198     * @param string $sCacheGroup    Name of cache group
199     * @param string $sLink           Url for aggregated file
200     * @param string $sLnEnd         Line end
201     */
202    protected function processLink($sType, $sCacheGroup, $sLink, $sLnEnd, $iCnt='') {
203        if ($sType == 'js') {
204            $iMinify = $this->params->get('js_minify', 0);
205        } elseif ($sType == 'css') {
206            $iMinify = $this->params->get('css_minify', 0);
207        }
208        $iImport = $this->params->get('import', 0);
209        $iSprite = $this->params->get('csg_enable', 0);
210
211        $sId = md5(serialize(implode('', $this->aLinks) . $this->params));
212        $aArgs = array($this->aLinks, $sType, $sLnEnd, $iMinify, $iImport, $iSprite, $sId);
213        $aFunction = array($this, 'getContents');
214
215        $bCached = $this->loadCache($aFunction, $aArgs, $sId);
216        $sFileName = $this->getFilename($sId, $sCacheGroup);
217
218        $iTime = (int) $this->params->get('lifetime', '30');
219
220        if ($bCached) {
221            $sUrl = $this->buildUrl($sFileName, $sType, $iTime, $this->isGZ());
222            $sNewLink = str_replace('URL', $sUrl, $sLink);
223            $this->replaceLink($sNewLink, $sType, $iCnt);
224        }
225        //print_r($sFile);
226        return $sId;
227    }
228
229    /**
230     * Returns url of aggregated file
231     *
232     * @param string $sFile		Aggregated file name
233     * @param string $sType		css or js
234     * @param mixed $bGz		True (or 1) if gzip set and enabled
235     * @param number $sTime		Expire header time
236     * @return string			Url of aggregated file
237     */
238    protected function buildUrl($sFile, $sType, $iTime, $bGz=false) {
239        $sPath = JURI::base(true) . '/plugins/system/jch_optimize/cache/';
240        if ($this->params->get('htaccess', 0)) {
241            $sUrl = $sPath . ($bGz ? 'gz/' : 'nz/') . $iTime . '/' . $sFile . '.' . $sType;
242        } else {
243            $oUri = $this->oUri;
244            $oUri->setPath($sPath . 'jscss.php');
245
246            $aVar = array();
247            $aVar['f'] = $sFile;
248            $aVar['type'] = $sType;
249            if ($bGz) {
250                $aVar['gz'] = 'gz';
251            }
252            $aVar['d'] = $iTime;
253            $oUri->setQuery($aVar);
254
255            $sUrl = htmlentities($oUri->toString(array('path', 'query')));
256        }
257        return ($sUrl);
258    }
259
260    /**
261     * Insert url of aggregated file in html
262     *
263     * @param string $sNewLink   Url of aggregated file
264     */
265    protected function replaceLink($sNewLink, $sType, $iCnt='') {
266        if ($sType == 'css') {
267            $this->sHead = str_replace('</title>', $sNewLink, $this->sHead);
268        }
269        if ($sType == 'js') {
270            switch ($this->params->get('bottom_js', 1)) {
271                case 0: //First found javascript tag
272                    $this->sHead = preg_replace('~<JCH_SCRIPT>~', $sNewLink, $this->sHead, 1);
273                    $this->sHead = str_replace('<JCH_SCRIPT>', '', $this->sHead);
274                    break;
275                case 2: //Last found javascript tag
276                    $iCnt--;
277                    $this->sHead = preg_replace('~<JCH_SCRIPT>~', '', $this->sHead, $iCnt);
278                    $this->sHead = str_replace('<JCH_SCRIPT>', $sNewLink, $this->sHead);
279                    break;
280                case 1: //Bottom of page
281                    $this->sHead = str_replace('<JCH_SCRIPT>', '', $this->sHead);
282                    $this->sBody = str_replace('</body>', $sNewLink, $this->sBody);
283                    break;
284
285                default:
286                    break;
287            }
288        }
289    }
290
291    /**
292     * Create and cache aggregated file if it doesn't exists, file will have
293     * lifetime set in global configurations.
294     *
295     * @param array $aFunction    Name of function used to aggregate files
296     * @param array $aArgs        Arguments used by function above
297     * @param string $sId         Generated id to identify cached file
298     * @return boolean           True on success
299     */
300    protected function loadCache($aFunction, $aArgs, $sId) {
301        unset($bCached);
302        $bCached = $this->oCache->get($aFunction, $aArgs, $sId);
303        if (isset($bCached)) {
304            return true;
305        }
306        return false;
307    }
308
309    /**
310     * Gets name of aggregated file
311     *
312     * @param string $sId			Id of cached file
313     * @param string $sCacheGroup	Name of cache group
314     * @return string				Cache file name
315     */
316    protected function getFilename($sId, $sCacheGroup) {
317        $oStorage = $this->oCache->_getStorage();
318        $sName1 = md5($oStorage->_application . '-' . $sId . '-' . $oStorage->_language);
319        $sName = $oStorage->_hash . '-cache-' . $sCacheGroup . '-' . $sName1;
320        return $sName;
321    }
322
323    /**
324     * Check if gzip is set or enabled
325     *
326     * @return boolean   True if gzip parameter set and server is enabled
327     */
328    public function isGZ() {
329        $iIsGz = $this->params->get('gzip', 0);
330        if ($iIsGz) {
331
332            if (!isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
333                return false;
334            } elseif (!extension_loaded('zlib') || ini_get('zlib.output_compression')) {
335                return false;
336            } elseif (false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) {
337                return true;
338            }
339        } else {
340            return false;
341        }
342    }
343
344    /**
345     * Callback function used to remove urls of css and js files in head tags
346     *
347     * @param array $aMatches Array of all matches
348     * @return string        Returns the url if excluded, empty string otherwise
349     */
350    protected function replaceScripts($aMatches) {
351        $sUrl = $aMatches[1];
352
353        if ($this->oUri->isInternal($sUrl)) {
354            $sFile = $aMatches[2];
355
356            $aExcludes = array();
357            if ($this->aCallbackArgs) {
358                $aExcludes = $this->aCallbackArgs['excludes'];
359            }
360            if (in_array($sFile, $aExcludes)) {
361                return $aMatches[0];
362            } else {
363                if (in_array($sFile, $this->aOrder)) {
364
365                    $sKey = array_search($sFile, $this->aOrder);
366                    $this->aLinks[$sKey] = $sUrl;
367                } else {
368
369                    $this->aLinks[$this->iCnt++] = $sUrl;
370                }
371                $iResult = preg_match('~media\s?=\s?["\']([^"\']+?)["\']~i', $aMatches[0], $aMediaTypes);
372                if ($iResult > 0) {
373                    $this->aMedia[$sUrl] = $aMediaTypes[1];
374                }
375                if ($this->aCallbackArgs['type'] == 'js') {
376                    $this->aCallbackArgs['counter']++;
377                    return '<JCH_SCRIPT>';
378                } else {
379                    return '';
380                }
381            }
382        }
383        return $aMatches[0];
384    }
385
386    /**
387     * Prepare css or js files for aggregation and minification
388     *
389     * @param array $aUrlArray   Array of urls of css or js files for aggregation
390     * @param string $sType       css or js
391     * @param string $sLnEnd     line end
392     * @return string         Aggregated (and possibly minified) contents of files
393     */
394    public function getContents($aUrlArray, $sType, $sLnEnd, $iMinify, $iImport, $iSprite, $sId) {
395        ksort($aUrlArray);
396
397        $sContents = $this->combineFiles($aUrlArray, $sLnEnd, $sType);
398
399        if ($sType == 'css') {
400            if ($iImport) {
401                $sContents = $this->replaceImports($sContents, $sLnEnd);
402            }
403            $sContents = trim($this->sortImports($sContents, $sLnEnd));
404
405            if ($iSprite) {
406                $sContents = $this->generateSprite($sContents, $sLnEnd);
407            }
408
409            if ($iMinify) {
410                $sContents = Minify_CSS::process($sContents);
411            }
412        }
413        if ($iMinify && $sType == 'js') {
414            try{
415                $sContents = JSMin::minify($sContents);
416            }catch (JSMinException $e){
417                //Need to test how this handles
418                JError::raiseWarning(101, $e->getMessage());
419		//return false;
420            }    
421        }
422        $sContents = str_replace('LINE_END', $sLnEnd, $sContents);
423        return $sContents;
424    }
425
426    /**
427     * Aggregate contents of CSS and JS files
428     *
429     * @param array $aUrlArray      Array of links of files
430     * @param string $sType          CSS or js
431     * @return string               Aggregarted contents
432     */
433    protected function combineFiles($aUrlArray, $sLnEnd, $sType='') {
434        $iJQueryNoConflict = $this->params->get('jqueryNoConflict', '');
435        $sJQueryFile = $this->params->get('jquery', '');
436        $sContents = '';
437        foreach ($aUrlArray as $sUrl) {
438            $sPath = $this->getFilepath($sUrl);
439            $aPath = explode(DS, $sPath);
440            $sFile = end($aPath);
441
442            preg_match('~.*\.[A-Za-z]{2,3}~', $sPath, $aMatches);
443            if (file_exists($aMatches[0])) {
444                if (preg_match('~.*?\.php~', $sPath)) {
445                    $sContent = $this->evalCss($sPath, $sUrl);
446                } else {
447                    $sContent = file_get_contents($sPath);
448                }
449                if ($iJQueryNoConflict && $sJQueryFile == $sFile && $sType == 'js') {
450                    $sContent.="\n jQuery.noConflict();\n";
451                }
452                if ($sType == 'css') {
453                    unset($this->aCallbackArgs['css_url']);
454                    $this->aCallbackArgs['css_url'] = $sUrl;
455                    $sContent = preg_replace('~@import\s?[\'"]([^\'"]+?)[\'"];~', '@import url("$1");', $sContent);
456                    $sContent = preg_replace_callback('~url\s?\([\'"]?(?![a-z]+:|/+)([^\'")]+)[\'"]?\)~i', array($this, 'correctUrl'), $sContent);
457                    if (@$this->aMedia[$sUrl]) {
458                        $sContent = '@media ' . $this->aMedia[$sUrl] . ' {' . $sLnEnd . $sContent . $sLnEnd . ' }';
459                    }
460                }
461                $sContents .= $sContent . 'LINE_END';
462            }
463        }
464        return $sContents;
465    }
466
467    /**
468     * Get local path of file from the url
469     *
470     * @param string  $sUrl  Url of file
471     * @return string       File path
472     */
473    static public function getFilepath($sUrl) {
474        $sUriBase = str_replace('~', '\~', JURI::base());
475        $sUriPath = str_replace('~', '\~', JURI::base(true));
476        $sUrl = preg_replace(array('~^' . $sUriBase . '~', '~^' . $sUriPath . '/~', '~\?.*?$~'), '', $sUrl);
477        $sUrl = str_replace('/', DS, $sUrl);
478        $sFilePath = JPATH_ROOT . DS . $sUrl;
479
480        return $sFilePath;
481    }
482
483    /**
484     * Splits a string into an array using any regular delimiter or whitespace
485     *
486     * @param string  $sString   Delimited string of components
487     * @return array            An array of the components
488     */
489    static public function getArray($sString) {
490        return $aArray = preg_split('~[\s,;:]+~', $sString);
491    }
492
493    /**
494     * Returns url of current host
495     *
496     * @return string    Url of current host
497     */
498    public function getHost() {
499        $sWww = $this->oUri->toString(array('scheme', 'user', 'pass', 'host', 'port'));
500        return $sWww;
501    }
502
503    /**
504     * Callback function to correct urls in aggregated css files
505     *
506     * @param array $aMatches Array of all matches
507     * @return string         Correct url of images from aggregated css file
508     */
509    protected function correctUrl($aMatches) {
510
511        if (!preg_match('~^(/|http)~', $aMatches[1])) {
512            $sCssRootPath = preg_replace('~/[^/]+\.css~', '/', $this->aCallbackArgs['css_url']);
513            $sImagePath = $sCssRootPath . $aMatches[1];
514            $oUri = clone JURI::getInstance($sImagePath);
515            $oUri->setPath($oUri->getPath());
516            $sCleanPath = $oUri->getPath();
517            $sCleanPath = preg_replace('~^(?!/)~', '/', $sCleanPath);
518            return 'url(' . $sCleanPath . ')';
519        } else {
520            return $aMatches[0];
521        }
522    }
523
524    /**
525     * Replace all @import with internal urls with the file contents
526     *
527     * @param string $sCss   Combined css file
528     * @return string       CSS file with contents from import prepended.
529     */
530    protected function replaceImports($sCss, $sLnEnd) {
531        unset($this->aLinks);
532        $this->aCallbackArgs = array();
533        // print_r($sCss);
534        $sImportCss = '';
535        $sCss = preg_replace_callback('~@import.*?url\([\'"]?(.*?/([^/]+\.css))[\'"]?\);~i', array($this, 'replaceScripts'), $sCss);
536        if (!empty($this->aLinks)) {
537            $sImportCss = $this->combineFiles($this->aLinks, $sLnEnd, 'css');
538        }
539        return $sImportCss . $sCss;
540    }
541
542    /**
543     * Sorts @import and @charset as according to w3C <http://www.w3.org/TR/CSS2/cascade.html> Section 6.3
544     *
545     * @param string $sCss       Combined css
546     * @param string $sLnEnd     Line ending
547     * @return string           CSS with @import and @charset properly sorted
548     */
549    protected function sortImports($sCss, $sLnEnd) {
550        $sImportRegex = '~@import[^;]+?;~i';
551        $sCharsetRegex = '~@charset[^;]+?;~i';
552
553        $n = preg_match_all($sImportRegex, $sCss, $aImportMatches);
554        $aExcludesMatches = preg_match($sCharsetRegex, $sCss, $charset);
555
556        $sImports = '';
557        foreach ($aImportMatches[0] as $sImport) {
558            $sImports .= $sImport . $sLnEnd;
559        }
560        preg_replace(array($sImportRegex, $sCharsetRegex), '', $sCss);
561        $charset = $aExcludesMatches <= 0 ? '' : $charset[0];
562        return $sCss = trim($charset . $sLnEnd . $sImports . $sCss);
563    }
564
565    /**
566     * //Not implemented yet
567     * @param <type> $sPath
568     * @param <type> $sUrl
569     */
570    protected function evalCss($sPath, $sUrl) {
571        $oUri = JURI::getInstance($sUrl);
572        $sQuery = $oUri->getQuery(true);
573        eval('?>' . $sContent = file_get_contents($sPath) . '<?');
574    }
575
576    /**
577     * Grabs background images with no-repeat attribute from css and merge them in one file called a sprite.
578     * Css is updated with sprite url and correct background positions for affected images.
579     * Sprite saved in {Joomla! base}/images/jch-optimize/
580     *
581     * @param string $sCss       Aggregated css file before sprite generation
582     * @param string $sLnEnd     Document line end
583     * @return string           Css updated with sprite information on success. Original css on failure
584     */
585    protected function generateSprite($sCss, $sLnEnd) {
586        if (extension_loaded('imagick') && extension_loaded('exif')) {
587            $sImageLibrary = 'imagick';
588        } else {
589            if (!extension_loaded('gd') || !extension_loaded('exif')) {
590                return $sCss;
591            }
592            $sImageLibrary = 'gd';
593        }
594        $iMinMaxImages = $this->params->get('csg_min_max_images', 0);
595        $sDelStart = '~';
596        $sRegexStart = '(?:(?<=^|})
597                            (?=([^{]+?)({[^}]+?(url\(([^}]+?\.(?:png|gif|jpe?g))[^}]*?\))[^}]+?}))
598                            (?:(?!(?:\s(?<!no-)repeat(?:-(?:x|y))?)|(?:background[^;]+?(?:\s(?:left|right|center|top|bottom)';
599        $sRegexMin = '|(?:\s0)|(?:\s\d{1,5}(?:%|in|(?:c|m)m|e(?:m|x)|p(?:t|c|x)))){1,2}[^;]*?;)';
600        $sRegexMax = '|(?:\s[1-9]\d{0,4}(?:%|in|(?:c|m)m|e(?:m|x)|p(?:t|c|x)))){1,2}[^;]*?;)';
601        $sRegexEnd = ')[^}])*?})';
602        $sDelEnd = '~sx';
603
604        $aIncludeImages = $this->getArray($this->params->get('csg_include_images'));
605        $aExcludeImages = $this->getArray($this->params->get('csg_exclude_images'));
606        $sIncImagesRegex = '';
607        if (!empty($aIncludeImages[0]) && !$iMinMaxImages) {
608            foreach ($aIncludeImages as $sIncImage) {
609                $sIncImage = str_replace('.', '\.', $sIncImage);
610                $sIncImagesRegex .= '|(?:(?<=^|})([^{]+?){[^}]+?(url\(([^}]+?' . $sIncImage . ')[^}]*?\))[^}]*?})';
611            }
612        }
613        $sExImagesRegex = '';
614        if (!empty($aExcludeImages[0]) && $iMinMaxImages) {
615            foreach ($aExcludeImages as $sExImage) {
616                $sExImage = str_replace('.', '\.', $sExImage);
617                $sExImagesRegex .= '|(?:\b' . $sExImage . ')';
618            }
619        }
620
621
622        $sMinMaxRegex = $iMinMaxImages ? $sRegexMax : $sRegexMin;
623        $sRegex = $sDelStart . $sRegexStart . $sMinMaxRegex . $sExImagesRegex . $sRegexEnd . $sIncImagesRegex . $sDelEnd;
624
625        $iResult = preg_match_all($sRegex, $sCss, $aMatches);
626        //print_r($aMatches);
627        if ($iResult <= 0) {
628            return $sCss;
629        }
630
631        require_once ( dirname(__FILE__) . DS . 'cache' . DS . 'css-sprite-gen.inc.php' );
632
633        $aDeclaration = $aMatches[2];
634        $aImages = $aMatches[4];
635        //print_r($aDeclaration);
636        //print_r($sImages);
637        $aFormValues = array();
638        $aFormValues['wrap-columns'] = $this->params->get('csg_wrap_images', 'off');
639        $aFormValues['build-direction'] = $this->params->get('csg_direction', 'vertical');
640        $aFormValues['image-output'] = $this->params->get('csg_file_output', 'PNG');
641        $oSpriteGen = new CssSpriteGen($sImageLibrary, $aFormValues);
642        $aImageTypes = $oSpriteGen->GetImageTypes();
643
644        $oSpriteGen->CreateSprite($aImages);
645        $aSpriteCss = $oSpriteGen->GetCssBackground();
646        //print_r($aSpriteCss);
647        $aNeedles = array();
648        $aReplacements = array();
649        $sImageSelector = '';
650
651        $sBaseUrl = JURI::getInstance()->base();
652        
653       //  $sBaseUrl = clone JURI::base(true);
654        $sBaseUrl = $sBaseUrl == '/' ? $sBaseUrl : $sBaseUrl . '/';
655        for ($i = 0; $i < count($aSpriteCss); $i++) {
656            if (@$aSpriteCss[$i]) {
657                $aNeedles[] = $aDeclaration[$i];
658                preg_match('~(?<=background)(?:[^;]+?)(
659                            (?<!/)\b(?:aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)\b(?!/)
660                            |\#[a-fA-F\d]{3,6}
661                            |rgb\([^\)]+?\))
662                            (?:[^;]*?);~ix', $aDeclaration[$i], $aBgColor);
663                preg_match('~(?<=background)(?:[^;]+?)\s(
664                                scroll|fixed
665                                )\s(?:(?:[^;]+?)?;)~ix', $aDeclaration[$i], $aBgAttach);
666                $sBgImage = 'url(' . $sBaseUrl . 'images/jch-optimize/' . $oSpriteGen->GetSpriteFilename() . ')';
667                $sBackground = 'background: ' . @$aBgColor[1] . ' ' . $sBgImage . ' ' . @$aBgAttach[1] . ' ' . $aSpriteCss[$i] . ' no-repeat; ';
668                $sDecUnique = preg_replace('~background[^;]+?;~sx', '', $aDeclaration[$i]);
669                $aReplacements[] = str_replace('{', '{' . $sLnEnd . $sBackground, $sDecUnique);
670            }
671        }
672        $sCss = str_replace($aNeedles, $aReplacements, $sCss);
673        //$sCss    =   $sImageSelector.'{background-image:'.$sBgImage.');}'.$sCss;
674        return $sCss;
675    }
676
677}