Skip to content

Commit

Permalink
Merge pull request #48786 from nextcloud/feat/files-bulk-tagging
Browse files Browse the repository at this point in the history
  • Loading branch information
skjnldsv authored Oct 29, 2024
2 parents 6601c50 + 3e6d18a commit 73fdf2c
Show file tree
Hide file tree
Showing 143 changed files with 2,188 additions and 253 deletions.
2 changes: 2 additions & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -362,9 +362,11 @@
'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',
'OCA\\DAV\\SystemTag\\SystemTagsObjectList' => $baseDir . '/../lib/SystemTag/SystemTagsObjectList.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => $baseDir . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => $baseDir . '/../lib/SystemTag/SystemTagsRelationsCollection.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,11 @@ 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',
'OCA\\DAV\\SystemTag\\SystemTagsObjectList' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectList.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectMappingCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectMappingCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsObjectTypeCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsObjectTypeCollection.php',
'OCA\\DAV\\SystemTag\\SystemTagsRelationsCollection' => __DIR__ . '/..' . '/../lib/SystemTag/SystemTagsRelationsCollection.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
34 changes: 31 additions & 3 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,7 +21,7 @@
/**
* DAV node representing a system tag, with the name being the tag id.
*/
class SystemTagNode implements \Sabre\DAV\INode {
class SystemTagNode implements \Sabre\DAV\ICollection {

protected int $numberOfFiles = -1;
protected int $referenceFileId = -1;
Expand All @@ -43,8 +43,9 @@ public function __construct(
/**
* Whether to allow permissions for admins
*/
protected $isAdmin,
protected bool $isAdmin,
protected ISystemTagManager $tagManager,
protected ISystemTagObjectMapper $tagMapper,
) {
}

Expand Down Expand Up @@ -164,4 +165,31 @@ 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() {
$objectTypes = $this->tagMapper->getAvailableObjectTypes();
return array_map(
function ($objectType) {
return new SystemTagObjectType($this->tag, $objectType, $this->tagManager, $this->tagMapper);
},
$objectTypes
);
}
}
81 changes: 81 additions & 0 deletions apps/dav/lib/SystemTag/SystemTagObjectType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?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\IFile {
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.
*/
public function getObjectsIds(): array {
if (empty($this->objectsIds)) {
$this->objectsIds = $this->tagMapper->getObjectIdsForTags($this->tag->getId(), $this->type);
}

return $this->objectsIds;
}

/**
* Returns the system tag represented by this node
*
* @return ISystemTag system tag
*/
public function getSystemTag() {
return $this->tag;
}

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

public function getLastModified() {
return null;
}

public function getETag() {
return '"' . $this->tag->getETag() . '"';
}

public function setName($name) {
throw new MethodNotAllowed();
}
public function put($data) {
throw new MethodNotAllowed();
}
public function get() {
throw new MethodNotAllowed();
}
public function delete() {
throw new MethodNotAllowed();
}
public function getContentType() {
throw new MethodNotAllowed();
}
public function getSize() {
throw new MethodNotAllowed();
}
}
70 changes: 64 additions & 6 deletions apps/dav/lib/SystemTag/SystemTagPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OCA\DAV\SystemTag;

use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCA\DAV\Connector\Sabre\Node;
use OCP\IGroupManager;
use OCP\IUser;
Expand Down Expand Up @@ -37,6 +38,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 +47,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 OBJECTIDS_PROPERTYNAME = '{http://nextcloud.org/ns}object-ids';

/**
* @var \Sabre\DAV\Server $server
Expand Down Expand Up @@ -78,6 +81,9 @@ public function __construct(
*/
public function initialize(\Sabre\DAV\Server $server) {
$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
$server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';

$server->xml->elementMap[self::OBJECTIDS_PROPERTYNAME] = SystemTagsObjectList::class;

$server->protectedProperties[] = self::ID_PROPERTYNAME;

Expand Down Expand Up @@ -202,7 +208,7 @@ public function handleGetProperties(
return;
}

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

Expand All @@ -211,6 +217,10 @@ public function handleGetProperties(
$propFind->setPath(str_replace('systemtags-assigned/', 'systemtags/', $propFind->getPath()));
}

$propFind->handle(FilesPlugin::GETETAG_PROPERTYNAME, function () use ($node): string {
return '"' . ($node->getSystemTag()->getETag() ?? '') . '"';
});

$propFind->handle(self::ID_PROPERTYNAME, function () use ($node) {
return $node->getSystemTag()->getId();
});
Expand Down Expand Up @@ -251,9 +261,25 @@ 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();
});

$propFind->handle(self::OBJECTIDS_PROPERTYNAME, function () use ($node): SystemTagsObjectList {
$objectTypes = $this->tagMapper->getAvailableObjectTypes();
$objects = [];
foreach ($objectTypes as $type) {
$systemTagObjectType = new SystemTagObjectType($node->getSystemTag(), $type, $this->tagManager, $this->tagMapper);
$objects = array_merge($objects, array_fill_keys($systemTagObjectType->getObjectsIds(), $type));
}
return new SystemTagsObjectList($objects);
});
}

if ($node instanceof SystemTagObjectType) {
$propFind->handle(self::OBJECTIDS_PROPERTYNAME, function () use ($node): SystemTagsObjectList {
return new SystemTagsObjectList(array_fill_keys($node->getObjectsIds(), $node->getName()));
});
}
}

Expand Down Expand Up @@ -341,18 +367,50 @@ private function getTagsForFile(int $fileId, ?IUser $user): array {
*/
public function handleUpdateProperties($path, PropPatch $propPatch) {
$node = $this->server->tree->getNodeForPath($path);
if (!($node instanceof SystemTagNode)) {
if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagObjectType)) {
return;
}

$propPatch->handle([self::OBJECTIDS_PROPERTYNAME], function ($props) use ($node) {
if (!($node instanceof SystemTagObjectType)) {
return false;
}

if (isset($props[self::OBJECTIDS_PROPERTYNAME])) {
$propValue = $props[self::OBJECTIDS_PROPERTYNAME];
if (!($propValue instanceof SystemTagsObjectList) || count($propValue->getObjects()) === 0) {
throw new BadRequest('Invalid object-ids property');
}

$objects = $propValue->getObjects();
$objectTypes = array_unique(array_values($objects));

if (count($objectTypes) !== 1 || $objectTypes[0] !== $node->getName()) {
throw new BadRequest('Invalid object-ids property. All object types must be of the same type: ' . $node->getName());
}

$this->tagMapper->setObjectIdsForTag($node->getSystemTag()->getId(), $node->getName(), array_keys($objects));
}

if ($props[self::OBJECTIDS_PROPERTYNAME] === null) {
$this->tagMapper->setObjectIdsForTag($node->getSystemTag()->getId(), $node->getName(), []);
}

return true;
});

$propPatch->handle([
self::DISPLAYNAME_PROPERTYNAME,
self::USERVISIBLE_PROPERTYNAME,
self::USERASSIGNABLE_PROPERTYNAME,
self::GROUPS_PROPERTYNAME,
self::NUM_FILES_PROPERTYNAME,
self::FILEID_PROPERTYNAME,
self::REFERENCE_FILEID_PROPERTYNAME,
], function ($props) use ($node) {
if (!($node instanceof SystemTagNode)) {
return false;
}

$tag = $node->getSystemTag();
$name = $tag->getName();
$userVisible = $tag->isUserVisible();
Expand Down Expand Up @@ -388,7 +446,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
4 changes: 3 additions & 1 deletion 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 @@ -30,6 +31,7 @@ public function __construct(
private ISystemTagManager $tagManager,
private IUserSession $userSession,
private IGroupManager $groupManager,
protected ISystemTagObjectMapper $tagMapper,
) {
}

Expand Down Expand Up @@ -162,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);
}
}
8 changes: 5 additions & 3 deletions apps/dav/lib/SystemTag/SystemTagsInUseCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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;
Expand All @@ -28,6 +29,7 @@ public function __construct(
protected IUserSession $userSession,
protected IRootFolder $rootFolder,
protected ISystemTagManager $systemTagManager,
protected ISystemTagObjectMapper $tagMapper,
SystemTagsInFilesDetector $systemTagsInFilesDetector,
protected string $mediaType = '',
) {
Expand All @@ -46,7 +48,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 @@ -71,9 +73,9 @@ public function getChildren(): array {
$result = $this->systemTagsInFilesDetector->detectAssignedSystemTagsIn($userFolder, $this->mediaType);
$children = [];
foreach ($result as $tagData) {
$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable']);
$tag = new SystemTag((string)$tagData['id'], $tagData['name'], (bool)$tagData['visibility'], (bool)$tagData['editable'], $tagData['etag']);
// 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
Loading

0 comments on commit 73fdf2c

Please sign in to comment.