Skip to content

Commit

Permalink
expose system tags as dav property for files
Browse files Browse the repository at this point in the history
Signed-off-by: Robin Appelman <robin@icewind.nl>
  • Loading branch information
icewind1991 committed Apr 19, 2023
1 parent 9db3305 commit f52d757
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 18 deletions.
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@
'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',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,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',
Expand Down
6 changes: 1 addition & 5 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,7 @@ public function __construct(IRequest $request, string $baseUri) {
}

// 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(
Expand Down
73 changes: 73 additions & 0 deletions apps/dav/lib/SystemTag/SystemTagList.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
91 changes: 83 additions & 8 deletions apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@
*/
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;
Expand Down Expand Up @@ -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
Expand All @@ -77,17 +81,21 @@ 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) {
private array $cachedTagMappings = [];
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;
}

/**
Expand Down Expand Up @@ -220,6 +228,11 @@ public function handleGetProperties(
PropFind $propFind,
\Sabre\DAV\INode $node
) {
if ($node instanceof Node) {
$this->propfindForFile($propFind, $node);
return;
}

if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
return;
}
Expand Down Expand Up @@ -260,6 +273,68 @@ public function handleGetProperties(
});
}

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) {
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
*
Expand Down
15 changes: 10 additions & 5 deletions apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -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)
Expand All @@ -108,11 +111,13 @@ protected function setUp(): void {
->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);
}
Expand Down Expand Up @@ -233,7 +238,7 @@ public function testGetProperties(ISystemTag $systemTag, $groups, $requestedProp
$this->assertEquals($expectedProperties, $result[200]);
}


public function testGetPropertiesForbidden(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);

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


public function testUpdatePropertiesForbidden(): void {
$this->expectException(\Sabre\DAV\Exception\Forbidden::class);

Expand Down Expand Up @@ -537,7 +542,7 @@ public function testCreateTagInByIdCollection($userVisible, $userAssignable, $gr
->method('createTag')
->with('Test', $userVisible, $userAssignable)
->willReturn($systemTag);

if (!empty($groups)) {
$this->tagManager->expects($this->once())
->method('setTagGroups')
Expand Down Expand Up @@ -658,7 +663,7 @@ public function testCreateTagInMappingCollection(): void {
$this->plugin->httpPost($request, $response);
}


public function testCreateTagToUnknownNode(): void {
$this->expectException(\Sabre\DAV\Exception\NotFound::class);

Expand Down

0 comments on commit f52d757

Please sign in to comment.