Completed
Pull Request — master (#26700)
by Philipp
08:19
created

apps/dav/lib/Connector/Sabre/FilesReportPlugin.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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', [$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
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
			return;
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);
202
		}
203
204
		// find sabre nodes by file id, restricted to the root node path
205
		$results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
206
207
		$filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath());
208
		$responses = $this->prepareResponses($filesUri, $requestedProps, $results);
209
210
		$xml = $this->server->xml->write(
211
			'{DAV:}multistatus',
212
			new MultiStatus($responses)
213
		);
214
215
		$this->server->httpResponse->setStatus(207);
216
		$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
217
		$this->server->httpResponse->setBody($xml);
218
219
		return false;
220
	}
221
222
	/**
223
	 * Returns the base uri of the files root by removing
224
	 * the subpath from the URI
225
	 *
226
	 * @param string $uri URI from this request
227
	 * @param string $subPath subpath to remove from the URI
228
	 *
229
	 * @return string files base uri
230
	 */
231
	private function getFilesBaseUri($uri, $subPath) {
232
		$uri = trim($uri, '/');
233
		$subPath = trim($subPath, '/');
234
		$filesUri = '';
0 ignored issues
show
$filesUri is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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