From d27554f580757b735c6ee7e8438de455aebb6d1f Mon Sep 17 00:00:00 2001 From: Steffen Kamper <info@sk-typo3.de> Date: Sun, 2 May 2010 15:14:10 +0000 Subject: [PATCH] Fixed bug #14277: Improve t3lib_compressor (thanks to Steffen Gebert) git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@7508 709f56b5-9817-0410-a4d7-c38de5d9e867 --- ChangeLog | 1 + misc/advanced.htaccess | 14 ++++++ t3lib/class.t3lib_compressor.php | 76 +++++++++++++++++++++++++---- t3lib/class.t3lib_pagerenderer.php | 78 ++++++++++++++++++++++-------- typo3/backend.php | 3 +- typo3/template.php | 1 + 6 files changed, 142 insertions(+), 31 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0841a1dbd165..42ed81191976 100755 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 2010-05-02 Steffen Kamper <info@sk-typo3.de> + * Fixed bug #14277: Improve t3lib_compressor (thanks to Steffen Gebert) * Fixed bug #14279: Some non-XHTML tags in Backend (thanks to Steffen Gebert) 2010-05-02 Sebastian Kurfuerst <sebastian@typo3.org> diff --git a/misc/advanced.htaccess b/misc/advanced.htaccess index b49fc3f48590..ef23b49af5c6 100644 --- a/misc/advanced.htaccess +++ b/misc/advanced.htaccess @@ -36,6 +36,20 @@ # #### +## +# Compressed .js and .css files +## +# uncomment the following lines if you use compression +# +#<FilesMatch "\.js\.gzip$"> +# AddType "text/javascript" .gzip +#</FilesMatch> +#<FilesMatch "\.css\.gzip$"> +# AddType "text/css" .gzip +#</FilesMatch> +#AddEncoding gzip .gzip + + ### Begin: Rewrite stuff ### # Enable URL rewriting diff --git a/t3lib/class.t3lib_compressor.php b/t3lib/class.t3lib_compressor.php index 74bf065e3aaf..06e26cc99e5e 100644 --- a/t3lib/class.t3lib_compressor.php +++ b/t3lib/class.t3lib_compressor.php @@ -27,7 +27,7 @@ /** * Compressor - * This class can currently merge CSS files of the TYPO3 Backend. + * This merges and compresses CSS and JavaScript files of the TYPO3 Backend. * * @author Steffen Gebert <steffen@steffen-gebert.de> * @package TYPO3 @@ -63,6 +63,17 @@ class t3lib_compressor { $this->gzipCompressionLevel = $compressionLevel; } } + + // decide whether we should create gzipped versions or not + $compressionLevel = $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['compressionLevel']; + // we need zlib for gzencode() + if (extension_loaded('zlib') && $compressionLevel) { + $this->createGzipped = TRUE; + // $compressionLevel can also be TRUE + if (t3lib_div::testInt($compressionLevel)) { + $this->gzipCompressionLevel = $compressionLevel; + } + } } /** @@ -71,7 +82,7 @@ class t3lib_compressor { * Options: * baseDirectories If set, only include files below one of the base directories * - * @param array $cssFiles CSS files added to the PageRenderer + * @param array $cssFiles CSS files to process * @param array $options Additional options * @return array CSS files */ @@ -144,8 +155,8 @@ class t3lib_compressor { /** * Compress multiple css files * - * @param array $cssFiles The files to compress (array key = filename) - * @return array The CSS files after compression (array key = new filename) + * @param array $cssFiles The files to compress (array key = filename), relative to requested page + * @return array The CSS files after compression (array key = new filename), relative to requested page */ public function compressCssFiles(array $cssFiles) { $filesAfterCompression = array(); @@ -154,7 +165,7 @@ class t3lib_compressor { $filenameFromMainDir = substr($filename, strlen($GLOBALS['BACK_PATH'])); // if compression is enabled if ($fileOptions['compress']) { - $filesAfterCompression[$GLOBALS['BACK_PATH'] . $this->compressCssFile($filename)] = $fileOptions; + $filesAfterCompression[$this->compressCssFile($filename)] = $fileOptions; } else { $filesAfterCompression[$filename] = $fileOptions; } @@ -171,8 +182,8 @@ class t3lib_compressor { * removes comments and whitespaces * Adopted from http://drupal.org/files/issues/minify_css.php__1.txt * - * @param string $filename Source filename - * @return string Filename of the compressed file + * @param string $filename Source filename, relative to requested page + * @return string Compressed filename, relative to requested page */ public function compressCssFile($filename) { // generate the unique name of the file @@ -218,7 +229,7 @@ class t3lib_compressor { $this->writeFileAndCompressed($targetFile, $contents); } - return '../' . $this->returnFileReference($targetFile); + return $GLOBALS['BACK_PATH'] . '../' . $this->returnFileReference($targetFile); } /** @@ -255,6 +266,51 @@ class t3lib_compressor { return $matches[0] . "\n/* ERROR! Unexpected _proccess_css_minify() parameter */\n"; // never get here } + /** + * Compress multiple javascript files + * + * @param array $jsFiles The files to compress (array key = filename), relative to requested page + * @return array The js files after compression (array key = new filename), relative to requested page + */ + public function compressJsFiles(array $jsFiles) { + $filesAfterCompression = array(); + foreach ($jsFiles as $filename => $fileOptions) { + // we remove BACK_PATH from $filename, so make it relative to TYPO3_mainDir + $filenameFromMainDir = substr($filename, strlen($GLOBALS['BACK_PATH'])); + // if compression is enabled + if ($fileOptions['compress']) { + $filesAfterCompression[$this->compressJsFile($filename)] = $fileOptions; + } else { + $filesAfterCompression[$filename] = $fileOptions; + } + } + return $filesAfterCompression; + } + + /** + * Compresses a javascript file + * + * Options: + * baseDirectories If set, only include files below one of the base directories + * + * @param string $filename Source filename, relative to requested page + * @return string Filename of the compressed file, relative to requested page + */ + public function compressJsFile($filename) { + // generate the unique name of the file + $filenameAbsolute = t3lib_div::resolveBackPath(PATH_typo3 . substr($filename, strlen($GLOBALS['BACK_PATH']))); + $unique = $filenameAbsolute . filemtime($filenameAbsolute) . filesize($filenameAbsolute); + + $pathinfo = pathinfo($filename); + $targetFile = $this->targetDirectory . $pathinfo['filename'] . '-' . md5($unique) . '.js'; + // only create it, if it doesn't exist, yet + if (!file_exists(PATH_site . $targetFile) || ($this->createGzipped && !file_exists(PATH_site . $targetFile . '.gz'))) { + $contents = t3lib_div::getUrl($filenameAbsolute); + $this->writeFileAndCompressed($targetFile, $contents); + } + return $GLOBALS['BACK_PATH'] . '../' . $this->returnFileReference($targetFile); + } + /** * Decides whether a CSS file comes from one of the baseDirectories * @@ -309,7 +365,7 @@ class t3lib_compressor { if (isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['compressionLevel']) && is_numeric($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['compressionLevel'])) { $compressionLevel = intval($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['compressionLevel']); } - t3lib_div::writeFile(PATH_site . $filename . '.gz', gzencode($contents, $compressionLevel)); + t3lib_div::writeFile(PATH_site . $filename . '.gzip', gzencode($contents, $compressionLevel)); } } @@ -323,7 +379,7 @@ class t3lib_compressor { protected function returnFileReference($filename) { // if the client accepts gzip and we can create gzipped files, we give him compressed versions if ($this->createGzipped && strpos(t3lib_div::getIndpEnv('HTTP_ACCEPT_ENCODING'), 'gzip') !== FALSE) { - return $filename . '.gz'; + return $filename . '.gzip'; } else { return $filename; } diff --git a/t3lib/class.t3lib_pagerenderer.php b/t3lib/class.t3lib_pagerenderer.php index b6ce14820e47..97814303e8f8 100644 --- a/t3lib/class.t3lib_pagerenderer.php +++ b/t3lib/class.t3lib_pagerenderer.php @@ -47,6 +47,9 @@ class t3lib_PageRenderer implements t3lib_Singleton { protected $csConvObj; protected $lang; + /* @var t3lib_compressor Instance of t3lib_compressor */ + protected $compressor; + // static array containing associative array for the included files protected static $jsFiles = array (); protected static $jsFooterFiles = array (); @@ -1200,8 +1203,8 @@ class t3lib_PageRenderer implements t3lib_Singleton { $out = ''; if ($this->addPrototype) { - $out .= '<script src="' . t3lib_div::createVersionNumberedFilename($this->backPath . - 'contrib/prototype/prototype.js') . '" type="text/javascript"></script>' . LF; + $out .= '<script src="' . $this->processJsFile($this->backPath . 'contrib/prototype/prototype.js') . + '" type="text/javascript"></script>' . LF; unset($this->jsFiles[$this->backPath . 'contrib/prototype/prototype.js']); } @@ -1218,17 +1221,20 @@ class t3lib_PageRenderer implements t3lib_Singleton { } if (count($mods)) { - $moduleLoadString = '?load=' . implode(',', $mods); + foreach ($mods as $module) { + $out .= '<script src="' . $this->processJsFile($this->backPath . + 'contrib/scriptaculous/' . $module . '.js') . '" type="text/javascript"></script>' . LF; + unset($this->jsFiles[$this->backPath . 'contrib/scriptaculous/' . $module . '.js']); } - $out .= '<script src="' . t3lib_div::createVersionNumberedFilename($this->backPath . - 'contrib/scriptaculous/scriptaculous.js' . $moduleLoadString, TRUE) . - '" type="text/javascript"></script>' . LF; - unset($this->jsFiles[$this->backPath . 'contrib/scriptaculous/scriptaculous.js' . $moduleLoadString]); + } + $out .= '<script src="' . $this->processJsFile($this->backPath . + 'contrib/scriptaculous/scriptaculous.js') . '" type="text/javascript"></script>' . LF; + unset($this->jsFiles[$this->backPath . 'contrib/scriptaculous/scriptaculous.js']); } // include extCore if ($this->addExtCore) { - $out .= '<script src="' . t3lib_div::createVersionNumberedFilename($this->backPath . + $out .= '<script src="' . $this->processJsFile($this->backPath . 'contrib/extjs/ext-core' . ($this->enableExtCoreDebug ? '-debug' : '') . '.js') . '" type="text/javascript"></script>' . LF; unset($this->jsFiles[$this->backPath . 'contrib/extjs/ext-core' . ($this->enableExtCoreDebug ? '-debug' : '') . '.js']); @@ -1237,11 +1243,11 @@ class t3lib_PageRenderer implements t3lib_Singleton { // include extJS if ($this->addExtJS) { // use the base adapter all the time - $out .= '<script src="' . t3lib_div::createVersionNumberedFilename($this->backPath . + $out .= '<script src="' . $this->processJsFile($this->backPath . 'contrib/extjs/adapter/' . ($this->enableExtJsDebug ? str_replace('.js', '-debug.js', $this->extJSadapter) : $this->extJSadapter)) . '" type="text/javascript"></script>' . LF; - $out .= '<script src="' . t3lib_div::createVersionNumberedFilename($this->backPath . + $out .= '<script src="' . $this->processJsFile($this->backPath . 'contrib/extjs/ext-all' . ($this->enableExtJsDebug ? '-debug' : '') . '.js') . '" type="text/javascript"></script>' . LF; @@ -1258,7 +1264,7 @@ class t3lib_PageRenderer implements t3lib_Singleton { // TODO autoconvert file from UTF8 to current BE charset if necessary!!!! $extJsLocaleFile = 'contrib/extjs/locale/ext-lang-' . $extJsLang . '.js'; if (file_exists(PATH_typo3 . $extJsLocaleFile)) { - $out .= '<script src="' . t3lib_div::createVersionNumberedFilename($this->backPath . + $out .= '<script src="' . $this->processJsFile($this->backPath . $extJsLocaleFile) . '" type="text/javascript" charset="utf-8"></script>' . LF; } @@ -1302,16 +1308,16 @@ class t3lib_PageRenderer implements t3lib_Singleton { if ($this->extJStheme) { if (isset($GLOBALS['TBE_STYLES']['extJS']['theme'])) { - $this->addCssFile($this->backPath . $GLOBALS['TBE_STYLES']['extJS']['theme'], 'stylesheet', 'screen', '', FALSE, TRUE); + $this->addCssFile($this->backPath . $GLOBALS['TBE_STYLES']['extJS']['theme'], 'stylesheet', 'all', '', TRUE, TRUE); } else { - $this->addCssFile($this->backPath . 'contrib/extjs/resources/css/xtheme-blue.css', 'stylesheet', 'screen', '', FALSE, TRUE); + $this->addCssFile($this->backPath . 'contrib/extjs/resources/css/xtheme-blue.css', 'stylesheet', 'all', '', TRUE, TRUE); } } if ($this->extJScss) { if (isset($GLOBALS['TBE_STYLES']['extJS']['all'])) { - $this->addCssFile($this->backPath . $GLOBALS['TBE_STYLES']['extJS']['all'], 'stylesheet', 'screen', '', FALSE, TRUE); + $this->addCssFile($this->backPath . $GLOBALS['TBE_STYLES']['extJS']['all'], 'stylesheet', 'all', '', TRUE, TRUE); } else { - $this->addCssFile($this->backPath . 'contrib/extjs/resources/css/ext-all-notheme.css', 'stylesheet', 'screen', '', FALSE, TRUE); + $this->addCssFile($this->backPath . 'contrib/extjs/resources/css/ext-all-notheme.css', 'stylesheet', 'all', '', TRUE, TRUE); } } } else { @@ -1333,7 +1339,6 @@ class t3lib_PageRenderer implements t3lib_Singleton { /** * concatenate files into one file * registered handler - * TODO: implement own method * * @return void */ @@ -1355,9 +1360,8 @@ class t3lib_PageRenderer implements t3lib_Singleton { // use extern concatenate routine t3lib_div::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['concatenateHandler'], $params, $this); } elseif (TYPO3_MODE === 'BE') { - $compressor = t3lib_div::makeInstance('t3lib_compressor'); $cssOptions = array('baseDirectories' => $GLOBALS['TBE_TEMPLATE']->getSkinStylesheetDirectories()); - $this->cssFiles = $compressor->concatenateCssFiles($this->cssFiles, $cssOptions); + $this->cssFiles = $this->getCompressor()->concatenateCssFiles($this->cssFiles, $cssOptions); } } } @@ -1396,8 +1400,12 @@ class t3lib_PageRenderer implements t3lib_Singleton { } } } + if (TYPO3_MODE === 'BE') { + $this->jsFiles = $this->getCompressor()->compressJsFiles($this->jsFiles); + $this->jsFooterFiles = $this->getCompressor()->compressJsFiles($this->jsFooterFiles); } } + } if ($this->compressCss) { // use extern compress routine $params = array ( @@ -1411,12 +1419,42 @@ class t3lib_PageRenderer implements t3lib_Singleton { // use extern concatenate routine t3lib_div::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssCompressHandler'], $params, $this); } elseif (TYPO3_MODE === 'BE') { - $compressor = t3lib_div::makeInstance('t3lib_compressor'); - $this->cssFiles = $compressor->compressCssFiles($this->cssFiles); + $this->cssFiles = $this->getCompressor()->compressCssFiles($this->cssFiles); } } } + /** + * Returns instance of t3lib_compressor + * + * @return t3lib_compressor Instance of t3lib_compressor + */ + protected function getCompressor() { + if ($this->compressor === NULL) { + $this->compressor = t3lib_div::makeInstance('t3lib_compressor'); +} + return $this->compressor; + } + + /** + * Processes a Javascript file dependent on the current context + * + * Adds the version number for Frontend, compresses the file for Backend + * + * @param string $filename Filename + * @return string new filename + */ + protected function processJsFile($filename) { + switch (TYPO3_MODE) { + case 'FE': + $filename = t3lib_div::createVersionNumberedFilename($filename); + break; + case 'BE': + $filename = $this->getCompressor()->compressJsFile($filename); + break; + } + return $filename; + } } if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_pagerenderer.php']) { diff --git a/typo3/backend.php b/typo3/backend.php index 2676acb121f2..376241c4bb68 100644 --- a/typo3/backend.php +++ b/typo3/backend.php @@ -127,7 +127,6 @@ class TYPO3backend { 'js/iecompatibility.js', 'js/flashupload.js', '../t3lib/jsfunc.evalfield.js', - 'ajax.php?ajaxID=ExtDirect::getAPI&namespace=TYPO3.Backend', '../t3lib/js/extjs/ux/flashmessages.js', 'js/backend.js', 'js/loginrefresh.js', @@ -238,6 +237,8 @@ class TYPO3backend { foreach ($this->jsFiles as $jsFile) { $this->pageRenderer->addJsFile($jsFile); } + // we mustn't compress this file + $this->pageRenderer->addJsFile('ajax.php?ajaxID=ExtDirect::getAPI&namespace=TYPO3.Backend', NULL, FALSE); $this->generateJavascript(); $this->pageRenderer->addJsInlineCode('BackendInlineJavascript', $this->js); diff --git a/typo3/template.php b/typo3/template.php index 9aa78af556be..c431e5e67815 100644 --- a/typo3/template.php +++ b/typo3/template.php @@ -306,6 +306,7 @@ class template { $this->pageRenderer->setLanguage($GLOBALS['LANG']->lang); $this->pageRenderer->enableConcatenateFiles(); $this->pageRenderer->enableCompressCss(); + $this->pageRenderer->enableCompressJavascript(); } return $this->pageRenderer; } -- GitLab