Passed
Push — master ( 07dffb...bd5189 )
by John
11:02 queued 10s
created
lib/private/Template/SCSSCacher.php 2 patches
Indentation   +459 added lines, -459 removed lines patch added patch discarded remove patch
@@ -50,463 +50,463 @@
 block discarded – undo
50 50
 
51 51
 class SCSSCacher {
52 52
 
53
-	/** @var ILogger */
54
-	protected $logger;
55
-
56
-	/** @var IAppData */
57
-	protected $appData;
58
-
59
-	/** @var IURLGenerator */
60
-	protected $urlGenerator;
61
-
62
-	/** @var IConfig */
63
-	protected $config;
64
-
65
-	/** @var \OC_Defaults */
66
-	private $defaults;
67
-
68
-	/** @var string */
69
-	protected $serverRoot;
70
-
71
-	/** @var ICache */
72
-	protected $depsCache;
73
-
74
-	/** @var null|string */
75
-	private $injectedVariables;
76
-
77
-	/** @var ICacheFactory */
78
-	private $cacheFactory;
79
-
80
-	/** @var IconsCacher */
81
-	private $iconsCacher;
82
-
83
-	/** @var ICache */
84
-	private $isCachedCache;
85
-
86
-	/** @var ITimeFactory */
87
-	private $timeFactory;
88
-
89
-	/** @var IMemcache */
90
-	private $lockingCache;
91
-
92
-	/**
93
-	 * @param ILogger $logger
94
-	 * @param Factory $appDataFactory
95
-	 * @param IURLGenerator $urlGenerator
96
-	 * @param IConfig $config
97
-	 * @param \OC_Defaults $defaults
98
-	 * @param string $serverRoot
99
-	 * @param ICacheFactory $cacheFactory
100
-	 * @param IconsCacher $iconsCacher
101
-	 * @param ITimeFactory $timeFactory
102
-	 */
103
-	public function __construct(ILogger $logger,
104
-								Factory $appDataFactory,
105
-								IURLGenerator $urlGenerator,
106
-								IConfig $config,
107
-								\OC_Defaults $defaults,
108
-								$serverRoot,
109
-								ICacheFactory $cacheFactory,
110
-								IconsCacher $iconsCacher,
111
-								ITimeFactory $timeFactory) {
112
-		$this->logger       = $logger;
113
-		$this->appData      = $appDataFactory->get('css');
114
-		$this->urlGenerator = $urlGenerator;
115
-		$this->config       = $config;
116
-		$this->defaults     = $defaults;
117
-		$this->serverRoot   = $serverRoot;
118
-		$this->cacheFactory = $cacheFactory;
119
-		$this->depsCache    = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl()));
120
-		$this->isCachedCache = $cacheFactory->createDistributed('SCSS-cached-' . md5($this->urlGenerator->getBaseUrl()));
121
-		$lockingCache = $cacheFactory->createDistributed('SCSS-locks-' . md5($this->urlGenerator->getBaseUrl()));
122
-		if (!($lockingCache instanceof IMemcache)) {
123
-			$lockingCache = new NullCache();
124
-		}
125
-		$this->lockingCache = $lockingCache;
126
-		$this->iconsCacher = $iconsCacher;
127
-		$this->timeFactory = $timeFactory;
128
-	}
129
-
130
-	/**
131
-	 * Process the caching process if needed
132
-	 *
133
-	 * @param string $root Root path to the nextcloud installation
134
-	 * @param string $file
135
-	 * @param string $app The app name
136
-	 * @return boolean
137
-	 * @throws NotPermittedException
138
-	 */
139
-	public function process(string $root, string $file, string $app): bool {
140
-		$path = explode('/', $root . '/' . $file);
141
-
142
-		$fileNameSCSS = array_pop($path);
143
-		$fileNameCSS  = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app);
144
-
145
-		$path   = implode('/', $path);
146
-		$webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT);
147
-
148
-		if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
149
-			// Inject icons vars css if any
150
-			return $this->injectCssVariablesIfAny();
151
-		}
152
-
153
-		try {
154
-			$folder = $this->appData->getFolder($app);
155
-		} catch (NotFoundException $e) {
156
-			// creating css appdata folder
157
-			$folder = $this->appData->newFolder($app);
158
-		}
159
-
160
-		$lockKey = $webDir . '/' . $fileNameSCSS;
161
-
162
-		if (!$this->lockingCache->add($lockKey, 'locked!', 120)) {
163
-			$retry = 0;
164
-			sleep(1);
165
-			while ($retry < 10) {
166
-				if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
167
-					// Inject icons vars css if any
168
-					$this->lockingCache->remove($lockKey);
169
-					$this->logger->debug('SCSSCacher: ' .$lockKey.' is now available after '.$retry.'s. Moving on...', ['app' => 'core']);
170
-					return $this->injectCssVariablesIfAny();
171
-				}
172
-				$this->logger->debug('SCSSCacher: scss cache file locked for '.$lockKey, ['app' => 'core']);
173
-				sleep($retry);
174
-				$retry++;
175
-			}
176
-			$this->logger->debug('SCSSCacher: Giving up scss caching for '.$lockKey, ['app' => 'core']);
177
-			return false;
178
-		}
179
-
180
-		try {
181
-			$cached = $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
182
-		} catch (\Exception $e) {
183
-			$this->lockingCache->remove($lockKey);
184
-			throw $e;
185
-		}
186
-
187
-		// Cleaning lock
188
-		$this->lockingCache->remove($lockKey);
189
-
190
-		// Inject icons vars css if any
191
-		if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
192
-			$this->iconsCacher->injectCss();
193
-		}
194
-
195
-		return $cached;
196
-	}
197
-
198
-	/**
199
-	 * @param $appName
200
-	 * @param $fileName
201
-	 * @return ISimpleFile
202
-	 */
203
-	public function getCachedCSS(string $appName, string $fileName): ISimpleFile {
204
-		$folder         = $this->appData->getFolder($appName);
205
-		$cachedFileName = $this->prependVersionPrefix($this->prependBaseurlPrefix($fileName), $appName);
206
-
207
-		return $folder->getFile($cachedFileName);
208
-	}
209
-
210
-	/**
211
-	 * Check if the file is cached or not
212
-	 * @param string $fileNameCSS
213
-	 * @param string $app
214
-	 * @return boolean
215
-	 */
216
-	private function isCached(string $fileNameCSS, string $app) {
217
-		$key = $this->config->getSystemValue('version') . '/' . $app . '/' . $fileNameCSS;
218
-
219
-		// If the file mtime is more recent than our cached one,
220
-		// let's consider the file is properly cached
221
-		if ($cacheValue = $this->isCachedCache->get($key)) {
222
-			if ($cacheValue > $this->timeFactory->getTime()) {
223
-				return true;
224
-			}
225
-		}
226
-
227
-		// Creating file cache if none for further checks
228
-		try {
229
-			$folder = $this->appData->getFolder($app);
230
-		} catch (NotFoundException $e) {
231
-			return false;
232
-		}
233
-
234
-		// Checking if file size is coherent
235
-		// and if one of the css dependency changed
236
-		try {
237
-			$cachedFile = $folder->getFile($fileNameCSS);
238
-			if ($cachedFile->getSize() > 0) {
239
-				$depFileName = $fileNameCSS . '.deps';
240
-				$deps        = $this->depsCache->get($folder->getName() . '-' . $depFileName);
241
-				if ($deps === null) {
242
-					$depFile = $folder->getFile($depFileName);
243
-					$deps    = $depFile->getContent();
244
-					// Set to memcache for next run
245
-					$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
246
-				}
247
-				$deps = json_decode($deps, true);
248
-
249
-				foreach ((array) $deps as $file => $mtime) {
250
-					if (!file_exists($file) || filemtime($file) > $mtime) {
251
-						return false;
252
-					}
253
-				}
254
-
255
-				$this->isCachedCache->set($key, $this->timeFactory->getTime() + 5 * 60);
256
-				return true;
257
-			}
258
-
259
-			return false;
260
-		} catch (NotFoundException $e) {
261
-			return false;
262
-		}
263
-	}
264
-
265
-	/**
266
-	 * Check if the variables file has changed
267
-	 * @return bool
268
-	 */
269
-	private function variablesChanged(): bool {
270
-		$injectedVariables = $this->getInjectedVariables();
271
-		if ($this->config->getAppValue('core', 'theming.variables') !== md5($injectedVariables)) {
272
-			$this->config->setAppValue('core', 'theming.variables', md5($injectedVariables));
273
-			$this->resetCache();
274
-			return true;
275
-		}
276
-		return false;
277
-	}
278
-
279
-	/**
280
-	 * Cache the file with AppData
281
-	 *
282
-	 * @param string $path
283
-	 * @param string $fileNameCSS
284
-	 * @param string $fileNameSCSS
285
-	 * @param ISimpleFolder $folder
286
-	 * @param string $webDir
287
-	 * @return boolean
288
-	 * @throws NotPermittedException
289
-	 */
290
-	private function cache(string $path, string $fileNameCSS, string $fileNameSCSS, ISimpleFolder $folder, string $webDir) {
291
-		$scss = new Compiler();
292
-		$scss->setImportPaths([
293
-			$path,
294
-			$this->serverRoot . '/core/css/'
295
-		]);
296
-
297
-		// Continue after throw
298
-		$scss->setIgnoreErrors(true);
299
-		if ($this->config->getSystemValue('debug')) {
300
-			// Debug mode
301
-			$scss->setFormatter(Expanded::class);
302
-			$scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
303
-		} else {
304
-			// Compression
305
-			$scss->setFormatter(Crunched::class);
306
-		}
307
-
308
-		try {
309
-			$cachedfile = $folder->getFile($fileNameCSS);
310
-		} catch (NotFoundException $e) {
311
-			$cachedfile = $folder->newFile($fileNameCSS);
312
-		}
313
-
314
-		$depFileName = $fileNameCSS . '.deps';
315
-		try {
316
-			$depFile = $folder->getFile($depFileName);
317
-		} catch (NotFoundException $e) {
318
-			$depFile = $folder->newFile($depFileName);
319
-		}
320
-
321
-		// Compile
322
-		try {
323
-			$compiledScss = $scss->compile(
324
-				'$webroot: \'' . $this->getRoutePrefix() . '\';' .
325
-				$this->getInjectedVariables() .
326
-				'@import "variables.scss";' .
327
-				'@import "functions.scss";' .
328
-				'@import "' . $fileNameSCSS . '";');
329
-		} catch (ParserException $e) {
330
-			$this->logger->logException($e, ['app' => 'core']);
331
-
332
-			return false;
333
-		}
334
-
335
-		// Parse Icons and create related css variables
336
-		$compiledScss = $this->iconsCacher->setIconsCss($compiledScss);
337
-
338
-		// Gzip file
339
-		try {
340
-			$gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
341
-		} catch (NotFoundException $e) {
342
-			$gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
343
-		}
344
-
345
-		try {
346
-			$data = $this->rebaseUrls($compiledScss, $webDir);
347
-			$cachedfile->putContent($data);
348
-			$deps = json_encode($scss->getParsedFiles());
349
-			$depFile->putContent($deps);
350
-			$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
351
-			$gzipFile->putContent(gzencode($data, 9));
352
-			$this->logger->debug('SCSSCacher: ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached', ['app' => 'core']);
353
-
354
-			return true;
355
-		} catch (NotPermittedException $e) {
356
-			$this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS);
357
-
358
-			return false;
359
-		}
360
-	}
361
-
362
-	/**
363
-	 * Reset scss cache by deleting all generated css files
364
-	 * We need to regenerate all files when variables change
365
-	 */
366
-	public function resetCache() {
367
-		if (!$this->lockingCache->add('resetCache', 'locked!', 120)) {
368
-			return;
369
-		}
370
-		$this->injectedVariables = null;
371
-
372
-		// do not clear locks
373
-		$this->cacheFactory->createDistributed('SCSS-deps-')->clear();
374
-		$this->cacheFactory->createDistributed('SCSS-cached-')->clear();
375
-
376
-		$appDirectory = $this->appData->getDirectoryListing();
377
-		foreach ($appDirectory as $folder) {
378
-			foreach ($folder->getDirectoryListing() as $file) {
379
-				try {
380
-					$file->delete();
381
-				} catch (NotPermittedException $e) {
382
-					$this->logger->logException($e, ['message' => 'SCSSCacher: unable to delete file: ' . $file->getName()]);
383
-				}
384
-			}
385
-		}
386
-		$this->logger->debug('SCSSCacher: css cache cleared!');
387
-		$this->lockingCache->remove('resetCache');
388
-	}
389
-
390
-	/**
391
-	 * @return string SCSS code for variables from OC_Defaults
392
-	 */
393
-	private function getInjectedVariables(): string {
394
-		if ($this->injectedVariables !== null) {
395
-			return $this->injectedVariables;
396
-		}
397
-		$variables = '';
398
-		foreach ($this->defaults->getScssVariables() as $key => $value) {
399
-			$variables .= '$' . $key . ': ' . $value . ' !default;';
400
-		}
401
-
402
-		// check for valid variables / otherwise fall back to defaults
403
-		try {
404
-			$scss = new Compiler();
405
-			$scss->compile($variables);
406
-			$this->injectedVariables = $variables;
407
-		} catch (ParserException $e) {
408
-			$this->logger->logException($e, ['app' => 'core']);
409
-		}
410
-
411
-		return $variables;
412
-	}
413
-
414
-	/**
415
-	 * Add the correct uri prefix to make uri valid again
416
-	 * @param string $css
417
-	 * @param string $webDir
418
-	 * @return string
419
-	 */
420
-	private function rebaseUrls(string $css, string $webDir): string {
421
-		$re    = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
422
-		$subst = 'url(\'' . $webDir . '/$1\')';
423
-
424
-		return preg_replace($re, $subst, $css);
425
-	}
426
-
427
-	/**
428
-	 * Return the cached css file uri
429
-	 * @param string $appName the app name
430
-	 * @param string $fileName
431
-	 * @return string
432
-	 */
433
-	public function getCachedSCSS(string $appName, string $fileName): string {
434
-		$tmpfileLoc = explode('/', $fileName);
435
-		$fileName   = array_pop($tmpfileLoc);
436
-		$fileName   = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName);
437
-
438
-		return substr($this->urlGenerator->linkToRoute('core.Css.getCss', [
439
-			'fileName' => $fileName,
440
-			'appName' => $appName,
441
-			'v' => $this->config->getAppValue('core', 'theming.variables', '0')
442
-		]), \strlen(\OC::$WEBROOT) + 1);
443
-	}
444
-
445
-	/**
446
-	 * Prepend hashed base url to the css file
447
-	 * @param string $cssFile
448
-	 * @return string
449
-	 */
450
-	private function prependBaseurlPrefix(string $cssFile): string {
451
-		return substr(md5($this->urlGenerator->getBaseUrl() . $this->getRoutePrefix()), 0, 4) . '-' . $cssFile;
452
-	}
453
-
454
-	private function getRoutePrefix() {
455
-		$frontControllerActive = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
456
-		$prefix = \OC::$WEBROOT . '/index.php';
457
-		if ($frontControllerActive) {
458
-			$prefix = \OC::$WEBROOT;
459
-		}
460
-		return $prefix;
461
-	}
462
-
463
-	/**
464
-	 * Prepend hashed app version hash
465
-	 * @param string $cssFile
466
-	 * @param string $appId
467
-	 * @return string
468
-	 */
469
-	private function prependVersionPrefix(string $cssFile, string $appId): string {
470
-		$appVersion = \OC_App::getAppVersion($appId);
471
-		if ($appVersion !== '0') {
472
-			return substr(md5($appVersion), 0, 4) . '-' . $cssFile;
473
-		}
474
-		$coreVersion = \OC_Util::getVersionString();
475
-
476
-		return substr(md5($coreVersion), 0, 4) . '-' . $cssFile;
477
-	}
478
-
479
-	/**
480
-	 * Get WebDir root
481
-	 * @param string $path the css file path
482
-	 * @param string $appName the app name
483
-	 * @param string $serverRoot the server root path
484
-	 * @param string $webRoot the nextcloud installation root path
485
-	 * @return string the webDir
486
-	 */
487
-	private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string {
488
-		// Detect if path is within server root AND if path is within an app path
489
-		if (strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
490
-			// Get the file path within the app directory
491
-			$appDirectoryPath = explode($appName, $path)[1];
492
-			// Remove the webroot
493
-
494
-			return str_replace($webRoot, '', $appWebPath . $appDirectoryPath);
495
-		}
496
-
497
-		return $webRoot . substr($path, strlen($serverRoot));
498
-	}
499
-
500
-	/**
501
-	 * Add the icons css cache in the header if needed 
502
-	 *
503
-	 * @return boolean true
504
-	 */
505
-	private function injectCssVariablesIfAny() {
506
-		// Inject icons vars css if any
507
-		if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
508
-			$this->iconsCacher->injectCss();
509
-		}
510
-		return true;
511
-	}
53
+    /** @var ILogger */
54
+    protected $logger;
55
+
56
+    /** @var IAppData */
57
+    protected $appData;
58
+
59
+    /** @var IURLGenerator */
60
+    protected $urlGenerator;
61
+
62
+    /** @var IConfig */
63
+    protected $config;
64
+
65
+    /** @var \OC_Defaults */
66
+    private $defaults;
67
+
68
+    /** @var string */
69
+    protected $serverRoot;
70
+
71
+    /** @var ICache */
72
+    protected $depsCache;
73
+
74
+    /** @var null|string */
75
+    private $injectedVariables;
76
+
77
+    /** @var ICacheFactory */
78
+    private $cacheFactory;
79
+
80
+    /** @var IconsCacher */
81
+    private $iconsCacher;
82
+
83
+    /** @var ICache */
84
+    private $isCachedCache;
85
+
86
+    /** @var ITimeFactory */
87
+    private $timeFactory;
88
+
89
+    /** @var IMemcache */
90
+    private $lockingCache;
91
+
92
+    /**
93
+     * @param ILogger $logger
94
+     * @param Factory $appDataFactory
95
+     * @param IURLGenerator $urlGenerator
96
+     * @param IConfig $config
97
+     * @param \OC_Defaults $defaults
98
+     * @param string $serverRoot
99
+     * @param ICacheFactory $cacheFactory
100
+     * @param IconsCacher $iconsCacher
101
+     * @param ITimeFactory $timeFactory
102
+     */
103
+    public function __construct(ILogger $logger,
104
+                                Factory $appDataFactory,
105
+                                IURLGenerator $urlGenerator,
106
+                                IConfig $config,
107
+                                \OC_Defaults $defaults,
108
+                                $serverRoot,
109
+                                ICacheFactory $cacheFactory,
110
+                                IconsCacher $iconsCacher,
111
+                                ITimeFactory $timeFactory) {
112
+        $this->logger       = $logger;
113
+        $this->appData      = $appDataFactory->get('css');
114
+        $this->urlGenerator = $urlGenerator;
115
+        $this->config       = $config;
116
+        $this->defaults     = $defaults;
117
+        $this->serverRoot   = $serverRoot;
118
+        $this->cacheFactory = $cacheFactory;
119
+        $this->depsCache    = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl()));
120
+        $this->isCachedCache = $cacheFactory->createDistributed('SCSS-cached-' . md5($this->urlGenerator->getBaseUrl()));
121
+        $lockingCache = $cacheFactory->createDistributed('SCSS-locks-' . md5($this->urlGenerator->getBaseUrl()));
122
+        if (!($lockingCache instanceof IMemcache)) {
123
+            $lockingCache = new NullCache();
124
+        }
125
+        $this->lockingCache = $lockingCache;
126
+        $this->iconsCacher = $iconsCacher;
127
+        $this->timeFactory = $timeFactory;
128
+    }
129
+
130
+    /**
131
+     * Process the caching process if needed
132
+     *
133
+     * @param string $root Root path to the nextcloud installation
134
+     * @param string $file
135
+     * @param string $app The app name
136
+     * @return boolean
137
+     * @throws NotPermittedException
138
+     */
139
+    public function process(string $root, string $file, string $app): bool {
140
+        $path = explode('/', $root . '/' . $file);
141
+
142
+        $fileNameSCSS = array_pop($path);
143
+        $fileNameCSS  = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app);
144
+
145
+        $path   = implode('/', $path);
146
+        $webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT);
147
+
148
+        if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
149
+            // Inject icons vars css if any
150
+            return $this->injectCssVariablesIfAny();
151
+        }
152
+
153
+        try {
154
+            $folder = $this->appData->getFolder($app);
155
+        } catch (NotFoundException $e) {
156
+            // creating css appdata folder
157
+            $folder = $this->appData->newFolder($app);
158
+        }
159
+
160
+        $lockKey = $webDir . '/' . $fileNameSCSS;
161
+
162
+        if (!$this->lockingCache->add($lockKey, 'locked!', 120)) {
163
+            $retry = 0;
164
+            sleep(1);
165
+            while ($retry < 10) {
166
+                if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
167
+                    // Inject icons vars css if any
168
+                    $this->lockingCache->remove($lockKey);
169
+                    $this->logger->debug('SCSSCacher: ' .$lockKey.' is now available after '.$retry.'s. Moving on...', ['app' => 'core']);
170
+                    return $this->injectCssVariablesIfAny();
171
+                }
172
+                $this->logger->debug('SCSSCacher: scss cache file locked for '.$lockKey, ['app' => 'core']);
173
+                sleep($retry);
174
+                $retry++;
175
+            }
176
+            $this->logger->debug('SCSSCacher: Giving up scss caching for '.$lockKey, ['app' => 'core']);
177
+            return false;
178
+        }
179
+
180
+        try {
181
+            $cached = $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
182
+        } catch (\Exception $e) {
183
+            $this->lockingCache->remove($lockKey);
184
+            throw $e;
185
+        }
186
+
187
+        // Cleaning lock
188
+        $this->lockingCache->remove($lockKey);
189
+
190
+        // Inject icons vars css if any
191
+        if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
192
+            $this->iconsCacher->injectCss();
193
+        }
194
+
195
+        return $cached;
196
+    }
197
+
198
+    /**
199
+     * @param $appName
200
+     * @param $fileName
201
+     * @return ISimpleFile
202
+     */
203
+    public function getCachedCSS(string $appName, string $fileName): ISimpleFile {
204
+        $folder         = $this->appData->getFolder($appName);
205
+        $cachedFileName = $this->prependVersionPrefix($this->prependBaseurlPrefix($fileName), $appName);
206
+
207
+        return $folder->getFile($cachedFileName);
208
+    }
209
+
210
+    /**
211
+     * Check if the file is cached or not
212
+     * @param string $fileNameCSS
213
+     * @param string $app
214
+     * @return boolean
215
+     */
216
+    private function isCached(string $fileNameCSS, string $app) {
217
+        $key = $this->config->getSystemValue('version') . '/' . $app . '/' . $fileNameCSS;
218
+
219
+        // If the file mtime is more recent than our cached one,
220
+        // let's consider the file is properly cached
221
+        if ($cacheValue = $this->isCachedCache->get($key)) {
222
+            if ($cacheValue > $this->timeFactory->getTime()) {
223
+                return true;
224
+            }
225
+        }
226
+
227
+        // Creating file cache if none for further checks
228
+        try {
229
+            $folder = $this->appData->getFolder($app);
230
+        } catch (NotFoundException $e) {
231
+            return false;
232
+        }
233
+
234
+        // Checking if file size is coherent
235
+        // and if one of the css dependency changed
236
+        try {
237
+            $cachedFile = $folder->getFile($fileNameCSS);
238
+            if ($cachedFile->getSize() > 0) {
239
+                $depFileName = $fileNameCSS . '.deps';
240
+                $deps        = $this->depsCache->get($folder->getName() . '-' . $depFileName);
241
+                if ($deps === null) {
242
+                    $depFile = $folder->getFile($depFileName);
243
+                    $deps    = $depFile->getContent();
244
+                    // Set to memcache for next run
245
+                    $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
246
+                }
247
+                $deps = json_decode($deps, true);
248
+
249
+                foreach ((array) $deps as $file => $mtime) {
250
+                    if (!file_exists($file) || filemtime($file) > $mtime) {
251
+                        return false;
252
+                    }
253
+                }
254
+
255
+                $this->isCachedCache->set($key, $this->timeFactory->getTime() + 5 * 60);
256
+                return true;
257
+            }
258
+
259
+            return false;
260
+        } catch (NotFoundException $e) {
261
+            return false;
262
+        }
263
+    }
264
+
265
+    /**
266
+     * Check if the variables file has changed
267
+     * @return bool
268
+     */
269
+    private function variablesChanged(): bool {
270
+        $injectedVariables = $this->getInjectedVariables();
271
+        if ($this->config->getAppValue('core', 'theming.variables') !== md5($injectedVariables)) {
272
+            $this->config->setAppValue('core', 'theming.variables', md5($injectedVariables));
273
+            $this->resetCache();
274
+            return true;
275
+        }
276
+        return false;
277
+    }
278
+
279
+    /**
280
+     * Cache the file with AppData
281
+     *
282
+     * @param string $path
283
+     * @param string $fileNameCSS
284
+     * @param string $fileNameSCSS
285
+     * @param ISimpleFolder $folder
286
+     * @param string $webDir
287
+     * @return boolean
288
+     * @throws NotPermittedException
289
+     */
290
+    private function cache(string $path, string $fileNameCSS, string $fileNameSCSS, ISimpleFolder $folder, string $webDir) {
291
+        $scss = new Compiler();
292
+        $scss->setImportPaths([
293
+            $path,
294
+            $this->serverRoot . '/core/css/'
295
+        ]);
296
+
297
+        // Continue after throw
298
+        $scss->setIgnoreErrors(true);
299
+        if ($this->config->getSystemValue('debug')) {
300
+            // Debug mode
301
+            $scss->setFormatter(Expanded::class);
302
+            $scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
303
+        } else {
304
+            // Compression
305
+            $scss->setFormatter(Crunched::class);
306
+        }
307
+
308
+        try {
309
+            $cachedfile = $folder->getFile($fileNameCSS);
310
+        } catch (NotFoundException $e) {
311
+            $cachedfile = $folder->newFile($fileNameCSS);
312
+        }
313
+
314
+        $depFileName = $fileNameCSS . '.deps';
315
+        try {
316
+            $depFile = $folder->getFile($depFileName);
317
+        } catch (NotFoundException $e) {
318
+            $depFile = $folder->newFile($depFileName);
319
+        }
320
+
321
+        // Compile
322
+        try {
323
+            $compiledScss = $scss->compile(
324
+                '$webroot: \'' . $this->getRoutePrefix() . '\';' .
325
+                $this->getInjectedVariables() .
326
+                '@import "variables.scss";' .
327
+                '@import "functions.scss";' .
328
+                '@import "' . $fileNameSCSS . '";');
329
+        } catch (ParserException $e) {
330
+            $this->logger->logException($e, ['app' => 'core']);
331
+
332
+            return false;
333
+        }
334
+
335
+        // Parse Icons and create related css variables
336
+        $compiledScss = $this->iconsCacher->setIconsCss($compiledScss);
337
+
338
+        // Gzip file
339
+        try {
340
+            $gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
341
+        } catch (NotFoundException $e) {
342
+            $gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
343
+        }
344
+
345
+        try {
346
+            $data = $this->rebaseUrls($compiledScss, $webDir);
347
+            $cachedfile->putContent($data);
348
+            $deps = json_encode($scss->getParsedFiles());
349
+            $depFile->putContent($deps);
350
+            $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
351
+            $gzipFile->putContent(gzencode($data, 9));
352
+            $this->logger->debug('SCSSCacher: ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached', ['app' => 'core']);
353
+
354
+            return true;
355
+        } catch (NotPermittedException $e) {
356
+            $this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS);
357
+
358
+            return false;
359
+        }
360
+    }
361
+
362
+    /**
363
+     * Reset scss cache by deleting all generated css files
364
+     * We need to regenerate all files when variables change
365
+     */
366
+    public function resetCache() {
367
+        if (!$this->lockingCache->add('resetCache', 'locked!', 120)) {
368
+            return;
369
+        }
370
+        $this->injectedVariables = null;
371
+
372
+        // do not clear locks
373
+        $this->cacheFactory->createDistributed('SCSS-deps-')->clear();
374
+        $this->cacheFactory->createDistributed('SCSS-cached-')->clear();
375
+
376
+        $appDirectory = $this->appData->getDirectoryListing();
377
+        foreach ($appDirectory as $folder) {
378
+            foreach ($folder->getDirectoryListing() as $file) {
379
+                try {
380
+                    $file->delete();
381
+                } catch (NotPermittedException $e) {
382
+                    $this->logger->logException($e, ['message' => 'SCSSCacher: unable to delete file: ' . $file->getName()]);
383
+                }
384
+            }
385
+        }
386
+        $this->logger->debug('SCSSCacher: css cache cleared!');
387
+        $this->lockingCache->remove('resetCache');
388
+    }
389
+
390
+    /**
391
+     * @return string SCSS code for variables from OC_Defaults
392
+     */
393
+    private function getInjectedVariables(): string {
394
+        if ($this->injectedVariables !== null) {
395
+            return $this->injectedVariables;
396
+        }
397
+        $variables = '';
398
+        foreach ($this->defaults->getScssVariables() as $key => $value) {
399
+            $variables .= '$' . $key . ': ' . $value . ' !default;';
400
+        }
401
+
402
+        // check for valid variables / otherwise fall back to defaults
403
+        try {
404
+            $scss = new Compiler();
405
+            $scss->compile($variables);
406
+            $this->injectedVariables = $variables;
407
+        } catch (ParserException $e) {
408
+            $this->logger->logException($e, ['app' => 'core']);
409
+        }
410
+
411
+        return $variables;
412
+    }
413
+
414
+    /**
415
+     * Add the correct uri prefix to make uri valid again
416
+     * @param string $css
417
+     * @param string $webDir
418
+     * @return string
419
+     */
420
+    private function rebaseUrls(string $css, string $webDir): string {
421
+        $re    = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
422
+        $subst = 'url(\'' . $webDir . '/$1\')';
423
+
424
+        return preg_replace($re, $subst, $css);
425
+    }
426
+
427
+    /**
428
+     * Return the cached css file uri
429
+     * @param string $appName the app name
430
+     * @param string $fileName
431
+     * @return string
432
+     */
433
+    public function getCachedSCSS(string $appName, string $fileName): string {
434
+        $tmpfileLoc = explode('/', $fileName);
435
+        $fileName   = array_pop($tmpfileLoc);
436
+        $fileName   = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName);
437
+
438
+        return substr($this->urlGenerator->linkToRoute('core.Css.getCss', [
439
+            'fileName' => $fileName,
440
+            'appName' => $appName,
441
+            'v' => $this->config->getAppValue('core', 'theming.variables', '0')
442
+        ]), \strlen(\OC::$WEBROOT) + 1);
443
+    }
444
+
445
+    /**
446
+     * Prepend hashed base url to the css file
447
+     * @param string $cssFile
448
+     * @return string
449
+     */
450
+    private function prependBaseurlPrefix(string $cssFile): string {
451
+        return substr(md5($this->urlGenerator->getBaseUrl() . $this->getRoutePrefix()), 0, 4) . '-' . $cssFile;
452
+    }
453
+
454
+    private function getRoutePrefix() {
455
+        $frontControllerActive = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
456
+        $prefix = \OC::$WEBROOT . '/index.php';
457
+        if ($frontControllerActive) {
458
+            $prefix = \OC::$WEBROOT;
459
+        }
460
+        return $prefix;
461
+    }
462
+
463
+    /**
464
+     * Prepend hashed app version hash
465
+     * @param string $cssFile
466
+     * @param string $appId
467
+     * @return string
468
+     */
469
+    private function prependVersionPrefix(string $cssFile, string $appId): string {
470
+        $appVersion = \OC_App::getAppVersion($appId);
471
+        if ($appVersion !== '0') {
472
+            return substr(md5($appVersion), 0, 4) . '-' . $cssFile;
473
+        }
474
+        $coreVersion = \OC_Util::getVersionString();
475
+
476
+        return substr(md5($coreVersion), 0, 4) . '-' . $cssFile;
477
+    }
478
+
479
+    /**
480
+     * Get WebDir root
481
+     * @param string $path the css file path
482
+     * @param string $appName the app name
483
+     * @param string $serverRoot the server root path
484
+     * @param string $webRoot the nextcloud installation root path
485
+     * @return string the webDir
486
+     */
487
+    private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string {
488
+        // Detect if path is within server root AND if path is within an app path
489
+        if (strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
490
+            // Get the file path within the app directory
491
+            $appDirectoryPath = explode($appName, $path)[1];
492
+            // Remove the webroot
493
+
494
+            return str_replace($webRoot, '', $appWebPath . $appDirectoryPath);
495
+        }
496
+
497
+        return $webRoot . substr($path, strlen($serverRoot));
498
+    }
499
+
500
+    /**
501
+     * Add the icons css cache in the header if needed 
502
+     *
503
+     * @return boolean true
504
+     */
505
+    private function injectCssVariablesIfAny() {
506
+        // Inject icons vars css if any
507
+        if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
508
+            $this->iconsCacher->injectCss();
509
+        }
510
+        return true;
511
+    }
512 512
 }
Please login to merge, or discard this patch.
Spacing   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -116,9 +116,9 @@  discard block
 block discarded – undo
116 116
 		$this->defaults     = $defaults;
117 117
 		$this->serverRoot   = $serverRoot;
118 118
 		$this->cacheFactory = $cacheFactory;
119
-		$this->depsCache    = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl()));
120
-		$this->isCachedCache = $cacheFactory->createDistributed('SCSS-cached-' . md5($this->urlGenerator->getBaseUrl()));
121
-		$lockingCache = $cacheFactory->createDistributed('SCSS-locks-' . md5($this->urlGenerator->getBaseUrl()));
119
+		$this->depsCache    = $cacheFactory->createDistributed('SCSS-deps-'.md5($this->urlGenerator->getBaseUrl()));
120
+		$this->isCachedCache = $cacheFactory->createDistributed('SCSS-cached-'.md5($this->urlGenerator->getBaseUrl()));
121
+		$lockingCache = $cacheFactory->createDistributed('SCSS-locks-'.md5($this->urlGenerator->getBaseUrl()));
122 122
 		if (!($lockingCache instanceof IMemcache)) {
123 123
 			$lockingCache = new NullCache();
124 124
 		}
@@ -137,7 +137,7 @@  discard block
 block discarded – undo
137 137
 	 * @throws NotPermittedException
138 138
 	 */
139 139
 	public function process(string $root, string $file, string $app): bool {
140
-		$path = explode('/', $root . '/' . $file);
140
+		$path = explode('/', $root.'/'.$file);
141 141
 
142 142
 		$fileNameSCSS = array_pop($path);
143 143
 		$fileNameCSS  = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app);
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
 			$folder = $this->appData->newFolder($app);
158 158
 		}
159 159
 
160
-		$lockKey = $webDir . '/' . $fileNameSCSS;
160
+		$lockKey = $webDir.'/'.$fileNameSCSS;
161 161
 
162 162
 		if (!$this->lockingCache->add($lockKey, 'locked!', 120)) {
163 163
 			$retry = 0;
@@ -166,7 +166,7 @@  discard block
 block discarded – undo
166 166
 				if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
167 167
 					// Inject icons vars css if any
168 168
 					$this->lockingCache->remove($lockKey);
169
-					$this->logger->debug('SCSSCacher: ' .$lockKey.' is now available after '.$retry.'s. Moving on...', ['app' => 'core']);
169
+					$this->logger->debug('SCSSCacher: '.$lockKey.' is now available after '.$retry.'s. Moving on...', ['app' => 'core']);
170 170
 					return $this->injectCssVariablesIfAny();
171 171
 				}
172 172
 				$this->logger->debug('SCSSCacher: scss cache file locked for '.$lockKey, ['app' => 'core']);
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
 	 * @return boolean
215 215
 	 */
216 216
 	private function isCached(string $fileNameCSS, string $app) {
217
-		$key = $this->config->getSystemValue('version') . '/' . $app . '/' . $fileNameCSS;
217
+		$key = $this->config->getSystemValue('version').'/'.$app.'/'.$fileNameCSS;
218 218
 
219 219
 		// If the file mtime is more recent than our cached one,
220 220
 		// let's consider the file is properly cached
@@ -236,13 +236,13 @@  discard block
 block discarded – undo
236 236
 		try {
237 237
 			$cachedFile = $folder->getFile($fileNameCSS);
238 238
 			if ($cachedFile->getSize() > 0) {
239
-				$depFileName = $fileNameCSS . '.deps';
240
-				$deps        = $this->depsCache->get($folder->getName() . '-' . $depFileName);
239
+				$depFileName = $fileNameCSS.'.deps';
240
+				$deps        = $this->depsCache->get($folder->getName().'-'.$depFileName);
241 241
 				if ($deps === null) {
242 242
 					$depFile = $folder->getFile($depFileName);
243 243
 					$deps    = $depFile->getContent();
244 244
 					// Set to memcache for next run
245
-					$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
245
+					$this->depsCache->set($folder->getName().'-'.$depFileName, $deps);
246 246
 				}
247 247
 				$deps = json_decode($deps, true);
248 248
 
@@ -291,7 +291,7 @@  discard block
 block discarded – undo
291 291
 		$scss = new Compiler();
292 292
 		$scss->setImportPaths([
293 293
 			$path,
294
-			$this->serverRoot . '/core/css/'
294
+			$this->serverRoot.'/core/css/'
295 295
 		]);
296 296
 
297 297
 		// Continue after throw
@@ -311,7 +311,7 @@  discard block
 block discarded – undo
311 311
 			$cachedfile = $folder->newFile($fileNameCSS);
312 312
 		}
313 313
 
314
-		$depFileName = $fileNameCSS . '.deps';
314
+		$depFileName = $fileNameCSS.'.deps';
315 315
 		try {
316 316
 			$depFile = $folder->getFile($depFileName);
317 317
 		} catch (NotFoundException $e) {
@@ -321,11 +321,11 @@  discard block
 block discarded – undo
321 321
 		// Compile
322 322
 		try {
323 323
 			$compiledScss = $scss->compile(
324
-				'$webroot: \'' . $this->getRoutePrefix() . '\';' .
325
-				$this->getInjectedVariables() .
326
-				'@import "variables.scss";' .
327
-				'@import "functions.scss";' .
328
-				'@import "' . $fileNameSCSS . '";');
324
+				'$webroot: \''.$this->getRoutePrefix().'\';'.
325
+				$this->getInjectedVariables().
326
+				'@import "variables.scss";'.
327
+				'@import "functions.scss";'.
328
+				'@import "'.$fileNameSCSS.'";');
329 329
 		} catch (ParserException $e) {
330 330
 			$this->logger->logException($e, ['app' => 'core']);
331 331
 
@@ -337,9 +337,9 @@  discard block
 block discarded – undo
337 337
 
338 338
 		// Gzip file
339 339
 		try {
340
-			$gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
340
+			$gzipFile = $folder->getFile($fileNameCSS.'.gzip'); # Safari doesn't like .gz
341 341
 		} catch (NotFoundException $e) {
342
-			$gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
342
+			$gzipFile = $folder->newFile($fileNameCSS.'.gzip'); # Safari doesn't like .gz
343 343
 		}
344 344
 
345 345
 		try {
@@ -347,13 +347,13 @@  discard block
 block discarded – undo
347 347
 			$cachedfile->putContent($data);
348 348
 			$deps = json_encode($scss->getParsedFiles());
349 349
 			$depFile->putContent($deps);
350
-			$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
350
+			$this->depsCache->set($folder->getName().'-'.$depFileName, $deps);
351 351
 			$gzipFile->putContent(gzencode($data, 9));
352
-			$this->logger->debug('SCSSCacher: ' . $webDir . '/' . $fileNameSCSS . ' compiled and successfully cached', ['app' => 'core']);
352
+			$this->logger->debug('SCSSCacher: '.$webDir.'/'.$fileNameSCSS.' compiled and successfully cached', ['app' => 'core']);
353 353
 
354 354
 			return true;
355 355
 		} catch (NotPermittedException $e) {
356
-			$this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS);
356
+			$this->logger->error('SCSSCacher: unable to cache: '.$fileNameSCSS);
357 357
 
358 358
 			return false;
359 359
 		}
@@ -379,7 +379,7 @@  discard block
 block discarded – undo
379 379
 				try {
380 380
 					$file->delete();
381 381
 				} catch (NotPermittedException $e) {
382
-					$this->logger->logException($e, ['message' => 'SCSSCacher: unable to delete file: ' . $file->getName()]);
382
+					$this->logger->logException($e, ['message' => 'SCSSCacher: unable to delete file: '.$file->getName()]);
383 383
 				}
384 384
 			}
385 385
 		}
@@ -396,7 +396,7 @@  discard block
 block discarded – undo
396 396
 		}
397 397
 		$variables = '';
398 398
 		foreach ($this->defaults->getScssVariables() as $key => $value) {
399
-			$variables .= '$' . $key . ': ' . $value . ' !default;';
399
+			$variables .= '$'.$key.': '.$value.' !default;';
400 400
 		}
401 401
 
402 402
 		// check for valid variables / otherwise fall back to defaults
@@ -419,7 +419,7 @@  discard block
 block discarded – undo
419 419
 	 */
420 420
 	private function rebaseUrls(string $css, string $webDir): string {
421 421
 		$re    = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
422
-		$subst = 'url(\'' . $webDir . '/$1\')';
422
+		$subst = 'url(\''.$webDir.'/$1\')';
423 423
 
424 424
 		return preg_replace($re, $subst, $css);
425 425
 	}
@@ -448,12 +448,12 @@  discard block
 block discarded – undo
448 448
 	 * @return string
449 449
 	 */
450 450
 	private function prependBaseurlPrefix(string $cssFile): string {
451
-		return substr(md5($this->urlGenerator->getBaseUrl() . $this->getRoutePrefix()), 0, 4) . '-' . $cssFile;
451
+		return substr(md5($this->urlGenerator->getBaseUrl().$this->getRoutePrefix()), 0, 4).'-'.$cssFile;
452 452
 	}
453 453
 
454 454
 	private function getRoutePrefix() {
455 455
 		$frontControllerActive = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
456
-		$prefix = \OC::$WEBROOT . '/index.php';
456
+		$prefix = \OC::$WEBROOT.'/index.php';
457 457
 		if ($frontControllerActive) {
458 458
 			$prefix = \OC::$WEBROOT;
459 459
 		}
@@ -469,11 +469,11 @@  discard block
 block discarded – undo
469 469
 	private function prependVersionPrefix(string $cssFile, string $appId): string {
470 470
 		$appVersion = \OC_App::getAppVersion($appId);
471 471
 		if ($appVersion !== '0') {
472
-			return substr(md5($appVersion), 0, 4) . '-' . $cssFile;
472
+			return substr(md5($appVersion), 0, 4).'-'.$cssFile;
473 473
 		}
474 474
 		$coreVersion = \OC_Util::getVersionString();
475 475
 
476
-		return substr(md5($coreVersion), 0, 4) . '-' . $cssFile;
476
+		return substr(md5($coreVersion), 0, 4).'-'.$cssFile;
477 477
 	}
478 478
 
479 479
 	/**
@@ -491,10 +491,10 @@  discard block
 block discarded – undo
491 491
 			$appDirectoryPath = explode($appName, $path)[1];
492 492
 			// Remove the webroot
493 493
 
494
-			return str_replace($webRoot, '', $appWebPath . $appDirectoryPath);
494
+			return str_replace($webRoot, '', $appWebPath.$appDirectoryPath);
495 495
 		}
496 496
 
497
-		return $webRoot . substr($path, strlen($serverRoot));
497
+		return $webRoot.substr($path, strlen($serverRoot));
498 498
 	}
499 499
 
500 500
 	/**
Please login to merge, or discard this patch.