PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / AssetManager / UIAssetMerger / StylesheetUIAssetMerger.php
matomo / app / core / AssetManager / UIAssetMerger Last commit date
JScriptUIAssetMerger.php 2 years ago StylesheetUIAssetMerger.php 2 months ago
StylesheetUIAssetMerger.php
237 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\AssetManager\UIAssetMerger;
10
11 use Exception;
12 use Matomo\Dependencies\lessc;
13 use Piwik\AssetManager\UIAsset;
14 use Piwik\AssetManager\UIAssetMerger;
15 use Piwik\Common;
16 use Piwik\Container\StaticContainer;
17 use Piwik\Exception\StylesheetLessCompileException;
18 use Piwik\Piwik;
19 use Piwik\Plugin\Manager;
20 class StylesheetUIAssetMerger extends UIAssetMerger
21 {
22 /**
23 * @var lessc
24 */
25 private $lessCompiler;
26 /**
27 * @var UIAsset[]
28 */
29 private $cssAssetsToReplace = array();
30 public function __construct($mergedAsset, $assetFetcher, $cacheBuster)
31 {
32 parent::__construct($mergedAsset, $assetFetcher, $cacheBuster);
33 $this->lessCompiler = self::getLessCompiler();
34 }
35 protected function getMergedAssets()
36 {
37 // note: we're using setImportDir on purpose (not addImportDir)
38 $this->lessCompiler->setImportDir(PIWIK_DOCUMENT_ROOT);
39 $concatenatedAssets = $this->getConcatenatedAssets();
40 $this->lessCompiler->setFormatter('classic');
41 try {
42 $compiled = $this->lessCompiler->compile($concatenatedAssets);
43 } catch (\Exception $e) {
44 // save the concatenated less files so we can debug the issue
45 $this->saveConcatenatedAssets($concatenatedAssets);
46 throw new StylesheetLessCompileException($e->getMessage());
47 }
48 foreach ($this->cssAssetsToReplace as $asset) {
49 // to fix #10173
50 $cssPath = $asset->getAbsoluteLocation();
51 $cssContent = $this->processFileContent($asset);
52 $compiled = str_replace($this->getCssStatementForReplacement($cssPath), $cssContent, $compiled);
53 }
54 $this->mergedContent = $compiled;
55 $this->cssAssetsToReplace = array();
56 return $compiled;
57 }
58 private function getCssStatementForReplacement($path)
59 {
60 return ".nonExistingSelectorOnlyForReplacementOfCssFiles {\n display: \"" . $path . "\";\n}";
61 }
62 protected function concatenateAssets()
63 {
64 $concatenatedContent = '';
65 foreach ($this->getAssetCatalog()->getAssets() as $uiAsset) {
66 $uiAsset->validateFile();
67 try {
68 $path = $uiAsset->getAbsoluteLocation();
69 } catch (Exception $e) {
70 $path = null;
71 }
72 if (!empty($path) && Common::stringEndsWith($path, '.css')) {
73 // to fix #10173
74 $concatenatedContent .= "\n" . $this->getCssStatementForReplacement($path) . "\n";
75 $this->cssAssetsToReplace[] = $uiAsset;
76 } else {
77 $content = $this->processFileContent($uiAsset);
78 $concatenatedContent .= $this->getFileSeparator() . $content;
79 }
80 }
81 /**
82 * Triggered after all less stylesheets are concatenated into one long string but before it is
83 * minified and merged into one file.
84 *
85 * This event can be used to add less stylesheets that are not located in a file on the disc.
86 *
87 * @param string $concatenatedContent The content of all concatenated less files.
88 */
89 Piwik::postEvent('AssetManager.addStylesheets', array(&$concatenatedContent));
90 $this->mergedContent = $concatenatedContent;
91 }
92 /**
93 * @return lessc
94 * @throws Exception
95 */
96 private static function getLessCompiler()
97 {
98 if (!class_exists("\\Matomo\\Dependencies\\lessc")) {
99 throw new Exception("Less was added to composer during 2.0. ==> Execute this command to update composer packages: \$ php composer.phar install");
100 }
101 $less = new lessc();
102 return $less;
103 }
104 protected function generateCacheBuster()
105 {
106 $fileHash = $this->cacheBuster->md5BasedCacheBuster($this->getConcatenatedAssets());
107 return "/* compile_me_once={$fileHash} */";
108 }
109 protected function getPreamble()
110 {
111 return $this->getCacheBusterValue() . "\n" . "/* Matomo CSS file is compiled with Less. You may be interested in writing a custom Theme for Matomo! */\n";
112 }
113 protected function postEvent(&$mergedContent)
114 {
115 /**
116 * Triggered after all less stylesheets are compiled to CSS, minified and merged into
117 * one file, but before the generated CSS is written to disk.
118 *
119 * This event can be used to modify merged CSS.
120 *
121 * @param string $mergedContent The merged and minified CSS.
122 */
123 Piwik::postEvent('AssetManager.filterMergedStylesheets', array(&$mergedContent));
124 }
125 public function getFileSeparator()
126 {
127 return '';
128 }
129 protected function processFileContent($uiAsset)
130 {
131 $pathsRewriter = $this->getCssPathsRewriter($uiAsset);
132 $content = $uiAsset->getContent();
133 $content = $this->rewriteCssImagePaths($content, $pathsRewriter);
134 $content = $this->rewriteCssImportPaths($content, $pathsRewriter);
135 return $content;
136 }
137 /**
138 * Rewrite CSS url() directives
139 *
140 * @param string $content
141 * @param callable $pathsRewriter
142 * @return string
143 */
144 private function rewriteCssImagePaths($content, $pathsRewriter)
145 {
146 $content = preg_replace_callback("/(url\\(['\"]?)([^'\")]*)/", $pathsRewriter, $content);
147 return $content;
148 }
149 /**
150 * Rewrite CSS import directives
151 *
152 * @param string $content
153 * @param callable $pathsRewriter
154 * @return string
155 */
156 private function rewriteCssImportPaths($content, $pathsRewriter)
157 {
158 $content = preg_replace_callback("/(@import \")([^\")]*)/", $pathsRewriter, $content);
159 return $content;
160 }
161 /**
162 * Rewrite CSS url directives
163 * - rewrites paths defined relatively to their css/less definition file
164 * - rewrite windows directory separator \\ to /
165 *
166 * @param UIAsset $uiAsset
167 * @return \Closure
168 */
169 private function getCssPathsRewriter($uiAsset)
170 {
171 $baseDirectory = dirname($uiAsset->getRelativeLocation());
172 $webDirs = Manager::getAlternativeWebRootDirectories();
173 return function ($matches) use($baseDirectory, $webDirs) {
174 $absolutePath = PIWIK_DOCUMENT_ROOT . "/{$baseDirectory}/" . $matches[2];
175 // Allow to import extension less file
176 if (strpos($matches[2], '.') === \false) {
177 $absolutePath .= '.less';
178 }
179 // Prevent from rewriting full path
180 $absolutePathReal = realpath($absolutePath);
181 if ($absolutePathReal) {
182 $relativePath = $baseDirectory . "/" . $matches[2];
183 $relativePath = str_replace('\\', '/', $relativePath);
184 $publicPath = $matches[1] . $relativePath;
185 } else {
186 foreach ($webDirs as $absPath => $relativePath) {
187 if (strpos($baseDirectory, $relativePath) === 0) {
188 if (strpos($matches[2], '.') === 0) {
189 // eg ../images/ok.png
190 $fileRelative = $baseDirectory . '/' . $matches[2];
191 $fileAbsolute = $absPath . str_replace($relativePath, '', $fileRelative);
192 if (file_exists($fileAbsolute)) {
193 return $matches[1] . $fileRelative;
194 }
195 } elseif (strpos($matches[2], 'plugins/') === 0) {
196 // eg plugins/Foo/images/ok.png
197 $fileRelative = substr($matches[2], strlen('plugins/'));
198 $fileAbsolute = $absPath . $fileRelative;
199 if (file_exists($fileAbsolute)) {
200 return $matches[1] . $relativePath . $fileRelative;
201 }
202 } elseif ($matches[1] === '@import "') {
203 $fileRelative = $baseDirectory . '/' . $matches[2];
204 $fileAbsolute = $absPath . str_replace($relativePath, '', $fileRelative);
205 if (file_exists($fileAbsolute)) {
206 return $matches[1] . $baseDirectory . '/' . $matches[2];
207 }
208 }
209 }
210 }
211 $publicPath = $matches[1] . $matches[2];
212 }
213 return $publicPath;
214 };
215 }
216 /**
217 * @param UIAsset $uiAsset
218 * @return int
219 */
220 protected function countDirectoriesInPathToRoot($uiAsset)
221 {
222 $rootDirectory = realpath($uiAsset->getBaseDirectory());
223 if ($rootDirectory != \PATH_SEPARATOR && substr($rootDirectory, -strlen(\PATH_SEPARATOR)) !== \PATH_SEPARATOR) {
224 $rootDirectory .= \PATH_SEPARATOR;
225 }
226 $rootDirectoryLen = strlen($rootDirectory);
227 return $rootDirectoryLen;
228 }
229 private function saveConcatenatedAssets($concatenatedAssets)
230 {
231 $file = StaticContainer::get('path.tmp') . '/assets/uimergedassets.concat.less';
232 if (is_writable(dirname($file))) {
233 file_put_contents($file, $concatenatedAssets);
234 }
235 }
236 }
237