]> source.dussan.org Git - nextcloud-server.git/commitdiff
expose system tags as dav property for files
authorRobin Appelman <robin@icewind.nl>
Mon, 17 Apr 2023 14:10:41 +0000 (16:10 +0200)
committerJohn Molakvoæ <skjnldsv@protonmail.com>
Fri, 28 Apr 2023 08:46:38 +0000 (10:46 +0200)
Signed-off-by: Robin Appelman <robin@icewind.nl>
apps/dav/composer/composer/autoload_classmap.php
apps/dav/composer/composer/autoload_static.php
apps/dav/lib/Server.php
apps/dav/lib/SystemTag/SystemTagList.php [new file with mode: 0644]
apps/dav/lib/SystemTag/SystemTagPlugin.php
apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php

index 6745ffe41b4517eaed6ef018199353e622c20e6a..db7de8c9ac6d3ff8326f69633053b9fbe136af1d 100644 (file)
@@ -304,6 +304,7 @@ return array(
     'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',
     'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
     'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',
+    'OCA\\DAV\\SystemTag\\SystemTagList' => $baseDir . '/../lib/SystemTag/SystemTagList.php',
     'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => $baseDir . '/../lib/SystemTag/SystemTagMappingNode.php',
     'OCA\\DAV\\SystemTag\\SystemTagNode' => $baseDir . '/../lib/SystemTag/SystemTagNode.php',
     'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.php',
index 302a424d08ea634976abeb9f0b4718b362908483..c29d93d72a833086f03006bb15fbc4d9142ef72b 100644 (file)
@@ -319,6 +319,7 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',
         'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
         'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',
+        'OCA\\DAV\\SystemTag\\SystemTagList' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagList.php',
         'OCA\\DAV\\SystemTag\\SystemTagMappingNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagMappingNode.php',
         'OCA\\DAV\\SystemTag\\SystemTagNode' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagNode.php',
         'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php',
index 4be149ac44034d5367def6a18315c7eca1fcf3a2..909bcaa71e8f98ae69beae644395701c60dadb57 100644 (file)
@@ -208,11 +208,7 @@ class Server {
                }
 
                // system tags plugins
-               $this->server->addPlugin(new SystemTagPlugin(
-                       \OC::$server->getSystemTagManager(),
-                       \OC::$server->getGroupManager(),
-                       \OC::$server->getUserSession()
-               ));
+               $this->server->addPlugin(\OC::$server->get(SystemTagPlugin::class));
 
                // comments plugin
                $this->server->addPlugin(new CommentsPlugin(
diff --git a/apps/dav/lib/SystemTag/SystemTagList.php b/apps/dav/lib/SystemTag/SystemTagList.php
new file mode 100644 (file)
index 0000000..67d33b9
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Vincent Petry <vincent@nextcloud.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\SystemTag;
+
+use OCP\IUser;
+use OCP\SystemTag\ISystemTag;
+use OCP\SystemTag\ISystemTagManager;
+use Sabre\Xml\Element;
+use Sabre\Xml\Reader;
+use Sabre\Xml\Writer;
+
+/**
+ * TagList property
+ *
+ * This property contains multiple "tag" elements, each containing a tag name.
+ */
+class SystemTagList implements Element {
+       public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
+
+       /** @var ISystemTag[] */
+       private array $tags;
+       private ISystemTagManager $tagManager;
+       private IUser $user;
+
+       public function __construct(array $tags, ISystemTagManager $tagManager, IUser $user) {
+               $this->tags = $tags;
+               $this->tagManager = $tagManager;
+               $this->user = $user;
+       }
+
+       public function getTags() {
+               return $this->tags;
+       }
+
+       public static function xmlDeserialize(Reader $reader): void {
+               // unsupported/unused
+       }
+
+       public function xmlSerialize(Writer $writer): void {
+               foreach ($this->tags as $tag) {
+                       $writer->startElement('{' . self::NS_NEXTCLOUD . '}system-tag');
+                       $writer->writeAttributes([
+                               SystemTagPlugin::CANASSIGN_PROPERTYNAME => $this->tagManager->canUserAssignTag($tag, $this->user) ? 'true' : 'false',
+                               SystemTagPlugin::ID_PROPERTYNAME => $tag->getId(),
+                               SystemTagPlugin::USERASSIGNABLE_PROPERTYNAME => $tag->isUserAssignable() ? 'true' : 'false',
+                               SystemTagPlugin::USERVISIBLE_PROPERTYNAME => $tag->isUserVisible() ? 'true' : 'false',
+                       ]);
+                       $writer->write($tag->getName());
+                       $writer->endElement();
+               }
+       }
+}
index c21935edfdc3a2d121383c672f66a6812a2b2fc6..5fe1c013571037d4644c646e3e341e941d28da57 100644 (file)
  */
 namespace OCA\DAV\SystemTag;
 
+use OCA\DAV\Connector\Sabre\Directory;
+use OCA\DAV\Connector\Sabre\Node;
 use OCP\IGroupManager;
 use OCP\IUserSession;
 use OCP\SystemTag\ISystemTag;
 use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
 use OCP\SystemTag\TagAlreadyExistsException;
 use Sabre\DAV\Exception\BadRequest;
 use Sabre\DAV\Exception\Conflict;
@@ -56,6 +59,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
        public const USERASSIGNABLE_PROPERTYNAME = '{http://owncloud.org/ns}user-assignable';
        public const GROUPS_PROPERTYNAME = '{http://owncloud.org/ns}groups';
        public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
+       public const SYSTEM_TAGS_PROPERTYNAME = '{http://nextcloud.org/ns}system-tags';
 
        /**
         * @var \Sabre\DAV\Server $server
@@ -77,17 +81,23 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
         */
        protected $groupManager;
 
-       /**
-        * @param ISystemTagManager $tagManager tag manager
-        * @param IGroupManager $groupManager
-        * @param IUserSession $userSession
-        */
-       public function __construct(ISystemTagManager $tagManager,
-                                                               IGroupManager $groupManager,
-                                                               IUserSession $userSession) {
+       /** @var array<int, int[]> */
+       private array $cachedTagMappings = [];
+       /** @var array<int, ISystemTag> */
+       private array $cachedTags = [];
+
+       private ISystemTagObjectMapper $tagMapper;
+
+       public function __construct(
+               ISystemTagManager $tagManager,
+               IGroupManager $groupManager,
+               IUserSession $userSession,
+               ISystemTagObjectMapper $tagMapper,
+       ) {
                $this->tagManager = $tagManager;
                $this->userSession = $userSession;
                $this->groupManager = $groupManager;
+               $this->tagMapper = $tagMapper;
        }
 
        /**
@@ -220,6 +230,11 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
                PropFind $propFind,
                \Sabre\DAV\INode $node
        ) {
+               if ($node instanceof Node) {
+                       $this->propfindForFile($propFind, $node);
+                       return;
+               }
+
                if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
                        return;
                }
@@ -260,6 +275,68 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
                });
        }
 
+       private function propfindForFile(PropFind $propFind, Node $node): void {
+               if ($node instanceof Directory
+                       && $propFind->getDepth() !== 0
+                       && !is_null($propFind->getStatus(self::SYSTEM_TAGS_PROPERTYNAME))) {
+                       // note: pre-fetching only supported for depth <= 1
+                       $folderContent = $node->getNode()->getDirectoryListing();
+                       $fileIds[] = (int)$node->getId();
+                       foreach ($folderContent as $info) {
+                               $fileIds[] = (int)$info->getId();
+                       }
+                       $tags = $this->tagMapper->getTagIdsForObjects($fileIds, 'files');
+
+                       $this->cachedTagMappings = $this->cachedTagMappings + $tags;
+                       $emptyFileIds = array_diff($fileIds, array_keys($tags));
+                       // also cache the ones that were not found
+                       foreach ($emptyFileIds as $fileId) {
+                               $this->cachedTagMappings[$fileId] = [];
+                       }
+               }
+
+               $propFind->handle(self::SYSTEM_TAGS_PROPERTYNAME, function () use ($node) {
+                       $tags = $this->getTagsForFile($node->getId());
+                       return new SystemTagList($tags, $this->tagManager, $this->userSession->getUser());
+               });
+       }
+
+       /**
+        * @param int $fileId
+        * @return ISystemTag[]
+        */
+       private function getTagsForFile(int $fileId): array {
+               $user = $this->userSession->getUser();
+               if (isset($this->cachedTagMappings[$fileId])) {
+                       $tagIds = $this->cachedTagMappings[$fileId];
+               } else {
+                       $tags = $this->tagMapper->getTagIdsForObjects([$fileId], 'files');
+                       $fileTags = current($tags);
+                       if ($fileTags) {
+                               $tagIds = $fileTags;
+                       } else {
+                               $tagIds = [];
+                       }
+               }
+               $tags = array_filter(array_map(function($tagId) {
+                       return $this->cachedTags[$tagId] ?? null;
+               }, $tagIds));
+
+               $uncachedTagIds = array_filter($tagIds, function($tagId): bool {
+                       return !isset($this->cachedTags[$tagId]);
+               });
+               if (count($uncachedTagIds)) {
+                       $retrievedTags = $this->tagManager->getTagsByIds($uncachedTagIds);
+                       foreach ($retrievedTags as $tag) {
+                               $this->cachedTags[$tag->getId()] = $tag;
+                       }
+                       $tags += $retrievedTags;
+               }
+               return array_filter($tags, function(ISystemTag $tag) use ($user) {
+                       return $this->tagManager->canUserSeeTag($tag, $user);
+               });
+       }
+
        /**
         * Updates tag attributes
         *
index 291aa45ad0ef7a53b213d9fa10188b15fc5070c2..199bf28fb7da2e4258339c182143e7196f38161f 100644 (file)
@@ -36,6 +36,7 @@ use OCP\IUser;
 use OCP\IUserSession;
 use OCP\SystemTag\ISystemTag;
 use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\ISystemTagObjectMapper;
 use OCP\SystemTag\TagAlreadyExistsException;
 use Sabre\DAV\Tree;
 use Sabre\HTTP\RequestInterface;
@@ -84,6 +85,8 @@ class SystemTagPluginTest extends \Test\TestCase {
         */
        private $plugin;
 
+       private ISystemTagObjectMapper $tagMapper;
+
        protected function setUp(): void {
                parent::setUp();
                $this->tree = $this->getMockBuilder(Tree::class)
@@ -108,11 +111,13 @@ class SystemTagPluginTest extends \Test\TestCase {
                        ->expects($this->any())
                        ->method('isLoggedIn')
                        ->willReturn(true);
+               $this->tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class);
 
                $this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin(
                        $this->tagManager,
                        $this->groupManager,
-                       $this->userSession
+                       $this->userSession,
+                       $this->tagMapper
                );
                $this->plugin->initialize($this->server);
        }
@@ -233,7 +238,7 @@ class SystemTagPluginTest extends \Test\TestCase {
                $this->assertEquals($expectedProperties, $result[200]);
        }
 
-       
+
        public function testGetPropertiesForbidden(): void {
                $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
 
@@ -330,7 +335,7 @@ class SystemTagPluginTest extends \Test\TestCase {
                $this->assertEquals(200, $result[self::USERVISIBLE_PROPERTYNAME]);
        }
 
-       
+
        public function testUpdatePropertiesForbidden(): void {
                $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
 
@@ -537,7 +542,7 @@ class SystemTagPluginTest extends \Test\TestCase {
                        ->method('createTag')
                        ->with('Test', $userVisible, $userAssignable)
                        ->willReturn($systemTag);
-               
+
                if (!empty($groups)) {
                        $this->tagManager->expects($this->once())
                                ->method('setTagGroups')
@@ -658,7 +663,7 @@ class SystemTagPluginTest extends \Test\TestCase {
                $this->plugin->httpPost($request, $response);
        }
 
-       
+
        public function testCreateTagToUnknownNode(): void {
                $this->expectException(\Sabre\DAV\Exception\NotFound::class);