Passed
Push — master ( e17684...b6c034 )
by Blizzz
35:05 queued 17:14
created
lib/private/Files/Node/Folder.php 1 patch
Indentation   +398 added lines, -398 removed lines patch added patch discarded remove patch
@@ -50,402 +50,402 @@
 block discarded – undo
50 50
 use OCP\IUserManager;
51 51
 
52 52
 class Folder extends Node implements \OCP\Files\Folder {
53
-	/**
54
-	 * Creates a Folder that represents a non-existing path
55
-	 *
56
-	 * @param string $path path
57
-	 * @return NonExistingFolder non-existing node
58
-	 */
59
-	protected function createNonExistingNode($path) {
60
-		return new NonExistingFolder($this->root, $this->view, $path);
61
-	}
62
-
63
-	/**
64
-	 * @param string $path path relative to the folder
65
-	 * @return string
66
-	 * @throws \OCP\Files\NotPermittedException
67
-	 */
68
-	public function getFullPath($path) {
69
-		$path = $this->normalizePath($path);
70
-		if (!$this->isValidPath($path)) {
71
-			throw new NotPermittedException('Invalid path "' . $path . '"');
72
-		}
73
-		return $this->path . $path;
74
-	}
75
-
76
-	/**
77
-	 * @param string $path
78
-	 * @return string|null
79
-	 */
80
-	public function getRelativePath($path) {
81
-		return PathHelper::getRelativePath($this->getPath(), $path);
82
-	}
83
-
84
-	/**
85
-	 * check if a node is a (grand-)child of the folder
86
-	 *
87
-	 * @param \OC\Files\Node\Node $node
88
-	 * @return bool
89
-	 */
90
-	public function isSubNode($node) {
91
-		return strpos($node->getPath(), $this->path . '/') === 0;
92
-	}
93
-
94
-	/**
95
-	 * get the content of this directory
96
-	 *
97
-	 * @return Node[]
98
-	 * @throws \OCP\Files\NotFoundException
99
-	 */
100
-	public function getDirectoryListing() {
101
-		$folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
102
-
103
-		return array_map(function (FileInfo $info) {
104
-			if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
105
-				return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
106
-			} else {
107
-				return new File($this->root, $this->view, $info->getPath(), $info, $this);
108
-			}
109
-		}, $folderContent);
110
-	}
111
-
112
-	/**
113
-	 * @param string $path
114
-	 * @param FileInfo $info
115
-	 * @return File|Folder
116
-	 */
117
-	protected function createNode($path, FileInfo $info = null, bool $infoHasSubMountsIncluded = true) {
118
-		if (is_null($info)) {
119
-			$isDir = $this->view->is_dir($path);
120
-		} else {
121
-			$isDir = $info->getType() === FileInfo::TYPE_FOLDER;
122
-		}
123
-		$parent = dirname($path) === $this->getPath() ? $this : null;
124
-		if ($isDir) {
125
-			return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded);
126
-		} else {
127
-			return new File($this->root, $this->view, $path, $info, $parent);
128
-		}
129
-	}
130
-
131
-	/**
132
-	 * Get the node at $path
133
-	 *
134
-	 * @param string $path
135
-	 * @return \OC\Files\Node\Node
136
-	 * @throws \OCP\Files\NotFoundException
137
-	 */
138
-	public function get($path) {
139
-		return $this->root->get($this->getFullPath($path));
140
-	}
141
-
142
-	/**
143
-	 * @param string $path
144
-	 * @return bool
145
-	 */
146
-	public function nodeExists($path) {
147
-		try {
148
-			$this->get($path);
149
-			return true;
150
-		} catch (NotFoundException $e) {
151
-			return false;
152
-		}
153
-	}
154
-
155
-	/**
156
-	 * @param string $path
157
-	 * @return \OC\Files\Node\Folder
158
-	 * @throws \OCP\Files\NotPermittedException
159
-	 */
160
-	public function newFolder($path) {
161
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
162
-			$fullPath = $this->getFullPath($path);
163
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
164
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
165
-			if (!$this->view->mkdir($fullPath)) {
166
-				throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
167
-			}
168
-			$parent = dirname($fullPath) === $this->getPath() ? $this : null;
169
-			$node = new Folder($this->root, $this->view, $fullPath, null, $parent);
170
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
171
-			return $node;
172
-		} else {
173
-			throw new NotPermittedException('No create permission for folder "' . $path . '"');
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * @param string $path
179
-	 * @param string | resource | null $content
180
-	 * @return \OC\Files\Node\File
181
-	 * @throws \OCP\Files\NotPermittedException
182
-	 */
183
-	public function newFile($path, $content = null) {
184
-		if (empty($path)) {
185
-			throw new NotPermittedException('Could not create as provided path is empty');
186
-		}
187
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
188
-			$fullPath = $this->getFullPath($path);
189
-			$nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
190
-			$this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
191
-			if ($content !== null) {
192
-				$result = $this->view->file_put_contents($fullPath, $content);
193
-			} else {
194
-				$result = $this->view->touch($fullPath);
195
-			}
196
-			if ($result === false) {
197
-				throw new NotPermittedException('Could not create path "' . $fullPath . '"');
198
-			}
199
-			$node = new File($this->root, $this->view, $fullPath, null, $this);
200
-			$this->sendHooks(['postWrite', 'postCreate'], [$node]);
201
-			return $node;
202
-		}
203
-		throw new NotPermittedException('No create permission for path "' . $path . '"');
204
-	}
205
-
206
-	private function queryFromOperator(ISearchOperator $operator, string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
207
-		if ($uid === null) {
208
-			$user = null;
209
-		} else {
210
-			/** @var IUserManager $userManager */
211
-			$userManager = \OC::$server->query(IUserManager::class);
212
-			$user = $userManager->get($uid);
213
-		}
214
-		return new SearchQuery($operator, $limit, $offset, [], $user);
215
-	}
216
-
217
-	/**
218
-	 * search for files with the name matching $query
219
-	 *
220
-	 * @param string|ISearchQuery $query
221
-	 * @return \OC\Files\Node\Node[]
222
-	 */
223
-	public function search($query) {
224
-		if (is_string($query)) {
225
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
226
-		}
227
-
228
-		// search is handled by a single query covering all caches that this folder contains
229
-		// this is done by collect
230
-
231
-		$limitToHome = $query->limitToHome();
232
-		if ($limitToHome && count(explode('/', $this->path)) !== 3) {
233
-			throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
234
-		}
235
-
236
-		/** @var QuerySearchHelper $searchHelper */
237
-		$searchHelper = \OC::$server->get(QuerySearchHelper::class);
238
-		[$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome);
239
-		$resultsPerCache = $searchHelper->searchInCaches($query, $caches);
240
-
241
-		// loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
242
-		$files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
243
-			$mount = $mountByMountPoint[$relativeMountPoint];
244
-			return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
245
-				return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
246
-			}, $results);
247
-		}, array_values($resultsPerCache), array_keys($resultsPerCache)));
248
-
249
-		// don't include this folder in the results
250
-		$files = array_filter($files, function (FileInfo $file) {
251
-			return $file->getPath() !== $this->getPath();
252
-		});
253
-
254
-		// since results were returned per-cache, they are no longer fully sorted
255
-		$order = $query->getOrder();
256
-		if ($order) {
257
-			usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
258
-				foreach ($order as $orderField) {
259
-					$cmp = $orderField->sortFileInfo($a, $b);
260
-					if ($cmp !== 0) {
261
-						return $cmp;
262
-					}
263
-				}
264
-				return 0;
265
-			});
266
-		}
267
-
268
-		return array_map(function (FileInfo $file) {
269
-			return $this->createNode($file->getPath(), $file);
270
-		}, $files);
271
-	}
272
-
273
-	private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
274
-		$cacheEntry['internalPath'] = $cacheEntry['path'];
275
-		$cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
276
-		$subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
277
-		return new \OC\Files\FileInfo($this->path . $subPath, $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
278
-	}
279
-
280
-	/**
281
-	 * search for files by mimetype
282
-	 *
283
-	 * @param string $mimetype
284
-	 * @return Node[]
285
-	 */
286
-	public function searchByMime($mimetype) {
287
-		if (strpos($mimetype, '/') === false) {
288
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
289
-		} else {
290
-			$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
291
-		}
292
-		return $this->search($query);
293
-	}
294
-
295
-	/**
296
-	 * search for files by tag
297
-	 *
298
-	 * @param string|int $tag name or tag id
299
-	 * @param string $userId owner of the tags
300
-	 * @return Node[]
301
-	 */
302
-	public function searchByTag($tag, $userId) {
303
-		$query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
304
-		return $this->search($query);
305
-	}
306
-
307
-	/**
308
-	 * @param int $id
309
-	 * @return \OC\Files\Node\Node[]
310
-	 */
311
-	public function getById($id) {
312
-		return $this->root->getByIdInPath((int)$id, $this->getPath());
313
-	}
314
-
315
-	protected function getAppDataDirectoryName(): string {
316
-		$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
317
-		return 'appdata_' . $instanceId;
318
-	}
319
-
320
-	/**
321
-	 * In case the path we are currently in is inside the appdata_* folder,
322
-	 * the original getById method does not work, because it can only look inside
323
-	 * the user's mount points. But the user has no mount point for the root storage.
324
-	 *
325
-	 * So in that case we directly check the mount of the root if it contains
326
-	 * the id. If it does we check if the path is inside the path we are working
327
-	 * in.
328
-	 *
329
-	 * @param int $id
330
-	 * @return array
331
-	 */
332
-	protected function getByIdInRootMount(int $id): array {
333
-		$mount = $this->root->getMount('');
334
-		$cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
335
-		if (!$cacheEntry) {
336
-			return [];
337
-		}
338
-
339
-		$absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
340
-		$currentPath = rtrim($this->path, '/') . '/';
341
-
342
-		if (strpos($absolutePath, $currentPath) !== 0) {
343
-			return [];
344
-		}
345
-
346
-		return [$this->root->createNode(
347
-			$absolutePath, new \OC\Files\FileInfo(
348
-				$absolutePath,
349
-				$mount->getStorage(),
350
-				$cacheEntry->getPath(),
351
-				$cacheEntry,
352
-				$mount
353
-			))];
354
-	}
355
-
356
-	public function getFreeSpace() {
357
-		return $this->view->free_space($this->path);
358
-	}
359
-
360
-	public function delete() {
361
-		if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
362
-			$this->sendHooks(['preDelete']);
363
-			$fileInfo = $this->getFileInfo();
364
-			$this->view->rmdir($this->path);
365
-			$nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
366
-			$this->sendHooks(['postDelete'], [$nonExisting]);
367
-		} else {
368
-			throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
369
-		}
370
-	}
371
-
372
-	/**
373
-	 * Add a suffix to the name in case the file exists
374
-	 *
375
-	 * @param string $name
376
-	 * @return string
377
-	 * @throws NotPermittedException
378
-	 */
379
-	public function getNonExistingName($name) {
380
-		$uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
381
-		return trim($this->getRelativePath($uniqueName), '/');
382
-	}
383
-
384
-	/**
385
-	 * @param int $limit
386
-	 * @param int $offset
387
-	 * @return \OCP\Files\Node[]
388
-	 */
389
-	public function getRecent($limit, $offset = 0) {
390
-		$filterOutNonEmptyFolder = new SearchBinaryOperator(
391
-			// filter out non empty folders
392
-			ISearchBinaryOperator::OPERATOR_OR,
393
-			[
394
-				new SearchBinaryOperator(
395
-					ISearchBinaryOperator::OPERATOR_NOT,
396
-					[
397
-						new SearchComparison(
398
-							ISearchComparison::COMPARE_EQUAL,
399
-							'mimetype',
400
-							FileInfo::MIMETYPE_FOLDER
401
-						),
402
-					]
403
-				),
404
-				new SearchComparison(
405
-					ISearchComparison::COMPARE_EQUAL,
406
-					'size',
407
-					0
408
-				),
409
-			]
410
-		);
411
-
412
-		$filterNonRecentFiles = new SearchComparison(
413
-			ISearchComparison::COMPARE_GREATER_THAN,
414
-			'mtime',
415
-			strtotime("-2 week")
416
-		);
417
-		if ($offset === 0 && $limit <= 100) {
418
-			$query = new SearchQuery(
419
-				new SearchBinaryOperator(
420
-					ISearchBinaryOperator::OPERATOR_AND,
421
-					[
422
-						$filterOutNonEmptyFolder,
423
-						$filterNonRecentFiles,
424
-					],
425
-				),
426
-				$limit,
427
-				$offset,
428
-				[
429
-					new SearchOrder(
430
-						ISearchOrder::DIRECTION_DESCENDING,
431
-						'mtime'
432
-					),
433
-				]
434
-			);
435
-		} else {
436
-			$query = new SearchQuery(
437
-				$filterOutNonEmptyFolder,
438
-				$limit,
439
-				$offset,
440
-				[
441
-					new SearchOrder(
442
-						ISearchOrder::DIRECTION_DESCENDING,
443
-						'mtime'
444
-					),
445
-				]
446
-			);
447
-		}
448
-
449
-		return $this->search($query);
450
-	}
53
+    /**
54
+     * Creates a Folder that represents a non-existing path
55
+     *
56
+     * @param string $path path
57
+     * @return NonExistingFolder non-existing node
58
+     */
59
+    protected function createNonExistingNode($path) {
60
+        return new NonExistingFolder($this->root, $this->view, $path);
61
+    }
62
+
63
+    /**
64
+     * @param string $path path relative to the folder
65
+     * @return string
66
+     * @throws \OCP\Files\NotPermittedException
67
+     */
68
+    public function getFullPath($path) {
69
+        $path = $this->normalizePath($path);
70
+        if (!$this->isValidPath($path)) {
71
+            throw new NotPermittedException('Invalid path "' . $path . '"');
72
+        }
73
+        return $this->path . $path;
74
+    }
75
+
76
+    /**
77
+     * @param string $path
78
+     * @return string|null
79
+     */
80
+    public function getRelativePath($path) {
81
+        return PathHelper::getRelativePath($this->getPath(), $path);
82
+    }
83
+
84
+    /**
85
+     * check if a node is a (grand-)child of the folder
86
+     *
87
+     * @param \OC\Files\Node\Node $node
88
+     * @return bool
89
+     */
90
+    public function isSubNode($node) {
91
+        return strpos($node->getPath(), $this->path . '/') === 0;
92
+    }
93
+
94
+    /**
95
+     * get the content of this directory
96
+     *
97
+     * @return Node[]
98
+     * @throws \OCP\Files\NotFoundException
99
+     */
100
+    public function getDirectoryListing() {
101
+        $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false));
102
+
103
+        return array_map(function (FileInfo $info) {
104
+            if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) {
105
+                return new Folder($this->root, $this->view, $info->getPath(), $info, $this);
106
+            } else {
107
+                return new File($this->root, $this->view, $info->getPath(), $info, $this);
108
+            }
109
+        }, $folderContent);
110
+    }
111
+
112
+    /**
113
+     * @param string $path
114
+     * @param FileInfo $info
115
+     * @return File|Folder
116
+     */
117
+    protected function createNode($path, FileInfo $info = null, bool $infoHasSubMountsIncluded = true) {
118
+        if (is_null($info)) {
119
+            $isDir = $this->view->is_dir($path);
120
+        } else {
121
+            $isDir = $info->getType() === FileInfo::TYPE_FOLDER;
122
+        }
123
+        $parent = dirname($path) === $this->getPath() ? $this : null;
124
+        if ($isDir) {
125
+            return new Folder($this->root, $this->view, $path, $info, $parent, $infoHasSubMountsIncluded);
126
+        } else {
127
+            return new File($this->root, $this->view, $path, $info, $parent);
128
+        }
129
+    }
130
+
131
+    /**
132
+     * Get the node at $path
133
+     *
134
+     * @param string $path
135
+     * @return \OC\Files\Node\Node
136
+     * @throws \OCP\Files\NotFoundException
137
+     */
138
+    public function get($path) {
139
+        return $this->root->get($this->getFullPath($path));
140
+    }
141
+
142
+    /**
143
+     * @param string $path
144
+     * @return bool
145
+     */
146
+    public function nodeExists($path) {
147
+        try {
148
+            $this->get($path);
149
+            return true;
150
+        } catch (NotFoundException $e) {
151
+            return false;
152
+        }
153
+    }
154
+
155
+    /**
156
+     * @param string $path
157
+     * @return \OC\Files\Node\Folder
158
+     * @throws \OCP\Files\NotPermittedException
159
+     */
160
+    public function newFolder($path) {
161
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
162
+            $fullPath = $this->getFullPath($path);
163
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $fullPath);
164
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
165
+            if (!$this->view->mkdir($fullPath)) {
166
+                throw new NotPermittedException('Could not create folder "' . $fullPath . '"');
167
+            }
168
+            $parent = dirname($fullPath) === $this->getPath() ? $this : null;
169
+            $node = new Folder($this->root, $this->view, $fullPath, null, $parent);
170
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
171
+            return $node;
172
+        } else {
173
+            throw new NotPermittedException('No create permission for folder "' . $path . '"');
174
+        }
175
+    }
176
+
177
+    /**
178
+     * @param string $path
179
+     * @param string | resource | null $content
180
+     * @return \OC\Files\Node\File
181
+     * @throws \OCP\Files\NotPermittedException
182
+     */
183
+    public function newFile($path, $content = null) {
184
+        if (empty($path)) {
185
+            throw new NotPermittedException('Could not create as provided path is empty');
186
+        }
187
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
188
+            $fullPath = $this->getFullPath($path);
189
+            $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
190
+            $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
191
+            if ($content !== null) {
192
+                $result = $this->view->file_put_contents($fullPath, $content);
193
+            } else {
194
+                $result = $this->view->touch($fullPath);
195
+            }
196
+            if ($result === false) {
197
+                throw new NotPermittedException('Could not create path "' . $fullPath . '"');
198
+            }
199
+            $node = new File($this->root, $this->view, $fullPath, null, $this);
200
+            $this->sendHooks(['postWrite', 'postCreate'], [$node]);
201
+            return $node;
202
+        }
203
+        throw new NotPermittedException('No create permission for path "' . $path . '"');
204
+    }
205
+
206
+    private function queryFromOperator(ISearchOperator $operator, string $uid = null, int $limit = 0, int $offset = 0): ISearchQuery {
207
+        if ($uid === null) {
208
+            $user = null;
209
+        } else {
210
+            /** @var IUserManager $userManager */
211
+            $userManager = \OC::$server->query(IUserManager::class);
212
+            $user = $userManager->get($uid);
213
+        }
214
+        return new SearchQuery($operator, $limit, $offset, [], $user);
215
+    }
216
+
217
+    /**
218
+     * search for files with the name matching $query
219
+     *
220
+     * @param string|ISearchQuery $query
221
+     * @return \OC\Files\Node\Node[]
222
+     */
223
+    public function search($query) {
224
+        if (is_string($query)) {
225
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'name', '%' . $query . '%'));
226
+        }
227
+
228
+        // search is handled by a single query covering all caches that this folder contains
229
+        // this is done by collect
230
+
231
+        $limitToHome = $query->limitToHome();
232
+        if ($limitToHome && count(explode('/', $this->path)) !== 3) {
233
+            throw new \InvalidArgumentException('searching by owner is only allowed in the users home folder');
234
+        }
235
+
236
+        /** @var QuerySearchHelper $searchHelper */
237
+        $searchHelper = \OC::$server->get(QuerySearchHelper::class);
238
+        [$caches, $mountByMountPoint] = $searchHelper->getCachesAndMountPointsForSearch($this->root, $this->path, $limitToHome);
239
+        $resultsPerCache = $searchHelper->searchInCaches($query, $caches);
240
+
241
+        // loop through all results per-cache, constructing the FileInfo object from the CacheEntry and merge them all
242
+        $files = array_merge(...array_map(function (array $results, string $relativeMountPoint) use ($mountByMountPoint) {
243
+            $mount = $mountByMountPoint[$relativeMountPoint];
244
+            return array_map(function (ICacheEntry $result) use ($relativeMountPoint, $mount) {
245
+                return $this->cacheEntryToFileInfo($mount, $relativeMountPoint, $result);
246
+            }, $results);
247
+        }, array_values($resultsPerCache), array_keys($resultsPerCache)));
248
+
249
+        // don't include this folder in the results
250
+        $files = array_filter($files, function (FileInfo $file) {
251
+            return $file->getPath() !== $this->getPath();
252
+        });
253
+
254
+        // since results were returned per-cache, they are no longer fully sorted
255
+        $order = $query->getOrder();
256
+        if ($order) {
257
+            usort($files, function (FileInfo $a, FileInfo $b) use ($order) {
258
+                foreach ($order as $orderField) {
259
+                    $cmp = $orderField->sortFileInfo($a, $b);
260
+                    if ($cmp !== 0) {
261
+                        return $cmp;
262
+                    }
263
+                }
264
+                return 0;
265
+            });
266
+        }
267
+
268
+        return array_map(function (FileInfo $file) {
269
+            return $this->createNode($file->getPath(), $file);
270
+        }, $files);
271
+    }
272
+
273
+    private function cacheEntryToFileInfo(IMountPoint $mount, string $appendRoot, ICacheEntry $cacheEntry): FileInfo {
274
+        $cacheEntry['internalPath'] = $cacheEntry['path'];
275
+        $cacheEntry['path'] = rtrim($appendRoot . $cacheEntry->getPath(), '/');
276
+        $subPath = $cacheEntry['path'] !== '' ? '/' . $cacheEntry['path'] : '';
277
+        return new \OC\Files\FileInfo($this->path . $subPath, $mount->getStorage(), $cacheEntry['internalPath'], $cacheEntry, $mount);
278
+    }
279
+
280
+    /**
281
+     * search for files by mimetype
282
+     *
283
+     * @param string $mimetype
284
+     * @return Node[]
285
+     */
286
+    public function searchByMime($mimetype) {
287
+        if (strpos($mimetype, '/') === false) {
288
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $mimetype . '/%'));
289
+        } else {
290
+            $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', $mimetype));
291
+        }
292
+        return $this->search($query);
293
+    }
294
+
295
+    /**
296
+     * search for files by tag
297
+     *
298
+     * @param string|int $tag name or tag id
299
+     * @param string $userId owner of the tags
300
+     * @return Node[]
301
+     */
302
+    public function searchByTag($tag, $userId) {
303
+        $query = $this->queryFromOperator(new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'tagname', $tag), $userId);
304
+        return $this->search($query);
305
+    }
306
+
307
+    /**
308
+     * @param int $id
309
+     * @return \OC\Files\Node\Node[]
310
+     */
311
+    public function getById($id) {
312
+        return $this->root->getByIdInPath((int)$id, $this->getPath());
313
+    }
314
+
315
+    protected function getAppDataDirectoryName(): string {
316
+        $instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
317
+        return 'appdata_' . $instanceId;
318
+    }
319
+
320
+    /**
321
+     * In case the path we are currently in is inside the appdata_* folder,
322
+     * the original getById method does not work, because it can only look inside
323
+     * the user's mount points. But the user has no mount point for the root storage.
324
+     *
325
+     * So in that case we directly check the mount of the root if it contains
326
+     * the id. If it does we check if the path is inside the path we are working
327
+     * in.
328
+     *
329
+     * @param int $id
330
+     * @return array
331
+     */
332
+    protected function getByIdInRootMount(int $id): array {
333
+        $mount = $this->root->getMount('');
334
+        $cacheEntry = $mount->getStorage()->getCache($this->path)->get($id);
335
+        if (!$cacheEntry) {
336
+            return [];
337
+        }
338
+
339
+        $absolutePath = '/' . ltrim($cacheEntry->getPath(), '/');
340
+        $currentPath = rtrim($this->path, '/') . '/';
341
+
342
+        if (strpos($absolutePath, $currentPath) !== 0) {
343
+            return [];
344
+        }
345
+
346
+        return [$this->root->createNode(
347
+            $absolutePath, new \OC\Files\FileInfo(
348
+                $absolutePath,
349
+                $mount->getStorage(),
350
+                $cacheEntry->getPath(),
351
+                $cacheEntry,
352
+                $mount
353
+            ))];
354
+    }
355
+
356
+    public function getFreeSpace() {
357
+        return $this->view->free_space($this->path);
358
+    }
359
+
360
+    public function delete() {
361
+        if ($this->checkPermissions(\OCP\Constants::PERMISSION_DELETE)) {
362
+            $this->sendHooks(['preDelete']);
363
+            $fileInfo = $this->getFileInfo();
364
+            $this->view->rmdir($this->path);
365
+            $nonExisting = new NonExistingFolder($this->root, $this->view, $this->path, $fileInfo);
366
+            $this->sendHooks(['postDelete'], [$nonExisting]);
367
+        } else {
368
+            throw new NotPermittedException('No delete permission for path "' . $this->path . '"');
369
+        }
370
+    }
371
+
372
+    /**
373
+     * Add a suffix to the name in case the file exists
374
+     *
375
+     * @param string $name
376
+     * @return string
377
+     * @throws NotPermittedException
378
+     */
379
+    public function getNonExistingName($name) {
380
+        $uniqueName = \OC_Helper::buildNotExistingFileNameForView($this->getPath(), $name, $this->view);
381
+        return trim($this->getRelativePath($uniqueName), '/');
382
+    }
383
+
384
+    /**
385
+     * @param int $limit
386
+     * @param int $offset
387
+     * @return \OCP\Files\Node[]
388
+     */
389
+    public function getRecent($limit, $offset = 0) {
390
+        $filterOutNonEmptyFolder = new SearchBinaryOperator(
391
+            // filter out non empty folders
392
+            ISearchBinaryOperator::OPERATOR_OR,
393
+            [
394
+                new SearchBinaryOperator(
395
+                    ISearchBinaryOperator::OPERATOR_NOT,
396
+                    [
397
+                        new SearchComparison(
398
+                            ISearchComparison::COMPARE_EQUAL,
399
+                            'mimetype',
400
+                            FileInfo::MIMETYPE_FOLDER
401
+                        ),
402
+                    ]
403
+                ),
404
+                new SearchComparison(
405
+                    ISearchComparison::COMPARE_EQUAL,
406
+                    'size',
407
+                    0
408
+                ),
409
+            ]
410
+        );
411
+
412
+        $filterNonRecentFiles = new SearchComparison(
413
+            ISearchComparison::COMPARE_GREATER_THAN,
414
+            'mtime',
415
+            strtotime("-2 week")
416
+        );
417
+        if ($offset === 0 && $limit <= 100) {
418
+            $query = new SearchQuery(
419
+                new SearchBinaryOperator(
420
+                    ISearchBinaryOperator::OPERATOR_AND,
421
+                    [
422
+                        $filterOutNonEmptyFolder,
423
+                        $filterNonRecentFiles,
424
+                    ],
425
+                ),
426
+                $limit,
427
+                $offset,
428
+                [
429
+                    new SearchOrder(
430
+                        ISearchOrder::DIRECTION_DESCENDING,
431
+                        'mtime'
432
+                    ),
433
+                ]
434
+            );
435
+        } else {
436
+            $query = new SearchQuery(
437
+                $filterOutNonEmptyFolder,
438
+                $limit,
439
+                $offset,
440
+                [
441
+                    new SearchOrder(
442
+                        ISearchOrder::DIRECTION_DESCENDING,
443
+                        'mtime'
444
+                    ),
445
+                ]
446
+            );
447
+        }
448
+
449
+        return $this->search($query);
450
+    }
451 451
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/CacheQueryBuilder.php 1 patch
Indentation   +91 added lines, -91 removed lines patch added patch discarded remove patch
@@ -35,95 +35,95 @@
 block discarded – undo
35 35
  * Query builder with commonly used helpers for filecache queries
36 36
  */
37 37
 class CacheQueryBuilder extends QueryBuilder {
38
-	private $alias = null;
39
-
40
-	public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) {
41
-		parent::__construct($connection, $systemConfig, $logger);
42
-	}
43
-
44
-	public function selectTagUsage(): self {
45
-		$this
46
-			->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable')
47
-			->selectAlias($this->createFunction('COUNT(filecache.fileid)'), 'number_files')
48
-			->selectAlias($this->createFunction('MAX(filecache.fileid)'), 'ref_file_id')
49
-			->from('filecache', 'filecache')
50
-			->leftJoin('filecache', 'systemtag_object_mapping', 'systemtagmap', $this->expr()->andX(
51
-				$this->expr()->eq('filecache.fileid', $this->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
52
-				$this->expr()->eq('systemtagmap.objecttype', $this->createNamedParameter('files'))
53
-			))
54
-			->leftJoin('systemtagmap', 'systemtag', 'systemtag', $this->expr()->andX(
55
-				$this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'),
56
-				$this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true))
57
-			))
58
-			->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable');
59
-
60
-		return $this;
61
-	}
62
-
63
-	public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
64
-		$name = $alias ?: 'filecache';
65
-		$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
66
-			'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
67
-			->from('filecache', $name);
68
-
69
-		if ($joinExtendedCache) {
70
-			$this->addSelect('metadata_etag', 'creation_time', 'upload_time');
71
-			$this->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid'));
72
-		}
73
-
74
-		$this->alias = $name;
75
-
76
-		return $this;
77
-	}
78
-
79
-	public function whereStorageId(int $storageId) {
80
-		$this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
81
-
82
-		return $this;
83
-	}
84
-
85
-	public function whereFileId(int $fileId) {
86
-		$alias = $this->alias;
87
-		if ($alias) {
88
-			$alias .= '.';
89
-		} else {
90
-			$alias = '';
91
-		}
92
-
93
-		$this->andWhere($this->expr()->eq("{$alias}fileid", $this->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
94
-
95
-		return $this;
96
-	}
97
-
98
-	public function wherePath(string $path) {
99
-		$this->andWhere($this->expr()->eq('path_hash', $this->createNamedParameter(md5($path))));
100
-
101
-		return $this;
102
-	}
103
-
104
-	public function whereParent(int $parent) {
105
-		$alias = $this->alias;
106
-		if ($alias) {
107
-			$alias .= '.';
108
-		} else {
109
-			$alias = '';
110
-		}
111
-
112
-		$this->andWhere($this->expr()->eq("{$alias}parent", $this->createNamedParameter($parent, IQueryBuilder::PARAM_INT)));
113
-
114
-		return $this;
115
-	}
116
-
117
-	public function whereParentInParameter(string $parameter) {
118
-		$alias = $this->alias;
119
-		if ($alias) {
120
-			$alias .= '.';
121
-		} else {
122
-			$alias = '';
123
-		}
124
-
125
-		$this->andWhere($this->expr()->in("{$alias}parent", $this->createParameter($parameter)));
126
-
127
-		return $this;
128
-	}
38
+    private $alias = null;
39
+
40
+    public function __construct(IDBConnection $connection, SystemConfig $systemConfig, LoggerInterface $logger) {
41
+        parent::__construct($connection, $systemConfig, $logger);
42
+    }
43
+
44
+    public function selectTagUsage(): self {
45
+        $this
46
+            ->select('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable')
47
+            ->selectAlias($this->createFunction('COUNT(filecache.fileid)'), 'number_files')
48
+            ->selectAlias($this->createFunction('MAX(filecache.fileid)'), 'ref_file_id')
49
+            ->from('filecache', 'filecache')
50
+            ->leftJoin('filecache', 'systemtag_object_mapping', 'systemtagmap', $this->expr()->andX(
51
+                $this->expr()->eq('filecache.fileid', $this->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
52
+                $this->expr()->eq('systemtagmap.objecttype', $this->createNamedParameter('files'))
53
+            ))
54
+            ->leftJoin('systemtagmap', 'systemtag', 'systemtag', $this->expr()->andX(
55
+                $this->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'),
56
+                $this->expr()->eq('systemtag.visibility', $this->createNamedParameter(true))
57
+            ))
58
+            ->groupBy('systemtag.name', 'systemtag.id', 'systemtag.visibility', 'systemtag.editable');
59
+
60
+        return $this;
61
+    }
62
+
63
+    public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
64
+        $name = $alias ?: 'filecache';
65
+        $this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
66
+            'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
67
+            ->from('filecache', $name);
68
+
69
+        if ($joinExtendedCache) {
70
+            $this->addSelect('metadata_etag', 'creation_time', 'upload_time');
71
+            $this->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid'));
72
+        }
73
+
74
+        $this->alias = $name;
75
+
76
+        return $this;
77
+    }
78
+
79
+    public function whereStorageId(int $storageId) {
80
+        $this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)));
81
+
82
+        return $this;
83
+    }
84
+
85
+    public function whereFileId(int $fileId) {
86
+        $alias = $this->alias;
87
+        if ($alias) {
88
+            $alias .= '.';
89
+        } else {
90
+            $alias = '';
91
+        }
92
+
93
+        $this->andWhere($this->expr()->eq("{$alias}fileid", $this->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
94
+
95
+        return $this;
96
+    }
97
+
98
+    public function wherePath(string $path) {
99
+        $this->andWhere($this->expr()->eq('path_hash', $this->createNamedParameter(md5($path))));
100
+
101
+        return $this;
102
+    }
103
+
104
+    public function whereParent(int $parent) {
105
+        $alias = $this->alias;
106
+        if ($alias) {
107
+            $alias .= '.';
108
+        } else {
109
+            $alias = '';
110
+        }
111
+
112
+        $this->andWhere($this->expr()->eq("{$alias}parent", $this->createNamedParameter($parent, IQueryBuilder::PARAM_INT)));
113
+
114
+        return $this;
115
+    }
116
+
117
+    public function whereParentInParameter(string $parameter) {
118
+        $alias = $this->alias;
119
+        if ($alias) {
120
+            $alias .= '.';
121
+        } else {
122
+            $alias = '';
123
+        }
124
+
125
+        $this->andWhere($this->expr()->in("{$alias}parent", $this->createParameter($parameter)));
126
+
127
+        return $this;
128
+    }
129 129
 }
Please login to merge, or discard this patch.
lib/private/Files/Cache/QuerySearchHelper.php 1 patch
Indentation   +185 added lines, -185 removed lines patch added patch discarded remove patch
@@ -42,189 +42,189 @@
 block discarded – undo
42 42
 use Psr\Log\LoggerInterface;
43 43
 
44 44
 class QuerySearchHelper {
45
-	/** @var IMimeTypeLoader */
46
-	private $mimetypeLoader;
47
-	/** @var IDBConnection */
48
-	private $connection;
49
-	/** @var SystemConfig */
50
-	private $systemConfig;
51
-	private LoggerInterface $logger;
52
-	/** @var SearchBuilder */
53
-	private $searchBuilder;
54
-	/** @var QueryOptimizer */
55
-	private $queryOptimizer;
56
-
57
-	public function __construct(
58
-		IMimeTypeLoader $mimetypeLoader,
59
-		IDBConnection $connection,
60
-		SystemConfig $systemConfig,
61
-		LoggerInterface $logger,
62
-		SearchBuilder $searchBuilder,
63
-		QueryOptimizer $queryOptimizer
64
-	) {
65
-		$this->mimetypeLoader = $mimetypeLoader;
66
-		$this->connection = $connection;
67
-		$this->systemConfig = $systemConfig;
68
-		$this->logger = $logger;
69
-		$this->searchBuilder = $searchBuilder;
70
-		$this->queryOptimizer = $queryOptimizer;
71
-	}
72
-
73
-	protected function getQueryBuilder() {
74
-		return new CacheQueryBuilder(
75
-			$this->connection,
76
-			$this->systemConfig,
77
-			$this->logger
78
-		);
79
-	}
80
-
81
-	protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void {
82
-		$storageFilters = array_values(array_map(function (ICache $cache) {
83
-			return $cache->getQueryFilterForStorage();
84
-		}, $caches));
85
-		$storageFilter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters);
86
-		$filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]);
87
-		$this->queryOptimizer->processOperator($filter);
88
-
89
-		$searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter);
90
-		if ($searchExpr) {
91
-			$query->andWhere($searchExpr);
92
-		}
93
-
94
-		$this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
95
-
96
-		if ($searchQuery->getLimit()) {
97
-			$query->setMaxResults($searchQuery->getLimit());
98
-		}
99
-		if ($searchQuery->getOffset()) {
100
-			$query->setFirstResult($searchQuery->getOffset());
101
-		}
102
-	}
103
-
104
-
105
-	/**
106
-	 * @return array<array-key, array{id: int, name: string, visibility: int, editable: int, ref_file_id: int, number_files: int}>
107
-	 */
108
-	public function findUsedTagsInCaches(ISearchQuery $searchQuery, array $caches): array {
109
-		$query = $this->getQueryBuilder();
110
-		$query->selectTagUsage();
111
-
112
-		$this->applySearchConstraints($query, $searchQuery, $caches);
113
-
114
-		$result = $query->execute();
115
-		$tags = $result->fetchAll();
116
-		$result->closeCursor();
117
-		return $tags;
118
-	}
119
-
120
-	/**
121
-	 * Perform a file system search in multiple caches
122
-	 *
123
-	 * the results will be grouped by the same array keys as the $caches argument to allow
124
-	 * post-processing based on which cache the result came from
125
-	 *
126
-	 * @template T of array-key
127
-	 * @param ISearchQuery $searchQuery
128
-	 * @param array<T, ICache> $caches
129
-	 * @return array<T, ICacheEntry[]>
130
-	 */
131
-	public function searchInCaches(ISearchQuery $searchQuery, array $caches): array {
132
-		// search in multiple caches at once by creating one query in the following format
133
-		// SELECT ... FROM oc_filecache WHERE
134
-		//     <filter expressions from the search query>
135
-		// AND (
136
-		//     <filter expression for storage1> OR
137
-		//     <filter expression for storage2> OR
138
-		//     ...
139
-		// );
140
-		//
141
-		// This gives us all the files matching the search query from all caches
142
-		//
143
-		// while the resulting rows don't have a way to tell what storage they came from (multiple storages/caches can share storage_id)
144
-		// we can just ask every cache if the row belongs to them and give them the cache to do any post processing on the result.
145
-
146
-		$builder = $this->getQueryBuilder();
147
-
148
-		$query = $builder->selectFileCache('file', false);
149
-
150
-		if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) {
151
-			$user = $searchQuery->getUser();
152
-			if ($user === null) {
153
-				throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
154
-			}
155
-			$query
156
-				->leftJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
157
-				->leftJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
158
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
159
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id'),
160
-					$builder->expr()->eq('tag.type', $builder->createNamedParameter('files')),
161
-					$builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID()))
162
-				))
163
-				->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $builder->expr()->andX(
164
-					$builder->expr()->eq('file.fileid', $builder->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
165
-					$builder->expr()->eq('systemtagmap.objecttype', $builder->createNamedParameter('files'))
166
-				))
167
-				->leftJoin('systemtagmap', 'systemtag', 'systemtag', $builder->expr()->andX(
168
-					$builder->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'),
169
-					$builder->expr()->eq('systemtag.visibility', $builder->createNamedParameter(true))
170
-				));
171
-		}
172
-
173
-		$this->applySearchConstraints($query, $searchQuery, $caches);
174
-
175
-		$result = $query->execute();
176
-		$files = $result->fetchAll();
177
-
178
-		$rawEntries = array_map(function (array $data) {
179
-			return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
180
-		}, $files);
181
-
182
-		$result->closeCursor();
183
-
184
-		// loop through all caches for each result to see if the result matches that storage
185
-		// results are grouped by the same array keys as the caches argument to allow the caller to distinguish the source of the results
186
-		$results = array_fill_keys(array_keys($caches), []);
187
-		foreach ($rawEntries as $rawEntry) {
188
-			foreach ($caches as $cacheKey => $cache) {
189
-				$entry = $cache->getCacheEntryFromSearchResult($rawEntry);
190
-				if ($entry) {
191
-					$results[$cacheKey][] = $entry;
192
-				}
193
-			}
194
-		}
195
-		return $results;
196
-	}
197
-
198
-	/**
199
-	 * @return array{array<string, ICache>, array<string, IMountPoint>}
200
-	 */
201
-	public function getCachesAndMountPointsForSearch(Root $root, string $path, bool $limitToHome = false): array {
202
-		$rootLength = strlen($path);
203
-		$mount = $root->getMount($path);
204
-		$storage = $mount->getStorage();
205
-		$internalPath = $mount->getInternalPath($path);
206
-
207
-		if ($internalPath !== '') {
208
-			// a temporary CacheJail is used to handle filtering down the results to within this folder
209
-			$caches = ['' => new CacheJail($storage->getCache(''), $internalPath)];
210
-		} else {
211
-			$caches = ['' => $storage->getCache('')];
212
-		}
213
-		$mountByMountPoint = ['' => $mount];
214
-
215
-		if (!$limitToHome) {
216
-			/** @var IMountPoint[] $mounts */
217
-			$mounts = $root->getMountsIn($path);
218
-			foreach ($mounts as $mount) {
219
-				$storage = $mount->getStorage();
220
-				if ($storage) {
221
-					$relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
222
-					$caches[$relativeMountPoint] = $storage->getCache('');
223
-					$mountByMountPoint[$relativeMountPoint] = $mount;
224
-				}
225
-			}
226
-		}
227
-
228
-		return [$caches, $mountByMountPoint];
229
-	}
45
+    /** @var IMimeTypeLoader */
46
+    private $mimetypeLoader;
47
+    /** @var IDBConnection */
48
+    private $connection;
49
+    /** @var SystemConfig */
50
+    private $systemConfig;
51
+    private LoggerInterface $logger;
52
+    /** @var SearchBuilder */
53
+    private $searchBuilder;
54
+    /** @var QueryOptimizer */
55
+    private $queryOptimizer;
56
+
57
+    public function __construct(
58
+        IMimeTypeLoader $mimetypeLoader,
59
+        IDBConnection $connection,
60
+        SystemConfig $systemConfig,
61
+        LoggerInterface $logger,
62
+        SearchBuilder $searchBuilder,
63
+        QueryOptimizer $queryOptimizer
64
+    ) {
65
+        $this->mimetypeLoader = $mimetypeLoader;
66
+        $this->connection = $connection;
67
+        $this->systemConfig = $systemConfig;
68
+        $this->logger = $logger;
69
+        $this->searchBuilder = $searchBuilder;
70
+        $this->queryOptimizer = $queryOptimizer;
71
+    }
72
+
73
+    protected function getQueryBuilder() {
74
+        return new CacheQueryBuilder(
75
+            $this->connection,
76
+            $this->systemConfig,
77
+            $this->logger
78
+        );
79
+    }
80
+
81
+    protected function applySearchConstraints(CacheQueryBuilder $query, ISearchQuery $searchQuery, array $caches): void {
82
+        $storageFilters = array_values(array_map(function (ICache $cache) {
83
+            return $cache->getQueryFilterForStorage();
84
+        }, $caches));
85
+        $storageFilter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, $storageFilters);
86
+        $filter = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$searchQuery->getSearchOperation(), $storageFilter]);
87
+        $this->queryOptimizer->processOperator($filter);
88
+
89
+        $searchExpr = $this->searchBuilder->searchOperatorToDBExpr($query, $filter);
90
+        if ($searchExpr) {
91
+            $query->andWhere($searchExpr);
92
+        }
93
+
94
+        $this->searchBuilder->addSearchOrdersToQuery($query, $searchQuery->getOrder());
95
+
96
+        if ($searchQuery->getLimit()) {
97
+            $query->setMaxResults($searchQuery->getLimit());
98
+        }
99
+        if ($searchQuery->getOffset()) {
100
+            $query->setFirstResult($searchQuery->getOffset());
101
+        }
102
+    }
103
+
104
+
105
+    /**
106
+     * @return array<array-key, array{id: int, name: string, visibility: int, editable: int, ref_file_id: int, number_files: int}>
107
+     */
108
+    public function findUsedTagsInCaches(ISearchQuery $searchQuery, array $caches): array {
109
+        $query = $this->getQueryBuilder();
110
+        $query->selectTagUsage();
111
+
112
+        $this->applySearchConstraints($query, $searchQuery, $caches);
113
+
114
+        $result = $query->execute();
115
+        $tags = $result->fetchAll();
116
+        $result->closeCursor();
117
+        return $tags;
118
+    }
119
+
120
+    /**
121
+     * Perform a file system search in multiple caches
122
+     *
123
+     * the results will be grouped by the same array keys as the $caches argument to allow
124
+     * post-processing based on which cache the result came from
125
+     *
126
+     * @template T of array-key
127
+     * @param ISearchQuery $searchQuery
128
+     * @param array<T, ICache> $caches
129
+     * @return array<T, ICacheEntry[]>
130
+     */
131
+    public function searchInCaches(ISearchQuery $searchQuery, array $caches): array {
132
+        // search in multiple caches at once by creating one query in the following format
133
+        // SELECT ... FROM oc_filecache WHERE
134
+        //     <filter expressions from the search query>
135
+        // AND (
136
+        //     <filter expression for storage1> OR
137
+        //     <filter expression for storage2> OR
138
+        //     ...
139
+        // );
140
+        //
141
+        // This gives us all the files matching the search query from all caches
142
+        //
143
+        // while the resulting rows don't have a way to tell what storage they came from (multiple storages/caches can share storage_id)
144
+        // we can just ask every cache if the row belongs to them and give them the cache to do any post processing on the result.
145
+
146
+        $builder = $this->getQueryBuilder();
147
+
148
+        $query = $builder->selectFileCache('file', false);
149
+
150
+        if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) {
151
+            $user = $searchQuery->getUser();
152
+            if ($user === null) {
153
+                throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query");
154
+            }
155
+            $query
156
+                ->leftJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
157
+                ->leftJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
158
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
159
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id'),
160
+                    $builder->expr()->eq('tag.type', $builder->createNamedParameter('files')),
161
+                    $builder->expr()->eq('tag.uid', $builder->createNamedParameter($user->getUID()))
162
+                ))
163
+                ->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $builder->expr()->andX(
164
+                    $builder->expr()->eq('file.fileid', $builder->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
165
+                    $builder->expr()->eq('systemtagmap.objecttype', $builder->createNamedParameter('files'))
166
+                ))
167
+                ->leftJoin('systemtagmap', 'systemtag', 'systemtag', $builder->expr()->andX(
168
+                    $builder->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'),
169
+                    $builder->expr()->eq('systemtag.visibility', $builder->createNamedParameter(true))
170
+                ));
171
+        }
172
+
173
+        $this->applySearchConstraints($query, $searchQuery, $caches);
174
+
175
+        $result = $query->execute();
176
+        $files = $result->fetchAll();
177
+
178
+        $rawEntries = array_map(function (array $data) {
179
+            return Cache::cacheEntryFromData($data, $this->mimetypeLoader);
180
+        }, $files);
181
+
182
+        $result->closeCursor();
183
+
184
+        // loop through all caches for each result to see if the result matches that storage
185
+        // results are grouped by the same array keys as the caches argument to allow the caller to distinguish the source of the results
186
+        $results = array_fill_keys(array_keys($caches), []);
187
+        foreach ($rawEntries as $rawEntry) {
188
+            foreach ($caches as $cacheKey => $cache) {
189
+                $entry = $cache->getCacheEntryFromSearchResult($rawEntry);
190
+                if ($entry) {
191
+                    $results[$cacheKey][] = $entry;
192
+                }
193
+            }
194
+        }
195
+        return $results;
196
+    }
197
+
198
+    /**
199
+     * @return array{array<string, ICache>, array<string, IMountPoint>}
200
+     */
201
+    public function getCachesAndMountPointsForSearch(Root $root, string $path, bool $limitToHome = false): array {
202
+        $rootLength = strlen($path);
203
+        $mount = $root->getMount($path);
204
+        $storage = $mount->getStorage();
205
+        $internalPath = $mount->getInternalPath($path);
206
+
207
+        if ($internalPath !== '') {
208
+            // a temporary CacheJail is used to handle filtering down the results to within this folder
209
+            $caches = ['' => new CacheJail($storage->getCache(''), $internalPath)];
210
+        } else {
211
+            $caches = ['' => $storage->getCache('')];
212
+        }
213
+        $mountByMountPoint = ['' => $mount];
214
+
215
+        if (!$limitToHome) {
216
+            /** @var IMountPoint[] $mounts */
217
+            $mounts = $root->getMountsIn($path);
218
+            foreach ($mounts as $mount) {
219
+                $storage = $mount->getStorage();
220
+                if ($storage) {
221
+                    $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
222
+                    $caches[$relativeMountPoint] = $storage->getCache('');
223
+                    $mountByMountPoint[$relativeMountPoint] = $mount;
224
+                }
225
+            }
226
+        }
227
+
228
+        return [$caches, $mountByMountPoint];
229
+    }
230 230
 }
Please login to merge, or discard this patch.
lib/private/SystemTag/SystemTagsInFilesDetector.php 1 patch
Indentation   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -36,37 +36,37 @@
 block discarded – undo
36 36
 use OCP\Files\Search\ISearchComparison;
37 37
 
38 38
 class SystemTagsInFilesDetector {
39
-	public function __construct(protected QuerySearchHelper $searchHelper) {
40
-	}
39
+    public function __construct(protected QuerySearchHelper $searchHelper) {
40
+    }
41 41
 
42
-	public function detectAssignedSystemTagsIn(
43
-		Folder $folder,
44
-		string $filteredMediaType = '',
45
-		int $limit = 0,
46
-		int $offset = 0
47
-	): array {
48
-		$operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%');
49
-		// Currently query has to have exactly one search condition. If no media type is provided,
50
-		// we fall back to the presence of a system tag.
51
-		if ($filteredMediaType !== '') {
52
-			$mimeOperator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%');
53
-			$operator = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$operator, $mimeOperator]);
54
-		}
42
+    public function detectAssignedSystemTagsIn(
43
+        Folder $folder,
44
+        string $filteredMediaType = '',
45
+        int $limit = 0,
46
+        int $offset = 0
47
+    ): array {
48
+        $operator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'systemtag', '%');
49
+        // Currently query has to have exactly one search condition. If no media type is provided,
50
+        // we fall back to the presence of a system tag.
51
+        if ($filteredMediaType !== '') {
52
+            $mimeOperator = new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', $filteredMediaType . '/%');
53
+            $operator = new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_AND, [$operator, $mimeOperator]);
54
+        }
55 55
 
56
-		$query = new SearchQuery($operator, $limit, $offset, []);
57
-		[$caches, ] = $this->searchHelper->getCachesAndMountPointsForSearch(
58
-			$this->getRootFolder($folder),
59
-			$folder->getPath(),
60
-		);
61
-		return $this->searchHelper->findUsedTagsInCaches($query, $caches);
62
-	}
56
+        $query = new SearchQuery($operator, $limit, $offset, []);
57
+        [$caches, ] = $this->searchHelper->getCachesAndMountPointsForSearch(
58
+            $this->getRootFolder($folder),
59
+            $folder->getPath(),
60
+        );
61
+        return $this->searchHelper->findUsedTagsInCaches($query, $caches);
62
+    }
63 63
 
64
-	protected function getRootFolder(?Folder $folder): Root {
65
-		if ($folder instanceof Root) {
66
-			return $folder;
67
-		} elseif ($folder === null) {
68
-			throw new \LogicException('Could not climb up to root folder');
69
-		}
70
-		return $this->getRootFolder($folder->getParent());
71
-	}
64
+    protected function getRootFolder(?Folder $folder): Root {
65
+        if ($folder instanceof Root) {
66
+            return $folder;
67
+        } elseif ($folder === null) {
68
+            throw new \LogicException('Could not climb up to root folder');
69
+        }
70
+        return $this->getRootFolder($folder->getParent());
71
+    }
72 72
 }
Please login to merge, or discard this patch.
apps/dav/lib/RootCollection.php 1 patch
Indentation   +139 added lines, -139 removed lines patch added patch discarded remove patch
@@ -54,143 +54,143 @@
 block discarded – undo
54 54
 use Sabre\DAV\SimpleCollection;
55 55
 
56 56
 class RootCollection extends SimpleCollection {
57
-	public function __construct() {
58
-		$l10n = \OC::$server->getL10N('dav');
59
-		$random = \OC::$server->getSecureRandom();
60
-		$logger = \OC::$server->get(LoggerInterface::class);
61
-		$userManager = \OC::$server->getUserManager();
62
-		$userSession = \OC::$server->getUserSession();
63
-		$groupManager = \OC::$server->getGroupManager();
64
-		$shareManager = \OC::$server->getShareManager();
65
-		$db = \OC::$server->getDatabaseConnection();
66
-		$dispatcher = \OC::$server->get(IEventDispatcher::class);
67
-		$config = \OC::$server->get(IConfig::class);
68
-		$proxyMapper = \OC::$server->query(ProxyMapper::class);
69
-		$rootFolder = \OCP\Server::get(IRootFolder::class);
70
-
71
-		$userPrincipalBackend = new Principal(
72
-			$userManager,
73
-			$groupManager,
74
-			\OC::$server->get(IAccountManager::class),
75
-			$shareManager,
76
-			\OC::$server->getUserSession(),
77
-			\OC::$server->getAppManager(),
78
-			$proxyMapper,
79
-			\OC::$server->get(KnownUserService::class),
80
-			\OC::$server->getConfig(),
81
-			\OC::$server->getL10NFactory()
82
-		);
83
-		$groupPrincipalBackend = new GroupPrincipalBackend($groupManager, $userSession, $shareManager, $config);
84
-		$calendarResourcePrincipalBackend = new ResourcePrincipalBackend($db, $userSession, $groupManager, $logger, $proxyMapper);
85
-		$calendarRoomPrincipalBackend = new RoomPrincipalBackend($db, $userSession, $groupManager, $logger, $proxyMapper);
86
-		// as soon as debug mode is enabled we allow listing of principals
87
-		$disableListing = !$config->getSystemValue('debug', false);
88
-
89
-		// setup the first level of the dav tree
90
-		$userPrincipals = new Collection($userPrincipalBackend, 'principals/users');
91
-		$userPrincipals->disableListing = $disableListing;
92
-		$groupPrincipals = new Collection($groupPrincipalBackend, 'principals/groups');
93
-		$groupPrincipals->disableListing = $disableListing;
94
-		$systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system');
95
-		$systemPrincipals->disableListing = $disableListing;
96
-		$calendarResourcePrincipals = new Collection($calendarResourcePrincipalBackend, 'principals/calendar-resources');
97
-		$calendarResourcePrincipals->disableListing = $disableListing;
98
-		$calendarRoomPrincipals = new Collection($calendarRoomPrincipalBackend, 'principals/calendar-rooms');
99
-		$calendarRoomPrincipals->disableListing = $disableListing;
100
-
101
-
102
-		$filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users');
103
-		$filesCollection->disableListing = $disableListing;
104
-		$caldavBackend = new CalDavBackend(
105
-			$db,
106
-			$userPrincipalBackend,
107
-			$userManager,
108
-			$groupManager,
109
-			$random,
110
-			$logger,
111
-			$dispatcher,
112
-			$config
113
-		);
114
-		$userCalendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users', $logger);
115
-		$userCalendarRoot->disableListing = $disableListing;
116
-
117
-		$resourceCalendarRoot = new CalendarRoot($calendarResourcePrincipalBackend, $caldavBackend, 'principals/calendar-resources', $logger);
118
-		$resourceCalendarRoot->disableListing = $disableListing;
119
-		$roomCalendarRoot = new CalendarRoot($calendarRoomPrincipalBackend, $caldavBackend, 'principals/calendar-rooms', $logger);
120
-		$roomCalendarRoot->disableListing = $disableListing;
121
-
122
-		$publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config, $logger);
123
-
124
-		$systemTagCollection = new SystemTag\SystemTagsByIdCollection(
125
-			\OC::$server->getSystemTagManager(),
126
-			\OC::$server->getUserSession(),
127
-			$groupManager
128
-		);
129
-		$systemTagRelationsCollection = new SystemTag\SystemTagsRelationsCollection(
130
-			\OC::$server->getSystemTagManager(),
131
-			\OC::$server->getSystemTagObjectMapper(),
132
-			\OC::$server->getUserSession(),
133
-			$groupManager,
134
-			\OC::$server->getEventDispatcher()
135
-		);
136
-		$systemTagInUseCollection = \OCP\Server::get(SystemTag\SystemTagsInUseCollection::class);
137
-		$commentsCollection = new Comments\RootCollection(
138
-			\OC::$server->getCommentsManager(),
139
-			$userManager,
140
-			\OC::$server->getUserSession(),
141
-			\OC::$server->getEventDispatcher(),
142
-			$logger
143
-		);
144
-
145
-		$pluginManager = new PluginManager(\OC::$server, \OC::$server->query(IAppManager::class));
146
-		$usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
147
-		$usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, 'principals/users');
148
-		$usersAddressBookRoot->disableListing = $disableListing;
149
-
150
-		$systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
151
-		$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, 'principals/system');
152
-		$systemAddressBookRoot->disableListing = $disableListing;
153
-
154
-		$uploadCollection = new Upload\RootCollection(
155
-			$userPrincipalBackend,
156
-			'principals/users',
157
-			\OC::$server->query(CleanupService::class));
158
-		$uploadCollection->disableListing = $disableListing;
159
-
160
-		$avatarCollection = new Avatars\RootCollection($userPrincipalBackend, 'principals/users');
161
-		$avatarCollection->disableListing = $disableListing;
162
-
163
-		$appleProvisioning = new AppleProvisioningNode(
164
-			\OC::$server->query(ITimeFactory::class));
165
-
166
-		$children = [
167
-			new SimpleCollection('principals', [
168
-				$userPrincipals,
169
-				$groupPrincipals,
170
-				$systemPrincipals,
171
-				$calendarResourcePrincipals,
172
-				$calendarRoomPrincipals]),
173
-			$filesCollection,
174
-			$userCalendarRoot,
175
-			new SimpleCollection('system-calendars', [
176
-				$resourceCalendarRoot,
177
-				$roomCalendarRoot,
178
-			]),
179
-			$publicCalendarRoot,
180
-			new SimpleCollection('addressbooks', [
181
-				$usersAddressBookRoot,
182
-				$systemAddressBookRoot]),
183
-			$systemTagCollection,
184
-			$systemTagRelationsCollection,
185
-			$systemTagInUseCollection,
186
-			$commentsCollection,
187
-			$uploadCollection,
188
-			$avatarCollection,
189
-			new SimpleCollection('provisioning', [
190
-				$appleProvisioning
191
-			])
192
-		];
193
-
194
-		parent::__construct('root', $children);
195
-	}
57
+    public function __construct() {
58
+        $l10n = \OC::$server->getL10N('dav');
59
+        $random = \OC::$server->getSecureRandom();
60
+        $logger = \OC::$server->get(LoggerInterface::class);
61
+        $userManager = \OC::$server->getUserManager();
62
+        $userSession = \OC::$server->getUserSession();
63
+        $groupManager = \OC::$server->getGroupManager();
64
+        $shareManager = \OC::$server->getShareManager();
65
+        $db = \OC::$server->getDatabaseConnection();
66
+        $dispatcher = \OC::$server->get(IEventDispatcher::class);
67
+        $config = \OC::$server->get(IConfig::class);
68
+        $proxyMapper = \OC::$server->query(ProxyMapper::class);
69
+        $rootFolder = \OCP\Server::get(IRootFolder::class);
70
+
71
+        $userPrincipalBackend = new Principal(
72
+            $userManager,
73
+            $groupManager,
74
+            \OC::$server->get(IAccountManager::class),
75
+            $shareManager,
76
+            \OC::$server->getUserSession(),
77
+            \OC::$server->getAppManager(),
78
+            $proxyMapper,
79
+            \OC::$server->get(KnownUserService::class),
80
+            \OC::$server->getConfig(),
81
+            \OC::$server->getL10NFactory()
82
+        );
83
+        $groupPrincipalBackend = new GroupPrincipalBackend($groupManager, $userSession, $shareManager, $config);
84
+        $calendarResourcePrincipalBackend = new ResourcePrincipalBackend($db, $userSession, $groupManager, $logger, $proxyMapper);
85
+        $calendarRoomPrincipalBackend = new RoomPrincipalBackend($db, $userSession, $groupManager, $logger, $proxyMapper);
86
+        // as soon as debug mode is enabled we allow listing of principals
87
+        $disableListing = !$config->getSystemValue('debug', false);
88
+
89
+        // setup the first level of the dav tree
90
+        $userPrincipals = new Collection($userPrincipalBackend, 'principals/users');
91
+        $userPrincipals->disableListing = $disableListing;
92
+        $groupPrincipals = new Collection($groupPrincipalBackend, 'principals/groups');
93
+        $groupPrincipals->disableListing = $disableListing;
94
+        $systemPrincipals = new Collection(new SystemPrincipalBackend(), 'principals/system');
95
+        $systemPrincipals->disableListing = $disableListing;
96
+        $calendarResourcePrincipals = new Collection($calendarResourcePrincipalBackend, 'principals/calendar-resources');
97
+        $calendarResourcePrincipals->disableListing = $disableListing;
98
+        $calendarRoomPrincipals = new Collection($calendarRoomPrincipalBackend, 'principals/calendar-rooms');
99
+        $calendarRoomPrincipals->disableListing = $disableListing;
100
+
101
+
102
+        $filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users');
103
+        $filesCollection->disableListing = $disableListing;
104
+        $caldavBackend = new CalDavBackend(
105
+            $db,
106
+            $userPrincipalBackend,
107
+            $userManager,
108
+            $groupManager,
109
+            $random,
110
+            $logger,
111
+            $dispatcher,
112
+            $config
113
+        );
114
+        $userCalendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users', $logger);
115
+        $userCalendarRoot->disableListing = $disableListing;
116
+
117
+        $resourceCalendarRoot = new CalendarRoot($calendarResourcePrincipalBackend, $caldavBackend, 'principals/calendar-resources', $logger);
118
+        $resourceCalendarRoot->disableListing = $disableListing;
119
+        $roomCalendarRoot = new CalendarRoot($calendarRoomPrincipalBackend, $caldavBackend, 'principals/calendar-rooms', $logger);
120
+        $roomCalendarRoot->disableListing = $disableListing;
121
+
122
+        $publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config, $logger);
123
+
124
+        $systemTagCollection = new SystemTag\SystemTagsByIdCollection(
125
+            \OC::$server->getSystemTagManager(),
126
+            \OC::$server->getUserSession(),
127
+            $groupManager
128
+        );
129
+        $systemTagRelationsCollection = new SystemTag\SystemTagsRelationsCollection(
130
+            \OC::$server->getSystemTagManager(),
131
+            \OC::$server->getSystemTagObjectMapper(),
132
+            \OC::$server->getUserSession(),
133
+            $groupManager,
134
+            \OC::$server->getEventDispatcher()
135
+        );
136
+        $systemTagInUseCollection = \OCP\Server::get(SystemTag\SystemTagsInUseCollection::class);
137
+        $commentsCollection = new Comments\RootCollection(
138
+            \OC::$server->getCommentsManager(),
139
+            $userManager,
140
+            \OC::$server->getUserSession(),
141
+            \OC::$server->getEventDispatcher(),
142
+            $logger
143
+        );
144
+
145
+        $pluginManager = new PluginManager(\OC::$server, \OC::$server->query(IAppManager::class));
146
+        $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
147
+        $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, 'principals/users');
148
+        $usersAddressBookRoot->disableListing = $disableListing;
149
+
150
+        $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
151
+        $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, 'principals/system');
152
+        $systemAddressBookRoot->disableListing = $disableListing;
153
+
154
+        $uploadCollection = new Upload\RootCollection(
155
+            $userPrincipalBackend,
156
+            'principals/users',
157
+            \OC::$server->query(CleanupService::class));
158
+        $uploadCollection->disableListing = $disableListing;
159
+
160
+        $avatarCollection = new Avatars\RootCollection($userPrincipalBackend, 'principals/users');
161
+        $avatarCollection->disableListing = $disableListing;
162
+
163
+        $appleProvisioning = new AppleProvisioningNode(
164
+            \OC::$server->query(ITimeFactory::class));
165
+
166
+        $children = [
167
+            new SimpleCollection('principals', [
168
+                $userPrincipals,
169
+                $groupPrincipals,
170
+                $systemPrincipals,
171
+                $calendarResourcePrincipals,
172
+                $calendarRoomPrincipals]),
173
+            $filesCollection,
174
+            $userCalendarRoot,
175
+            new SimpleCollection('system-calendars', [
176
+                $resourceCalendarRoot,
177
+                $roomCalendarRoot,
178
+            ]),
179
+            $publicCalendarRoot,
180
+            new SimpleCollection('addressbooks', [
181
+                $usersAddressBookRoot,
182
+                $systemAddressBookRoot]),
183
+            $systemTagCollection,
184
+            $systemTagRelationsCollection,
185
+            $systemTagInUseCollection,
186
+            $commentsCollection,
187
+            $uploadCollection,
188
+            $avatarCollection,
189
+            new SimpleCollection('provisioning', [
190
+                $appleProvisioning
191
+            ])
192
+        ];
193
+
194
+        parent::__construct('root', $children);
195
+    }
196 196
 }
Please login to merge, or discard this patch.
apps/dav/lib/SystemTag/SystemTagPlugin.php 1 patch
Indentation   +388 added lines, -388 removed lines patch added patch discarded remove patch
@@ -53,392 +53,392 @@
 block discarded – undo
53 53
  */
54 54
 class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
55 55
 
56
-	// namespace
57
-	public const NS_OWNCLOUD = 'http://owncloud.org/ns';
58
-	public const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
59
-	public const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
60
-	public const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
61
-	public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
62
-	public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
63
-	public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
64
-	public const SYSTEM_TAGS_PROPERTYNAME = '{http://nextcloud.org/ns}system-tags';
65
-	public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned';
66
-	public const FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid';
67
-
68
-	/**
69
-	 * @var \Sabre\DAV\Server $server
70
-	 */
71
-	private $server;
72
-
73
-	/**
74
-	 * @var ISystemTagManager
75
-	 */
76
-	protected $tagManager;
77
-
78
-	/**
79
-	 * @var IUserSession
80
-	 */
81
-	protected $userSession;
82
-
83
-	/**
84
-	 * @var IGroupManager
85
-	 */
86
-	protected $groupManager;
87
-
88
-	/** @var array<int, string[]> */
89
-	private array $cachedTagMappings = [];
90
-	/** @var array<string, ISystemTag> */
91
-	private array $cachedTags = [];
92
-
93
-	private ISystemTagObjectMapper $tagMapper;
94
-
95
-	public function __construct(
96
-		ISystemTagManager $tagManager,
97
-		IGroupManager $groupManager,
98
-		IUserSession $userSession,
99
-		ISystemTagObjectMapper $tagMapper,
100
-	) {
101
-		$this->tagManager = $tagManager;
102
-		$this->userSession = $userSession;
103
-		$this->groupManager = $groupManager;
104
-		$this->tagMapper = $tagMapper;
105
-	}
106
-
107
-	/**
108
-	 * This initializes the plugin.
109
-	 *
110
-	 * This function is called by \Sabre\DAV\Server, after
111
-	 * addPlugin is called.
112
-	 *
113
-	 * This method should set up the required event subscriptions.
114
-	 *
115
-	 * @param \Sabre\DAV\Server $server
116
-	 * @return void
117
-	 */
118
-	public function initialize(\Sabre\DAV\Server $server) {
119
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
120
-
121
-		$server->protectedProperties[] = self::ID_PROPERTYNAME;
122
-
123
-		$server->on('propFind', [$this, 'handleGetProperties']);
124
-		$server->on('propPatch', [$this, 'handleUpdateProperties']);
125
-		$server->on('method:POST', [$this, 'httpPost']);
126
-
127
-		$this->server = $server;
128
-	}
129
-
130
-	/**
131
-	 * POST operation on system tag collections
132
-	 *
133
-	 * @param RequestInterface $request request object
134
-	 * @param ResponseInterface $response response object
135
-	 * @return null|false
136
-	 */
137
-	public function httpPost(RequestInterface $request, ResponseInterface $response) {
138
-		$path = $request->getPath();
139
-
140
-		// Making sure the node exists
141
-		$node = $this->server->tree->getNodeForPath($path);
142
-		if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
143
-			$data = $request->getBodyAsString();
144
-
145
-			$tag = $this->createTag($data, $request->getHeader('Content-Type'));
146
-
147
-			if ($node instanceof SystemTagsObjectMappingCollection) {
148
-				// also add to collection
149
-				$node->createFile($tag->getId());
150
-				$url = $request->getBaseUrl() . 'systemtags/';
151
-			} else {
152
-				$url = $request->getUrl();
153
-			}
154
-
155
-			if ($url[strlen($url) - 1] !== '/') {
156
-				$url .= '/';
157
-			}
158
-
159
-			$response->setHeader('Content-Location', $url . $tag->getId());
160
-
161
-			// created
162
-			$response->setStatus(201);
163
-			return false;
164
-		}
165
-	}
166
-
167
-	/**
168
-	 * Creates a new tag
169
-	 *
170
-	 * @param string $data JSON encoded string containing the properties of the tag to create
171
-	 * @param string $contentType content type of the data
172
-	 * @return ISystemTag newly created system tag
173
-	 *
174
-	 * @throws BadRequest if a field was missing
175
-	 * @throws Conflict if a tag with the same properties already exists
176
-	 * @throws UnsupportedMediaType if the content type is not supported
177
-	 */
178
-	private function createTag($data, $contentType = 'application/json') {
179
-		if (explode(';', $contentType)[0] === 'application/json') {
180
-			$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
181
-		} else {
182
-			throw new UnsupportedMediaType();
183
-		}
184
-
185
-		if (!isset($data['name'])) {
186
-			throw new BadRequest('Missing "name" attribute');
187
-		}
188
-
189
-		$tagName = $data['name'];
190
-		$userVisible = true;
191
-		$userAssignable = true;
192
-
193
-		if (isset($data['userVisible'])) {
194
-			$userVisible = (bool)$data['userVisible'];
195
-		}
196
-
197
-		if (isset($data['userAssignable'])) {
198
-			$userAssignable = (bool)$data['userAssignable'];
199
-		}
200
-
201
-		$groups = [];
202
-		if (isset($data['groups'])) {
203
-			$groups = $data['groups'];
204
-			if (is_string($groups)) {
205
-				$groups = explode('|', $groups);
206
-			}
207
-		}
208
-
209
-		if ($userVisible === false || $userAssignable === false || !empty($groups)) {
210
-			if (!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
211
-				throw new BadRequest('Not sufficient permissions');
212
-			}
213
-		}
214
-
215
-		try {
216
-			$tag = $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
217
-			if (!empty($groups)) {
218
-				$this->tagManager->setTagGroups($tag, $groups);
219
-			}
220
-			return $tag;
221
-		} catch (TagAlreadyExistsException $e) {
222
-			throw new Conflict('Tag already exists', 0, $e);
223
-		}
224
-	}
225
-
226
-
227
-	/**
228
-	 * Retrieves system tag properties
229
-	 *
230
-	 * @param PropFind $propFind
231
-	 * @param \Sabre\DAV\INode $node
232
-	 *
233
-	 * @return void
234
-	 */
235
-	public function handleGetProperties(
236
-		PropFind $propFind,
237
-		\Sabre\DAV\INode $node
238
-	) {
239
-		if ($node instanceof Node) {
240
-			$this->propfindForFile($propFind, $node);
241
-			return;
242
-		}
243
-
244
-		if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
245
-			return;
246
-		}
247
-
248
-		// child nodes from systemtags-assigned should point to normal tag endpoint
249
-		if (preg_match('/^systemtags-assigned\/[0-9]+/', $propFind->getPath())) {
250
-			$propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath()));
251
-		}
252
-
253
-		$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
254
-			return $node->getSystemTag()->getId();
255
-		});
256
-
257
-		$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
258
-			return $node->getSystemTag()->getName();
259
-		});
260
-
261
-		$propFind->handle(self::USERVISIBLE_PROPERTYNAME, function () use ($node) {
262
-			return $node->getSystemTag()->isUserVisible() ? 'true' : 'false';
263
-		});
264
-
265
-		$propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function () use ($node) {
266
-			// this is the tag's inherent property "is user assignable"
267
-			return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false';
268
-		});
269
-
270
-		$propFind->handle(self::CANASSIGN_PROPERTYNAME, function () use ($node) {
271
-			// this is the effective permission for the current user
272
-			return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false';
273
-		});
274
-
275
-		$propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) {
276
-			if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
277
-				// property only available for admins
278
-				throw new Forbidden();
279
-			}
280
-			$groups = [];
281
-			// no need to retrieve groups for namespaces that don't qualify
282
-			if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) {
283
-				$groups = $this->tagManager->getTagGroups($node->getSystemTag());
284
-			}
285
-			return implode('|', $groups);
286
-		});
287
-
288
-		if ($node instanceof SystemTagNode) {
289
-			$propFind->handle(self::NUM_FILES_PROPERTYNAME, function () use ($node): int {
290
-				return $node->getNumberOfFiles();
291
-			});
292
-
293
-			$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node): int {
294
-				return $node->getReferenceFileId();
295
-			});
296
-		}
297
-	}
298
-
299
-	private function propfindForFile(PropFind $propFind, Node $node): void {
300
-		if ($node instanceof Directory
301
-			&& $propFind->getDepth() !== 0
302
-			&& !is_null($propFind->getStatus(self::SYSTEM_TAGS_PROPERTYNAME))) {
303
-			$fileIds = [$node->getId()];
304
-
305
-			// note: pre-fetching only supported for depth <= 1
306
-			$folderContent = $node->getNode()->getDirectoryListing();
307
-			foreach ($folderContent as $info) {
308
-				$fileIds[] = $info->getId();
309
-			}
310
-
311
-			$tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files');
312
-
313
-			$this->cachedTagMappings = $this->cachedTagMappings + $tags;
314
-			$emptyFileIds = array_diff($fileIds, array_keys($tags));
315
-
316
-			// also cache the ones that were not found
317
-			foreach ($emptyFileIds as $fileId) {
318
-				$this->cachedTagMappings[$fileId] = [];
319
-			}
320
-		}
321
-
322
-		$propFind->handle(self::SYSTEM_TAGS_PROPERTYNAME, function () use ($node) {
323
-			$user = $this->userSession->getUser();
324
-			if ($user === null) {
325
-				return;
326
-			}
327
-
328
-			$tags = $this->getTagsForFile($node->getId(), $user);
329
-			usort($tags, function (ISystemTag $tagA, ISystemTag $tagB): int {
330
-				return Util::naturalSortCompare($tagA->getName(), $tagB->getName());
331
-			});
332
-			return new SystemTagList($tags, $this->tagManager, $user);
333
-		});
334
-	}
335
-
336
-	/**
337
-	 * @param int $fileId
338
-	 * @return ISystemTag[]
339
-	 */
340
-	private function getTagsForFile(int $fileId, IUser $user): array {
341
-
342
-		if (isset($this->cachedTagMappings[$fileId])) {
343
-			$tagIds = $this->cachedTagMappings[$fileId];
344
-		} else {
345
-			$tags = $this->tagMapper->getTagIdsForObjects([$fileId], 'files');
346
-			$fileTags = current($tags);
347
-			if ($fileTags) {
348
-				$tagIds = $fileTags;
349
-			} else {
350
-				$tagIds = [];
351
-			}
352
-		}
353
-
354
-		$tags = array_filter(array_map(function(string $tagId) {
355
-			return $this->cachedTags[$tagId] ?? null;
356
-		}, $tagIds));
357
-
358
-		$uncachedTagIds = array_filter($tagIds, function(string $tagId): bool {
359
-			return !isset($this->cachedTags[$tagId]);
360
-		});
361
-
362
-		if (count($uncachedTagIds)) {
363
-			$retrievedTags = $this->tagManager->getTagsByIds($uncachedTagIds);
364
-			foreach ($retrievedTags as $tag) {
365
-				$this->cachedTags[$tag->getId()] = $tag;
366
-			}
367
-			$tags += $retrievedTags;
368
-		}
369
-
370
-		return array_filter($tags, function(ISystemTag $tag) use ($user) {
371
-			return $this->tagManager->canUserSeeTag($tag, $user);
372
-		});
373
-	}
374
-
375
-	/**
376
-	 * Updates tag attributes
377
-	 *
378
-	 * @param string $path
379
-	 * @param PropPatch $propPatch
380
-	 *
381
-	 * @return void
382
-	 */
383
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
384
-		$node = $this->server->tree->getNodeForPath($path);
385
-		if (!($node instanceof SystemTagNode)) {
386
-			return;
387
-		}
388
-
389
-		$propPatch->handle([
390
-			self::DISPLAYNAME_PROPERTYNAME,
391
-			self::USERVISIBLE_PROPERTYNAME,
392
-			self::USERASSIGNABLE_PROPERTYNAME,
393
-			self::GROUPS_PROPERTYNAME,
394
-			self::NUM_FILES_PROPERTYNAME,
395
-			self::FILEID_PROPERTYNAME,
396
-		], function ($props) use ($node) {
397
-			$tag = $node->getSystemTag();
398
-			$name = $tag->getName();
399
-			$userVisible = $tag->isUserVisible();
400
-			$userAssignable = $tag->isUserAssignable();
401
-
402
-			$updateTag = false;
403
-
404
-			if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) {
405
-				$name = $props[self::DISPLAYNAME_PROPERTYNAME];
406
-				$updateTag = true;
407
-			}
408
-
409
-			if (isset($props[self::USERVISIBLE_PROPERTYNAME])) {
410
-				$propValue = $props[self::USERVISIBLE_PROPERTYNAME];
411
-				$userVisible = ($propValue !== 'false' && $propValue !== '0');
412
-				$updateTag = true;
413
-			}
414
-
415
-			if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) {
416
-				$propValue = $props[self::USERASSIGNABLE_PROPERTYNAME];
417
-				$userAssignable = ($propValue !== 'false' && $propValue !== '0');
418
-				$updateTag = true;
419
-			}
420
-
421
-			if (isset($props[self::GROUPS_PROPERTYNAME])) {
422
-				if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
423
-					// property only available for admins
424
-					throw new Forbidden();
425
-				}
426
-
427
-				$propValue = $props[self::GROUPS_PROPERTYNAME];
428
-				$groupIds = explode('|', $propValue);
429
-				$this->tagManager->setTagGroups($tag, $groupIds);
430
-			}
431
-
432
-			if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::FILEID_PROPERTYNAME])) {
433
-				// read-only properties
434
-				throw new Forbidden();
435
-			}
436
-
437
-			if ($updateTag) {
438
-				$node->update($name, $userVisible, $userAssignable);
439
-			}
440
-
441
-			return true;
442
-		});
443
-	}
56
+    // namespace
57
+    public const NS_OWNCLOUD = 'http://owncloud.org/ns';
58
+    public const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
59
+    public const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
60
+    public const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
61
+    public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
62
+    public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
63
+    public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
64
+    public const SYSTEM_TAGS_PROPERTYNAME = '{http://nextcloud.org/ns}system-tags';
65
+    public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned';
66
+    public const FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid';
67
+
68
+    /**
69
+     * @var \Sabre\DAV\Server $server
70
+     */
71
+    private $server;
72
+
73
+    /**
74
+     * @var ISystemTagManager
75
+     */
76
+    protected $tagManager;
77
+
78
+    /**
79
+     * @var IUserSession
80
+     */
81
+    protected $userSession;
82
+
83
+    /**
84
+     * @var IGroupManager
85
+     */
86
+    protected $groupManager;
87
+
88
+    /** @var array<int, string[]> */
89
+    private array $cachedTagMappings = [];
90
+    /** @var array<string, ISystemTag> */
91
+    private array $cachedTags = [];
92
+
93
+    private ISystemTagObjectMapper $tagMapper;
94
+
95
+    public function __construct(
96
+        ISystemTagManager $tagManager,
97
+        IGroupManager $groupManager,
98
+        IUserSession $userSession,
99
+        ISystemTagObjectMapper $tagMapper,
100
+    ) {
101
+        $this->tagManager = $tagManager;
102
+        $this->userSession = $userSession;
103
+        $this->groupManager = $groupManager;
104
+        $this->tagMapper = $tagMapper;
105
+    }
106
+
107
+    /**
108
+     * This initializes the plugin.
109
+     *
110
+     * This function is called by \Sabre\DAV\Server, after
111
+     * addPlugin is called.
112
+     *
113
+     * This method should set up the required event subscriptions.
114
+     *
115
+     * @param \Sabre\DAV\Server $server
116
+     * @return void
117
+     */
118
+    public function initialize(\Sabre\DAV\Server $server) {
119
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
120
+
121
+        $server->protectedProperties[] = self::ID_PROPERTYNAME;
122
+
123
+        $server->on('propFind', [$this, 'handleGetProperties']);
124
+        $server->on('propPatch', [$this, 'handleUpdateProperties']);
125
+        $server->on('method:POST', [$this, 'httpPost']);
126
+
127
+        $this->server = $server;
128
+    }
129
+
130
+    /**
131
+     * POST operation on system tag collections
132
+     *
133
+     * @param RequestInterface $request request object
134
+     * @param ResponseInterface $response response object
135
+     * @return null|false
136
+     */
137
+    public function httpPost(RequestInterface $request, ResponseInterface $response) {
138
+        $path = $request->getPath();
139
+
140
+        // Making sure the node exists
141
+        $node = $this->server->tree->getNodeForPath($path);
142
+        if ($node instanceof SystemTagsByIdCollection || $node instanceof SystemTagsObjectMappingCollection) {
143
+            $data = $request->getBodyAsString();
144
+
145
+            $tag = $this->createTag($data, $request->getHeader('Content-Type'));
146
+
147
+            if ($node instanceof SystemTagsObjectMappingCollection) {
148
+                // also add to collection
149
+                $node->createFile($tag->getId());
150
+                $url = $request->getBaseUrl() . 'systemtags/';
151
+            } else {
152
+                $url = $request->getUrl();
153
+            }
154
+
155
+            if ($url[strlen($url) - 1] !== '/') {
156
+                $url .= '/';
157
+            }
158
+
159
+            $response->setHeader('Content-Location', $url . $tag->getId());
160
+
161
+            // created
162
+            $response->setStatus(201);
163
+            return false;
164
+        }
165
+    }
166
+
167
+    /**
168
+     * Creates a new tag
169
+     *
170
+     * @param string $data JSON encoded string containing the properties of the tag to create
171
+     * @param string $contentType content type of the data
172
+     * @return ISystemTag newly created system tag
173
+     *
174
+     * @throws BadRequest if a field was missing
175
+     * @throws Conflict if a tag with the same properties already exists
176
+     * @throws UnsupportedMediaType if the content type is not supported
177
+     */
178
+    private function createTag($data, $contentType = 'application/json') {
179
+        if (explode(';', $contentType)[0] === 'application/json') {
180
+            $data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
181
+        } else {
182
+            throw new UnsupportedMediaType();
183
+        }
184
+
185
+        if (!isset($data['name'])) {
186
+            throw new BadRequest('Missing "name" attribute');
187
+        }
188
+
189
+        $tagName = $data['name'];
190
+        $userVisible = true;
191
+        $userAssignable = true;
192
+
193
+        if (isset($data['userVisible'])) {
194
+            $userVisible = (bool)$data['userVisible'];
195
+        }
196
+
197
+        if (isset($data['userAssignable'])) {
198
+            $userAssignable = (bool)$data['userAssignable'];
199
+        }
200
+
201
+        $groups = [];
202
+        if (isset($data['groups'])) {
203
+            $groups = $data['groups'];
204
+            if (is_string($groups)) {
205
+                $groups = explode('|', $groups);
206
+            }
207
+        }
208
+
209
+        if ($userVisible === false || $userAssignable === false || !empty($groups)) {
210
+            if (!$this->userSession->isLoggedIn() || !$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
211
+                throw new BadRequest('Not sufficient permissions');
212
+            }
213
+        }
214
+
215
+        try {
216
+            $tag = $this->tagManager->createTag($tagName, $userVisible, $userAssignable);
217
+            if (!empty($groups)) {
218
+                $this->tagManager->setTagGroups($tag, $groups);
219
+            }
220
+            return $tag;
221
+        } catch (TagAlreadyExistsException $e) {
222
+            throw new Conflict('Tag already exists', 0, $e);
223
+        }
224
+    }
225
+
226
+
227
+    /**
228
+     * Retrieves system tag properties
229
+     *
230
+     * @param PropFind $propFind
231
+     * @param \Sabre\DAV\INode $node
232
+     *
233
+     * @return void
234
+     */
235
+    public function handleGetProperties(
236
+        PropFind $propFind,
237
+        \Sabre\DAV\INode $node
238
+    ) {
239
+        if ($node instanceof Node) {
240
+            $this->propfindForFile($propFind, $node);
241
+            return;
242
+        }
243
+
244
+        if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
245
+            return;
246
+        }
247
+
248
+        // child nodes from systemtags-assigned should point to normal tag endpoint
249
+        if (preg_match('/^systemtags-assigned\/[0-9]+/', $propFind->getPath())) {
250
+            $propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath()));
251
+        }
252
+
253
+        $propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
254
+            return $node->getSystemTag()->getId();
255
+        });
256
+
257
+        $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
258
+            return $node->getSystemTag()->getName();
259
+        });
260
+
261
+        $propFind->handle(self::USERVISIBLE_PROPERTYNAME, function () use ($node) {
262
+            return $node->getSystemTag()->isUserVisible() ? 'true' : 'false';
263
+        });
264
+
265
+        $propFind->handle(self::USERASSIGNABLE_PROPERTYNAME, function () use ($node) {
266
+            // this is the tag's inherent property "is user assignable"
267
+            return $node->getSystemTag()->isUserAssignable() ? 'true' : 'false';
268
+        });
269
+
270
+        $propFind->handle(self::CANASSIGN_PROPERTYNAME, function () use ($node) {
271
+            // this is the effective permission for the current user
272
+            return $this->tagManager->canUserAssignTag($node->getSystemTag(), $this->userSession->getUser()) ? 'true' : 'false';
273
+        });
274
+
275
+        $propFind->handle(self::GROUPS_PROPERTYNAME, function () use ($node) {
276
+            if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
277
+                // property only available for admins
278
+                throw new Forbidden();
279
+            }
280
+            $groups = [];
281
+            // no need to retrieve groups for namespaces that don't qualify
282
+            if ($node->getSystemTag()->isUserVisible() && !$node->getSystemTag()->isUserAssignable()) {
283
+                $groups = $this->tagManager->getTagGroups($node->getSystemTag());
284
+            }
285
+            return implode('|', $groups);
286
+        });
287
+
288
+        if ($node instanceof SystemTagNode) {
289
+            $propFind->handle(self::NUM_FILES_PROPERTYNAME, function () use ($node): int {
290
+                return $node->getNumberOfFiles();
291
+            });
292
+
293
+            $propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node): int {
294
+                return $node->getReferenceFileId();
295
+            });
296
+        }
297
+    }
298
+
299
+    private function propfindForFile(PropFind $propFind, Node $node): void {
300
+        if ($node instanceof Directory
301
+            && $propFind->getDepth() !== 0
302
+            && !is_null($propFind->getStatus(self::SYSTEM_TAGS_PROPERTYNAME))) {
303
+            $fileIds = [$node->getId()];
304
+
305
+            // note: pre-fetching only supported for depth <= 1
306
+            $folderContent = $node->getNode()->getDirectoryListing();
307
+            foreach ($folderContent as $info) {
308
+                $fileIds[] = $info->getId();
309
+            }
310
+
311
+            $tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files');
312
+
313
+            $this->cachedTagMappings = $this->cachedTagMappings + $tags;
314
+            $emptyFileIds = array_diff($fileIds, array_keys($tags));
315
+
316
+            // also cache the ones that were not found
317
+            foreach ($emptyFileIds as $fileId) {
318
+                $this->cachedTagMappings[$fileId] = [];
319
+            }
320
+        }
321
+
322
+        $propFind->handle(self::SYSTEM_TAGS_PROPERTYNAME, function () use ($node) {
323
+            $user = $this->userSession->getUser();
324
+            if ($user === null) {
325
+                return;
326
+            }
327
+
328
+            $tags = $this->getTagsForFile($node->getId(), $user);
329
+            usort($tags, function (ISystemTag $tagA, ISystemTag $tagB): int {
330
+                return Util::naturalSortCompare($tagA->getName(), $tagB->getName());
331
+            });
332
+            return new SystemTagList($tags, $this->tagManager, $user);
333
+        });
334
+    }
335
+
336
+    /**
337
+     * @param int $fileId
338
+     * @return ISystemTag[]
339
+     */
340
+    private function getTagsForFile(int $fileId, IUser $user): array {
341
+
342
+        if (isset($this->cachedTagMappings[$fileId])) {
343
+            $tagIds = $this->cachedTagMappings[$fileId];
344
+        } else {
345
+            $tags = $this->tagMapper->getTagIdsForObjects([$fileId], 'files');
346
+            $fileTags = current($tags);
347
+            if ($fileTags) {
348
+                $tagIds = $fileTags;
349
+            } else {
350
+                $tagIds = [];
351
+            }
352
+        }
353
+
354
+        $tags = array_filter(array_map(function(string $tagId) {
355
+            return $this->cachedTags[$tagId] ?? null;
356
+        }, $tagIds));
357
+
358
+        $uncachedTagIds = array_filter($tagIds, function(string $tagId): bool {
359
+            return !isset($this->cachedTags[$tagId]);
360
+        });
361
+
362
+        if (count($uncachedTagIds)) {
363
+            $retrievedTags = $this->tagManager->getTagsByIds($uncachedTagIds);
364
+            foreach ($retrievedTags as $tag) {
365
+                $this->cachedTags[$tag->getId()] = $tag;
366
+            }
367
+            $tags += $retrievedTags;
368
+        }
369
+
370
+        return array_filter($tags, function(ISystemTag $tag) use ($user) {
371
+            return $this->tagManager->canUserSeeTag($tag, $user);
372
+        });
373
+    }
374
+
375
+    /**
376
+     * Updates tag attributes
377
+     *
378
+     * @param string $path
379
+     * @param PropPatch $propPatch
380
+     *
381
+     * @return void
382
+     */
383
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
384
+        $node = $this->server->tree->getNodeForPath($path);
385
+        if (!($node instanceof SystemTagNode)) {
386
+            return;
387
+        }
388
+
389
+        $propPatch->handle([
390
+            self::DISPLAYNAME_PROPERTYNAME,
391
+            self::USERVISIBLE_PROPERTYNAME,
392
+            self::USERASSIGNABLE_PROPERTYNAME,
393
+            self::GROUPS_PROPERTYNAME,
394
+            self::NUM_FILES_PROPERTYNAME,
395
+            self::FILEID_PROPERTYNAME,
396
+        ], function ($props) use ($node) {
397
+            $tag = $node->getSystemTag();
398
+            $name = $tag->getName();
399
+            $userVisible = $tag->isUserVisible();
400
+            $userAssignable = $tag->isUserAssignable();
401
+
402
+            $updateTag = false;
403
+
404
+            if (isset($props[self::DISPLAYNAME_PROPERTYNAME])) {
405
+                $name = $props[self::DISPLAYNAME_PROPERTYNAME];
406
+                $updateTag = true;
407
+            }
408
+
409
+            if (isset($props[self::USERVISIBLE_PROPERTYNAME])) {
410
+                $propValue = $props[self::USERVISIBLE_PROPERTYNAME];
411
+                $userVisible = ($propValue !== 'false' && $propValue !== '0');
412
+                $updateTag = true;
413
+            }
414
+
415
+            if (isset($props[self::USERASSIGNABLE_PROPERTYNAME])) {
416
+                $propValue = $props[self::USERASSIGNABLE_PROPERTYNAME];
417
+                $userAssignable = ($propValue !== 'false' && $propValue !== '0');
418
+                $updateTag = true;
419
+            }
420
+
421
+            if (isset($props[self::GROUPS_PROPERTYNAME])) {
422
+                if (!$this->groupManager->isAdmin($this->userSession->getUser()->getUID())) {
423
+                    // property only available for admins
424
+                    throw new Forbidden();
425
+                }
426
+
427
+                $propValue = $props[self::GROUPS_PROPERTYNAME];
428
+                $groupIds = explode('|', $propValue);
429
+                $this->tagManager->setTagGroups($tag, $groupIds);
430
+            }
431
+
432
+            if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::FILEID_PROPERTYNAME])) {
433
+                // read-only properties
434
+                throw new Forbidden();
435
+            }
436
+
437
+            if ($updateTag) {
438
+                $node->update($name, $userVisible, $userAssignable);
439
+            }
440
+
441
+            return true;
442
+        });
443
+    }
444 444
 }
Please login to merge, or discard this patch.
apps/dav/lib/SystemTag/SystemTagNode.php 1 patch
Indentation   +158 added lines, -158 removed lines patch added patch discarded remove patch
@@ -40,162 +40,162 @@
 block discarded – undo
40 40
  */
41 41
 class SystemTagNode implements \Sabre\DAV\INode {
42 42
 
43
-	/**
44
-	 * @var ISystemTag
45
-	 */
46
-	protected $tag;
47
-
48
-	/**
49
-	 * @var ISystemTagManager
50
-	 */
51
-	protected $tagManager;
52
-
53
-	/**
54
-	 * User
55
-	 *
56
-	 * @var IUser
57
-	 */
58
-	protected $user;
59
-
60
-	/**
61
-	 * Whether to allow permissions for admins
62
-	 *
63
-	 * @var bool
64
-	 */
65
-	protected $isAdmin;
66
-
67
-	protected int $numberOfFiles = -1;
68
-	protected int $referenceFileId = -1;
69
-
70
-	/**
71
-	 * Sets up the node, expects a full path name
72
-	 *
73
-	 * @param ISystemTag $tag system tag
74
-	 * @param IUser $user user
75
-	 * @param bool $isAdmin whether to allow operations for admins
76
-	 * @param ISystemTagManager $tagManager tag manager
77
-	 */
78
-	public function __construct(ISystemTag $tag, IUser $user, $isAdmin, ISystemTagManager $tagManager) {
79
-		$this->tag = $tag;
80
-		$this->user = $user;
81
-		$this->isAdmin = $isAdmin;
82
-		$this->tagManager = $tagManager;
83
-	}
84
-
85
-	/**
86
-	 *  Returns the id of the tag
87
-	 *
88
-	 * @return string
89
-	 */
90
-	public function getName() {
91
-		return $this->tag->getId();
92
-	}
93
-
94
-	/**
95
-	 * Returns the system tag represented by this node
96
-	 *
97
-	 * @return ISystemTag system tag
98
-	 */
99
-	public function getSystemTag() {
100
-		return $this->tag;
101
-	}
102
-
103
-	/**
104
-	 * Renames the node
105
-	 *
106
-	 * @param string $name The new name
107
-	 *
108
-	 * @throws MethodNotAllowed not allowed to rename node
109
-	 *
110
-	 * @return never
111
-	 */
112
-	public function setName($name) {
113
-		throw new MethodNotAllowed();
114
-	}
115
-
116
-	/**
117
-	 * Update tag
118
-	 *
119
-	 * @param string $name new tag name
120
-	 * @param bool $userVisible user visible
121
-	 * @param bool $userAssignable user assignable
122
-	 *
123
-	 * @throws NotFound whenever the given tag id does not exist
124
-	 * @throws Forbidden whenever there is no permission to update said tag
125
-	 * @throws Conflict whenever a tag already exists with the given attributes
126
-	 */
127
-	public function update($name, $userVisible, $userAssignable): void {
128
-		try {
129
-			if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) {
130
-				throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist');
131
-			}
132
-			if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) {
133
-				throw new Forbidden('No permission to update tag ' . $this->tag->getId());
134
-			}
135
-
136
-			// only admin is able to change permissions, regular users can only rename
137
-			if (!$this->isAdmin) {
138
-				// only renaming is allowed for regular users
139
-				if ($userVisible !== $this->tag->isUserVisible()
140
-					|| $userAssignable !== $this->tag->isUserAssignable()
141
-				) {
142
-					throw new Forbidden('No permission to update permissions for tag ' . $this->tag->getId());
143
-				}
144
-			}
145
-
146
-			$this->tagManager->updateTag($this->tag->getId(), $name, $userVisible, $userAssignable);
147
-		} catch (TagNotFoundException $e) {
148
-			throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist');
149
-		} catch (TagAlreadyExistsException $e) {
150
-			throw new Conflict(
151
-				'Tag with the properties "' . $name . '", ' .
152
-				$userVisible . ', ' . $userAssignable . ' already exists'
153
-			);
154
-		}
155
-	}
156
-
157
-	/**
158
-	 * Returns null, not supported
159
-	 *
160
-	 * @return null
161
-	 */
162
-	public function getLastModified() {
163
-		return null;
164
-	}
165
-
166
-	/**
167
-	 * @return void
168
-	 */
169
-	public function delete() {
170
-		try {
171
-			if (!$this->isAdmin) {
172
-				throw new Forbidden('No permission to delete tag ' . $this->tag->getId());
173
-			}
174
-
175
-			if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) {
176
-				throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found');
177
-			}
178
-
179
-			$this->tagManager->deleteTags($this->tag->getId());
180
-		} catch (TagNotFoundException $e) {
181
-			// can happen if concurrent deletion occurred
182
-			throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e);
183
-		}
184
-	}
185
-
186
-	public function getNumberOfFiles(): int {
187
-		return $this->numberOfFiles;
188
-	}
189
-
190
-	public function setNumberOfFiles(int $numberOfFiles): void {
191
-		$this->numberOfFiles = $numberOfFiles;
192
-	}
193
-
194
-	public function getReferenceFileId(): int {
195
-		return $this->referenceFileId;
196
-	}
197
-
198
-	public function setReferenceFileId(int $referenceFileId): void {
199
-		$this->referenceFileId = $referenceFileId;
200
-	}
43
+    /**
44
+     * @var ISystemTag
45
+     */
46
+    protected $tag;
47
+
48
+    /**
49
+     * @var ISystemTagManager
50
+     */
51
+    protected $tagManager;
52
+
53
+    /**
54
+     * User
55
+     *
56
+     * @var IUser
57
+     */
58
+    protected $user;
59
+
60
+    /**
61
+     * Whether to allow permissions for admins
62
+     *
63
+     * @var bool
64
+     */
65
+    protected $isAdmin;
66
+
67
+    protected int $numberOfFiles = -1;
68
+    protected int $referenceFileId = -1;
69
+
70
+    /**
71
+     * Sets up the node, expects a full path name
72
+     *
73
+     * @param ISystemTag $tag system tag
74
+     * @param IUser $user user
75
+     * @param bool $isAdmin whether to allow operations for admins
76
+     * @param ISystemTagManager $tagManager tag manager
77
+     */
78
+    public function __construct(ISystemTag $tag, IUser $user, $isAdmin, ISystemTagManager $tagManager) {
79
+        $this->tag = $tag;
80
+        $this->user = $user;
81
+        $this->isAdmin = $isAdmin;
82
+        $this->tagManager = $tagManager;
83
+    }
84
+
85
+    /**
86
+     *  Returns the id of the tag
87
+     *
88
+     * @return string
89
+     */
90
+    public function getName() {
91
+        return $this->tag->getId();
92
+    }
93
+
94
+    /**
95
+     * Returns the system tag represented by this node
96
+     *
97
+     * @return ISystemTag system tag
98
+     */
99
+    public function getSystemTag() {
100
+        return $this->tag;
101
+    }
102
+
103
+    /**
104
+     * Renames the node
105
+     *
106
+     * @param string $name The new name
107
+     *
108
+     * @throws MethodNotAllowed not allowed to rename node
109
+     *
110
+     * @return never
111
+     */
112
+    public function setName($name) {
113
+        throw new MethodNotAllowed();
114
+    }
115
+
116
+    /**
117
+     * Update tag
118
+     *
119
+     * @param string $name new tag name
120
+     * @param bool $userVisible user visible
121
+     * @param bool $userAssignable user assignable
122
+     *
123
+     * @throws NotFound whenever the given tag id does not exist
124
+     * @throws Forbidden whenever there is no permission to update said tag
125
+     * @throws Conflict whenever a tag already exists with the given attributes
126
+     */
127
+    public function update($name, $userVisible, $userAssignable): void {
128
+        try {
129
+            if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) {
130
+                throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist');
131
+            }
132
+            if (!$this->tagManager->canUserAssignTag($this->tag, $this->user)) {
133
+                throw new Forbidden('No permission to update tag ' . $this->tag->getId());
134
+            }
135
+
136
+            // only admin is able to change permissions, regular users can only rename
137
+            if (!$this->isAdmin) {
138
+                // only renaming is allowed for regular users
139
+                if ($userVisible !== $this->tag->isUserVisible()
140
+                    || $userAssignable !== $this->tag->isUserAssignable()
141
+                ) {
142
+                    throw new Forbidden('No permission to update permissions for tag ' . $this->tag->getId());
143
+                }
144
+            }
145
+
146
+            $this->tagManager->updateTag($this->tag->getId(), $name, $userVisible, $userAssignable);
147
+        } catch (TagNotFoundException $e) {
148
+            throw new NotFound('Tag with id ' . $this->tag->getId() . ' does not exist');
149
+        } catch (TagAlreadyExistsException $e) {
150
+            throw new Conflict(
151
+                'Tag with the properties "' . $name . '", ' .
152
+                $userVisible . ', ' . $userAssignable . ' already exists'
153
+            );
154
+        }
155
+    }
156
+
157
+    /**
158
+     * Returns null, not supported
159
+     *
160
+     * @return null
161
+     */
162
+    public function getLastModified() {
163
+        return null;
164
+    }
165
+
166
+    /**
167
+     * @return void
168
+     */
169
+    public function delete() {
170
+        try {
171
+            if (!$this->isAdmin) {
172
+                throw new Forbidden('No permission to delete tag ' . $this->tag->getId());
173
+            }
174
+
175
+            if (!$this->tagManager->canUserSeeTag($this->tag, $this->user)) {
176
+                throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found');
177
+            }
178
+
179
+            $this->tagManager->deleteTags($this->tag->getId());
180
+        } catch (TagNotFoundException $e) {
181
+            // can happen if concurrent deletion occurred
182
+            throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e);
183
+        }
184
+    }
185
+
186
+    public function getNumberOfFiles(): int {
187
+        return $this->numberOfFiles;
188
+    }
189
+
190
+    public function setNumberOfFiles(int $numberOfFiles): void {
191
+        $this->numberOfFiles = $numberOfFiles;
192
+    }
193
+
194
+    public function getReferenceFileId(): int {
195
+        return $this->referenceFileId;
196
+    }
197
+
198
+    public function setReferenceFileId(int $referenceFileId): void {
199
+        $this->referenceFileId = $referenceFileId;
200
+    }
201 201
 }
Please login to merge, or discard this patch.
apps/dav/lib/SystemTag/SystemTagsInUseCollection.php 1 patch
Indentation   +62 added lines, -62 removed lines patch added patch discarded remove patch
@@ -38,71 +38,71 @@
 block discarded – undo
38 38
 use Sabre\DAV\SimpleCollection;
39 39
 
40 40
 class SystemTagsInUseCollection extends SimpleCollection {
41
-	protected IUserSession $userSession;
42
-	protected IRootFolder $rootFolder;
43
-	protected string $mediaType;
44
-	protected ISystemTagManager $systemTagManager;
45
-	protected SystemTagsInFilesDetector $systemTagsInFilesDetector;
41
+    protected IUserSession $userSession;
42
+    protected IRootFolder $rootFolder;
43
+    protected string $mediaType;
44
+    protected ISystemTagManager $systemTagManager;
45
+    protected SystemTagsInFilesDetector $systemTagsInFilesDetector;
46 46
 
47
-	/** @noinspection PhpMissingParentConstructorInspection */
48
-	public function __construct(
49
-		IUserSession $userSession,
50
-		IRootFolder $rootFolder,
51
-		ISystemTagManager $systemTagManager,
52
-		SystemTagsInFilesDetector $systemTagsInFilesDetector,
53
-		string $mediaType = ''
54
-	) {
55
-		$this->userSession = $userSession;
56
-		$this->rootFolder = $rootFolder;
57
-		$this->systemTagManager = $systemTagManager;
58
-		$this->mediaType = $mediaType;
59
-		$this->systemTagsInFilesDetector = $systemTagsInFilesDetector;
60
-		$this->name = 'systemtags-assigned';
61
-		if ($this->mediaType != '') {
62
-			$this->name .= '/' . $this->mediaType;
63
-		}
64
-	}
47
+    /** @noinspection PhpMissingParentConstructorInspection */
48
+    public function __construct(
49
+        IUserSession $userSession,
50
+        IRootFolder $rootFolder,
51
+        ISystemTagManager $systemTagManager,
52
+        SystemTagsInFilesDetector $systemTagsInFilesDetector,
53
+        string $mediaType = ''
54
+    ) {
55
+        $this->userSession = $userSession;
56
+        $this->rootFolder = $rootFolder;
57
+        $this->systemTagManager = $systemTagManager;
58
+        $this->mediaType = $mediaType;
59
+        $this->systemTagsInFilesDetector = $systemTagsInFilesDetector;
60
+        $this->name = 'systemtags-assigned';
61
+        if ($this->mediaType != '') {
62
+            $this->name .= '/' . $this->mediaType;
63
+        }
64
+    }
65 65
 
66
-	public function setName($name): void {
67
-		throw new Forbidden('Permission denied to rename this collection');
68
-	}
66
+    public function setName($name): void {
67
+        throw new Forbidden('Permission denied to rename this collection');
68
+    }
69 69
 
70
-	public function getChild($name): self {
71
-		if ($this->mediaType !== '') {
72
-			throw new NotFound('Invalid media type');
73
-		}
74
-		return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name);
75
-	}
70
+    public function getChild($name): self {
71
+        if ($this->mediaType !== '') {
72
+            throw new NotFound('Invalid media type');
73
+        }
74
+        return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name);
75
+    }
76 76
 
77
-	/**
78
-	 * @return SystemTagNode[]
79
-	 * @throws NotPermittedException
80
-	 * @throws Forbidden
81
-	 */
82
-	public function getChildren(): array {
83
-		$user = $this->userSession->getUser();
84
-		$userFolder = null;
85
-		try {
86
-			if ($user) {
87
-				$userFolder = $this->rootFolder->getUserFolder($user->getUID());
88
-			}
89
-		} catch (NoUserException) {
90
-			// will throw a Sabre exception in the next step.
91
-		}
92
-		if ($user === null || $userFolder === null) {
93
-			throw new Forbidden('Permission denied to read this collection');
94
-		}
77
+    /**
78
+     * @return SystemTagNode[]
79
+     * @throws NotPermittedException
80
+     * @throws Forbidden
81
+     */
82
+    public function getChildren(): array {
83
+        $user = $this->userSession->getUser();
84
+        $userFolder = null;
85
+        try {
86
+            if ($user) {
87
+                $userFolder = $this->rootFolder->getUserFolder($user->getUID());
88
+            }
89
+        } catch (NoUserException) {
90
+            // will throw a Sabre exception in the next step.
91
+        }
92
+        if ($user === null || $userFolder === null) {
93
+            throw new Forbidden('Permission denied to read this collection');
94
+        }
95 95
 
96
-		$result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType);
97
-		$children = [];
98
-		foreach ($result as $tagData) {
99
-			$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']);
100
-			// read only, so we can submit the isAdmin parameter as false generally
101
-			$node = new SystemTagNode($tag, $user, false, $this->systemTagManager);
102
-			$node->setNumberOfFiles($tagData['number_files']);
103
-			$node->setReferenceFileId($tagData['ref_file_id']);
104
-			$children[] = $node;
105
-		}
106
-		return $children;
107
-	}
96
+        $result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType);
97
+        $children = [];
98
+        foreach ($result as $tagData) {
99
+            $tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']);
100
+            // read only, so we can submit the isAdmin parameter as false generally
101
+            $node = new SystemTagNode($tag, $user, false, $this->systemTagManager);
102
+            $node->setNumberOfFiles($tagData['number_files']);
103
+            $node->setReferenceFileId($tagData['ref_file_id']);
104
+            $children[] = $node;
105
+        }
106
+        return $children;
107
+    }
108 108
 }
Please login to merge, or discard this patch.