Test Setup Failed
Push — master ( 4a93bf...5a5f5f )
by Daniel
23:25
created

Pico::evaluateRequestUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 0
dl 0
loc 2
c 0
b 0
f 0
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 0
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\Model\WebsiteRequest;
37
use OCA\CMSPico\Service\PicoService;
38
use OCP\Files\InvalidPathException;
39
use OCP\Files\NotFoundException;
40
use OCP\Files\NotPermittedException;
41
use Symfony\Component\Yaml\Exception\ParseException;
42
43
class Pico extends \Pico
44
{
45
	/**
46
	 * API version 0, used by Pico 0.9 and earlier
47
	 *
48
	 * @var int
49
	 */
50
	public const API_VERSION_0 = 0;
51
52
	/**
53
	 * API version 1, used by Pico 1.0
54
	 *
55
	 * @var int
56
	 */
57
	public const API_VERSION_1 = 1;
58
59
	/**
60
	 * API version 2, used by Pico 2.0
61
	 *
62
	 * @var int
63
	 */
64
	public const API_VERSION_2 = 2;
65
66
	/**
67
	 * API version 3, used by Pico 2.1
68
	 *
69
	 * @var int
70
	 */
71
	public const API_VERSION_3 = 3;
72
73
	/** @var PicoService */
74
	private $picoService;
75
76
	/** @var HTMLPurifier */
77
	private $htmlPurifier;
78
79
	/** @var WebsiteRequest */
80
	private $websiteRequest;
81
82
	/** @var Website */
83
	private $website;
84
85
	/**
86
	 * Pico constructor.
87
	 *
88
	 * {@inheritDoc}
89
	 */
90 4
	public function __construct($rootDir, $configDir, $pluginsDir, $themesDir, $enableLocalPlugins = true)
91
	{
92 4
		$this->picoService = \OC::$server->query(PicoService::class);
1 ignored issue
show
Documentation Bug introduced by
It seems like OC::server->query(OCA\CM...ice\PicoService::class) can also be of type stdClass. However, the property $picoService is declared as type OCA\CMSPico\Service\PicoService. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
93
94 4
		parent::__construct($rootDir, $configDir, $pluginsDir, $themesDir, $enableLocalPlugins);
95 4
	}
96
97
	/**
98
	 * {@inheritDoc}
99
	 *
100
	 * @return string
101
	 * @throws WebsiteInvalidFilesystemException
102
	 * @throws InvalidPathException
103
	 * @throws NotFoundException
104
	 * @throws NotPermittedException
105
	 */
106 4
	public function run()
107
	{
108 4
		return parent::run();
109
	}
110
111
	/**
112
	 * Set's Nextcloud's website and website request instances.
113
	 *
114
	 * @param WebsiteRequest $websiteRequest Nextcloud's website request instance
115
	 *
116
	 * @return void
117
	 */
118 4
	public function setNextcloudWebsite(WebsiteRequest $websiteRequest)
119
	{
120 4
		$this->websiteRequest = $websiteRequest;
121 4
		$this->website = $websiteRequest->getWebsite();
122 4
	}
123
124
	/**
125
	 * Set's Pico's request URL.
126
	 *
127
	 * @param string $requestUrl request URL
128
	 *
129
	 * @return void
130
	 */
131 4
	public function setRequestUrl($requestUrl)
132
	{
133 4
		$this->requestUrl = $requestUrl;
134 4
	}
135
136
	/**
137
	 * Don't let Pico evaluate the request URL.
138
	 *
139
	 * @see Pico::setRequestUrl()
140
	 *
141
	 * @return void
142
	 */
143 4
	protected function evaluateRequestUrl()
144
	{
145
		// do nothing
146 4
	}
147
148
	/**
149
	 * Checks whether a file is readable in Nextcloud and returns the raw contents of this file
150
	 *
151
	 * @param string $absolutePath file path
152
	 *
153
	 * @return string raw contents of the file
154
	 * @throws WebsiteInvalidFilesystemException
155
	 * @throws InvalidPathException
156
	 * @throws NotFoundException
157
	 * @throws NotPermittedException
158
	 */
159 4
	public function loadFileContent($absolutePath)
160
	{
161
		/** @var FolderInterface $folder */
162
		/** @var string $basePath */
163
		/** @var string $relativePath */
164 4
		[ $folder, $basePath, $relativePath ] = $this->picoService->getRelativePath($this->website, $absolutePath);
165
166 4
		$file = $folder->getFile($relativePath);
167 4
		return $file->getContent();
168
	}
169
170
	/**
171
	 * Returns the parsed and purified file meta from raw file contents.
172
	 *
173
	 * @param string $rawContent
174
	 * @param string[] $headers
175
	 *
176
	 * @return array
177
	 * @throws ParseException
178
	 */
179 4
	public function parseFileMeta($rawContent, array $headers)
180
	{
181 4
		$meta = parent::parseFileMeta($rawContent, $headers);
182 4
		return $this->purifyFileMeta($meta);
183
	}
184
185
	/**
186
	 * Purifies file meta.
187
	 *
188
	 * @param array $meta
189
	 *
190
	 * @return array
191
	 */
192 4
	protected function purifyFileMeta(array $meta): array
193
	{
194 4
		$newMeta = [];
195 4
		foreach ($meta as $key => $value) {
196 4
			if (is_array($value)) {
197 2
				$newMeta[$key] = $this->purifyFileMeta($value);
198
			} else {
199 4
				$newMeta[$key] = $this->getHtmlPurifier()->purify($value);
200
			}
201
		}
202
203 4
		return $newMeta;
204
	}
205
206
	/**
207
	 * Returns the parsed and purified contents of a page.
208
	 *
209
	 * @param string $markdown
210
	 * @param bool   $singleLine
211
	 *
212
	 * @return string
213
	 */
214 4
	public function parseFileContent($markdown, $singleLine = false)
215
	{
216 4
		$content = parent::parseFileContent($markdown, $singleLine);
217 4
		return $this->purifyFileContent($content);
218
	}
219
220
	/**
221
	 * Purifies file content.
222
	 *
223
	 * @param string $content
224
	 *
225
	 * @return string
226
	 */
227 4
	protected function purifyFileContent(string $content): string
228
	{
229 4
		return $this->getHtmlPurifier()->purify($content);
230
	}
231
232
	/**
233
	 * Returns the HTMLPurifier instance.
234
	 *
235
	 * @return HTMLPurifier
236
	 */
237 4
	public function getHtmlPurifier(): HTMLPurifier
238
	{
239 4
		if ($this->htmlPurifier === null) {
240 4
			$htmlPurifierConfig = HTMLPurifier_HTML5Config::createDefault();
241 4
			$htmlPurifierConfig->set('Attr.EnableID', true);
242
243 4
			$this->htmlPurifier = new HTMLPurifier($htmlPurifierConfig);
244
245 4
			$this->triggerEvent('onHtmlPurifier', [ &$this->htmlPurifier ]);
246
		}
247
248 4
		return $this->htmlPurifier;
249
	}
250
251
	/**
252
	 * @param string $absolutePath
253
	 * @param string $fileExtension
254
	 * @param int    $order
255
	 *
256
	 * @return string[]
257
	 * @throws WebsiteInvalidFilesystemException
258
	 * @throws InvalidPathException
259
	 */
260 4
	public function getFiles($absolutePath, $fileExtension = '', $order = \Pico::SORT_ASC)
261
	{
262
		/** @var FolderInterface $folder */
263
		/** @var string $basePath */
264
		/** @var string $relativePath */
265 4
		[ $folder, $basePath, $relativePath ] = $this->picoService->getRelativePath($this->website, $absolutePath);
266
267 4
		if ($folder->isLocal()) {
268 4
			return parent::getFiles($absolutePath, $fileExtension, $order);
269
		}
270
271
		$folderFilter = function (NodeInterface $node, int $key, FolderInterface $folder) use ($fileExtension) {
272
			$fileName = $node->getName();
273
274
			// exclude hidden files/dirs starting with a .
275
			// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
276
			if (($fileName[0] === '.') || in_array($fileName[-1], [ '~', '#' ], true)) {
277
				return false;
278
			}
279
280
			if ($node->isFile()) {
281
				/** @var FileInterface $node */
282
				if ($fileExtension && ($fileExtension !== '.' . $node->getExtension())) {
283
					return false;
284
				}
285
			}
286
287
			return true;
288
		};
289
290
		try {
291
			$folderIterator = new \RecursiveCallbackFilterIterator($folder->fakeRoot(), $folderFilter);
292
293
			$result = [];
294
			foreach (new \RecursiveIteratorIterator($folderIterator) as $file) {
295
				$result[] = $basePath . '/' . $relativePath . $file->getPath();
296
			}
297
298
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
299
		} catch (\Exception $e) {
300
			return [];
301
		}
302
	}
303
304
	/**
305
	 * @param string $absolutePathPattern
306
	 * @param int    $order
307
	 *
308
	 * @return string[]
309
	 * @throws WebsiteInvalidFilesystemException
310
	 * @throws InvalidPathException
311
	 */
312 4
	public function getFilesGlob($absolutePathPattern, $order = \Pico::SORT_ASC)
313
	{
314
		/** @var FolderInterface $folder */
315
		/** @var string $basePath */
316
		/** @var string $pattern */
317 4
		[ $folder, $basePath, $pattern ] = $this->picoService->getRelativePath($this->website, $absolutePathPattern);
318
319 4
		if ($folder->isLocal()) {
320 4
			return parent::getFilesGlob($absolutePathPattern, $order);
321
		}
322
323
		try {
324
			$result = [];
325
			foreach (new GlobIterator($folder, $pattern) as $file) {
326
				$fileName = $file->getName();
327
328
				// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
329
				if (in_array($fileName[-1], [ '~', '#' ], true)) {
330
					continue;
331
				}
332
333
				$result[] = $basePath . $file->getPath();
334
			}
335
336
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
337
		} catch (\Exception $e) {
338
			return [];
339
		}
340
	}
341
}
342