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:
authorJasper Weyne <jasperweyne@gmail.com>2022-08-11 09:54:08 +0300
committerGitHub <noreply@github.com>2022-08-11 09:54:08 +0300
commit44f6c931e7c9c74ea4f448d3cdfbaa89f3b7c379 (patch)
tree710a8c1bd1c20c685991de146aa9ef149ec1de7a /apps/dav/lib
parent0633a1d9f5a7ef06d577ae6556d09db9e94f5684 (diff)
parenta61331f4560468e6d433cf32e008b157b06e7ea9 (diff)
Merge branch 'master' into patch-2
Diffstat (limited to 'apps/dav/lib')
-rw-r--r--apps/dav/lib/AppInfo/Application.php138
-rw-r--r--apps/dav/lib/BackgroundJob/UploadCleanup.php21
-rw-r--r--apps/dav/lib/BackgroundJob/UserStatusAutomation.php188
-rw-r--r--apps/dav/lib/CalDAV/Activity/Backend.php116
-rw-r--r--apps/dav/lib/CalDAV/Activity/Filter/Todo.php2
-rw-r--r--apps/dav/lib/CalDAV/Activity/Provider/Event.php23
-rw-r--r--apps/dav/lib/CalDAV/Activity/Provider/Todo.php42
-rw-r--r--apps/dav/lib/CalDAV/Activity/Setting/Todo.php2
-rw-r--r--apps/dav/lib/CalDAV/BirthdayService.php74
-rw-r--r--apps/dav/lib/CalDAV/CalDavBackend.php197
-rw-r--r--apps/dav/lib/CalDAV/Calendar.php73
-rw-r--r--apps/dav/lib/CalDAV/CalendarImpl.php2
-rw-r--r--apps/dav/lib/CalDAV/CalendarObject.php7
-rw-r--r--apps/dav/lib/CalDAV/PublicCalendar.php2
-rw-r--r--apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php2
-rw-r--r--apps/dav/lib/CalDAV/Schedule/Plugin.php48
-rw-r--r--apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php50
-rw-r--r--apps/dav/lib/CardDAV/Activity/Backend.php31
-rw-r--r--apps/dav/lib/CardDAV/AddressBook.php24
-rw-r--r--apps/dav/lib/CardDAV/AddressBookImpl.php3
-rw-r--r--apps/dav/lib/CardDAV/CardDavBackend.php129
-rw-r--r--apps/dav/lib/CardDAV/SyncService.php88
-rw-r--r--apps/dav/lib/Command/CreateCalendar.php4
-rw-r--r--apps/dav/lib/Command/MoveCalendar.php44
-rw-r--r--apps/dav/lib/Connector/Sabre/ChecksumList.php2
-rw-r--r--apps/dav/lib/Connector/Sabre/Directory.php4
-rw-r--r--apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php11
-rw-r--r--apps/dav/lib/Connector/Sabre/File.php2
-rw-r--r--apps/dav/lib/Connector/Sabre/FilesPlugin.php6
-rw-r--r--apps/dav/lib/Connector/Sabre/MaintenancePlugin.php5
-rw-r--r--apps/dav/lib/Connector/Sabre/Node.php26
-rw-r--r--apps/dav/lib/Connector/Sabre/Principal.php16
-rw-r--r--apps/dav/lib/Connector/Sabre/ServerFactory.php6
-rw-r--r--apps/dav/lib/Connector/Sabre/ShareTypeList.php2
-rw-r--r--apps/dav/lib/Connector/Sabre/ShareeList.php2
-rw-r--r--apps/dav/lib/Connector/Sabre/TagList.php2
-rw-r--r--apps/dav/lib/Controller/DirectController.php17
-rw-r--r--apps/dav/lib/DAV/CustomPropertiesBackend.php186
-rw-r--r--apps/dav/lib/DAV/Sharing/Backend.php80
-rw-r--r--apps/dav/lib/DAV/Sharing/IShareable.php20
-rw-r--r--apps/dav/lib/DAV/Sharing/Xml/Invite.php2
-rw-r--r--apps/dav/lib/DAV/SystemPrincipalBackend.php2
-rw-r--r--apps/dav/lib/DAV/ViewOnlyPlugin.php108
-rw-r--r--apps/dav/lib/Direct/DirectHome.php2
-rw-r--r--apps/dav/lib/Events/CalendarObjectMovedEvent.php120
-rw-r--r--apps/dav/lib/Events/CalendarPublishedEvent.php13
-rw-r--r--apps/dav/lib/Events/CalendarShareUpdatedEvent.php39
-rw-r--r--apps/dav/lib/Events/CalendarUnpublishedEvent.php9
-rw-r--r--apps/dav/lib/Exception/ServerMaintenanceMode.php31
-rw-r--r--apps/dav/lib/Listener/ActivityUpdaterListener.php22
-rw-r--r--apps/dav/lib/Listener/BirthdayListener.php54
-rw-r--r--apps/dav/lib/Listener/CalendarPublicationListener.php65
-rw-r--r--apps/dav/lib/Listener/CalendarShareUpdateListener.php62
-rw-r--r--apps/dav/lib/Listener/ClearPhotoCacheListener.php48
-rw-r--r--apps/dav/lib/Listener/SubscriptionListener.php85
-rw-r--r--apps/dav/lib/Listener/TrustedServerRemovedListener.php50
-rw-r--r--apps/dav/lib/Listener/UserPreferenceListener.php59
-rw-r--r--apps/dav/lib/Migration/RemoveObjectProperties.php65
-rw-r--r--apps/dav/lib/Migration/Version1024Date20211221144219.php58
-rw-r--r--apps/dav/lib/RootCollection.php9
-rw-r--r--apps/dav/lib/Server.php6
-rw-r--r--apps/dav/lib/Settings/AvailabilitySettings.php24
-rw-r--r--apps/dav/lib/UserMigration/ContactsMigrator.php4
63 files changed, 1864 insertions, 770 deletions
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index 580918a6450..86749862626 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/apps/dav/lib/AppInfo/Application.php
@@ -35,18 +35,14 @@ namespace OCA\DAV\AppInfo;
use Exception;
use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob;
use OCA\DAV\CalDAV\Activity\Backend;
-use OCA\DAV\CalDAV\BirthdayService;
-use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\CalendarManager;
use OCA\DAV\CalDAV\CalendarProvider;
-use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
use OCA\DAV\CalDAV\Reminder\NotificationProviderManager;
use OCA\DAV\CalDAV\Reminder\Notifier;
-use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService;
use OCA\DAV\Capabilities;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\ContactsManager;
@@ -61,22 +57,35 @@ use OCA\DAV\Events\CalendarDeletedEvent;
use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedEvent;
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;
use OCA\DAV\Events\CardCreatedEvent;
use OCA\DAV\Events\CardDeletedEvent;
use OCA\DAV\Events\CardUpdatedEvent;
+use OCA\DAV\Events\SubscriptionCreatedEvent;
+use OCA\DAV\Events\SubscriptionDeletedEvent;
+use OCP\Federation\Events\TrustedServerRemovedEvent;
use OCA\DAV\HookManager;
use OCA\DAV\Listener\ActivityUpdaterListener;
use OCA\DAV\Listener\AddressbookListener;
+use OCA\DAV\Listener\BirthdayListener;
use OCA\DAV\Listener\CalendarContactInteractionListener;
use OCA\DAV\Listener\CalendarDeletionDefaultUpdaterListener;
use OCA\DAV\Listener\CalendarObjectReminderUpdaterListener;
+use OCA\DAV\Listener\CalendarPublicationListener;
+use OCA\DAV\Listener\CalendarShareUpdateListener;
use OCA\DAV\Listener\CardListener;
+use OCA\DAV\Listener\ClearPhotoCacheListener;
+use OCA\DAV\Listener\SubscriptionListener;
+use OCA\DAV\Listener\TrustedServerRemovedListener;
+use OCA\DAV\Listener\UserPreferenceListener;
use OCA\DAV\Search\ContactsSearchProvider;
use OCA\DAV\Search\EventsSearchProvider;
use OCA\DAV\Search\TasksSearchProvider;
@@ -88,6 +97,8 @@ use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\IAppContainer;
use OCP\Calendar\IManager as ICalendarManager;
+use OCP\Config\BeforePreferenceDeletedEvent;
+use OCP\Config\BeforePreferenceSetEvent;
use OCP\Contacts\IManager as IContactsManager;
use OCP\IServerContainer;
use OCP\IUser;
@@ -149,11 +160,19 @@ 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(CalendarObjectMovedEvent::class, ActivityUpdaterListener::class);
+ $context->registerEventListener(CalendarObjectMovedEvent::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);
+ $context->registerEventListener(CalendarPublishedEvent::class, CalendarPublicationListener::class);
+ $context->registerEventListener(CalendarUnpublishedEvent::class, CalendarPublicationListener::class);
+ $context->registerEventListener(CalendarShareUpdatedEvent::class, CalendarShareUpdateListener::class);
+
+ $context->registerEventListener(SubscriptionCreatedEvent::class, SubscriptionListener::class);
+ $context->registerEventListener(SubscriptionDeletedEvent::class, SubscriptionListener::class);
$context->registerEventListener(AddressBookCreatedEvent::class, AddressbookListener::class);
@@ -163,6 +182,15 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(CardCreatedEvent::class, CardListener::class);
$context->registerEventListener(CardDeletedEvent::class, CardListener::class);
$context->registerEventListener(CardUpdatedEvent::class, CardListener::class);
+ $context->registerEventListener(CardCreatedEvent::class, BirthdayListener::class);
+ $context->registerEventListener(CardDeletedEvent::class, BirthdayListener::class);
+ $context->registerEventListener(CardUpdatedEvent::class, BirthdayListener::class);
+ $context->registerEventListener(CardDeletedEvent::class, ClearPhotoCacheListener::class);
+ $context->registerEventListener(CardUpdatedEvent::class, ClearPhotoCacheListener::class);
+ $context->registerEventListener(TrustedServerRemovedEvent::class, TrustedServerRemovedListener::class);
+
+ $context->registerEventListener(BeforePreferenceDeletedEvent::class, UserPreferenceListener::class);
+ $context->registerEventListener(BeforePreferenceSetEvent::class, UserPreferenceListener::class);
$context->registerNotifierService(Notifier::class);
@@ -195,44 +223,6 @@ class Application extends App implements IBootstrap {
}
});
- $birthdayListener = function ($event) use ($container): void {
- if ($event instanceof GenericEvent) {
- /** @var BirthdayService $b */
- $b = $container->query(BirthdayService::class);
- $b->onCardChanged(
- (int) $event->getArgument('addressBookId'),
- (string) $event->getArgument('cardUri'),
- (string) $event->getArgument('cardData')
- );
- }
- };
-
- $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $birthdayListener);
- $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $birthdayListener);
- $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function ($event) use ($container) {
- if ($event instanceof GenericEvent) {
- /** @var BirthdayService $b */
- $b = $container->query(BirthdayService::class);
- $b->onCardDeleted(
- (int) $event->getArgument('addressBookId'),
- (string) $event->getArgument('cardUri')
- );
- }
- });
-
- $clearPhotoCache = function ($event) use ($container): void {
- if ($event instanceof GenericEvent) {
- /** @var PhotoCache $p */
- $p = $container->query(PhotoCache::class);
- $p->delete(
- $event->getArgument('addressBookId'),
- $event->getArgument('cardUri')
- );
- }
- };
- $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $clearPhotoCache);
- $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', $clearPhotoCache);
-
$dispatcher->addListener('OC\AccountManager::userUpdated', function (GenericEvent $event) use ($container) {
$user = $event->getSubject();
/** @var SyncService $syncService */
@@ -254,70 +244,6 @@ class Application extends App implements IBootstrap {
// Here we should recalculate if reminders should be sent to new or old sharees
});
- $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function (GenericEvent $event) use ($container) {
- /** @var Backend $backend */
- $backend = $container->query(Backend::class);
- $backend->onCalendarPublication(
- $event->getArgument('calendarData'),
- $event->getArgument('public')
- );
- });
-
-
- $dispatcher->addListener('OCP\Federation\TrustedServerEvent::remove',
- function (GenericEvent $event) {
- /** @var CardDavBackend $cardDavBackend */
- $cardDavBackend = \OC::$server->query(CardDavBackend::class);
- $addressBookUri = $event->getSubject();
- $addressBook = $cardDavBackend->getAddressBooksByUri('principals/system/system', $addressBookUri);
- if (!is_null($addressBook)) {
- $cardDavBackend->deleteAddressBook($addressBook['id']);
- }
- }
- );
-
- $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
- function (GenericEvent $event) use ($container, $serverContainer) {
- $jobList = $serverContainer->getJobList();
- $subscriptionData = $event->getArgument('subscriptionData');
-
- /**
- * Initial subscription refetch
- *
- * @var RefreshWebcalService $refreshWebcalService
- */
- $refreshWebcalService = $container->query(RefreshWebcalService::class);
- $refreshWebcalService->refreshSubscription(
- (string) $subscriptionData['principaluri'],
- (string) $subscriptionData['uri']
- );
-
- $jobList->add(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
- 'principaluri' => $subscriptionData['principaluri'],
- 'uri' => $subscriptionData['uri']
- ]);
- }
- );
-
- $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
- function (GenericEvent $event) use ($container, $serverContainer) {
- $jobList = $serverContainer->getJobList();
- $subscriptionData = $event->getArgument('subscriptionData');
-
- $jobList->remove(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
- 'principaluri' => $subscriptionData['principaluri'],
- 'uri' => $subscriptionData['uri']
- ]);
-
- /** @var CalDavBackend $calDavBackend */
- $calDavBackend = $container->get(CalDavBackend::class);
- $calDavBackend->purgeAllCachedEventsForSubscription($subscriptionData['id']);
- /** @var ReminderBackend $calDavBackend */
- $reminderBackend = $container->get(ReminderBackend::class);
- $reminderBackend->cleanRemindersForCalendar((int) $subscriptionData['id']);
- }
- );
-
$eventHandler = function () use ($container, $serverContainer): void {
try {
/** @var UpdateCalendarResourcesRoomsBackgroundJob $job */
diff --git a/apps/dav/lib/BackgroundJob/UploadCleanup.php b/apps/dav/lib/BackgroundJob/UploadCleanup.php
index f612f58cd7c..3a6f296deaf 100644
--- a/apps/dav/lib/BackgroundJob/UploadCleanup.php
+++ b/apps/dav/lib/BackgroundJob/UploadCleanup.php
@@ -37,15 +37,18 @@ use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
+use Psr\Log\LoggerInterface;
class UploadCleanup extends TimedJob {
private IRootFolder $rootFolder;
private IJobList $jobList;
+ private LoggerInterface $logger;
- public function __construct(ITimeFactory $time, IRootFolder $rootFolder, IJobList $jobList) {
+ public function __construct(ITimeFactory $time, IRootFolder $rootFolder, IJobList $jobList, LoggerInterface $logger) {
parent::__construct($time);
$this->rootFolder = $rootFolder;
$this->jobList = $jobList;
+ $this->logger = $logger;
// Run once a day
$this->setInterval(60 * 60 * 24);
@@ -61,19 +64,27 @@ class UploadCleanup extends TimedJob {
$userRoot = $userFolder->getParent();
/** @var Folder $uploads */
$uploads = $userRoot->get('uploads');
- /** @var Folder $uploadFolder */
$uploadFolder = $uploads->get($folder);
} catch (NotFoundException | NoUserException $e) {
$this->jobList->remove(self::class, $argument);
return;
}
- /** @var File[] $files */
- $files = $uploadFolder->getDirectoryListing();
-
// Remove if all files have an mtime of more than a day
$time = $this->time->getTime() - 60 * 60 * 24;
+ if (!($uploadFolder instanceof Folder)) {
+ $this->logger->error("Found a file inside the uploads folder. Uid: " . $uid . ' folder: ' . $folder);
+ if ($uploadFolder->getMTime() < $time) {
+ $uploadFolder->delete();
+ }
+ $this->jobList->remove(self::class, $argument);
+ return;
+ }
+
+ /** @var File[] $files */
+ $files = $uploadFolder->getDirectoryListing();
+
// The folder has to be more than a day old
$initial = $uploadFolder->getMTime() < $time;
diff --git a/apps/dav/lib/BackgroundJob/UserStatusAutomation.php b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
new file mode 100644
index 00000000000..bbd92d903ee
--- /dev/null
+++ b/apps/dav/lib/BackgroundJob/UserStatusAutomation.php
@@ -0,0 +1,188 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Schedule\Plugin;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\BackgroundJob\TimedJob;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\UserStatus\IManager;
+use OCP\UserStatus\IUserStatus;
+use Psr\Log\LoggerInterface;
+use Sabre\VObject\Component\Available;
+use Sabre\VObject\Component\VAvailability;
+use Sabre\VObject\Reader;
+use Sabre\VObject\Recur\RRuleIterator;
+
+class UserStatusAutomation extends TimedJob {
+ protected IDBConnection $connection;
+ protected IJobList $jobList;
+ protected LoggerInterface $logger;
+ protected IManager $manager;
+ protected IConfig $config;
+
+ public function __construct(ITimeFactory $timeFactory,
+ IDBConnection $connection,
+ IJobList $jobList,
+ LoggerInterface $logger,
+ IManager $manager,
+ IConfig $config) {
+ parent::__construct($timeFactory);
+ $this->connection = $connection;
+ $this->jobList = $jobList;
+ $this->logger = $logger;
+ $this->manager = $manager;
+ $this->config = $config;
+
+ // Interval 0 might look weird, but the last_checked is always moved
+ // to the next time we need this and then it's 0 seconds ago.
+ $this->setInterval(0);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function run($argument) {
+ if (!isset($argument['userId'])) {
+ $this->jobList->remove(self::class, $argument);
+ $this->logger->info('Removing invalid ' . self::class . ' background job');
+ return;
+ }
+
+ $userId = $argument['userId'];
+ $automationEnabled = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes';
+ if (!$automationEnabled) {
+ $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the setting is disabled');
+ $this->jobList->remove(self::class, $argument);
+ return;
+ }
+
+ $property = $this->getAvailabilityFromPropertiesTable($userId);
+
+ if (!$property) {
+ $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no availability settings');
+ $this->jobList->remove(self::class, $argument);
+ return;
+ }
+
+ $isCurrentlyAvailable = false;
+ $nextPotentialToggles = [];
+
+ $now = new \DateTime('now');
+ $lastMidnight = (clone $now)->setTime(0, 0);
+
+ $vObject = Reader::read($property);
+ foreach ($vObject->getComponents() as $component) {
+ if ($component->name !== 'VAVAILABILITY') {
+ continue;
+ }
+ /** @var VAvailability $component */
+ $availables = $component->getComponents();
+ foreach ($availables as $available) {
+ /** @var Available $available */
+ if ($available->name === 'AVAILABLE') {
+ /** @var \DateTimeInterface $effectiveStart */
+ /** @var \DateTimeInterface $effectiveEnd */
+ [$effectiveStart, $effectiveEnd] = $available->getEffectiveStartEnd();
+
+ try {
+ $it = new RRuleIterator((string) $available->RRULE, $effectiveStart);
+ $it->fastForward($lastMidnight);
+
+ $startToday = $it->current();
+ if ($startToday && $startToday <= $now) {
+ $duration = $effectiveStart->diff($effectiveEnd);
+ $endToday = $startToday->add($duration);
+ if ($endToday > $now) {
+ // User is currently available
+ // Also queuing the end time as next status toggle
+ $isCurrentlyAvailable = true;
+ $nextPotentialToggles[] = $endToday->getTimestamp();
+ }
+
+ // Availability enabling already done for today,
+ // so jump to the next recurrence to find the next status toggle
+ $it->next();
+ }
+
+ if ($it->current()) {
+ $nextPotentialToggles[] = $it->current()->getTimestamp();
+ }
+ } catch (\Exception $e) {
+ $this->logger->error($e->getMessage(), ['exception' => $e]);
+ }
+ }
+ }
+ }
+
+ $nextAutomaticToggle = min($nextPotentialToggles);
+ $this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
+
+ if ($isCurrentlyAvailable) {
+ $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
+ } else {
+ // The DND status automation is more important than the "Away - In call" so we also restore that one if it exists.
+ $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
+ $this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
+ }
+ $this->logger->debug('User status automation ran');
+ }
+
+ protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void {
+ $query = $this->connection->getQueryBuilder();
+
+ $query->update('jobs')
+ ->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
+ ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
+ $query->executeStatement();
+
+ $this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId);
+ }
+
+ /**
+ * @param string $userId
+ * @return false|string
+ */
+ protected function getAvailabilityFromPropertiesTable(string $userId) {
+ $propertyPath = 'calendars/' . $userId . '/inbox';
+ $propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability';
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('propertyvalue')
+ ->from('properties')
+ ->where($query->expr()->eq('userid', $query->createNamedParameter($userId)))
+ ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath)))
+ ->where($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName)))
+ ->setMaxResults(1);
+
+ $result = $query->executeQuery();
+ $property = $result->fetchOne();
+ $result->closeCursor();
+
+ return $property;
+ }
+}
diff --git a/apps/dav/lib/CalDAV/Activity/Backend.php b/apps/dav/lib/CalDAV/Activity/Backend.php
index 84ba50b8c37..781a456b5b3 100644
--- a/apps/dav/lib/CalDAV/Activity/Backend.php
+++ b/apps/dav/lib/CalDAV/Activity/Backend.php
@@ -34,6 +34,7 @@ use OCP\App\IAppManager;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
+use OCP\IUserManager;
use OCP\IUserSession;
use Sabre\VObject\Reader;
@@ -56,11 +57,15 @@ class Backend {
/** @var IAppManager */
protected $appManager;
- public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession, IAppManager $appManager) {
+ /** @var IUserManager */
+ protected $userManager;
+
+ public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession, IAppManager $appManager, IUserManager $userManager) {
$this->activityManager = $activityManager;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
$this->appManager = $appManager;
+ $this->userManager = $userManager;
}
/**
@@ -119,7 +124,7 @@ class Backend {
* @param array $calendarData
* @param bool $publishStatus
*/
- public function onCalendarPublication(array $calendarData, $publishStatus) {
+ public function onCalendarPublication(array $calendarData, bool $publishStatus): void {
$this->triggerCalendarActivity($publishStatus ? Calendar::SUBJECT_PUBLISH : Calendar::SUBJECT_UNPUBLISH, $calendarData);
}
@@ -165,6 +170,11 @@ class Backend {
}
foreach ($users as $user) {
+ if ($action === Calendar::SUBJECT_DELETE && !$this->userManager->userExists($user)) {
+ // Avoid creating calendar_delete activities for deleted users
+ continue;
+ }
+
$event->setAffectedUser($user)
->setSubject(
$user === $currentUser ? $action . '_self' : $action,
@@ -438,6 +448,11 @@ class Backend {
$classification = $objectData['classification'] ?? CalDavBackend::CLASSIFICATION_PUBLIC;
$object = $this->getObjectNameAndType($objectData);
+
+ if (!$object) {
+ return;
+ }
+
$action = $action . '_' . $object['type'];
if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') {
@@ -494,8 +509,103 @@ class Backend {
}
/**
+ * Creates activities when a calendar object was moved
+ */
+ public function onMovedCalendarObject(array $sourceCalendarData, array $targetCalendarData, array $sourceShares, array $targetShares, array $objectData): void {
+ if (!isset($targetCalendarData['principaluri'])) {
+ return;
+ }
+
+ $sourcePrincipal = explode('/', $sourceCalendarData['principaluri']);
+ $sourceOwner = array_pop($sourcePrincipal);
+
+ $targetPrincipal = explode('/', $targetCalendarData['principaluri']);
+ $targetOwner = array_pop($targetPrincipal);
+
+ if ($sourceOwner !== $targetOwner) {
+ $this->onTouchCalendarObject(
+ Event::SUBJECT_OBJECT_DELETE,
+ $sourceCalendarData,
+ $sourceShares,
+ $objectData
+ );
+ $this->onTouchCalendarObject(
+ Event::SUBJECT_OBJECT_ADD,
+ $targetCalendarData,
+ $targetShares,
+ $objectData
+ );
+ return;
+ }
+
+ $currentUser = $this->userSession->getUser();
+ if ($currentUser instanceof IUser) {
+ $currentUser = $currentUser->getUID();
+ } else {
+ $currentUser = $targetOwner;
+ }
+
+ $classification = $objectData['classification'] ?? CalDavBackend::CLASSIFICATION_PUBLIC;
+ $object = $this->getObjectNameAndType($objectData);
+
+ if (!$object) {
+ return;
+ }
+
+ $event = $this->activityManager->generateEvent();
+ $event->setApp('dav')
+ ->setObject('calendar', (int) $targetCalendarData['id'])
+ ->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo')
+ ->setAuthor($currentUser);
+
+ $users = $this->getUsersForShares(array_intersect($sourceShares, $targetShares));
+ $users[] = $targetOwner;
+
+ // Users for share can return the owner itself if the calendar is published
+ foreach (array_unique($users) as $user) {
+ if ($classification === CalDavBackend::CLASSIFICATION_PRIVATE && $user !== $targetOwner) {
+ // Private events are only shown to the owner
+ continue;
+ }
+
+ $params = [
+ 'actor' => $event->getAuthor(),
+ 'sourceCalendar' => [
+ 'id' => (int) $sourceCalendarData['id'],
+ 'uri' => $sourceCalendarData['uri'],
+ 'name' => $sourceCalendarData['{DAV:}displayname'],
+ ],
+ 'targetCalendar' => [
+ 'id' => (int) $targetCalendarData['id'],
+ 'uri' => $targetCalendarData['uri'],
+ 'name' => $targetCalendarData['{DAV:}displayname'],
+ ],
+ 'object' => [
+ 'id' => $object['id'],
+ 'name' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $targetOwner ? 'Busy' : $object['name'],
+ 'classified' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $targetOwner,
+ ],
+ ];
+
+ if ($object['type'] === 'event' && $this->appManager->isEnabledForUser('calendar')) {
+ $params['object']['link']['object_uri'] = $objectData['uri'];
+ $params['object']['link']['calendar_uri'] = $targetCalendarData['uri'];
+ $params['object']['link']['owner'] = $targetOwner;
+ }
+
+ $event->setAffectedUser($user)
+ ->setSubject(
+ $user === $currentUser ? Event::SUBJECT_OBJECT_MOVE . '_' . $object['type'] . '_self' : Event::SUBJECT_OBJECT_MOVE . '_' . $object['type'],
+ $params
+ );
+
+ $this->activityManager->publish($event);
+ }
+ }
+
+ /**
* @param array $objectData
- * @return string[]|bool
+ * @return string[]|false
*/
protected function getObjectNameAndType(array $objectData) {
$vObject = Reader::read($objectData['calendardata']);
diff --git a/apps/dav/lib/CalDAV/Activity/Filter/Todo.php b/apps/dav/lib/CalDAV/Activity/Filter/Todo.php
index f727c10befe..6f1dbbdcb5c 100644
--- a/apps/dav/lib/CalDAV/Activity/Filter/Todo.php
+++ b/apps/dav/lib/CalDAV/Activity/Filter/Todo.php
@@ -52,7 +52,7 @@ class Todo implements IFilter {
* @since 11.0.0
*/
public function getName() {
- return $this->l->t('Todos');
+ return $this->l->t('To-dos');
}
/**
diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php
index 3ed591219af..9ae04aadbba 100644
--- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php
+++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php
@@ -40,6 +40,7 @@ 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 = 'object_move';
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';
@@ -145,6 +146,10 @@ 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 . '_event') {
+ $subject = $this->l->t('{actor} moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_MOVE . '_event_self') {
+ $subject = $this->l->t('You moved event {event} from calendar {sourceCalendar} to calendar {targetCalendar}');
} 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') {
@@ -198,6 +203,24 @@ class Event extends Base {
}
}
+ if (isset($parameters['sourceCalendar']) && isset($parameters['targetCalendar'])) {
+ switch ($subject) {
+ case self::SUBJECT_OBJECT_MOVE . '_event':
+ return [
+ 'actor' => $this->generateUserParameter($parameters['actor']),
+ 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l),
+ 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l),
+ 'event' => $this->generateClassifiedObjectParameter($parameters['object']),
+ ];
+ case self::SUBJECT_OBJECT_MOVE . '_event_self':
+ return [
+ 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l),
+ 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l),
+ 'event' => $this->generateClassifiedObjectParameter($parameters['object']),
+ ];
+ }
+ }
+
// Legacy - Do NOT Remove unless necessary
// Removing this will break parsing of activities that were created on
// Nextcloud 12, so we should keep this as long as it's acceptable.
diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Todo.php b/apps/dav/lib/CalDAV/Activity/Provider/Todo.php
index a3ab81e38ae..e2ddb99a1af 100644
--- a/apps/dav/lib/CalDAV/Activity/Provider/Todo.php
+++ b/apps/dav/lib/CalDAV/Activity/Provider/Todo.php
@@ -50,25 +50,29 @@ class Todo extends Event {
}
if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_todo') {
- $subject = $this->l->t('{actor} created todo {todo} in list {calendar}');
+ $subject = $this->l->t('{actor} created to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_todo_self') {
- $subject = $this->l->t('You created todo {todo} in list {calendar}');
+ $subject = $this->l->t('You created to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_todo') {
- $subject = $this->l->t('{actor} deleted todo {todo} from list {calendar}');
+ $subject = $this->l->t('{actor} deleted to-do {todo} from list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_todo_self') {
- $subject = $this->l->t('You deleted todo {todo} from list {calendar}');
+ $subject = $this->l->t('You deleted to-do {todo} from list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo') {
- $subject = $this->l->t('{actor} updated todo {todo} in list {calendar}');
+ $subject = $this->l->t('{actor} updated to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_self') {
- $subject = $this->l->t('You updated todo {todo} in list {calendar}');
+ $subject = $this->l->t('You updated to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_completed') {
- $subject = $this->l->t('{actor} solved todo {todo} in list {calendar}');
+ $subject = $this->l->t('{actor} solved to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self') {
- $subject = $this->l->t('You solved todo {todo} in list {calendar}');
+ $subject = $this->l->t('You solved to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action') {
- $subject = $this->l->t('{actor} reopened todo {todo} in list {calendar}');
+ $subject = $this->l->t('{actor} reopened to-do {todo} in list {calendar}');
} elseif ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self') {
- $subject = $this->l->t('You reopened todo {todo} in list {calendar}');
+ $subject = $this->l->t('You reopened to-do {todo} in list {calendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_MOVE . '_todo') {
+ $subject = $this->l->t('{actor} moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}');
+ } elseif ($event->getSubject() === self::SUBJECT_OBJECT_MOVE . '_todo_self') {
+ $subject = $this->l->t('You moved to-do {todo} from list {sourceCalendar} to list {targetCalendar}');
} else {
throw new \InvalidArgumentException();
}
@@ -114,6 +118,24 @@ class Todo extends Event {
}
}
+ if (isset($parameters['sourceCalendar']) && isset($parameters['targetCalendar'])) {
+ switch ($subject) {
+ case self::SUBJECT_OBJECT_MOVE . '_todo':
+ return [
+ 'actor' => $this->generateUserParameter($parameters['actor']),
+ 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l),
+ 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l),
+ 'todo' => $this->generateObjectParameter($parameters['object']),
+ ];
+ case self::SUBJECT_OBJECT_MOVE . '_todo_self':
+ return [
+ 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l),
+ 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l),
+ 'todo' => $this->generateObjectParameter($parameters['object']),
+ ];
+ }
+ }
+
// Legacy - Do NOT Remove unless necessary
// Removing this will break parsing of activities that were created on
// Nextcloud 12, so we should keep this as long as it's acceptable.
diff --git a/apps/dav/lib/CalDAV/Activity/Setting/Todo.php b/apps/dav/lib/CalDAV/Activity/Setting/Todo.php
index 7d27b30c4af..a4ac3f5d569 100644
--- a/apps/dav/lib/CalDAV/Activity/Setting/Todo.php
+++ b/apps/dav/lib/CalDAV/Activity/Setting/Todo.php
@@ -38,7 +38,7 @@ class Todo extends CalDAVSetting {
* @since 11.0.0
*/
public function getName() {
- return $this->l->t('A calendar <strong>todo</strong> was modified');
+ return $this->l->t('A calendar <strong>to-do</strong> was modified');
}
/**
diff --git a/apps/dav/lib/CalDAV/BirthdayService.php b/apps/dav/lib/CalDAV/BirthdayService.php
index 1030768e26f..be6b0911538 100644
--- a/apps/dav/lib/CalDAV/BirthdayService.php
+++ b/apps/dav/lib/CalDAV/BirthdayService.php
@@ -14,6 +14,7 @@ declare(strict_types=1);
* @author Sven Strickroth <email@cs-ware.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Valdnet <47037905+Valdnet@users.noreply.github.com>
+ * @author Cédric Neukom <github@webguy.ch>
*
* @license AGPL-3.0
*
@@ -86,6 +87,9 @@ class BirthdayService {
$targetPrincipals = $this->getAllAffectedPrincipals($addressBookId);
$book = $this->cardDavBackEnd->getAddressBookById($addressBookId);
+ if ($book === null) {
+ return;
+ }
$targetPrincipals[] = $book['principaluri'];
$datesToSync = [
['postfix' => '', 'field' => 'BDAY'],
@@ -98,9 +102,14 @@ class BirthdayService {
continue;
}
+ $reminderOffset = $this->getReminderOffsetForUser($principalUri);
+
$calendar = $this->ensureCalendarExists($principalUri);
+ if ($calendar === null) {
+ return;
+ }
foreach ($datesToSync as $type) {
- $this->updateCalendar($cardUri, $cardData, $book, (int) $calendar['id'], $type);
+ $this->updateCalendar($cardUri, $cardData, $book, (int) $calendar['id'], $type, $reminderOffset);
}
}
}
@@ -148,12 +157,14 @@ class BirthdayService {
* @param $cardData
* @param $dateField
* @param $postfix
+ * @param $reminderOffset
* @return VCalendar|null
* @throws InvalidDataException
*/
- public function buildDateFromContact(string $cardData,
- string $dateField,
- string $postfix):?VCalendar {
+ public function buildDateFromContact(string $cardData,
+ string $dateField,
+ string $postfix,
+ ?string $reminderOffset):?VCalendar {
if (empty($cardData)) {
return null;
}
@@ -258,11 +269,13 @@ class BirthdayService {
if ($originalYear !== null) {
$vEvent->{'X-NEXTCLOUD-BC-YEAR'} = (string) $originalYear;
}
- $alarm = $vCal->createComponent('VALARM');
- $alarm->add($vCal->createProperty('TRIGGER', '-PT0M', ['VALUE' => 'DURATION']));
- $alarm->add($vCal->createProperty('ACTION', 'DISPLAY'));
- $alarm->add($vCal->createProperty('DESCRIPTION', $vEvent->{'SUMMARY'}));
- $vEvent->add($alarm);
+ if ($reminderOffset) {
+ $alarm = $vCal->createComponent('VALARM');
+ $alarm->add($vCal->createProperty('TRIGGER', $reminderOffset, ['VALUE' => 'DURATION']));
+ $alarm->add($vCal->createProperty('ACTION', 'DISPLAY'));
+ $alarm->add($vCal->createProperty('DESCRIPTION', $vEvent->{'SUMMARY'}));
+ $vEvent->add($alarm);
+ }
$vCal->add($vEvent);
return $vCal;
}
@@ -273,6 +286,9 @@ class BirthdayService {
public function resetForUser(string $user):void {
$principal = 'principals/users/'.$user;
$calendar = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI);
+ if (!$calendar) {
+ return; // The user's birthday calendar doesn't exist, no need to purge it
+ }
$calendarObjects = $this->calDavBackEnd->getCalendarObjects($calendar['id'], CalDavBackend::CALENDAR_TYPE_CALENDAR);
foreach ($calendarObjects as $calendarObject) {
@@ -341,6 +357,7 @@ class BirthdayService {
* @param array $book
* @param int $calendarId
* @param array $type
+ * @param string $reminderOffset
* @throws InvalidDataException
* @throws \Sabre\DAV\Exception\BadRequest
*/
@@ -348,9 +365,10 @@ class BirthdayService {
string $cardData,
array $book,
int $calendarId,
- array $type):void {
+ array $type,
+ ?string $reminderOffset):void {
$objectUri = $book['uri'] . '-' . $cardUri . $type['postfix'] . '.ics';
- $calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['postfix']);
+ $calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['postfix'], $reminderOffset);
$existing = $this->calDavBackEnd->getCalendarObject($calendarId, $objectUri);
if ($calendarData === null) {
if ($existing !== null) {
@@ -391,14 +409,27 @@ class BirthdayService {
}
/**
+ * Extracts the userId part of a principal
+ *
+ * @param string $userPrincipal
+ * @return string|null
+ */
+ private function principalToUserId(string $userPrincipal):?string {
+ if (substr($userPrincipal, 0, 17) === 'principals/users/') {
+ return substr($userPrincipal, 17);
+ }
+ return null;
+ }
+
+ /**
* Checks if the user opted-out of birthday calendars
*
* @param string $userPrincipal The user principal to check for
* @return bool
*/
private function isUserEnabled(string $userPrincipal):bool {
- if (strpos($userPrincipal, 'principals/users/') === 0) {
- $userId = substr($userPrincipal, 17);
+ $userId = $this->principalToUserId($userPrincipal);
+ if ($userId !== null) {
$isEnabled = $this->config->getUserValue($userId, 'dav', 'generateBirthdayCalendar', 'yes');
return $isEnabled === 'yes';
}
@@ -408,6 +439,23 @@ class BirthdayService {
}
/**
+ * Get the reminder offset value for a user. This is a duration string (e.g.
+ * PT9H) or null if no reminder is wanted.
+ *
+ * @param string $userPrincipal
+ * @return string|null
+ */
+ private function getReminderOffsetForUser(string $userPrincipal):?string {
+ $userId = $this->principalToUserId($userPrincipal);
+ if ($userId !== null) {
+ return $this->config->getUserValue($userId, 'dav', 'birthdayCalendarReminderOffset', 'PT9H') ?: null;
+ }
+
+ // not sure how we got here, just be on the safe side and return the default value
+ return 'PT9H';
+ }
+
+ /**
* Formats title of Birthday event
*
* @param string $field Field name like BDAY, ANNIVERSARY, ...
diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php
index 8bdc084cd7b..2bbdc51f42e 100644
--- a/apps/dav/lib/CalDAV/CalDavBackend.php
+++ b/apps/dav/lib/CalDAV/CalDavBackend.php
@@ -52,6 +52,7 @@ use OCA\DAV\Events\CalendarDeletedEvent;
use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedEvent;
use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectRestoredEvent;
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
@@ -95,8 +96,6 @@ use Sabre\VObject\ParseException;
use Sabre\VObject\Property;
use Sabre\VObject\Reader;
use Sabre\VObject\Recur\EventIterator;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
use function array_column;
use function array_merge;
use function array_values;
@@ -150,7 +149,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @var array
* @psalm-var array<string, string[]>
*/
- public $propertyMap = [
+ public array $propertyMap = [
'{DAV:}displayname' => ['displayname', 'string'],
'{urn:ietf:params:xml:ns:caldav}calendar-description' => ['description', 'string'],
'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => ['timezone', 'string'],
@@ -164,7 +163,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
*
* @var array
*/
- public $subscriptionPropertyMap = [
+ public array $subscriptionPropertyMap = [
'{DAV:}displayname' => ['displayname', 'string'],
'{http://apple.com/ns/ical/}refreshrate' => ['refreshrate', 'string'],
'{http://apple.com/ns/ical/}calendar-order' => ['calendarorder', 'int'],
@@ -195,7 +194,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
];
/** @var array parameters to index */
- public static $indexParameters = [
+ public static array $indexParameters = [
'ATTENDEE' => ['CN'],
'ORGANIZER' => ['CN'],
];
@@ -203,43 +202,19 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/**
* @var string[] Map of uid => display name
*/
- protected $userDisplayNames;
-
- /** @var IDBConnection */
- private $db;
-
- /** @var Backend */
- private $calendarSharingBackend;
-
- /** @var Principal */
- private $principalBackend;
-
- /** @var IUserManager */
- private $userManager;
-
- /** @var ISecureRandom */
- private $random;
+ protected array $userDisplayNames;
+ private IDBConnection $db;
+ private Backend $calendarSharingBackend;
+ private Principal $principalBackend;
+ private IUserManager $userManager;
+ private ISecureRandom $random;
private LoggerInterface $logger;
+ private IEventDispatcher $dispatcher;
+ private IConfig $config;
+ private bool $legacyEndpoint;
+ private string $dbObjectPropertiesTable = 'calendarobjects_props';
- /** @var IEventDispatcher */
- private $dispatcher;
-
- /** @var EventDispatcherInterface */
- private $legacyDispatcher;
-
- /** @var IConfig */
- private $config;
-
- /** @var bool */
- private $legacyEndpoint;
-
- /** @var string */
- private $dbObjectPropertiesTable = 'calendarobjects_props';
-
- /**
- * CalDavBackend constructor.
- */
public function __construct(IDBConnection $db,
Principal $principalBackend,
IUserManager $userManager,
@@ -247,7 +222,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
ISecureRandom $random,
LoggerInterface $logger,
IEventDispatcher $dispatcher,
- EventDispatcherInterface $legacyDispatcher,
IConfig $config,
bool $legacyEndpoint = false) {
$this->db = $db;
@@ -257,7 +231,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->random = $random;
$this->logger = $logger;
$this->dispatcher = $dispatcher;
- $this->legacyDispatcher = $legacyDispatcher;
$this->config = $config;
$this->legacyEndpoint = $legacyEndpoint;
}
@@ -701,10 +674,9 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
- * @param $calendarId
- * @return array|null
+ * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp }|null
*/
- public function getCalendarById($calendarId) {
+ public function getCalendarById(int $calendarId): ?array {
$fields = array_column($this->propertyMap, 0);
$fields[] = 'id';
$fields[] = 'uri';
@@ -737,7 +709,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
'uri' => $row['uri'],
'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
- '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
+ '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?? 0,
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
];
@@ -893,7 +865,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendarData = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
+ $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent($calendarId, $calendarData, $shares, $mutations));
return true;
});
@@ -943,7 +915,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
// Only dispatch if we actually deleted anything
if ($calendarData) {
- $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
+ $this->dispatcher->dispatchTyped(new CalendarDeletedEvent($calendarId, $calendarData, $shares));
}
} else {
$qbMarkCalendarDeleted = $this->db->getQueryBuilder();
@@ -956,7 +928,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$shares = $this->getShares($calendarId);
if ($calendarData) {
$this->dispatcher->dispatchTyped(new CalendarMovedToTrashEvent(
- (int)$calendarId,
+ $calendarId,
$calendarData,
$shares
));
@@ -1292,24 +1264,17 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->addChange($calendarId, $objectUri, 1, $calendarType);
$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
+ assert($objectRow !== null);
+
if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
$calendarRow = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
+ $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent($calendarId, $calendarRow, $shares, $objectRow));
} else {
$subscriptionRow = $this->getSubscriptionById($calendarId);
- $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
- [
- 'subscriptionId' => $calendarId,
- 'calendarData' => $subscriptionRow,
- 'shares' => [],
- 'objectData' => $objectRow,
- ]
- ));
+ $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent($calendarId, $subscriptionRow, [], $objectRow));
}
return '"' . $extraData['etag'] . '"';
@@ -1361,20 +1326,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendarRow = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
+ $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($calendarId, $calendarRow, $shares, $objectRow));
} else {
$subscriptionRow = $this->getSubscriptionById($calendarId);
- $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
- [
- 'subscriptionId' => $calendarId,
- 'calendarData' => $subscriptionRow,
- 'shares' => [],
- 'objectData' => $objectRow,
- ]
- ));
+ $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent($calendarId, $subscriptionRow, [], $objectRow));
}
}
@@ -1387,13 +1343,14 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param int $sourceCalendarId
* @param int $targetCalendarId
* @param int $objectId
- * @param string $principalUri
+ * @param string $oldPrincipalUri
+ * @param string $newPrincipalUri
* @param int $calendarType
* @return bool
* @throws Exception
*/
- public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId, int $objectId, string $principalUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR): bool {
- $object = $this->getCalendarObjectById($principalUri, $objectId);
+ public function moveCalendarObject(int $sourceCalendarId, int $targetCalendarId, int $objectId, string $oldPrincipalUri, string $newPrincipalUri, int $calendarType = self::CALENDAR_TYPE_CALENDAR): bool {
+ $object = $this->getCalendarObjectById($oldPrincipalUri, $objectId);
if (empty($object)) {
return false;
}
@@ -1411,21 +1368,23 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$this->addChange($sourceCalendarId, $object['uri'], 1, $calendarType);
$this->addChange($targetCalendarId, $object['uri'], 3, $calendarType);
- $object = $this->getCalendarObjectById($principalUri, $objectId);
+ $object = $this->getCalendarObjectById($newPrincipalUri, $objectId);
// Calendar Object wasn't found - possibly because it was deleted in the meantime by a different client
if (empty($object)) {
return false;
}
- $calendarRow = $this->getCalendarById($targetCalendarId);
+ $targetCalendarRow = $this->getCalendarById($targetCalendarId);
// the calendar this event is being moved to does not exist any longer
- if (empty($calendarRow)) {
+ if (empty($targetCalendarRow)) {
return false;
}
if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
- $shares = $this->getShares($targetCalendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent($targetCalendarId, $calendarRow, $shares, $object));
+ $sourceShares = $this->getShares($sourceCalendarId);
+ $targetShares = $this->getShares($targetCalendarId);
+ $sourceCalendarRow = $this->getCalendarById($sourceCalendarId);
+ $this->dispatcher->dispatchTyped(new CalendarObjectMovedEvent($sourceCalendarId, $sourceCalendarRow, $targetCalendarId, $targetCalendarRow, $sourceShares, $targetShares, $object));
}
return true;
}
@@ -1477,20 +1436,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$calendarRow = $this->getCalendarById($calendarId);
$shares = $this->getShares($calendarId);
- $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
+ $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent($calendarId, $calendarRow, $shares, $data));
} else {
$subscriptionRow = $this->getSubscriptionById($calendarId);
- $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
- [
- 'subscriptionId' => $calendarId,
- 'calendarData' => $subscriptionRow,
- 'shares' => [],
- 'objectData' => $data,
- ]
- ));
+ $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent($calendarId, $subscriptionRow, [], $data));
}
} else {
$pathInfo = pathinfo($data['uri']);
@@ -1530,7 +1480,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
if ($calendarData !== null) {
$this->dispatcher->dispatchTyped(
new CalendarObjectMovedToTrashEvent(
- (int)$calendarId,
+ $calendarId,
$calendarData,
$this->getShares($calendarId),
$data
@@ -1634,7 +1584,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* Note that especially time-range-filters may be difficult to parse. A
* time-range filter specified on a VEVENT must for instance also handle
* recurrence rules correctly.
- * A good example of how to interprete all these filters can also simply
+ * A good example of how to interpret all these filters can also simply
* be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
* as possible, so it gives you a good idea on what type of stuff you need
* to think of.
@@ -2501,12 +2451,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$subscriptionRow = $this->getSubscriptionById($subscriptionId);
$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent($subscriptionId, $subscriptionRow));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
- [
- 'subscriptionId' => $subscriptionId,
- 'subscriptionData' => $subscriptionRow,
- ]));
return $subscriptionId;
}
@@ -2554,13 +2498,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
$subscriptionRow = $this->getSubscriptionById($subscriptionId);
$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
- [
- 'subscriptionId' => $subscriptionId,
- 'subscriptionData' => $subscriptionRow,
- 'propertyMutations' => $mutations,
- ]));
return true;
});
@@ -2575,13 +2512,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function deleteSubscription($subscriptionId) {
$subscriptionRow = $this->getSubscriptionById($subscriptionId);
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
- [
- 'subscriptionId' => $subscriptionId,
- 'subscriptionData' => $this->getSubscriptionById($subscriptionId),
- ]));
-
$query = $this->db->getQueryBuilder();
$query->delete('calendarsubscriptions')
->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
@@ -2870,34 +2800,26 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
}
/**
- * @param IShareable $shareable
- * @param array $add
- * @param array $remove
+ * @param list<array{href: string, commonName: string, readOnly: bool}> $add
+ * @param list<string> $remove
*/
- public function updateShares($shareable, $add, $remove) {
+ public function updateShares(IShareable $shareable, array $add, array $remove): void {
$calendarId = $shareable->getResourceId();
$calendarRow = $this->getCalendarById($calendarId);
+ if ($calendarRow === null) {
+ throw new \RuntimeException('Trying to update shares for innexistant calendar: ' . $calendarId);
+ }
$oldShares = $this->getShares($calendarId);
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
- [
- 'calendarId' => $calendarId,
- 'calendarData' => $calendarRow,
- 'shares' => $oldShares,
- 'add' => $add,
- 'remove' => $remove,
- ]));
$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
- $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
+ $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent($calendarId, $calendarRow, $oldShares, $add, $remove));
}
/**
- * @param int $resourceId
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
- public function getShares($resourceId) {
+ public function getShares(int $resourceId): array {
return $this->calendarSharingBackend->getShares($resourceId);
}
@@ -2909,13 +2831,6 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
public function setPublishStatus($value, $calendar) {
$calendarId = $calendar->getResourceId();
$calendarData = $this->getCalendarById($calendarId);
- $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
- '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
- [
- 'calendarId' => $calendarId,
- 'calendarData' => $calendarData,
- 'public' => $value,
- ]));
$query = $this->db->getQueryBuilder();
if ($value) {
@@ -2930,7 +2845,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
]);
$query->executeStatement();
- $this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
+ $this->dispatcher->dispatchTyped(new CalendarPublishedEvent($calendarId, $calendarData, $publicUri));
return $publicUri;
}
$query->delete('dav_shares')
@@ -2938,7 +2853,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
$query->executeStatement();
- $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
+ $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent($calendarId, $calendarData));
return null;
}
@@ -2961,15 +2876,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/**
* @param int $resourceId
- * @param array $acl
- * @return array
+ * @param list<array{privilege: string, principal: string, protected: bool}> $acl
+ * @return list<array{privilege: string, principal: string, protected: bool}>
*/
- public function applyShareAcl($resourceId, $acl) {
+ public function applyShareAcl(int $resourceId, array $acl): array {
return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
}
-
-
/**
* update properties table
*
diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php
index 75c815c3b0a..92ad3242d78 100644
--- a/apps/dav/lib/CalDAV/Calendar.php
+++ b/apps/dav/lib/CalDAV/Calendar.php
@@ -51,27 +51,11 @@ use Sabre\DAV\PropPatch;
* @property CalDavBackend $caldavBackend
*/
class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable, IMoveTarget {
+ private IConfig $config;
+ protected IL10N $l10n;
+ private bool $useTrashbin = true;
+ private LoggerInterface $logger;
- /** @var IConfig */
- private $config;
-
- /** @var IL10N */
- protected $l10n;
-
- /** @var bool */
- private $useTrashbin = true;
-
- /** @var LoggerInterface */
- private $logger;
-
- /**
- * Calendar constructor.
- *
- * @param BackendInterface $caldavBackend
- * @param $calendarInfo
- * @param IL10N $l10n
- * @param IConfig $config
- */
public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n, IConfig $config, LoggerInterface $logger) {
// Convert deletion date to ISO8601 string
if (isset($calendarInfo[TrashbinPlugin::PROPERTY_DELETED_AT])) {
@@ -96,25 +80,10 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
}
/**
- * Updates the list of shares.
- *
- * The first array is a list of people that are to be added to the
- * resource.
- *
- * Every element in the add array has the following properties:
- * * href - A url. Usually a mailto: address
- * * commonName - Usually a first and last name, or false
- * * summary - A description of the share, can also be false
- * * readOnly - A boolean value
- *
- * Every element in the remove array is just the address string.
- *
- * @param array $add
- * @param array $remove
- * @return void
+ * {@inheritdoc}
* @throws Forbidden
*/
- public function updateShares(array $add, array $remove) {
+ public function updateShares(array $add, array $remove): void {
if ($this->isShared()) {
throw new Forbidden();
}
@@ -131,19 +100,16 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
* * readOnly - boolean
* * summary - Optional, a description for the share
*
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
- public function getShares() {
+ public function getShares(): array {
if ($this->isShared()) {
return [];
}
return $this->caldavBackend->getShares($this->getResourceId());
}
- /**
- * @return int
- */
- public function getResourceId() {
+ public function getResourceId(): int {
return $this->calendarInfo['id'];
}
@@ -155,7 +121,9 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
}
/**
- * @return array
+ * @param int $resourceId
+ * @param list<array{privilege: string, principal: string, protected: bool}> $acl
+ * @return list<array{privilege: string, principal: ?string, protected: bool}>
*/
public function getACL() {
$acl = [
@@ -246,16 +214,18 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
parent::getOwner(),
'principals/system/public'
];
- return array_filter($acl, function ($rule) use ($allowedPrincipals) {
+ /** @var list<array{privilege: string, principal: string, protected: bool}> $acl */
+ $acl = array_filter($acl, function (array $rule) use ($allowedPrincipals): bool {
return \in_array($rule['principal'], $allowedPrincipals, true);
});
+ return $acl;
}
public function getChildACL() {
return $this->getACL();
}
- public function getOwner() {
+ public function getOwner(): ?string {
if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'];
}
@@ -400,7 +370,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
return isset($this->calendarInfo['{http://owncloud.org/ns}public']);
}
- protected function isShared() {
+ public function isShared() {
if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
return false;
}
@@ -412,6 +382,13 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
return isset($this->calendarInfo['{http://calendarserver.org/ns/}source']);
}
+ public function isDeleted(): bool {
+ if (!isset($this->calendarInfo[TrashbinPlugin::PROPERTY_DELETED_AT])) {
+ return false;
+ }
+ return $this->calendarInfo[TrashbinPlugin::PROPERTY_DELETED_AT] !== null;
+ }
+
/**
* @inheritDoc
*/
@@ -443,7 +420,7 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IRestorable, IShareable
}
try {
- return $this->caldavBackend->moveCalendarObject($sourceNode->getCalendarId(), (int)$this->calendarInfo['id'], $sourceNode->getId(), $sourceNode->getPrincipalUri());
+ return $this->caldavBackend->moveCalendarObject($sourceNode->getCalendarId(), (int)$this->calendarInfo['id'], $sourceNode->getId(), $sourceNode->getOwner(), $this->getOwner());
} catch (Exception $e) {
$this->logger->error('Could not move calendar object: ' . $e->getMessage(), ['exception' => $e]);
return false;
diff --git a/apps/dav/lib/CalDAV/CalendarImpl.php b/apps/dav/lib/CalDAV/CalendarImpl.php
index 406389e3a3d..27a428a4075 100644
--- a/apps/dav/lib/CalDAV/CalendarImpl.php
+++ b/apps/dav/lib/CalDAV/CalendarImpl.php
@@ -138,7 +138,7 @@ class CalendarImpl implements ICreateFromString {
* Create a new calendar event for this calendar
* by way of an ICS string
*
- * @param string $name the file name - needs to contan the .ics ending
+ * @param string $name the file name - needs to contain the .ics ending
* @param string $calendarData a string containing a valid VEVENT ics
*
* @throws CalendarException
diff --git a/apps/dav/lib/CalDAV/CalendarObject.php b/apps/dav/lib/CalDAV/CalendarObject.php
index c927254fba3..32bcff900c2 100644
--- a/apps/dav/lib/CalDAV/CalendarObject.php
+++ b/apps/dav/lib/CalDAV/CalendarObject.php
@@ -162,4 +162,11 @@ class CalendarObject extends \Sabre\CalDAV\CalendarObject {
public function getPrincipalUri(): string {
return $this->calendarInfo['principaluri'];
}
+
+ public function getOwner(): ?string {
+ if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) {
+ return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'];
+ }
+ return parent::getOwner();
+ }
}
diff --git a/apps/dav/lib/CalDAV/PublicCalendar.php b/apps/dav/lib/CalDAV/PublicCalendar.php
index 4a29c8d237a..16c7f86917d 100644
--- a/apps/dav/lib/CalDAV/PublicCalendar.php
+++ b/apps/dav/lib/CalDAV/PublicCalendar.php
@@ -84,7 +84,7 @@ class PublicCalendar extends Calendar {
* public calendars are always shared
* @return bool
*/
- protected function isShared() {
+ public function isShared() {
return true;
}
}
diff --git a/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php b/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php
index 35bce872bf8..cbff5e66a85 100644
--- a/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php
+++ b/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php
@@ -55,7 +55,7 @@ class Publisher implements XmlSerializable {
}
/**
- * The xmlSerialize metod is called during xml writing.
+ * The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php
index 96bacce4454..44517541faa 100644
--- a/apps/dav/lib/CalDAV/Schedule/Plugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php
@@ -30,6 +30,7 @@ namespace OCA\DAV\CalDAV\Schedule;
use DateTimeZone;
use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\CalendarHome;
use OCP\IConfig;
use Sabre\CalDAV\ICalendar;
@@ -178,7 +179,7 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
// If parent::scheduleLocalDelivery set scheduleStatus to 1.2,
// it means that it was successfully delivered locally.
- // Meaning that the ACL plugin is loaded and that a principial
+ // Meaning that the ACL plugin is loaded and that a principal
// exists for the given recipient id, no need to double check
/** @var \Sabre\DAVACL\Plugin $aclPlugin */
$aclPlugin = $this->server->getPlugin('acl');
@@ -299,12 +300,14 @@ EOF;
return null;
}
+ $isResourceOrRoom = strpos($principalUrl, 'principals/calendar-resources') === 0 ||
+ strpos($principalUrl, 'principals/calendar-rooms') === 0;
+
if (strpos($principalUrl, 'principals/users') === 0) {
[, $userId] = split($principalUrl);
$uri = $this->config->getUserValue($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
$displayName = CalDavBackend::PERSONAL_CALENDAR_NAME;
- } elseif (strpos($principalUrl, 'principals/calendar-resources') === 0 ||
- strpos($principalUrl, 'principals/calendar-rooms') === 0) {
+ } elseif ($isResourceOrRoom) {
$uri = CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI;
$displayName = CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME;
} else {
@@ -316,9 +319,40 @@ EOF;
/** @var CalendarHome $calendarHome */
$calendarHome = $this->server->tree->getNodeForPath($calendarHomePath);
if (!$calendarHome->childExists($uri)) {
- $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [
- '{DAV:}displayname' => $displayName,
- ]);
+ // If the default calendar doesn't exist
+ if ($isResourceOrRoom) {
+ $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [
+ '{DAV:}displayname' => $displayName,
+ ]);
+ } else {
+ // And we're not handling scheduling on resource/room booking
+ $userCalendars = [];
+ /**
+ * If the default calendar of the user isn't set and the
+ * fallback doesn't match any of the user's calendar
+ * try to find the first "personal" calendar we can write to
+ * instead of creating a new one.
+ * A appropriate personal calendar to receive invites:
+ * - isn't a calendar subscription
+ * - user can write to it (no virtual/3rd-party calendars)
+ * - calendar isn't a share
+ */
+ foreach ($calendarHome->getChildren() as $node) {
+ if ($node instanceof Calendar && !$node->isSubscription() && $node->canWrite() && !$node->isShared() && !$node->isDeleted()) {
+ $userCalendars[] = $node;
+ }
+ }
+
+ if (count($userCalendars) > 0) {
+ // Calendar backend returns calendar by calendarorder property
+ $uri = $userCalendars[0]->getName();
+ } else {
+ // Otherwise if we have really nothing, create a new calendar
+ $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [
+ '{DAV:}displayname' => $displayName,
+ ]);
+ }
+ }
}
$result = $this->server->getPropertiesForPath($calendarHomePath . '/' . $uri, [], 1);
@@ -533,7 +567,7 @@ EOF;
}
// If more than one Free-Busy property was returned, it means that an event
- // starts or ends inside this time-range, so it's not availabe and we return false
+ // starts or ends inside this time-range, so it's not available and we return false
if (count($freeBusyProperties) > 1) {
return false;
}
diff --git a/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php b/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php
index 0dbe7398f52..eadeea3457c 100644
--- a/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php
+++ b/apps/dav/lib/CalDAV/WebcalCaching/RefreshWebcalService.php
@@ -56,14 +56,15 @@ use function count;
class RefreshWebcalService {
/** @var CalDavBackend */
- private $calDavBackend;
+ private CalDavBackend $calDavBackend;
/** @var IClientService */
- private $clientService;
+ private IClientService $clientService;
/** @var IConfig */
- private $config;
+ private IConfig $config;
+ /** @var LoggerInterface */
private LoggerInterface $logger;
public const REFRESH_RATE = '{http://apple.com/ns/ical/}refreshrate';
@@ -71,13 +72,7 @@ class RefreshWebcalService {
public const STRIP_ATTACHMENTS = '{http://calendarserver.org/ns/}subscribed-strip-attachments';
public const STRIP_TODOS = '{http://calendarserver.org/ns/}subscribed-strip-todos';
- /**
- * RefreshWebcalJob constructor.
- */
- public function __construct(CalDavBackend $calDavBackend,
- IClientService $clientService,
- IConfig $config,
- LoggerInterface $logger) {
+ public function __construct(CalDavBackend $calDavBackend, IClientService $clientService, IConfig $config, LoggerInterface $logger) {
$this->calDavBackend = $calDavBackend;
$this->clientService = $clientService;
$this->config = $config;
@@ -131,12 +126,12 @@ class RefreshWebcalService {
continue;
}
- $uri = $this->getRandomCalendarObjectUri();
+ $objectUri = $this->getRandomCalendarObjectUri();
$calendarData = $vObject->serialize();
try {
- $this->calDavBackend->createCalendarObject($subscription['id'], $uri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
+ $this->calDavBackend->createCalendarObject($subscription['id'], $objectUri, $calendarData, CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION);
} catch (NoInstancesException | BadRequest $ex) {
- $this->logger->error($ex->getMessage(), ['exception' => $ex]);
+ $this->logger->error('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $ex, 'subscriptionId' => $subscription['id'], 'source' => $subscription['source']]);
}
}
@@ -147,20 +142,14 @@ class RefreshWebcalService {
$this->updateSubscription($subscription, $mutations);
} catch (ParseException $ex) {
- $subscriptionId = $subscription['id'];
-
- $this->logger->error("Subscription $subscriptionId could not be refreshed due to a parsing error", ['exception' => $ex]);
+ $this->logger->error("Subscription {subscriptionId} could not be refreshed due to a parsing error", ['exception' => $ex, 'subscriptionId' => $subscription['id']]);
}
}
/**
* loads subscription from backend
- *
- * @param string $principalUri
- * @param string $uri
- * @return array|null
*/
- public function getSubscription(string $principalUri, string $uri) {
+ public function getSubscription(string $principalUri, string $uri): ?array {
$subscriptions = array_values(array_filter(
$this->calDavBackend->getSubscriptionsForUser($principalUri),
function ($sub) use ($uri) {
@@ -177,12 +166,8 @@ class RefreshWebcalService {
/**
* gets webcal feed from remote server
- *
- * @param array $subscription
- * @param array &$mutations
- * @return null|string
*/
- private function queryWebcalFeed(array $subscription, array &$mutations) {
+ private function queryWebcalFeed(array $subscription, array &$mutations): ?string {
$client = $this->clientService->newClient();
$didBreak301Chain = false;
@@ -244,7 +229,7 @@ class RefreshWebcalService {
$jCalendar = Reader::readJson($body, Reader::OPTION_FORGIVING);
} catch (Exception $ex) {
// In case of a parsing error return null
- $this->logger->debug("Subscription $subscriptionId could not be parsed");
+ $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $jCalendar->serialize();
@@ -254,7 +239,7 @@ class RefreshWebcalService {
$xCalendar = Reader::readXML($body);
} catch (Exception $ex) {
// In case of a parsing error return null
- $this->logger->debug("Subscription $subscriptionId could not be parsed");
+ $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $xCalendar->serialize();
@@ -265,7 +250,7 @@ class RefreshWebcalService {
$vCalendar = Reader::read($body);
} catch (Exception $ex) {
// In case of a parsing error return null
- $this->logger->debug("Subscription $subscriptionId could not be parsed");
+ $this->logger->warning("Subscription $subscriptionId could not be parsed", ['exception' => $ex]);
return null;
}
return $vCalendar->serialize();
@@ -291,11 +276,8 @@ class RefreshWebcalService {
* - the webcal feed suggests a refreshrate
* - return suggested refreshrate if user didn't set a custom one
*
- * @param array $subscription
- * @param string $webcalData
- * @return string|null
*/
- private function checkWebcalDataForRefreshRate($subscription, $webcalData) {
+ private function checkWebcalDataForRefreshRate(array $subscription, string $webcalData): ?string {
// if there is no refreshrate stored in the database, check the webcal feed
// whether it suggests any refresh rate and store that in the database
if (isset($subscription[self::REFRESH_RATE]) && $subscription[self::REFRESH_RATE] !== null) {
@@ -353,7 +335,7 @@ class RefreshWebcalService {
* @param string $url
* @return string|null
*/
- private function cleanURL(string $url) {
+ private function cleanURL(string $url): ?string {
$parsed = parse_url($url);
if ($parsed === false) {
return null;
diff --git a/apps/dav/lib/CardDAV/Activity/Backend.php b/apps/dav/lib/CardDAV/Activity/Backend.php
index b713284e182..8c8c643e287 100644
--- a/apps/dav/lib/CardDAV/Activity/Backend.php
+++ b/apps/dav/lib/CardDAV/Activity/Backend.php
@@ -32,6 +32,7 @@ use OCP\App\IAppManager;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
+use OCP\IUserManager;
use OCP\IUserSession;
use Sabre\CardDAV\Plugin;
use Sabre\VObject\Reader;
@@ -50,14 +51,19 @@ class Backend {
/** @var IAppManager */
protected $appManager;
+ /** @var IUserManager */
+ protected $userManager;
+
public function __construct(IActivityManager $activityManager,
IGroupManager $groupManager,
IUserSession $userSession,
- IAppManager $appManager) {
+ IAppManager $appManager,
+ IUserManager $userManager) {
$this->activityManager = $activityManager;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
$this->appManager = $appManager;
+ $this->userManager = $userManager;
}
/**
@@ -103,7 +109,14 @@ class Backend {
return;
}
- $principal = explode('/', $addressbookData['principaluri']);
+ $principalUri = $addressbookData['principaluri'];
+
+ // We are not interested in changes from the system addressbook
+ if ($principalUri === 'principals/system/system') {
+ return;
+ }
+
+ $principal = explode('/', $principalUri);
$owner = array_pop($principal);
$currentUser = $this->userSession->getUser();
@@ -132,6 +145,11 @@ class Backend {
}
foreach ($users as $user) {
+ if ($action === Addressbook::SUBJECT_DELETE && !$this->userManager->userExists($user)) {
+ // Avoid creating addressbook_delete activities for deleted users
+ continue;
+ }
+
$event->setAffectedUser($user)
->setSubject(
$user === $currentUser ? $action . '_self' : $action,
@@ -393,7 +411,14 @@ class Backend {
return;
}
- $principal = explode('/', $addressbookData['principaluri']);
+ $principalUri = $addressbookData['principaluri'];
+
+ // We are not interested in changes from the system addressbook
+ if ($principalUri === 'principals/system/system') {
+ return;
+ }
+
+ $principal = explode('/', $principalUri);
$owner = array_pop($principal);
$currentUser = $this->userSession->getUser();
diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php
index 9bd24bedbac..bca478feec1 100644
--- a/apps/dav/lib/CardDAV/AddressBook.php
+++ b/apps/dav/lib/CardDAV/AddressBook.php
@@ -67,17 +67,15 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
- * * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
- * @param array $add
- * @param array $remove
- * @return void
+ * @param list<array{href: string, commonName: string, readOnly: bool}> $add
+ * @param list<string> $remove
* @throws Forbidden
*/
- public function updateShares(array $add, array $remove) {
+ public function updateShares(array $add, array $remove): void {
if ($this->isShared()) {
throw new Forbidden();
}
@@ -92,11 +90,10 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
- * * summary - Optional, a description for the share
*
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
- public function getShares() {
+ public function getShares(): array {
if ($this->isShared()) {
return [];
}
@@ -163,14 +160,11 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
}
- /**
- * @return int
- */
- public function getResourceId() {
+ public function getResourceId(): int {
return $this->addressBookInfo['id'];
}
- public function getOwner() {
+ public function getOwner(): ?string {
if (isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'];
}
@@ -207,7 +201,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
return $this->carddavBackend->collectCardProperties($this->getResourceId(), 'CATEGORIES');
}
- private function isShared() {
+ private function isShared(): bool {
if (!isset($this->addressBookInfo['{http://owncloud.org/ns}owner-principal'])) {
return false;
}
@@ -215,7 +209,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable {
return $this->addressBookInfo['{http://owncloud.org/ns}owner-principal'] !== $this->addressBookInfo['principaluri'];
}
- private function canWrite() {
+ private function canWrite(): bool {
if (isset($this->addressBookInfo['{http://owncloud.org/ns}read-only'])) {
return !$this->addressBookInfo['{http://owncloud.org/ns}read-only'];
}
diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php
index 3db20cb4220..2f7f0a22900 100644
--- a/apps/dav/lib/CardDAV/AddressBookImpl.php
+++ b/apps/dav/lib/CardDAV/AddressBookImpl.php
@@ -12,6 +12,7 @@
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
+ * @author Thomas Citharel <nextcloud@tcit.fr>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
@@ -206,7 +207,7 @@ class AddressBookImpl implements IAddressBook {
}
/**
- * @param object $id the unique identifier to a contact
+ * @param int $id the unique identifier to a contact
* @return bool successful or not
* @since 5.0.0
*/
diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php
index 864f3da9367..a9ca2eb30a3 100644
--- a/apps/dav/lib/CardDAV/CardDavBackend.php
+++ b/apps/dav/lib/CardDAV/CardDavBackend.php
@@ -58,30 +58,19 @@ use Sabre\CardDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
class CardDavBackend implements BackendInterface, SyncSupport {
public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
- /** @var Principal */
- private $principalBackend;
-
- /** @var string */
- private $dbCardsTable = 'cards';
-
- /** @var string */
- private $dbCardsPropertiesTable = 'cards_properties';
-
- /** @var IDBConnection */
- private $db;
-
- /** @var Backend */
- private $sharingBackend;
+ private Principal $principalBackend;
+ private string $dbCardsTable = 'cards';
+ private string $dbCardsPropertiesTable = 'cards_properties';
+ private IDBConnection $db;
+ private Backend $sharingBackend;
/** @var array properties to index */
- public static $indexProperties = [
+ public static array $indexProperties = [
'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO',
'CLOUD', 'X-SOCIALPROFILE'];
@@ -89,18 +78,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
/**
* @var string[] Map of uid => display name
*/
- protected $userDisplayNames;
-
- /** @var IUserManager */
- private $userManager;
-
- /** @var IEventDispatcher */
- private $dispatcher;
-
- /** @var EventDispatcherInterface */
- private $legacyDispatcher;
-
- private $etagCache = [];
+ protected array $userDisplayNames;
+ private IUserManager $userManager;
+ private IEventDispatcher $dispatcher;
+ private array $etagCache = [];
/**
* CardDavBackend constructor.
@@ -110,19 +91,16 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @param IUserManager $userManager
* @param IGroupManager $groupManager
* @param IEventDispatcher $dispatcher
- * @param EventDispatcherInterface $legacyDispatcher
*/
public function __construct(IDBConnection $db,
Principal $principalBackend,
IUserManager $userManager,
IGroupManager $groupManager,
- IEventDispatcher $dispatcher,
- EventDispatcherInterface $legacyDispatcher) {
+ IEventDispatcher $dispatcher) {
$this->db = $db;
$this->principalBackend = $principalBackend;
$this->userManager = $userManager;
$this->dispatcher = $dispatcher;
- $this->legacyDispatcher = $legacyDispatcher;
$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
}
@@ -318,18 +296,14 @@ class CardDavBackend implements BackendInterface, SyncSupport {
return $addressBook;
}
- /**
- * @param $addressBookUri
- * @return array|null
- */
- public function getAddressBooksByUri($principal, $addressBookUri) {
+ public function getAddressBooksByUri(string $principal, string $addressBookUri): ?array {
$query = $this->db->getQueryBuilder();
$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
->from('addressbooks')
->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
->setMaxResults(1)
- ->execute();
+ ->executeQuery();
$row = $result->fetch();
$result->closeCursor();
@@ -393,12 +367,12 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$query->set($key, $query->createNamedParameter($value));
}
$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
- ->execute();
+ ->executeStatement();
$this->addChange($addressBookId, "", 2);
$addressBookRow = $this->getAddressBookById((int)$addressBookId);
- $shares = $this->getShares($addressBookId);
+ $shares = $this->getShares((int)$addressBookId);
$this->dispatcher->dispatchTyped(new AddressBookUpdatedEvent((int)$addressBookId, $addressBookRow, $shares, $mutations));
return true;
@@ -468,30 +442,31 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* @return void
*/
public function deleteAddressBook($addressBookId) {
+ $addressBookId = (int)$addressBookId;
$addressBookData = $this->getAddressBookById($addressBookId);
$shares = $this->getShares($addressBookId);
$query = $this->db->getQueryBuilder();
$query->delete($this->dbCardsTable)
->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
- ->setParameter('addressbookid', $addressBookId)
- ->execute();
+ ->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT)
+ ->executeStatement();
$query->delete('addressbookchanges')
->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
- ->setParameter('addressbookid', $addressBookId)
- ->execute();
+ ->setParameter('addressbookid', $addressBookId, IQueryBuilder::PARAM_INT)
+ ->executeStatement();
$query->delete('addressbooks')
->where($query->expr()->eq('id', $query->createParameter('id')))
- ->setParameter('id', $addressBookId)
- ->execute();
+ ->setParameter('id', $addressBookId, IQueryBuilder::PARAM_INT)
+ ->executeStatement();
$this->sharingBackend->deleteAllShares($addressBookId);
$query->delete($this->dbCardsPropertiesTable)
- ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
- ->execute();
+ ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId, IQueryBuilder::PARAM_INT)))
+ ->executeStatement();
if ($addressBookData) {
$this->dispatcher->dispatchTyped(new AddressBookDeletedEvent($addressBookId, $addressBookData, $shares));
@@ -511,7 +486,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
- * calculating them. If they are specified, you can also ommit carddata.
+ * calculating them. If they are specified, you can also omit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
@@ -692,11 +667,6 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$shares = $this->getShares($addressBookId);
$objectRow = $this->getCard($addressBookId, $cardUri);
$this->dispatcher->dispatchTyped(new CardCreatedEvent($addressBookId, $addressBookData, $shares, $objectRow));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
- new GenericEvent(null, [
- 'addressBookId' => $addressBookId,
- 'cardUri' => $cardUri,
- 'cardData' => $cardData]));
return '"' . $etag . '"';
}
@@ -756,12 +726,6 @@ class CardDavBackend implements BackendInterface, SyncSupport {
$shares = $this->getShares($addressBookId);
$objectRow = $this->getCard($addressBookId, $cardUri);
$this->dispatcher->dispatchTyped(new CardUpdatedEvent($addressBookId, $addressBookData, $shares, $objectRow));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
- new GenericEvent(null, [
- 'addressBookId' => $addressBookId,
- 'cardUri' => $cardUri,
- 'cardData' => $cardData]));
-
return '"' . $etag . '"';
}
@@ -793,11 +757,6 @@ class CardDavBackend implements BackendInterface, SyncSupport {
if ($ret === 1) {
if ($cardId !== null) {
$this->dispatcher->dispatchTyped(new CardDeletedEvent($addressBookId, $addressBookData, $shares, $objectRow));
- $this->legacyDispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
- new GenericEvent(null, [
- 'addressBookId' => $addressBookId,
- 'cardUri' => $cardUri]));
-
$this->purgeProperties($addressBookId, $cardId);
}
return true;
@@ -1002,11 +961,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
/**
- * @param IShareable $shareable
- * @param string[] $add
- * @param string[] $remove
+ * @param list<array{href: string, commonName: string, readOnly: bool}> $add
+ * @param list<string> $remove
*/
- public function updateShares(IShareable $shareable, $add, $remove) {
+ public function updateShares(IShareable $shareable, array $add, array $remove): void {
$addressBookId = $shareable->getResourceId();
$addressBookData = $this->getAddressBookById($addressBookId);
$oldShares = $this->getShares($addressBookId);
@@ -1237,11 +1195,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
- * * summary - Optional, a description for the share
*
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
- public function getShares($addressBookId) {
+ public function getShares(int $addressBookId): array {
return $this->sharingBackend->getShares($addressBookId);
}
@@ -1321,13 +1278,9 @@ class CardDavBackend implements BackendInterface, SyncSupport {
}
/**
- * get ID from a given contact
- *
- * @param int $addressBookId
- * @param string $uri
- * @return int
+ * Get ID from a given contact
*/
- protected function getCardId($addressBookId, $uri) {
+ protected function getCardId(int $addressBookId, string $uri): int {
$query = $this->db->getQueryBuilder();
$query->select('id')->from($this->dbCardsTable)
->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
@@ -1347,15 +1300,15 @@ class CardDavBackend implements BackendInterface, SyncSupport {
/**
* For shared address books the sharee is set in the ACL of the address book
*
- * @param $addressBookId
- * @param $acl
- * @return array
+ * @param int $addressBookId
+ * @param list<array{privilege: string, principal: string, protected: bool}> $acl
+ * @return list<array{privilege: string, principal: string, protected: bool}>
*/
- public function applyShareAcl($addressBookId, $acl) {
+ public function applyShareAcl(int $addressBookId, array $acl): array {
return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
}
- private function convertPrincipal($principalUri, $toV2) {
+ private function convertPrincipal(string $principalUri, bool $toV2): string {
if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
[, $name] = \Sabre\Uri\split($principalUri);
if ($toV2 === true) {
@@ -1366,7 +1319,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
return $principalUri;
}
- private function addOwnerPrincipal(&$addressbookInfo) {
+ private function addOwnerPrincipal(array &$addressbookInfo): void {
$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
if (isset($addressbookInfo[$ownerPrincipalKey])) {
@@ -1386,10 +1339,10 @@ class CardDavBackend implements BackendInterface, SyncSupport {
*
* @param string $cardData the vcard raw data
* @return string the uid
- * @throws BadRequest if no UID is available
+ * @throws BadRequest if no UID is available or vcard is empty
*/
- private function getUID($cardData) {
- if ($cardData != '') {
+ private function getUID(string $cardData): string {
+ if ($cardData !== '') {
$vCard = Reader::read($cardData);
if ($vCard->UID) {
$uid = $vCard->UID->getValue();
diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php
index 169dbc79e0f..5094b7f3f5c 100644
--- a/apps/dav/lib/CardDAV/SyncService.php
+++ b/apps/dav/lib/CardDAV/SyncService.php
@@ -40,27 +40,13 @@ use Sabre\HTTP\ClientHttpException;
use Sabre\VObject\Reader;
class SyncService {
-
- /** @var CardDavBackend */
- private $backend;
-
- /** @var IUserManager */
- private $userManager;
-
+ private CardDavBackend $backend;
+ private IUserManager $userManager;
private LoggerInterface $logger;
+ private ?array $localSystemAddressBook = null;
+ private Converter $converter;
+ protected string $certPath;
- /** @var array */
- private $localSystemAddressBook;
-
- /** @var Converter */
- private $converter;
-
- /** @var string */
- protected $certPath;
-
- /**
- * SyncService constructor.
- */
public function __construct(CardDavBackend $backend,
IUserManager $userManager,
LoggerInterface $logger,
@@ -73,20 +59,11 @@ class SyncService {
}
/**
- * @param string $url
- * @param string $userName
- * @param string $addressBookUrl
- * @param string $sharedSecret
- * @param string $syncToken
- * @param int $targetBookId
- * @param string $targetPrincipal
- * @param array $targetProperties
- * @return string
* @throws \Exception
*/
- public function syncRemoteAddressBook($url, $userName, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetProperties) {
+ public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): string {
// 1. create addressbook
- $book = $this->ensureSystemAddressBookExists($targetPrincipal, (string)$targetBookId, $targetProperties);
+ $book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookHash, $targetProperties);
$addressBookId = $book['id'];
// 2. query changes
@@ -122,28 +99,23 @@ class SyncService {
}
/**
- * @param string $principal
- * @param string $id
- * @param array $properties
- * @return array|null
* @throws \Sabre\DAV\Exception\BadRequest
*/
- public function ensureSystemAddressBookExists($principal, $id, $properties) {
- $book = $this->backend->getAddressBooksByUri($principal, $id);
+ public function ensureSystemAddressBookExists(string $principal, string $uri, array $properties): ?array {
+ $book = $this->backend->getAddressBooksByUri($principal, $uri);
if (!is_null($book)) {
return $book;
}
- $this->backend->createAddressBook($principal, $id, $properties);
+ // FIXME This might break in clustered DB setup
+ $this->backend->createAddressBook($principal, $uri, $properties);
- return $this->backend->getAddressBooksByUri($principal, $id);
+ return $this->backend->getAddressBooksByUri($principal, $uri);
}
/**
* Check if there is a valid certPath we should use
- *
- * @return string
*/
- protected function getCertPath() {
+ protected function getCertPath(): string {
// we already have a valid certPath
if ($this->certPath !== '') {
@@ -159,14 +131,7 @@ class SyncService {
return $this->certPath;
}
- /**
- * @param string $url
- * @param string $userName
- * @param string $addressBookUrl
- * @param string $sharedSecret
- * @return Client
- */
- protected function getClient($url, $userName, $sharedSecret) {
+ protected function getClient(string $url, string $userName, string $sharedSecret): Client {
$settings = [
'baseUri' => $url . '/',
'userName' => $userName,
@@ -183,15 +148,7 @@ class SyncService {
return $client;
}
- /**
- * @param string $url
- * @param string $userName
- * @param string $addressBookUrl
- * @param string $sharedSecret
- * @param string $syncToken
- * @return array
- */
- protected function requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken) {
+ protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array {
$client = $this->getClient($url, $userName, $sharedSecret);
$body = $this->buildSyncCollectionRequestBody($syncToken);
@@ -203,23 +160,12 @@ class SyncService {
return $this->parseMultiStatus($response['body']);
}
- /**
- * @param string $url
- * @param string $userName
- * @param string $sharedSecret
- * @param string $resourcePath
- * @return array
- */
- protected function download($url, $userName, $sharedSecret, $resourcePath) {
+ protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): array {
$client = $this->getClient($url, $userName, $sharedSecret);
return $client->request('GET', $resourcePath);
}
- /**
- * @param string|null $syncToken
- * @return string
- */
- private function buildSyncCollectionRequestBody($syncToken) {
+ private function buildSyncCollectionRequestBody(?string $syncToken): string {
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElementNS('DAV:', 'd:sync-collection');
diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php
index 2bea82a345e..08f937dff9d 100644
--- a/apps/dav/lib/Command/CreateCalendar.php
+++ b/apps/dav/lib/Command/CreateCalendar.php
@@ -29,6 +29,7 @@ use OC\KnownUser\KnownUserService;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
use OCA\DAV\Connector\Sabre\Principal;
+use OCP\Accounts\IAccountManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IDBConnection;
@@ -83,6 +84,7 @@ class CreateCalendar extends Command {
$principalBackend = new Principal(
$this->userManager,
$this->groupManager,
+ \OC::$server->get(IAccountManager::class),
\OC::$server->getShareManager(),
\OC::$server->getUserSession(),
\OC::$server->getAppManager(),
@@ -94,7 +96,6 @@ class CreateCalendar extends Command {
$random = \OC::$server->getSecureRandom();
$logger = \OC::$server->get(LoggerInterface::class);
$dispatcher = \OC::$server->get(IEventDispatcher::class);
- $legacyDispatcher = \OC::$server->getEventDispatcher();
$config = \OC::$server->get(IConfig::class);
$name = $input->getArgument('name');
@@ -106,7 +107,6 @@ class CreateCalendar extends Command {
$random,
$logger,
$dispatcher,
- $legacyDispatcher,
$config
);
$caldav->createCalendar("principals/users/$user", $name, []);
diff --git a/apps/dav/lib/Command/MoveCalendar.php b/apps/dav/lib/Command/MoveCalendar.php
index 320fe8aeac6..9272b20b10d 100644
--- a/apps/dav/lib/Command/MoveCalendar.php
+++ b/apps/dav/lib/Command/MoveCalendar.php
@@ -42,41 +42,17 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class MoveCalendar extends Command {
-
- /** @var IUserManager */
- private $userManager;
-
- /** @var IGroupManager */
- private $groupManager;
-
- /** @var IShareManager */
- private $shareManager;
-
- /** @var IConfig $config */
- private $config;
-
- /** @var IL10N */
- private $l10n;
-
- /** @var SymfonyStyle */
- private $io;
-
- /** @var CalDavBackend */
- private $calDav;
-
- /** @var LoggerInterface */
- private $logger;
+ private IUserManager $userManager;
+ private IGroupManager $groupManager;
+ private IShareManager $shareManager;
+ private IConfig $config;
+ private IL10N $l10n;
+ private ?SymfonyStyle $io = null;
+ private CalDavBackend $calDav;
+ private LoggerInterface $logger;
public const URI_USERS = 'principals/users/';
- /**
- * @param IUserManager $userManager
- * @param IGroupManager $groupManager
- * @param IShareManager $shareManager
- * @param IConfig $config
- * @param IL10N $l10n
- * @param CalDavBackend $calDav
- */
public function __construct(
IUserManager $userManager,
IGroupManager $groupManager,
@@ -224,7 +200,7 @@ class MoveCalendar extends Command {
*/
if ($this->shareManager->shareWithGroupMembersOnly() === true && 'groups' === $prefix && !$this->groupManager->isInGroup($userDestination, $userOrGroup)) {
if ($force) {
- $this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['href' => 'principal:principals/groups/' . $userOrGroup]);
+ $this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['principal:principals/groups/' . $userOrGroup]);
} else {
throw new \InvalidArgumentException("User <$userDestination> is not part of the group <$userOrGroup> with whom the calendar <" . $calendar['uri'] . "> was shared. You may use -f to move the calendar while deleting this share.");
}
@@ -235,7 +211,7 @@ class MoveCalendar extends Command {
*/
if ($userOrGroup === $userDestination) {
if ($force) {
- $this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['href' => 'principal:principals/users/' . $userOrGroup]);
+ $this->calDav->updateShares(new Calendar($this->calDav, $calendar, $this->l10n, $this->config, $this->logger), [], ['principal:principals/users/' . $userOrGroup]);
} else {
throw new \InvalidArgumentException("The calendar <" . $calendar['uri'] . "> is already shared to user <$userDestination>.You may use -f to move the calendar while deleting this share.");
}
diff --git a/apps/dav/lib/Connector/Sabre/ChecksumList.php b/apps/dav/lib/Connector/Sabre/ChecksumList.php
index 344e6a4ab3c..44573de03c8 100644
--- a/apps/dav/lib/Connector/Sabre/ChecksumList.php
+++ b/apps/dav/lib/Connector/Sabre/ChecksumList.php
@@ -42,7 +42,7 @@ class ChecksumList implements XmlSerializable {
}
/**
- * The xmlSerialize metod is called during xml writing.
+ * The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php
index 5280511d5be..b575a051b2a 100644
--- a/apps/dav/lib/Connector/Sabre/Directory.php
+++ b/apps/dav/lib/Connector/Sabre/Directory.php
@@ -66,7 +66,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
/** Cached quota info */
private ?array $quotaInfo = null;
- private ?ObjectTree $tree = null;
+ private ?CachingTree $tree = null;
/** @var array<string, array<int, FileMetadata>> */
private array $metadata = [];
@@ -74,7 +74,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
/**
* Sets up the node, expects a full path name
*/
- public function __construct(View $view, FileInfo $info, ?ObjectTree $tree = null, IShareManager $shareManager = null) {
+ public function __construct(View $view, FileInfo $info, ?CachingTree $tree = null, IShareManager $shareManager = null) {
parent::__construct($view, $info, $shareManager);
$this->tree = $tree;
}
diff --git a/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php b/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php
index ea94b5c8933..ebf3e4021eb 100644
--- a/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php
@@ -29,6 +29,7 @@ namespace OCA\DAV\Connector\Sabre;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
+use OCA\DAV\Exception\ServerMaintenanceMode;
use OCP\Files\StorageNotAvailableException;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Exception\BadRequest;
@@ -81,6 +82,7 @@ class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
FileLocked::class => true,
// An invalid range is requested
RequestedRangeNotSatisfiable::class => true,
+ ServerMaintenanceMode::class => true,
];
private string $appName;
@@ -114,17 +116,12 @@ class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
*/
public function logException(\Throwable $ex) {
$exceptionClass = get_class($ex);
- if (isset($this->nonFatalExceptions[$exceptionClass]) ||
- (
- $exceptionClass === ServiceUnavailable::class &&
- $ex->getMessage() === 'System in maintenance mode.'
- )
- ) {
+ if (isset($this->nonFatalExceptions[$exceptionClass])) {
$this->logger->debug($ex->getMessage(), [
'app' => $this->appName,
'exception' => $ex,
]);
- return;
+ return;
}
$this->logger->critical($ex->getMessage(), [
diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php
index ebcfdabc6b3..94632b265db 100644
--- a/apps/dav/lib/Connector/Sabre/File.php
+++ b/apps/dav/lib/Connector/Sabre/File.php
@@ -553,7 +553,7 @@ class File extends Node implements IFile {
* @return array|bool
*/
public function getDirectDownload() {
- if (\OCP\App::isEnabled('encryption')) {
+ if (\OCP\Server::get(\OCP\App\IAppManager::class)->isEnabledForUser('encryption')) {
return [];
}
/** @var \OCP\Files\Storage $storage */
diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
index b784764f8fe..e9d27d4e7f6 100644
--- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
@@ -65,6 +65,7 @@ class FilesPlugin extends ServerPlugin {
public const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
public const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
public const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
+ public const SHARE_ATTRIBUTES_PROPERTYNAME = '{http://nextcloud.org/ns}share-attributes';
public const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
public const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
@@ -134,6 +135,7 @@ class FilesPlugin extends ServerPlugin {
$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
$server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
+ $server->protectedProperties[] = self::SHARE_ATTRIBUTES_PROPERTYNAME;
$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
@@ -321,6 +323,10 @@ class FilesPlugin extends ServerPlugin {
return json_encode($ocmPermissions);
});
+ $propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function () use ($node, $httpRequest) {
+ return json_encode($node->getShareAttributes());
+ });
+
$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node): string {
return $node->getETag();
});
diff --git a/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php b/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php
index e7e3b273b98..1fc02320805 100644
--- a/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php
+++ b/apps/dav/lib/Connector/Sabre/MaintenancePlugin.php
@@ -27,6 +27,7 @@
*/
namespace OCA\DAV\Connector\Sabre;
+use OCA\DAV\Exception\ServerMaintenanceMode;
use OCP\IConfig;
use OCP\IL10N;
use OCP\Util;
@@ -82,10 +83,10 @@ class MaintenancePlugin extends ServerPlugin {
*/
public function checkMaintenanceMode() {
if ($this->config->getSystemValueBool('maintenance')) {
- throw new ServiceUnavailable($this->l10n->t('System is in maintenance mode.'));
+ throw new ServerMaintenanceMode($this->l10n->t('System is in maintenance mode.'));
}
if (Util::needUpgrade()) {
- throw new ServiceUnavailable($this->l10n->t('Upgrade needed'));
+ throw new ServerMaintenanceMode($this->l10n->t('Upgrade needed'));
}
return true;
diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php
index e4517068f42..87f2fea394f 100644
--- a/apps/dav/lib/Connector/Sabre/Node.php
+++ b/apps/dav/lib/Connector/Sabre/Node.php
@@ -38,6 +38,7 @@ namespace OCA\DAV\Connector\Sabre;
use OC\Files\Mount\MoveableMount;
use OC\Files\Node\File;
use OC\Files\Node\Folder;
+use OC\Files\Storage\Wrapper\Wrapper;
use OC\Files\View;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCP\Files\FileInfo;
@@ -323,6 +324,31 @@ abstract class Node implements \Sabre\DAV\INode {
}
/**
+ * @return array
+ */
+ public function getShareAttributes(): array {
+ $attributes = [];
+
+ try {
+ $storage = $this->info->getStorage();
+ } catch (StorageNotAvailableException $e) {
+ $storage = null;
+ }
+
+ if ($storage && $storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
+ /** @var \OCA\Files_Sharing\SharedStorage $storage */
+ $attributes = $storage->getShare()->getAttributes();
+ if ($attributes === null) {
+ return [];
+ } else {
+ return $attributes->toArray();
+ }
+ }
+
+ return $attributes;
+ }
+
+ /**
* @param string $user
* @return string
*/
diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php
index 94e3978e67d..75bee4e7b42 100644
--- a/apps/dav/lib/Connector/Sabre/Principal.php
+++ b/apps/dav/lib/Connector/Sabre/Principal.php
@@ -41,6 +41,9 @@ use OC\KnownUser\KnownUserService;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
use OCA\DAV\Traits\PrincipalProxyTrait;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\IAccountProperty;
+use OCP\Accounts\PropertyDoesNotExistException;
use OCP\App\IAppManager;
use OCP\AppFramework\QueryException;
use OCP\Constants;
@@ -64,6 +67,9 @@ class Principal implements BackendInterface {
/** @var IGroupManager */
private $groupManager;
+ /** @var IAccountManager */
+ private $accountManager;
+
/** @var IShareManager */
private $shareManager;
@@ -95,6 +101,7 @@ class Principal implements BackendInterface {
public function __construct(IUserManager $userManager,
IGroupManager $groupManager,
+ IAccountManager $accountManager,
IShareManager $shareManager,
IUserSession $userSession,
IAppManager $appManager,
@@ -105,6 +112,7 @@ class Principal implements BackendInterface {
string $principalPrefix = 'principals/users/') {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
+ $this->accountManager = $accountManager;
$this->shareManager = $shareManager;
$this->userSession = $userSession;
$this->appManager = $appManager;
@@ -506,6 +514,7 @@ class Principal implements BackendInterface {
/**
* @param IUser $user
* @return array
+ * @throws PropertyDoesNotExistException
*/
protected function userToPrincipal($user) {
$userId = $user->getUID();
@@ -517,11 +526,18 @@ class Principal implements BackendInterface {
'{http://nextcloud.com/ns}language' => $this->languageFactory->getUserLanguage($user),
];
+ $account = $this->accountManager->getAccount($user);
+ $alternativeEmails = array_map(fn (IAccountProperty $property) => 'mailto:' . $property->getValue(), $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties());
+
$email = $user->getSystemEMailAddress();
if (!empty($email)) {
$principal['{http://sabredav.org/ns}email-address'] = $email;
}
+ if (!empty($alternativeEmails)) {
+ $principal['{DAV:}alternate-URI-set'] = $alternativeEmails;
+ }
+
return $principal;
}
diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php
index 8f1f710ca5e..4c57f3412e3 100644
--- a/apps/dav/lib/Connector/Sabre/ServerFactory.php
+++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php
@@ -33,6 +33,7 @@ namespace OCA\DAV\Connector\Sabre;
use OCP\Files\Folder;
use OCA\DAV\AppInfo\PluginManager;
+use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCP\Files\Mount\IMountManager;
use OCP\IConfig;
@@ -158,6 +159,11 @@ class ServerFactory {
$server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view, true));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ChecksumUpdatePlugin());
+ // Allow view-only plugin for webdav requests
+ $server->addPlugin(new ViewOnlyPlugin(
+ $this->logger
+ ));
+
if ($this->userSession->isLoggedIn()) {
$server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin(
diff --git a/apps/dav/lib/Connector/Sabre/ShareTypeList.php b/apps/dav/lib/Connector/Sabre/ShareTypeList.php
index 6fbae0dee4a..bacbdc99a73 100644
--- a/apps/dav/lib/Connector/Sabre/ShareTypeList.php
+++ b/apps/dav/lib/Connector/Sabre/ShareTypeList.php
@@ -79,7 +79,7 @@ class ShareTypeList implements Element {
}
/**
- * The xmlSerialize metod is called during xml writing.
+ * The xmlSerialize method is called during xml writing.
*
* @param Writer $writer
* @return void
diff --git a/apps/dav/lib/Connector/Sabre/ShareeList.php b/apps/dav/lib/Connector/Sabre/ShareeList.php
index db8c011cc45..e43f552a8cc 100644
--- a/apps/dav/lib/Connector/Sabre/ShareeList.php
+++ b/apps/dav/lib/Connector/Sabre/ShareeList.php
@@ -45,7 +45,7 @@ class ShareeList implements XmlSerializable {
}
/**
- * The xmlSerialize metod is called during xml writing.
+ * The xmlSerialize method is called during xml writing.
*
* @param Writer $writer
* @return void
diff --git a/apps/dav/lib/Connector/Sabre/TagList.php b/apps/dav/lib/Connector/Sabre/TagList.php
index bbb938fb27d..86006cd3404 100644
--- a/apps/dav/lib/Connector/Sabre/TagList.php
+++ b/apps/dav/lib/Connector/Sabre/TagList.php
@@ -95,7 +95,7 @@ class TagList implements Element {
}
/**
- * The xmlSerialize metod is called during xml writing.
+ * The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
diff --git a/apps/dav/lib/Controller/DirectController.php b/apps/dav/lib/Controller/DirectController.php
index 955400998cf..f9c83488935 100644
--- a/apps/dav/lib/Controller/DirectController.php
+++ b/apps/dav/lib/Controller/DirectController.php
@@ -31,8 +31,12 @@ use OCA\DAV\Db\DirectMapper;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSNotFoundException;
+use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\GenericEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\IRequest;
@@ -59,6 +63,8 @@ class DirectController extends OCSController {
/** @var IURLGenerator */
private $urlGenerator;
+ /** @var IEventDispatcher */
+ private $eventDispatcher;
public function __construct(string $appName,
IRequest $request,
@@ -67,7 +73,8 @@ class DirectController extends OCSController {
DirectMapper $mapper,
ISecureRandom $random,
ITimeFactory $timeFactory,
- IURLGenerator $urlGenerator) {
+ IURLGenerator $urlGenerator,
+ IEventDispatcher $eventDispatcher) {
parent::__construct($appName, $request);
$this->rootFolder = $rootFolder;
@@ -76,6 +83,7 @@ class DirectController extends OCSController {
$this->random = $random;
$this->timeFactory = $timeFactory;
$this->urlGenerator = $urlGenerator;
+ $this->eventDispatcher = $eventDispatcher;
}
/**
@@ -99,6 +107,13 @@ class DirectController extends OCSController {
throw new OCSBadRequestException('Direct download only works for files');
}
+ $event = new BeforeDirectFileDownloadEvent($userFolder->getRelativePath($file->getPath()));
+ $this->eventDispatcher->dispatchTyped($event);
+
+ if ($event->isSuccessful() === false) {
+ throw new OCSForbiddenException('Permission denied to download file');
+ }
+
//TODO: at some point we should use the directdownlaod function of storages
$direct = new Direct();
$direct->setUserId($this->userId);
diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php
index acee65cd00d..0110990a408 100644
--- a/apps/dav/lib/DAV/CustomPropertiesBackend.php
+++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php
@@ -24,13 +24,15 @@
*/
namespace OCA\DAV\DAV;
-use OCA\DAV\Connector\Sabre\Node;
+use Exception;
+use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
use Sabre\DAV\PropertyStorage\Backend\BackendInterface;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Tree;
+use Sabre\DAV\Xml\Property\Complex;
use function array_intersect;
class CustomPropertiesBackend implements BackendInterface {
@@ -39,6 +41,21 @@ class CustomPropertiesBackend implements BackendInterface {
private const TABLE_NAME = 'properties';
/**
+ * Value is stored as string.
+ */
+ public const PROPERTY_TYPE_STRING = 1;
+
+ /**
+ * Value is stored as XML fragment.
+ */
+ public const PROPERTY_TYPE_XML = 2;
+
+ /**
+ * Value is stored as a property object.
+ */
+ public const PROPERTY_TYPE_OBJECT = 3;
+
+ /**
* Ignored properties
*
* @var string[]
@@ -239,7 +256,7 @@ class CustomPropertiesBackend implements BackendInterface {
$result = $qb->executeQuery();
$props = [];
while ($row = $result->fetch()) {
- $props[$row['propertyname']] = $row['propertyvalue'];
+ $props[$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']);
}
$result->closeCursor();
return $props;
@@ -282,7 +299,7 @@ class CustomPropertiesBackend implements BackendInterface {
$props = [];
while ($row = $result->fetch()) {
- $props[$row['propertyname']] = $row['propertyvalue'];
+ $props[$row['propertyname']] = $this->decodeValueFromDatabase($row['propertyvalue'], $row['valuetype']);
}
$result->closeCursor();
@@ -292,68 +309,54 @@ class CustomPropertiesBackend implements BackendInterface {
}
/**
- * Update properties
- *
- * @param string $path path for which to update properties
- * @param array $properties array of properties to update
- *
- * @return bool
+ * @throws Exception
*/
- private function updateProperties(string $path, array $properties) {
- $deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
- ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
-
- $insertStatement = 'INSERT INTO `*PREFIX*properties`' .
- ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
-
- $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
- ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
-
+ private function updateProperties(string $path, array $properties): bool {
// TODO: use "insert or update" strategy ?
$existing = $this->getUserProperties($path, []);
- $this->connection->beginTransaction();
- foreach ($properties as $propertyName => $propertyValue) {
- // If it was null, we need to delete the property
- if (is_null($propertyValue)) {
- if (array_key_exists($propertyName, $existing)) {
- $this->connection->executeUpdate($deleteStatement,
- [
- $this->user->getUID(),
- $this->formatPath($path),
- $propertyName,
- ]
- );
- }
- } else {
- if ($propertyValue instanceOf \Sabre\DAV\Xml\Property\Complex) {
- $propertyValue = $propertyValue->getXml();
- } elseif (!is_string($propertyValue)) {
- $propertyValue = (string)$propertyValue;
- }
- if (!array_key_exists($propertyName, $existing)) {
- $this->connection->executeUpdate($insertStatement,
- [
- $this->user->getUID(),
- $this->formatPath($path),
- $propertyName,
- $propertyValue,
- ]
- );
+ try {
+ $this->connection->beginTransaction();
+ foreach ($properties as $propertyName => $propertyValue) {
+ // common parameters for all queries
+ $dbParameters = [
+ 'userid' => $this->user->getUID(),
+ 'propertyPath' => $this->formatPath($path),
+ 'propertyName' => $propertyName
+ ];
+
+ // If it was null, we need to delete the property
+ if (is_null($propertyValue)) {
+ if (array_key_exists($propertyName, $existing)) {
+ $deleteQuery = $deleteQuery ?? $this->createDeleteQuery();
+ $deleteQuery
+ ->setParameters($dbParameters)
+ ->executeStatement();
+ }
} else {
- $this->connection->executeUpdate($updateStatement,
- [
- $propertyValue,
- $this->user->getUID(),
- $this->formatPath($path),
- $propertyName,
- ]
- );
+ [$value, $valueType] = $this->encodeValueForDatabase($propertyValue);
+ $dbParameters['propertyValue'] = $value;
+ $dbParameters['valueType'] = $valueType;
+
+ if (!array_key_exists($propertyName, $existing)) {
+ $insertQuery = $insertQuery ?? $this->createInsertQuery();
+ $insertQuery
+ ->setParameters($dbParameters)
+ ->executeStatement();
+ } else {
+ $updateQuery = $updateQuery ?? $this->createUpdateQuery();
+ $updateQuery
+ ->setParameters($dbParameters)
+ ->executeStatement();
+ }
}
}
- }
- $this->connection->commit();
- unset($this->userCache[$path]);
+ $this->connection->commit();
+ unset($this->userCache[$path]);
+ } catch (Exception $e) {
+ $this->connection->rollBack();
+ throw $e;
+ }
return true;
}
@@ -367,8 +370,73 @@ class CustomPropertiesBackend implements BackendInterface {
private function formatPath(string $path): string {
if (strlen($path) > 250) {
return sha1($path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * @param mixed $value
+ * @return array
+ */
+ private function encodeValueForDatabase($value): array {
+ if (is_scalar($value)) {
+ $valueType = self::PROPERTY_TYPE_STRING;
+ } elseif ($value instanceof Complex) {
+ $valueType = self::PROPERTY_TYPE_XML;
+ $value = $value->getXml();
} else {
- return $path;
+ $valueType = self::PROPERTY_TYPE_OBJECT;
+ $value = serialize($value);
+ }
+ return [$value, $valueType];
+ }
+
+ /**
+ * @return mixed|Complex|string
+ */
+ private function decodeValueFromDatabase(string $value, int $valueType) {
+ switch ($valueType) {
+ case self::PROPERTY_TYPE_XML:
+ return new Complex($value);
+ case self::PROPERTY_TYPE_OBJECT:
+ return unserialize($value);
+ case self::PROPERTY_TYPE_STRING:
+ default:
+ return $value;
}
}
+
+ private function createDeleteQuery(): IQueryBuilder {
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete('properties')
+ ->where($deleteQuery->expr()->eq('userid', $deleteQuery->createParameter('userid')))
+ ->andWhere($deleteQuery->expr()->eq('propertypath', $deleteQuery->createParameter('propertyPath')))
+ ->andWhere($deleteQuery->expr()->eq('propertyname', $deleteQuery->createParameter('propertyName')));
+ return $deleteQuery;
+ }
+
+ private function createInsertQuery(): IQueryBuilder {
+ $insertQuery = $this->connection->getQueryBuilder();
+ $insertQuery->insert('properties')
+ ->values([
+ 'userid' => $insertQuery->createParameter('userid'),
+ 'propertypath' => $insertQuery->createParameter('propertyPath'),
+ 'propertyname' => $insertQuery->createParameter('propertyName'),
+ 'propertyvalue' => $insertQuery->createParameter('propertyValue'),
+ 'valuetype' => $insertQuery->createParameter('valueType'),
+ ]);
+ return $insertQuery;
+ }
+
+ private function createUpdateQuery(): IQueryBuilder {
+ $updateQuery = $this->connection->getQueryBuilder();
+ $updateQuery->update('properties')
+ ->set('propertyvalue', $updateQuery->createParameter('propertyValue'))
+ ->set('valuetype', $updateQuery->createParameter('valueType'))
+ ->where($updateQuery->expr()->eq('userid', $updateQuery->createParameter('userid')))
+ ->andWhere($updateQuery->expr()->eq('propertypath', $updateQuery->createParameter('propertyPath')))
+ ->andWhere($updateQuery->expr()->eq('propertyname', $updateQuery->createParameter('propertyName')));
+ return $updateQuery;
+ }
}
diff --git a/apps/dav/lib/DAV/Sharing/Backend.php b/apps/dav/lib/DAV/Sharing/Backend.php
index 0f675ea4c15..90d2c7ebf82 100644
--- a/apps/dav/lib/DAV/Sharing/Backend.php
+++ b/apps/dav/lib/DAV/Sharing/Backend.php
@@ -32,32 +32,20 @@ use OCA\DAV\Connector\Sabre\Principal;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
+use OCP\DB\QueryBuilder\IQueryBuilder;
class Backend {
-
- /** @var IDBConnection */
- private $db;
- /** @var IUserManager */
- private $userManager;
- /** @var IGroupManager */
- private $groupManager;
- /** @var Principal */
- private $principalBackend;
- /** @var string */
- private $resourceType;
+ private IDBConnection $db;
+ private IUserManager $userManager;
+ private IGroupManager $groupManager;
+ private Principal $principalBackend;
+ private string $resourceType;
public const ACCESS_OWNER = 1;
public const ACCESS_READ_WRITE = 2;
public const ACCESS_READ = 3;
- /**
- * @param IDBConnection $db
- * @param IUserManager $userManager
- * @param IGroupManager $groupManager
- * @param Principal $principalBackend
- * @param string $resourceType
- */
- public function __construct(IDBConnection $db, IUserManager $userManager, IGroupManager $groupManager, Principal $principalBackend, $resourceType) {
+ public function __construct(IDBConnection $db, IUserManager $userManager, IGroupManager $groupManager, Principal $principalBackend, string $resourceType) {
$this->db = $db;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
@@ -66,11 +54,10 @@ class Backend {
}
/**
- * @param IShareable $shareable
- * @param string[] $add
- * @param string[] $remove
+ * @param list<array{href: string, commonName: string, readOnly: bool}> $add
+ * @param list<string> $remove
*/
- public function updateShares(IShareable $shareable, array $add, array $remove) {
+ public function updateShares(IShareable $shareable, array $add, array $remove): void {
foreach ($add as $element) {
$principal = $this->principalBackend->findByUri($element['href'], '');
if ($principal !== '') {
@@ -86,10 +73,9 @@ class Backend {
}
/**
- * @param IShareable $shareable
- * @param string $element
+ * @param array{href: string, commonName: string, readOnly: bool} $element
*/
- private function shareWith($shareable, $element) {
+ private function shareWith(IShareable $shareable, array $element): void {
$user = $element['href'];
$parts = explode(':', $user, 2);
if ($parts[0] !== 'principal') {
@@ -129,33 +115,26 @@ class Backend {
'access' => $query->createNamedParameter($access),
'resourceid' => $query->createNamedParameter($shareable->getResourceId())
]);
- $query->execute();
+ $query->executeStatement();
}
- /**
- * @param $resourceId
- */
- public function deleteAllShares($resourceId) {
+ public function deleteAllShares(int $resourceId): void {
$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
- ->execute();
+ ->executeStatement();
}
- public function deleteAllSharesByUser($principaluri) {
+ public function deleteAllSharesByUser(string $principaluri): void {
$query = $this->db->getQueryBuilder();
$query->delete('dav_shares')
->where($query->expr()->eq('principaluri', $query->createNamedParameter($principaluri)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
- ->execute();
+ ->executeStatement();
}
- /**
- * @param IShareable $shareable
- * @param string $element
- */
- private function unshare($shareable, $element) {
+ private function unshare(IShareable $shareable, string $element): void {
$parts = explode(':', $element, 2);
if ($parts[0] !== 'principal') {
return;
@@ -172,7 +151,7 @@ class Backend {
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1])))
;
- $query->execute();
+ $query->executeStatement();
}
/**
@@ -183,29 +162,28 @@ class Backend {
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
- * * summary - Optional, a description for the share
*
* @param int $resourceId
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
- public function getShares($resourceId) {
+ public function getShares(int $resourceId): array {
$query = $this->db->getQueryBuilder();
$result = $query->select(['principaluri', 'access'])
->from('dav_shares')
- ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId)))
+ ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType)))
->groupBy(['principaluri', 'access'])
- ->execute();
+ ->executeQuery();
$shares = [];
while ($row = $result->fetch()) {
$p = $this->principalBackend->getPrincipalByPath($row['principaluri']);
$shares[] = [
- 'href' => "principal:${row['principaluri']}",
- 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '',
+ 'href' => "principal:{$row['principaluri']}",
+ 'commonName' => isset($p['{DAV:}displayname']) ? (string)$p['{DAV:}displayname'] : '',
'status' => 1,
'readOnly' => (int) $row['access'] === self::ACCESS_READ,
- '{http://owncloud.org/ns}principal' => $row['principaluri'],
+ '{http://owncloud.org/ns}principal' => (string)$row['principaluri'],
'{http://owncloud.org/ns}group-share' => is_null($p)
];
}
@@ -217,10 +195,10 @@ class Backend {
* For shared resources the sharee is set in the ACL of the resource
*
* @param int $resourceId
- * @param array $acl
- * @return array
+ * @param list<array{privilege: string, principal: string, protected: bool}> $acl
+ * @return list<array{privilege: string, principal: string, protected: bool}>
*/
- public function applyShareAcl($resourceId, $acl) {
+ public function applyShareAcl(int $resourceId, array $acl): array {
$shares = $this->getShares($resourceId);
foreach ($shares as $share) {
$acl[] = [
diff --git a/apps/dav/lib/DAV/Sharing/IShareable.php b/apps/dav/lib/DAV/Sharing/IShareable.php
index 3833e026696..759981af078 100644
--- a/apps/dav/lib/DAV/Sharing/IShareable.php
+++ b/apps/dav/lib/DAV/Sharing/IShareable.php
@@ -40,16 +40,14 @@ interface IShareable extends INode {
* Every element in the add array has the following properties:
* * href - A url. Usually a mailto: address
* * commonName - Usually a first and last name, or false
- * * summary - A description of the share, can also be false
* * readOnly - A boolean value
*
* Every element in the remove array is just the address string.
*
- * @param array $add
- * @param array $remove
- * @return void
+ * @param list<array{href: string, commonName: string, readOnly: bool}> $add
+ * @param list<string> $remove
*/
- public function updateShares(array $add, array $remove);
+ public function updateShares(array $add, array $remove): void;
/**
* Returns the list of people whom this resource is shared with.
@@ -59,19 +57,15 @@ interface IShareable extends INode {
* * commonName - Optional, for example a first + last name
* * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
* * readOnly - boolean
- * * summary - Optional, a description for the share
*
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
*/
- public function getShares();
+ public function getShares(): array;
- /**
- * @return int
- */
- public function getResourceId();
+ public function getResourceId(): int;
/**
- * @return string
+ * @return ?string
*/
public function getOwner();
}
diff --git a/apps/dav/lib/DAV/Sharing/Xml/Invite.php b/apps/dav/lib/DAV/Sharing/Xml/Invite.php
index 161a8dd0ebf..e6219f2bbfe 100644
--- a/apps/dav/lib/DAV/Sharing/Xml/Invite.php
+++ b/apps/dav/lib/DAV/Sharing/Xml/Invite.php
@@ -100,7 +100,7 @@ class Invite implements XmlSerializable {
}
/**
- * The xmlSerialize metod is called during xml writing.
+ * The xmlSerialize method is called during xml writing.
*
* Use the $writer argument to write its own xml serialization.
*
diff --git a/apps/dav/lib/DAV/SystemPrincipalBackend.php b/apps/dav/lib/DAV/SystemPrincipalBackend.php
index e5b9a20037f..07431feb6dd 100644
--- a/apps/dav/lib/DAV/SystemPrincipalBackend.php
+++ b/apps/dav/lib/DAV/SystemPrincipalBackend.php
@@ -87,7 +87,7 @@ class SystemPrincipalBackend extends AbstractBackend {
}
/**
- * Updates one ore more webdav properties on a principal.
+ * Updates one or more webdav properties on a principal.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
diff --git a/apps/dav/lib/DAV/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php
new file mode 100644
index 00000000000..1504969b5b4
--- /dev/null
+++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @author Piotr Mrowczynski piotr@owncloud.com
+ *
+ * @copyright Copyright (c) 2019, ownCloud GmbH
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\DAV;
+
+use OCA\DAV\Connector\Sabre\Exception\Forbidden;
+use OCA\DAV\Connector\Sabre\File as DavFile;
+use OCA\DAV\Meta\MetaFile;
+use OCP\Files\FileInfo;
+use OCP\Files\NotFoundException;
+use Psr\Log\LoggerInterface;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\DAV\Exception\NotFound;
+
+/**
+ * Sabre plugin for restricting file share receiver download:
+ */
+class ViewOnlyPlugin extends ServerPlugin {
+
+ private ?Server $server = null;
+ private LoggerInterface $logger;
+
+ public function __construct(LoggerInterface $logger) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after
+ * addPlugin is called.
+ *
+ * This method should set up the required event subscriptions.
+ */
+ public function initialize(Server $server): void {
+ $this->server = $server;
+ //priority 90 to make sure the plugin is called before
+ //Sabre\DAV\CorePlugin::httpGet
+ $this->server->on('method:GET', [$this, 'checkViewOnly'], 90);
+ }
+
+ /**
+ * Disallow download via DAV Api in case file being received share
+ * and having special permission
+ *
+ * @throws Forbidden
+ * @throws NotFoundException
+ */
+ public function checkViewOnly(RequestInterface $request): bool {
+ $path = $request->getPath();
+
+ try {
+ assert($this->server !== null);
+ $davNode = $this->server->tree->getNodeForPath($path);
+ if (!($davNode instanceof DavFile)) {
+ return true;
+ }
+ // Restrict view-only to nodes which are shared
+ $node = $davNode->getNode();
+
+ $storage = $node->getStorage();
+
+ if (!$storage->instanceOfStorage(\OCA\Files_Sharing\SharedStorage::class)) {
+ return true;
+ }
+ // Extract extra permissions
+ /** @var \OCA\Files_Sharing\SharedStorage $storage */
+ $share = $storage->getShare();
+
+ $attributes = $share->getAttributes();
+ if ($attributes === null) {
+ return true;
+ }
+
+ // Check if read-only and on whether permission can download is both set and disabled.
+ $canDownload = $attributes->getAttribute('permissions', 'download');
+ if ($canDownload !== null && !$canDownload) {
+ throw new Forbidden('Access to this resource has been denied because it is in view-only mode.');
+ }
+ } catch (NotFound $e) {
+ $this->logger->warning($e->getMessage(), [
+ 'exception' => $e,
+ ]);
+ }
+
+ return true;
+ }
+}
diff --git a/apps/dav/lib/Direct/DirectHome.php b/apps/dav/lib/Direct/DirectHome.php
index a385cd8f39d..5453a61ed46 100644
--- a/apps/dav/lib/Direct/DirectHome.php
+++ b/apps/dav/lib/Direct/DirectHome.php
@@ -91,7 +91,7 @@ class DirectHome implements ICollection {
return new DirectFile($direct, $this->rootFolder, $this->eventDispatcher);
} catch (DoesNotExistException $e) {
- // Since the token space is so huge only throttle on non exsisting token
+ // Since the token space is so huge only throttle on non-existing token
$this->throttler->registerAttempt('directlink', $this->request->getRemoteAddress());
$this->throttler->sleepDelay($this->request->getRemoteAddress(), 'directlink');
diff --git a/apps/dav/lib/Events/CalendarObjectMovedEvent.php b/apps/dav/lib/Events/CalendarObjectMovedEvent.php
new file mode 100644
index 00000000000..0143dad9a96
--- /dev/null
+++ b/apps/dav/lib/Events/CalendarObjectMovedEvent.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @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;
+
+/**
+ * Class CalendarObjectMovedEvent
+ *
+ * @package OCA\DAV\Events
+ * @since 25.0.0
+ */
+class CalendarObjectMovedEvent extends Event {
+ private int $sourceCalendarId;
+ private array $sourceCalendarData;
+ private int $targetCalendarId;
+ private array $targetCalendarData;
+ private array $sourceShares;
+ private array $targetShares;
+ private array $objectData;
+
+ /**
+ * @since 25.0.0
+ */
+ public function __construct(int $sourceCalendarId,
+ array $sourceCalendarData,
+ int $targetCalendarId,
+ array $targetCalendarData,
+ array $sourceShares,
+ array $targetShares,
+ array $objectData) {
+ parent::__construct();
+ $this->sourceCalendarId = $sourceCalendarId;
+ $this->sourceCalendarData = $sourceCalendarData;
+ $this->targetCalendarId = $targetCalendarId;
+ $this->targetCalendarData = $targetCalendarData;
+ $this->sourceShares = $sourceShares;
+ $this->targetShares = $targetShares;
+ $this->objectData = $objectData;
+ }
+
+ /**
+ * @return int
+ * @since 25.0.0
+ */
+ public function getSourceCalendarId(): int {
+ return $this->sourceCalendarId;
+ }
+
+ /**
+ * @return array
+ * @since 25.0.0
+ */
+ public function getSourceCalendarData(): array {
+ return $this->sourceCalendarData;
+ }
+
+ /**
+ * @return int
+ * @since 25.0.0
+ */
+ public function getTargetCalendarId(): int {
+ return $this->targetCalendarId;
+ }
+
+ /**
+ * @return array
+ * @since 25.0.0
+ */
+ public function getTargetCalendarData(): array {
+ return $this->targetCalendarData;
+ }
+
+ /**
+ * @return array
+ * @since 25.0.0
+ */
+ public function getSourceShares(): array {
+ return $this->sourceShares;
+ }
+
+ /**
+ * @return array
+ * @since 25.0.0
+ */
+ public function getTargetShares(): array {
+ return $this->targetShares;
+ }
+
+ /**
+ * @return array
+ * @since 25.0.0
+ */
+ public function getObjectData(): array {
+ return $this->objectData;
+ }
+}
diff --git a/apps/dav/lib/Events/CalendarPublishedEvent.php b/apps/dav/lib/Events/CalendarPublishedEvent.php
index 7b3b95f2f77..a95e9f294c1 100644
--- a/apps/dav/lib/Events/CalendarPublishedEvent.php
+++ b/apps/dav/lib/Events/CalendarPublishedEvent.php
@@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
+ * @author Thomas Citharel <nextcloud@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
@@ -34,15 +35,9 @@ use OCP\EventDispatcher\Event;
* @since 20.0.0
*/
class CalendarPublishedEvent extends Event {
-
- /** @var int */
- private $calendarId;
-
- /** @var array */
- private $calendarData;
-
- /** @var string */
- private $publicUri;
+ private int $calendarId;
+ private array $calendarData;
+ private string $publicUri;
/**
* CalendarPublishedEvent constructor.
diff --git a/apps/dav/lib/Events/CalendarShareUpdatedEvent.php b/apps/dav/lib/Events/CalendarShareUpdatedEvent.php
index a9011bc0273..d5a568d149b 100644
--- a/apps/dav/lib/Events/CalendarShareUpdatedEvent.php
+++ b/apps/dav/lib/Events/CalendarShareUpdatedEvent.php
@@ -26,6 +26,8 @@ declare(strict_types=1);
namespace OCA\DAV\Events;
use OCP\EventDispatcher\Event;
+use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
+use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
/**
* Class CalendarShareUpdatedEvent
@@ -34,30 +36,28 @@ use OCP\EventDispatcher\Event;
* @since 20.0.0
*/
class CalendarShareUpdatedEvent extends Event {
+ private int $calendarId;
- /** @var int */
- private $calendarId;
+ /** @var array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } */
+ private array $calendarData;
- /** @var array */
- private $calendarData;
+ /** @var list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> */
+ private array $oldShares;
- /** @var array */
- private $oldShares;
+ /** @var list<array{href: string, commonName: string, readOnly: bool}> */
+ private array $added;
- /** @var array */
- private $added;
-
- /** @var array */
- private $removed;
+ /** @var list<string> */
+ private array $removed;
/**
* CalendarShareUpdatedEvent constructor.
*
* @param int $calendarId
- * @param array $calendarData
- * @param array $oldShares
- * @param array $added
- * @param array $removed
+ * @param array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp } $calendarData
+ * @param list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}> $oldShares
+ * @param list<array{href: string, commonName: string, readOnly: bool}> $added
+ * @param list<string> $removed
* @since 20.0.0
*/
public function __construct(int $calendarId,
@@ -74,7 +74,6 @@ class CalendarShareUpdatedEvent extends Event {
}
/**
- * @return int
* @since 20.0.0
*/
public function getCalendarId(): int {
@@ -82,7 +81,7 @@ class CalendarShareUpdatedEvent extends Event {
}
/**
- * @return array
+ * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp }
* @since 20.0.0
*/
public function getCalendarData(): array {
@@ -90,7 +89,7 @@ class CalendarShareUpdatedEvent extends Event {
}
/**
- * @return array
+ * @return list<array{href: string, commonName: string, status: int, readOnly: bool, '{http://owncloud.org/ns}principal': string, '{http://owncloud.org/ns}group-share': bool}>
* @since 20.0.0
*/
public function getOldShares(): array {
@@ -98,7 +97,7 @@ class CalendarShareUpdatedEvent extends Event {
}
/**
- * @return array
+ * @return list<array{href: string, commonName: string, readOnly: bool}>
* @since 20.0.0
*/
public function getAdded(): array {
@@ -106,7 +105,7 @@ class CalendarShareUpdatedEvent extends Event {
}
/**
- * @return array
+ * @return list<string>
* @since 20.0.0
*/
public function getRemoved(): array {
diff --git a/apps/dav/lib/Events/CalendarUnpublishedEvent.php b/apps/dav/lib/Events/CalendarUnpublishedEvent.php
index 0cea53c6f0d..b2536fc7aef 100644
--- a/apps/dav/lib/Events/CalendarUnpublishedEvent.php
+++ b/apps/dav/lib/Events/CalendarUnpublishedEvent.php
@@ -6,6 +6,7 @@ declare(strict_types=1);
* @copyright Copyright (c) 2020, Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
+ * @author Thomas Citharel <nextcloud@tcit.fr>
*
* @license GNU AGPL version 3 or any later version
*
@@ -34,12 +35,8 @@ use OCP\EventDispatcher\Event;
* @since 20.0.0
*/
class CalendarUnpublishedEvent extends Event {
-
- /** @var int */
- private $calendarId;
-
- /** @var array */
- private $calendarData;
+ private int $calendarId;
+ private array $calendarData;
/**
* CalendarUnpublishedEvent constructor.
diff --git a/apps/dav/lib/Exception/ServerMaintenanceMode.php b/apps/dav/lib/Exception/ServerMaintenanceMode.php
new file mode 100644
index 00000000000..9dad9f2d4d1
--- /dev/null
+++ b/apps/dav/lib/Exception/ServerMaintenanceMode.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Exception;
+
+use Sabre\DAV\Exception\ServiceUnavailable;
+
+class ServerMaintenanceMode extends ServiceUnavailable {
+
+}
diff --git a/apps/dav/lib/Listener/ActivityUpdaterListener.php b/apps/dav/lib/Listener/ActivityUpdaterListener.php
index 371912ff035..ea3ec49c14d 100644
--- a/apps/dav/lib/Listener/ActivityUpdaterListener.php
+++ b/apps/dav/lib/Listener/ActivityUpdaterListener.php
@@ -32,6 +32,7 @@ use OCA\DAV\Events\CalendarDeletedEvent;
use OCA\DAV\Events\CalendarMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectCreatedEvent;
use OCA\DAV\Events\CalendarObjectDeletedEvent;
+use OCA\DAV\Events\CalendarObjectMovedEvent;
use OCA\DAV\Events\CalendarObjectMovedToTrashEvent;
use OCA\DAV\Events\CalendarObjectRestoredEvent;
use OCA\DAV\Events\CalendarObjectUpdatedEvent;
@@ -173,7 +174,26 @@ class ActivityUpdaterListener implements IEventListener {
);
$this->logger->debug(
- sprintf('Activity generated for deleted calendar object %d', $event->getCalendarId())
+ sprintf('Activity generated for updated 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,
+ ]);
+ }
+ } elseif ($event instanceof CalendarObjectMovedEvent) {
+ try {
+ $this->activityBackend->onMovedCalendarObject(
+ $event->getSourceCalendarData(),
+ $event->getTargetCalendarData(),
+ $event->getSourceShares(),
+ $event->getTargetShares(),
+ $event->getObjectData()
+ );
+
+ $this->logger->debug(
+ sprintf('Activity generated for moved calendar object from calendar %d to calendar %d', $event->getSourceCalendarId(), $event->getTargetCalendarId())
);
} catch (Throwable $e) {
// Any error with activities shouldn't abort the calendar deletion, so we just log it
diff --git a/apps/dav/lib/Listener/BirthdayListener.php b/apps/dav/lib/Listener/BirthdayListener.php
new file mode 100644
index 00000000000..43ad782fa9e
--- /dev/null
+++ b/apps/dav/lib/Listener/BirthdayListener.php
@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @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\Listener;
+
+use OCA\DAV\CalDAV\BirthdayService;
+use OCA\DAV\Events\CardCreatedEvent;
+use OCA\DAV\Events\CardDeletedEvent;
+use OCA\DAV\Events\CardUpdatedEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+
+class BirthdayListener implements IEventListener {
+ private BirthdayService $birthdayService;
+
+ public function __construct(BirthdayService $birthdayService) {
+ $this->birthdayService = $birthdayService;
+ }
+
+ public function handle(Event $event): void {
+ if ($event instanceof CardCreatedEvent || $event instanceof CardUpdatedEvent) {
+ $cardData = $event->getCardData();
+
+ $this->birthdayService->onCardChanged($event->getAddressBookId(), $cardData['uri'], $cardData['carddata']);
+ }
+
+ if ($event instanceof CardDeletedEvent) {
+ $cardData = $event->getCardData();
+ $this->birthdayService->onCardDeleted($event->getAddressBookId(), $cardData['uri']);
+ }
+ }
+}
diff --git a/apps/dav/lib/Listener/CalendarPublicationListener.php b/apps/dav/lib/Listener/CalendarPublicationListener.php
new file mode 100644
index 00000000000..1453694d6fb
--- /dev/null
+++ b/apps/dav/lib/Listener/CalendarPublicationListener.php
@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @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\Listener;
+
+use OCA\DAV\CalDAV\Activity\Backend;
+use OCA\DAV\Events\CalendarPublishedEvent;
+use OCA\DAV\Events\CalendarUnpublishedEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+class CalendarPublicationListener implements IEventListener {
+ private Backend $activityBackend;
+ private LoggerInterface $logger;
+
+ public function __construct(Backend $activityBackend,
+ LoggerInterface $logger) {
+ $this->activityBackend = $activityBackend;
+ $this->logger = $logger;
+ }
+
+ /**
+ * In case the user has set their default calendar to the deleted one
+ */
+ public function handle(Event $event): void {
+ if ($event instanceof CalendarPublishedEvent) {
+ $this->logger->debug('Creating activity for Calendar being published');
+
+ $this->activityBackend->onCalendarPublication(
+ $event->getCalendarData(),
+ true
+ );
+ } elseif ($event instanceof CalendarUnpublishedEvent) {
+ $this->logger->debug('Creating activity for Calendar being unpublished');
+
+ $this->activityBackend->onCalendarPublication(
+ $event->getCalendarData(),
+ false
+ );
+ }
+ }
+}
diff --git a/apps/dav/lib/Listener/CalendarShareUpdateListener.php b/apps/dav/lib/Listener/CalendarShareUpdateListener.php
new file mode 100644
index 00000000000..88865759162
--- /dev/null
+++ b/apps/dav/lib/Listener/CalendarShareUpdateListener.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @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\Listener;
+
+use OCA\DAV\CalDAV\Activity\Backend;
+use OCA\DAV\Events\CalendarShareUpdatedEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+class CalendarShareUpdateListener implements IEventListener {
+ private Backend $activityBackend;
+ private LoggerInterface $logger;
+
+ public function __construct(Backend $activityBackend,
+ LoggerInterface $logger) {
+ $this->activityBackend = $activityBackend;
+ $this->logger = $logger;
+ }
+
+ /**
+ * In case the user has set their default calendar to the deleted one
+ */
+ public function handle(Event $event): void {
+ if (!($event instanceof CalendarShareUpdatedEvent)) {
+ // Not what we subscribed to
+ return;
+ }
+
+ $this->logger->debug("Creating activity for Calendar having it's shares updated");
+
+ $this->activityBackend->onCalendarUpdateShares(
+ $event->getCalendarData(),
+ $event->getOldShares(),
+ $event->getAdded(),
+ $event->getRemoved()
+ );
+ }
+}
diff --git a/apps/dav/lib/Listener/ClearPhotoCacheListener.php b/apps/dav/lib/Listener/ClearPhotoCacheListener.php
new file mode 100644
index 00000000000..ed02770e35d
--- /dev/null
+++ b/apps/dav/lib/Listener/ClearPhotoCacheListener.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @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\Listener;
+
+use OCA\DAV\CardDAV\PhotoCache;
+use OCA\DAV\Events\CardDeletedEvent;
+use OCA\DAV\Events\CardUpdatedEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+
+class ClearPhotoCacheListener implements IEventListener {
+ private PhotoCache $photoCache;
+
+ public function __construct(PhotoCache $photoCache) {
+ $this->photoCache = $photoCache;
+ }
+
+ public function handle(Event $event): void {
+ if ($event instanceof CardUpdatedEvent || $event instanceof CardDeletedEvent) {
+ $cardData = $event->getCardData();
+
+ $this->photoCache->delete($event->getAddressBookId(), $cardData['uri']);
+ }
+ }
+}
diff --git a/apps/dav/lib/Listener/SubscriptionListener.php b/apps/dav/lib/Listener/SubscriptionListener.php
new file mode 100644
index 00000000000..36db234aa05
--- /dev/null
+++ b/apps/dav/lib/Listener/SubscriptionListener.php
@@ -0,0 +1,85 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @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\Listener;
+
+use OCA\DAV\BackgroundJob\RefreshWebcalJob;
+use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend;
+use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService;
+use OCA\DAV\Events\SubscriptionCreatedEvent;
+use OCA\DAV\Events\SubscriptionDeletedEvent;
+use OCP\BackgroundJob\IJobList;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use Psr\Log\LoggerInterface;
+
+class SubscriptionListener implements IEventListener {
+ private IJobList $jobList;
+ private RefreshWebcalService $refreshWebcalService;
+ private ReminderBackend $reminderBackend;
+ private LoggerInterface $logger;
+
+ public function __construct(IJobList $jobList, RefreshWebcalService $refreshWebcalService, ReminderBackend $reminderBackend,
+ LoggerInterface $logger) {
+ $this->jobList = $jobList;
+ $this->refreshWebcalService = $refreshWebcalService;
+ $this->reminderBackend = $reminderBackend;
+ $this->logger = $logger;
+ }
+
+ /**
+ * In case the user has set their default calendar to the deleted one
+ */
+ public function handle(Event $event): void {
+ if ($event instanceof SubscriptionCreatedEvent) {
+ $subscriptionId = $event->getSubscriptionId();
+ $subscriptionData = $event->getSubscriptionData();
+
+ $this->logger->debug('Refreshing webcal data for subscription ' . $subscriptionId);
+ $this->refreshWebcalService->refreshSubscription(
+ (string)$subscriptionData['principaluri'],
+ (string)$subscriptionData['uri']
+ );
+
+ $this->logger->debug('Scheduling webcal data refreshment for subscription ' . $subscriptionId);
+ $this->jobList->add(RefreshWebcalJob::class, [
+ 'principaluri' => $subscriptionData['principaluri'],
+ 'uri' => $subscriptionData['uri']
+ ]);
+ } elseif ($event instanceof SubscriptionDeletedEvent) {
+ $subscriptionId = $event->getSubscriptionId();
+ $subscriptionData = $event->getSubscriptionData();
+
+ $this->logger->debug('Removing refresh webcal job for subscription ' . $subscriptionId);
+ $this->jobList->remove(RefreshWebcalJob::class, [
+ 'principaluri' => $subscriptionData['principaluri'],
+ 'uri' => $subscriptionData['uri']
+ ]);
+
+ $this->logger->debug('Cleaning all reminders for subscription ' . $subscriptionId);
+ $this->reminderBackend->cleanRemindersForCalendar($subscriptionId);
+ }
+ }
+}
diff --git a/apps/dav/lib/Listener/TrustedServerRemovedListener.php b/apps/dav/lib/Listener/TrustedServerRemovedListener.php
new file mode 100644
index 00000000000..29ff050983b
--- /dev/null
+++ b/apps/dav/lib/Listener/TrustedServerRemovedListener.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Carl Schwan <carl@carlschwan.eu>
+ *
+ * @author Carl Schwan <carl@carlschwan.eu>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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\Listener;
+
+use OCA\DAV\CardDAV\CardDavBackend;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Federation\Events\TrustedServerRemovedEvent;
+
+class TrustedServerRemovedListener implements IEventListener {
+ private CardDavBackend $cardDavBackend;
+
+ public function __construct(CardDavBackend $cardDavBackend) {
+ $this->cardDavBackend = $cardDavBackend;
+ }
+
+ public function handle(Event $event): void {
+ if (!$event instanceof TrustedServerRemovedEvent) {
+ return;
+ }
+ $addressBookUri = $event->getUrlHash();
+ $addressBook = $this->cardDavBackend->getAddressBooksByUri('principals/system/system', $addressBookUri);
+ if (!is_null($addressBook)) {
+ $this->cardDavBackend->deleteAddressBook($addressBook['id']);
+ }
+ }
+}
diff --git a/apps/dav/lib/Listener/UserPreferenceListener.php b/apps/dav/lib/Listener/UserPreferenceListener.php
new file mode 100644
index 00000000000..947f6d3fd01
--- /dev/null
+++ b/apps/dav/lib/Listener/UserPreferenceListener.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Listener;
+
+use OCA\DAV\BackgroundJob\UserStatusAutomation;
+use OCP\BackgroundJob\IJobList;
+use OCP\Config\BeforePreferenceDeletedEvent;
+use OCP\Config\BeforePreferenceSetEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+
+class UserPreferenceListener implements IEventListener {
+
+ protected IJobList $jobList;
+
+ public function __construct(IJobList $jobList) {
+ $this->jobList = $jobList;
+ }
+
+ public function handle(Event $event): void {
+ if ($event instanceof BeforePreferenceSetEvent) {
+ if ($event->getAppId() === 'dav' && $event->getConfigKey() === 'user_status_automation' && $event->getConfigValue() === 'yes') {
+ $event->setValid(true);
+
+ // Not the cleanest way, but we just add the job in the before event.
+ // If something ever turns wrong the first execution will remove the job again.
+ // We also first delete the current job, so the next run time is reset.
+ $this->jobList->remove(UserStatusAutomation::class, ['userId' => $event->getUserId()]);
+ $this->jobList->add(UserStatusAutomation::class, ['userId' => $event->getUserId()]);
+ }
+ } elseif ($event instanceof BeforePreferenceDeletedEvent) {
+ if ($event->getAppId() === 'dav' && $event->getConfigKey() === 'user_status_automation') {
+ $event->setValid(true);
+ }
+ }
+ }
+}
diff --git a/apps/dav/lib/Migration/RemoveObjectProperties.php b/apps/dav/lib/Migration/RemoveObjectProperties.php
new file mode 100644
index 00000000000..c72dfbebfea
--- /dev/null
+++ b/apps/dav/lib/Migration/RemoveObjectProperties.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @copyright Copyright (c) 2021, Thomas Citharel <nextcloud@tcit.fr>.
+ *
+ * @author Thomas Citharel <nextcloud@tcit.fr>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\DAV\Migration;
+
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class RemoveObjectProperties implements IRepairStep {
+ private const RESOURCE_TYPE_PROPERTY = '{DAV:}resourcetype';
+ private const ME_CARD_PROPERTY = '{http://calendarserver.org/ns/}me-card';
+ private const CALENDAR_TRANSP_PROPERTY = '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp';
+
+ /** @var IDBConnection */
+ private $connection;
+
+ /**
+ * RemoveObjectProperties constructor.
+ *
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getName() {
+ return 'Remove invalid object properties';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function run(IOutput $output) {
+ $query = $this->connection->getQueryBuilder();
+ $updated = $query->delete('properties')
+ ->where($query->expr()->in('propertyname', $query->createNamedParameter([self::RESOURCE_TYPE_PROPERTY, self::ME_CARD_PROPERTY, self::CALENDAR_TRANSP_PROPERTY], IQueryBuilder::PARAM_STR_ARRAY)))
+ ->andWhere($query->expr()->eq('propertyvalue', $query->createNamedParameter('Object')))
+ ->executeStatement();
+
+ $output->info("$updated invalid object properties removed.");
+ }
+}
diff --git a/apps/dav/lib/Migration/Version1024Date20211221144219.php b/apps/dav/lib/Migration/Version1024Date20211221144219.php
new file mode 100644
index 00000000000..b93f8ac801e
--- /dev/null
+++ b/apps/dav/lib/Migration/Version1024Date20211221144219.php
@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DAV\Migration;
+
+use Closure;
+use Doctrine\DBAL\Schema\SchemaException;
+use OCA\DAV\DAV\CustomPropertiesBackend;
+use OCP\DB\ISchemaWrapper;
+use OCP\DB\Types;
+use OCP\Migration\IOutput;
+use OCP\Migration\SimpleMigrationStep;
+
+/**
+ * Auto-generated migration step: Please modify to your needs!
+ */
+class Version1024Date20211221144219 extends SimpleMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ */
+ public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ * @return null|ISchemaWrapper
+ * @throws SchemaException
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+ $propertiesTable = $schema->getTable('properties');
+
+ if ($propertiesTable->hasColumn('valuetype')) {
+ return null;
+ }
+ $propertiesTable->addColumn('valuetype', Types::SMALLINT, [
+ 'notnull' => false,
+ 'default' => CustomPropertiesBackend::PROPERTY_TYPE_STRING
+ ]);
+
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+}
diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php
index 8a11a676609..29ab65d46a9 100644
--- a/apps/dav/lib/RootCollection.php
+++ b/apps/dav/lib/RootCollection.php
@@ -7,6 +7,7 @@
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Roeland Jago Douma <roeland@famdouma.nl>
+ * @author Thomas Citharel <nextcloud@tcit.fr>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
@@ -43,6 +44,7 @@ use OCA\DAV\DAV\GroupPrincipalBackend;
use OCA\DAV\DAV\SystemPrincipalBackend;
use OCA\DAV\Provisioning\Apple\AppleProvisioningNode;
use OCA\DAV\Upload\CleanupService;
+use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
@@ -61,13 +63,13 @@ class RootCollection extends SimpleCollection {
$shareManager = \OC::$server->getShareManager();
$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(
$userManager,
$groupManager,
+ \OC::$server->get(IAccountManager::class),
$shareManager,
\OC::$server->getUserSession(),
\OC::$server->getAppManager(),
@@ -105,7 +107,6 @@ class RootCollection extends SimpleCollection {
$random,
$logger,
$dispatcher,
- $legacyDispatcher,
$config
);
$userCalendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users', $logger);
@@ -140,11 +141,11 @@ class RootCollection extends SimpleCollection {
);
$pluginManager = new PluginManager(\OC::$server, \OC::$server->query(IAppManager::class));
- $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher, $legacyDispatcher);
+ $usersCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
$usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, 'principals/users');
$usersAddressBookRoot->disableListing = $disableListing;
- $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher, $legacyDispatcher);
+ $systemCardDavBackend = new CardDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $dispatcher);
$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, 'principals/system');
$systemAddressBookRoot->disableListing = $disableListing;
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 5b532465aba..2cfcb3f5393 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -62,6 +62,7 @@ use OCA\DAV\Connector\Sabre\SharesPlugin;
use OCA\DAV\Connector\Sabre\TagsPlugin;
use OCA\DAV\DAV\CustomPropertiesBackend;
use OCA\DAV\DAV\PublicAuth;
+use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Events\SabrePluginAuthInitEvent;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\LazySearchBackend;
@@ -229,6 +230,11 @@ class Server {
$this->server->addPlugin(new FakeLockerPlugin());
}
+ // Allow view-only plugin for webdav requests
+ $this->server->addPlugin(new ViewOnlyPlugin(
+ $logger
+ ));
+
if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
$this->server->addPlugin(new BrowserErrorPagePlugin());
}
diff --git a/apps/dav/lib/Settings/AvailabilitySettings.php b/apps/dav/lib/Settings/AvailabilitySettings.php
index 9a163e21edb..d2b75ba4866 100644
--- a/apps/dav/lib/Settings/AvailabilitySettings.php
+++ b/apps/dav/lib/Settings/AvailabilitySettings.php
@@ -27,10 +27,34 @@ namespace OCA\DAV\Settings;
use OCA\DAV\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\IConfig;
use OCP\Settings\ISettings;
class AvailabilitySettings implements ISettings {
+ protected IConfig $config;
+ protected IInitialState $initialState;
+ protected ?string $userId;
+
+ public function __construct(IConfig $config,
+ IInitialState $initialState,
+ ?string $userId) {
+ $this->config = $config;
+ $this->initialState = $initialState;
+ $this->userId = $userId;
+ }
+
public function getForm(): TemplateResponse {
+ $this->initialState->provideInitialState(
+ 'user_status_automation',
+ $this->config->getUserValue(
+ $this->userId,
+ 'dav',
+ 'user_status_automation',
+ 'no'
+ )
+ );
+
return new TemplateResponse(Application::APP_ID, 'settings-personal-availability');
}
diff --git a/apps/dav/lib/UserMigration/ContactsMigrator.php b/apps/dav/lib/UserMigration/ContactsMigrator.php
index ae1a61ce4f4..d2ba82eb2e5 100644
--- a/apps/dav/lib/UserMigration/ContactsMigrator.php
+++ b/apps/dav/lib/UserMigration/ContactsMigrator.php
@@ -131,6 +131,10 @@ class ContactsMigrator implements IMigrator, ISizeEstimationMigrator {
}
}
+ if (count($vCards) === 0) {
+ throw new InvalidAddressBookException();
+ }
+
return [
'name' => $addressBookNode->getName(),
'displayName' => $addressBookInfo['{DAV:}displayname'],