Passed
Pull Request — master (#77)
by Daniel
61:17
created

ThemesService   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Test Coverage

Coverage 66.39%

Importance

Changes 12
Bugs 0 Features 0
Metric Value
wmc 43
eloc 114
c 12
b 0
f 0
dl 0
loc 302
ccs 79
cts 119
cp 0.6639
rs 8.96

14 Methods

Rating   Name   Duplication   Size   Complexity  
A publishCustomTheme() 0 25 4
A depublishCustomTheme() 0 17 3
A getNewCustomThemes() 0 16 4
B copyTheme() 0 27 7
A publishSystemTheme() 0 20 3
B getThemesFolder() 0 26 7
A __construct() 0 5 1
A publishTheme() 0 17 2
A getThemesUrl() 0 6 2
A getThemesPath() 0 6 2
A getSystemThemes() 0 4 2
A assertValidTheme() 0 13 3
A getThemes() 0 3 1
A getCustomThemes() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like ThemesService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ThemesService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * CMS Pico - Create websites using Pico CMS for Nextcloud.
4
 *
5
 * @copyright Copyright (c) 2017, Maxence Lange (<[email protected]>)
6
 * @copyright Copyright (c) 2019, Daniel Rudolf (<[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
declare(strict_types=1);
25
26
namespace OCA\CMSPico\Service;
27
28
use OCA\CMSPico\AppInfo\Application;
29
use OCA\CMSPico\Exceptions\ThemeAlreadyExistsException;
30
use OCA\CMSPico\Exceptions\ThemeNotCompatibleException;
31
use OCA\CMSPico\Exceptions\ThemeNotFoundException;
32
use OCA\CMSPico\Files\FolderInterface;
33
use OCA\CMSPico\Files\LocalFolder;
34
use OCA\CMSPico\Model\Theme;
35
use OCP\Files\AlreadyExistsException;
36
use OCP\Files\NotFoundException;
37
38
class ThemesService
39
{
40
	/** @var ConfigService */
41
	private $configService;
42
43
	/** @var FileService */
44
	private $fileService;
45
46
	/** @var MiscService */
47
	private $miscService;
48
49
	/** @var bool */
50
	private $renewedETag = false;
51
52
	/**
53
	 * ThemesService constructor.
54
	 *
55
	 * @param ConfigService $configService
56
	 * @param FileService   $fileService
57
	 * @param MiscService   $miscService
58
	 */
59 1
	public function __construct(ConfigService $configService, FileService $fileService, MiscService $miscService)
60
	{
61 1
		$this->configService = $configService;
62 1
		$this->fileService = $fileService;
63 1
		$this->miscService = $miscService;
64 1
	}
65
66
	/**
67
	 * @param string $themeName
68
	 *
69
	 * @throws ThemeNotFoundException
70
	 * @throws ThemeNotCompatibleException
71
	 */
72 6
	public function assertValidTheme(string $themeName)
73
	{
74 6
		$themes = $this->getThemes();
75
76 6
		if (!isset($themes[$themeName])) {
77 1
			throw new ThemeNotFoundException();
78
		}
79
80 6
		if (!$themes[$themeName]['compat']) {
81
			throw new ThemeNotCompatibleException(
82
				$themeName,
83
				$themes[$themeName]['compatReason'],
84
				$themes[$themeName]['compatReasonData']
85
			);
86
		}
87 6
	}
88
89
	/**
90
	 * @return array[]
91
	 */
92 6
	public function getThemes(): array
93
	{
94 6
		return $this->getSystemThemes() + $this->getCustomThemes();
95
	}
96
97
	/**
98
	 * @return array[]
99
	 */
100 6
	public function getSystemThemes(): array
101
	{
102 6
		$json = $this->configService->getAppValue(ConfigService::SYSTEM_THEMES);
103 6
		return $json ? json_decode($json, true) : [];
104
	}
105
106
	/**
107
	 * @return array[]
108
	 */
109 6
	public function getCustomThemes(): array
110
	{
111 6
		$json = $this->configService->getAppValue(ConfigService::CUSTOM_THEMES);
112 6
		return $json ? json_decode($json, true) : [];
113
	}
114
115
	/**
116
	 * @return string[]
117
	 */
118 1
	public function getNewCustomThemes(): array
119
	{
120 1
		$customThemesFolder = $this->fileService->getAppDataFolder(PicoService::DIR_THEMES);
121 1
		$customThemesFolder->sync(FolderInterface::SYNC_SHALLOW);
122
123 1
		$currentThemes = $this->getThemes();
124
125 1
		$newCustomThemes = [];
126 1
		foreach ($customThemesFolder as $themeFolder) {
127 1
			$themeName = $themeFolder->getName();
128 1
			if ($themeFolder->isFolder() && !isset($currentThemes[$themeName])) {
129 1
				$newCustomThemes[] = $themeName;
130
			}
131
		}
132
133 1
		return $newCustomThemes;
134
	}
135
136
	/**
137
	 * @param string $themeName
138
	 *
139
	 * @return Theme
140
	 * @throws ThemeNotFoundException
141
	 * @throws ThemeAlreadyExistsException
142
	 */
143
	public function publishSystemTheme(string $themeName): Theme
144
	{
145
		if (!$themeName) {
146
			throw new ThemeNotFoundException();
147
		}
148
149
		$systemThemesFolder = $this->fileService->getSystemFolder(PicoService::DIR_THEMES);
150
		$systemThemesFolder->sync(FolderInterface::SYNC_SHALLOW);
151
152
		try {
153
			$systemThemeFolder = $systemThemesFolder->getFolder($themeName);
154
		} catch (NotFoundException $e) {
155
			throw new ThemeNotFoundException();
156
		}
157
158
		$themes = $this->getSystemThemes();
159
		$themes[$themeName] = $this->publishTheme($systemThemeFolder, Theme::TYPE_SYSTEM);
160
		$this->configService->setAppValue(ConfigService::SYSTEM_THEMES, json_encode($themes));
161
162
		return $themes[$themeName];
163
	}
164
165
	/**
166
	 * @param string $themeName
167
	 *
168
	 * @return Theme
169
	 * @throws ThemeNotFoundException
170
	 * @throws ThemeAlreadyExistsException
171
	 */
172 1
	public function publishCustomTheme(string $themeName): Theme
173
	{
174 1
		if (!$themeName) {
175
			throw new ThemeNotFoundException();
176
		}
177
178 1
		$systemThemes = $this->getSystemThemes();
179 1
		if (isset($systemThemes[$themeName])) {
180
			throw new ThemeAlreadyExistsException();
181
		}
182
183 1
		$appDataThemesFolder = $this->fileService->getAppDataFolder(PicoService::DIR_THEMES);
184 1
		$appDataThemesFolder->sync(FolderInterface::SYNC_SHALLOW);
185
186
		try {
187 1
			$appDataThemeFolder = $appDataThemesFolder->getFolder($themeName);
188
		} catch (NotFoundException $e) {
189
			throw new ThemeNotFoundException();
190
		}
191
192 1
		$themes = $this->getCustomThemes();
193 1
		$themes[$themeName] = $this->publishTheme($appDataThemeFolder, Theme::TYPE_CUSTOM);
194 1
		$this->configService->setAppValue(ConfigService::CUSTOM_THEMES, json_encode($themes));
195
196 1
		return $themes[$themeName];
197
	}
198
199
	/**
200
	 * @param FolderInterface $themeSourceFolder
201
	 * @param int             $themeType
202
	 *
203
	 * @return Theme
204
	 * @throws ThemeAlreadyExistsException
205
	 */
206 1
	private function publishTheme(FolderInterface $themeSourceFolder, int $themeType): Theme
207
	{
208 1
		$publicThemesFolder = $this->getThemesFolder(true);
209
210 1
		$themeName = $themeSourceFolder->getName();
211 1
		$themeSourceFolder->sync();
212
213
		try {
214 1
			$publicThemesFolder->getFolder($themeName);
215
			throw new ThemeAlreadyExistsException();
216 1
		} catch (NotFoundException $e) {
217
			// in fact we want the theme not to exist yet
218
		}
219
220
		/** @var LocalFolder $themeFolder */
221 1
		$themeFolder = $themeSourceFolder->copy($publicThemesFolder);
222 1
		return new Theme($themeFolder, $themeType);
223
	}
224
225
	/**
226
	 * @param string $themeName
227
	 *
228
	 * @throws ThemeNotFoundException
229
	 */
230 1
	public function depublishCustomTheme(string $themeName)
231
	{
232 1
		if (!$themeName) {
233
			throw new ThemeNotFoundException();
234
		}
235
236 1
		$publicThemesFolder = $this->getThemesFolder();
237
238
		try {
239 1
			$publicThemesFolder->getFolder($themeName)->delete();
240
		} catch (NotFoundException $e) {
241
			throw new ThemeNotFoundException();
242
		}
243
244 1
		$customThemes = $this->getCustomThemes();
245 1
		unset($customThemes[$themeName]);
246 1
		$this->configService->setAppValue(ConfigService::CUSTOM_THEMES, json_encode($customThemes));
247 1
	}
248
249
	/**
250
	 * @param string $baseThemeName
251
	 * @param string $themeName
252
	 *
253
	 * @return Theme
254
	 * @throws ThemeNotFoundException
255
	 * @throws ThemeAlreadyExistsException
256
	 */
257
	public function copyTheme(string $baseThemeName, string $themeName)
258
	{
259
		if (!$baseThemeName || !$themeName) {
260
			throw new ThemeNotFoundException();
261
		}
262
263
		$systemThemes = $this->getSystemThemes();
264
		$customThemes = $this->getCustomThemes();
265
266
		if (isset($systemThemes[$themeName]) || isset($customThemes[$themeName])) {
267
			throw new ThemeAlreadyExistsException();
268
		}
269
270
		try {
271
			$baseThemeFolder = $this->getThemesFolder()->getFolder($baseThemeName);
272
		} catch (NotFoundException $e) {
273
			throw new ThemeNotFoundException();
274
		}
275
276
		try {
277
			$appDataThemesFolder = $this->fileService->getAppDataFolder(PicoService::DIR_THEMES);
278
			$baseThemeFolder->copy($appDataThemesFolder, $themeName);
279
		} catch (AlreadyExistsException $e) {
280
			throw new ThemeAlreadyExistsException();
281
		}
282
283
		return $this->publishCustomTheme($themeName);
284
	}
285
286
	/**
287
	 * @param bool $renewETag
288
	 * @param bool $forceRenewETag
289
	 *
290
	 * @return LocalFolder
291
	 */
292 3
	public function getThemesFolder(bool $renewETag = false, bool $forceRenewETag = false): LocalFolder
293
	{
294 3
		$themesBaseFolder = $this->fileService->getPublicFolder(PicoService::DIR_THEMES);
295
296
		/** @var LocalFolder $themesFolder */
297 3
		$themesFolder = null;
298
299 3
		$themesETag = $this->configService->getAppValue(ConfigService::THEMES_ETAG);
300 3
		if ($themesETag) {
301 3
			$themesFolder = $themesBaseFolder->getFolder($themesETag);
302
		}
303
304 3
		if (($renewETag && !$this->renewedETag) || $forceRenewETag || !$themesFolder) {
305 1
			$themesETag = $this->miscService->getRandom();
306
307 1
			if ($themesFolder) {
308 1
				$themesFolder = $themesFolder->rename($themesETag);
309
			} else {
310
				$themesFolder = $themesBaseFolder->newFolder($themesETag);
311
			}
312
313 1
			$this->configService->setAppValue(ConfigService::THEMES_ETAG, $themesETag);
314 1
			$this->renewedETag = true;
315
		}
316
317 3
		return $themesFolder->fakeRoot();
318
	}
319
320
	/**
321
	 * @return string
322
	 */
323 2
	public function getThemesPath(): string
324
	{
325 2
		$appPath = Application::getAppPath() . '/';
326 2
		$themesPath = 'appdata_public/' . PicoService::DIR_THEMES . '/';
327 2
		$themesETag = $this->configService->getAppValue(ConfigService::THEMES_ETAG);
328 2
		return $appPath . $themesPath . ($themesETag ? $themesETag . '/' : '');
329
	}
330
331
	/**
332
	 * @return string
333
	 */
334 2
	public function getThemesUrl(): string
335
	{
336 2
		$appWebPath = Application::getAppWebPath() . '/';
337 2
		$themesPath = 'appdata_public/' . PicoService::DIR_THEMES . '/';
338 2
		$themesETag = $this->configService->getAppValue(ConfigService::THEMES_ETAG);
339 2
		return $appWebPath . $themesPath . ($themesETag ? $themesETag . '/' : '');
340
	}
341
}
342