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

apps/dav/lib/Connector/Sabre/FilesReportPlugin.php (1 issue)

Check for PhpDoc comments which do parse

Documentation Minor

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
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 = '';
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