Website::assertOwnedBy()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 3
cp 0.6667
rs 10
cc 2
nc 2
nop 1
crap 2.1481
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\Files\StorageUserFolder;
37
use OCA\CMSPico\Service\MiscService;
38
use OCA\CMSPico\Service\ThemesService;
39
use OCA\CMSPico\Service\WebsitesService;
40
use OCP\Files\InvalidPathException;
41
use OCP\Files\NotFoundException;
42
use OCP\IConfig;
43
use OCP\IGroupManager;
44
use OCP\IURLGenerator;
45
use OCP\IUserManager;
46
use function OCA\CMSPico\t;
47
48
class Website extends WebsiteCore
49
{
50
	/** @var int */
51
	public const SITE_LENGTH_MIN = 3;
52
53
	/** @var int */
54
	public const SITE_LENGTH_MAX = 255;
55
56
	/** @var string */
57
	public const SITE_REGEX = '^[a-z0-9][a-z0-9_-]+[a-z0-9]$';
58
59
	/** @var int */
60
	public const NAME_LENGTH_MIN = 3;
61
62
	/** @var int */
63
	public const NAME_LENGTH_MAX = 255;
64
65
	/** @var IConfig */
66
	private $config;
67
68
	/** @var IUserManager */
69
	private $userManager;
70
71
	/** @var IGroupManager */
72
	private $groupManager;
73
74
	/** @var IURLGenerator */
75
	private $urlGenerator;
76
77
	/** @var WebsitesService */
78
	private $websitesService;
79
80
	/** @var ThemesService */
81
	private $themesService;
82
83
	/** @var MiscService */
84
	private $miscService;
85
86
	/** @var StorageFolder */
87
	private $folder;
88
89
	/**
90
	 * Website constructor.
91
	 *
92
	 * @param array|null $data
93
	 */
94 15
	public function __construct($data = null)
95
	{
96 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

96
		$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...
97 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

97
		$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...
98 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

98
		$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...
99 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

99
		$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...
100 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

100
		$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...
101 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

101
		$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...
102 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

102
		$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...
103
104 15
		parent::__construct($data);
105 15
	}
106
107
	/**
108
	 * @return string
109
	 */
110 5
	public function getOptionsJSON(): string
111
	{
112 5
		return json_encode($this->getOptions());
113
	}
114
115
	/**
116
	 * @return string
117
	 */
118 13
	public function getTimeZone(): string
119
	{
120 13
		$serverTimeZone = date_default_timezone_get() ?: 'UTC';
121 13
		return $this->config->getUserValue($this->getUserId(), 'core', 'timezone', $serverTimeZone);
122
	}
123
124
	/**
125
	 * @param string[] $groupAccess
126
	 *
127
	 * @return $this
128
	 */
129
	public function setGroupAccess(array $groupAccess): self
130
	{
131
		foreach ($groupAccess as $group) {
132
			if (!$this->groupManager->groupExists($group)) {
133
				throw new \UnexpectedValueException();
134
			}
135
		}
136
137
		$this->setOption('group_access', $groupAccess ?: null);
138
		return $this;
139
	}
140
141
	/**
142
	 * @return string[]
143
	 */
144 1
	public function getGroupAccess(): array
145
	{
146 1
		return $this->getOption('group_access') ?? [];
147
	}
148
149
	/**
150
	 * {@inheritDoc}
151
	 */
152 10
	public function getData(): array
153
	{
154 10
		$data = parent::getData();
155 10
		$data['timezone'] = $this->getTimeZone();
156 10
		return $data;
157
	}
158
159
	/**
160
	 * @throws WebsiteInvalidOwnerException
161
	 */
162 12
	public function assertValidOwner(): void
163
	{
164 12
		$user = $this->userManager->get($this->getUserId());
165 12
		if ($user === null) {
166
			throw new WebsiteInvalidOwnerException($this->getSite());
167
		}
168 12
		if (!$user->isEnabled()) {
169
			throw new WebsiteInvalidOwnerException($this->getSite());
170
		}
171 12
		if (!$this->websitesService->isUserAllowed($this->getUserId())) {
172
			throw new WebsiteInvalidOwnerException($this->getSite());
173
		}
174 12
	}
175
176
	/**
177
	 * @throws WebsiteInvalidDataException
178
	 */
179 5
	public function assertValidName(): void
180
	{
181 5
		if (strlen($this->getName()) < self::NAME_LENGTH_MIN) {
182
			$error = t('The name of the website must be longer.');
183
			throw new WebsiteInvalidDataException($this->getSite(), 'name', $error);
184
		}
185 5
		if (strlen($this->getName()) > self::NAME_LENGTH_MAX) {
186
			$error = t('The name of the website is too long.');
187
			throw new WebsiteInvalidDataException($this->getSite(), 'name', $error);
188
		}
189 5
	}
190
191
	/**
192
	 * @throws WebsiteInvalidDataException
193
	 */
194 5
	public function assertValidSite(): void
195
	{
196 5
		if (strlen($this->getSite()) < self::SITE_LENGTH_MIN) {
197
			$error = t('The identifier of the website must be longer.');
198
			throw new WebsiteInvalidDataException($this->getSite(), 'site', $error);
199
		}
200 5
		if (strlen($this->getSite()) > self::SITE_LENGTH_MAX) {
201
			$error = t('The identifier of the website is too long.');
202
			throw new WebsiteInvalidDataException($this->getSite(), 'site', $error);
203
		}
204 5
		if (preg_match('/' . self::SITE_REGEX . '/', $this->getSite()) !== 1) {
205
			$error = t('The identifier of the website can only contain lowercase alpha numeric chars.');
206
			throw new WebsiteInvalidDataException($this->getSite(), 'site', $error);
207
		}
208 5
	}
209
210
	/**
211
	 * @throws WebsiteInvalidDataException
212
	 */
213 5
	public function assertValidPath(): void
214
	{
215
		try {
216 5
			$path = $this->miscService->normalizePath($this->getPath());
217 5
			if ($path === '') {
218 5
				throw new InvalidPathException();
219
			}
220
		} catch (InvalidPathException $e) {
221
			$error = t('The path of the website is invalid.');
222
			throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
223
		}
224
225 5
		$userFolder = new StorageUserFolder($this->getUserId());
226
227
		try {
228 5
			$websiteBaseFolder = $userFolder->getFolder(dirname($path));
229
230
			try {
231 5
				$websiteFolder = $websiteBaseFolder->getFolder(basename($path));
232
233 2
				if (!$websiteFolder->isLocal()) {
234
					$error = t('The website\'s path is stored on a non-local storage.');
235 2
					throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
236
				}
237 3
			} catch (NotFoundException $e) {
238 3
				if (!$websiteBaseFolder->isLocal()) {
239
					$error = t('The website\'s path is stored on a non-local storage.');
240 5
					throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
241
				}
242
			}
243
		} catch (InvalidPathException | NotFoundException $e) {
244
			$error = t('Parent folder of the website\'s path not found.');
245
			throw new WebsiteInvalidDataException($this->getSite(), 'path', $error);
246
		}
247 5
	}
248
249
	/**
250
	 * @throws ThemeNotFoundException
251
	 * @throws ThemeNotCompatibleException
252
	 */
253 5
	public function assertValidTheme(): void
254
	{
255 5
		$this->themesService->assertValidTheme($this->getTheme());
256 5
	}
257
258
	/**
259
	 * @param string $userId
260
	 *
261
	 * @throws WebsiteForeignOwnerException
262
	 */
263 2
	public function assertOwnedBy(string $userId): void
264
	{
265 2
		if ($this->getUserId() !== $userId) {
266
			throw new WebsiteForeignOwnerException($this->getSite());
267
		}
268 2
	}
269
270
	/**
271
	 * @return StorageFolder
272
	 * @throws WebsiteInvalidFilesystemException
273
	 */
274 9
	public function getWebsiteFolder(): StorageFolder
275
	{
276 9
		if ($this->folder !== null) {
277
			try {
278
				// NC doesn't guarantee that mounts are present for the whole request lifetime
279
				// for example, if you call \OC\Files\Utils\Scanner::scan(), all mounts are reset
280
				// this makes OCNode instances, which rely on mounts of different users than the current, unusable
281
				// by calling OCFolder::get('') we can detect this situation and re-init the required mounts
282 9
				$this->folder->get('');
283
			} catch (\Exception $e) {
284
				$this->folder = null;
285
			}
286
		}
287
288 9
		if ($this->folder === null) {
289
			try {
290 9
				$userFolder = new StorageUserFolder($this->getUserId());
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