Skip to content

Commit 74ced3d

Browse files
icewind1991skjnldsv
authored andcommitted
expose system tags as dav property for files
Signed-off-by: Robin Appelman <robin@icewind.nl>
1 parent a9798a3 commit 74ced3d

6 files changed

Lines changed: 171 additions & 18 deletions

File tree

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\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',
305305
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
306306
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',
307+
'OCA\\DAV\\SystemTag\\SystemTagList' => $baseDir . '/../lib/SystemTag/SystemTagList.php',
307308
'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => $baseDir . '/../lib/SystemTag/SystemTagMappingNode.php',
308309
'OCA\\DAV\\SystemTag\\SystemTagNode' => $baseDir . '/../lib/SystemTag/SystemTagNode.php',
309310
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.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\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',
320320
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
321321
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',
322+
'OCA\\DAV\\SystemTag\\SystemTagList' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagList.php',
322323
'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagMappingNode.php',
323324
'OCA\\DAV\\SystemTag\\SystemTagNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagNode.php',
324325
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php',

apps/dav/lib/Server.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,7 @@ public function __construct(IRequest $request, string $baseUri) {
208208
}
209209

210210
// system tags plugins
211-
$this->server->addPlugin(new SystemTagPlugin(
212-
\OC::$server->getSystemTagManager(),
213-
\OC::$server->getGroupManager(),
214-
\OC::$server->getUserSession()
215-
));
211+
$this->server->addPlugin(\OC::$server->get(SystemTagPlugin::class));
216212

217213
// comments plugin
218214
$this->server->addPlugin(new CommentsPlugin(
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2016, ownCloud, Inc.
4+
*
5+
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
6+
* @author Thomas Müller <thomas.mueller@tmit.eu>
7+
* @author Vincent Petry <vincent@nextcloud.com>
8+
*
9+
* @license AGPL-3.0
10+
*
11+
* This code is free software: you can redistribute it and/or modify
12+
* it under the terms of the GNU Affero General Public License, version 3,
13+
* as published by the Free Software Foundation.
14+
*
15+
* This program is distributed in the hope that it will be useful,
16+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
* GNU Affero General Public License for more details.
19+
*
20+
* You should have received a copy of the GNU Affero General Public License, version 3,
21+
* along with this program. If not, see <http://www.gnu.org/licenses/>
22+
*
23+
*/
24+
namespace OCA\DAV\SystemTag;
25+
26+
use OCP\IUser;
27+
use OCP\SystemTag\ISystemTag;
28+
use OCP\SystemTag\ISystemTagManager;
29+
use Sabre\Xml\Element;
30+
use Sabre\Xml\Reader;
31+
use Sabre\Xml\Writer;
32+
33+
/**
34+
* TagList property
35+
*
36+
* This property contains multiple "tag" elements, each containing a tag name.
37+
*/
38+
class SystemTagList implements Element {
39+
public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
40+
41+
/** @var ISystemTag[] */
42+
private array $tags;
43+
private ISystemTagManager $tagManager;
44+
private IUser $user;
45+
46+
public function __construct(array $tags, ISystemTagManager $tagManager, IUser $user) {
47+
$this->tags = $tags;
48+
$this->tagManager = $tagManager;
49+
$this->user = $user;
50+
}
51+
52+
public function getTags() {
53+
return $this->tags;
54+
}
55+
56+
public static function xmlDeserialize(Reader $reader): void {
57+
// unsupported/unused
58+
}
59+
60+
public function xmlSerialize(Writer $writer): void {
61+
foreach ($this->tags as $tag) {
62+
$writer->startElement('{' . self::NS_NEXTCLOUD . '}system-tag');
63+
$writer->writeAttributes([
64+
SystemTagPlugin::CANASSIGN_PROPERTYNAME => $this->tagManager->canUserAssignTag($tag, $this->user) ? 'true' : 'false',
65+
SystemTagPlugin::ID_PROPERTYNAME => $tag->getId(),
66+
SystemTagPlugin::USERASSIGNABLE_PROPERTYNAME => $tag->isUserAssignable() ? 'true' : 'false',
67+
SystemTagPlugin::USERVISIBLE_PROPERTYNAME => $tag->isUserVisible() ? 'true' : 'false',
68+
]);
69+
$writer->write($tag->getName());
70+
$writer->endElement();
71+
}
72+
}
73+
}

apps/dav/lib/SystemTag/SystemTagPlugin.php

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525
*/
2626
namespace OCA\DAV\SystemTag;
2727

28+
use OCA\DAV\Connector\Sabre\Directory;
29+
use OCA\DAV\Connector\Sabre\Node;
2830
use OCP\IGroupManager;
2931
use OCP\IUserSession;
3032
use OCP\SystemTag\ISystemTag;
3133
use OCP\SystemTag\ISystemTagManager;
34+
use OCP\SystemTag\ISystemTagObjectMapper;
3235
use OCP\SystemTag\TagAlreadyExistsException;
3336
use Sabre\DAV\Exception\BadRequest;
3437
use Sabre\DAV\Exception\Conflict;
@@ -56,6 +59,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
5659
public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
5760
public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
5861
public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
62+
public const SYSTEM_TAGS_PROPERTYNAME = '{http://nextcloud.org/ns}system-tags';
5963

6064
/**
6165
* @var \Sabre\DAV\Server $server
@@ -77,17 +81,23 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
7781
*/
7882
protected $groupManager;
7983

80-
/**
81-
* @param ISystemTagManager $tagManager tag manager
82-
* @param IGroupManager $groupManager
83-
* @param IUserSession $userSession
84-
*/
85-
public function __construct(ISystemTagManager $tagManager,
86-
IGroupManager $groupManager,
87-
IUserSession $userSession) {
84+
/** @var array<int, int[]> */
85+
private array $cachedTagMappings = [];
86+
/** @var array<int, ISystemTag> */
87+
private array $cachedTags = [];
88+
89+
private ISystemTagObjectMapper $tagMapper;
90+
91+
public function __construct(
92+
ISystemTagManager $tagManager,
93+
IGroupManager $groupManager,
94+
IUserSession $userSession,
95+
ISystemTagObjectMapper $tagMapper,
96+
) {
8897
$this->tagManager = $tagManager;
8998
$this->userSession = $userSession;
9099
$this->groupManager = $groupManager;
100+
$this->tagMapper = $tagMapper;
91101
}
92102

93103
/**
@@ -220,6 +230,11 @@ public function handleGetProperties(
220230
PropFind $propFind,
221231
\Sabre\DAV\INode $node
222232
) {
233+
if ($node instanceof Node) {
234+
$this->propfindForFile($propFind, $node);
235+
return;
236+
}
237+
223238
if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
224239
return;
225240
}
@@ -260,6 +275,68 @@ public function handleGetProperties(
260275
});
261276
}
262277

278+
private function propfindForFile(PropFind $propFind, Node $node): void {
279+
if ($node instanceof Directory
280+
&& $propFind->getDepth() !== 0
281+
&& !is_null($propFind->getStatus(self::SYSTEM_TAGS_PROPERTYNAME))) {
282+
// note: pre-fetching only supported for depth <= 1
283+
$folderContent = $node->getNode()->getDirectoryListing();
284+
$fileIds[] = (int)$node->getId();
285+
foreach ($folderContent as $info) {
286+
$fileIds[] = (int)$info->getId();
287+
}
288+
$tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files');
289+
290+
$this->cachedTagMappings = $this->cachedTagMappings + $tags;
291+
$emptyFileIds = array_diff($fileIds, array_keys($tags));
292+
// also cache the ones that were not found
293+
foreach ($emptyFileIds as $fileId) {
294+
$this->cachedTagMappings[$fileId] = [];
295+
}
296+
}
297+
298+
$propFind->handle(self::SYSTEM_TAGS_PROPERTYNAME, function () use ($node) {
299+
$tags = $this->getTagsForFile($node->getId());
300+
return new SystemTagList($tags, $this->tagManager, $this->userSession->getUser());
301+
});
302+
}
303+
304+
/**
305+
* @param int $fileId
306+
* @return ISystemTag[]
307+
*/
308+
private function getTagsForFile(int $fileId): array {
309+
$user = $this->userSession->getUser();
310+
if (isset($this->cachedTagMappings[$fileId])) {
311+
$tagIds = $this->cachedTagMappings[$fileId];
312+
} else {
313+
$tags = $this->tagMapper->getTagIdsForObjects([$fileId], 'files');
314+
$fileTags = current($tags);
315+
if ($fileTags) {
316+
$tagIds = $fileTags;
317+
} else {
318+
$tagIds = [];
319+
}
320+
}
321+
$tags = array_filter(array_map(function($tagId) {
322+
return $this->cachedTags[$tagId] ?? null;
323+
}, $tagIds));
324+
325+
$uncachedTagIds = array_filter($tagIds, function($tagId): bool {
326+
return !isset($this->cachedTags[$tagId]);
327+
});
328+
if (count($uncachedTagIds)) {
329+
$retrievedTags = $this->tagManager->getTagsByIds($uncachedTagIds);
330+
foreach ($retrievedTags as $tag) {
331+
$this->cachedTags[$tag->getId()] = $tag;
332+
}
333+
$tags += $retrievedTags;
334+
}
335+
return array_filter($tags, function(ISystemTag $tag) use ($user) {
336+
return $this->tagManager->canUserSeeTag($tag, $user);
337+
});
338+
}
339+
263340
/**
264341
* Updates tag attributes
265342
*

apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use OCP\IUserSession;
3737
use OCP\SystemTag\ISystemTag;
3838
use OCP\SystemTag\ISystemTagManager;
39+
use OCP\SystemTag\ISystemTagObjectMapper;
3940
use OCP\SystemTag\TagAlreadyExistsException;
4041
use Sabre\DAV\Tree;
4142
use Sabre\HTTP\RequestInterface;
@@ -84,6 +85,8 @@ class SystemTagPluginTest extends \Test\TestCase {
8485
*/
8586
private $plugin;
8687

88+
private ISystemTagObjectMapper $tagMapper;
89+
8790
protected function setUp(): void {
8891
parent::setUp();
8992
$this->tree = $this->getMockBuilder(Tree::class)
@@ -108,11 +111,13 @@ protected function setUp(): void {
108111
->expects($this->any())
109112
->method('isLoggedIn')
110113
->willReturn(true);
114+
$this->tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class);
111115

112116
$this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin(
113117
$this->tagManager,
114118
$this->groupManager,
115-
$this->userSession
119+
$this->userSession,
120+
$this->tagMapper
116121
);
117122
$this->plugin->initialize($this->server);
118123
}
@@ -233,7 +238,7 @@ public function testGetProperties(ISystemTag $systemTag, $groups, $requestedProp
233238
$this->assertEquals($expectedProperties, $result[200]);
234239
}
235240

236-
241+
237242
public function testGetPropertiesForbidden(): void {
238243
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
239244

@@ -330,7 +335,7 @@ public function testUpdatePropertiesAdmin(): void {
330335
$this->assertEquals(200, $result[self::USERVISIBLE_PROPERTYNAME]);
331336
}
332337

333-
338+
334339
public function testUpdatePropertiesForbidden(): void {
335340
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
336341

@@ -537,7 +542,7 @@ public function testCreateTagInByIdCollection($userVisible, $userAssignable, $gr
537542
->method('createTag')
538543
->with('Test', $userVisible, $userAssignable)
539544
->willReturn($systemTag);
540-
545+
541546
if (!empty($groups)) {
542547
$this->tagManager->expects($this->once())
543548
->method('setTagGroups')
@@ -658,7 +663,7 @@ public function testCreateTagInMappingCollection(): void {
658663
$this->plugin->httpPost($request, $response);
659664
}
660665

661-
666+
662667
public function testCreateTagToUnknownNode(): void {
663668
$this->expectException(\Sabre\DAV\Exception\NotFound::class);
664669

0 commit comments

Comments
 (0)