Completed
Push — master ( 59ed89...26050b )
by Daniel
13:12 queued 13:12
created

Website   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Test Coverage

Coverage 66.97%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 40
eloc 104
dl 0
loc 281
ccs 71
cts 106
cp 0.6697
rs 9.2
c 5
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getOptionsJSON() 0 3 1
A getGroupAccess() 0 3 1
A setGroupAccess() 0 10 4
A assertValidSite() 0 13 4
A __construct() 0 11 1
A assertValidOwner() 0 11 4
A getWebsitePath() 0 6 2
A assertOwnedBy() 0 4 2
A getData() 0 5 1
A getTimeZone() 0 4 2
A getWebsiteFolder() 0 27 5
A assertValidName() 0 9 3
B assertValidPath() 0 33 7
A getWebsiteUrl() 0 8 2
A assertValidTheme() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Website 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 Website, 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\Model;
27
28
use OCA\CMSPico\AppInfo\Application;
29
use OCA\CMSPico\Exceptions\ThemeNotCompatibleException;
30
use OCA\CMSPico\Exceptions\ThemeNotFoundException;
31
use OCA\CMSPico\Exceptions\WebsiteForeignOwnerException;
32
use OCA\CMSPico\Exceptions\WebsiteInvalidDataException;
33
use OCA\CMSPico\Exceptions\WebsiteInvalidFilesystemException;
34
use OCA\CMSPico\Exceptions\WebsiteInvalidOwnerException;
35
use OCA\CMSPico\Files\StorageFolder;
36
use OCA\CMSPico\Service\MiscService;
37
use OCA\CMSPico\Service\ThemesService;
38
use OCA\CMSPico\Service\WebsitesService;
39
use OCP\Files\InvalidPathException;
40
use OCP\Files\NotFoundException;
41
use OCP\IConfig;
42
use OCP\IGroupManager;
43
use OCP\IURLGenerator;
44
use OCP\IUserManager;
45
use function OCA\CMSPico\t;
46
47
class Website extends WebsiteCore
48
{
49
	/** @var int */
50
	public const SITE_LENGTH_MIN = 3;
51
52
	/** @var int */
53
	public const SITE_LENGTH_MAX = 255;
54
55
	/** @var string */
56
	public const SITE_REGEX = '^[a-z0-9][a-z0-9_-]+[a-z0-9]$';
57
58
	/** @var int */
59
	public const NAME_LENGTH_MIN = 3;
60
61
	/** @var int */
62
	public const NAME_LENGTH_MAX = 255;
63
64
	/** @var IConfig */
65
	private $config;
66
67
	/** @var IUserManager */
68
	private $userManager;
69
70
	/** @var IGroupManager */
71
	private $groupManager;
72
73
	/** @var IURLGenerator */
74
	private $urlGenerator;
75
76
	/** @var WebsitesService */
77
	private $websitesService;
78
79
	/** @var ThemesService */
80
	private $themesService;
81
82
	/** @var MiscService */
83
	private $miscService;
84
85
	/** @var StorageFolder */
86
	private $folder;
87
88
	/**
89
	 * Website constructor.
90
	 *
91
	 * @param array|null $data
92
	 */
93 15
	public function __construct($data = null)
94
	{
95 15
		$this->config = \OC::$server->getConfig();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getConfig() has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

95
		$this->config = /** @scrutinizer ignore-deprecated */ \OC::$server->getConfig();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
96 15
		$this->userManager = \OC::$server->getUserManager();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserManager() has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

96
		$this->userManager = /** @scrutinizer ignore-deprecated */ \OC::$server->getUserManager();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
97 15
		$this->groupManager = \OC::$server->getGroupManager();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getGroupManager() has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

97
		$this->groupManager = /** @scrutinizer ignore-deprecated */ \OC::$server->getGroupManager();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
98 15
		$this->urlGenerator = \OC::$server->getURLGenerator();
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getURLGenerator() has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

98
		$this->urlGenerator = /** @scrutinizer ignore-deprecated */ \OC::$server->getURLGenerator();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
99 15
		$this->websitesService = \OC::$server->query(WebsitesService::class);
0 ignored issues
show
Deprecated Code introduced by
The function OC\ServerContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

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

99
		$this->websitesService = /** @scrutinizer ignore-deprecated */ \OC::$server->query(WebsitesService::class);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
100 15
		$this->themesService = \OC::$server->query(ThemesService::class);
0 ignored issues
show
Deprecated Code introduced by
The function OC\ServerContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

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

100
		$this->themesService = /** @scrutinizer ignore-deprecated */ \OC::$server->query(ThemesService::class);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
101 15
		$this->miscService = \OC::$server->query(MiscService::class);
0 ignored issues
show
Deprecated Code introduced by
The function OC\ServerContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

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

101
		$this->miscService = /** @scrutinizer ignore-deprecated */ \OC::$server->query(MiscService::class);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
102
103 15
		parent::__construct($data);
104 15
	}
105
106
	/**
107
	 * @return string
108
	 */
109 5
	public function getOptionsJSON(): string
110
	{
111 5
		return json_encode($this->getOptions());
112
	}
113
114
	/**
115
	 * @return string
116
	 */
117 13
	public function getTimeZone(): string
118
	{
119 13
		$serverTimeZone = date_default_timezone_get() ?: 'UTC';
120 13
		return $this->config->getUserValue($this->getUserId(), 'core', 'timezone', $serverTimeZone);
121
	}
122
123
	/**
124
	 * @param string[] $groupAccess
125
	 *
126
	 * @return $this
127
	 */
128
	public function setGroupAccess(array $groupAccess): self
129
	{
130
		foreach ($groupAccess as $group) {
131
			if (!$this->groupManager->groupExists($group)) {
132
				throw new \UnexpectedValueException();
133
			}
134
		}
135
136
		$this->setOption('group_access', $groupAccess ?: null);
137
		return $this;
138
	}
139
140
	/**
141
	 * @return string[]
142
	 */
143 1
	public function getGroupAccess(): array
144
	{
145 1
		return $this->getOption('group_access') ?? [];
146
	}
147
148
	/**
149
	 * {@inheritDoc}
150
	 */
151 10
	public function getData(): array
152
	{
153 10
		$data = parent::getData();
154 10
		$data['timezone'] = $this->getTimeZone();
155 10
		return $data;
156
	}
157
158
	/**
159
	 * @throws WebsiteInvalidOwnerException
160
	 */
161 12
	public function assertValidOwner(): void
162
	{
163 12
		$user = $this->userManager->get($this->getUserId());
164 12
		if ($user === null) {
165
			throw new WebsiteInvalidOwnerException($this->getSite());
166
		}
167 12
		if (!$user->isEnabled()) {
168
			throw new WebsiteInvalidOwnerException($this->getSite());
169
		}
170 12
		if (!$this->websitesService->isUserAllowed($this->getUserId())) {
171
			throw new WebsiteInvalidOwnerException($this->getSite());
172
		}
173 12
	}
174
175
	/**
176
	 * @throws WebsiteInvalidDataException
177
	 */
178 5
	public function assertValidName(): void
179
	{
180 5
		if (strlen($this->getName()) < self::NAME_LENGTH_MIN) {
181
			$error = t('The name of the website must be longer.');
182
			throw new WebsiteInvalidDataException($this->getSite(), 'name', $error);
183
		}
184 5
		if (strlen($this->getName()) > self::NAME_LENGTH_MAX) {
185
			$error = t('The name of the website is too long.');
186
			throw new WebsiteInvalidDataException($this->getSite(), 'name', $error);
187
		}
188 5
	}
189
190
	/**
191
	 * @throws WebsiteInvalidDataException
192
	 */
193 5
	public function assertValidSite(): void
194
	{
195 5
		if (strlen($this->getSite()) < self::SITE_LENGTH_MIN) {
196
			$error = t('The identifier of the website must be longer.');
197
			throw new WebsiteInvalidDataException($this->getSite(), 'site', $error);
198
		}
199 5
		if (strlen($this->getSite()) > self::SITE_LENGTH_MAX) {
200
			$error = t('The identifier of the website is too long.');
201
			throw new WebsiteInvalidDataException($this->getSite(), 'site', $error);
202
		}
203 5
		if (preg_match('/' . self::SITE_REGEX . '/', $this->getSite()) !== 1) {
204
			$error = t('The identifier of the website can only contain lowercase alpha numeric chars.');
205
			throw new WebsiteInvalidDataException($this->getSite(), 'site', $error);
206
		}
207 5
	}
208
209
	/**
210
	 * @throws WebsiteInvalidDataException
211
	 */
212 5
	public function assertValidPath(): void
213
	{
214
		try {
215 5
			$path = $this->miscService->normalizePath($this->getPath());
216 5
			if ($path === '') {
217 5
				throw new InvalidPathException();
218
			}
219
		} catch (InvalidPathException $e) {
220
			$error = t('The path of the website is invalid.');
221
			throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
222
		}
223
224 5
		$userFolder = new StorageFolder(\OC::$server->getUserFolder($this->getUserId()));
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserFolder() has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

224
		$userFolder = new StorageFolder(/** @scrutinizer ignore-deprecated */ \OC::$server->getUserFolder($this->getUserId()));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Bug introduced by
It seems like OC::server->getUserFolder($this->getUserId()) can also be of type null; however, parameter $folder of OCA\CMSPico\Files\StorageFolder::__construct() does only seem to accept OCP\Files\Folder, maybe add an additional type check? ( Ignorable by Annotation )

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

224
		$userFolder = new StorageFolder(/** @scrutinizer ignore-type */ \OC::$server->getUserFolder($this->getUserId()));
Loading history...
225
226
		try {
227 5
			$websiteBaseFolder = $userFolder->getFolder(dirname($path));
228
229
			try {
230 5
				$websiteFolder = $websiteBaseFolder->getFolder(basename($path));
231
232 2
				if (!$websiteFolder->isLocal()) {
233
					$error = t('The website\'s path is stored on a non-local storage.');
234 2
					throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
235
				}
236 3
			} catch (NotFoundException $e) {
237 3
				if (!$websiteBaseFolder->isLocal()) {
238
					$error = t('The website\'s path is stored on a non-local storage.');
239 5
					throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
240
				}
241
			}
242
		} catch (InvalidPathException | NotFoundException $e) {
243
			$error = t('Parent folder of the website\'s path not found.');
244
			throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
245
		}
246 5
	}
247
248
	/**
249
	 * @throws ThemeNotFoundException
250
	 * @throws ThemeNotCompatibleException
251
	 */
252 5
	public function assertValidTheme(): void
253
	{
254 5
		$this->themesService->assertValidTheme($this->getTheme());
255 5
	}
256
257
	/**
258
	 * @param string $userId
259
	 *
260
	 * @throws WebsiteForeignOwnerException
261
	 */
262 2
	public function assertOwnedBy(string $userId): void
263
	{
264 2
		if ($this->getUserId() !== $userId) {
265
			throw new WebsiteForeignOwnerException($this->getSite());
266
		}
267 2
	}
268
269
	/**
270
	 * @return StorageFolder
271
	 * @throws WebsiteInvalidFilesystemException
272
	 */
273 9
	public function getWebsiteFolder(): StorageFolder
274
	{
275 9
		if ($this->folder !== null) {
276
			try {
277
				// NC doesn't guarantee that mounts are present for the whole request lifetime
278
				// for example, if you call \OC\Files\Utils\Scanner::scan(), all mounts are reset
279
				// this makes OCNode instances, which rely on mounts of different users than the current, unusable
280
				// by calling OCFolder::get('') we can detect this situation and re-init the required mounts
281 9
				$this->folder->get('');
282
			} catch (\Exception $e) {
283
				$this->folder = null;
284
			}
285
		}
286
287 9
		if ($this->folder === null) {
288
			try {
289 9
				$ocUserFolder = \OC::$server->getUserFolder($this->getUserId());
0 ignored issues
show
Deprecated Code introduced by
The function OC\Server::getUserFolder() has been deprecated: 20.0.0 ( Ignorable by Annotation )

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

289
				$ocUserFolder = /** @scrutinizer ignore-deprecated */ \OC::$server->getUserFolder($this->getUserId());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
290 9
				$userFolder = new StorageFolder($ocUserFolder);
0 ignored issues
show
Bug introduced by
It seems like $ocUserFolder can also be of type null; however, parameter $folder of OCA\CMSPico\Files\StorageFolder::__construct() does only seem to accept OCP\Files\Folder, maybe add an additional type check? ( Ignorable by Annotation )

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

290
				$userFolder = new StorageFolder(/** @scrutinizer ignore-type */ $ocUserFolder);
Loading history...
291
292 9
				$websiteFolder = $userFolder->getFolder($this->getPath());
293 9
				$this->folder = $websiteFolder->fakeRoot();
294
			} catch (InvalidPathException | NotFoundException $e) {
295
				throw new WebsiteInvalidFilesystemException($this->getSite(), $e);
296
			}
297
		}
298
299 9
		return $this->folder;
300
	}
301
302
	/**
303
	 * @return string
304
	 * @throws WebsiteInvalidFilesystemException
305
	 */
306 5
	public function getWebsitePath(): string
307
	{
308
		try {
309 5
			return $this->getWebsiteFolder()->getLocalPath() . '/';
310
		} catch (InvalidPathException | NotFoundException $e) {
311
			throw new WebsiteInvalidFilesystemException($this->getSite(), $e);
312
		}
313
	}
314
315
	/**
316
	 * @param bool $proxyRequest
317
	 *
318
	 * @return string
319
	 */
320 5
	public function getWebsiteUrl(bool $proxyRequest = false): string
321
	{
322 5
		if (!$proxyRequest) {
323 5
			$route = Application::APP_NAME . '.Pico.getPage';
324 5
			$parameters = [ 'site' => $this->getSite(), 'page' => '' ];
325 5
			return $this->urlGenerator->linkToRoute($route, $parameters) . '/';
326
		} else {
327
			return \OC::$WEBROOT . '/sites/' . urlencode($this->getSite()) . '/';
328
		}
329
	}
330
}
331