diff options
author | Sebastian Krupinski <165827823+SebastianKrupinski@users.noreply.github.com> | 2024-11-04 13:29:44 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-04 13:29:44 -0500 |
commit | 5f49effcba28043050e46e1ba6be7b40f4b6dcd4 (patch) | |
tree | 779661acad56c488b056e8c19ffbae6945e8dd4a /apps/dav/lib | |
parent | 165041746e603683ff0bd11250dff0af950b584b (diff) | |
parent | 5cd3a8c9ada5ea2951eb011b449ee483b9d5a322 (diff) | |
download | nextcloud-server-5f49effcba28043050e46e1ba6be7b40f4b6dcd4.tar.gz nextcloud-server-5f49effcba28043050e46e1ba6be7b40f4b6dcd4.zip |
Merge branch 'master' into feat/issue-3786-allow-shared-calendarsfeat/issue-3786-allow-shared-calendars
Diffstat (limited to 'apps/dav/lib')
-rw-r--r-- | apps/dav/lib/CalDAV/Activity/Provider/Base.php | 6 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/EventReader.php | 15 | ||||
-rw-r--r-- | apps/dav/lib/RootCollection.php | 6 | ||||
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagNode.php | 34 | ||||
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagObjectType.php | 81 | ||||
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagPlugin.php | 70 | ||||
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagsByIdCollection.php | 4 | ||||
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagsInUseCollection.php | 8 | ||||
-rw-r--r-- | apps/dav/lib/SystemTag/SystemTagsObjectList.php | 80 |
9 files changed, 277 insertions, 27 deletions
diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Base.php b/apps/dav/lib/CalDAV/Activity/Provider/Base.php index 43d80b4bb6a..9a75acb878c 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Base.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Base.php @@ -44,14 +44,14 @@ abstract class Base implements IProvider { $data['name'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { return [ 'type' => 'calendar', - 'id' => $data['id'], + 'id' => (string)$data['id'], 'name' => $l->t('Personal'), ]; } return [ 'type' => 'calendar', - 'id' => $data['id'], + 'id' => (string)$data['id'], 'name' => $data['name'], ]; } @@ -64,7 +64,7 @@ abstract class Base implements IProvider { protected function generateLegacyCalendarParameter($id, $name) { return [ 'type' => 'calendar', - 'id' => $id, + 'id' => (string)$id, 'name' => $name, ]; } diff --git a/apps/dav/lib/CalDAV/EventReader.php b/apps/dav/lib/CalDAV/EventReader.php index 6e845c596ae..7ff05501ea8 100644 --- a/apps/dav/lib/CalDAV/EventReader.php +++ b/apps/dav/lib/CalDAV/EventReader.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace OCA\DAV\CalDAV; use DateTime; +use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; use InvalidArgumentException; @@ -109,7 +110,7 @@ class EventReader { unset($events[$key]); } } - + // No base event was found. CalDAV does allow cases where only // overridden instances are stored. // @@ -173,15 +174,17 @@ class EventReader { // evaluate if duration exists // extract duration and calculate end date elseif (isset($this->baseEvent->DURATION)) { - $this->baseEventDuration = $this->baseEvent->DURATION->getDateInterval(); - $this->baseEventEndDate = ((clone $this->baseEventStartDate)->add($this->baseEventDuration)); + $this->baseEventEndDate = DateTimeImmutable::createFromInterface($this->baseEventStartDate) + ->add($this->baseEvent->DURATION->getDateInterval()); + $this->baseEventDuration = $this->baseEventEndDate->getTimestamp() - $this->baseEventStartDate->getTimestamp(); } // evaluate if start date is floating // set duration to 24 hours and calculate the end date // according to the rfc any event without a end date or duration is a complete day elseif ($this->baseEventStartDateFloating == true) { $this->baseEventDuration = 86400; - $this->baseEventEndDate = ((clone $this->baseEventStartDate)->add($this->baseEventDuration)); + $this->baseEventEndDate = DateTimeImmutable::createFromInterface($this->baseEventStartDate) + ->setTimestamp($this->baseEventStartDate->getTimestamp() + $this->baseEventDuration); } // otherwise, set duration to zero this should never happen else { @@ -220,7 +223,7 @@ class EventReader { foreach ($events as $vevent) { $this->recurrenceModified[$vevent->{'RECURRENCE-ID'}->getDateTime($this->baseEventStartTimeZone)->getTimeStamp()] = $vevent; } - + $this->recurrenceCurrentDate = clone $this->baseEventStartDate; } @@ -375,7 +378,7 @@ class EventReader { * @return int|null */ public function recurringConcludesAfter(): ?int { - + // construct count place holder $count = 0; // retrieve and add RRULE iterations count diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 6ede8cb683c..751ab17bb7a 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -105,11 +105,7 @@ class RootCollection extends SimpleCollection { $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(), diff --git a/apps/dav/lib/SystemTag/SystemTagNode.php b/apps/dav/lib/SystemTag/SystemTagNode.php index 9357f207779..ed4eb44cbbd 100644 --- a/apps/dav/lib/SystemTag/SystemTagNode.php +++ b/apps/dav/lib/SystemTag/SystemTagNode.php @@ -10,8 +10,8 @@ namespace OCA\DAV\SystemTag; 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; @@ -21,7 +21,7 @@ use Sabre\DAV\Exception\NotFound; /** * 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; @@ -43,8 +43,9 @@ class SystemTagNode implements \Sabre\DAV\INode { /** * Whether to allow permissions for admins */ - protected $isAdmin, + protected bool $isAdmin, protected ISystemTagManager $tagManager, + protected ISystemTagObjectMapper $tagMapper, ) { } @@ -164,4 +165,31 @@ class SystemTagNode implements \Sabre\DAV\INode { 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 + ); + } } diff --git a/apps/dav/lib/SystemTag/SystemTagObjectType.php b/apps/dav/lib/SystemTag/SystemTagObjectType.php new file mode 100644 index 00000000000..0e1368854cd --- /dev/null +++ b/apps/dav/lib/SystemTag/SystemTagObjectType.php @@ -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(); + } +} diff --git a/apps/dav/lib/SystemTag/SystemTagPlugin.php b/apps/dav/lib/SystemTag/SystemTagPlugin.php index 3c34f4263f6..00585953b29 100644 --- a/apps/dav/lib/SystemTag/SystemTagPlugin.php +++ b/apps/dav/lib/SystemTag/SystemTagPlugin.php @@ -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; @@ -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'; @@ -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 @@ -78,6 +81,9 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { */ 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; @@ -202,7 +208,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { return; } - if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode)) { + if (!($node instanceof SystemTagNode) && !($node instanceof SystemTagMappingNode) && !($node instanceof SystemTagObjectType)) { return; } @@ -211,6 +217,10 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { $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(); }); @@ -251,9 +261,25 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { 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())); + }); } } @@ -341,9 +367,37 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { */ 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, @@ -351,8 +405,12 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { 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(); @@ -388,7 +446,7 @@ class SystemTagPlugin extends \Sabre\DAV\ServerPlugin { $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(); } diff --git a/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php b/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php index cf889985d1b..b854db7b94d 100644 --- a/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsByIdCollection.php @@ -11,6 +11,7 @@ use OCP\IGroupManager; 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; @@ -30,6 +31,7 @@ class SystemTagsByIdCollection implements ICollection { private ISystemTagManager $tagManager, private IUserSession $userSession, private IGroupManager $groupManager, + protected ISystemTagObjectMapper $tagMapper, ) { } @@ -162,6 +164,6 @@ class SystemTagsByIdCollection implements ICollection { * @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); } } diff --git a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php index 6a3dd0c2155..9b02a5be42c 100644 --- a/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php +++ b/apps/dav/lib/SystemTag/SystemTagsInUseCollection.php @@ -16,6 +16,7 @@ use OCP\Files\IRootFolder; 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; @@ -28,6 +29,7 @@ class SystemTagsInUseCollection extends SimpleCollection { protected IUserSession $userSession, protected IRootFolder $rootFolder, protected ISystemTagManager $systemTagManager, + protected ISystemTagObjectMapper $tagMapper, SystemTagsInFilesDetector $systemTagsInFilesDetector, protected string $mediaType = '', ) { @@ -46,7 +48,7 @@ class SystemTagsInUseCollection extends SimpleCollection { 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); } /** @@ -71,9 +73,9 @@ class SystemTagsInUseCollection extends SimpleCollection { $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; diff --git a/apps/dav/lib/SystemTag/SystemTagsObjectList.php b/apps/dav/lib/SystemTag/SystemTagsObjectList.php new file mode 100644 index 00000000000..0743b8d8130 --- /dev/null +++ b/apps/dav/lib/SystemTag/SystemTagsObjectList.php @@ -0,0 +1,80 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\SystemTag; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; +use Sabre\Xml\XmlDeserializable; +use Sabre\Xml\XmlSerializable; + +/** + * This property contains multiple "object-id" elements. + */ +class SystemTagsObjectList implements XmlSerializable, XmlDeserializable { + + public const NS_NEXTCLOUD = 'http://nextcloud.org/ns'; + public const OBJECTID_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}object-id'; + public const OBJECTID_PROPERTYNAME = '{http://nextcloud.org/ns}id'; + public const OBJECTTYPE_PROPERTYNAME = '{http://nextcloud.org/ns}type'; + + /** + * @param array<string, string> $objects An array of object ids and their types + */ + public function __construct( + private array $objects, + ) { + } + + public function getObjects(): array { + return $this->objects; + } + + public static function xmlDeserialize(Reader $reader) { + $tree = $reader->parseInnerTree(); + if ($tree === null) { + return null; + } + + $objects = []; + foreach ($tree as $elem) { + if ($elem['name'] === self::OBJECTID_ROOT_PROPERTYNAME) { + $value = $elem['value']; + $id = ''; + $type = ''; + foreach ($value as $subElem) { + if ($subElem['name'] === self::OBJECTID_PROPERTYNAME) { + $id = $subElem['value']; + } elseif ($subElem['name'] === self::OBJECTTYPE_PROPERTYNAME) { + $type = $subElem['value']; + } + } + if ($id !== '' && $type !== '') { + $objects[$id] = $type; + } + } + } + + return new self($objects); + } + + /** + * The xmlSerialize method is called during xml writing. + * + * @param Writer $writer + * @return void + */ + public function xmlSerialize(Writer $writer) { + foreach ($this->objects as $objectsId => $type) { + $writer->startElement(SystemTagPlugin::OBJECTIDS_PROPERTYNAME); + $writer->writeElement(self::OBJECTID_PROPERTYNAME, $objectsId); + $writer->writeElement(self::OBJECTTYPE_PROPERTYNAME, $type); + $writer->endElement(); + } + } +} |