site-accueil-insa/matomo/core/AssetManager.php

522 lines
16 KiB
PHP
Raw Permalink Normal View History

2022-10-26 08:59:36 +02:00
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik;
use Exception;
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\PluginUmdAssetFetcher;
use Piwik\AssetManager\UIAssetFetcher;
use Piwik\AssetManager\UIAssetMerger\JScriptUIAssetMerger;
use Piwik\AssetManager\UIAssetMerger\StylesheetUIAssetMerger;
use Piwik\Container\StaticContainer;
use Piwik\Plugin\Manager;
/**
* AssetManager is the class used to manage the inclusion of UI assets:
* JavaScript and CSS files.
*
* It performs the following actions:
* - Identifies required assets
* - Includes assets in the rendered HTML page
* - Manages asset merging and minifying
* - Manages server-side cache
*
* Whether assets are included individually or as merged files is defined by
* the global option 'disable_merged_assets'. See the documentation in the global
* config for more information.
*/
class AssetManager extends Singleton
{
const MERGED_CSS_FILE = "asset_manager_global_css.css";
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 = "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\" />\n";
const JS_IMPORT_DIRECTIVE = "<script type=\"text/javascript\" src=\"%s\"></script>\n";
const JS_DEFER_IMPORT_DIRECTIVE = "<script type=\"text/javascript\" src=\"%s\" defer></script>\n";
const GET_CSS_MODULE_ACTION = "index.php?module=Proxy&action=getCss";
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";
const GET_JS_UMD_MODULE_ACTION = "index.php?module=Proxy&action=getUmdJs&chunk=";
/**
* @var UIAssetCacheBuster
*/
private $cacheBuster;
/**
* @var UIAssetFetcher
*/
private $minimalStylesheetFetcher;
/**
* @var Theme
*/
private $theme;
public function __construct()
{
$this->cacheBuster = UIAssetCacheBuster::getInstance();
$this->minimalStylesheetFetcher = new StaticUIAssetFetcher(array(), array(), $this->theme);
$theme = Manager::getInstance()->getThemeEnabled();
if (!empty($theme)) {
$this->theme = new Theme();
}
}
/**
* @inheritDoc
* @return AssetManager
*/
public static function getInstance()
{
$assetManager = parent::getInstance();
/**
* Triggered when creating an instance of the asset manager. Lets you overwrite the
* asset manager behavior.
*
* @param AssetManager &$assetManager
*
* @ignore
* This event is not a public event since we don't want to make the asset manager itself public
* API
*/
Piwik::postEvent('AssetManager.makeNewAssetManagerObject', array(&$assetManager));
return $assetManager;
}
/**
* @param UIAssetCacheBuster $cacheBuster
*/
public function setCacheBuster($cacheBuster)
{
$this->cacheBuster = $cacheBuster;
}
/**
* @param UIAssetFetcher $minimalStylesheetFetcher
*/
public function setMinimalStylesheetFetcher($minimalStylesheetFetcher)
{
$this->minimalStylesheetFetcher = $minimalStylesheetFetcher;
}
/**
* @param Theme $theme
*/
public function setTheme($theme)
{
$this->theme = $theme;
}
/**
* Return CSS file inclusion directive(s) using the markup <link>
*
* @return string
*/
public function getCssInclusionDirective()
{
return sprintf(self::CSS_IMPORT_DIRECTIVE, self::GET_CSS_MODULE_ACTION);
}
/**
* Return JS file inclusion directive(s) using the markup <script>
*
* @return string
*/
public function getJsInclusionDirective()
{
$result = "<script type=\"text/javascript\">\n" . StaticContainer::get('Piwik\Translation\Translator')->getJavascriptTranslations() . "\n</script>";
if ($this->isMergedAssetsDisabled()) {
$this->getMergedCoreJSAsset()->delete();
$this->getMergedNonCoreJSAsset()->delete();
$result .= $this->getIndividualCoreAndNonCoreJsIncludes();
} else {
$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);
$result .= $this->getPluginUmdChunks();
}
return $result;
}
protected function getPluginUmdChunks()
{
$fetcher = $this->getPluginUmdJScriptFetcher();
$chunks = $fetcher->getChunkFiles();
$result = '';
foreach ($chunks as $chunk) {
$src = self::GET_JS_UMD_MODULE_ACTION . urlencode($chunk->getChunkName());
$result .= sprintf(self::JS_DEFER_IMPORT_DIRECTIVE, $src);
}
return $result;
}
/**
* Return the base.less compiled to css
*
* @return UIAsset
*/
public function getCompiledBaseCss()
{
$mergedAsset = new InMemoryUIAsset();
$assetMerger = new StylesheetUIAssetMerger($mergedAsset, $this->minimalStylesheetFetcher, $this->cacheBuster);
$assetMerger->generateFile();
return $mergedAsset;
}
/**
* Return the css merged file absolute location.
* If there is none, the generation process will be triggered.
*
* @return UIAsset
*/
public function getMergedStylesheet()
{
$mergedAsset = $this->getMergedStylesheetAsset();
$assetFetcher = new StylesheetUIAssetFetcher(Manager::getInstance()->getLoadedPluginsName(), $this->theme);
$assetMerger = new StylesheetUIAssetMerger($mergedAsset, $assetFetcher, $this->cacheBuster);
$assetMerger->generateFile();
return $mergedAsset;
}
/**
* Return the core js merged file absolute location.
* If there is none, the generation process will be triggered.
*
* @return UIAsset
*/
public function getMergedCoreJavaScript()
{
return $this->getMergedJavascript($this->getCoreJScriptFetcher(), $this->getMergedCoreJSAsset());
}
/**
* Return the non core js merged file absolute location.
* If there is none, the generation process will be triggered.
*
* @return UIAsset
*/
public function getMergedNonCoreJavaScript()
{
return $this->getMergedJavascript($this->getNonCoreJScriptFetcher(), $this->getMergedNonCoreJSAsset());
}
/**
* Return a chunk JS merged file absolute location.
* If there is none, the generation process will be triggered.
*
* @param string $chunk The name of the chunk. Will either be a plugin name or an integer.
* @return UIAsset
*/
public function getMergedJavaScriptChunk($chunk)
{
$assetFetcher = $this->getPluginUmdJScriptFetcher($chunk);
$outputFile = $assetFetcher->getRequestedChunkOutputFile();
return $this->getMergedJavascript($assetFetcher, $this->getMergedUIAsset($outputFile));
}
/**
* @param boolean|"all" $core
* @return string[]
*/
public function getLoadedPlugins($core)
{
$loadedPlugins = array();
foreach (Manager::getInstance()->getPluginsLoadedAndActivated() as $plugin) {
$pluginName = $plugin->getPluginName();
$pluginIsCore = Manager::getInstance()->isPluginBundledWithCore($pluginName);
if ($core === 'all' || ($pluginIsCore && $core) || (!$pluginIsCore && !$core)) {
$loadedPlugins[] = $pluginName;
}
}
return $loadedPlugins;
}
/**
* Remove previous merged assets
*/
public function removeMergedAssets($pluginName = false)
{
$assetsToRemove = array($this->getMergedStylesheetAsset());
if ($pluginName) {
if ($this->pluginContainsJScriptAssets($pluginName)) {
if (Manager::getInstance()->isPluginBundledWithCore($pluginName)) {
$assetsToRemove[] = $this->getMergedCoreJSAsset();
} else {
$assetsToRemove[] = $this->getMergedNonCoreJSAsset();
}
$assetFetcher = $this->getPluginUmdJScriptFetcher();
foreach ($assetFetcher->getChunkFiles() as $chunk) {
$files = $chunk->getFiles();
$foundInChunk = false;
foreach ($files as $file) {
if (strpos($file, "/$pluginName.umd.") !== false) {
$foundInChunk = true;
}
}
if ($foundInChunk) {
$outputFile = $chunk->getOutputFile();
$asset = $this->getMergedUIAsset($outputFile);
if ($asset->exists()) {
$assetsToRemove[] = $asset;
}
break;
}
}
}
} else {
$assetsToRemove[] = $this->getMergedCoreJSAsset();
$assetsToRemove[] = $this->getMergedNonCoreJSAsset();
$assetFetcher = $this->getPluginUmdJScriptFetcher();
foreach ($assetFetcher->getChunkFiles() as $chunk) {
$outputFile = $chunk->getOutputFile();
$asset = $this->getMergedUIAsset($outputFile);
if ($asset->exists()) {
$assetsToRemove[] = $asset;
}
}
}
$this->removeAssets($assetsToRemove);
}
/**
* Check if the merged file directory exists and is writable.
*
* @return string The directory location
* @throws Exception if directory is not writable.
*/
public function getAssetDirectory()
{
$mergedFileDirectory = StaticContainer::get('path.tmp') . '/assets';
if (!is_dir($mergedFileDirectory)) {
Filesystem::mkdir($mergedFileDirectory);
}
if (!is_writable($mergedFileDirectory)) {
throw new Exception("Directory " . $mergedFileDirectory . " has to be writable.");
}
return $mergedFileDirectory;
}
/**
* Return the global option disable_merged_assets
*
* @return boolean
*/
public function isMergedAssetsDisabled()
{
if (Config::getInstance()->Development['disable_merged_assets'] == 1) {
return true;
}
if (isset($_GET['disable_merged_assets']) && $_GET['disable_merged_assets'] == 1) {
return true;
}
return false;
}
/**
* @param UIAssetFetcher $assetFetcher
* @param UIAsset $mergedAsset
* @return UIAsset
*/
private function getMergedJavascript($assetFetcher, $mergedAsset)
{
$assetMerger = new JScriptUIAssetMerger($mergedAsset, $assetFetcher, $this->cacheBuster);
$assetMerger->generateFile();
return $mergedAsset;
}
/**
* Return individual JS file inclusion directive(s) using the markup <script>
*
* @return string
*/
protected function getIndividualCoreAndNonCoreJsIncludes()
{
return
$this->getIndividualJsIncludesFromAssetFetcher($this->getCoreJScriptFetcher()) .
$this->getIndividualJsIncludesFromAssetFetcher($this->getNonCoreJScriptFetcher()) .
$this->getIndividualJsIncludesFromAssetFetcher($this->getPluginUmdJScriptFetcher());
}
/**
* @param UIAssetFetcher $assetFetcher
* @return string
*/
protected function getIndividualJsIncludesFromAssetFetcher($assetFetcher)
{
$jsIncludeString = '';
$assets = $assetFetcher->getCatalog()->getAssets();
foreach ($assets as $jsFile) {
$jsFile->validateFile();
$jsIncludeString = $jsIncludeString . sprintf(self::JS_IMPORT_DIRECTIVE, $jsFile->getRelativeLocation());
}
return $jsIncludeString;
}
private function getCoreJScriptFetcher()
{
return new JScriptUIAssetFetcher($this->getLoadedPlugins(true), $this->theme);
}
protected function getNonCoreJScriptFetcher()
{
return new JScriptUIAssetFetcher($this->getLoadedPlugins(false), $this->theme);
}
protected function getPluginUmdJScriptFetcher($chunk = null)
{
return new PluginUmdAssetFetcher($this->getLoadedPlugins('all'), $this->theme, $chunk);
}
/**
* @param string $pluginName
* @return boolean
*/
private function pluginContainsJScriptAssets($pluginName)
{
$fetcher = new JScriptUIAssetFetcher(array($pluginName), $this->theme);
try {
$assets = $fetcher->getCatalog()->getAssets();
} catch (\Exception $e) {
// This can happen when a plugin is not valid (eg. Piwik 1.x format)
// When posting the event to the plugin, it returns an exception "Plugin has not been loaded"
return false;
}
$pluginManager = Manager::getInstance();
$plugin = null;
if ($pluginManager->isPluginLoaded($pluginName)) {
$plugin = $pluginManager->getLoadedPlugin($pluginName);
}
if ($plugin && $plugin->isTheme()) {
$theme = $pluginManager->getTheme($pluginName);
$javaScriptFiles = $theme->getJavaScriptFiles();
if (!empty($javaScriptFiles)) {
$assets = array_merge($assets, $javaScriptFiles);
}
}
return !empty($assets);
}
/**
* @param UIAsset[] $uiAssets
*/
public function removeAssets($uiAssets)
{
foreach ($uiAssets as $uiAsset) {
$uiAsset->delete();
}
}
/**
* @return UIAsset
*/
public function getMergedStylesheetAsset()
{
return $this->getMergedUIAsset(self::MERGED_CSS_FILE);
}
/**
* @return UIAsset
*/
private function getMergedCoreJSAsset()
{
return $this->getMergedUIAsset(self::MERGED_CORE_JS_FILE);
}
/**
* @return UIAsset
*/
protected function getMergedNonCoreJSAsset()
{
return $this->getMergedUIAsset(self::MERGED_NON_CORE_JS_FILE);
}
/**
* @param string $fileName
* @return UIAsset
*/
private function getMergedUIAsset($fileName)
{
return new OnDiskUIAsset($this->getAssetDirectory(), $fileName);
}
public static function compileCustomStylesheets($files)
{
$assetManager = new AssetManager();
$fetcher = new StaticUIAssetFetcher($files, $priorityOrder = array(), $theme = null);
$assetManager->setMinimalStylesheetFetcher($fetcher);
return $assetManager->getCompiledBaseCss()->getContent();
}
public static function compileCustomJs($files)
{
$mergedAsset = new InMemoryUIAsset();
$fetcher = new StaticUIAssetFetcher($files, $priorityOrder = array(), $theme = null);
$cacheBuster = UIAssetCacheBuster::getInstance();
$assetMerger = new JScriptUIAssetMerger($mergedAsset, $fetcher, $cacheBuster);
$assetMerger->generateFile();
return $mergedAsset->getContent();
}
}