Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(systemtags): add bulk tagging action #48786

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -361,6 +361,7 @@
'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\\SystemTagObjectType' => $baseDir . '/../lib/SystemTag/SystemTagObjectType.php',
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => $baseDir . '/../lib/SystemTag/SystemTagPlugin.php',
'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => $baseDir . '/../lib/SystemTag/SystemTagsByIdCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => $baseDir . '/../lib/SystemTag/SystemTagsInUseCollection.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 @@ -376,6 +376,7 @@ class ComposerStaticInitDAV
'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\\SystemTagObjectType' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagObjectType.php',
'OCA\\DAV\\SystemTag\\SystemTagPlugin' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagPlugin.php',
'OCA\\DAV\\SystemTag\\SystemTagsByIdCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsByIdCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsInUseCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsInUseCollection.php',
Expand Down
6 changes: 1 addition & 5 deletions apps/dav/lib/RootCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ public function __construct() {

$publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config, $logger);

$systemTagCollection = new SystemTagsByIdCollection(
\OC::$server->getSystemTagManager(),
\OC::$server->getUserSession(),
$groupManager
);
$systemTagCollection = Server::get(SystemTagsByIdCollection::class);
$systemTagRelationsCollection = new SystemTagsRelationsCollection(
\OC::$server->getSystemTagManager(),
\OC::$server->getSystemTagObjectMapper(),
Expand Down
62 changes: 31 additions & 31 deletions apps/dav/lib/SystemTag/SystemTagNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
use OCP\IUser;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\SystemTag\TagAlreadyExistsException;

use OCP\SystemTag\TagNotFoundException;
use Sabre\DAV\Exception\Conflict;
use Sabre\DAV\Exception\Forbidden;
Expand All @@ -21,31 +21,7 @@
/**
* DAV node representing a system tag, with the name being the tag id.
*/
class SystemTagNode implements \Sabre\DAV\INode {

/**
* @var ISystemTag
*/
protected $tag;

/**
* @var ISystemTagManager
*/
protected $tagManager;

/**
* User
*
* @var IUser
*/
protected $user;

/**
* Whether to allow permissions for admins
*
* @var bool
*/
protected $isAdmin;
class SystemTagNode implements \Sabre\DAV\ICollection {

protected int $numberOfFiles = -1;
protected int $referenceFileId = -1;
Expand All @@ -58,11 +34,13 @@ class SystemTagNode implements \Sabre\DAV\INode {
* @param bool $isAdmin whether to allow operations for admins
* @param ISystemTagManager $tagManager tag manager
*/
public function __construct(ISystemTag $tag, IUser $user, $isAdmin, ISystemTagManager $tagManager) {
$this->tag = $tag;
$this->user = $user;
$this->isAdmin = $isAdmin;
$this->tagManager = $tagManager;
public function __construct(
protected ISystemTag $tag,
protected IUser $user,
protected bool $isAdmin,
protected ISystemTagManager $tagManager,
protected ISystemTagObjectMapper $tagMapper,
) {
}

/**
Expand Down Expand Up @@ -181,4 +159,26 @@ public function getReferenceFileId(): int {
public function setReferenceFileId(int $referenceFileId): void {
$this->referenceFileId = $referenceFileId;
}

public function createFile($name, $data = null) {
throw new MethodNotAllowed();
}

public function createDirectory($name) {
throw new MethodNotAllowed();
}

public function getChild($name) {
return new SystemTagObjectType($this->tag, $name, $this->tagManager, $this->tagMapper);
}

public function childExists($name) {
$objectTypes = $this->tagMapper->getAvailableObjectTypes();
return in_array($name, $objectTypes);
}

public function getChildren() {
// We currently don't have a method to list allowed tag mappings types
return [new SystemTagObjectType($this->tag, 'files', $this->tagManager, $this->tagMapper)];
}
}
58 changes: 58 additions & 0 deletions apps/dav/lib/SystemTag/SystemTagObjectType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\DAV\SystemTag;

use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use Sabre\DAV\Exception\MethodNotAllowed;

/**
* SystemTagObjectType property
* This property represent a type of object which tags are assigned to.
*/
class SystemTagObjectType implements \Sabre\DAV\INode {
public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';

/** @var string[] */
private array $objectsIds = [];

public function __construct(
private ISystemTag $tag,
private string $type,
private ISystemTagManager $tagManager,
private ISystemTagObjectMapper $tagMapper,
) {
}

/**
* Get the list of object ids that have this tag assigned.
* @return string

Check failure on line 33 in apps/dav/lib/SystemTag/SystemTagObjectType.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

MismatchingDocblockReturnType

apps/dav/lib/SystemTag/SystemTagObjectType.php:33:13: MismatchingDocblockReturnType: Docblock has incorrect return type 'string', should be 'array<array-key, mixed>' (see https://psalm.dev/142)
*/
public function getObjectsIds(): array {
if (empty($this->objectsIds)) {
$this->objectsIds = $this->tagMapper->getObjectIdsForTags($this->tag->getId(), $this->type);
}

return $this->objectsIds;
}

public function delete() {
throw new MethodNotAllowed();
}

public function getName() {
return $this->type;
}

public function setName($name) {
throw new MethodNotAllowed();
}

public function getLastModified() {
return null;
}
}
18 changes: 13 additions & 5 deletions apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {

// namespace
public const NS_OWNCLOUD = 'http://owncloud.org/ns';
public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
public const ID_PROPERTYNAME = '{http://owncloud.org/ns}id';
public const DISPLAYNAME_PROPERTYNAME = '{http://owncloud.org/ns}display-name';
public const USERVISIBLE_PROPERTYNAME = '{http://owncloud.org/ns}user-visible';
Expand All @@ -45,7 +46,8 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin {
public const CANASSIGN_PROPERTYNAME = '{http://owncloud.org/ns}can-assign';
public const SYSTEM_TAGS_PROPERTYNAME = '{http://nextcloud.org/ns}system-tags';
public const NUM_FILES_PROPERTYNAME = '{http://nextcloud.org/ns}files-assigned';
public const FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid';
public const REFERENCE_FILEID_PROPERTYNAME = '{http://nextcloud.org/ns}reference-fileid';
public const OBJECTID_PROPERTYNAME = '{http://nextcloud.org/ns}object-id';

/**
* @var \Sabre\DAV\Server $server
Expand Down Expand Up @@ -223,7 +225,7 @@ public function handleGetProperties(
return;
}

if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) {
if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode) && !($node instanceof SystemTagObjectType)) {
return;
}

Expand Down Expand Up @@ -272,10 +274,16 @@ public function handleGetProperties(
return $node->getNumberOfFiles();
});

$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node): int {
$propFind->handle(self::REFERENCE_FILEID_PROPERTYNAME, function () use ($node): int {
return $node->getReferenceFileId();
});
}

if ($node instanceof SystemTagObjectType) {
$propFind->handle(self::OBJECTID_PROPERTYNAME, function () use ($node) {
return $node->getObjectsIds();
});
}
}

private function propfindForFile(PropFind $propFind, Node $node): void {
Expand Down Expand Up @@ -372,7 +380,7 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
self::USERASSIGNABLE_PROPERTYNAME,
self::GROUPS_PROPERTYNAME,
self::NUM_FILES_PROPERTYNAME,
self::FILEID_PROPERTYNAME,
self::REFERENCE_FILEID_PROPERTYNAME,
], function ($props) use ($node) {
$tag = $node->getSystemTag();
$name = $tag->getName();
Expand Down Expand Up @@ -409,7 +417,7 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
$this->tagManager->setTagGroups($tag, $groupIds);
}

if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::FILEID_PROPERTYNAME])) {
if (isset($props[self::NUM_FILES_PROPERTYNAME]) || isset($props[self::REFERENCE_FILEID_PROPERTYNAME])) {
// read-only properties
throw new Forbidden();
}
Expand Down
28 changes: 6 additions & 22 deletions apps/dav/lib/SystemTag/SystemTagsByIdCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use OCP\IUserSession;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\SystemTag\TagNotFoundException;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
Expand All @@ -19,21 +20,6 @@

class SystemTagsByIdCollection implements ICollection {

/**
* @var ISystemTagManager
*/
private $tagManager;

/**
* @var IGroupManager
*/
private $groupManager;

/**
* @var IUserSession
*/
private $userSession;

/**
* SystemTagsByIdCollection constructor.
*
Expand All @@ -42,13 +28,11 @@ class SystemTagsByIdCollection implements ICollection {
* @param IGroupManager $groupManager
*/
public function __construct(
ISystemTagManager $tagManager,
IUserSession $userSession,
IGroupManager $groupManager,
private ISystemTagManager $tagManager,
private IUserSession $userSession,
private IGroupManager $groupManager,
protected ISystemTagObjectMapper $tagMapper,
) {
$this->tagManager = $tagManager;
$this->userSession = $userSession;
$this->groupManager = $groupManager;
}

/**
Expand Down Expand Up @@ -180,6 +164,6 @@ public function getLastModified() {
* @return SystemTagNode
*/
private function makeNode(ISystemTag $tag) {
return new SystemTagNode($tag, $this->userSession->getUser(), $this->isAdmin(), $this->tagManager);
return new SystemTagNode($tag, $this->userSession->getUser(), $this->isAdmin(), $this->tagManager, $this->tagMapper);
}
}
21 changes: 9 additions & 12 deletions apps/dav/lib/SystemTag/SystemTagsInUseCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,21 @@
use OCP\Files\NotPermittedException;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\SimpleCollection;

class SystemTagsInUseCollection extends SimpleCollection {
protected IUserSession $userSession;
protected IRootFolder $rootFolder;
protected string $mediaType;
protected ISystemTagManager $systemTagManager;
protected SystemTagsInFilesDetector $systemTagsInFilesDetector;

/** @noinspection PhpMissingParentConstructorInspection */
public function __construct(
IUserSession $userSession,
IRootFolder $rootFolder,
ISystemTagManager $systemTagManager,
SystemTagsInFilesDetector $systemTagsInFilesDetector,
string $mediaType = '',
protected IUserSession $userSession,
protected IRootFolder $rootFolder,
protected ISystemTagManager $systemTagManager,
protected ISystemTagObjectMapper $tagMapper,
protected SystemTagsInFilesDetector $systemTagsInFilesDetector,
protected string $mediaType = '',
) {
$this->userSession = $userSession;
$this->rootFolder = $rootFolder;
Expand All @@ -54,7 +51,7 @@ public function getChild($name): self {
if ($this->mediaType !== '') {
throw new NotFound('Invalid media type');
}
return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->systemTagsInFilesDetector, $name);
return new self($this->userSession, $this->rootFolder, $this->systemTagManager, $this->tagMapper, $this->systemTagsInFilesDetector, $name);
}

/**
Expand All @@ -81,7 +78,7 @@ public function getChildren(): array {
foreach ($result as $tagData) {
$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']);
// read only, so we can submit the isAdmin parameter as false generally
$node = new SystemTagNode($tag, $user, false, $this->systemTagManager);
$node = new SystemTagNode($tag, $user, false, $this->systemTagManager, $this->tagMapper);
$node->setNumberOfFiles((int)$tagData['number_files']);
$node->setReferenceFileId((int)$tagData['ref_file_id']);
$children[] = $node;
Expand Down
14 changes: 13 additions & 1 deletion apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
use OCP\IUser;
use OCP\IUserSession;
use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\SystemTag\TagNotFoundException;
use PHPUnit\Framework\MockObject\MockObject;

class SystemTagsByIdCollectionTest extends \Test\TestCase {

Expand All @@ -40,21 +42,31 @@ public function getNode($isAdmin = true) {
$this->user->expects($this->any())
->method('getUID')
->willReturn('testuser');

/** @var IUserSession|MockObject */
$userSession = $this->getMockBuilder(IUserSession::class)
->getMock();
$userSession->expects($this->any())
->method('getUser')
->willReturn($this->user);

/** @var IGroupManager|MockObject */
$groupManager = $this->getMockBuilder(IGroupManager::class)
->getMock();
$groupManager->expects($this->any())
->method('isAdmin')
->with('testuser')
->willReturn($isAdmin);

/** @var ISystemTagObjectMapper|MockObject */
$tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class)
->getMock();

return new SystemTagsByIdCollection(
$this->tagManager,
$userSession,
$groupManager
$groupManager,
$tagMapper,
);
}

Expand Down
Loading
Loading