Passed
Pull Request — master (#99)
by Daniel
26:12 queued 03:19
created

PicoService::setupPico()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 16
c 3
b 0
f 0
dl 0
loc 20
ccs 16
cts 16
cp 1
rs 9.7333
cc 1
nc 1
nop 3
crap 1
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\PageInvalidPathException;
30
use OCA\CMSPico\Exceptions\PageNotFoundException;
31
use OCA\CMSPico\Exceptions\PageNotPermittedException;
32
use OCA\CMSPico\Exceptions\PicoRuntimeException;
33
use OCA\CMSPico\Exceptions\ThemeNotCompatibleException;
34
use OCA\CMSPico\Exceptions\ThemeNotFoundException;
35
use OCA\CMSPico\Exceptions\WebsiteInvalidFilesystemException;
36
use OCA\CMSPico\Exceptions\WebsiteNotPermittedException;
37
use OCA\CMSPico\Files\StorageFolder;
38
use OCA\CMSPico\Model\PicoPage;
39
use OCA\CMSPico\Model\Website;
40
use OCA\CMSPico\Pico;
41
use OCP\Files\InvalidPathException;
42
use OCP\Files\NotFoundException;
43
use OCP\Files\NotPermittedException;
44
use OCP\ILogger;
45
46
class PicoService
47
{
48
	/** @var string */
49
	public const DIR_TEMPLATES = 'templates';
50
51
	/** @var string */
52
	public const DIR_CONFIG = 'config';
53
54
	/** @var string */
55
	public const DIR_PLUGINS = 'plugins';
56
57
	/** @var string */
58
	public const DIR_THEMES = 'themes';
59
60
	/** @var string */
61
	public const DIR_CONTENT = 'content';
62
63
	/** @var string */
64
	public const DIR_ASSETS = 'assets';
65
66
	/** @var string */
67
	public const CONTENT_EXT = '.md';
68
69
	/** @var ILogger */
70
	private $logger;
71
72
	/** @var AssetsService */
73
	private $assetsService;
74
75
	/** @var ThemesService */
76
	private $themesService;
77
78
	/** @var PluginsService */
79
	private $pluginsService;
80
81
	/** @var FileService */
82
	private $fileService;
83
84
	/** @var MiscService */
85
	private $miscService;
86
87
	/**
88
	 * PicoService constructor.
89
	 *
90
	 * @param ILogger        $logger
91
	 * @param AssetsService  $assetsService
92
	 * @param ThemesService  $themesService
93
	 * @param PluginsService $pluginsService
94
	 * @param FileService    $fileService
95
	 * @param MiscService    $miscService
96
	 */
97 1
	public function __construct(
98
		ILogger $logger,
99
		AssetsService $assetsService,
100
		ThemesService $themesService,
101
		PluginsService $pluginsService,
102
		FileService $fileService,
103
		MiscService $miscService
104
	) {
105 1
		$this->logger = $logger;
106 1
		$this->assetsService = $assetsService;
107 1
		$this->themesService = $themesService;
108 1
		$this->pluginsService = $pluginsService;
109 1
		$this->fileService = $fileService;
110 1
		$this->miscService = $miscService;
111 1
	}
112
113
	/**
114
	 * @param Website $website
115
	 *
116
	 * @return PicoPage
117
	 * @throws WebsiteInvalidFilesystemException
118
	 * @throws WebsiteNotPermittedException
119
	 * @throws ThemeNotFoundException
120
	 * @throws ThemeNotCompatibleException
121
	 * @throws PageInvalidPathException
122
	 * @throws PageNotFoundException
123
	 * @throws PageNotPermittedException
124
	 * @throws PicoRuntimeException
125
	 */
126 2
	public function getPage(Website $website): PicoPage
127
	{
128
		try {
129 2
			$page = $website->getPage();
130
131 2
			$website->assertViewerAccess(self::DIR_CONTENT . '/' . ($page ?: 'index') . self::CONTENT_EXT);
132
133 2
			$this->themesService->assertValidTheme($website->getTheme());
134
135 2
			$pico = new Pico(
136 2
				$website->getWebsitePath(),
137 2
				$this->getConfigPath(),
138 2
				$this->pluginsService->getPluginsPath(),
139 2
				$this->themesService->getThemesPath(),
140 2
				false
141
			);
142
143
			try {
144 2
				$this->setupPico($website, $pico, $page);
145 2
				$this->loadPicoPlugins($pico);
146
147 2
				$output = $pico->run();
148
			} catch (WebsiteInvalidFilesystemException $e) {
149
				throw $e;
150
			} catch (InvalidPathException | NotFoundException | NotPermittedException $e) {
151
				throw $e;
152
			} catch (\Exception $e) {
153
				$exception = new PicoRuntimeException($e);
154
				$this->logger->logException($exception, [ 'app' => Application::APP_NAME ]);
155
				throw $exception;
156
			}
157
158 2
			$picoPage = new PicoPage($website, $pico, $output);
159
160 2
			$picoPagePath = self::DIR_CONTENT . '/' . $picoPage->getRelativePath() . self::CONTENT_EXT;
161 2
			$website->assertViewerAccess($picoPagePath, $picoPage->getMeta());
162
		} catch (InvalidPathException $e) {
163
			throw new PageInvalidPathException($e);
164
		} catch (NotFoundException $e) {
165
			throw new PageNotFoundException($e);
166
		} catch (NotPermittedException $e) {
167
			throw new PageNotPermittedException($e);
168
		}
169
170 2
		return $picoPage;
171
	}
172
173
	/**
174
	 * @param Website $website
175
	 * @param Pico    $pico
176
	 * @param string  $page
177
	 *
178
	 * @throws WebsiteInvalidFilesystemException
179
	 */
180 2
	private function setupPico(Website $website, Pico $pico, string $page): void
181
	{
182 2
		$pico->setRequestUrl($page);
183 2
		$pico->setNextcloudWebsite($website);
184
185 2
		$pico->setConfig(
186
			[
187 2
				'site_title'     => $website->getName(),
188 2
				'base_url'       => $website->getWebsiteUrl(),
189
				'rewrite_url'    => true,
190 2
				'debug'          => \OC::$server->getConfig()->getSystemValue('debug', false),
191 2
				'timezone'       => $website->getTimeZone(),
192 2
				'theme'          => $website->getTheme(),
193 2
				'themes_url'     => $this->themesService->getThemesUrl(),
194 2
				'content_dir'    => $this->getContentPath($website),
195 2
				'content_ext'    => self::CONTENT_EXT,
196 2
				'assets_dir'     => $this->assetsService->getAssetsPath($website),
197 2
				'assets_url'     => $this->assetsService->getAssetsUrl($website),
198 2
				'plugins_url'    => $this->pluginsService->getPluginsUrl(),
199 2
				'nextcloud_site' => $website->getSite(),
200
			]
201
		);
202 2
	}
203
204
	/**
205
	 * @param Pico $pico
206
	 */
207
	private function loadPicoPlugins(Pico $pico): void
208
	{
209 2
		$includeClosure = static function (string $pluginFile) {
210
			/** @noinspection PhpIncludeInspection */
211 2
			require_once($pluginFile);
212 2
		};
213
214 2
		foreach ($this->pluginsService->getPlugins() as $pluginData) {
215 2
			if ($pluginData['compat']) {
216 2
				$pluginFile = $pluginData['name'] . '/' . $pluginData['name'] . '.php';
217 2
				$includeClosure($this->pluginsService->getPluginsPath() . '/' . $pluginFile);
218
219 2
				$pico->loadPlugin($pluginData['name']);
220
			}
221
		}
222 2
	}
223
224
	/**
225
	 * @param Website $website
226
	 * @param string  $absolutePath
227
	 *
228
	 * @return array
229
	 * @throws WebsiteInvalidFilesystemException
230
	 * @throws InvalidPathException
231
	 */
232 2
	public function getRelativePath(Website $website, string $absolutePath): array
233
	{
234 2
		$folder = $website->getWebsiteFolder();
235 2
		$basePath = $website->getWebsitePath();
236
237
		try {
238 2
			$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
239 2
		} catch (InvalidPathException $e) {
240 2
			$folder = $this->pluginsService->getPluginsFolder();
241 2
			$basePath = $this->pluginsService->getPluginsPath();
242
243
			try {
244 2
				$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
245 2
			} catch (InvalidPathException $e) {
246 2
				$folder = $this->themesService->getThemesFolder();
247 2
				$basePath = $this->themesService->getThemesPath();
248
249
				try {
250 2
					$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
251 2
				} catch (InvalidPathException $e) {
252 2
					$folder = $this->getConfigFolder();
253 2
					$basePath = $this->getConfigPath();
254
255
					try {
256 2
						$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
257
					} catch (InvalidPathException $e) {
258
						// the file is neither in the content nor assets, plugins, themes or config folder
259
						// Pico mustn't have access to any other directory
260
						throw new InvalidPathException();
261
					}
262
				}
263
			}
264
		}
265
266 2
		return [ $folder, rtrim($basePath, '/'), $relativePath ];
267
	}
268
269
	/**
270
	 * @param Website $website
271
	 *
272
	 * @return StorageFolder
273
	 * @throws WebsiteInvalidFilesystemException
274
	 */
275 2
	public function getContentFolder(Website $website): StorageFolder
276
	{
277
		try {
278
			/** @var StorageFolder $websiteFolder */
279 2
			$websiteFolder = $website->getWebsiteFolder()->getFolder(PicoService::DIR_CONTENT)->fakeRoot();
280 2
			return $websiteFolder;
281
		} catch (InvalidPathException | NotFoundException $e) {
282
			throw new WebsiteInvalidFilesystemException($e);
283
		}
284
	}
285
286
	/**
287
	 * @param Website $website
288
	 *
289
	 * @return string
290
	 * @throws WebsiteInvalidFilesystemException
291
	 */
292 2
	public function getContentPath(Website $website): string
293
	{
294
		try {
295 2
			return $this->getContentFolder($website)->getLocalPath() . '/';
296
		} catch (InvalidPathException | NotFoundException $e) {
297
			throw new WebsiteInvalidFilesystemException($e);
298
		}
299
	}
300
301
	/**
302
	 * @return StorageFolder
303
	 */
304 2
	public function getConfigFolder(): StorageFolder
305
	{
306
		/** @var StorageFolder $configFolder */
307 2
		$configFolder = $this->fileService->getAppDataFolder(self::DIR_CONFIG)->fakeRoot();
308 2
		return $configFolder;
309
	}
310
311
	/**
312
	 * @return string
313
	 */
314 2
	public function getConfigPath(): string
315
	{
316 2
		return $this->fileService->getAppDataFolderPath(self::DIR_CONFIG);
317
	}
318
}
319