Test Failed
Push — master ( dc798f...d76e2d )
by Daniel
48:22
created

Pico   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
wmc 30
eloc 66
c 6
b 0
f 1
dl 0
loc 292
ccs 0
cts 73
cp 0
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A loadFileContent() 0 9 1
A run() 0 3 1
A setNextcloudWebsite() 0 3 1
A purifyFileContent() 0 3 1
A setRequestUrl() 0 3 1
A getFilesGlob() 0 27 6
A evaluateRequestUrl() 0 2 1
B getFiles() 0 41 10
A getHtmlPurifier() 0 12 2
A parseFileContent() 0 4 1
A parseFileMeta() 0 4 1
A __construct() 0 5 1
A purifyFileMeta() 0 12 3
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
	public const API_VERSION_0 = 0;
50
51
	/**
52
	 * API version 1, used by Pico 1.0
53
	 *
54
	 * @var int
55
	 */
56
	public const API_VERSION_1 = 1;
57
58
	/**
59
	 * API version 2, used by Pico 2.0
60
	 *
61
	 * @var int
62
	 */
63
	public const API_VERSION_2 = 2;
64
65
	/**
66
	 * API version 3, used by Pico 2.1
67
	 *
68
	 * @var int
69
	 */
70
	public 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
	public function __construct($rootDir, $configDir, $pluginsDir, $themesDir, $enableLocalPlugins = true)
87
	{
88
		$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...
Deprecated Code introduced by
The function OC\ServerContainer::query() has been deprecated: 20.0.0 use \Psr\Container\ContainerInterface::get ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

88
		$this->picoService = /** @scrutinizer ignore-deprecated */ \OC::$server->query(PicoService::class);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
89
90
		parent::__construct($rootDir, $configDir, $pluginsDir, $themesDir, $enableLocalPlugins);
91
	}
92
93
	/**
94
	 * {@inheritDoc}
95
	 *
96
	 * @return string
97
	 * @throws WebsiteInvalidFilesystemException
98
	 * @throws InvalidPathException
99
	 * @throws NotFoundException
100
	 * @throws NotPermittedException
101
	 */
102
	public function run()
103
	{
104
		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
	public function setNextcloudWebsite(Website $website)
115
	{
116
		$this->website = $website;
117
	}
118
119
	/**
120
	 * Set's Pico's request URL.
121
	 *
122
	 * @param string $requestUrl request URL
123
	 *
124
	 * @return void
125
	 */
126
	public function setRequestUrl($requestUrl)
127
	{
128
		$this->requestUrl = $requestUrl;
129
	}
130
131
	/**
132
	 * Don't let Pico evaluate the request URL.
133
	 *
134
	 * @see Pico::setRequestUrl()
135
	 *
136
	 * @return void
137
	 */
138
	protected function evaluateRequestUrl()
139
	{
140
		// do nothing
141
	}
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
	public function loadFileContent($absolutePath)
155
	{
156
		/** @var FolderInterface $folder */
157
		/** @var string $basePath */
158
		/** @var string $relativePath */
159
		[ $folder, $basePath, $relativePath ] = $this->picoService->getRelativePath($this->website, $absolutePath);
160
161
		$file = $folder->getFile($relativePath);
162
		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
	public function parseFileMeta($rawContent, array $headers)
175
	{
176
		$meta = parent::parseFileMeta($rawContent, $headers);
177
		return $this->purifyFileMeta($meta);
178
	}
179
180
	/**
181
	 * Purifies file meta.
182
	 *
183
	 * @param array $meta
184
	 *
185
	 * @return array
186
	 */
187
	protected function purifyFileMeta(array $meta): array
188
	{
189
		$newMeta = [];
190
		foreach ($meta as $key => $value) {
191
			if (is_array($value)) {
192
				$newMeta[$key] = $this->purifyFileMeta($value);
193
			} else {
194
				$newMeta[$key] = $this->getHtmlPurifier()->purify($value);
195
			}
196
		}
197
198
		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
	public function parseFileContent($markdown, $singleLine = false)
210
	{
211
		$content = parent::parseFileContent($markdown, $singleLine);
212
		return $this->purifyFileContent($content);
213
	}
214
215
	/**
216
	 * Purifies file content.
217
	 *
218
	 * @param string $content
219
	 *
220
	 * @return string
221
	 */
222
	protected function purifyFileContent(string $content): string
223
	{
224
		return $this->getHtmlPurifier()->purify($content);
225
	}
226
227
	/**
228
	 * Returns the HTMLPurifier instance.
229
	 *
230
	 * @return HTMLPurifier
231
	 */
232
	public function getHtmlPurifier(): HTMLPurifier
233
	{
234
		if ($this->htmlPurifier === null) {
235
			$htmlPurifierConfig = HTMLPurifier_HTML5Config::createDefault();
236
			$htmlPurifierConfig->set('Attr.EnableID', true);
237
238
			$this->htmlPurifier = new HTMLPurifier($htmlPurifierConfig);
239
240
			$this->triggerEvent('onHtmlPurifier', [ &$this->htmlPurifier ]);
241
		}
242
243
		return $this->htmlPurifier;
244
	}
245
246
	/**
247
	 * @param string $absolutePath
248
	 * @param string $fileExtension
249
	 * @param int    $order
250
	 *
251
	 * @return string[]
252
	 * @throws WebsiteInvalidFilesystemException
253
	 * @throws InvalidPathException
254
	 */
255
	public function getFiles($absolutePath, $fileExtension = '', $order = \Pico::SORT_ASC)
256
	{
257
		/** @var FolderInterface $folder */
258
		/** @var string $basePath */
259
		/** @var string $relativePath */
260
		[ $folder, $basePath, $relativePath ] = $this->picoService->getRelativePath($this->website, $absolutePath);
261
262
		if ($folder->isLocal()) {
263
			return parent::getFiles($absolutePath, $fileExtension, $order);
264
		}
265
266
		$folderFilter = function (NodeInterface $node, int $key, FolderInterface $folder) use ($fileExtension) {
267
			$fileName = $node->getName();
268
269
			// exclude hidden files/dirs starting with a .
270
			// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
271
			if (($fileName[0] === '.') || in_array($fileName[-1], [ '~', '#' ], true)) {
272
				return false;
273
			}
274
275
			if ($node->isFile()) {
276
				/** @var FileInterface $node */
277
				if ($fileExtension && ($fileExtension !== '.' . $node->getExtension())) {
278
					return false;
279
				}
280
			}
281
282
			return true;
283
		};
284
285
		try {
286
			$folderIterator = new \RecursiveCallbackFilterIterator($folder->fakeRoot(), $folderFilter);
287
288
			$result = [];
289
			foreach (new \RecursiveIteratorIterator($folderIterator) as $file) {
290
				$result[] = $basePath . '/' . $relativePath . $file->getPath();
291
			}
292
293
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
294
		} catch (\Exception $e) {
295
			return [];
296
		}
297
	}
298
299
	/**
300
	 * @param string $absolutePathPattern
301
	 * @param int    $order
302
	 *
303
	 * @return string[]
304
	 * @throws WebsiteInvalidFilesystemException
305
	 * @throws InvalidPathException
306
	 */
307
	public function getFilesGlob($absolutePathPattern, $order = \Pico::SORT_ASC)
308
	{
309
		/** @var FolderInterface $folder */
310
		/** @var string $basePath */
311
		/** @var string $pattern */
312
		[ $folder, $basePath, $pattern ] = $this->picoService->getRelativePath($this->website, $absolutePathPattern);
313
314
		if ($folder->isLocal()) {
315
			return parent::getFilesGlob($absolutePathPattern, $order);
316
		}
317
318
		try {
319
			$result = [];
320
			foreach (new GlobIterator($folder, $pattern) as $file) {
321
				$fileName = $file->getName();
322
323
				// exclude files ending with a ~ (vim/nano backup) or # (emacs backup)
324
				if (in_array($fileName[-1], [ '~', '#' ], true)) {
325
					continue;
326
				}
327
328
				$result[] = $basePath . $file->getPath();
329
			}
330
331
			return ($order === \Pico::SORT_DESC) ? array_reverse($result) : $result;
332
		} catch (\Exception $e) {
333
			return [];
334
		}
335
	}
336
}
337