Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/dav/appinfo/info.xml4
-rw-r--r--apps/dav/appinfo/v1/caldav.php14
-rw-r--r--apps/dav/composer/composer/autoload_classmap.php14
-rw-r--r--apps/dav/composer/composer/autoload_static.php14
-rw-r--r--apps/dav/lib/AppInfo/Application.php13
-rw-r--r--apps/dav/lib/BackgroundJob/CalendarRetentionJob.php48
-rw-r--r--apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php5
-rw-r--r--apps/dav/lib/CalDAV/Activity/Backend.php22
-rw-r--r--apps/dav/lib/CalDAV/Activity/Provider/Calendar.php14
-rw-r--r--apps/dav/lib/CalDAV/Activity/Provider/Event.php14
-rw-r--r--apps/dav/lib/CalDAV/CalDavBackend.php430
-rw-r--r--apps/dav/lib/CalDAV/Calendar.php20
-rw-r--r--apps/dav/lib/CalDAV/CalendarHome.php20
-rw-r--r--apps/dav/lib/CalDAV/CalendarObject.php4
-rw-r--r--apps/dav/lib/CalDAV/IRestorable.php41
-rw-r--r--apps/dav/lib/CalDAV/RetentionService.php80
-rw-r--r--apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php96
-rw-r--r--apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php131
-rw-r--r--apps/dav/lib/CalDAV/Trashbin/Plugin.php96
-rw-r--r--apps/dav/lib/CalDAV/Trashbin/RestoreTarget.php82
-rw-r--r--apps/dav/lib/CalDAV/Trashbin/TrashbinHome.php129
-rw-r--r--apps/dav/lib/Command/CreateCalendar.php14
-rw-r--r--apps/dav/lib/Command/RetentionCleanupCommand.php48
-rw-r--r--apps/dav/lib/Events/CalendarMovedToTrashEvent.php82
-rw-r--r--apps/dav/lib/Events/CalendarObjectMovedToTrashEvent.php96
-rw-r--r--apps/dav/lib/Events/CalendarObjectRestoredEvent.php96
-rw-r--r--apps/dav/lib/Events/CalendarRestoredEvent.php82
-rw-r--r--apps/dav/lib/HookManager.php5
-rw-r--r--apps/dav/lib/Listener/ActivityUpdaterListener.php99
-rw-r--r--apps/dav/lib/Listener/CalendarObjectReminderUpdaterListener.php78
-rw-r--r--apps/dav/lib/Migration/Version1018Date20210312100735.php45
-rw-r--r--apps/dav/lib/RootCollection.php18
-rw-r--r--apps/dav/lib/Server.php3
-rw-r--r--apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php10
-rw-r--r--apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php9
-rw-r--r--apps/dav/tests/travis/caldav/install.sh6
-rw-r--r--apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php15
-rw-r--r--apps/dav/tests/unit/CalDAV/CalDavBackendTest.php4
-rw-r--r--apps/dav/tests/unit/CalDAV/CalendarHomeTest.php12
-rw-r--r--apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php6
-rw-r--r--build/integration/features/bootstrap/CalDavContext.php3
-rw-r--r--build/psalm-baseline.xml26
42 files changed, 1938 insertions, 110 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index ff99623e382..c99d82a798d 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
<name>WebDAV</name>
<summary>WebDAV endpoint</summary>
<description>WebDAV endpoint</description>
- <version>1.17.2</version>
+ <version>1.18.0</version>
<licence>agpl</licence>
<author>owncloud.org</author>
<namespace>DAV</namespace>
@@ -24,6 +24,7 @@
<job>OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob</job>
<job>OCA\DAV\BackgroundJob\CleanupInvitationTokenJob</job>
<job>OCA\DAV\BackgroundJob\EventReminderJob</job>
+ <job>OCA\DAV\BackgroundJob\CalendarRetentionJob</job>
</background-jobs>
<repair-steps>
@@ -48,6 +49,7 @@
<command>OCA\DAV\Command\CreateCalendar</command>
<command>OCA\DAV\Command\MoveCalendar</command>
<command>OCA\DAV\Command\ListCalendars</command>
+ <command>OCA\DAV\Command\RetentionCleanupCommand</command>
<command>OCA\DAV\Command\SendEventReminders</command>
<command>OCA\DAV\Command\SyncBirthdayCalendar</command>
<command>OCA\DAV\Command\SyncSystemAddressBook</command>
diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php
index 236d81f66f8..19842c91f09 100644
--- a/apps/dav/appinfo/v1/caldav.php
+++ b/apps/dav/appinfo/v1/caldav.php
@@ -61,8 +61,20 @@ $random = \OC::$server->getSecureRandom();
$logger = \OC::$server->getLogger();
$dispatcher = \OC::$server->get(\OCP\EventDispatcher\IEventDispatcher::class);
$legacyDispatcher = \OC::$server->getEventDispatcher();
+$config = \OC::$server->get(\OCP\IConfig::class);
-$calDavBackend = new CalDavBackend($db, $principalBackend, $userManager, \OC::$server->getGroupManager(), $random, $logger, $dispatcher, $legacyDispatcher, true);
+$calDavBackend = new CalDavBackend(
+ $db,
+ $principalBackend,
+ $userManager,
+ \OC::$server->getGroupManager(),
+ $random,
+ $logger,
+ $dispatcher,
+ $legacyDispatcher,
+ $config,
+ true
+);
$debugging = \OC::$server->getConfig()->getSystemValue('debug', false);
$sendInvitations = \OC::$server->getConfig()->getAppValue('dav', 'sendInvitations', 'yes') === 'yes';
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index bedb7b6ec78..19c0e2549f6 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -13,6 +13,7 @@ return array(
'OCA\\DAV\\Avatars\\AvatarNode' => $baseDir . '/../lib/Avatars/AvatarNode.php',
'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php',
'OCA\\DAV\\BackgroundJob\\BuildReminderIndexBackgroundJob' => $baseDir . '/../lib/BackgroundJob/BuildReminderIndexBackgroundJob.php',
+ 'OCA\\DAV\\BackgroundJob\\CalendarRetentionJob' => $baseDir . '/../lib/BackgroundJob/CalendarRetentionJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => $baseDir . '/../lib/BackgroundJob/EventReminderJob.php',
@@ -44,6 +45,7 @@ return array(
'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
+ 'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php',
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => $baseDir . '/../lib/CalDAV/Integration/ExternalCalendar.php',
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => $baseDir . '/../lib/CalDAV/Integration/ICalendarProvider.php',
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
@@ -72,6 +74,7 @@ return array(
'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php',
'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php',
'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => $baseDir . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php',
+ 'OCA\\DAV\\CalDAV\\RetentionService' => $baseDir . '/../lib/CalDAV/RetentionService.php',
'OCA\\DAV\\CalDAV\\Schedule\\IMipPlugin' => $baseDir . '/../lib/CalDAV/Schedule/IMipPlugin.php',
'OCA\\DAV\\CalDAV\\Schedule\\Plugin' => $baseDir . '/../lib/CalDAV/Schedule/Plugin.php',
'OCA\\DAV\\CalDAV\\Search\\SearchPlugin' => $baseDir . '/../lib/CalDAV/Search/SearchPlugin.php',
@@ -82,6 +85,11 @@ return array(
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/PropFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => $baseDir . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObjectsCollection' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\Plugin' => $baseDir . '/../lib/CalDAV/Trashbin/Plugin.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\RestoreTarget' => $baseDir . '/../lib/CalDAV/Trashbin/RestoreTarget.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\TrashbinHome' => $baseDir . '/../lib/CalDAV/Trashbin/TrashbinHome.php',
'OCA\\DAV\\CalDAV\\WebcalCaching\\Plugin' => $baseDir . '/../lib/CalDAV/WebcalCaching/Plugin.php',
'OCA\\DAV\\CalDAV\\WebcalCaching\\RefreshWebcalService' => $baseDir . '/../lib/CalDAV/WebcalCaching/RefreshWebcalService.php',
'OCA\\DAV\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
@@ -113,6 +121,7 @@ return array(
'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php',
'OCA\\DAV\\Command\\MoveCalendar' => $baseDir . '/../lib/Command/MoveCalendar.php',
'OCA\\DAV\\Command\\RemoveInvalidShares' => $baseDir . '/../lib/Command/RemoveInvalidShares.php',
+ 'OCA\\DAV\\Command\\RetentionCleanupCommand' => $baseDir . '/../lib/Command/RetentionCleanupCommand.php',
'OCA\\DAV\\Command\\SendEventReminders' => $baseDir . '/../lib/Command/SendEventReminders.php',
'OCA\\DAV\\Command\\SyncBirthdayCalendar' => $baseDir . '/../lib/Command/SyncBirthdayCalendar.php',
'OCA\\DAV\\Command\\SyncSystemAddressBook' => $baseDir . '/../lib/Command/SyncSystemAddressBook.php',
@@ -188,10 +197,14 @@ return array(
'OCA\\DAV\\Events\\CachedCalendarObjectUpdatedEvent' => $baseDir . '/../lib/Events/CachedCalendarObjectUpdatedEvent.php',
'OCA\\DAV\\Events\\CalendarCreatedEvent' => $baseDir . '/../lib/Events/CalendarCreatedEvent.php',
'OCA\\DAV\\Events\\CalendarDeletedEvent' => $baseDir . '/../lib/Events/CalendarDeletedEvent.php',
+ 'OCA\\DAV\\Events\\CalendarMovedToTrashEvent' => $baseDir . '/../lib/Events/CalendarMovedToTrashEvent.php',
'OCA\\DAV\\Events\\CalendarObjectCreatedEvent' => $baseDir . '/../lib/Events/CalendarObjectCreatedEvent.php',
'OCA\\DAV\\Events\\CalendarObjectDeletedEvent' => $baseDir . '/../lib/Events/CalendarObjectDeletedEvent.php',
+ 'OCA\\DAV\\Events\\CalendarObjectMovedToTrashEvent' => $baseDir . '/../lib/Events/CalendarObjectMovedToTrashEvent.php',
+ 'OCA\\DAV\\Events\\CalendarObjectRestoredEvent' => $baseDir . '/../lib/Events/CalendarObjectRestoredEvent.php',
'OCA\\DAV\\Events\\CalendarObjectUpdatedEvent' => $baseDir . '/../lib/Events/CalendarObjectUpdatedEvent.php',
'OCA\\DAV\\Events\\CalendarPublishedEvent' => $baseDir . '/../lib/Events/CalendarPublishedEvent.php',
+ 'OCA\\DAV\\Events\\CalendarRestoredEvent' => $baseDir . '/../lib/Events/CalendarRestoredEvent.php',
'OCA\\DAV\\Events\\CalendarShareUpdatedEvent' => $baseDir . '/../lib/Events/CalendarShareUpdatedEvent.php',
'OCA\\DAV\\Events\\CalendarUnpublishedEvent' => $baseDir . '/../lib/Events/CalendarUnpublishedEvent.php',
'OCA\\DAV\\Events\\CalendarUpdatedEvent' => $baseDir . '/../lib/Events/CalendarUpdatedEvent.php',
@@ -248,6 +261,7 @@ return array(
'OCA\\DAV\\Migration\\Version1012Date20190808122342' => $baseDir . '/../lib/Migration/Version1012Date20190808122342.php',
'OCA\\DAV\\Migration\\Version1016Date20201109085907' => $baseDir . '/../lib/Migration/Version1016Date20201109085907.php',
'OCA\\DAV\\Migration\\Version1017Date20210216083742' => $baseDir . '/../lib/Migration/Version1017Date20210216083742.php',
+ 'OCA\\DAV\\Migration\\Version1018Date20210312100735' => $baseDir . '/../lib/Migration/Version1018Date20210312100735.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index 0a81ac8b4e9..6895064e39d 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -28,6 +28,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Avatars\\AvatarNode' => __DIR__ . '/..' . '/../lib/Avatars/AvatarNode.php',
'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php',
'OCA\\DAV\\BackgroundJob\\BuildReminderIndexBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/BuildReminderIndexBackgroundJob.php',
+ 'OCA\\DAV\\BackgroundJob\\CalendarRetentionJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CalendarRetentionJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
'OCA\\DAV\\BackgroundJob\\EventReminderJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/EventReminderJob.php',
@@ -59,6 +60,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
+ 'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php',
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ExternalCalendar.php',
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ICalendarProvider.php',
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
@@ -87,6 +89,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\ResourceBooking\\AbstractPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/AbstractPrincipalBackend.php',
'OCA\\DAV\\CalDAV\\ResourceBooking\\ResourcePrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/ResourcePrincipalBackend.php',
'OCA\\DAV\\CalDAV\\ResourceBooking\\RoomPrincipalBackend' => __DIR__ . '/..' . '/../lib/CalDAV/ResourceBooking/RoomPrincipalBackend.php',
+ 'OCA\\DAV\\CalDAV\\RetentionService' => __DIR__ . '/..' . '/../lib/CalDAV/RetentionService.php',
'OCA\\DAV\\CalDAV\\Schedule\\IMipPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Schedule/IMipPlugin.php',
'OCA\\DAV\\CalDAV\\Schedule\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Schedule/Plugin.php',
'OCA\\DAV\\CalDAV\\Search\\SearchPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Search/SearchPlugin.php',
@@ -97,6 +100,11 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/PropFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObjectsCollection' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/Plugin.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\RestoreTarget' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/RestoreTarget.php',
+ 'OCA\\DAV\\CalDAV\\Trashbin\\TrashbinHome' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/TrashbinHome.php',
'OCA\\DAV\\CalDAV\\WebcalCaching\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/WebcalCaching/Plugin.php',
'OCA\\DAV\\CalDAV\\WebcalCaching\\RefreshWebcalService' => __DIR__ . '/..' . '/../lib/CalDAV/WebcalCaching/RefreshWebcalService.php',
'OCA\\DAV\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
@@ -128,6 +136,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php',
'OCA\\DAV\\Command\\MoveCalendar' => __DIR__ . '/..' . '/../lib/Command/MoveCalendar.php',
'OCA\\DAV\\Command\\RemoveInvalidShares' => __DIR__ . '/..' . '/../lib/Command/RemoveInvalidShares.php',
+ 'OCA\\DAV\\Command\\RetentionCleanupCommand' => __DIR__ . '/..' . '/../lib/Command/RetentionCleanupCommand.php',
'OCA\\DAV\\Command\\SendEventReminders' => __DIR__ . '/..' . '/../lib/Command/SendEventReminders.php',
'OCA\\DAV\\Command\\SyncBirthdayCalendar' => __DIR__ . '/..' . '/../lib/Command/SyncBirthdayCalendar.php',
'OCA\\DAV\\Command\\SyncSystemAddressBook' => __DIR__ . '/..' . '/../lib/Command/SyncSystemAddressBook.php',
@@ -203,10 +212,14 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Events\\CachedCalendarObjectUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CachedCalendarObjectUpdatedEvent.php',
'OCA\\DAV\\Events\\CalendarCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarCreatedEvent.php',
'OCA\\DAV\\Events\\CalendarDeletedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarDeletedEvent.php',
+ 'OCA\\DAV\\Events\\CalendarMovedToTrashEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarMovedToTrashEvent.php',
'OCA\\DAV\\Events\\CalendarObjectCreatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarObjectCreatedEvent.php',
'OCA\\DAV\\Events\\CalendarObjectDeletedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarObjectDeletedEvent.php',
+ 'OCA\\DAV\\Events\\CalendarObjectMovedToTrashEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarObjectMovedToTrashEvent.php',
+ 'OCA\\DAV\\Events\\CalendarObjectRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarObjectRestoredEvent.php',
'OCA\\DAV\\Events\\CalendarObjectUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarObjectUpdatedEvent.php',
'OCA\\DAV\\Events\\CalendarPublishedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarPublishedEvent.php',
+ 'OCA\\DAV\\Events\\CalendarRestoredEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarRestoredEvent.php',
'OCA\\DAV\\Events\\CalendarShareUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarShareUpdatedEvent.php',
'OCA\\DAV\\Events\\CalendarUnpublishedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarUnpublishedEvent.php',
'OCA\\DAV\\Events\\CalendarUpdatedEvent' => __DIR__ . '/..' . '/../lib/Events/CalendarUpdatedEvent.php',
@@ -263,6 +276,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Migration\\Version1012Date20190808122342' => __DIR__ . '/..' . '/../lib/Migration/Version1012Date20190808122342.php',
'OCA\\DAV\\Migration\\Version1016Date20201109085907' => __DIR__ . '/..' . '/../lib/Migration/Version1016Date20201109085907.php',
'OCA\\DAV\\Migration\\Version1017Date20210216083742' => __DIR__ . '/..' . '/../lib/Migration/Version1017Date20210216083742.php',
+ 'OCA\\DAV\\Migration\\Version1018Date20210312100735' => __DIR__ . '/..' . '/../lib/Migration/Version1018Date20210312100735.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index d6c20f81d96..8dfbf62db2c 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/apps/dav/lib/AppInfo/Application.php
@@ -57,9 +57,13 @@ use OCA\DAV\Events\AddressBookShareUpdatedEvent;
use OCA\DAV\Events\AddressBookUpdatedEvent;
use OCA\DAV\Events\CalendarCreatedEvent;
use OCA\DAV\Events\CalendarDeletedEvent;
+use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
+use OCA\DAV\Events\CalendarObjectRestoredEvent;
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
+use OCA\DAV\Events\CalendarRestoredEvent;
use OCA\DAV\Events\CalendarShareUpdatedEvent;
use OCA\DAV\Events\CalendarUpdatedEvent;
use OCA\DAV\Events\CardCreatedEvent;
@@ -129,7 +133,11 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(CalendarDeletedEvent::class, ActivityUpdaterListener::class);
$context->registerEventListener(CalendarDeletedEvent::class, CalendarObjectReminderUpdaterListener::class);
$context->registerEventListener(CalendarDeletedEvent::class, CalendarDeletionDefaultUpdaterListener::class);
+ $context->registerEventListener(CalendarMovedToTrashEvent::class, ActivityUpdaterListener::class);
+ $context->registerEventListener(CalendarMovedToTrashEvent::class, CalendarObjectReminderUpdaterListener::class);
$context->registerEventListener(CalendarUpdatedEvent::class, ActivityUpdaterListener::class);
+ $context->registerEventListener(CalendarRestoredEvent::class, ActivityUpdaterListener::class);
+ $context->registerEventListener(CalendarRestoredEvent::class, CalendarObjectReminderUpdaterListener::class);
$context->registerEventListener(CalendarObjectCreatedEvent::class, ActivityUpdaterListener::class);
$context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarContactInteractionListener::class);
$context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarObjectReminderUpdaterListener::class);
@@ -138,6 +146,10 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarObjectReminderUpdaterListener::class);
$context->registerEventListener(CalendarObjectDeletedEvent::class, ActivityUpdaterListener::class);
$context->registerEventListener(CalendarObjectDeletedEvent::class, CalendarObjectReminderUpdaterListener::class);
+ $context->registerEventListener(CalendarObjectMovedToTrashEvent::class, ActivityUpdaterListener::class);
+ $context->registerEventListener(CalendarObjectMovedToTrashEvent::class, CalendarObjectReminderUpdaterListener::class);
+ $context->registerEventListener(CalendarObjectRestoredEvent::class, ActivityUpdaterListener::class);
+ $context->registerEventListener(CalendarObjectRestoredEvent::class, CalendarObjectReminderUpdaterListener::class);
$context->registerEventListener(CalendarShareUpdatedEvent::class, CalendarContactInteractionListener::class);
@@ -220,6 +232,7 @@ class Application extends App implements IBootstrap {
$syncService->updateUser($user);
});
+
$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function (GenericEvent $event) use ($container) {
/** @var Backend $backend */
$backend = $container->query(Backend::class);
diff --git a/apps/dav/lib/BackgroundJob/CalendarRetentionJob.php b/apps/dav/lib/BackgroundJob/CalendarRetentionJob.php
new file mode 100644
index 00000000000..51215051659
--- /dev/null
+++ b/apps/dav/lib/BackgroundJob/CalendarRetentionJob.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\BackgroundJob;
+
+use OCA\DAV\CalDAV\RetentionService;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
+
+class CalendarRetentionJob extends TimedJob {
+ /** @var RetentionService */
+ private $service;
+
+ public function __construct(ITimeFactory $time,
+ RetentionService $service) {
+ parent::__construct($time);
+ $this->service = $service;
+
+ // Run four times a day
+ $this->setInterval(6 * 60 * 60);
+ }
+
+ protected function run($argument): void {
+ $this->service->cleanUp();
+ }
+}
diff --git a/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php b/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php
index 4ed4319d701..86d2d3ae35d 100644
--- a/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php
+++ b/apps/dav/lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php
@@ -415,7 +415,10 @@ class UpdateCalendarResourcesRoomsBackgroundJob extends TimedJob {
CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI);
if ($calendar !== null) {
- $this->calDavBackend->deleteCalendar($calendar['id']);
+ $this->calDavBackend->deleteCalendar(
+ $calendar['id'],
+ true // Because this wasn't deleted by a user
+ );
}
}
diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php
index 16f581db872..4b896de3b89 100644
--- a/apps/dav/lib/CalDAV/Activity/Backend.php
+++ b/apps/dav/lib/CalDAV/Activity/Backend.php
@@ -85,12 +85,32 @@ class Backend {
}
/**
+ * Creates activities when a calendar was moved to trash
+ *
+ * @param array $calendarData
+ * @param array $shares
+ */
+ public function onCalendarMovedToTrash(array $calendarData, array $shares): void {
+ $this->triggerCalendarActivity(Calendar::SUBJECT_MOVE_TO_TRASH, $calendarData, $shares);
+ }
+
+ /**
+ * Creates activities when a calendar was restored
+ *
+ * @param array $calendarData
+ * @param array $shares
+ */
+ public function onCalendarRestored(array $calendarData, array $shares): void {
+ $this->triggerCalendarActivity(Calendar::SUBJECT_RESTORE, $calendarData, $shares);
+ }
+
+ /**
* Creates activities when a calendar was deleted
*
* @param array $calendarData
* @param array $shares
*/
- public function onCalendarDelete(array $calendarData, array $shares) {
+ public function onCalendarDelete(array $calendarData, array $shares): void {
$this->triggerCalendarActivity(Calendar::SUBJECT_DELETE, $calendarData, $shares);
}
diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php b/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php
index 382825d8955..a30c628a3ad 100644
--- a/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php
+++ b/apps/dav/lib/CalDAV/Activity/Provider/Calendar.php
@@ -39,6 +39,8 @@ use OCP\L10N\IFactory;
class Calendar extends Base {
public const SUBJECT_ADD = 'calendar_add';
public const SUBJECT_UPDATE = 'calendar_update';
+ public const SUBJECT_MOVE_TO_TRASH = 'calendar_move_to_trash';
+ public const SUBJECT_RESTORE = 'calendar_restore';
public const SUBJECT_DELETE = 'calendar_delete';
public const SUBJECT_PUBLISH = 'calendar_publish';
public const SUBJECT_UNPUBLISH = 'calendar_unpublish';
@@ -107,6 +109,14 @@ class Calendar extends Base {
$subject = $this->l->t('{actor} updated calendar {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_UPDATE . '_self') {
$subject = $this->l->t('You updated calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_MOVE_TO_TRASH) {
+ $subject = $this->l->t('{actor} deleted calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_MOVE_TO_TRASH . '_self') {
+ $subject = $this->l->t('You deleted calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_RESTORE) {
+ $subject = $this->l->t('{actor} restored calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_RESTORE . '_self') {
+ $subject = $this->l->t('You restored calendar {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_PUBLISH . '_self') {
$subject = $this->l->t('You shared calendar {calendar} as public link');
} elseif ($event->getSubject() === self::SUBJECT_UNPUBLISH . '_self') {
@@ -172,6 +182,10 @@ class Calendar extends Base {
case self::SUBJECT_DELETE . '_self':
case self::SUBJECT_UPDATE:
case self::SUBJECT_UPDATE . '_self':
+ case self::SUBJECT_MOVE_TO_TRASH:
+ case self::SUBJECT_MOVE_TO_TRASH . '_self':
+ case self::SUBJECT_RESTORE:
+ case self::SUBJECT_RESTORE . '_self':
case self::SUBJECT_PUBLISH . '_self':
case self::SUBJECT_UNPUBLISH . '_self':
case self::SUBJECT_SHARE_USER:
diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php
index 8850715a1c5..03845cc4d87 100644
--- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php
+++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php
@@ -40,6 +40,8 @@ use OCP\L10N\IFactory;
class Event extends Base {
public const SUBJECT_OBJECT_ADD = 'object_add';
public const SUBJECT_OBJECT_UPDATE = 'object_update';
+ public const SUBJECT_OBJECT_MOVE_TO_TRASH = 'object_move_to_trash';
+ public const SUBJECT_OBJECT_RESTORE = 'object_restore';
public const SUBJECT_OBJECT_DELETE = 'object_delete';
/** @var IFactory */
@@ -143,6 +145,14 @@ class Event extends Base {
$subject = $this->l->t('{actor} updated event {event} in calendar {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_event_self') {
$subject = $this->l->t('You updated event {event} in calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_MOVE_TO_TRASH . '_event') {
+ $subject = $this->l->t('{actor} deleted event {event} from calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_MOVE_TO_TRASH . '_event_self') {
+ $subject = $this->l->t('You deleted event {event} from calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_RESTORE . '_event') {
+ $subject = $this->l->t('{actor} restored event {event} of calendar {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_RESTORE . '_event_self') {
+ $subject = $this->l->t('You restored event {event} of calendar {calendar}');
} else {
throw new \InvalidArgumentException();
}
@@ -169,6 +179,8 @@ class Event extends Base {
case self::SUBJECT_OBJECT_ADD . '_event':
case self::SUBJECT_OBJECT_DELETE . '_event':
case self::SUBJECT_OBJECT_UPDATE . '_event':
+ case self::SUBJECT_OBJECT_MOVE_TO_TRASH . '_event':
+ case self::SUBJECT_OBJECT_RESTORE . '_event':
return [
'actor' => $this->generateUserParameter($parameters['actor']),
'calendar' => $this->generateCalendarParameter($parameters['calendar'], $this->l),
@@ -177,6 +189,8 @@ class Event extends Base {
case self::SUBJECT_OBJECT_ADD . '_event_self':
case self::SUBJECT_OBJECT_DELETE . '_event_self':
case self::SUBJECT_OBJECT_UPDATE . '_event_self':
+ case self::SUBJECT_OBJECT_MOVE_TO_TRASH . '_event_self':
+ case self::SUBJECT_OBJECT_RESTORE . '_event_self':
return [
'calendar' => $this->generateCalendarParameter($parameters['calendar'], $this->l),
'event' => $this->generateClassifiedObjectParameter($parameters['object']),
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php
index 2daa03843de..53749855b8c 100644
--- a/apps/dav/lib/CalDAV/CalDavBackend.php
+++ b/apps/dav/lib/CalDAV/CalDavBackend.php
@@ -39,6 +39,7 @@
namespace OCA\DAV\CalDAV;
use DateTime;
+use OCA\DAV\AppInfo\Application;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\DAV\Sharing\Backend;
use OCA\DAV\DAV\Sharing\IShareable;
@@ -47,10 +48,14 @@ use OCA\DAV\Events\CachedCalendarObjectDeletedEvent;
use OCA\DAV\Events\CachedCalendarObjectUpdatedEvent;
use OCA\DAV\Events\CalendarCreatedEvent;
use OCA\DAV\Events\CalendarDeletedEvent;
+use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
+use OCA\DAV\Events\CalendarObjectRestoredEvent;
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
use OCA\DAV\Events\CalendarPublishedEvent;
+use OCA\DAV\Events\CalendarRestoredEvent;
use OCA\DAV\Events\CalendarShareUpdatedEvent;
use OCA\DAV\Events\CalendarUnpublishedEvent;
use OCA\DAV\Events\CalendarUpdatedEvent;
@@ -59,12 +64,14 @@ use OCA\DAV\Events\SubscriptionDeletedEvent;
use OCA\DAV\Events\SubscriptionUpdatedEvent;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
+use RuntimeException;
use Sabre\CalDAV\Backend\AbstractBackend;
use Sabre\CalDAV\Backend\SchedulingSupport;
use Sabre\CalDAV\Backend\SubscriptionSupport;
@@ -72,6 +79,7 @@ use Sabre\CalDAV\Backend\SyncSupport;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV;
+use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\PropPatch;
@@ -87,6 +95,15 @@ use Sabre\VObject\Reader;
use Sabre\VObject\Recur\EventIterator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
+use function array_merge;
+use function array_values;
+use function explode;
+use function is_array;
+use function pathinfo;
+use function sprintf;
+use function str_replace;
+use function strtolower;
+use function time;
/**
* Class CalDavBackend
@@ -134,6 +151,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => 'deleted_at',
];
/**
@@ -191,6 +209,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/** @var EventDispatcherInterface */
private $legacyDispatcher;
+ /** @var IConfig */
+ private $config;
+
/** @var bool */
private $legacyEndpoint;
@@ -218,6 +239,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
ILogger $logger,
IEventDispatcher $dispatcher,
EventDispatcherInterface $legacyDispatcher,
+ IConfig $config,
bool $legacyEndpoint = false) {
$this->db = $db;
$this->principalBackend = $principalBackend;
@@ -227,6 +249,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->logger = $logger;
$this->dispatcher = $dispatcher;
$this->legacyDispatcher = $legacyDispatcher;
+ $this->config = $config;
$this->legacyEndpoint = $legacyEndpoint;
}
@@ -262,6 +285,26 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
+ * @return array{id: int, deleted_at: int}[]
+ */
+ public function getDeletedCalendars(int $deletedBefore): array {
+ $qb = $this->db->getQueryBuilder();
+ $qb->select(['id', 'deleted_at'])
+ ->from('calendars')
+ ->where($qb->expr()->isNotNull('deleted_at'))
+ ->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($deletedBefore)));
+ $result = $qb->executeQuery();
+ $raw = $result->fetchAll();
+ $result->closeCursor();
+ return array_map(function ($row) {
+ return [
+ 'id' => (int) $row['id'],
+ 'deleted_at' => (int) $row['deleted_at'],
+ ];
+ }, $raw);
+ }
+
+ /**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
@@ -334,7 +377,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
if (!isset($calendars[$calendar['id']])) {
$calendars[$calendar['id']] = $calendar;
@@ -410,7 +454,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
$calendars[$calendar['id']] = $calendar;
}
@@ -458,7 +503,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
if (!isset($calendars[$calendar['id']])) {
$calendars[$calendar['id']] = $calendar;
@@ -534,7 +580,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
if (!isset($calendars[$calendar['id']])) {
$calendars[$calendar['id']] = $calendar;
@@ -601,7 +648,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
return $calendar;
}
@@ -654,7 +702,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
return $calendar;
}
@@ -705,7 +754,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendar[$xmlName] = $row[$dbName];
}
- $this->addOwnerPrincipal($calendar);
+ $calendar = $this->addOwnerPrincipalToCalendar($calendar);
+ $calendar = $this->addResourceTypeToCalendar($row, $calendar);
return $calendar;
}
@@ -872,33 +922,84 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param mixed $calendarId
* @return void
*/
- public function deleteCalendar($calendarId) {
+ public function deleteCalendar($calendarId, bool $forceDeletePermanently = false) {
+ // The calendar is deleted right away if this is either enforced by the caller
+ // or the special contacts birthday calendar or when the preference of an empty
+ // retention (0 seconds) is set, which signals a disabled trashbin.
$calendarData = $this->getCalendarById($calendarId);
- $shares = $this->getShares($calendarId);
+ $isBirthdayCalendar = isset($calendarData['uri']) && $calendarData['uri'] === BirthdayService::BIRTHDAY_CALENDAR_URI;
+ $trashbinDisabled = $this->config->getAppValue(Application::APP_ID, RetentionService::RETENTION_CONFIG_KEY) === '0';
+ if ($forceDeletePermanently || $isBirthdayCalendar || $trashbinDisabled) {
+ $calendarData = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
- $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
- $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
+ $qbDeleteCalendarObjectProps = $this->db->getQueryBuilder();
+ $qbDeleteCalendarObjectProps->delete($this->dbObjectPropertiesTable)
+ ->where($qbDeleteCalendarObjectProps->expr()->eq('calendarid', $qbDeleteCalendarObjectProps->createNamedParameter($calendarId)))
+ ->andWhere($qbDeleteCalendarObjectProps->expr()->eq('calendartype', $qbDeleteCalendarObjectProps->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
+ ->executeStatement();
- $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
- $stmt->execute([$calendarId]);
+ $qbDeleteCalendarObjects = $this->db->getQueryBuilder();
+ $qbDeleteCalendarObjects->delete('calendarobjects')
+ ->where($qbDeleteCalendarObjects->expr()->eq('calendarid', $qbDeleteCalendarObjects->createNamedParameter($calendarId)))
+ ->andWhere($qbDeleteCalendarObjects->expr()->eq('calendartype', $qbDeleteCalendarObjects->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
+ ->executeStatement();
- $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
- $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
+ $qbDeleteCalendarChanges = $this->db->getQueryBuilder();
+ $qbDeleteCalendarObjects->delete('calendarchanges')
+ ->where($qbDeleteCalendarChanges->expr()->eq('calendarid', $qbDeleteCalendarChanges->createNamedParameter($calendarId)))
+ ->andWhere($qbDeleteCalendarChanges->expr()->eq('calendartype', $qbDeleteCalendarChanges->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
+ ->executeStatement();
- $this->calendarSharingBackend->deleteAllShares($calendarId);
+ $this->calendarSharingBackend->deleteAllShares($calendarId);
- $query = $this->db->getQueryBuilder();
- $query->delete($this->dbObjectPropertiesTable)
- ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
- ->executeStatement();
+ $qbDeleteCalendar = $this->db->getQueryBuilder();
+ $qbDeleteCalendarObjects->delete('calendars')
+ ->where($qbDeleteCalendar->expr()->eq('id', $qbDeleteCalendar->createNamedParameter($calendarId)))
+ ->executeStatement();
+
+ // Only dispatch if we actually deleted anything
+ if ($calendarData) {
+ $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
+ }
+ } else {
+ $qbMarkCalendarDeleted = $this->db->getQueryBuilder();
+ $qbMarkCalendarDeleted->update('calendars')
+ ->set('deleted_at', $qbMarkCalendarDeleted->createNamedParameter(time()))
+ ->where($qbMarkCalendarDeleted->expr()->eq('id', $qbMarkCalendarDeleted->createNamedParameter($calendarId)))
+ ->executeStatement();
- // Only dispatch if we actually deleted anything
- if ($calendarData) {
- $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
+ $calendarData = $this->getCalendarById($calendarId);
+ $shares = $this->getShares($calendarId);
+ if ($calendarData) {
+ $this->dispatcher->dispatchTyped(new CalendarMovedToTrashEvent(
+ (int)$calendarId,
+ $calendarData,
+ $shares
+ ));
+ }
}
}
+ public function restoreCalendar(int $id): void {
+ $qb = $this->db->getQueryBuilder();
+ $update = $qb->update('calendars')
+ ->set('deleted_at', $qb->createNamedParameter(null))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $update->executeStatement();
+
+ $calendarData = $this->getCalendarById($id);
+ $shares = $this->getShares($id);
+ if ($calendarData === null) {
+ throw new RuntimeException('Calendar data that was just written can\'t be read back. Check your database configuration.');
+ }
+ $this->dispatcher->dispatchTyped(new CalendarRestoredEvent(
+ $id,
+ $calendarData,
+ $shares
+ ));
+ }
+
/**
* Delete all of an user's shares
*
@@ -946,7 +1047,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
->from('calendarobjects')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
+ ->andWhere($query->expr()->isNull('deleted_at'));
$stmt = $query->executeQuery();
$result = [];
@@ -967,6 +1069,63 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $result;
}
+ public function getDeletedCalendarObjects(int $deletedBefore): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.calendartype', 'co.size', 'co.componenttype', 'co.classification', 'co.deleted_at'])
+ ->from('calendarobjects', 'co')
+ ->join('co', 'calendars', 'c', $query->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->isNotNull('co.deleted_at'))
+ ->andWhere($query->expr()->lt('co.deleted_at', $query->createNamedParameter($deletedBefore)));
+ $stmt = $query->executeQuery();
+
+ $result = [];
+ foreach ($stmt->fetchAll() as $row) {
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => (int) $row['calendarid'],
+ 'calendartype' => (int) $row['calendartype'],
+ 'size' => (int) $row['size'],
+ 'component' => strtolower($row['componenttype']),
+ 'classification' => (int) $row['classification'],
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'],
+ ];
+ }
+ $stmt->closeCursor();
+
+ return $result;
+ }
+
+ public function getDeletedCalendarObjectsByPrincipal(string $principalUri): array {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.size', 'co.componenttype', 'co.classification', 'co.deleted_at'])
+ ->from('calendarobjects', 'co')
+ ->join('co', 'calendars', 'c', $query->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
+ ->andWhere($query->expr()->isNotNull('co.deleted_at'));
+ $stmt = $query->executeQuery();
+
+ $result = [];
+ while ($row = $stmt->fetch()) {
+ $result[] = [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'component' => strtolower($row['componenttype']),
+ 'classification' => (int)$row['classification'],
+ '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'],
+ ];
+ }
+ $stmt->closeCursor();
+
+ return $result;
+ }
+
/**
* Returns information from a single calendar object, based on it's object
* uri.
@@ -984,7 +1143,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param int $calendarType
* @return array|null
*/
- public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
+ public function getCalendarObject($calendarId, $objectUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$query = $this->db->getQueryBuilder();
$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
->from('calendarobjects')
@@ -1038,7 +1197,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->from('calendarobjects')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
+ ->andWhere($query->expr()->isNull('deleted_at'));
foreach ($chunks as $uris) {
$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
@@ -1085,19 +1245,34 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
$extraData = $this->getDenormalizedData($calendarData);
- $q = $this->db->getQueryBuilder();
- $q->select($q->func()->count('*'))
+ // Try to detect duplicates
+ $qb = $this->db->getQueryBuilder();
+ $qb->select($qb->func()->count('*'))
->from('calendarobjects')
- ->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
- ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
- ->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
-
- $result = $q->executeQuery();
+ ->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
+ ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($extraData['uid'])))
+ ->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
+ ->andWhere($qb->expr()->isNull('deleted_at'));
+ $result = $qb->executeQuery();
$count = (int) $result->fetchOne();
$result->closeCursor();
if ($count !== 0) {
- throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
+ throw new BadRequest('Calendar object with uid already exists in this calendar collection.');
+ }
+ // For a more specific error message we also try to explicitly look up the UID but as a deleted entry
+ $qbDel = $this->db->getQueryBuilder();
+ $qbDel->select($qb->func()->count('*'))
+ ->from('calendarobjects')
+ ->where($qbDel->expr()->eq('calendarid', $qbDel->createNamedParameter($calendarId)))
+ ->andWhere($qbDel->expr()->eq('uid', $qbDel->createNamedParameter($extraData['uid'])))
+ ->andWhere($qbDel->expr()->eq('calendartype', $qbDel->createNamedParameter($calendarType)))
+ ->andWhere($qbDel->expr()->isNotNull('deleted_at'));
+ $result = $qbDel->executeQuery();
+ $count = (int) $result->fetchOne();
+ $result->closeCursor();
+ if ($count !== 0) {
+ throw new BadRequest('Deleted calendar object with uid already exists in this calendar collection.');
}
$query = $this->db->getQueryBuilder();
@@ -1236,11 +1411,23 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param mixed $calendarId
* @param string $objectUri
* @param int $calendarType
+ * @param bool $forceDeletePermanently
* @return void
*/
- public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
+ public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR, bool $forceDeletePermanently = false) {
$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
- if (is_array($data)) {
+
+ if ($data === null) {
+ // Nothing to delete
+ return;
+ }
+
+ if ($forceDeletePermanently || $this->config->getAppValue(Application::APP_ID, RetentionService::RETENTION_CONFIG_KEY) === '0') {
+ $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
+ $stmt->execute([$calendarId, $objectUri, $calendarType]);
+
+ $this->purgeProperties($calendarId, $data['id']);
+
if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
$calendarRow = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
@@ -1260,19 +1447,108 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
]
));
}
- }
+ } else {
+ $pathInfo = pathinfo($data['uri']);
+ if (!empty($pathInfo['extension'])) {
+ // Append a suffix to "free" the old URI for recreation
+ $newUri = sprintf(
+ "%s-deleted.%s",
+ $pathInfo['filename'],
+ $pathInfo['extension']
+ );
+ } else {
+ $newUri = sprintf(
+ "%s-deleted",
+ $pathInfo['filename']
+ );
+ }
- $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
- $stmt->execute([$calendarId, $objectUri, $calendarType]);
+ // Try to detect conflicts before the DB does
+ // As unlikely as it seems, this can happen when the user imports, then deletes, imports and deletes again
+ $newObject = $this->getCalendarObject($calendarId, $newUri, $calendarType);
+ if ($newObject !== null) {
+ throw new Forbidden("A calendar object with URI $newUri already exists in calendar $calendarId, therefore this object can't be moved into the trashbin");
+ }
+
+ $qb = $this->db->getQueryBuilder();
+ $markObjectDeletedQuery = $qb->update('calendarobjects')
+ ->set('deleted_at', $qb->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
+ ->set('uri', $qb->createNamedParameter($newUri))
+ ->where(
+ $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
+ $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
+ $qb->expr()->eq('uri', $qb->createNamedParameter($objectUri))
+ );
+ $markObjectDeletedQuery->executeStatement();
- if (is_array($data)) {
- $this->purgeProperties($calendarId, $data['id'], $calendarType);
+ $calendarData = $this->getCalendarById($calendarId);
+ if ($calendarData !== null) {
+ $this->dispatcher->dispatchTyped(
+ new CalendarObjectMovedToTrashEvent(
+ (int)$calendarId,
+ $calendarData,
+ $this->getShares($calendarId),
+ $data
+ )
+ );
+ }
}
$this->addChange($calendarId, $objectUri, 3, $calendarType);
}
/**
+ * @param mixed $objectData
+ *
+ * @throws Forbidden
+ */
+ public function restoreCalendarObject(array $objectData): void {
+ $id = (int) $objectData['id'];
+ $restoreUri = str_replace("-deleted.ics", ".ics", $objectData['uri']);
+ $targetObject = $this->getCalendarObject(
+ $objectData['calendarid'],
+ $restoreUri
+ );
+ if ($targetObject !== null) {
+ throw new Forbidden("Can not restore calendar $id because a calendar object with the URI $restoreUri already exists");
+ }
+
+ $qb = $this->db->getQueryBuilder();
+ $update = $qb->update('calendarobjects')
+ ->set('uri', $qb->createNamedParameter($restoreUri))
+ ->set('deleted_at', $qb->createNamedParameter(null))
+ ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $update->executeStatement();
+
+ // Make sure this change is tracked in the changes table
+ $qb2 = $this->db->getQueryBuilder();
+ $selectObject = $qb2->select('calendardata', 'uri', 'calendarid', 'calendartype')
+ ->from('calendarobjects')
+ ->where($qb2->expr()->eq('id', $qb2->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $result = $selectObject->executeQuery();
+ $row = $result->fetch();
+ $result->closeCursor();
+ if ($row === false) {
+ // Welp, this should possibly not have happened, but let's ignore
+ return;
+ }
+ $this->addChange($row['calendarid'], $row['uri'], 1, (int) $row['calendartype']);
+
+ $calendarRow = $this->getCalendarById((int) $row['calendarid']);
+ if ($calendarRow === null) {
+ throw new RuntimeException('Calendar object data that was just written can\'t be read back. Check your database configuration.');
+ }
+ $this->dispatcher->dispatchTyped(
+ new CalendarObjectRestoredEvent(
+ (int) $objectData['calendarid'],
+ $calendarRow,
+ $this->getShares((int) $row['calendarid']),
+ $row
+ )
+ );
+ }
+
+ /**
* Performs a calendar-query on the contents of this calendar.
*
* The calendar-query is defined in RFC4791 : CalDAV. Using the
@@ -1359,7 +1635,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$query->select($columns)
->from('calendarobjects')
->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
- ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
+ ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
+ ->andWhere($query->expr()->isNull('deleted_at'));
if ($componentType) {
$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
@@ -1508,7 +1785,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->andWhere($compExpr)
->andWhere($propParamExpr)
->andWhere($query->expr()->iLike('i.value',
- $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
+ $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')))
+ ->andWhere($query->expr()->isNull('deleted_at'));
if ($offset) {
$query->setFirstResult($offset);
@@ -1574,7 +1852,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
- ->from('calendarobjects', 'c');
+ ->from('calendarobjects', 'c')
+ ->where($outerQuery->expr()->isNull('deleted_at'));
if (isset($options['timerange'])) {
if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
@@ -1776,7 +2055,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
->andWhere($calendarOr)
- ->andWhere($searchOr);
+ ->andWhere($searchOr)
+ ->andWhere($calendarObjectIdQuery->expr()->isNull('deleted_at'));
if ('' !== $pattern) {
if (!$escapePattern) {
@@ -1843,8 +2123,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->from('calendarobjects', 'co')
->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
- ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
-
+ ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)))
+ ->andWhere($query->expr()->isNull('co.deleted_at'));
$stmt = $query->executeQuery();
$row = $stmt->fetch();
$stmt->closeCursor();
@@ -1855,6 +2135,35 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return null;
}
+ public function getCalendarObjectById(string $principalUri, int $id): ?array {
+ $query = $this->db->getQueryBuilder();
+ $query->select(['co.id', 'co.uri', 'co.lastmodified', 'co.etag', 'co.calendarid', 'co.size', 'co.calendardata', 'co.componenttype', 'co.classification', 'co.deleted_at'])
+ ->from('calendarobjects', 'co')
+ ->join('co', 'calendars', 'c', $query->expr()->eq('c.id', 'co.calendarid', IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
+ ->andWhere($query->expr()->eq('co.id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT));
+ $stmt = $query->executeQuery();
+ $row = $stmt->fetch();
+ $stmt->closeCursor();
+
+ if (!$row) {
+ return null;
+ }
+
+ return [
+ 'id' => $row['id'],
+ 'uri' => $row['uri'],
+ 'lastmodified' => $row['lastmodified'],
+ 'etag' => '"' . $row['etag'] . '"',
+ 'calendarid' => $row['calendarid'],
+ 'size' => (int)$row['size'],
+ 'calendardata' => $this->readBlob($row['calendardata']),
+ 'component' => strtolower($row['componenttype']),
+ 'classification' => (int)$row['classification'],
+ 'deleted_at' => isset($row['deleted_at']) ? ((int) $row['deleted_at']) : null,
+ ];
+ }
+
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken in the specified calendar.
@@ -2410,7 +2719,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
}
if (!$componentType) {
- throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
+ throw new BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
}
if ($hasDTSTART) {
@@ -2670,7 +2979,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$ids = $result->fetchAll();
foreach ($ids as $id) {
- $this->deleteCalendar($id['id']);
+ $this->deleteCalendar(
+ $id['id'],
+ true // No data to keep in the trashbin, if the user re-enables then we regenerate
+ );
}
}
@@ -2802,9 +3114,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/**
* adds information about an owner to the calendar data
*
- * @param $calendarInfo
*/
- private function addOwnerPrincipal(&$calendarInfo) {
+ private function addOwnerPrincipalToCalendar(array $calendarInfo): array {
$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
if (isset($calendarInfo[$ownerPrincipalKey])) {
@@ -2817,5 +3128,20 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
if (isset($principalInformation['{DAV:}displayname'])) {
$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
}
+ return $calendarInfo;
+ }
+
+ private function addResourceTypeToCalendar(array $row, array $calendar): array {
+ if (isset($row['deleted_at'])) {
+ // Columns is set and not null -> this is a deleted calendar
+ // we send a custom resourcetype to hide the deleted calendar
+ // from ordinary DAV clients, but the Calendar app will know
+ // how to handle this special resource.
+ $calendar['{DAV:}resourcetype'] = new DAV\Xml\Property\ResourceType([
+ '{DAV:}collection',
+ sprintf('{%s}deleted-calendar', \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD),
+ ]);
+ }
+ return $calendar;
}
}
diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php
index abddf2b706f..191ac384e72 100644
--- a/apps/dav/lib/CalDAV/Calendar.php
+++ b/apps/dav/lib/CalDAV/Calendar.php
@@ -42,9 +42,9 @@ use Sabre\DAV\PropPatch;
* Class Calendar
*
* @package OCA\DAV\CalDAV
- * @property BackendInterface|CalDavBackend $caldavBackend
+ * @property CalDavBackend $caldavBackend
*/
-class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
+class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable {
/** @var IConfig */
private $config;
@@ -52,6 +52,9 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
/** @var IL10N */
protected $l10n;
+ /** @var bool */
+ private $useTrashbin = true;
+
/**
* Calendar constructor.
*
@@ -269,7 +272,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
$this->config->setUserValue($userId, 'dav', 'generateBirthdayCalendar', 'no');
}
- parent::delete();
+ $this->caldavBackend->deleteCalendar(
+ $this->calendarInfo['id'],
+ !$this->useTrashbin
+ );
}
public function propPatch(PropPatch $propPatch) {
@@ -399,4 +405,12 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
return parent::getChanges($syncToken, $syncLevel, $limit);
}
+
+ public function restore(): void {
+ $this->caldavBackend->restoreCalendar((int) $this->calendarInfo['id']);
+ }
+
+ public function disableTrashbin(): void {
+ $this->useTrashbin = false;
+ }
}
diff --git a/apps/dav/lib/CalDAV/CalendarHome.php b/apps/dav/lib/CalDAV/CalendarHome.php
index c746ab04112..c418ff049c1 100644
--- a/apps/dav/lib/CalDAV/CalendarHome.php
+++ b/apps/dav/lib/CalDAV/CalendarHome.php
@@ -30,6 +30,7 @@ namespace OCA\DAV\CalDAV;
use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
+use OCA\DAV\CalDAV\Trashbin\TrashbinHome;
use Sabre\CalDAV\Backend\BackendInterface;
use Sabre\CalDAV\Backend\NotificationSupport;
use Sabre\CalDAV\Backend\SchedulingSupport;
@@ -38,6 +39,7 @@ use Sabre\CalDAV\Schedule\Inbox;
use Sabre\CalDAV\Subscriptions\Subscription;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\INode;
use Sabre\DAV\MkCol;
class CalendarHome extends \Sabre\CalDAV\CalendarHome {
@@ -74,8 +76,11 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
/**
* @inheritdoc
*/
- public function createExtendedCollection($name, MkCol $mkCol) {
- $reservedNames = [BirthdayService::BIRTHDAY_CALENDAR_URI];
+ public function createExtendedCollection($name, MkCol $mkCol): void {
+ $reservedNames = [
+ BirthdayService::BIRTHDAY_CALENDAR_URI,
+ TrashbinHome::NAME,
+ ];
if (\in_array($name, $reservedNames, true) || ExternalCalendar::doesViolateReservedName($name)) {
throw new MethodNotAllowed('The resource you tried to create has a reserved name');
@@ -104,6 +109,10 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
$objects[] = new \Sabre\CalDAV\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
+ if ($this->caldavBackend instanceof CalDavBackend) {
+ $objects[] = new TrashbinHome($this->caldavBackend, $this->principalInfo);
+ }
+
// If the backend supports subscriptions, we'll add those as well,
if ($this->caldavBackend instanceof SubscriptionSupport) {
foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
@@ -127,7 +136,9 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
}
/**
- * @inheritdoc
+ * @param string $name
+ *
+ * @return INode
*/
public function getChild($name) {
// Special nodes
@@ -140,6 +151,9 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
if ($name === 'notifications' && $this->caldavBackend instanceof NotificationSupport) {
return new \Sabre\CalDAV\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
}
+ if ($name === TrashbinHome::NAME && $this->caldavBackend instanceof CalDavBackend) {
+ return new TrashbinHome($this->caldavBackend, $this->principalInfo);
+ }
// Calendars
foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
diff --git a/apps/dav/lib/CalDAV/CalendarObject.php b/apps/dav/lib/CalDAV/CalendarObject.php
index 766062ffdf1..0e42d9522ab 100644
--- a/apps/dav/lib/CalDAV/CalendarObject.php
+++ b/apps/dav/lib/CalDAV/CalendarObject.php
@@ -82,6 +82,10 @@ class CalendarObject extends \Sabre\CalDAV\CalendarObject {
return $vObject->serialize();
}
+ public function getId(): int {
+ return (int) $this->objectData['id'];
+ }
+
protected function isShared() {
if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
return false;
diff --git a/apps/dav/lib/CalDAV/IRestorable.php b/apps/dav/lib/CalDAV/IRestorable.php
new file mode 100644
index 00000000000..438098b5a0e
--- /dev/null
+++ b/apps/dav/lib/CalDAV/IRestorable.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV;
+
+use Sabre\DAV\Exception;
+
+/**
+ * Interface for nodes that can be restored from the trashbin
+ */
+interface IRestorable {
+
+ /**
+ * Restore this node
+ *
+ * @throws Exception
+ */
+ public function restore(): void;
+}
diff --git a/apps/dav/lib/CalDAV/RetentionService.php b/apps/dav/lib/CalDAV/RetentionService.php
new file mode 100644
index 00000000000..934e6f71530
--- /dev/null
+++ b/apps/dav/lib/CalDAV/RetentionService.php
@@ -0,0 +1,80 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV;
+
+use OCA\DAV\AppInfo\Application;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
+use function max;
+
+class RetentionService {
+ public const RETENTION_CONFIG_KEY = 'calendarRetentionObligation';
+ private const DEFAULT_RETENTION_SECONDS = 30 * 24 * 60 * 60;
+
+ /** @var IConfig */
+ private $config;
+
+ /** @var ITimeFactory */
+ private $time;
+
+ /** @var CalDavBackend */
+ private $calDavBackend;
+
+ public function __construct(IConfig $config,
+ ITimeFactory $time,
+ CalDavBackend $calDavBackend) {
+ $this->config = $config;
+ $this->time = $time;
+ $this->calDavBackend = $calDavBackend;
+ }
+
+ public function cleanUp(): void {
+ $retentionTime = max(
+ (int) $this->config->getAppValue(
+ Application::APP_ID,
+ self::RETENTION_CONFIG_KEY,
+ (string) self::DEFAULT_RETENTION_SECONDS
+ ),
+ 0 // Just making sure we don't delete things in the future when a negative number is passed
+ );
+ $now = $this->time->getTime();
+
+ $calendars = $this->calDavBackend->getDeletedCalendars($now - $retentionTime);
+ foreach ($calendars as $calendar) {
+ $this->calDavBackend->deleteCalendar($calendar['id'], true);
+ }
+
+ $objects = $this->calDavBackend->getDeletedCalendarObjects($now - $retentionTime);
+ foreach ($objects as $object) {
+ $this->calDavBackend->deleteCalendarObject(
+ $object['calendarid'],
+ $object['uri'],
+ $object['calendartype'],
+ true
+ );
+ }
+ }
+}
diff --git a/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php
new file mode 100644
index 00000000000..43d96b52efd
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObject.php
@@ -0,0 +1,96 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV\Trashbin;
+
+use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\IRestorable;
+use Sabre\CalDAV\ICalendarObject;
+use Sabre\DAV\Exception\Forbidden;
+
+class DeletedCalendarObject implements ICalendarObject, IRestorable {
+
+ /** @var string */
+ private $name;
+
+ /** @var mixed[] */
+ private $objectData;
+
+ /** @var CalDavBackend */
+ private $calDavBackend;
+
+ public function __construct(string $name,
+ array $objectData,
+ CalDavBackend $calDavBackend) {
+ $this->name = $name;
+ $this->objectData = $objectData;
+ $this->calDavBackend = $calDavBackend;
+ }
+
+ public function delete() {
+ throw new Forbidden();
+ }
+
+ public function getName() {
+ return $this->name;
+ }
+
+ public function setName($name) {
+ throw new Forbidden();
+ }
+
+ public function getLastModified() {
+ return 0;
+ }
+
+ public function put($data) {
+ throw new Forbidden();
+ }
+
+ public function get() {
+ return $this->objectData['calendardata'];
+ }
+
+ public function getContentType() {
+ $mime = 'text/calendar; charset=utf-8';
+ if (isset($this->objectData['component']) && $this->objectData['component']) {
+ $mime .= '; component='.$this->objectData['component'];
+ }
+
+ return $mime;
+ }
+
+ public function getETag() {
+ return $this->objectData['etag'];
+ }
+
+ public function getSize() {
+ return (int) $this->objectData['size'];
+ }
+
+ public function restore(): void {
+ $this->calDavBackend->restoreCalendarObject($this->objectData);
+ }
+}
diff --git a/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php
new file mode 100644
index 00000000000..2d79db03bce
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php
@@ -0,0 +1,131 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV\Trashbin;
+
+use OCA\DAV\CalDAV\CalDavBackend;
+use Sabre\CalDAV\ICalendarObjectContainer;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\NotImplemented;
+use function array_map;
+use function implode;
+use function preg_match;
+
+class DeletedCalendarObjectsCollection implements ICalendarObjectContainer {
+ public const NAME = 'objects';
+
+ /** @var CalDavBackend */
+ protected $caldavBackend;
+
+ /** @var mixed[] */
+ private $principalInfo;
+
+ public function __construct(CalDavBackend $caldavBackend,
+ array $principalInfo) {
+ $this->caldavBackend = $caldavBackend;
+ $this->principalInfo = $principalInfo;
+ }
+
+ /**
+ * @see \OCA\DAV\CalDAV\Trashbin\DeletedCalendarObjectsCollection::calendarQuery
+ */
+ public function getChildren() {
+ throw new NotImplemented();
+ }
+
+ public function getChild($name) {
+ if (!preg_match("/(\d+)\\.ics/", $name, $matches)) {
+ throw new NotFound();
+ }
+
+ $data = $this->caldavBackend->getCalendarObjectById(
+ $this->principalInfo['uri'],
+ (int) $matches[1],
+ );
+
+ // If the object hasn't been deleted yet then we don't want to find it here
+ if ($data === null) {
+ throw new NotFound();
+ }
+ if (!isset($data['deleted_at'])) {
+ throw new BadRequest('The calendar object you\'re trying to restore is not marked as deleted');
+ }
+
+ return new DeletedCalendarObject(
+ $this->getRelativeObjectPath($data),
+ $data,
+ $this->caldavBackend
+ );
+ }
+
+ public function createFile($name, $data = null) {
+ throw new Forbidden();
+ }
+
+ public function createDirectory($name) {
+ throw new Forbidden();
+ }
+
+ public function childExists($name) {
+ try {
+ $this->getChild($name);
+ } catch (NotFound $e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function delete() {
+ throw new Forbidden();
+ }
+
+ public function getName(): string {
+ return self::NAME;
+ }
+
+ public function setName($name) {
+ throw new Forbidden();
+ }
+
+ public function getLastModified(): int {
+ return 0;
+ }
+
+ public function calendarQuery(array $filters) {
+ return array_map(function (array $calendarInfo) {
+ return $this->getRelativeObjectPath($calendarInfo);
+ }, $this->caldavBackend->getDeletedCalendarObjectsByPrincipal($this->principalInfo['uri']));
+ }
+
+ private function getRelativeObjectPath(array $calendarInfo): string {
+ return implode(
+ '.',
+ [$calendarInfo['id'], 'ics'],
+ );
+ }
+}
diff --git a/apps/dav/lib/CalDAV/Trashbin/Plugin.php b/apps/dav/lib/CalDAV/Trashbin/Plugin.php
new file mode 100644
index 00000000000..d42a09f1c0d
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Trashbin/Plugin.php
@@ -0,0 +1,96 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV\Trashbin;
+
+use OCA\DAV\CalDAV\Calendar;
+use OCP\IRequest;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use function array_slice;
+use function implode;
+
+/**
+ * Conditional logic to bypass the calendar trashbin
+ */
+class Plugin extends ServerPlugin {
+
+ /** @var bool */
+ private $disableTrashbin;
+
+ /** @var Server */
+ private $server;
+
+ public function __construct(IRequest $request) {
+ $this->disableTrashbin = $request->getHeader('X-NC-CalDAV-No-Trashbin') === '1';
+ }
+
+ public function initialize(Server $server): void {
+ $this->server = $server;
+ $server->on('beforeMethod:*', [$this, 'beforeMethod']);
+ }
+
+ public function beforeMethod(RequestInterface $request, ResponseInterface $response): void {
+ if (!$this->disableTrashbin) {
+ return;
+ }
+
+ $path = $request->getPath();
+ $pathParts = explode('/', ltrim($path, '/'));
+ if (\count($pathParts) < 3) {
+ // We are looking for a path like calendars/username/calendarname
+ return;
+ }
+
+ // $calendarPath will look like calendars/username/calendarname
+ $calendarPath = implode(
+ '/',
+ array_slice($pathParts, 0, 3)
+ );
+ try {
+ $calendar = $this->server->tree->getNodeForPath($calendarPath);
+ if (!($calendar instanceof Calendar)) {
+ // This is odd
+ return;
+ }
+
+ /** @var Calendar $calendar */
+ $calendar->disableTrashbin();
+ } catch (NotFound $ex) {
+ return;
+ }
+ }
+
+ public function getFeatures(): array {
+ return ['nc-calendar-trashbin'];
+ }
+
+ public function getPluginName(): string {
+ return 'nc-calendar-trashbin';
+ }
+}
diff --git a/apps/dav/lib/CalDAV/Trashbin/RestoreTarget.php b/apps/dav/lib/CalDAV/Trashbin/RestoreTarget.php
new file mode 100644
index 00000000000..0175168e8d2
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Trashbin/RestoreTarget.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV\Trashbin;
+
+use OCA\DAV\CalDAV\IRestorable;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\ICollection;
+use Sabre\DAV\IMoveTarget;
+use Sabre\DAV\INode;
+
+class RestoreTarget implements ICollection, IMoveTarget {
+ public const NAME = 'restore';
+
+ public function createFile($name, $data = null) {
+ throw new Forbidden();
+ }
+
+ public function createDirectory($name) {
+ throw new Forbidden();
+ }
+
+ public function getChild($name) {
+ throw new NotFound();
+ }
+
+ public function getChildren(): array {
+ return [];
+ }
+
+ public function childExists($name): bool {
+ return false;
+ }
+
+ public function moveInto($targetName, $sourcePath, INode $sourceNode): bool {
+ if ($sourceNode instanceof IRestorable) {
+ $sourceNode->restore();
+ return true;
+ }
+
+ return false;
+ }
+
+ public function delete() {
+ throw new Forbidden();
+ }
+
+ public function getName(): string {
+ return 'restore';
+ }
+
+ public function setName($name) {
+ throw new Forbidden();
+ }
+
+ public function getLastModified() {
+ return 0;
+ }
+}
diff --git a/apps/dav/lib/CalDAV/Trashbin/TrashbinHome.php b/apps/dav/lib/CalDAV/Trashbin/TrashbinHome.php
new file mode 100644
index 00000000000..87f1f24aaab
--- /dev/null
+++ b/apps/dav/lib/CalDAV/Trashbin/TrashbinHome.php
@@ -0,0 +1,129 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\CalDAV\Trashbin;
+
+use OCA\DAV\CalDAV\CalDavBackend;
+use Sabre\DAV\Exception\Forbidden;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\ICollection;
+use Sabre\DAV\INode;
+use Sabre\DAV\IProperties;
+use Sabre\DAV\PropPatch;
+use Sabre\DAV\Xml\Property\ResourceType;
+use Sabre\DAVACL\ACLTrait;
+use Sabre\DAVACL\IACL;
+use function in_array;
+use function sprintf;
+
+class TrashbinHome implements IACL, ICollection, IProperties {
+ use ACLTrait;
+
+ public const NAME = 'trashbin';
+
+ /** @var CalDavBackend */
+ private $caldavBackend;
+
+ /** @var array */
+ private $principalInfo;
+
+ public function __construct(CalDavBackend $caldavBackend,
+ array $principalInfo) {
+ $this->caldavBackend = $caldavBackend;
+ $this->principalInfo = $principalInfo;
+ }
+
+ public function getOwner(): string {
+ return $this->principalInfo['uri'];
+ }
+
+ public function createFile($name, $data = null) {
+ throw new Forbidden('Permission denied to create files in the trashbin');
+ }
+
+ public function createDirectory($name) {
+ throw new Forbidden('Permission denied to create a directory in the trashbin');
+ }
+
+ public function getChild($name): INode {
+ switch ($name) {
+ case RestoreTarget::NAME:
+ return new RestoreTarget();
+ case DeletedCalendarObjectsCollection::NAME:
+ return new DeletedCalendarObjectsCollection(
+ $this->caldavBackend,
+ $this->principalInfo
+ );
+ }
+
+ throw new NotFound();
+ }
+
+ public function getChildren(): array {
+ return [
+ new RestoreTarget(),
+ new DeletedCalendarObjectsCollection(
+ $this->caldavBackend,
+ $this->principalInfo
+ ),
+ ];
+ }
+
+ public function childExists($name): bool {
+ return in_array($name, [
+ RestoreTarget::NAME,
+ DeletedCalendarObjectsCollection::NAME,
+ ], true);
+ }
+
+ public function delete() {
+ throw new Forbidden('Permission denied to delete the trashbin');
+ }
+
+ public function getName(): string {
+ return self::NAME;
+ }
+
+ public function setName($name) {
+ throw new Forbidden('Permission denied to rename the trashbin');
+ }
+
+ public function getLastModified(): int {
+ return 0;
+ }
+
+ public function propPatch(PropPatch $propPatch): void {
+ throw new Forbidden('not implemented');
+ }
+
+ public function getProperties($properties): array {
+ return [
+ '{DAV:}resourcetype' => new ResourceType([
+ '{DAV:}collection',
+ sprintf('{%s}trash-bin', \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD),
+ ]),
+ ];
+ }
+}
diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php
index 1d543c71bc2..5d0eede54b6 100644
--- a/apps/dav/lib/Command/CreateCalendar.php
+++ b/apps/dav/lib/Command/CreateCalendar.php
@@ -32,6 +32,7 @@ use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
@@ -94,9 +95,20 @@ class CreateCalendar extends Command {
$logger = \OC::$server->getLogger();
$dispatcher = \OC::$server->get(IEventDispatcher::class);
$legacyDispatcher = \OC::$server->getEventDispatcher();
+ $config = \OC::$server->get(IConfig::class);
$name = $input->getArgument('name');
- $caldav = new CalDavBackend($this->dbConnection, $principalBackend, $this->userManager, $this->groupManager, $random, $logger, $dispatcher, $legacyDispatcher);
+ $caldav = new CalDavBackend(
+ $this->dbConnection,
+ $principalBackend,
+ $this->userManager,
+ $this->groupManager,
+ $random,
+ $logger,
+ $dispatcher,
+ $legacyDispatcher,
+ $config
+ );
$caldav->createCalendar("principals/users/$user", $name, []);
return 0;
}
diff --git a/apps/dav/lib/Command/RetentionCleanupCommand.php b/apps/dav/lib/Command/RetentionCleanupCommand.php
new file mode 100644
index 00000000000..a1f1d201d83
--- /dev/null
+++ b/apps/dav/lib/Command/RetentionCleanupCommand.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\Command;
+
+use OCA\DAV\CalDAV\RetentionService;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class RetentionCleanupCommand extends Command {
+ /** @var RetentionService */
+ private $service;
+
+ public function __construct(RetentionService $service) {
+ parent::__construct('dav:retention:clean-up');
+
+ $this->service = $service;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $this->service->cleanUp();
+
+ return 0;
+ }
+}
diff --git a/apps/dav/lib/Events/CalendarMovedToTrashEvent.php b/apps/dav/lib/Events/CalendarMovedToTrashEvent.php
new file mode 100644
index 00000000000..cc09ef0f072
--- /dev/null
+++ b/apps/dav/lib/Events/CalendarMovedToTrashEvent.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 22.0.0
+ */
+class CalendarMovedToTrashEvent extends Event {
+
+ /** @var int */
+ private $calendarId;
+
+ /** @var array */
+ private $calendarData;
+
+ /** @var array */
+ private $shares;
+
+ /**
+ * @param int $calendarId
+ * @param array $calendarData
+ * @param array $shares
+ * @since 22.0.0
+ */
+ public function __construct(int $calendarId,
+ array $calendarData,
+ array $shares) {
+ parent::__construct();
+ $this->calendarId = $calendarId;
+ $this->calendarData = $calendarData;
+ $this->shares = $shares;
+ }
+
+ /**
+ * @return int
+ * @since 22.0.0
+ */
+ public function getCalendarId(): int {
+ return $this->calendarId;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getCalendarData(): array {
+ return $this->calendarData;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getShares(): array {
+ return $this->shares;
+ }
+}
diff --git a/apps/dav/lib/Events/CalendarObjectMovedToTrashEvent.php b/apps/dav/lib/Events/CalendarObjectMovedToTrashEvent.php
new file mode 100644
index 00000000000..91a1fed4019
--- /dev/null
+++ b/apps/dav/lib/Events/CalendarObjectMovedToTrashEvent.php
@@ -0,0 +1,96 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 22.0.0
+ */
+class CalendarObjectMovedToTrashEvent extends Event {
+
+ /** @var int */
+ private $calendarId;
+
+ /** @var array */
+ private $calendarData;
+
+ /** @var array */
+ private $shares;
+
+ /** @var array */
+ private $objectData;
+
+ /**
+ * @param int $calendarId
+ * @param array $calendarData
+ * @param array $shares
+ * @param array $objectData
+ * @since 22.0.0
+ */
+ public function __construct(int $calendarId,
+ array $calendarData,
+ array $shares,
+ array $objectData) {
+ parent::__construct();
+ $this->calendarId = $calendarId;
+ $this->calendarData = $calendarData;
+ $this->shares = $shares;
+ $this->objectData = $objectData;
+ }
+
+ /**
+ * @return int
+ * @since 22.0.0
+ */
+ public function getCalendarId(): int {
+ return $this->calendarId;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getCalendarData(): array {
+ return $this->calendarData;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getShares(): array {
+ return $this->shares;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getObjectData(): array {
+ return $this->objectData;
+ }
+}
diff --git a/apps/dav/lib/Events/CalendarObjectRestoredEvent.php b/apps/dav/lib/Events/CalendarObjectRestoredEvent.php
new file mode 100644
index 00000000000..d86c21e9ce8
--- /dev/null
+++ b/apps/dav/lib/Events/CalendarObjectRestoredEvent.php
@@ -0,0 +1,96 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 22.0.0
+ */
+class CalendarObjectRestoredEvent extends Event {
+
+ /** @var int */
+ private $calendarId;
+
+ /** @var array */
+ private $calendarData;
+
+ /** @var array */
+ private $shares;
+
+ /** @var array */
+ private $objectData;
+
+ /**
+ * @param int $calendarId
+ * @param array $calendarData
+ * @param array $shares
+ * @param array $objectData
+ * @since 22.0.0
+ */
+ public function __construct(int $calendarId,
+ array $calendarData,
+ array $shares,
+ array $objectData) {
+ parent::__construct();
+ $this->calendarId = $calendarId;
+ $this->calendarData = $calendarData;
+ $this->shares = $shares;
+ $this->objectData = $objectData;
+ }
+
+ /**
+ * @return int
+ * @since 22.0.0
+ */
+ public function getCalendarId(): int {
+ return $this->calendarId;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getCalendarData(): array {
+ return $this->calendarData;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getShares(): array {
+ return $this->shares;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getObjectData(): array {
+ return $this->objectData;
+ }
+}
diff --git a/apps/dav/lib/Events/CalendarRestoredEvent.php b/apps/dav/lib/Events/CalendarRestoredEvent.php
new file mode 100644
index 00000000000..3d4ac9cb863
--- /dev/null
+++ b/apps/dav/lib/Events/CalendarRestoredEvent.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2021 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\DAV\Events;
+
+use OCP\EventDispatcher\Event;
+
+/**
+ * @since 22.0.0
+ */
+class CalendarRestoredEvent extends Event {
+
+ /** @var int */
+ private $calendarId;
+
+ /** @var array */
+ private $calendarData;
+
+ /** @var array */
+ private $shares;
+
+ /**
+ * @param int $calendarId
+ * @param array $calendarData
+ * @param array $shares
+ * @since 22.0.0
+ */
+ public function __construct(int $calendarId,
+ array $calendarData,
+ array $shares) {
+ parent::__construct();
+ $this->calendarId = $calendarId;
+ $this->calendarData = $calendarData;
+ $this->shares = $shares;
+ }
+
+ /**
+ * @return int
+ * @since 22.0.0
+ */
+ public function getCalendarId(): int {
+ return $this->calendarId;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getCalendarData(): array {
+ return $this->calendarData;
+ }
+
+ /**
+ * @return array
+ * @since 22.0.0
+ */
+ public function getShares(): array {
+ return $this->shares;
+ }
+}
diff --git a/apps/dav/lib/HookManager.php b/apps/dav/lib/HookManager.php
index 558aad72c03..756d05bedbe 100644
--- a/apps/dav/lib/HookManager.php
+++ b/apps/dav/lib/HookManager.php
@@ -128,7 +128,10 @@ class HookManager {
}
foreach ($this->calendarsToDelete as $calendar) {
- $this->calDav->deleteCalendar($calendar['id']);
+ $this->calDav->deleteCalendar(
+ $calendar['id'],
+ true // Make sure the data doesn't go into the trashbin, a new user with the same UID would later see it otherwise
+ );
}
$this->calDav->deleteAllSharesByUser('principals/users/' . $uid);
diff --git a/apps/dav/lib/Listener/ActivityUpdaterListener.php b/apps/dav/lib/Listener/ActivityUpdaterListener.php
index 30e0008b183..181a5717c95 100644
--- a/apps/dav/lib/Listener/ActivityUpdaterListener.php
+++ b/apps/dav/lib/Listener/ActivityUpdaterListener.php
@@ -26,11 +26,16 @@ declare(strict_types=1);
namespace OCA\DAV\Listener;
use OCA\DAV\CalDAV\Activity\Backend as ActivityBackend;
+use OCA\DAV\DAV\Sharing\Plugin;
use OCA\DAV\Events\CalendarCreatedEvent;
use OCA\DAV\Events\CalendarDeletedEvent;
+use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
+use OCA\DAV\Events\CalendarObjectRestoredEvent;
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
+use OCA\DAV\Events\CalendarRestoredEvent;
use OCA\DAV\Events\CalendarUpdatedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@@ -85,17 +90,56 @@ class ActivityUpdaterListener implements IEventListener {
'exception' => $e,
]);
}
- } elseif ($event instanceof CalendarDeletedEvent) {
+ } elseif ($event instanceof CalendarMovedToTrashEvent) {
try {
- $this->activityBackend->onCalendarDelete(
+ $this->activityBackend->onCalendarMovedToTrash(
$event->getCalendarData(),
$event->getShares()
);
$this->logger->debug(
- sprintf('Activity generated for deleted calendar %d', $event->getCalendarId())
+ sprintf('Activity generated for changed calendar %d', $event->getCalendarId())
);
} catch (Throwable $e) {
+ // Any error with activities shouldn't abort the calendar update, so we just log it
+ $this->logger->error('Error generating activities for changed calendar: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ } elseif ($event instanceof CalendarRestoredEvent) {
+ try {
+ $this->activityBackend->onCalendarRestored(
+ $event->getCalendarData(),
+ $event->getShares()
+ );
+
+ $this->logger->debug(
+ sprintf('Activity generated for changed calendar %d', $event->getCalendarId())
+ );
+ } catch (Throwable $e) {
+ // Any error with activities shouldn't abort the calendar update, so we just log it
+ $this->logger->error('Error generating activities for changed calendar: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ } elseif ($event instanceof CalendarDeletedEvent) {
+ try {
+ $deletedProp = '{' . Plugin::NS_NEXTCLOUD . '}deleted-at';
+ if (isset($event->getCalendarData()[$deletedProp])) {
+ $this->logger->debug(
+ sprintf('Calendar %d was already in trashbin, skipping deletion activity', $event->getCalendarId())
+ );
+ } else {
+ $this->activityBackend->onCalendarDelete(
+ $event->getCalendarData(),
+ $event->getShares()
+ );
+
+ $this->logger->debug(
+ sprintf('Activity generated for deleted calendar %d', $event->getCalendarId())
+ );
+ }
+ } catch (Throwable $e) {
// Any error with activities shouldn't abort the calendar deletion, so we just log it
$this->logger->error('Error generating activities for a deleted calendar: ' . $e->getMessage(), [
'exception' => $e,
@@ -137,19 +181,62 @@ class ActivityUpdaterListener implements IEventListener {
'exception' => $e,
]);
}
- } elseif ($event instanceof CalendarObjectDeletedEvent) {
+ } elseif ($event instanceof CalendarObjectMovedToTrashEvent) {
try {
$this->activityBackend->onTouchCalendarObject(
- \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_DELETE,
+ \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_MOVE_TO_TRASH,
$event->getCalendarData(),
$event->getShares(),
$event->getObjectData()
);
$this->logger->debug(
- sprintf('Activity generated for deleted calendar object %d', $event->getCalendarId())
+ sprintf('Activity generated for a calendar object of calendar %d that is moved to trash', $event->getCalendarId())
+ );
+ } catch (Throwable $e) {
+ // Any error with activities shouldn't abort the calendar object creation, so we just log it
+ $this->logger->error('Error generating activity for a new calendar object: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ } elseif ($event instanceof CalendarObjectRestoredEvent) {
+ try {
+ $this->activityBackend->onTouchCalendarObject(
+ \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_RESTORE,
+ $event->getCalendarData(),
+ $event->getShares(),
+ $event->getObjectData()
+ );
+
+ $this->logger->debug(
+ sprintf('Activity generated for a restore calendar object of calendar %d', $event->getCalendarId())
);
} catch (Throwable $e) {
+ // Any error with activities shouldn't abort the calendar object restoration, so we just log it
+ $this->logger->error('Error generating activity for a restored calendar object: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ } elseif ($event instanceof CalendarObjectDeletedEvent) {
+ try {
+ $deletedProp = '{' . Plugin::NS_NEXTCLOUD . '}deleted-at';
+ if (isset($event->getObjectData()[$deletedProp])) {
+ $this->logger->debug(
+ sprintf('Calendar object in calendar %d was already in trashbin, skipping deletion activity', $event->getCalendarId())
+ );
+ } else {
+ $this->activityBackend->onTouchCalendarObject(
+ \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_DELETE,
+ $event->getCalendarData(),
+ $event->getShares(),
+ $event->getObjectData()
+ );
+
+ $this->logger->debug(
+ sprintf('Activity generated for deleted calendar object in calendar %d', $event->getCalendarId())
+ );
+ }
+ } catch (Throwable $e) {
// Any error with activities shouldn't abort the calendar deletion, so we just log it
$this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
'exception' => $e,
diff --git a/apps/dav/lib/Listener/CalendarObjectReminderUpdaterListener.php b/apps/dav/lib/Listener/CalendarObjectReminderUpdaterListener.php
index b976ef3ad9d..8ce6c024367 100644
--- a/apps/dav/lib/Listener/CalendarObjectReminderUpdaterListener.php
+++ b/apps/dav/lib/Listener/CalendarObjectReminderUpdaterListener.php
@@ -25,12 +25,17 @@ declare(strict_types=1);
namespace OCA\DAV\Listener;
+use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend;
use OCA\DAV\CalDAV\Reminder\ReminderService;
use OCA\DAV\Events\CalendarDeletedEvent;
+use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
+use OCA\DAV\Events\CalendarObjectRestoredEvent;
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
+use OCA\DAV\Events\CalendarRestoredEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use Psr\Log\LoggerInterface;
@@ -45,19 +50,39 @@ class CalendarObjectReminderUpdaterListener implements IEventListener {
/** @var ReminderService */
private $reminderService;
+ /** @var CalDavBackend */
+ private $calDavBackend;
+
/** @var LoggerInterface */
private $logger;
public function __construct(ReminderBackend $reminderBackend,
ReminderService $reminderService,
+ CalDavBackend $calDavBackend,
LoggerInterface $logger) {
$this->reminderBackend = $reminderBackend;
$this->reminderService = $reminderService;
+ $this->calDavBackend = $calDavBackend;
$this->logger = $logger;
}
public function handle(Event $event): void {
- if ($event instanceof CalendarDeletedEvent) {
+ if ($event instanceof CalendarMovedToTrashEvent) {
+ try {
+ $this->reminderBackend->cleanRemindersForCalendar(
+ $event->getCalendarId()
+ );
+
+ $this->logger->debug(
+ sprintf('Reminders of calendar %d cleaned up after move into trashbin', $event->getCalendarId())
+ );
+ } catch (Throwable $e) {
+ // Any error with reminders shouldn't abort the calendar move, so we just log it
+ $this->logger->error('Error cleaning up reminders of a calendar moved into trashbin: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ } elseif ($event instanceof CalendarDeletedEvent) {
try {
$this->reminderBackend->cleanRemindersForCalendar(
$event->getCalendarId()
@@ -72,6 +97,27 @@ class CalendarObjectReminderUpdaterListener implements IEventListener {
'exception' => $e,
]);
}
+ } elseif ($event instanceof CalendarRestoredEvent) {
+ try {
+ $objects = $this->calDavBackend->getCalendarObjects($event->getCalendarId());
+ $this->logger->debug(sprintf('Restoring calendar reminder objects for %d items', count($objects)));
+ foreach ($objects as $object) {
+ $fullObject = $this->calDavBackend->getCalendarObject(
+ $event->getCalendarId(),
+ $object['uri']
+ );
+ $this->reminderService->onCalendarObjectCreate($fullObject);
+ }
+
+ $this->logger->debug(
+ sprintf('Reminders of calendar %d restored', $event->getCalendarId())
+ );
+ } catch (Throwable $e) {
+ // Any error with reminders shouldn't abort the calendar deletion, so we just log it
+ $this->logger->error('Error restoring reminders of a calendar: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
} elseif ($event instanceof CalendarObjectCreatedEvent) {
try {
$this->reminderService->onCalendarObjectCreate(
@@ -102,6 +148,36 @@ class CalendarObjectReminderUpdaterListener implements IEventListener {
'exception' => $e,
]);
}
+ } elseif ($event instanceof CalendarObjectMovedToTrashEvent) {
+ try {
+ $this->reminderService->onCalendarObjectDelete(
+ $event->getObjectData()
+ );
+
+ $this->logger->debug(
+ sprintf('Reminders of restored calendar object of calendar %d deleted', $event->getCalendarId())
+ );
+ } catch (Throwable $e) {
+ // Any error with reminders shouldn't abort the calendar object deletion, so we just log it
+ $this->logger->error('Error deleting reminders of a calendar object: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+ } elseif ($event instanceof CalendarObjectRestoredEvent) {
+ try {
+ $this->reminderService->onCalendarObjectCreate(
+ $event->getObjectData()
+ );
+
+ $this->logger->debug(
+ sprintf('Reminders of restored calendar object of calendar %d restored', $event->getCalendarId())
+ );
+ } catch (Throwable $e) {
+ // Any error with reminders shouldn't abort the calendar object deletion, so we just log it
+ $this->logger->error('Error restoring reminders of a calendar object: ' . $e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
} elseif ($event instanceof CalendarObjectDeletedEvent) {
try {
$this->reminderService->onCalendarObjectDelete(
diff --git a/apps/dav/lib/Migration/Version1018Date20210312100735.php b/apps/dav/lib/Migration/Version1018Date20210312100735.php
new file mode 100644
index 00000000000..0bf160fb517
--- /dev/null
+++ b/apps/dav/lib/Migration/Version1018Date20210312100735.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DAV\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version1018Date20210312100735 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $calendarsTable = $schema->getTable('calendars');
+ $calendarsTable->addColumn('deleted_at', Types::INTEGER, [
+ 'notnull' => false,
+ 'length' => 4,
+ 'unsigned' => true,
+ ]);
+ $calendarsTable->addIndex([
+ 'principaluri',
+ 'deleted_at',
+ ], 'cals_princ_del_idx');
+
+ $calendarObjectsTable = $schema->getTable('calendarobjects');
+ $calendarObjectsTable->addColumn('deleted_at', Types::INTEGER, [
+ 'notnull' => false,
+ 'length' => 4,
+ 'unsigned' => true,
+ ]);
+
+ return $schema;
+ }
+}
diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php
index 16a209a98f0..dcc22566809 100644
--- a/apps/dav/lib/RootCollection.php
+++ b/apps/dav/lib/RootCollection.php
@@ -47,6 +47,7 @@ use OCA\DAV\Upload\CleanupService;
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
use Sabre\DAV\SimpleCollection;
class RootCollection extends SimpleCollection {
@@ -62,6 +63,7 @@ class RootCollection extends SimpleCollection {
$db = \OC::$server->getDatabaseConnection();
$dispatcher = \OC::$server->get(IEventDispatcher::class);
$legacyDispatcher = \OC::$server->getEventDispatcher();
+ $config = \OC::$server->get(IConfig::class);
$proxyMapper = \OC::$server->query(ProxyMapper::class);
$userPrincipalBackend = new Principal(
@@ -95,15 +97,23 @@ class RootCollection extends SimpleCollection {
$filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users');
$filesCollection->disableListing = $disableListing;
- $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher, $legacyDispatcher);
+ $caldavBackend = new CalDavBackend(
+ $db,
+ $userPrincipalBackend,
+ $userManager,
+ $groupManager,
+ $random,
+ $logger,
+ $dispatcher,
+ $legacyDispatcher,
+ $config
+ );
$userCalendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users');
$userCalendarRoot->disableListing = $disableListing;
- $resourceCalendarCaldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher, $legacyDispatcher);
$resourceCalendarRoot = new CalendarRoot($calendarResourcePrincipalBackend, $caldavBackend, 'principals/calendar-resources');
$resourceCalendarRoot->disableListing = $disableListing;
- $roomCalendarCaldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher, $legacyDispatcher);
- $roomCalendarRoot = new CalendarRoot($calendarRoomPrincipalBackend, $roomCalendarCaldavBackend, 'principals/calendar-rooms');
+ $roomCalendarRoot = new CalendarRoot($calendarRoomPrincipalBackend, $caldavBackend, 'principals/calendar-rooms');
$roomCalendarRoot->disableListing = $disableListing;
$publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config);
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index ba5bcf935e1..0999da3dbe1 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -165,7 +165,8 @@ class Server {
$this->server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
}
- $this->server->addPlugin(new CalDAV\WebcalCaching\Plugin($request));
+ $this->server->addPlugin(new \OCA\DAV\CalDAV\Trashbin\Plugin($request));
+ $this->server->addPlugin(new \OCA\DAV\CalDAV\WebcalCaching\Plugin($request));
$this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
diff --git a/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php b/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php
index 0bd450467a6..0216ddd1ae9 100644
--- a/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php
+++ b/apps/dav/lib/SystemTag/SystemTagsObjectMappingCollection.php
@@ -115,20 +115,20 @@ class SystemTagsObjectMappingCollection implements ICollection {
throw new Forbidden('Permission denied to create collections');
}
- public function getChild($tagId) {
+ public function getChild($tagName) {
try {
- if ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagId, true)) {
- $tag = $this->tagManager->getTagsByIds([$tagId]);
+ if ($this->tagMapper->haveTag([$this->objectId], $this->objectType, $tagName, true)) {
+ $tag = $this->tagManager->getTagsByIds([$tagName]);
$tag = current($tag);
if ($this->tagManager->canUserSeeTag($tag, $this->user)) {
return $this->makeNode($tag);
}
}
- throw new NotFound('Tag with id ' . $tagId . ' not present for object ' . $this->objectId);
+ throw new NotFound('Tag with id ' . $tagName . ' not present for object ' . $this->objectId);
} catch (\InvalidArgumentException $e) {
throw new BadRequest('Invalid tag id', 0, $e);
} catch (TagNotFoundException $e) {
- throw new NotFound('Tag with id ' . $tagId . ' not found', 0, $e);
+ throw new NotFound('Tag with id ' . $tagName . ' not found', 0, $e);
}
}
diff --git a/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php b/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php
index 683162cdc91..8f663cbe8f8 100644
--- a/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php
+++ b/apps/dav/lib/SystemTag/SystemTagsObjectTypeCollection.php
@@ -115,17 +115,18 @@ class SystemTagsObjectTypeCollection implements ICollection {
}
/**
- * @param string $objectId
+ * @param string $objectName
+ *
* @return SystemTagsObjectMappingCollection
* @throws NotFound
*/
- public function getChild($objectId) {
+ public function getChild($objectName) {
// make sure the object exists and is reachable
- if (!$this->childExists($objectId)) {
+ if (!$this->childExists($objectName)) {
throw new NotFound('Entity does not exist or is not available');
}
return new SystemTagsObjectMappingCollection(
- $objectId,
+ $objectName,
$this->objectType,
$this->userSession->getUser(),
$this->tagManager,
diff --git a/apps/dav/tests/travis/caldav/install.sh b/apps/dav/tests/travis/caldav/install.sh
index e0ac30c9e46..d6064fb7a38 100644
--- a/apps/dav/tests/travis/caldav/install.sh
+++ b/apps/dav/tests/travis/caldav/install.sh
@@ -11,8 +11,12 @@ if [ ! -f pycalendar/setup.py ]; then
git clone https://github.com/apple/ccs-pycalendar.git pycalendar
fi
-# create test user
cd "$SCRIPTPATH/../../../../../"
+
+# disable the trashbin, so recurrent deletion of the same object works
+php occ config:app:set dav calendarRetentionObligation --value=0
+
+# create test user
OC_PASS=user01 php occ user:add --password-from-env user01
php occ dav:create-calendar user01 calendar
php occ dav:create-calendar user01 shared
diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
index 1264342e27c..eb0530a8b3d 100644
--- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
+++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php
@@ -113,7 +113,18 @@ abstract class AbstractCalDavBackend extends TestCase {
$db = \OC::$server->getDatabaseConnection();
$this->random = \OC::$server->getSecureRandom();
$this->logger = $this->createMock(ILogger::class);
- $this->backend = new CalDavBackend($db, $this->principal, $this->userManager, $this->groupManager, $this->random, $this->logger, $this->dispatcher, $this->legacyDispatcher);
+ $this->config = $this->createMock(IConfig::class);
+ $this->backend = new CalDavBackend(
+ $db,
+ $this->principal,
+ $this->userManager,
+ $this->groupManager,
+ $this->random,
+ $this->logger,
+ $this->dispatcher,
+ $this->legacyDispatcher,
+ $this->config
+ );
$this->cleanUpBackend();
}
@@ -142,7 +153,7 @@ abstract class AbstractCalDavBackend extends TestCase {
return $event instanceof CalendarDeletedEvent;
}));
foreach ($calendars as $calendar) {
- $this->backend->deleteCalendar($calendar['id']);
+ $this->backend->deleteCalendar($calendar['id'], true);
}
$subscriptions = $this->backend->getSubscriptionsForUser($principal);
foreach ($subscriptions as $subscription) {
diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
index 2ac333b1526..03abd554e2b 100644
--- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
+++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
@@ -83,7 +83,7 @@ class CalDavBackendTest extends AbstractCalDavBackend {
->with(self::callback(function ($event) {
return $event instanceof CalendarDeletedEvent;
}));
- $this->backend->deleteCalendar($calendars[0]['id']);
+ $this->backend->deleteCalendar($calendars[0]['id'], true);
$calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
self::assertEmpty($calendars);
}
@@ -212,7 +212,7 @@ EOD;
->with(self::callback(function ($event) {
return $event instanceof CalendarDeletedEvent;
}));
- $this->backend->deleteCalendar($calendars[0]['id']);
+ $this->backend->deleteCalendar($calendars[0]['id'], true);
$calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
self::assertEmpty($calendars);
}
diff --git a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php
index a1df2d6254f..c9ef312df03 100644
--- a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php
+++ b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php
@@ -32,6 +32,7 @@ use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
use OCA\DAV\CalDAV\Outbox;
+use OCA\DAV\CalDAV\Trashbin\TrashbinHome;
use Sabre\CalDAV\Schedule\Inbox;
use Sabre\DAV\MkCol;
use Test\TestCase;
@@ -141,13 +142,14 @@ class CalendarHomeTest extends TestCase {
$actual = $this->calendarHome->getChildren();
- $this->assertCount(6, $actual);
+ $this->assertCount(7, $actual);
$this->assertInstanceOf(Inbox::class, $actual[0]);
$this->assertInstanceOf(Outbox::class, $actual[1]);
- $this->assertEquals('plugin1calendar1', $actual[2]);
- $this->assertEquals('plugin1calendar2', $actual[3]);
- $this->assertEquals('plugin2calendar1', $actual[4]);
- $this->assertEquals('plugin2calendar2', $actual[5]);
+ $this->assertInstanceOf(TrashbinHome::class, $actual[2]);
+ $this->assertEquals('plugin1calendar1', $actual[3]);
+ $this->assertEquals('plugin1calendar2', $actual[4]);
+ $this->assertEquals('plugin2calendar1', $actual[5]);
+ $this->assertEquals('plugin2calendar2', $actual[6]);
}
public function testGetChildNonAppGenerated():void {
diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php
index 5200d201f50..94e224e5952 100644
--- a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php
+++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php
@@ -86,6 +86,7 @@ class PublicCalendarRootTest extends TestCase {
$this->logger = $this->createMock(ILogger::class);
$dispatcher = $this->createMock(IEventDispatcher::class);
$legacyDispatcher = $this->createMock(EventDispatcherInterface::class);
+ $config = $this->createMock(IConfig::class);
$this->principal->expects($this->any())->method('getGroupMembership')
->withAnyParameters()
@@ -103,7 +104,8 @@ class PublicCalendarRootTest extends TestCase {
$this->random,
$this->logger,
$dispatcher,
- $legacyDispatcher
+ $legacyDispatcher,
+ $config
);
$this->l10n = $this->getMockBuilder(IL10N::class)
->disableOriginalConstructor()->getMock();
@@ -129,7 +131,7 @@ class PublicCalendarRootTest extends TestCase {
$books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
foreach ($books as $book) {
- $this->backend->deleteCalendar($book['id']);
+ $this->backend->deleteCalendar($book['id'], true);
}
}
diff --git a/build/integration/features/bootstrap/CalDavContext.php b/build/integration/features/bootstrap/CalDavContext.php
index b1981568e44..e01621359f1 100644
--- a/build/integration/features/bootstrap/CalDavContext.php
+++ b/build/integration/features/bootstrap/CalDavContext.php
@@ -70,6 +70,9 @@ class CalDavContext implements \Behat\Behat\Context\Context {
'admin',
'admin',
],
+ 'headers' => [
+ 'X-NC-CalDAV-No-Trashbin' => '1',
+ ]
]
);
} catch (\GuzzleHttp\Exception\ClientException $e) {
diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 225d191f640..5971cc18c35 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -163,11 +163,14 @@
<file src="apps/dav/lib/CalDAV/CalDavBackend.php">
<InvalidArgument occurrences="8">
<code>'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject'</code>
+
<code>'\OCA\DAV\CalDAV\CalDavBackend::createSubscription'</code>
<code>'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject'</code>
+
<code>'\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription'</code>
<code>'\OCA\DAV\CalDAV\CalDavBackend::publishCalendar'</code>
<code>'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject'</code>
+
<code>'\OCA\DAV\CalDAV\CalDavBackend::updateShares'</code>
<code>'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription'</code>
</InvalidArgument>
@@ -186,7 +189,7 @@
<RedundantCast occurrences="1">
<code>(int)$calendarId</code>
</RedundantCast>
- <TooManyArguments occurrences="9">
+ <TooManyArguments occurrences="13">
<code>dispatch</code>
<code>dispatch</code>
<code>dispatch</code>
@@ -200,7 +203,7 @@
<code>dispatch</code>
<code>dispatch</code>
<code>dispatch</code>
- <code>purgeProperties</code>
+
</TooManyArguments>
<UndefinedFunction occurrences="4">
<code>Uri\split($principalUri)</code>
@@ -210,16 +213,13 @@
</UndefinedFunction>
</file>
<file src="apps/dav/lib/CalDAV/CalendarHome.php">
- <InvalidReturnStatement occurrences="5">
- <code>$calendarPlugin-&gt;getCalendarInCalendarHome($this-&gt;principalInfo['uri'], $calendarUri)</code>
- <code>new Inbox($this-&gt;caldavBackend, $this-&gt;principalInfo['uri'])</code>
- <code>new Outbox($this-&gt;config, $this-&gt;principalInfo['uri'])</code>
- <code>new Subscription($this-&gt;caldavBackend, $subscription)</code>
- <code>new \Sabre\CalDAV\Notifications\Collection($this-&gt;caldavBackend, $this-&gt;principalInfo['uri'])</code>
- </InvalidReturnStatement>
- <InvalidReturnType occurrences="1">
- <code>getChild</code>
- </InvalidReturnType>
+ <InvalidNullableReturnType occurrences="1">
+ <code>INode</code>
+ </InvalidNullableReturnType>
+ <LessSpecificImplementedReturnType occurrences="1">
+ <code>INode</code>
+ </LessSpecificImplementedReturnType>
+
<NullableReturnStatement occurrences="1">
<code>$calendarPlugin-&gt;getCalendarInCalendarHome($this-&gt;principalInfo['uri'], $calendarUri)</code>
</NullableReturnStatement>
@@ -5095,4 +5095,4 @@
<code>$e-&gt;getCode()</code>
</InvalidScalarArgument>
</file>
-</files>
+</files> \ No newline at end of file