Passed
Push — master ( 804a15...6331f1 )
by Morris
12:05
created

FilesReportPlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 9
dl 0
loc 19
rs 9.9666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 * @author Vincent Petry <[email protected]>
8
 *
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
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, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OCA\DAV\Connector\Sabre;
26
27
use OC\Files\View;
28
use OCP\App\IAppManager;
29
use Sabre\DAV\Exception\PreconditionFailed;
30
use Sabre\DAV\Exception\BadRequest;
31
use Sabre\DAV\ServerPlugin;
32
use Sabre\DAV\Tree;
33
use Sabre\DAV\Xml\Element\Response;
34
use Sabre\DAV\Xml\Response\MultiStatus;
35
use Sabre\DAV\PropFind;
36
use OCP\SystemTag\ISystemTagObjectMapper;
37
use OCP\IUserSession;
38
use OCP\Files\Folder;
39
use OCP\IGroupManager;
40
use OCP\SystemTag\ISystemTagManager;
41
use OCP\SystemTag\TagNotFoundException;
42
use OCP\ITagManager;
43
44
class FilesReportPlugin extends ServerPlugin {
45
46
	// namespace
47
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
48
	const REPORT_NAME            = '{http://owncloud.org/ns}filter-files';
49
	const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
50
	const CIRCLE_PROPERTYNAME = '{http://owncloud.org/ns}circle';
51
52
	/**
53
	 * Reference to main server object
54
	 *
55
	 * @var \Sabre\DAV\Server
56
	 */
57
	private $server;
58
59
	/**
60
	 * @var Tree
61
	 */
62
	private $tree;
63
64
	/**
65
	 * @var View
66
	 */
67
	private $fileView;
68
69
	/**
70
	 * @var ISystemTagManager
71
	 */
72
	private $tagManager;
73
74
	/**
75
	 * @var ISystemTagObjectMapper
76
	 */
77
	private $tagMapper;
78
79
	/**
80
	 * Manager for private tags
81
	 *
82
	 * @var ITagManager
83
	 */
84
	private $fileTagger;
85
86
	/**
87
	 * @var IUserSession
88
	 */
89
	private $userSession;
90
91
	/**
92
	 * @var IGroupManager
93
	 */
94
	private $groupManager;
95
96
	/**
97
	 * @var Folder
98
	 */
99
	private $userFolder;
100
101
	/**
102
	 * @var IAppManager
103
	 */
104
	private $appManager;
105
106
	/**
107
	 * @param Tree $tree
108
	 * @param View $view
109
	 * @param ISystemTagManager $tagManager
110
	 * @param ISystemTagObjectMapper $tagMapper
111
	 * @param ITagManager $fileTagger manager for private tags
112
	 * @param IUserSession $userSession
113
	 * @param IGroupManager $groupManager
114
	 * @param Folder $userFolder
115
	 * @param IAppManager $appManager
116
	 */
117
	public function __construct(Tree $tree,
118
								View $view,
119
								ISystemTagManager $tagManager,
120
								ISystemTagObjectMapper $tagMapper,
121
								ITagManager $fileTagger,
122
								IUserSession $userSession,
123
								IGroupManager $groupManager,
124
								Folder $userFolder,
125
								IAppManager $appManager
126
	) {
127
		$this->tree = $tree;
128
		$this->fileView = $view;
129
		$this->tagManager = $tagManager;
130
		$this->tagMapper = $tagMapper;
131
		$this->fileTagger = $fileTagger;
132
		$this->userSession = $userSession;
133
		$this->groupManager = $groupManager;
134
		$this->userFolder = $userFolder;
135
		$this->appManager = $appManager;
136
	}
137
138
	/**
139
	 * This initializes the plugin.
140
	 *
141
	 * This function is called by \Sabre\DAV\Server, after
142
	 * addPlugin is called.
143
	 *
144
	 * This method should set up the required event subscriptions.
145
	 *
146
	 * @param \Sabre\DAV\Server $server
147
	 * @return void
148
	 */
149
	public function initialize(\Sabre\DAV\Server $server) {
150
151
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
152
153
		$this->server = $server;
154
		$this->server->on('report', array($this, 'onReport'));
155
	}
156
157
	/**
158
	 * Returns a list of reports this plugin supports.
159
	 *
160
	 * This will be used in the {DAV:}supported-report-set property.
161
	 *
162
	 * @param string $uri
163
	 * @return array
164
	 */
165
	public function getSupportedReportSet($uri) {
166
		return [self::REPORT_NAME];
167
	}
168
169
	/**
170
	 * REPORT operations to look for files
171
	 *
172
	 * @param string $reportName
173
	 * @param $report
174
	 * @param string $uri
175
	 * @return bool
176
	 * @throws BadRequest
177
	 * @throws PreconditionFailed
178
	 * @internal param $ [] $report
179
	 */
180
	public function onReport($reportName, $report, $uri) {
181
		$reportTargetNode = $this->server->tree->getNodeForPath($uri);
182
		if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
183
			return;
184
		}
185
186
		$ns = '{' . $this::NS_OWNCLOUD . '}';
187
		$requestedProps = [];
188
		$filterRules = [];
189
190
		// parse report properties and gather filter info
191
		foreach ($report as $reportProps) {
192
			$name = $reportProps['name'];
193
			if ($name === $ns . 'filter-rules') {
194
				$filterRules = $reportProps['value'];
195
			} else if ($name === '{DAV:}prop') {
196
				// propfind properties
197
				foreach ($reportProps['value'] as $propVal) {
198
					$requestedProps[] = $propVal['name'];
199
				}
200
			}
201
		}
202
203
		if (empty($filterRules)) {
204
			// an empty filter would return all existing files which would be slow
205
			throw new BadRequest('Missing filter-rule block in request');
206
		}
207
208
		// gather all file ids matching filter
209
		try {
210
			$resultFileIds = $this->processFilterRules($filterRules);
211
		} catch (TagNotFoundException $e) {
212
			throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
0 ignored issues
show
Unused Code introduced by
The call to Sabre\DAV\Exception\Prec...onFailed::__construct() has too many arguments starting with $e. ( Ignorable by Annotation )

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

212
			throw /** @scrutinizer ignore-call */ new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
213
		}
214
215
		// find sabre nodes by file id, restricted to the root node path
216
		$results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
217
218
		$filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath());
219
		$responses = $this->prepareResponses($filesUri, $requestedProps, $results);
220
221
		$xml = $this->server->xml->write(
222
			'{DAV:}multistatus',
223
			new MultiStatus($responses)
224
		);
225
226
		$this->server->httpResponse->setStatus(207);
227
		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
228
		$this->server->httpResponse->setBody($xml);
229
230
		return false;
231
	}
232
233
	/**
234
	 * Returns the base uri of the files root by removing
235
	 * the subpath from the URI
236
	 *
237
	 * @param string $uri URI from this request
238
	 * @param string $subPath subpath to remove from the URI
239
	 *
240
	 * @return string files base uri
241
	 */
242
	private function getFilesBaseUri($uri, $subPath) {
243
		$uri = trim($uri, '/');
244
		$subPath = trim($subPath, '/');
245
		if (empty($subPath)) {
246
			$filesUri = $uri;
247
		} else {
248
			$filesUri = substr($uri, 0, strlen($uri) - strlen($subPath));
249
		}
250
		$filesUri = trim($filesUri, '/');
251
		if (empty($filesUri)) {
252
			return '';
253
		}
254
		return '/' . $filesUri;
255
	}
256
257
	/**
258
	 * Find file ids matching the given filter rules
259
	 *
260
	 * @param array $filterRules
261
	 * @return array array of unique file id results
262
	 *
263
	 * @throws TagNotFoundException whenever a tag was not found
264
	 */
265
	protected function processFilterRules($filterRules) {
266
		$ns = '{' . $this::NS_OWNCLOUD . '}';
267
		$resultFileIds = null;
268
		$systemTagIds = [];
269
		$circlesIds = [];
270
		$favoriteFilter = null;
271
		foreach ($filterRules as $filterRule) {
272
			if ($filterRule['name'] === $ns . 'systemtag') {
273
				$systemTagIds[] = $filterRule['value'];
274
			}
275
			if ($filterRule['name'] === self::CIRCLE_PROPERTYNAME) {
276
				$circlesIds[] = $filterRule['value'];
277
			}
278
			if ($filterRule['name'] === $ns . 'favorite') {
279
				$favoriteFilter = true;
280
			}
281
282
		}
283
284
		if ($favoriteFilter !== null) {
285
			$resultFileIds = $this->fileTagger->load('files')->getFavorites();
286
			if (empty($resultFileIds)) {
287
				return [];
288
			}
289
		}
290
291
		if (!empty($systemTagIds)) {
292
			$fileIds = $this->getSystemTagFileIds($systemTagIds);
293
			if (empty($resultFileIds)) {
294
				$resultFileIds = $fileIds;
295
			} else {
296
				$resultFileIds = array_intersect($fileIds, $resultFileIds);
297
			}
298
		}
299
300
		if (!empty($circlesIds)) {
301
			$fileIds = $this->getCirclesFileIds($circlesIds);
302
			if (empty($resultFileIds)) {
303
				$resultFileIds = $fileIds;
304
			} else {
305
				$resultFileIds = array_intersect($fileIds, $resultFileIds);
306
			}
307
		}
308
309
		return $resultFileIds;
310
	}
311
312
	private function getSystemTagFileIds($systemTagIds) {
313
		$resultFileIds = null;
314
315
		// check user permissions, if applicable
316
		if (!$this->isAdmin()) {
317
			// check visibility/permission
318
			$tags = $this->tagManager->getTagsByIds($systemTagIds);
319
			$unknownTagIds = [];
320
			foreach ($tags as $tag) {
321
				if (!$tag->isUserVisible()) {
322
					$unknownTagIds[] = $tag->getId();
323
				}
324
			}
325
326
			if (!empty($unknownTagIds)) {
327
				throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
328
			}
329
		}
330
331
		// fetch all file ids and intersect them
332
		foreach ($systemTagIds as $systemTagId) {
333
			$fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
334
335
			if (empty($fileIds)) {
336
				// This tag has no files, nothing can ever show up
337
				return [];
338
			}
339
340
			// first run ?
341
			if ($resultFileIds === null) {
342
				$resultFileIds = $fileIds;
343
			} else {
344
				$resultFileIds = array_intersect($resultFileIds, $fileIds);
345
			}
346
347
			if (empty($resultFileIds)) {
348
				// Empty intersection, nothing can show up anymore
349
				return [];
350
			}
351
		}
352
		return $resultFileIds;
353
	}
354
355
	/**
356
	 * @suppress PhanUndeclaredClassMethod
357
	 * @param array $circlesIds
358
	 * @return array
359
	 */
360
	private function getCirclesFileIds(array $circlesIds) {
361
		if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
362
			return [];
363
		}
364
		return \OCA\Circles\Api\v1\Circles::getFilesForCircles($circlesIds);
0 ignored issues
show
Bug introduced by
The type OCA\Circles\Api\v1\Circles was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
365
	}
366
367
368
	/**
369
	 * Prepare propfind response for the given nodes
370
	 *
371
	 * @param string $filesUri $filesUri URI leading to root of the files URI,
372
	 * with a leading slash but no trailing slash
373
	 * @param string[] $requestedProps requested properties
374
	 * @param Node[] nodes nodes for which to fetch and prepare responses
0 ignored issues
show
Bug introduced by
The type OCA\DAV\Connector\Sabre\nodes was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
375
	 * @return Response[]
376
	 */
377
	public function prepareResponses($filesUri, $requestedProps, $nodes) {
378
		$responses = [];
379
		foreach ($nodes as $node) {
380
			$propFind = new PropFind($filesUri . $node->getPath(), $requestedProps);
381
382
			$this->server->getPropertiesByNode($propFind, $node);
383
			// copied from Sabre Server's getPropertiesForPath
384
			$result = $propFind->getResultForMultiStatus();
385
			$result['href'] = $propFind->getPath();
386
387
			$resourceType = $this->server->getResourceTypeForNode($node);
388
			if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
389
				$result['href'] .= '/';
390
			}
391
392
			$responses[] = new Response(
393
				rtrim($this->server->getBaseUri(), '/') . $filesUri . $node->getPath(),
394
				$result,
395
				200
396
			);
397
		}
398
		return $responses;
399
	}
400
401
	/**
402
	 * Find Sabre nodes by file ids
403
	 *
404
	 * @param Node $rootNode root node for search
405
	 * @param array $fileIds file ids
406
	 * @return Node[] array of Sabre nodes
407
	 */
408
	public function findNodesByFileIds($rootNode, $fileIds) {
409
		$folder = $this->userFolder;
410
		if (trim($rootNode->getPath(), '/') !== '') {
411
			$folder = $folder->get($rootNode->getPath());
412
		}
413
414
		$results = [];
415
		foreach ($fileIds as $fileId) {
416
			$entry = $folder->getById($fileId);
0 ignored issues
show
Bug introduced by
The method getById() does not exist on OCP\Files\Node. Did you maybe mean getId()? ( Ignorable by Annotation )

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

416
			/** @scrutinizer ignore-call */ 
417
   $entry = $folder->getById($fileId);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
417
			if ($entry) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entry of type OCP\Files\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
418
				$entry = current($entry);
419
				if ($entry instanceof \OCP\Files\File) {
420
					$results[] = new File($this->fileView, $entry);
421
				} else if ($entry instanceof \OCP\Files\Folder) {
422
					$results[] = new Directory($this->fileView, $entry);
423
				}
424
			}
425
		}
426
427
		return $results;
428
	}
429
430
	/**
431
	 * Returns whether the currently logged in user is an administrator
432
	 */
433
	private function isAdmin() {
434
		$user = $this->userSession->getUser();
435
		if ($user !== null) {
436
			return $this->groupManager->isAdmin($user->getUID());
437
		}
438
		return false;
439
	}
440
}
441