Passed
Push — master ( 859941...64f5a2 )
by Joas
12:41 queued 11s
created

AccessibilityController::invertSvgIconsColor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 1
dl 0
loc 8
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
	 * @PublicPage
134
	 * @NoCSRFRequired
135
	 * @NoSameSiteCookieRequired
136
	 *
137
	 * @return DataDisplayResponse
138
	 */
139
	public function getCss(): DataDisplayResponse {
140
		$css        = '';
141
		$imports    = '';
142
		if ($this->userSession->isLoggedIn()) {
143
			$userValues = $this->getUserValues();
144
		} else {
145
			$userValues = ['dark'];
146
		}
147
148
		foreach ($userValues as $key => $scssFile) {
149
			if ($scssFile !== false) {
150
				if ($scssFile === 'highcontrast' && in_array('dark', $userValues)) {
151
					$scssFile .= 'dark';
152
				}
153
				$imports .= '@import "' . $scssFile . '";';
154
			}
155
		}
156
157
		if ($imports !== '') {
158
			$scss = new Compiler();
159
			$scss->setImportPaths([
160
				$this->appRoot . '/css/',
161
				$this->serverRoot . '/core/css/'
162
			]);
163
164
			// Continue after throw
165
			$scss->setIgnoreErrors(true);
166
			$scss->setFormatter(Crunched::class);
167
168
			// Import theme, variables and compile css4 variables
169
			try {
170
				$css .= $scss->compile(
171
					$imports .
172
					$this->getInjectedVariables() .
173
					'@import "variables.scss";' .
174
					'@import "css-variables.scss";'
175
				);
176
			} catch (ParserException $e) {
177
				$this->logger->error($e->getMessage(), ['app' => 'core']);
178
			}
179
		}
180
181
		// We don't want to override vars with url since path is different
182
		$css = $this->filterOutRule('/--[a-z-:]+url\([^;]+\)/mi', $css);
183
184
		// Rebase all urls
185
		$appWebRoot = substr($this->appRoot, strlen($this->serverRoot) - strlen(\OC::$WEBROOT));
186
		$css        = $this->rebaseUrls($css, $appWebRoot . '/css');
187
188
		if (in_array('dark', $userValues) && $this->iconsCacher->getCachedList() && $this->iconsCacher->getCachedList()->getSize() > 0) {
189
			$iconsCss = $this->invertSvgIconsColor($this->iconsCacher->getCachedList()->getContent());
190
			$css = $css . $iconsCss;
191
		}
192
193
		$response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
194
195
		// Set cache control
196
		$ttl = 31536000;
197
		$response->addHeader('Cache-Control', 'max-age=' . $ttl . ', immutable');
198
		$expires = new \DateTime();
199
		$expires->setTimestamp($this->timeFactory->getTime());
200
		$expires->add(new \DateInterval('PT' . $ttl . 'S'));
201
		$response->addHeader('Expires', $expires->format(\DateTime::RFC1123));
202
		$response->addHeader('Pragma', 'cache');
203
204
		// store current cache hash
205
		if ($this->userSession->isLoggedIn()) {
206
			$this->config->setUserValue($this->userSession->getUser()->getUID(), $this->appName, 'icons-css', md5($css));
207
		}
208
209
		return $response;
210
	}
211
212
	/**
213
	 * Return an array with the user theme & font settings
214
	 *
215
	 * @return array
216
	 */
217
	private function getUserValues(): array {
218
		$userTheme = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'theme', false);
219
		$userFont  = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'font', false);
220
		$userHighContrast = $this->config->getUserValue($this->userSession->getUser()->getUID(), $this->appName, 'highcontrast', false);
221
222
		return [$userTheme, $userHighContrast, $userFont];
223
	}
224
225
	/**
226
	 * Remove all matches from the $rule regex
227
	 *
228
	 * @param string $rule regex to match
229
	 * @param string $css string to parse
230
	 * @return string
231
	 */
232
	private function filterOutRule(string $rule, string $css): string {
233
		return preg_replace($rule, '', $css);
234
	}
235
236
	/**
237
	 * Add the correct uri prefix to make uri valid again
238
	 *
239
	 * @param string $css
240
	 * @param string $webDir
241
	 * @return string
242
	 */
243
	private function rebaseUrls(string $css, string $webDir): string {
244
		$re    = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
245
		$subst = 'url(\'' . $webDir . '/$1\')';
246
247
		return preg_replace($re, $subst, $css);
248
	}
249
250
	/**
251
	 * Remove all matches from the $rule regex
252
	 *
253
	 * @param string $css string to parse
254
	 * @return string
255
	 */
256
	private function invertSvgIconsColor(string $css) {
257
		return str_replace(
258
			['color=000&', 'color=fff&', 'color=***&'],
259
			['color=***&', 'color=000&', 'color=fff&'],
260
			str_replace(
261
				['color=000000&', 'color=ffffff&', 'color=******&'],
262
				['color=******&', 'color=000000&', 'color=ffffff&'],
263
				$css
264
			)
265
		);
266
	}
267
268
	/**
269
	 * @return string SCSS code for variables from OC_Defaults
270
	 */
271
	private function getInjectedVariables(): string {
272
		if ($this->injectedVariables !== null) {
273
			return $this->injectedVariables;
274
		}
275
		$variables = '';
276
		foreach ($this->defaults->getScssVariables() as $key => $value) {
277
			$variables .= '$' . $key . ': ' . $value . ';';
278
		}
279
280
		// check for valid variables / otherwise fall back to defaults
281
		try {
282
			$scss = new Compiler();
283
			$scss->compile($variables);
284
			$this->injectedVariables = $variables;
285
		} catch (ParserException $e) {
286
			$this->logger->logException($e, ['app' => 'core']);
287
		}
288
		return $variables;
289
	}
290
}
291