Passed
Push — master ( 235c52...927bab )
by Daniel
30:30 queued 10s
created

Pico::setNextcloudWebsite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
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
	 * @param bool   $singleLine
206
	 *
207
	 * @return string
208
	 */
209 2
	public function parseFileContent($markdown, $singleLine = false)
210
	{
211 2
		$content = parent::parseFileContent($markdown, $singleLine);
212 2
		return $this->purifyFileContent($content);
213
	}
214
215
	/**
216
	 * Purifies file content.
217
	 *
218
	 * @param string $content
219
	 *
220
	 * @return string
221
	 */
222 2
	protected function purifyFileContent(string $content)
223
	{
224 2
		return $this->getHtmlPurifier()->purify($content);
225
	}
226
227
	/**
228
	 * Returns the HTMLPurifier instance.
229
	 *
230
	 * @return HTMLPurifier
231
	 */
232 2
	public function getHtmlPurifier()
233
	{
234 2
		if ($this->htmlPurifier === null) {
235 2
			$htmlPurifierConfig = HTMLPurifier_HTML5Config::createDefault();
236 2
			$this->htmlPurifier = new HTMLPurifier($htmlPurifierConfig);
237
238 2
			$this->triggerEvent('onHtmlPurifier', [ &$this->htmlPurifier ]);
239
		}
240
241 2
		return $this->htmlPurifier;
242
	}
243
244
	/**
245
	 * @param string $absolutePath
246
	 * @param string $fileExtension
247
	 * @param int    $order
248
	 *
249
	 * @return string[]
250
	 * @throws WebsiteInvalidFilesystemException
251
	 * @throws InvalidPathException
252
	 */
253 2
	public function getFiles($absolutePath, $fileExtension = '', $order = \Pico::SORT_ASC)
254
	{
255
		/** @var FolderInterface $folder */
256
		/** @var string $basePath */
257
		/** @var string $relativePath */
258 2
		list($folder, $basePath, $relativePath) = $this->picoService->getRelativePath($this->website, $absolutePath);
259
260 2
		if ($folder->isLocal()) {
261 2
			return parent::getFiles($absolutePath, $fileExtension, $order);
262
		}
263
264
		$folderFilter = function (NodeInterface $node, int $key, FolderInterface $folder) use ($fileExtension) {
265
			$fileName = $node->getName();
266
267
			// exclude hidden files/dirs starting with a .
268
			// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
269
			if (($fileName[0] === '.') || in_array(substr($fileName, -1), [ '~', '#' ], true)) {
270
				return false;
271
			}
272
273
			if ($node->isFile()) {
274
				/** @var FileInterface $node */
275
				if ($fileExtension && ($fileExtension !== '.' . $node->getExtension())) {
276
					return false;
277
				}
278
			}
279
280
			return true;
281
		};
282
283
		try {
284
			$folderIterator = new \RecursiveCallbackFilterIterator($folder->fakeRoot(), $folderFilter);
285
286
			$result = [];
287
			foreach (new \RecursiveIteratorIterator($folderIterator) as $file) {
288
				$result[] = $basePath . '/' . $relativePath . $file->getPath();
289
			}
290
291
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
292
		} catch (\Exception $e) {
293
			return [];
294
		}
295
	}
296
297
	/**
298
	 * @param string $absolutePathPattern
299
	 * @param int    $order
300
	 *
301
	 * @return string[]
302
	 * @throws WebsiteInvalidFilesystemException
303
	 * @throws InvalidPathException
304
	 */
305 2
	public function getFilesGlob($absolutePathPattern, $order = \Pico::SORT_ASC)
306
	{
307
		/** @var FolderInterface $folder */
308
		/** @var string $basePath */
309
		/** @var string $pattern */
310 2
		list($folder, $basePath, $pattern) = $this->picoService->getRelativePath($this->website, $absolutePathPattern);
311
312 2
		if ($folder->isLocal()) {
313 2
			return parent::getFilesGlob($absolutePathPattern, $order);
314
		}
315
316
		try {
317
			$result = [];
318
			foreach (new GlobIterator($folder, $pattern) as $file) {
319
				$fileName = $file->getName();
320
321
				// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
322
				if (in_array(substr($fileName, -1), [ '~', '#' ], true)) {
323
					continue;
324
				}
325
326
				$result[] = $basePath . $file->getPath();
327
			}
328
329
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
330
		} catch (\Exception $e) {
331
			return [];
332
		}
333
	}
334
}
335