Completed
Push — master ( 812bae...fa8bfe )
by Thomas
13:53
created

FilesReportPlugin::findNodesByFileIds()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 10
nop 2
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Joas Schilling <[email protected]>
4
 * @author Vincent Petry <[email protected]>
5
 *
6
 * @copyright Copyright (c) 2016, ownCloud GmbH.
7
 * @license AGPL-3.0
8
 *
9
 * This code is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License, version 3,
11
 * as published by the Free Software Foundation.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Affero General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public License, version 3,
19
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
20
 *
21
 */
22
23
namespace OCA\DAV\Connector\Sabre;
24
25
use OC\Files\View;
26
use Sabre\DAV\Exception\NotFound;
27
use Sabre\DAV\Exception\PreconditionFailed;
28
use Sabre\DAV\Exception\ReportNotSupported;
29
use Sabre\DAV\Exception\BadRequest;
30
use Sabre\DAV\ServerPlugin;
31
use Sabre\DAV\Tree;
32
use Sabre\DAV\Xml\Element\Response;
33
use Sabre\DAV\Xml\Response\MultiStatus;
34
use Sabre\DAV\PropFind;
35
use OCP\SystemTag\ISystemTagObjectMapper;
36
use OCP\IUserSession;
37
use OCP\Files\Folder;
38
use OCP\IGroupManager;
39
use OCP\SystemTag\ISystemTagManager;
40
use OCP\SystemTag\TagNotFoundException;
41
use OCP\ITagManager;
42
43
class FilesReportPlugin extends ServerPlugin {
44
45
	// namespace
46
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
47
	const REPORT_NAME            = '{http://owncloud.org/ns}filter-files';
48
	const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
49
50
	/**
51
	 * Reference to main server object
52
	 *
53
	 * @var \Sabre\DAV\Server
54
	 */
55
	private $server;
56
57
	/**
58
	 * @var Tree
59
	 */
60
	private $tree;
61
62
	/**
63
	 * @var View
64
	 */
65
	private $fileView;
66
67
	/**
68
	 * @var ISystemTagManager
69
	 */
70
	private $tagManager;
71
72
	/**
73
	 * @var ISystemTagObjectMapper
74
	 */
75
	private $tagMapper;
76
77
	/**
78
	 * Manager for private tags
79
	 *
80
	 * @var ITagManager
81
	 */
82
	private $fileTagger;
83
84
	/**
85
	 * @var IUserSession
86
	 */
87
	private $userSession;
88
89
	/**
90
	 * @var IGroupManager
91
	 */
92
	private $groupManager;
93
94
	/**
95
	 * @var Folder
96
	 */
97
	private $userFolder;
98
99
	/**
100
	 * @param Tree $tree
101
	 * @param View $view
102
	 * @param ISystemTagManager $tagManager
103
	 * @param ISystemTagObjectMapper $tagMapper
104
	 * @param ITagManager $fileTagger manager for private tags
105
	 * @param IUserSession $userSession
106
	 * @param IGroupManager $groupManager
107
	 * @param Folder $userfolder
0 ignored issues
show
Documentation introduced by
There is no parameter named $userfolder. Did you maybe mean $userFolder?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
108
	 */
109
	public function __construct(Tree $tree,
110
								View $view,
111
								ISystemTagManager $tagManager,
112
								ISystemTagObjectMapper $tagMapper,
113
								ITagManager $fileTagger,
114
								IUserSession $userSession,
115
								IGroupManager $groupManager,
116
								Folder $userFolder
117
	) {
118
		$this->tree = $tree;
119
		$this->fileView = $view;
120
		$this->tagManager = $tagManager;
121
		$this->tagMapper = $tagMapper;
122
		$this->fileTagger = $fileTagger;
123
		$this->userSession = $userSession;
124
		$this->groupManager = $groupManager;
125
		$this->userFolder = $userFolder;
126
	}
127
128
	/**
129
	 * This initializes the plugin.
130
	 *
131
	 * This function is called by \Sabre\DAV\Server, after
132
	 * addPlugin is called.
133
	 *
134
	 * This method should set up the required event subscriptions.
135
	 *
136
	 * @param \Sabre\DAV\Server $server
137
	 * @return void
138
	 */
139
	public function initialize(\Sabre\DAV\Server $server) {
140
141
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
142
143
		$this->server = $server;
144
		$this->server->on('report', array($this, 'onReport'));
145
	}
146
147
	/**
148
	 * Returns a list of reports this plugin supports.
149
	 *
150
	 * This will be used in the {DAV:}supported-report-set property.
151
	 *
152
	 * @param string $uri
153
	 * @return array
154
	 */
155
	public function getSupportedReportSet($uri) {
156
		return [self::REPORT_NAME];
157
	}
158
159
	/**
160
	 * REPORT operations to look for files
161
	 *
162
	 * @param string $reportName
163
	 * @param [] $report
0 ignored issues
show
Documentation introduced by
The doc-type [] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
164
	 * @param string $uri
165
	 * @return bool
166
	 * @throws NotFound
167
	 * @throws ReportNotSupported
168
	 */
169
	public function onReport($reportName, $report, $uri) {
170
		$reportTargetNode = $this->server->tree->getNodeForPath($uri);
171
		if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
172
			throw new ReportNotSupported();
173
		}
174
175
		$ns = '{' . $this::NS_OWNCLOUD . '}';
176
		$requestedProps = [];
177
		$filterRules = [];
178
179
		// parse report properties and gather filter info
180
		foreach ($report as $reportProps) {
181
			$name = $reportProps['name'];
182
			if ($name === $ns . 'filter-rules') {
183
				$filterRules = $reportProps['value'];
184
			} else if ($name === '{DAV:}prop') {
185
				// propfind properties
186
				foreach ($reportProps['value'] as $propVal) {
187
					$requestedProps[] = $propVal['name'];
188
				}
189
			}
190
		}
191
192
		if (empty($filterRules)) {
193
			// an empty filter would return all existing files which would be slow
194
			throw new BadRequest('Missing filter-rule block in request');
195
		}
196
197
		// gather all file ids matching filter
198
		try {
199
			$resultFileIds = $this->processFilterRules($filterRules);
200
		} catch (TagNotFoundException $e) {
201
			throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
0 ignored issues
show
Unused Code introduced by
The call to PreconditionFailed::__construct() has too many arguments starting with $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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
202
		}
203
204
		// find sabre nodes by file id, restricted to the root node path
205
		$results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
0 ignored issues
show
Bug introduced by
It seems like $resultFileIds defined by $this->processFilterRules($filterRules) on line 199 can also be of type null; however, OCA\DAV\Connector\Sabre\...n::findNodesByFileIds() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
206
207
		$responses = $this->prepareResponses($requestedProps, $results);
208
209
		$xml = $this->server->xml->write(
210
			'{DAV:}multistatus',
211
			new MultiStatus($responses)
212
		);
213
214
		$this->server->httpResponse->setStatus(207);
215
		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
216
		$this->server->httpResponse->setBody($xml);
217
218
		return false;
219
	}
220
221
	/**
222
	 * Find file ids matching the given filter rules
223
	 *
224
	 * @param array $filterRules
225
	 * @return array array of unique file id results
226
	 *
227
	 * @throws TagNotFoundException whenever a tag was not found
228
	 */
229
	protected function processFilterRules($filterRules) {
230
		$ns = '{' . $this::NS_OWNCLOUD . '}';
231
		$resultFileIds = null;
232
		$systemTagIds = [];
233
		$favoriteFilter = null;
234
		foreach ($filterRules as $filterRule) {
235
			if ($filterRule['name'] === $ns . 'systemtag') {
236
				$systemTagIds[] = $filterRule['value'];
237
			}
238
			if ($filterRule['name'] === $ns . 'favorite') {
239
				$favoriteFilter = true;
240
			}
241
		}
242
243
		if ($favoriteFilter !== null) {
244
			$resultFileIds = $this->fileTagger->load('files')->getFavorites();
245
			if (empty($resultFileIds)) {
246
				return [];
247
			}
248
		}
249
250
		if (!empty($systemTagIds)) {
251
			$fileIds = $this->getSystemTagFileIds($systemTagIds);
252
			if (empty($resultFileIds)) {
253
				$resultFileIds = $fileIds;
254
			} else {
255
				$resultFileIds = array_intersect($fileIds, $resultFileIds);
256
			}
257
		}
258
259
		return $resultFileIds;
260
	}
261
262
	private function getSystemTagFileIds($systemTagIds) {
263
		$resultFileIds = null;
264
265
		// check user permissions, if applicable
266
		if (!$this->isAdmin()) {
267
			// check visibility/permission
268
			$tags = $this->tagManager->getTagsByIds($systemTagIds);
269
			$unknownTagIds = [];
270
			foreach ($tags as $tag) {
271
				if (!$tag->isUserVisible()) {
272
					$unknownTagIds[] = $tag->getId();
273
				}
274
			}
275
276
			if (!empty($unknownTagIds)) {
277
				throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
278
			}
279
		}
280
281
		// fetch all file ids and intersect them
282
		foreach ($systemTagIds as $systemTagId) {
283
			$fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
284
285
			if (empty($fileIds)) {
286
				// This tag has no files, nothing can ever show up
287
				return [];
288
			}
289
290
			// first run ?
291
			if ($resultFileIds === null) {
292
				$resultFileIds = $fileIds;
293
			} else {
294
				$resultFileIds = array_intersect($resultFileIds, $fileIds);
295
			}
296
297
			if (empty($resultFileIds)) {
298
				// Empty intersection, nothing can show up anymore
299
				return [];
300
			}
301
		}
302
		return $resultFileIds;
303
	}
304
305
	/**
306
	 * Prepare propfind response for the given nodes
307
	 *
308
	 * @param string[] $requestedProps requested properties
309
	 * @param Node[] nodes nodes for which to fetch and prepare responses
310
	 * @return Response[]
311
	 */
312
	public function prepareResponses($requestedProps, $nodes) {
313
		$responses = [];
314
		foreach ($nodes as $node) {
315
			$propFind = new PropFind($node->getPath(), $requestedProps);
316
317
			$this->server->getPropertiesByNode($propFind, $node);
318
			// copied from Sabre Server's getPropertiesForPath
319
			$result = $propFind->getResultForMultiStatus();
320
			$result['href'] = $propFind->getPath();
321
322
			$resourceType = $this->server->getResourceTypeForNode($node);
323
			if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
324
				$result['href'] .= '/';
325
			}
326
327
			$responses[] = new Response(
328
				rtrim($this->server->getBaseUri(), '/') . $node->getPath(),
329
				$result,
330
				200
331
			);
332
		}
333
		return $responses;
334
	}
335
336
	/**
337
	 * Find Sabre nodes by file ids
338
	 *
339
	 * @param Node $rootNode root node for search
340
	 * @param array $fileIds file ids
341
	 * @return Node[] array of Sabre nodes
342
	 */
343
	public function findNodesByFileIds($rootNode, $fileIds) {
344
		$folder = $this->userFolder;
345
		if (trim($rootNode->getPath(), '/') !== '') {
346
			$folder = $folder->get($rootNode->getPath());
347
		}
348
349
		$results = [];
350
		foreach ($fileIds as $fileId) {
351
			$entry = $folder->getById($fileId);
352
			if ($entry) {
353
				$entry = current($entry);
354
				if ($entry instanceof \OCP\Files\File) {
355
					$results[] = new File($this->fileView, $entry);
356
				} else if ($entry instanceof \OCP\Files\Folder) {
357
					$results[] = new Directory($this->fileView, $entry);
358
				}
359
			}
360
		}
361
362
		return $results;
363
	}
364
365
	/**
366
	 * Returns whether the currently logged in user is an administrator
367
	 */
368 View Code Duplication
	private function isAdmin() {
369
		$user = $this->userSession->getUser();
370
		if ($user !== null) {
371
			return $this->groupManager->isAdmin($user->getUID());
372
		}
373
		return false;
374
	}
375
}
376