Completed
Push — master ( 830834...005b3d )
by Thomas
10:38
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
42
class FilesReportPlugin extends ServerPlugin {
43
44
	// namespace
45
	const NS_OWNCLOUD = 'http://owncloud.org/ns';
46
	const REPORT_NAME            = '{http://owncloud.org/ns}filter-files';
47
	const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
48
49
	/**
50
	 * Reference to main server object
51
	 *
52
	 * @var \Sabre\DAV\Server
53
	 */
54
	private $server;
55
56
	/**
57
	 * @var Tree
58
	 */
59
	private $tree;
60
61
	/**
62
	 * @var View
63
	 */
64
	private $fileView;
65
66
	/**
67
	 * @var ISystemTagManager
68
	 */
69
	private $tagManager;
70
71
	/**
72
	 * @var ISystemTagObjectMapper
73
	 */
74
	private $tagMapper;
75
76
	/**
77
	 * @var IUserSession
78
	 */
79
	private $userSession;
80
81
	/**
82
	 * @var IGroupManager
83
	 */
84
	private $groupManager;
85
86
	/**
87
	 * @var Folder
88
	 */
89
	private $userFolder;
90
91
	/**
92
	 * @param Tree $tree
93
	 * @param View $view
94
	 */
95 View Code Duplication
	public function __construct(Tree $tree,
96
								View $view,
97
								ISystemTagManager $tagManager,
98
								ISystemTagObjectMapper $tagMapper,
99
								IUserSession $userSession,
100
								IGroupManager $groupManager,
101
								Folder $userFolder
102
	) {
103
		$this->tree = $tree;
104
		$this->fileView = $view;
105
		$this->tagManager = $tagManager;
106
		$this->tagMapper = $tagMapper;
107
		$this->userSession = $userSession;
108
		$this->groupManager = $groupManager;
109
		$this->userFolder = $userFolder;
110
	}
111
112
	/**
113
	 * This initializes the plugin.
114
	 *
115
	 * This function is called by \Sabre\DAV\Server, after
116
	 * addPlugin is called.
117
	 *
118
	 * This method should set up the required event subscriptions.
119
	 *
120
	 * @param \Sabre\DAV\Server $server
121
	 * @return void
122
	 */
123
	public function initialize(\Sabre\DAV\Server $server) {
124
125
		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
126
127
		$this->server = $server;
128
		$this->server->on('report', array($this, 'onReport'));
129
	}
130
131
	/**
132
	 * Returns a list of reports this plugin supports.
133
	 *
134
	 * This will be used in the {DAV:}supported-report-set property.
135
	 *
136
	 * @param string $uri
137
	 * @return array
138
	 */
139
	public function getSupportedReportSet($uri) {
140
		return [self::REPORT_NAME];
141
	}
142
143
	/**
144
	 * REPORT operations to look for files
145
	 *
146
	 * @param string $reportName
147
	 * @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...
148
	 * @param string $uri
149
	 * @return bool
150
	 * @throws NotFound
151
	 * @throws ReportNotSupported
152
	 */
153
	public function onReport($reportName, $report, $uri) {
154
		$reportTargetNode = $this->server->tree->getNodeForPath($uri);
155
		if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
156
			throw new ReportNotSupported();
157
		}
158
159
		$ns = '{' . $this::NS_OWNCLOUD . '}';
160
		$requestedProps = [];
161
		$filterRules = [];
162
163
		// parse report properties and gather filter info
164
		foreach ($report as $reportProps) {
165
			$name = $reportProps['name'];
166
			if ($name === $ns . 'filter-rules') {
167
				$filterRules = $reportProps['value'];
168
			} else if ($name === '{DAV:}prop') {
169
				// propfind properties
170
				foreach ($reportProps['value'] as $propVal) {
171
					$requestedProps[] = $propVal['name'];
172
				}
173
			}
174
		}
175
176
		if (empty($filterRules)) {
177
			// an empty filter would return all existing files which would be slow
178
			throw new BadRequest('Missing filter-rule block in request');
179
		}
180
181
		// gather all file ids matching filter
182
		try {
183
			$resultFileIds = $this->processFilterRules($filterRules);
184
		} catch (TagNotFoundException $e) {
185
			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...
186
		}
187
188
		// find sabre nodes by file id, restricted to the root node path
189
		$results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
0 ignored issues
show
Bug introduced by
It seems like $resultFileIds defined by $this->processFilterRules($filterRules) on line 183 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...
190
191
		$responses = $this->prepareResponses($requestedProps, $results);
192
193
		$xml = $this->server->xml->write(
194
			'{DAV:}multistatus',
195
			new MultiStatus($responses)
196
		);
197
198
		$this->server->httpResponse->setStatus(207);
199
		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
200
		$this->server->httpResponse->setBody($xml);
201
202
		return false;
203
	}
204
205
	/**
206
	 * Find file ids matching the given filter rules
207
	 *
208
	 * @param array $filterRules
209
	 * @return array array of unique file id results
210
	 *
211
	 * @throws TagNotFoundException whenever a tag was not found
212
	 */
213
	protected function processFilterRules($filterRules) {
214
		$ns = '{' . $this::NS_OWNCLOUD . '}';
215
		$resultFileIds = null;
216
		$systemTagIds = [];
217
		foreach ($filterRules as $filterRule) {
218
			if ($filterRule['name'] === $ns . 'systemtag') {
219
				$systemTagIds[] = $filterRule['value'];
220
			}
221
		}
222
223
		// check user permissions, if applicable
224
		if (!$this->isAdmin()) {
225
			// check visibility/permission
226
			$tags = $this->tagManager->getTagsByIds($systemTagIds);
227
			$unknownTagIds = [];
228
			foreach ($tags as $tag) {
229
				if (!$tag->isUserVisible()) {
230
					$unknownTagIds[] = $tag->getId();
231
				}
232
			}
233
234
			if (!empty($unknownTagIds)) {
235
				throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
236
			}
237
		}
238
239
		// fetch all file ids and intersect them
240
		foreach ($systemTagIds as $systemTagId) {
241
			$fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
242
243
			if (empty($fileIds)) {
244
				// This tag has no files, nothing can ever show up
245
				return [];
246
			}
247
248
			// first run ?
249
			if ($resultFileIds === null) {
250
				$resultFileIds = $fileIds;
251
			} else {
252
				$resultFileIds = array_intersect($resultFileIds, $fileIds);
253
			}
254
255
			if (empty($resultFileIds)) {
256
				// Empty intersection, nothing can show up anymore
257
				return [];
258
			}
259
		}
260
		return $resultFileIds;
261
	}
262
263
	/**
264
	 * Prepare propfind response for the given nodes
265
	 *
266
	 * @param string[] $requestedProps requested properties
267
	 * @param Node[] nodes nodes for which to fetch and prepare responses
268
	 * @return Response[]
269
	 */
270
	public function prepareResponses($requestedProps, $nodes) {
271
		$responses = [];
272
		foreach ($nodes as $node) {
273
			$propFind = new PropFind($node->getPath(), $requestedProps);
274
275
			$this->server->getPropertiesByNode($propFind, $node);
276
			// copied from Sabre Server's getPropertiesForPath
277
			$result = $propFind->getResultForMultiStatus();
278
			$result['href'] = $propFind->getPath();
279
280
			$resourceType = $this->server->getResourceTypeForNode($node);
281
			if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
282
				$result['href'] .= '/';
283
			}
284
285
			$responses[] = new Response(
286
				rtrim($this->server->getBaseUri(), '/') . $node->getPath(),
287
				$result,
288
				200
289
			);
290
		}
291
		return $responses;
292
	}
293
294
	/**
295
	 * Find Sabre nodes by file ids
296
	 *
297
	 * @param Node $rootNode root node for search
298
	 * @param array $fileIds file ids
299
	 * @return Node[] array of Sabre nodes
300
	 */
301
	public function findNodesByFileIds($rootNode, $fileIds) {
302
		$folder = $this->userFolder;
303
		if (trim($rootNode->getPath(), '/') !== '') {
304
			$folder = $folder->get($rootNode->getPath());
305
		}
306
307
		$results = [];
308
		foreach ($fileIds as $fileId) {
309
			$entry = $folder->getById($fileId);
310
			if ($entry) {
311
				$entry = current($entry);
312
				if ($entry instanceof \OCP\Files\File) {
313
					$results[] = new File($this->fileView, $entry);
314
				} else if ($entry instanceof \OCP\Files\Folder) {
315
					$results[] = new Directory($this->fileView, $entry);
316
				}
317
			}
318
		}
319
320
		return $results;
321
	}
322
323
	/**
324
	 * Returns whether the currently logged in user is an administrator
325
	 */
326 View Code Duplication
	private function isAdmin() {
327
		$user = $this->userSession->getUser();
328
		if ($user !== null) {
329
			return $this->groupManager->isAdmin($user->getUID());
330
		}
331
		return false;
332
	}
333
}
334