Completed
Pull Request — master (#8999)
by John
15:06
created
lib/private/Template/SCSSCacher.php 2 patches
Spacing   +27 added lines, -27 removed lines patch added patch discarded remove patch
@@ -96,7 +96,7 @@  discard block
 block discarded – undo
96 96
 		$this->defaults = $defaults;
97 97
 		$this->serverRoot = $serverRoot;
98 98
 		$this->cacheFactory = $cacheFactory;
99
-		$this->depsCache = $cacheFactory->createDistributed('SCSS-' . md5($this->urlGenerator->getBaseUrl()));
99
+		$this->depsCache = $cacheFactory->createDistributed('SCSS-'.md5($this->urlGenerator->getBaseUrl()));
100 100
 	}
101 101
 
102 102
 	/**
@@ -109,7 +109,7 @@  discard block
 block discarded – undo
109 109
 	 * @throws NotPermittedException
110 110
 	 */
111 111
 	public function process(string $root, string $file, string $app): bool {
112
-		$path = explode('/', $root . '/' . $file);
112
+		$path = explode('/', $root.'/'.$file);
113 113
 
114 114
 		$fileNameSCSS = array_pop($path);
115 115
 		$fileNameCSS = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app);
@@ -119,13 +119,13 @@  discard block
 block discarded – undo
119 119
 
120 120
 		try {
121 121
 			$folder = $this->appData->getFolder($app);
122
-		} catch(NotFoundException $e) {
122
+		} catch (NotFoundException $e) {
123 123
 			// creating css appdata folder
124 124
 			$folder = $this->appData->newFolder($app);
125 125
 		}
126 126
 
127 127
 
128
-		if(!$this->variablesChanged() && $this->isCached($fileNameCSS, $folder)) {
128
+		if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $folder)) {
129 129
 			return true;
130 130
 		}
131 131
 		return $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
@@ -152,17 +152,17 @@  discard block
 block discarded – undo
152 152
 		try {
153 153
 			$cachedFile = $folder->getFile($fileNameCSS);
154 154
 			if ($cachedFile->getSize() > 0) {
155
-				$depFileName = $fileNameCSS . '.deps';
156
-				$deps = $this->depsCache->get($folder->getName() . '-' . $depFileName);
155
+				$depFileName = $fileNameCSS.'.deps';
156
+				$deps = $this->depsCache->get($folder->getName().'-'.$depFileName);
157 157
 				if ($deps === null) {
158 158
 					$depFile = $folder->getFile($depFileName);
159 159
 					$deps = $depFile->getContent();
160 160
 					//Set to memcache for next run
161
-					$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
161
+					$this->depsCache->set($folder->getName().'-'.$depFileName, $deps);
162 162
 				}
163 163
 				$deps = json_decode($deps, true);
164 164
 
165
-				foreach ((array)$deps as $file=>$mtime) {
165
+				foreach ((array) $deps as $file=>$mtime) {
166 166
 					if (!file_exists($file) || filemtime($file) > $mtime) {
167 167
 						return false;
168 168
 					}
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
 				return true;
171 171
 			}
172 172
 			return false;
173
-		} catch(NotFoundException $e) {
173
+		} catch (NotFoundException $e) {
174 174
 			return false;
175 175
 		}
176 176
 	}
@@ -181,7 +181,7 @@  discard block
 block discarded – undo
181 181
 	 */
182 182
 	private function variablesChanged(): bool {
183 183
 		$injectedVariables = $this->getInjectedVariables();
184
-		if($this->config->getAppValue('core', 'scss.variables') !== md5($injectedVariables)) {
184
+		if ($this->config->getAppValue('core', 'scss.variables') !== md5($injectedVariables)) {
185 185
 			$this->resetCache();
186 186
 			$this->config->setAppValue('core', 'scss.variables', md5($injectedVariables));
187 187
 			return true;
@@ -204,11 +204,11 @@  discard block
 block discarded – undo
204 204
 		$scss = new Compiler();
205 205
 		$scss->setImportPaths([
206 206
 			$path,
207
-			$this->serverRoot . '/core/css/',
207
+			$this->serverRoot.'/core/css/',
208 208
 		]);
209 209
 		// Continue after throw
210 210
 		$scss->setIgnoreErrors(true);
211
-		if($this->config->getSystemValue('debug')) {
211
+		if ($this->config->getSystemValue('debug')) {
212 212
 			// Debug mode
213 213
 			$scss->setFormatter(Expanded::class);
214 214
 			$scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
@@ -219,11 +219,11 @@  discard block
 block discarded – undo
219 219
 
220 220
 		try {
221 221
 			$cachedfile = $folder->getFile($fileNameCSS);
222
-		} catch(NotFoundException $e) {
222
+		} catch (NotFoundException $e) {
223 223
 			$cachedfile = $folder->newFile($fileNameCSS);
224 224
 		}
225 225
 
226
-		$depFileName = $fileNameCSS . '.deps';
226
+		$depFileName = $fileNameCSS.'.deps';
227 227
 		try {
228 228
 			$depFile = $folder->getFile($depFileName);
229 229
 		} catch (NotFoundException $e) {
@@ -233,19 +233,19 @@  discard block
 block discarded – undo
233 233
 		// Compile
234 234
 		try {
235 235
 			$compiledScss = $scss->compile(
236
-				'@import "variables.scss";' .
237
-				$this->getInjectedVariables() .
236
+				'@import "variables.scss";'.
237
+				$this->getInjectedVariables().
238 238
 				'@import "'.$fileNameSCSS.'";');
239
-		} catch(ParserException $e) {
239
+		} catch (ParserException $e) {
240 240
 			$this->logger->error($e, ['app' => 'core']);
241 241
 			return false;
242 242
 		}
243 243
 
244 244
 		// Gzip file
245 245
 		try {
246
-			$gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
246
+			$gzipFile = $folder->getFile($fileNameCSS.'.gzip'); # Safari doesn't like .gz
247 247
 		} catch (NotFoundException $e) {
248
-			$gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
248
+			$gzipFile = $folder->newFile($fileNameCSS.'.gzip'); # Safari doesn't like .gz
249 249
 		}
250 250
 
251 251
 		try {
@@ -253,12 +253,12 @@  discard block
 block discarded – undo
253 253
 			$cachedfile->putContent($data);
254 254
 			$deps = json_encode($scss->getParsedFiles());
255 255
 			$depFile->putContent($deps);
256
-			$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
256
+			$this->depsCache->set($folder->getName().'-'.$depFileName, $deps);
257 257
 			$gzipFile->putContent(gzencode($data, 9));
258 258
 			$this->logger->debug('SCSSCacher: '.$webDir.'/'.$fileNameSCSS.' compiled and successfully cached', ['app' => 'core']);
259 259
 			return true;
260
-		} catch(NotPermittedException $e) {
261
-			$this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS);
260
+		} catch (NotPermittedException $e) {
261
+			$this->logger->error('SCSSCacher: unable to cache: '.$fileNameSCSS);
262 262
 			return false;
263 263
 		}
264 264
 	}
@@ -287,7 +287,7 @@  discard block
 block discarded – undo
287 287
 		}
288 288
 		$variables = '';
289 289
 		foreach ($this->defaults->getScssVariables() as $key => $value) {
290
-			$variables .= '$' . $key . ': ' . $value . ';';
290
+			$variables .= '$'.$key.': '.$value.';';
291 291
 		}
292 292
 
293 293
 		// check for valid variables / otherwise fall back to defaults
@@ -335,7 +335,7 @@  discard block
 block discarded – undo
335 335
 	 */
336 336
 	private function prependBaseurlPrefix(string $cssFile): string {
337 337
 		$frontendController = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
338
-		return substr(md5($this->urlGenerator->getBaseUrl() . $frontendController), 0, 4) . '-' . $cssFile;
338
+		return substr(md5($this->urlGenerator->getBaseUrl().$frontendController), 0, 4).'-'.$cssFile;
339 339
 	}
340 340
 
341 341
 	/**
@@ -347,10 +347,10 @@  discard block
 block discarded – undo
347 347
 	private function prependVersionPrefix(string $cssFile, string $appId): string {
348 348
 		$appVersion = \OC_App::getAppVersion($appId);
349 349
 		if ($appVersion !== '0') {
350
-			return substr(md5($appVersion), 0, 4) . '-' . $cssFile;
350
+			return substr(md5($appVersion), 0, 4).'-'.$cssFile;
351 351
 		}
352 352
 		$coreVersion = \OC_Util::getVersionString();
353
-		return substr(md5($coreVersion), 0, 4) . '-' . $cssFile;
353
+		return substr(md5($coreVersion), 0, 4).'-'.$cssFile;
354 354
 	}
355 355
 
356 356
 	/**
@@ -363,7 +363,7 @@  discard block
 block discarded – undo
363 363
 	 */
364 364
 	private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string {
365 365
 		// Detect if path is within server root AND if path is within an app path
366
-		if ( strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
366
+		if (strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
367 367
 			// Get the file path within the app directory
368 368
 			$appDirectoryPath = explode($appName, $path)[1];
369 369
 			// Remove the webroot
Please login to merge, or discard this patch.
Indentation   +325 added lines, -325 removed lines patch added patch discarded remove patch
@@ -46,329 +46,329 @@
 block discarded – undo
46 46
 
47 47
 class SCSSCacher {
48 48
 
49
-	/** @var ILogger */
50
-	protected $logger;
51
-
52
-	/** @var IAppData */
53
-	protected $appData;
54
-
55
-	/** @var IURLGenerator */
56
-	protected $urlGenerator;
57
-
58
-	/** @var IConfig */
59
-	protected $config;
60
-
61
-	/** @var \OC_Defaults */
62
-	private $defaults;
63
-
64
-	/** @var string */
65
-	protected $serverRoot;
66
-
67
-	/** @var ICache */
68
-	protected $depsCache;
69
-
70
-	/** @var null|string */
71
-	private $injectedVariables;
72
-
73
-	/** @var ICacheFactory */
74
-	private $cacheFactory;
75
-
76
-	/**
77
-	 * @param ILogger $logger
78
-	 * @param Factory $appDataFactory
79
-	 * @param IURLGenerator $urlGenerator
80
-	 * @param IConfig $config
81
-	 * @param \OC_Defaults $defaults
82
-	 * @param string $serverRoot
83
-	 * @param ICacheFactory $cacheFactory
84
-	 */
85
-	public function __construct(ILogger $logger,
86
-								Factory $appDataFactory,
87
-								IURLGenerator $urlGenerator,
88
-								IConfig $config,
89
-								\OC_Defaults $defaults,
90
-								$serverRoot,
91
-								ICacheFactory $cacheFactory) {
92
-		$this->logger = $logger;
93
-		$this->appData = $appDataFactory->get('css');
94
-		$this->urlGenerator = $urlGenerator;
95
-		$this->config = $config;
96
-		$this->defaults = $defaults;
97
-		$this->serverRoot = $serverRoot;
98
-		$this->cacheFactory = $cacheFactory;
99
-		$this->depsCache = $cacheFactory->createDistributed('SCSS-' . md5($this->urlGenerator->getBaseUrl()));
100
-	}
101
-
102
-	/**
103
-	 * Process the caching process if needed
104
-	 *
105
-	 * @param string $root Root path to the nextcloud installation
106
-	 * @param string $file
107
-	 * @param string $app The app name
108
-	 * @return boolean
109
-	 * @throws NotPermittedException
110
-	 */
111
-	public function process(string $root, string $file, string $app): bool {
112
-		$path = explode('/', $root . '/' . $file);
113
-
114
-		$fileNameSCSS = array_pop($path);
115
-		$fileNameCSS = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app);
116
-
117
-		$path = implode('/', $path);
118
-		$webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT);
119
-
120
-		try {
121
-			$folder = $this->appData->getFolder($app);
122
-		} catch(NotFoundException $e) {
123
-			// creating css appdata folder
124
-			$folder = $this->appData->newFolder($app);
125
-		}
126
-
127
-
128
-		if(!$this->variablesChanged() && $this->isCached($fileNameCSS, $folder)) {
129
-			return true;
130
-		}
131
-		return $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
132
-	}
133
-
134
-	/**
135
-	 * @param $appName
136
-	 * @param $fileName
137
-	 * @return ISimpleFile
138
-	 */
139
-	public function getCachedCSS(string $appName, string $fileName): ISimpleFile {
140
-		$folder = $this->appData->getFolder($appName);
141
-		$cachedFileName = $this->prependVersionPrefix($this->prependBaseurlPrefix($fileName), $appName);
142
-		return $folder->getFile($cachedFileName);
143
-	}
144
-
145
-	/**
146
-	 * Check if the file is cached or not
147
-	 * @param string $fileNameCSS
148
-	 * @param ISimpleFolder $folder
149
-	 * @return boolean
150
-	 */
151
-	private function isCached(string $fileNameCSS, ISimpleFolder $folder) {
152
-		try {
153
-			$cachedFile = $folder->getFile($fileNameCSS);
154
-			if ($cachedFile->getSize() > 0) {
155
-				$depFileName = $fileNameCSS . '.deps';
156
-				$deps = $this->depsCache->get($folder->getName() . '-' . $depFileName);
157
-				if ($deps === null) {
158
-					$depFile = $folder->getFile($depFileName);
159
-					$deps = $depFile->getContent();
160
-					//Set to memcache for next run
161
-					$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
162
-				}
163
-				$deps = json_decode($deps, true);
164
-
165
-				foreach ((array)$deps as $file=>$mtime) {
166
-					if (!file_exists($file) || filemtime($file) > $mtime) {
167
-						return false;
168
-					}
169
-				}
170
-				return true;
171
-			}
172
-			return false;
173
-		} catch(NotFoundException $e) {
174
-			return false;
175
-		}
176
-	}
177
-
178
-	/**
179
-	 * Check if the variables file has changed
180
-	 * @return bool
181
-	 */
182
-	private function variablesChanged(): bool {
183
-		$injectedVariables = $this->getInjectedVariables();
184
-		if($this->config->getAppValue('core', 'scss.variables') !== md5($injectedVariables)) {
185
-			$this->resetCache();
186
-			$this->config->setAppValue('core', 'scss.variables', md5($injectedVariables));
187
-			return true;
188
-		}
189
-		return false;
190
-	}
191
-
192
-	/**
193
-	 * Cache the file with AppData
194
-	 *
195
-	 * @param string $path
196
-	 * @param string $fileNameCSS
197
-	 * @param string $fileNameSCSS
198
-	 * @param ISimpleFolder $folder
199
-	 * @param string $webDir
200
-	 * @return boolean
201
-	 * @throws NotPermittedException
202
-	 */
203
-	private function cache(string $path, string $fileNameCSS, string $fileNameSCSS, ISimpleFolder $folder, string $webDir) {
204
-		$scss = new Compiler();
205
-		$scss->setImportPaths([
206
-			$path,
207
-			$this->serverRoot . '/core/css/',
208
-		]);
209
-		// Continue after throw
210
-		$scss->setIgnoreErrors(true);
211
-		if($this->config->getSystemValue('debug')) {
212
-			// Debug mode
213
-			$scss->setFormatter(Expanded::class);
214
-			$scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
215
-		} else {
216
-			// Compression
217
-			$scss->setFormatter(Crunched::class);
218
-		}
219
-
220
-		try {
221
-			$cachedfile = $folder->getFile($fileNameCSS);
222
-		} catch(NotFoundException $e) {
223
-			$cachedfile = $folder->newFile($fileNameCSS);
224
-		}
225
-
226
-		$depFileName = $fileNameCSS . '.deps';
227
-		try {
228
-			$depFile = $folder->getFile($depFileName);
229
-		} catch (NotFoundException $e) {
230
-			$depFile = $folder->newFile($depFileName);
231
-		}
232
-
233
-		// Compile
234
-		try {
235
-			$compiledScss = $scss->compile(
236
-				'@import "variables.scss";' .
237
-				$this->getInjectedVariables() .
238
-				'@import "'.$fileNameSCSS.'";');
239
-		} catch(ParserException $e) {
240
-			$this->logger->error($e, ['app' => 'core']);
241
-			return false;
242
-		}
243
-
244
-		// Gzip file
245
-		try {
246
-			$gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
247
-		} catch (NotFoundException $e) {
248
-			$gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
249
-		}
250
-
251
-		try {
252
-			$data = $this->rebaseUrls($compiledScss, $webDir);
253
-			$cachedfile->putContent($data);
254
-			$deps = json_encode($scss->getParsedFiles());
255
-			$depFile->putContent($deps);
256
-			$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
257
-			$gzipFile->putContent(gzencode($data, 9));
258
-			$this->logger->debug('SCSSCacher: '.$webDir.'/'.$fileNameSCSS.' compiled and successfully cached', ['app' => 'core']);
259
-			return true;
260
-		} catch(NotPermittedException $e) {
261
-			$this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS);
262
-			return false;
263
-		}
264
-	}
265
-
266
-	/**
267
-	 * Reset scss cache by deleting all generated css files
268
-	 * We need to regenerate all files when variables change
269
-	 */
270
-	public function resetCache() {
271
-		$this->injectedVariables = null;
272
-		$this->cacheFactory->createDistributed('SCSS-')->clear();
273
-		$appDirectory = $this->appData->getDirectoryListing();
274
-		foreach ($appDirectory as $folder) {
275
-			foreach ($folder->getDirectoryListing() as $file) {
276
-				$file->delete();
277
-			}
278
-		}
279
-	}
280
-
281
-	/**
282
-	 * @return string SCSS code for variables from OC_Defaults
283
-	 */
284
-	private function getInjectedVariables(): string {
285
-		if ($this->injectedVariables !== null) {
286
-			return $this->injectedVariables;
287
-		}
288
-		$variables = '';
289
-		foreach ($this->defaults->getScssVariables() as $key => $value) {
290
-			$variables .= '$' . $key . ': ' . $value . ';';
291
-		}
292
-
293
-		// check for valid variables / otherwise fall back to defaults
294
-		try {
295
-			$scss = new Compiler();
296
-			$scss->compile($variables);
297
-			$this->injectedVariables = $variables;
298
-		} catch (ParserException $e) {
299
-			$this->logger->error($e, ['app' => 'core']);
300
-		}
301
-
302
-		return $variables;
303
-	}
304
-
305
-	/**
306
-	 * Add the correct uri prefix to make uri valid again
307
-	 * @param string $css
308
-	 * @param string $webDir
309
-	 * @return string
310
-	 */
311
-	private function rebaseUrls(string $css, string $webDir): string {
312
-		$re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
313
-		$subst = 'url(\''.$webDir.'/$1\')';
314
-		return preg_replace($re, $subst, $css);
315
-	}
316
-
317
-	/**
318
-	 * Return the cached css file uri
319
-	 * @param string $appName the app name
320
-	 * @param string $fileName
321
-	 * @return string
322
-	 */
323
-	public function getCachedSCSS(string $appName, string $fileName): string {
324
-		$tmpfileLoc = explode('/', $fileName);
325
-		$fileName = array_pop($tmpfileLoc);
326
-		$fileName = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName);
327
-
328
-		return substr($this->urlGenerator->linkToRoute('core.Css.getCss', ['fileName' => $fileName, 'appName' => $appName]), strlen(\OC::$WEBROOT) + 1);
329
-	}
330
-
331
-	/**
332
-	 * Prepend hashed base url to the css file
333
-	 * @param string $cssFile
334
-	 * @return string
335
-	 */
336
-	private function prependBaseurlPrefix(string $cssFile): string {
337
-		$frontendController = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
338
-		return substr(md5($this->urlGenerator->getBaseUrl() . $frontendController), 0, 4) . '-' . $cssFile;
339
-	}
340
-
341
-	/**
342
-	 * Prepend hashed app version hash
343
-	 * @param string $cssFile
344
-	 * @param string $appId
345
-	 * @return string
346
-	 */
347
-	private function prependVersionPrefix(string $cssFile, string $appId): string {
348
-		$appVersion = \OC_App::getAppVersion($appId);
349
-		if ($appVersion !== '0') {
350
-			return substr(md5($appVersion), 0, 4) . '-' . $cssFile;
351
-		}
352
-		$coreVersion = \OC_Util::getVersionString();
353
-		return substr(md5($coreVersion), 0, 4) . '-' . $cssFile;
354
-	}
355
-
356
-	/**
357
-	 * Get WebDir root
358
-	 * @param string $path the css file path
359
-	 * @param string $appName the app name
360
-	 * @param string $serverRoot the server root path
361
-	 * @param string $webRoot the nextcloud installation root path
362
-	 * @return string the webDir
363
-	 */
364
-	private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string {
365
-		// Detect if path is within server root AND if path is within an app path
366
-		if ( strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
367
-			// Get the file path within the app directory
368
-			$appDirectoryPath = explode($appName, $path)[1];
369
-			// Remove the webroot
370
-			return str_replace($webRoot, '', $appWebPath.$appDirectoryPath);
371
-		}
372
-		return $webRoot.substr($path, strlen($serverRoot));
373
-	}
49
+    /** @var ILogger */
50
+    protected $logger;
51
+
52
+    /** @var IAppData */
53
+    protected $appData;
54
+
55
+    /** @var IURLGenerator */
56
+    protected $urlGenerator;
57
+
58
+    /** @var IConfig */
59
+    protected $config;
60
+
61
+    /** @var \OC_Defaults */
62
+    private $defaults;
63
+
64
+    /** @var string */
65
+    protected $serverRoot;
66
+
67
+    /** @var ICache */
68
+    protected $depsCache;
69
+
70
+    /** @var null|string */
71
+    private $injectedVariables;
72
+
73
+    /** @var ICacheFactory */
74
+    private $cacheFactory;
75
+
76
+    /**
77
+     * @param ILogger $logger
78
+     * @param Factory $appDataFactory
79
+     * @param IURLGenerator $urlGenerator
80
+     * @param IConfig $config
81
+     * @param \OC_Defaults $defaults
82
+     * @param string $serverRoot
83
+     * @param ICacheFactory $cacheFactory
84
+     */
85
+    public function __construct(ILogger $logger,
86
+                                Factory $appDataFactory,
87
+                                IURLGenerator $urlGenerator,
88
+                                IConfig $config,
89
+                                \OC_Defaults $defaults,
90
+                                $serverRoot,
91
+                                ICacheFactory $cacheFactory) {
92
+        $this->logger = $logger;
93
+        $this->appData = $appDataFactory->get('css');
94
+        $this->urlGenerator = $urlGenerator;
95
+        $this->config = $config;
96
+        $this->defaults = $defaults;
97
+        $this->serverRoot = $serverRoot;
98
+        $this->cacheFactory = $cacheFactory;
99
+        $this->depsCache = $cacheFactory->createDistributed('SCSS-' . md5($this->urlGenerator->getBaseUrl()));
100
+    }
101
+
102
+    /**
103
+     * Process the caching process if needed
104
+     *
105
+     * @param string $root Root path to the nextcloud installation
106
+     * @param string $file
107
+     * @param string $app The app name
108
+     * @return boolean
109
+     * @throws NotPermittedException
110
+     */
111
+    public function process(string $root, string $file, string $app): bool {
112
+        $path = explode('/', $root . '/' . $file);
113
+
114
+        $fileNameSCSS = array_pop($path);
115
+        $fileNameCSS = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileNameSCSS)), $app);
116
+
117
+        $path = implode('/', $path);
118
+        $webDir = $this->getWebDir($path, $app, $this->serverRoot, \OC::$WEBROOT);
119
+
120
+        try {
121
+            $folder = $this->appData->getFolder($app);
122
+        } catch(NotFoundException $e) {
123
+            // creating css appdata folder
124
+            $folder = $this->appData->newFolder($app);
125
+        }
126
+
127
+
128
+        if(!$this->variablesChanged() && $this->isCached($fileNameCSS, $folder)) {
129
+            return true;
130
+        }
131
+        return $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
132
+    }
133
+
134
+    /**
135
+     * @param $appName
136
+     * @param $fileName
137
+     * @return ISimpleFile
138
+     */
139
+    public function getCachedCSS(string $appName, string $fileName): ISimpleFile {
140
+        $folder = $this->appData->getFolder($appName);
141
+        $cachedFileName = $this->prependVersionPrefix($this->prependBaseurlPrefix($fileName), $appName);
142
+        return $folder->getFile($cachedFileName);
143
+    }
144
+
145
+    /**
146
+     * Check if the file is cached or not
147
+     * @param string $fileNameCSS
148
+     * @param ISimpleFolder $folder
149
+     * @return boolean
150
+     */
151
+    private function isCached(string $fileNameCSS, ISimpleFolder $folder) {
152
+        try {
153
+            $cachedFile = $folder->getFile($fileNameCSS);
154
+            if ($cachedFile->getSize() > 0) {
155
+                $depFileName = $fileNameCSS . '.deps';
156
+                $deps = $this->depsCache->get($folder->getName() . '-' . $depFileName);
157
+                if ($deps === null) {
158
+                    $depFile = $folder->getFile($depFileName);
159
+                    $deps = $depFile->getContent();
160
+                    //Set to memcache for next run
161
+                    $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
162
+                }
163
+                $deps = json_decode($deps, true);
164
+
165
+                foreach ((array)$deps as $file=>$mtime) {
166
+                    if (!file_exists($file) || filemtime($file) > $mtime) {
167
+                        return false;
168
+                    }
169
+                }
170
+                return true;
171
+            }
172
+            return false;
173
+        } catch(NotFoundException $e) {
174
+            return false;
175
+        }
176
+    }
177
+
178
+    /**
179
+     * Check if the variables file has changed
180
+     * @return bool
181
+     */
182
+    private function variablesChanged(): bool {
183
+        $injectedVariables = $this->getInjectedVariables();
184
+        if($this->config->getAppValue('core', 'scss.variables') !== md5($injectedVariables)) {
185
+            $this->resetCache();
186
+            $this->config->setAppValue('core', 'scss.variables', md5($injectedVariables));
187
+            return true;
188
+        }
189
+        return false;
190
+    }
191
+
192
+    /**
193
+     * Cache the file with AppData
194
+     *
195
+     * @param string $path
196
+     * @param string $fileNameCSS
197
+     * @param string $fileNameSCSS
198
+     * @param ISimpleFolder $folder
199
+     * @param string $webDir
200
+     * @return boolean
201
+     * @throws NotPermittedException
202
+     */
203
+    private function cache(string $path, string $fileNameCSS, string $fileNameSCSS, ISimpleFolder $folder, string $webDir) {
204
+        $scss = new Compiler();
205
+        $scss->setImportPaths([
206
+            $path,
207
+            $this->serverRoot . '/core/css/',
208
+        ]);
209
+        // Continue after throw
210
+        $scss->setIgnoreErrors(true);
211
+        if($this->config->getSystemValue('debug')) {
212
+            // Debug mode
213
+            $scss->setFormatter(Expanded::class);
214
+            $scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
215
+        } else {
216
+            // Compression
217
+            $scss->setFormatter(Crunched::class);
218
+        }
219
+
220
+        try {
221
+            $cachedfile = $folder->getFile($fileNameCSS);
222
+        } catch(NotFoundException $e) {
223
+            $cachedfile = $folder->newFile($fileNameCSS);
224
+        }
225
+
226
+        $depFileName = $fileNameCSS . '.deps';
227
+        try {
228
+            $depFile = $folder->getFile($depFileName);
229
+        } catch (NotFoundException $e) {
230
+            $depFile = $folder->newFile($depFileName);
231
+        }
232
+
233
+        // Compile
234
+        try {
235
+            $compiledScss = $scss->compile(
236
+                '@import "variables.scss";' .
237
+                $this->getInjectedVariables() .
238
+                '@import "'.$fileNameSCSS.'";');
239
+        } catch(ParserException $e) {
240
+            $this->logger->error($e, ['app' => 'core']);
241
+            return false;
242
+        }
243
+
244
+        // Gzip file
245
+        try {
246
+            $gzipFile = $folder->getFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
247
+        } catch (NotFoundException $e) {
248
+            $gzipFile = $folder->newFile($fileNameCSS . '.gzip'); # Safari doesn't like .gz
249
+        }
250
+
251
+        try {
252
+            $data = $this->rebaseUrls($compiledScss, $webDir);
253
+            $cachedfile->putContent($data);
254
+            $deps = json_encode($scss->getParsedFiles());
255
+            $depFile->putContent($deps);
256
+            $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
257
+            $gzipFile->putContent(gzencode($data, 9));
258
+            $this->logger->debug('SCSSCacher: '.$webDir.'/'.$fileNameSCSS.' compiled and successfully cached', ['app' => 'core']);
259
+            return true;
260
+        } catch(NotPermittedException $e) {
261
+            $this->logger->error('SCSSCacher: unable to cache: ' . $fileNameSCSS);
262
+            return false;
263
+        }
264
+    }
265
+
266
+    /**
267
+     * Reset scss cache by deleting all generated css files
268
+     * We need to regenerate all files when variables change
269
+     */
270
+    public function resetCache() {
271
+        $this->injectedVariables = null;
272
+        $this->cacheFactory->createDistributed('SCSS-')->clear();
273
+        $appDirectory = $this->appData->getDirectoryListing();
274
+        foreach ($appDirectory as $folder) {
275
+            foreach ($folder->getDirectoryListing() as $file) {
276
+                $file->delete();
277
+            }
278
+        }
279
+    }
280
+
281
+    /**
282
+     * @return string SCSS code for variables from OC_Defaults
283
+     */
284
+    private function getInjectedVariables(): string {
285
+        if ($this->injectedVariables !== null) {
286
+            return $this->injectedVariables;
287
+        }
288
+        $variables = '';
289
+        foreach ($this->defaults->getScssVariables() as $key => $value) {
290
+            $variables .= '$' . $key . ': ' . $value . ';';
291
+        }
292
+
293
+        // check for valid variables / otherwise fall back to defaults
294
+        try {
295
+            $scss = new Compiler();
296
+            $scss->compile($variables);
297
+            $this->injectedVariables = $variables;
298
+        } catch (ParserException $e) {
299
+            $this->logger->error($e, ['app' => 'core']);
300
+        }
301
+
302
+        return $variables;
303
+    }
304
+
305
+    /**
306
+     * Add the correct uri prefix to make uri valid again
307
+     * @param string $css
308
+     * @param string $webDir
309
+     * @return string
310
+     */
311
+    private function rebaseUrls(string $css, string $webDir): string {
312
+        $re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
313
+        $subst = 'url(\''.$webDir.'/$1\')';
314
+        return preg_replace($re, $subst, $css);
315
+    }
316
+
317
+    /**
318
+     * Return the cached css file uri
319
+     * @param string $appName the app name
320
+     * @param string $fileName
321
+     * @return string
322
+     */
323
+    public function getCachedSCSS(string $appName, string $fileName): string {
324
+        $tmpfileLoc = explode('/', $fileName);
325
+        $fileName = array_pop($tmpfileLoc);
326
+        $fileName = $this->prependVersionPrefix($this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName)), $appName);
327
+
328
+        return substr($this->urlGenerator->linkToRoute('core.Css.getCss', ['fileName' => $fileName, 'appName' => $appName]), strlen(\OC::$WEBROOT) + 1);
329
+    }
330
+
331
+    /**
332
+     * Prepend hashed base url to the css file
333
+     * @param string $cssFile
334
+     * @return string
335
+     */
336
+    private function prependBaseurlPrefix(string $cssFile): string {
337
+        $frontendController = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
338
+        return substr(md5($this->urlGenerator->getBaseUrl() . $frontendController), 0, 4) . '-' . $cssFile;
339
+    }
340
+
341
+    /**
342
+     * Prepend hashed app version hash
343
+     * @param string $cssFile
344
+     * @param string $appId
345
+     * @return string
346
+     */
347
+    private function prependVersionPrefix(string $cssFile, string $appId): string {
348
+        $appVersion = \OC_App::getAppVersion($appId);
349
+        if ($appVersion !== '0') {
350
+            return substr(md5($appVersion), 0, 4) . '-' . $cssFile;
351
+        }
352
+        $coreVersion = \OC_Util::getVersionString();
353
+        return substr(md5($coreVersion), 0, 4) . '-' . $cssFile;
354
+    }
355
+
356
+    /**
357
+     * Get WebDir root
358
+     * @param string $path the css file path
359
+     * @param string $appName the app name
360
+     * @param string $serverRoot the server root path
361
+     * @param string $webRoot the nextcloud installation root path
362
+     * @return string the webDir
363
+     */
364
+    private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string {
365
+        // Detect if path is within server root AND if path is within an app path
366
+        if ( strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
367
+            // Get the file path within the app directory
368
+            $appDirectoryPath = explode($appName, $path)[1];
369
+            // Remove the webroot
370
+            return str_replace($webRoot, '', $appWebPath.$appDirectoryPath);
371
+        }
372
+        return $webRoot.substr($path, strlen($serverRoot));
373
+    }
374 374
 }
Please login to merge, or discard this patch.