Passed
Push — master ( e324c9...3ac048 )
by Roeland
19:27 queued 12s
created

AccessibilityController::filterOutRule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <[email protected]>
7
 * @copyright Copyright (c) 2019 Janis Köhr <[email protected]>
8
 *
9
 * @author Alexey Pyltsyn <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Janis Köhr <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author John Molakvoæ (skjnldsv) <[email protected]>
14
 * @author Julius Härtl <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Thomas Citharel <[email protected]>
17
 *
18
 * @license GNU AGPL version 3 or any later version
19
 *
20
 * This program is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License as
22
 * published by the Free Software Foundation, either version 3 of the
23
 * License, or (at your option) any later version.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
32
 *
33
 */
34
35
namespace OCA\Accessibility\Controller;
36
37
use OC\Template\IconsCacher;
38
use OCP\App\IAppManager;
39
use OCP\AppFramework\Controller;
40
use OCP\AppFramework\Http;
41
use OCP\AppFramework\Http\DataDisplayResponse;
42
use OCP\AppFramework\Utility\ITimeFactory;
43
use OCP\IConfig;
44
use OCP\ILogger;
45
use OCP\IRequest;
46
use OCP\IURLGenerator;
47
use OCP\IUserManager;
48
use OCP\IUserSession;
49
use ScssPhp\ScssPhp\Compiler;
50
use ScssPhp\ScssPhp\Exception\ParserException;
51
use ScssPhp\ScssPhp\Formatter\Crunched;
52
53
class AccessibilityController extends Controller {
54
55
	/** @var string */
56
	protected $appName;
57
58
	/** @var string */
59
	protected $serverRoot;
60
61
	/** @var IConfig */
62
	private $config;
63
64
	/** @var IUserManager */
65
	private $userManager;
66
67
	/** @var ILogger */
68
	private $logger;
69
70
	/** @var IURLGenerator */
71
	private $urlGenerator;
72
73
	/** @var ITimeFactory */
74
	protected $timeFactory;
75
76
	/** @var IUserSession */
77
	private $userSession;
78
79
	/** @var IAppManager */
80
	private $appManager;
81
82
	/** @var IconsCacher */
83
	protected $iconsCacher;
84
85
	/** @var \OC_Defaults */
86
	private $defaults;
87
88
	/** @var null|string */
89
	private $injectedVariables;
90
91
	/**
92
	 * Account constructor.
93
	 *
94
	 * @param string $appName
95
	 * @param IRequest $request
96
	 * @param IConfig $config
97
	 * @param IUserManager $userManager
98
	 * @param ILogger $logger
99
	 * @param IURLGenerator $urlGenerator
100
	 * @param ITimeFactory $timeFactory
101
	 * @param IUserSession $userSession
102
	 * @param IAppManager $appManager
103
	 * @param \OC_Defaults $defaults
104
	 */
105
	public function __construct(string $appName,
106
								IRequest $request,
107
								IConfig $config,
108
								IUserManager $userManager,
109
								ILogger $logger,
110
								IURLGenerator $urlGenerator,
111
								ITimeFactory $timeFactory,
112
								IUserSession $userSession,
113
								IAppManager $appManager,
114
								IconsCacher $iconsCacher,
115
								\OC_Defaults $defaults) {
116
		parent::__construct($appName, $request);
117
		$this->appName      = $appName;
118
		$this->config       = $config;
119
		$this->userManager  = $userManager;
120
		$this->logger       = $logger;
121
		$this->urlGenerator = $urlGenerator;
122
		$this->timeFactory  = $timeFactory;
123
		$this->userSession  = $userSession;
124
		$this->appManager   = $appManager;
125
		$this->iconsCacher  = $iconsCacher;
126
		$this->defaults     = $defaults;
127
128
		$this->serverRoot = \OC::$SERVERROOT;
129
		$this->appRoot    = $this->appManager->getAppPath($this->appName);
0 ignored issues
show
Bug Best Practice introduced by
The property appRoot does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
130
	}
131
132
	/**
133
	 * @NoAdminRequired
134
	 * @NoCSRFRequired
135
	 * @NoSameSiteCookieRequired
136
	 *
137
	 * @return DataDisplayResponse
138
	 */
139
	public function getCss(): DataDisplayResponse {
140
		$css        = '';
141
		$imports    = '';
142
		$userValues = $this->getUserValues();
143
144
		foreach ($userValues as $key => $scssFile) {
145
			if ($scssFile !== false) {
146
				if ($scssFile === 'highcontrast' && in_array('dark', $userValues)) {
147
					$scssFile .= 'dark';
148
				}
149
				$imports .= '@import "' . $scssFile . '";';
150
			}
151
		}
152
153
		if ($imports !== '') {
154
			$scss = new Compiler();
155
			$scss->setImportPaths([
156
				$this->appRoot . '/css/',
157
				$this->serverRoot . '/core/css/'
158
			]);
159
160
			// Continue after throw
161
			$scss->setIgnoreErrors(true);
162
			$scss->setFormatter(Crunched::class);
163
164
			// Import theme, variables and compile css4 variables
165
			try {
166
				$css .= $scss->compile(
167
					$imports .
168
					$this->getInjectedVariables() .
169
					'@import "variables.scss";' .
170
					'@import "css-variables.scss";'
171
				);
172
			} catch (ParserException $e) {
173
				$this->logger->error($e->getMessage(), ['app' => 'core']);
174
			}
175
		}
176
177
		// We don't want to override vars with url since path is different
178
		$css = $this->filterOutRule('/--[a-z-:]+url\([^;]+\)/mi', $css);
179
180
		// Rebase all urls
181
		$appWebRoot = substr($this->appRoot, strlen($this->serverRoot) - strlen(\OC::$WEBROOT));
182
		$css        = $this->rebaseUrls($css, $appWebRoot . '/css');
183
184
		if (in_array('dark', $userValues) && $this->iconsCacher->getCachedList() && $this->iconsCacher->getCachedList()->getSize() > 0) {
185
			$iconsCss = $this->invertSvgIconsColor($this->iconsCacher->getCachedList()->getContent());
186
			$css = $css . $iconsCss;
187
		}
188
189
		$response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
190
191
		// Set cache control
192
		$ttl = 31536000;
193
		$response->addHeader('Cache-Control', 'max-age=' . $ttl . ', immutable');
194
		$expires = new \DateTime();
195
		$expires->setTimestamp($this->timeFactory->getTime());
196
		$expires->add(new \DateInterval('PT' . $ttl . 'S'));
197
		$response->addHeader('Expires', $expires->format(\DateTime::RFC1123));
198
		$response->addHeader('Pragma', 'cache');
199
200
		// store current cache hash
201
		$this->config->setUserValue($this->userSession->getUser()->getUID(), $this->appName, 'icons-css', md5($css));
202
203
		return $response;
204
	}
205
206
	/**
207
	 * Return an array with the user theme & font settings
208
	 *
209
	 * @return array
210
	 */
211
	private function getUserValues(): array {
212
		$userTheme = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'theme', false);
213
		$userFont  = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'font', false);
214
		$userHighContrast = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'highcontrast', false);
215
216
		return [$userTheme, $userHighContrast, $userFont];
217
	}
218
219
	/**
220
	 * Remove all matches from the $rule regex
221
	 *
222
	 * @param string $rule regex to match
223
	 * @param string $css string to parse
224
	 * @return string
225
	 */
226
	private function filterOutRule(string $rule, string $css): string {
227
		return preg_replace($rule, '', $css);
228
	}
229
230
	/**
231
	 * Add the correct uri prefix to make uri valid again
232
	 *
233
	 * @param string $css
234
	 * @param string $webDir
235
	 * @return string
236
	 */
237
	private function rebaseUrls(string $css, string $webDir): string {
238
		$re    = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
239
		$subst = 'url(\'' . $webDir . '/$1\')';
240
241
		return preg_replace($re, $subst, $css);
242
	}
243
244
	/**
245
	 * Remove all matches from the $rule regex
246
	 *
247
	 * @param string $css string to parse
248
	 * @return string
249
	 */
250
	private function invertSvgIconsColor(string $css) {
251
		return str_replace(
252
			['color=000&', 'color=fff&', 'color=***&'],
253
			['color=***&', 'color=000&', 'color=fff&'],
254
			str_replace(
255
				['color=000000&', 'color=ffffff&', 'color=******&'],
256
				['color=******&', 'color=000000&', 'color=ffffff&'],
257
				$css
258
			)
259
		);
260
	}
261
262
	/**
263
	 * @return string SCSS code for variables from OC_Defaults
264
	 */
265
	private function getInjectedVariables(): string {
266
		if ($this->injectedVariables !== null) {
267
			return $this->injectedVariables;
268
		}
269
		$variables = '';
270
		foreach ($this->defaults->getScssVariables() as $key => $value) {
271
			$variables .= '$' . $key . ': ' . $value . ';';
272
		}
273
274
		// check for valid variables / otherwise fall back to defaults
275
		try {
276
			$scss = new Compiler();
277
			$scss->compile($variables);
278
			$this->injectedVariables = $variables;
279
		} catch (ParserException $e) {
280
			$this->logger->logException($e, ['app' => 'core']);
281
		}
282
		return $variables;
283
	}
284
}
285