From 6624e27e575d6056da7881bc05e9972c50ec1128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Moumn=C3=A9?= Date: Tue, 17 Dec 2013 17:52:36 +0100 Subject: fixes #4373, #1640 --- core/AssetManager.php | 710 ++++++++++++++------------------------------------ 1 file changed, 199 insertions(+), 511 deletions(-) (limited to 'core/AssetManager.php') diff --git a/core/AssetManager.php b/core/AssetManager.php index ec850de0db..1104ed42d8 100644 --- a/core/AssetManager.php +++ b/core/AssetManager.php @@ -11,14 +11,19 @@ namespace Piwik; use Exception; -use JSMin; -use lessc; +use Piwik\AssetManager\UIAsset; +use Piwik\AssetManager\UIAsset\InMemoryUIAsset; +use Piwik\AssetManager\UIAsset\OnDiskUIAsset; +use Piwik\AssetManager\UIAssetCacheBuster; +use Piwik\AssetManager\UIAssetFetcher\JScriptUIAssetFetcher; +use Piwik\AssetManager\UIAssetFetcher\StaticUIAssetFetcher; +use Piwik\AssetManager\UIAssetFetcher\StylesheetUIAssetFetcher; +use Piwik\AssetManager\UIAssetFetcher; +use Piwik\AssetManager\UIAssetMerger\JScriptUIAssetMerger; +use Piwik\AssetManager\UIAssetMerger\StylesheetUIAssetMerger; +use Piwik\Plugin\Manager; use Piwik\Translate; - -/** - * @see libs/jsmin/jsmin.php - */ -require_once PIWIK_INCLUDE_PATH . '/libs/jsmin/jsmin.php'; +use Piwik\Config as PiwikConfig; /** * AssetManager is the class used to manage the inclusion of UI assets: @@ -36,678 +41,361 @@ require_once PIWIK_INCLUDE_PATH . '/libs/jsmin/jsmin.php'; * When set to 0, files will be included within a pair of files: 1 JavaScript * and 1 css file. * + * @method static \Piwik\AssetManager getInstance() * @package Piwik */ -class AssetManager +class AssetManager extends Singleton { const MERGED_CSS_FILE = "asset_manager_global_css.css"; - const MERGED_JS_FILE = "asset_manager_global_js.js"; - const STYLESHEET_IMPORT_EVENT = "AssetManager.getStylesheetFiles"; - const JAVASCRIPT_IMPORT_EVENT = "AssetManager.getJavaScriptFiles"; - const MERGED_FILE_DIR = "tmp/assets/"; - const COMPRESSED_FILE_LOCATION = "/tmp/assets/"; + const MERGED_CORE_JS_FILE = "asset_manager_core_js.js"; + const MERGED_NON_CORE_JS_FILE = "asset_manager_non_core_js.js"; const CSS_IMPORT_DIRECTIVE = "\n"; const JS_IMPORT_DIRECTIVE = "\n"; const GET_CSS_MODULE_ACTION = "index.php?module=Proxy&action=getCss"; - const GET_JS_MODULE_ACTION = "index.php?module=Proxy&action=getJs"; - const MINIFIED_JS_RATIO = 100; + const GET_CORE_JS_MODULE_ACTION = "index.php?module=Proxy&action=getCoreJs"; + const GET_NON_CORE_JS_MODULE_ACTION = "index.php?module=Proxy&action=getNonCoreJs"; /** - * @param $file - * @param $less - * @internal param $mergedContent - * @return string + * @var UIAssetCacheBuster */ - protected static function getCssContentFromFile($file, $less) - { - self::validateCssFile($file); - - $fileLocation = self::getAbsoluteLocation($file); - $less->addImportDir(dirname($fileLocation)); - - $content = file_get_contents($fileLocation); - $content = self::rewriteCssPathsDirectives($file, $content); - - return $content; - } + private $cacheBuster; /** - * Returns CSS file inclusion directive(s) using the markup - * - * @return string + * @var UIAssetFetcher */ - public static function getCssAssets() - { - return sprintf(self::CSS_IMPORT_DIRECTIVE, self::GET_CSS_MODULE_ACTION); - } + private $minimalStylesheetFetcher; /** - * Returns JS file inclusion directive(s) using the markup "; + private $theme; - if (self::isMergedAssetsDisabled()) { - // Individual includes mode - self::removeMergedAsset(self::MERGED_JS_FILE); - $result .= self::getIndividualJsIncludes(); - } else { - $result .= sprintf(self::JS_IMPORT_DIRECTIVE, self::GET_JS_MODULE_ACTION); - } + function __construct() + { + $this->cacheBuster = UIAssetCacheBuster::getInstance(); + $this->minimalStylesheetFetcher = new StaticUIAssetFetcher(array('plugins/Zeitgeist/stylesheets/base.less'), array(), $this->theme); - return $result; + if(Manager::getInstance()->getThemeEnabled() != null) + $this->theme = new Theme(); } /** - * Assets are cached in the browser and Piwik server returns 304 after initial download. - * when the Cache buster string changes, the assets will be re-generated - * - * @return string + * @param UIAssetCacheBuster $cacheBuster */ - public static function generateAssetsCacheBuster() + public function setCacheBuster($cacheBuster) { - $currentGitHash = @file_get_contents(PIWIK_INCLUDE_PATH . '/.git/refs/heads/master'); - $pluginList = md5(implode(",", \Piwik\Plugin\Manager::getInstance()->getLoadedPluginsName())); - $cacheBuster = md5(SettingsPiwik::getSalt() . $pluginList . PHP_VERSION . Version::VERSION . trim($currentGitHash)); - return $cacheBuster; + $this->cacheBuster = $cacheBuster; } /** - * Generate the merged css file. - * - * @throws Exception if a file can not be opened in write mode + * @param UIAssetFetcher $minimalStylesheetFetcher */ - private static function prepareMergedCssFile() + public function setMinimalStylesheetFetcher($minimalStylesheetFetcher) { - $mergedCssAlreadyGenerated = self::isGenerated(self::MERGED_CSS_FILE); - $isDevelopingPiwik = self::isMergedAssetsDisabled(); - - if ($mergedCssAlreadyGenerated && !$isDevelopingPiwik) { - return; - } - - $files = self::getStylesheetFiles(); - $less = self::makeLess(); - - // Loop through each css file - $mergedContent = ""; - foreach ($files as $file) { - $mergedContent .= self::getCssContentFromFile($file, $less, $mergedContent); - } - - $fileHash = md5($mergedContent); - $firstLineCompileHash = "/* compile_me_once=$fileHash */"; - - // Disable Merged Assets ==> Check on each request if file needs re-compiling - if ($mergedCssAlreadyGenerated - && !$isDevelopingPiwik - ) { - $mergedFile = self::MERGED_CSS_FILE; - $cacheIsValid = self::isFirstLineMatching($mergedFile, $firstLineCompileHash); - if($cacheIsValid) { - return; - } - // Some CSS file in the merge, has changed since last merged asset was generated - // Note: we do not detect changes in @import'ed LESS files - } - - $mergedContent = $less->compile($mergedContent); - - /** - * Triggered after all less stylesheets are compiled to CSS, minified and merged into - * one file, but before the generated CSS is written to disk. - * - * This event can be used to modify merged CSS. - * - * @param string &$mergedContent The merged and minified CSS. - */ - Piwik::postEvent('AssetManager.filterMergedStylesheets', array(&$mergedContent)); - - $theme = new Theme; - $mergedContent = $theme->rewriteAssetsPathToTheme($mergedContent); - - $mergedContent = - $firstLineCompileHash . "\n" - . "/* Piwik CSS file is compiled with Less. You may be interested in writing a custom Theme for Piwik! */\n" - . $mergedContent; - - self::writeAssetToFile($mergedContent, self::MERGED_CSS_FILE); + $this->minimalStylesheetFetcher = $minimalStylesheetFetcher; } - protected static function makeLess() + /** + * @param Theme $theme + */ + public function setTheme($theme) { - if (!class_exists("lessc")) { - throw new Exception("Less was added to composer during 2.0. ==> Execute this command to update composer packages: \$ php composer.phar install"); - } - $less = new lessc; - return $less; + $this->theme = $theme; } /** - * Returns the base.less compiled to css + * Return CSS file inclusion directive(s) using the markup * * @return string */ - public static function getCompiledBaseCss() + public function getCssInclusionDirective() { - $file = '/plugins/Zeitgeist/stylesheets/base.less'; - $less = self::makeLess(); - $lessContent = self::getCssContentFromFile($file, $less); - $css = $less->compile($lessContent); - return $css; + return sprintf(self::CSS_IMPORT_DIRECTIVE, self::GET_CSS_MODULE_ACTION); } - /* - * Rewrite css url directives - * - rewrites relative paths - * - rewrite windows directory separator \\ to / + /** + * Return JS file inclusion directive(s) using the markup "; - $baseDirectory = dirname($relativePath); - $content = preg_replace_callback( - "/(url\(['\"]?)([^'\")]*)/", - function ($matches) use ($rootDirectoryLength, $baseDirectory) { - $absolutePath = substr(realpath(PIWIK_DOCUMENT_ROOT . "/$baseDirectory/" . $matches[2]), $rootDirectoryLength); - $rewritten = str_replace('\\', '/', $absolutePath); + if ($this->isMergedAssetsDisabled()) { - if (is_file($rewritten)) { // only rewrite the URL if transforming it points to a valid file - return $matches[1] . $rewritten; - } else { - return $matches[1] . $matches[2]; - } - }, - $content - ); - return $content; - } + $this->getMergedCoreJSAsset()->delete(); + $this->getMergedNonCoreJSAsset()->delete(); - protected static function countDirectoriesInPathToRoot() - { - $rootDirectory = realpath(PIWIK_DOCUMENT_ROOT); - if ($rootDirectory != '/' && substr_compare($rootDirectory, '/', -1)) { - $rootDirectory .= '/'; - } - $rootDirectoryLen = strlen($rootDirectory); - return $rootDirectoryLen; - } + $result .= $this->getIndividualJsIncludes(); - private static function writeAssetToFile($mergedContent, $name) - { - // Remove the previous file - self::removeMergedAsset($name); - - // Tries to open the new file - $newFilePath = self::getAbsoluteMergedFileLocation($name); - $newFile = @fopen($newFilePath, "w"); + } else { - if (!$newFile) { - throw new Exception ("The file : " . $newFile . " can not be opened in write mode."); + $result .= sprintf(self::JS_IMPORT_DIRECTIVE, self::GET_CORE_JS_MODULE_ACTION); + $result .= sprintf(self::JS_IMPORT_DIRECTIVE, self::GET_NON_CORE_JS_MODULE_ACTION); } - // Write the content in the new file - fwrite($newFile, $mergedContent); - fclose($newFile); + return $result; } /** - * Returns individual CSS file inclusion directive(s) using the markup + * Return the base.less compiled to css * - * @return string + * @return UIAsset */ - private static function getIndividualCssIncludes() + public function getCompiledBaseCss() { - $cssIncludeString = ''; - - $stylesheets = self::getStylesheetFiles(); + $mergedAsset = new InMemoryUIAsset(); - foreach ($stylesheets as $cssFile) { + $assetMerger = new StylesheetUIAssetMerger($mergedAsset, $this->minimalStylesheetFetcher, $this->cacheBuster); - self::validateCssFile($cssFile); - $cssIncludeString = $cssIncludeString . sprintf(self::CSS_IMPORT_DIRECTIVE, $cssFile); - } + $assetMerger->generateFile(); - return $cssIncludeString; + return $mergedAsset; } /** - * Returns required CSS files + * Return the css merged file absolute location. + * If there is none, the generation process will be triggered. * - * @return Array + * @return UIAsset */ - private static function getStylesheetFiles() + public function getMergedStylesheet() { - $stylesheets = array(); - - /** - * Triggered when gathering the list of all stylesheets (CSS and LESS) needed by - * Piwik and its plugins. - * - * Plugins that have stylesheets should use this event to make those stylesheets - * load. - * - * Stylesheets should be placed within a **stylesheets** subdirectory in your plugin's - * root directory. - * - * _Note: While you are developing your plugin you should enable the config setting - * `[Debug] disable_merged_assets` so your stylesheets will be reloaded immediately - * after a change._ - * - * **Example** - * - * public function getStylesheetFiles(&$stylesheets) - * { - * $stylesheets[] = "plugins/MyPlugin/stylesheets/myfile.less"; - * $stylesheets[] = "plugins/MyPlugin/stylesheets/myotherfile.css"; - * } - * - * @param string[] &$stylesheets The list of stylesheet paths. - */ - Piwik::postEvent(self::STYLESHEET_IMPORT_EVENT, array(&$stylesheets)); - - $stylesheets = self::sortCssFiles($stylesheets); - - $theme = new Theme; - $themeStylesheet = $theme->getStylesheet(); - if($themeStylesheet) { - $stylesheets[] = $themeStylesheet; - } + $mergedAsset = $this->getMergedStylesheetAsset(); - return $stylesheets; - } + $assetFetcher = new StylesheetUIAssetFetcher(Manager::getInstance()->getLoadedPluginsName(), $this->theme); - /** - * Ensure CSS stylesheets are loaded in a particular order regardless of the order that plugins are loaded. - * - * @param array $stylesheets Array of CSS stylesheet files - * @return array - */ - private static function sortCssFiles($stylesheets) - { - $priorityCssOrdered = array( - 'libs/', - 'plugins/CoreHome/stylesheets/color_manager.css', // must be before other Piwik stylesheets - 'plugins/Zeitgeist/stylesheets/base.less', - 'plugins/Zeitgeist/stylesheets/', - 'plugins/', - 'plugins/Dashboard/stylesheets/dashboard.less', - 'tests/', - ); - - return self::prioritySort($priorityCssOrdered, $stylesheets); + $assetMerger = new StylesheetUIAssetMerger($mergedAsset, $assetFetcher, $this->cacheBuster); + + $assetMerger->generateFile(); + + return $mergedAsset; } /** - * Check the validity of the css file + * Return the core js merged file absolute location. + * If there is none, the generation process will be triggered. * - * @param string $cssFile CSS file name - * @return boolean - * @throws Exception if a file can not be opened in write mode + * @return UIAsset */ - private static function validateCssFile($cssFile) + public function getMergedCoreJavaScript() { - if (!self::assetIsReadable($cssFile)) { - throw new Exception("The css asset with 'href' = " . $cssFile . " is not readable"); - } + return $this->getMergedJavascript($this->getCoreJScriptFetcher(), $this->getMergedCoreJSAsset()); } /** - * Generate the merged js file. + * Return the non core js merged file absolute location. + * If there is none, the generation process will be triggered. * - * @throws Exception if a file can not be opened in write mode + * @return UIAsset */ - private static function generateMergedJsFile() + public function getMergedNonCoreJavaScript() { - $mergedContent = self::getFirstLineOfMergedJs(); - - $files = self::getJsFiles(); - foreach ($files as $file) { - self::validateJsFile($file); - $fileLocation = self::getAbsoluteLocation($file); - $content = file_get_contents($fileLocation); - if (!self::isMinifiedJs($content)) { - $content = JSMin::minify($content); - } - $mergedContent = $mergedContent . PHP_EOL . $content; - } - $mergedContent = str_replace("\n", "\r\n", $mergedContent); - - /** - * Triggered after all the JavaScript files Piwik uses are minified and merged into a - * single file, but before the merged JavaScript is written to disk. - * - * Plugins can use this event to modify merged JavaScript or do something else - * with it. - * - * @param string &$mergedContent The minified and merged JavaScript. - */ - Piwik::postEvent('AssetManager.filterMergedJavaScripts', array(&$mergedContent)); - - $theme = new Theme; - $mergedContent = $theme->rewriteAssetsPathToTheme($mergedContent); - - self::writeAssetToFile($mergedContent, self::MERGED_JS_FILE); + return $this->getMergedJavascript($this->getNonCoreJScriptFetcher(), $this->getMergedNonCoreJSAsset()); } /** - * Returns individual JS file inclusion directive(s) using the markup