Skip to content

Commit 4bd2f33

Browse files
Merge pull request #38307 from nextcloud/backport/37961/stable26
[stable26] SystemTags endpoint to return tags used by a user with meta data
2 parents 83e0b31 + 298af3c commit 4bd2f33

File tree

12 files changed

+333
-54
lines changed

12 files changed

+333
-54
lines changed

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@
304304
'OCA\\DAV\\SystemTag\\SystemTagNode' => $baseDir . '/../lib/SystemTag/SystemTagNode.php',
305305
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.php',
306306
'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => $baseDir . '/../lib/SystemTag/SystemTagsByIdCollection.php',
307+
'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => $baseDir . '/../lib/SystemTag/SystemTagsInUseCollection.php',
307308
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
308309
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
309310
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ class ComposerStaticInitDAV
319319
'OCA\\DAV\\SystemTag\\SystemTagNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagNode.php',
320320
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php',
321321
'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsByIdCollection.php',
322+
'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsInUseCollection.php',
322323
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
323324
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
324325
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.php',

apps/dav/lib/RootCollection.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
use OCP\App\IAppManager;
4949
use OCP\AppFramework\Utility\ITimeFactory;
5050
use OCP\EventDispatcher\IEventDispatcher;
51+
use OCP\Files\IRootFolder;
5152
use OCP\IConfig;
5253
use Psr\Log\LoggerInterface;
5354
use Sabre\DAV\SimpleCollection;
@@ -65,6 +66,7 @@ public function __construct() {
6566
$dispatcher = \OC::$server->get(IEventDispatcher::class);
6667
$config = \OC::$server->get(IConfig::class);
6768
$proxyMapper = \OC::$server->query(ProxyMapper::class);
69+
$rootFolder = \OCP\Server::get(IRootFolder::class);
6870

6971
$userPrincipalBackend = new Principal(
7072
$userManager,
@@ -131,6 +133,7 @@ public function __construct() {
131133
$groupManager,
132134
\OC::$server->getEventDispatcher()
133135
);
136+
$systemTagInUseCollection = \OCP\Server::get(SystemTag\SystemTagsInUseCollection::class);
134137
$commentsCollection = new Comments\RootCollection(
135138
\OC::$server->getCommentsManager(),
136139
$userManager,
@@ -179,6 +182,7 @@ public function __construct() {
179182
$systemAddressBookRoot]),
180183
$systemTagCollection,
181184
$systemTagRelationsCollection,
185+
$systemTagInUseCollection,
182186
$commentsCollection,
183187
$uploadCollection,
184188
$avatarCollection,

apps/dav/lib/SystemTag/SystemTagNode.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class SystemTagNode implements \Sabre\DAV\INode {
6464
*/
6565
protected $isAdmin;
6666

67+
protected int $numberOfFiles = -1;
68+
protected int $referenceFileId = -1;
69+
6770
/**
6871
* Sets up the node, expects a full path name
6972
*
@@ -172,4 +175,20 @@ public function delete() {
172175
throw new NotFound('Tag with id ' . $this->tag->getId() . ' not found', 0, $e);
173176
}
174177
}
178+
179+
public function getNumberOfFiles(): int {
180+
return $this->numberOfFiles;
181+
}
182+
183+
public function setNumberOfFiles(int $numberOfFiles): void {
184+
$this->numberOfFiles = $numberOfFiles;
185+
}
186+
187+
public function getReferenceFileId(): int {
188+
return $this->referenceFileId;
189+
}
190+
191+
public function setReferenceFileId(int $referenceFileId): void {
192+
$this->referenceFileId = $referenceFileId;
193+
}
175194
}

apps/dav/lib/SystemTag/SystemTagPlugin.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
5656
public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
5757
public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
5858
public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
59+
public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned';
60+
public const FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid';
5961

6062
/**
6163
* @var \Sabre\DAV\Server $server
@@ -224,6 +226,11 @@ public function handleGetProperties(
224226
return;
225227
}
226228

229+
// child nodes from systemtags-assigned should point to normal tag endpoint
230+
if (preg_match('/^systemtags-assigned\/[0-9]+/', $propFind->getPath())) {
231+
$propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath()));
232+
}
233+
227234
$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
228235
return $node->getSystemTag()->getId();
229236
});
@@ -258,6 +265,16 @@ public function handleGetProperties(
258265
}
259266
return implode('|', $groups);
260267
});
268+
269+
if ($node instanceof SystemTagNode) {
270+
$propFind->handle(self::NUM_FILES_PROPERTYNAME, function () use ($node): int {
271+
return $node->getNumberOfFiles();
272+
});
273+
274+
$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node): int {
275+
return $node->getReferenceFileId();
276+
});
277+
}
261278
}
262279

263280
/**
@@ -279,6 +296,8 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
279296
self::USERVISIBLE_PROPERTYNAME,
280297
self::USERASSIGNABLE_PROPERTYNAME,
281298
self::GROUPS_PROPERTYNAME,
299+
self::NUM_FILES_PROPERTYNAME,
300+
self::FILEID_PROPERTYNAME,
282301
], function ($props) use ($node) {
283302
$tag = $node->getSystemTag();
284303
$name = $tag->getName();
@@ -315,6 +334,11 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
315334
$this->tagManager->setTagGroups($tag, $groupIds);
316335
}
317336

337+
if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::FILEID_PROPERTYNAME])) {
338+
// read-only properties
339+
throw new Forbidden();
340+
}
341+
318342
if ($updateTag) {
319343
$node->update($name, $userVisible, $userAssignable);
320344
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2023 Arthur Schiwon <blizzz@arthur-schiwon.de>
7+
*
8+
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\DAV\SystemTag;
28+
29+
use OC\SystemTag\SystemTag;
30+
use OC\SystemTag\SystemTagsInFilesDetector;
31+
use OC\User\NoUserException;
32+
use OCP\Files\IRootFolder;
33+
use OCP\Files\NotPermittedException;
34+
use OCP\IUserSession;
35+
use OCP\SystemTag\ISystemTagManager;
36+
use Sabre\DAV\Exception\Forbidden;
37+
use Sabre\DAV\Exception\NotFound;
38+
use Sabre\DAV\SimpleCollection;
39+
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;
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+
}
65+
66+
public function setName($name): void {
67+
throw new Forbidden('Permission denied to rename this collection');
68+
}
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+
}
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+
}
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+
}
108+
}

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,7 @@
15941594
'OC\\SystemTag\\SystemTag' => $baseDir . '/lib/private/SystemTag/SystemTag.php',
15951595
'OC\\SystemTag\\SystemTagManager' => $baseDir . '/lib/private/SystemTag/SystemTagManager.php',
15961596
'OC\\SystemTag\\SystemTagObjectMapper' => $baseDir . '/lib/private/SystemTag/SystemTagObjectMapper.php',
1597+
'OC\\SystemTag\\SystemTagsInFilesDetector' => $baseDir . '/lib/private/SystemTag/SystemTagsInFilesDetector.php',
15971598
'OC\\TagManager' => $baseDir . '/lib/private/TagManager.php',
15981599
'OC\\Tagging\\Tag' => $baseDir . '/lib/private/Tagging/Tag.php',
15991600
'OC\\Tagging\\TagMapper' => $baseDir . '/lib/private/Tagging/TagMapper.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
16271627
'OC\\SystemTag\\SystemTag' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTag.php',
16281628
'OC\\SystemTag\\SystemTagManager' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagManager.php',
16291629
'OC\\SystemTag\\SystemTagObjectMapper' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagObjectMapper.php',
1630+
'OC\\SystemTag\\SystemTagsInFilesDetector' => __DIR__ . '/../../..' . '/lib/private/SystemTag/SystemTagsInFilesDetector.php',
16301631
'OC\\TagManager' => __DIR__ . '/../../..' . '/lib/private/TagManager.php',
16311632
'OC\\Tagging\\Tag' => __DIR__ . '/../../..' . '/lib/private/Tagging/Tag.php',
16321633
'OC\\Tagging\\TagMapper' => __DIR__ . '/../../..' . '/lib/private/Tagging/TagMapper.php',

lib/private/Files/Cache/CacheQueryBuilder.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,27 @@ public function __construct(IDBConnection $connection, SystemConfig $systemConfi
4141
parent::__construct($connection, $systemConfig, $logger);
4242
}
4343

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+
4463
public function selectFileCache(string $alias = null, bool $joinExtendedCache = true) {
45-
$name = $alias ? $alias : 'filecache';
64+
$name = $alias ?: 'filecache';
4665
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", "$name.name", 'mimetype', 'mimepart', 'size', 'mtime',
4766
'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'unencrypted_size')
4867
->from('filecache', $name);

0 commit comments

Comments
 (0)