Passed
Pull Request — master (#77)
by Daniel
20:10
created

Pico::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 5
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;
27
28
use HTMLPurifier;
29
use HTMLPurifier_HTML5Config;
30
use OCA\CMSPico\Exceptions\WebsiteInvalidFilesystemException;
31
use OCA\CMSPico\Files\FileInterface;
32
use OCA\CMSPico\Files\FolderInterface;
33
use OCA\CMSPico\Files\Glob\GlobIterator;
34
use OCA\CMSPico\Files\NodeInterface;
35
use OCA\CMSPico\Model\Website;
36
use OCA\CMSPico\Service\PicoService;
37
use OCP\Files\InvalidPathException;
38
use OCP\Files\NotFoundException;
39
use OCP\Files\NotPermittedException;
40
use Symfony\Component\Yaml\Exception\ParseException;
41
42
class Pico extends \Pico
43
{
44
	/**
45
	 * API version 0, used by Pico 0.9 and earlier
46
	 *
47
	 * @var int
48
	 */
49
	const API_VERSION_0 = 0;
50
51
	/**
52
	 * API version 1, used by Pico 1.0
53
	 *
54
	 * @var int
55
	 */
56
	const API_VERSION_1 = 1;
57
58
	/**
59
	 * API version 2, used by Pico 2.0
60
	 *
61
	 * @var int
62
	 */
63
	const API_VERSION_2 = 2;
64
65
	/**
66
	 * API version 3, used by Pico 2.1
67
	 *
68
	 * @var int
69
	 */
70
	const API_VERSION_3 = 3;
71
72
	/** @var PicoService */
73
	private $picoService;
74
75
	/** @var HTMLPurifier */
76
	private $htmlPurifier;
77
78
	/** @var Website */
79
	private $website;
80
81
	/**
82
	 * Pico constructor.
83
	 *
84
	 * {@inheritDoc}
85
	 */
86 2
	public function __construct($rootDir, $configDir, $pluginsDir, $themesDir, $enableLocalPlugins = true)
87
	{
88 2
		$this->picoService = \OC::$server->query(PicoService::class);
89
90 2
		parent::__construct($rootDir, $configDir, $pluginsDir, $themesDir, $enableLocalPlugins);
91 2
	}
92
93
	/**
94
	 * {@inheritDoc}
95
	 *
96
	 * @return string
97
	 * @throws WebsiteInvalidFilesystemException
98
	 * @throws InvalidPathException
99
	 * @throws NotFoundException
100
	 * @throws NotPermittedException
101
	 */
102 2
	public function run()
103
	{
104 2
		return parent::run();
105
	}
106
107
	/**
108
	 * Set's Nextcloud's website instance.
109
	 *
110
	 * @param Website $website Nextcloud's website instance
111
	 *
112
	 * @return void
113
	 */
114 2
	public function setNextcloudWebsite(Website $website)
115
	{
116 2
		$this->website = $website;
117 2
	}
118
119
	/**
120
	 * Set's Pico's request URL.
121
	 *
122
	 * @param string $requestUrl request URL
123
	 *
124
	 * @return void
125
	 */
126 2
	public function setRequestUrl($requestUrl)
127
	{
128 2
		$this->requestUrl = $requestUrl;
129 2
	}
130
131
	/**
132
	 * Don't let Pico evaluate the request URL.
133
	 *
134
	 * @see Pico::setRequestUrl()
135
	 *
136
	 * @return void
137
	 */
138 2
	protected function evaluateRequestUrl()
139
	{
140
		// do nothing
141 2
	}
142
143
	/**
144
	 * Checks whether a file is readable in Nextcloud and returns the raw contents of this file
145
	 *
146
	 * @param string $absolutePath file path
147
	 *
148
	 * @return string raw contents of the file
149
	 * @throws WebsiteInvalidFilesystemException
150
	 * @throws InvalidPathException
151
	 * @throws NotFoundException
152
	 * @throws NotPermittedException
153
	 */
154 2
	public function loadFileContent($absolutePath)
155
	{
156
		/** @var FolderInterface $folder */
157
		/** @var string $basePath */
158
		/** @var string $relativePath */
159 2
		list($folder, $basePath, $relativePath) = $this->picoService->getRelativePath($this->website, $absolutePath);
160
161 2
		$file = $folder->getFile($relativePath);
162 2
		return $file->getContent();
163
	}
164
165
	/**
166
	 * Returns the parsed and purified file meta from raw file contents.
167
	 *
168
	 * @param string $rawContent
169
	 * @param string[] $headers
170
	 *
171
	 * @return array
172
	 * @throws ParseException
173
	 */
174 2
	public function parseFileMeta($rawContent, array $headers)
175
	{
176 2
		$meta = parent::parseFileMeta($rawContent, $headers);
177 2
		return $this->purifyFileMeta($meta);
178
	}
179
180
	/**
181
	 * Purifies file meta.
182
	 *
183
	 * @param array $meta
184
	 *
185
	 * @return array
186
	 */
187 2
	protected function purifyFileMeta(array $meta)
188
	{
189 2
		$newMeta = [];
190 2
		foreach ($meta as $key => $value) {
191 2
			if (is_array($value)) {
192 2
				$newMeta[$key] = $this->purifyFileMeta($value);
193
			} else {
194 2
				$newMeta[$key] = $this->getHtmlPurifier()->purify($value);
195
			}
196
		}
197
198 2
		return $newMeta;
199
	}
200
201
	/**
202
	 * Returns the parsed and purified contents of a page.
203
	 *
204
	 * @param string $markdown
205
	 *
206
	 * @return string
207
	 */
208 2
	public function parseFileContent($markdown)
209
	{
210 2
		$content = parent::parseFileContent($markdown);
211 2
		return $this->purifyFileContent($content);
212
	}
213
214
	/**
215
	 * Purifies file content.
216
	 *
217
	 * @param string $content
218
	 *
219
	 * @return string
220
	 */
221 2
	protected function purifyFileContent(string $content)
222
	{
223 2
		return $this->getHtmlPurifier()->purify($content);
224
	}
225
226
	/**
227
	 * Returns the HTMLPurifier instance.
228
	 *
229
	 * @return HTMLPurifier
230
	 */
231 2
	public function getHtmlPurifier()
232
	{
233 2
		if ($this->htmlPurifier === null) {
234 2
			$htmlPurifierConfig = HTMLPurifier_HTML5Config::createDefault();
235 2
			$this->htmlPurifier = new HTMLPurifier($htmlPurifierConfig);
236
237 2
			$this->triggerEvent('onHtmlPurifier', [ &$this->htmlPurifier ]);
238
		}
239
240 2
		return $this->htmlPurifier;
241
	}
242
243
	/**
244
	 * @param string $absolutePath
245
	 * @param string $fileExtension
246
	 * @param int    $order
247
	 *
248
	 * @return string[]
249
	 * @throws WebsiteInvalidFilesystemException
250
	 * @throws InvalidPathException
251
	 */
252 2
	public function getFiles($absolutePath, $fileExtension = '', $order = \Pico::SORT_ASC)
253
	{
254
		/** @var FolderInterface $folder */
255
		/** @var string $basePath */
256
		/** @var string $relativePath */
257 2
		list($folder, $basePath, $relativePath) = $this->picoService->getRelativePath($this->website, $absolutePath);
258
259 2
		if ($folder->isLocal()) {
260 2
			return parent::getFiles($absolutePath, $fileExtension, $order);
261
		}
262
263
		$folderFilter = function (NodeInterface $node, int $key, FolderInterface $folder) use ($fileExtension) {
264
			$fileName = $node->getName();
265
266
			// exclude hidden files/dirs starting with a .
267
			// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
268
			if (($fileName[0] === '.') || in_array(substr($fileName, -1), [ '~', '#' ], true)) {
269
				return false;
270
			}
271
272
			if ($node->isFile()) {
273
				/** @var FileInterface $node */
274
				if ($fileExtension && ($fileExtension !== '.' . $node->getExtension())) {
275
					return false;
276
				}
277
			}
278
279
			return true;
280
		};
281
282
		try {
283
			$folderIterator = new \RecursiveCallbackFilterIterator($folder->fakeRoot(), $folderFilter);
284
285
			$result = [];
286
			foreach (new \RecursiveIteratorIterator($folderIterator) as $file) {
287
				$result[] = $basePath . '/' . $relativePath . $file->getPath();
288
			}
289
290
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
291
		} catch (\Exception $e) {
292
			return [];
293
		}
294
	}
295
296
	/**
297
	 * @param string $absolutePathPattern
298
	 * @param int    $order
299
	 *
300
	 * @return string[]
301
	 * @throws WebsiteInvalidFilesystemException
302
	 * @throws InvalidPathException
303
	 */
304 2
	public function getFilesGlob($absolutePathPattern, $order = \Pico::SORT_ASC)
305
	{
306
		/** @var FolderInterface $folder */
307
		/** @var string $basePath */
308
		/** @var string $pattern */
309 2
		list($folder, $basePath, $pattern) = $this->picoService->getRelativePath($this->website, $absolutePathPattern);
310
311 2
		if ($folder->isLocal()) {
312 2
			return parent::getFilesGlob($absolutePathPattern, $order);
313
		}
314
315
		try {
316
			$result = [];
317
			foreach (new GlobIterator($folder, $pattern) as $file) {
318
				$fileName = $file->getName();
319
320
				// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
321
				if (in_array(substr($fileName, -1), [ '~', '#' ], true)) {
322
					continue;
323
				}
324
325
				$result[] = $basePath . $file->getPath();
326
			}
327
328
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
329
		} catch (\Exception $e) {
330
			return [];
331
		}
332
	}
333
}
334