PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.14.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.14.1
5.12.1 5.12.0 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 5 years ago StylesheetUIAssetMerger.php 5 years ago
StylesheetUIAssetMerger.php
273 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik\AssetManager\UIAssetMerger;
10
11 use Exception;
12 use 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
21 class StylesheetUIAssetMerger extends UIAssetMerger
22 {
23 /**
24 * @var lessc
25 */
26 private $lessCompiler;
27
28 /**
29 * @var UIAsset[]
30 */
31 private $cssAssetsToReplace = array();
32
33 public function __construct($mergedAsset, $assetFetcher, $cacheBuster)
34 {
35 parent::__construct($mergedAsset, $assetFetcher, $cacheBuster);
36
37 $this->lessCompiler = self::getLessCompiler();
38 }
39
40 protected function getMergedAssets()
41 {
42 // note: we're using setImportDir on purpose (not addImportDir)
43 $this->lessCompiler->setImportDir(PIWIK_DOCUMENT_ROOT);
44 $concatenatedAssets = $this->getConcatenatedAssets();
45
46 $this->lessCompiler->setFormatter('classic');
47 try {
48 $compiled = $this->lessCompiler->compile($concatenatedAssets);
49 } catch(\Exception $e) {
50 // save the concated less files so we can debug the issue
51 $this->saveConcatenatedAssets($concatenatedAssets);
52
53 throw new StylesheetLessCompileException($e->getMessage());
54 }
55
56 foreach ($this->cssAssetsToReplace as $asset) {
57 // to fix #10173
58 $cssPath = $asset->getAbsoluteLocation();
59 $cssContent = $this->processFileContent($asset);
60 $compiled = str_replace($this->getCssStatementForReplacement($cssPath), $cssContent, $compiled);
61 }
62
63 $this->mergedContent = $compiled;
64 $this->cssAssetsToReplace = array();
65
66 return $compiled;
67 }
68
69 private function getCssStatementForReplacement($path)
70 {
71 return ".nonExistingSelectorOnlyForReplacementOfCssFiles {\n display: \"" . $path . "\";\n}";
72 }
73
74 protected function concatenateAssets()
75 {
76 $concatenatedContent = '';
77
78 foreach ($this->getAssetCatalog()->getAssets() as $uiAsset) {
79 $uiAsset->validateFile();
80
81 try {
82 $path = $uiAsset->getAbsoluteLocation();
83 } catch (Exception $e) {
84 $path = null;
85 }
86
87 if (!empty($path) && Common::stringEndsWith($path, '.css')) {
88 // to fix #10173
89 $concatenatedContent .= "\n" . $this->getCssStatementForReplacement($path) . "\n";
90 $this->cssAssetsToReplace[] = $uiAsset;
91 } else {
92 $content = $this->processFileContent($uiAsset);
93 $concatenatedContent .= $this->getFileSeparator() . $content;
94 }
95 }
96
97 /**
98 * Triggered after all less stylesheets are concatenated into one long string but before it is
99 * minified and merged into one file.
100 *
101 * This event can be used to add less stylesheets that are not located in a file on the disc.
102 *
103 * @param string $concatenatedContent The content of all concatenated less files.
104 */
105 Piwik::postEvent('AssetManager.addStylesheets', array(&$concatenatedContent));
106
107 $this->mergedContent = $concatenatedContent;
108 }
109
110 /**
111 * @return lessc
112 * @throws Exception
113 */
114 private static function getLessCompiler()
115 {
116 if (!class_exists("lessc")) {
117 throw new Exception("Less was added to composer during 2.0. ==> Execute this command to update composer packages: \$ php composer.phar install");
118 }
119 $less = new lessc();
120 return $less;
121 }
122
123 protected function generateCacheBuster()
124 {
125 $fileHash = $this->cacheBuster->md5BasedCacheBuster($this->getConcatenatedAssets());
126 return "/* compile_me_once=$fileHash */";
127 }
128
129 protected function getPreamble()
130 {
131 return $this->getCacheBusterValue() . "\n"
132 . "/* Matomo CSS file is compiled with Less. You may be interested in writing a custom Theme for Matomo! */\n";
133 }
134
135 protected function postEvent(&$mergedContent)
136 {
137 /**
138 * Triggered after all less stylesheets are compiled to CSS, minified and merged into
139 * one file, but before the generated CSS is written to disk.
140 *
141 * This event can be used to modify merged CSS.
142 *
143 * @param string $mergedContent The merged and minified CSS.
144 */
145 Piwik::postEvent('AssetManager.filterMergedStylesheets', array(&$mergedContent));
146 }
147
148 public function getFileSeparator()
149 {
150 return '';
151 }
152
153 protected function processFileContent($uiAsset)
154 {
155 $pathsRewriter = $this->getCssPathsRewriter($uiAsset);
156 $content = $uiAsset->getContent();
157 $content = $this->rewriteCssImagePaths($content, $pathsRewriter);
158 $content = $this->rewriteCssImportPaths($content, $pathsRewriter);
159 return $content;
160 }
161
162 /**
163 * Rewrite CSS url() directives
164 *
165 * @param string $content
166 * @param callable $pathsRewriter
167 * @return string
168 */
169 private function rewriteCssImagePaths($content, $pathsRewriter)
170 {
171 $content = preg_replace_callback("/(url\(['\"]?)([^'\")]*)/", $pathsRewriter, $content);
172 return $content;
173 }
174
175 /**
176 * Rewrite CSS import directives
177 *
178 * @param string $content
179 * @param callable $pathsRewriter
180 * @return string
181 */
182 private function rewriteCssImportPaths($content, $pathsRewriter)
183 {
184 $content = preg_replace_callback("/(@import \")([^\")]*)/", $pathsRewriter, $content);
185 return $content;
186 }
187
188 /**
189 * Rewrite CSS url directives
190 * - rewrites paths defined relatively to their css/less definition file
191 * - rewrite windows directory separator \\ to /
192 *
193 * @param UIAsset $uiAsset
194 * @return \Closure
195 */
196 private function getCssPathsRewriter($uiAsset)
197 {
198 $baseDirectory = dirname($uiAsset->getRelativeLocation());
199 $webDirs = Manager::getAlternativeWebRootDirectories();
200
201 return function ($matches) use ($baseDirectory, $webDirs) {
202 $absolutePath = PIWIK_DOCUMENT_ROOT . "/$baseDirectory/" . $matches[2];
203
204 // Allow to import extension less file
205 if (strpos($matches[2], '.') === false) {
206 $absolutePath .= '.less';
207 }
208
209 // Prevent from rewriting full path
210 $absolutePathReal = realpath($absolutePath);
211 if ($absolutePathReal) {
212 $relativePath = $baseDirectory . "/" . $matches[2];
213 $relativePath = str_replace('\\', '/', $relativePath);
214 $publicPath = $matches[1] . $relativePath;
215 } else {
216 foreach ($webDirs as $absPath => $relativePath) {
217 if (strpos($baseDirectory, $relativePath) === 0) {
218 if (strpos($matches[2], '.') === 0) {
219 // eg ../images/ok.png
220 $fileRelative = $baseDirectory . '/' . $matches[2];
221 $fileAbsolute = $absPath . str_replace($relativePath, '', $fileRelative);
222 if (file_exists($fileAbsolute)) {
223 return $matches[1] . $fileRelative;
224 }
225 } elseif (strpos($matches[2], 'plugins/') === 0) {
226 // eg plugins/Foo/images/ok.png
227 $fileRelative = substr($matches[2], strlen('plugins/'));
228 $fileAbsolute = $absPath . $fileRelative;
229 if (file_exists($fileAbsolute)) {
230 return $matches[1] . $relativePath . $fileRelative;
231 }
232 } elseif ($matches[1] === '@import "') {
233 $fileRelative = $baseDirectory . '/' . $matches[2];
234 $fileAbsolute = $absPath . str_replace($relativePath, '', $fileRelative);
235 if (file_exists($fileAbsolute)) {
236 return $matches[1] . $baseDirectory . '/' . $matches[2];
237 }
238 }
239 }
240 }
241
242 $publicPath = $matches[1] . $matches[2];
243 }
244
245 return $publicPath;
246 };
247 }
248
249 /**
250 * @param UIAsset $uiAsset
251 * @return int
252 */
253 protected function countDirectoriesInPathToRoot($uiAsset)
254 {
255 $rootDirectory = realpath($uiAsset->getBaseDirectory());
256
257 if ($rootDirectory != PATH_SEPARATOR
258 && substr($rootDirectory, -strlen(PATH_SEPARATOR)) !== PATH_SEPARATOR) {
259 $rootDirectory .= PATH_SEPARATOR;
260 }
261 $rootDirectoryLen = strlen($rootDirectory);
262 return $rootDirectoryLen;
263 }
264
265 private function saveConcatenatedAssets($concatenatedAssets)
266 {
267 $file = StaticContainer::get('path.tmp') . '/assets/uimergedassets.concat.less';
268 if (is_writable(dirname($file))) {
269 file_put_contents($file, $concatenatedAssets);
270 }
271 }
272 }
273