Passed
Push — master ( 6ad7f3...61c388 )
by Morris
14:10
created

IconsCacher::colorizeSvg()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
declare (strict_types = 1);
3
/**
4
 * @copyright Copyright (c) 2018, John Molakvoæ ([email protected])
5
 *
6
 * @author John Molakvoæ (skjnldsv) <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
namespace OC\Template;
26
27
use OCP\Files\IAppData;
28
use OCP\Files\NotFoundException;
29
use OCP\Files\SimpleFS\ISimpleFolder;
30
use OCP\Files\SimpleFS\ISimpleFile;
31
use OCP\ILogger;
32
use OCP\IURLGenerator;
33
use OC\Files\AppData\Factory;
34
35
class IconsCacher {
36
37
	/** @var ILogger */
38
	protected $logger;
39
40
	/** @var IAppData */
41
	protected $appData;
42
43
	/** @var ISimpleFolder */
44
	private $folder;
45
46
	/** @var IURLGenerator */
47
	protected $urlGenerator;
48
49
	/** @var string */
50
	private $iconVarRE = '/--(icon-[a-zA-Z0-9-]+):\s?url\(["\']?([a-zA-Z0-9-_\~\/\.\?\=\:\;\+\,]+)[^;]+;/m';
51
52
	/** @var string */
53
	private $fileName = 'icons-vars.css';
54
55
	private $iconList = 'icons-list.template';
56
57
	/**
58
	 * @param ILogger $logger
59
	 * @param Factory $appDataFactory
60
	 * @param IURLGenerator $urlGenerator
61
	 * @throws \OCP\Files\NotPermittedException
62
	 */
63
	public function __construct(ILogger $logger,
64
								Factory $appDataFactory,
65
								IURLGenerator $urlGenerator) {
66
		$this->logger       = $logger;
67
		$this->appData      = $appDataFactory->get('css');
68
		$this->urlGenerator = $urlGenerator;
69
70
		try {
71
			$this->folder = $this->appData->getFolder('icons');
72
		} catch (NotFoundException $e) {
73
			$this->folder = $this->appData->newFolder('icons');
74
		}
75
	}
76
77
	private function getIconsFromCss(string $css): array {
78
		preg_match_all($this->iconVarRE, $css, $matches, PREG_SET_ORDER);
79
		$icons = [];
80
		foreach ($matches as $icon) {
81
			$icons[$icon[1]] = $icon[2];
82
		}
83
84
		return $icons;
85
	}
86
87
	/**
88
	 * @param string $css
89
	 * @return string
90
	 * @throws NotFoundException
91
	 * @throws \OCP\Files\NotPermittedException
92
	 */
93
	public function setIconsCss(string $css): string {
94
95
		$cachedFile = $this->getCachedList();
96
		if (!$cachedFile) {
97
			$currentData = '';
98
			$cachedFile = $this->folder->newFile($this->iconList);
99
		} else {
100
			$currentData = $cachedFile->getContent();
101
		}
102
103
		$cachedVarsCssFile = $this->getCachedCSS();
104
		if (!$cachedVarsCssFile) {
105
			$cachedVarsCssFile = $this->folder->newFile($this->fileName);
106
		}
107
108
		$icons = $this->getIconsFromCss($currentData . $css);
109
110
		$data = '';
111
		$list = '';
112
		foreach ($icons as $icon => $url) {
113
			$list .= "--$icon: url('$url');";
114
			list($location,$color) = $this->parseUrl($url);
115
			$svg = file_get_contents($location);
116
			if ($svg === false) {
117
				$this->logger->debug('Failed to get icon file ' . $location);
118
				$data .= "--$icon: url('$url');";
119
				continue;
120
			}
121
			$encode = base64_encode($this->colorizeSvg($svg, $color));
122
			$data .= '--' . $icon . ': url(data:image/svg+xml;base64,' . $encode . ');';
123
		}
124
125
		if (\strlen($data) > 0 && \strlen($list) > 0) {
126
			$data = ":root {\n$data\n}";
127
			$cachedVarsCssFile->putContent($data);
128
			$list = ":root {\n$list\n}";
129
			$cachedFile->putContent($list);
130
		}
131
132
		return preg_replace($this->iconVarRE, '', $css);
133
	}
134
135
	/**
136
	 * @param $url
137
	 * @return array
138
	 */
139
	private function parseUrl($url): array {
140
		$location = '';
141
		$color = '';
142
		$base = $this->getRoutePrefix() . '/svg/';
143
		$cleanUrl = \substr($url, \strlen($base));
144
		if (\strpos($url, $base . 'core') === 0) {
145
			$cleanUrl = \substr($cleanUrl, \strlen('core'), \strpos($cleanUrl, '?')-\strlen('core'));
146
			$parts = \explode('/', $cleanUrl);
147
			$color = \array_pop($parts);
148
			$cleanUrl = \implode('/', $parts);
149
			$location = \OC::$SERVERROOT . '/core/img/' . $cleanUrl . '.svg';
150
		} elseif (\strpos($url, $base) === 0) {
151
			$cleanUrl = \substr($cleanUrl, 0, \strpos($cleanUrl, '?'));
152
			$parts = \explode('/', $cleanUrl);
153
			$app = \array_shift($parts);
154
			$color = \array_pop($parts);
155
			$cleanUrl = \implode('/', $parts);
156
			$location = \OC_App::getAppPath($app) . '/img/' . $cleanUrl . '.svg';
0 ignored issues
show
Bug introduced by
Are you sure OC_App::getAppPath($app) of type string|false can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
			$location = /** @scrutinizer ignore-type */ \OC_App::getAppPath($app) . '/img/' . $cleanUrl . '.svg';
Loading history...
157
			if ($app === 'settings') {
158
				$location = \OC::$SERVERROOT . '/settings/img/' . $cleanUrl . '.svg';
159
			}
160
		}
161
		return [
162
			$location,
163
			$color
164
		];
165
	}
166
167
	/**
168
	 * @param $svg
169
	 * @param $color
170
	 * @return string
171
	 */
172
	public function colorizeSvg($svg, $color): string {
173
		// add fill (fill is not present on black elements)
174
		$fillRe = '/<((circle|rect|path)((?!fill)[a-z0-9 =".\-#():;])+)\/>/mi';
175
		$svg = preg_replace($fillRe, '<$1 fill="#' . $color . '"/>', $svg);
176
177
		// replace any fill or stroke colors
178
		$svg = preg_replace('/stroke="#([a-z0-9]{3,6})"/mi', 'stroke="#' . $color . '"', $svg);
179
		$svg = preg_replace('/fill="#([a-z0-9]{3,6})"/mi', 'fill="#' . $color . '"', $svg);
180
		return $svg;
181
	}
182
183
	private function getRoutePrefix() {
184
		$frontControllerActive = (\OC::$server->getConfig()->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
185
		$prefix = \OC::$WEBROOT . '/index.php';
186
		if ($frontControllerActive) {
187
			$prefix = \OC::$WEBROOT;
188
		}
189
		return $prefix;
190
	}
191
192
	/**
193
	 * Get icons css file
194
	 * @return ISimpleFile|boolean
195
	 */
196
	public function getCachedCSS() {
197
		try {
198
			return $this->folder->getFile($this->fileName);
199
		} catch (NotFoundException $e) {
200
			return false;
201
		}
202
	}
203
204
	/**
205
	 * Get icon-vars list template
206
	 * @return ISimpleFile|boolean
207
	 */
208
	public function getCachedList() {
209
		try {
210
			return $this->folder->getFile($this->iconList);
211
		} catch (NotFoundException $e) {
212
			return false;
213
		}
214
	}
215
216
	public function injectCss() {
217
		// Only inject once
218
		foreach (\OC_Util::$headers as $header) {
219
			if (
220
				array_key_exists('attributes', $header) &&
221
				array_key_exists('href', $header['attributes']) &&
222
				strpos($header['attributes']['href'], $this->fileName) !== false) {
223
				return;
224
			}
225
		}
226
		$linkToCSS = $this->urlGenerator->linkToRoute('core.Css.getCss', ['appName' => 'icons', 'fileName' => $this->fileName]);
227
		\OC_Util::addHeader('link', ['rel' => 'stylesheet', 'href' => $linkToCSS], null, true);
228
	}
229
230
}