Passed
Pull Request — master (#77)
by Daniel
36:15
created

PicoService::getContentFromPico()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
cc 2
nc 2
nop 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
	const DIR_TEMPLATES = 'templates';
50
51
	/** @var string */
52
	const DIR_CONFIG = 'config';
53
54
	/** @var string */
55
	const DIR_PLUGINS = 'plugins';
56
57
	/** @var string */
58
	const DIR_THEMES = 'themes';
59
60
	/** @var string */
61
	const DIR_CONTENT = 'content';
62
63
	/** @var string */
64
	const DIR_ASSETS = 'assets';
65
66
	/** @var string */
67
	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->getConfigFolder()->getLocalPath(),
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 $e) {
151
				throw $e;
152
			} catch (NotFoundException $e) {
153
				throw $e;
154
			} catch (NotPermittedException $e) {
155
				throw $e;
156
			} catch (\Exception $e) {
157
				$exception = new PicoRuntimeException($e);
158
				$this->logger->logException($exception, [ 'app' => Application::APP_NAME ]);
159
				throw $exception;
160
			}
161
162 2
			$picoPage = new PicoPage($website, $pico, $output);
163
164 2
			$picoPagePath = self::DIR_CONTENT . '/' . $picoPage->getRelativePath() . self::CONTENT_EXT;
165 2
			$website->assertViewerAccess($picoPagePath, $picoPage->getMeta());
166
		} catch (InvalidPathException $e) {
167
			throw new PageInvalidPathException($e);
168
		} catch (NotFoundException $e) {
169
			throw new PageNotFoundException($e);
170
		} catch (NotPermittedException $e) {
171
			throw new PageNotPermittedException($e);
172
		}
173
174 2
		return $picoPage;
175
	}
176
177
	/**
178
	 * @param Website $website
179
	 * @param Pico    $pico
180
	 * @param string  $page
181
	 *
182
	 * @throws WebsiteInvalidFilesystemException
183
	 */
184 2
	private function setupPico(Website $website, Pico $pico, string $page)
185
	{
186 2
		$pico->setRequestUrl($page);
187 2
		$pico->setNextcloudWebsite($website);
188
189 2
		$pico->setConfig(
190
			[
191 2
				'site_title'     => $website->getName(),
192 2
				'base_url'       => $website->getWebsiteUrl(),
193
				'rewrite_url'    => true,
194 2
				'debug'          => \OC::$server->getConfig()->getSystemValue('debug', false),
195 2
				'timezone'       => $website->getTimeZone(),
196 2
				'theme'          => $website->getTheme(),
197 2
				'themes_url'     => $this->themesService->getThemesUrl(),
198 2
				'content_dir'    => $this->getContentPath($website),
199 2
				'content_ext'    => self::CONTENT_EXT,
200 2
				'assets_dir'     => $this->assetsService->getAssetsPath($website),
201 2
				'assets_url'     => $this->assetsService->getAssetsUrl($website),
202 2
				'plugins_url'    => $this->pluginsService->getPluginsUrl(),
203 2
				'nextcloud_site' => $website->getSite(),
204
			]
205
		);
206 2
	}
207
208
	/**
209
	 * @param Pico $pico
210
	 */
211
	private function loadPicoPlugins(Pico $pico)
212
	{
213 2
		$includeClosure = static function (string $pluginFile) {
214
			/** @noinspection PhpIncludeInspection */
215 2
			require_once($pluginFile);
216 2
		};
217
218 2
		$plugins = $this->pluginsService->getPlugins();
219 2
		foreach ($plugins as $pluginData) {
220 2
			if ($pluginData['compat']) {
221 2
				$pluginFile = $pluginData['name'] . '/' . $pluginData['name'] . '.php';
222 2
				$includeClosure($this->pluginsService->getPluginsPath() . '/' . $pluginFile);
223
224 2
				$pico->loadPlugin($pluginData['name']);
225
			}
226
		}
227 2
	}
228
229
	/**
230
	 * @param Website $website
231
	 * @param string  $absolutePath
232
	 *
233
	 * @return array
234
	 * @throws WebsiteInvalidFilesystemException
235
	 * @throws InvalidPathException
236
	 */
237 2
	public function getRelativePath(Website $website, string $absolutePath): array
238
	{
239 2
		$folder = $website->getWebsiteFolder();
240 2
		$basePath = $website->getWebsitePath();
241
242
		try {
243 2
			$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
244 2
		} catch (InvalidPathException $e) {
245 2
			$folder = $this->pluginsService->getPluginsFolder();
246 2
			$basePath = $this->pluginsService->getPluginsPath();
247
248
			try {
249 2
				$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
250 2
			} catch (InvalidPathException $e) {
251 2
				$folder = $this->themesService->getThemesFolder();
252 2
				$basePath = $this->themesService->getThemesPath();
253
254
				try {
255 2
					$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
256 2
				} catch (InvalidPathException $e) {
257 2
					$folder = $this->getConfigFolder();
258 2
					$basePath = $this->getConfigPath();
259
260
					try {
261 2
						$relativePath = $this->miscService->getRelativePath($absolutePath, $basePath);
262
					} catch (InvalidPathException $e) {
263
						// the file is neither in the content nor assets, plugins, themes or config folder
264
						// Pico mustn't have access to any other directory
265
						throw new InvalidPathException();
266
					}
267
				}
268
			}
269
		}
270
271 2
		return [ $folder, rtrim($basePath, '/'), $relativePath ];
272
	}
273
274
	/**
275
	 * @param Website $website
276
	 *
277
	 * @return StorageFolder
278
	 * @throws WebsiteInvalidFilesystemException
279
	 */
280 2
	public function getContentFolder(Website $website): StorageFolder
281
	{
282
		try {
283
			/** @var StorageFolder $websiteFolder */
284 2
			$websiteFolder = $website->getWebsiteFolder()->getFolder(PicoService::DIR_CONTENT)->fakeRoot();
285 2
			return $websiteFolder;
286
		} catch (InvalidPathException $e) {
287
			throw new WebsiteInvalidFilesystemException($e);
288
		} catch (NotFoundException $e) {
289
			throw new WebsiteInvalidFilesystemException($e);
290
		}
291
	}
292
293
	/**
294
	 * @param Website $website
295
	 *
296
	 * @return string
297
	 * @throws WebsiteInvalidFilesystemException
298
	 */
299 2
	public function getContentPath(Website $website): string
300
	{
301
		try {
302 2
			return $this->getContentFolder($website)->getLocalPath() . '/';
303
		} catch (InvalidPathException $e) {
304
			throw new WebsiteInvalidFilesystemException($e);
305
		} catch (NotFoundException $e) {
306
			throw new WebsiteInvalidFilesystemException($e);
307
		}
308
	}
309
310
	/**
311
	 * @return StorageFolder
312
	 */
313 2
	public function getConfigFolder(): StorageFolder
314
	{
315
		/** @var StorageFolder $configFolder */
316 2
		$configFolder = $this->fileService->getAppDataFolder(self::DIR_CONFIG)->fakeRoot();
317 2
		return $configFolder;
318
	}
319
320
	/**
321
	 * @return string
322
	 */
323 2
	public function getConfigPath(): string
324
	{
325 2
		return $this->fileService->getAppDataFolderPath(self::DIR_CONFIG);
326
	}
327
}
328