'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',
'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',
}
// 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(
--- /dev/null
+<?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();
+ }
+ }
+}
*/
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;
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
*/
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;
}
/**
PropFind $propFind,
\Sabre\DAV\INode $node
) {
+ if ($node instanceof Node) {
+ $this->propfindForFile($propFind, $node);
+ return;
+ }
+
if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
return;
}
});
}
+ 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
*
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;
*/
private $plugin;
+ private ISystemTagObjectMapper $tagMapper;
+
protected function setUp(): void {
parent::setUp();
$this->tree = $this->getMockBuilder(Tree::class)
->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);
}
$this->assertEquals($expectedProperties, $result[200]);
}
-
+
public function testGetPropertiesForbidden(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
$this->assertEquals(200, $result[self::USERVISIBLE_PROPERTYNAME]);
}
-
+
public function testUpdatePropertiesForbidden(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
->method('createTag')
->with('Test', $userVisible, $userAssignable)
->willReturn($systemTag);
-
+
if (!empty($groups)) {
$this->tagManager->expects($this->once())
->method('setTagGroups')
$this->plugin->httpPost($request, $response);
}
-
+
public function testCreateTagToUnknownNode(): void {
$this->expectException(\Sabre\DAV\Exception\NotFound::class);