Skip to content

Commit

Permalink
Merge pull request #32361 from nextcloud/dav-scheduling-default-calendar
Browse files Browse the repository at this point in the history
Put calendar invites into the user's first available calendar
  • Loading branch information
PVince81 authored Jun 10, 2022
2 parents 8e61671 + a7b2e8a commit dcfdcf9
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 25 deletions.
9 changes: 8 additions & 1 deletion apps/dav/lib/CalDAV/Calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ private function isPublic() {
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;
}
Expand All @@ -412,6 +412,13 @@ public function isSubscription() {
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
*/
Expand Down
2 changes: 1 addition & 1 deletion apps/dav/lib/CalDAV/PublicCalendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function getMultipleChildren(array $paths) {
* public calendars are always shared
* @return bool
*/
protected function isShared() {
public function isShared() {
return true;
}
}
44 changes: 39 additions & 5 deletions apps/dav/lib/CalDAV/Schedule/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

use DateTimeZone;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\CalendarHome;
use OCP\IConfig;
use Sabre\CalDAV\ICalendar;
Expand Down Expand Up @@ -299,12 +300,14 @@ public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) {
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 {
Expand All @@ -316,9 +319,40 @@ public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) {
/** @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);
Expand Down
85 changes: 67 additions & 18 deletions apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@
namespace OCA\DAV\Tests\unit\CalDAV\Schedule;

use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\CalDAV\Plugin as CalDAVPlugin;
use OCA\DAV\CalDAV\Schedule\Plugin;
use OCA\DAV\CalDAV\Trashbin\Plugin as TrashbinPlugin;
use OCP\IConfig;
use OCP\IL10N;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\Tree;
Expand Down Expand Up @@ -73,17 +77,22 @@ protected function setUp(): void {
public function testInitialize() {
$plugin = new Plugin($this->config);

$this->server->expects($this->at(7))
$this->server->expects($this->exactly(10))
->method('on')
->with('propFind', [$plugin, 'propFindDefaultCalendarUrl'], 90);

$this->server->expects($this->at(8))
->method('on')
->with('afterWriteContent', [$plugin, 'dispatchSchedulingResponses']);

$this->server->expects($this->at(9))
->method('on')
->with('afterCreateFile', [$plugin, 'dispatchSchedulingResponses']);
->withConsecutive(
// Sabre\CalDAV\Schedule\Plugin events
['method:POST', [$plugin, 'httpPost']],
['propFind', [$plugin, 'propFind']],
['propPatch', [$plugin, 'propPatch']],
['calendarObjectChange', [$plugin, 'calendarObjectChange']],
['beforeUnbind', [$plugin, 'beforeUnbind']],
['schedule', [$plugin, 'scheduleLocalDelivery']],
['getSupportedPrivilegeSet', [$plugin, 'getSupportedPrivilegeSet']],
// OCA\DAV\CalDAV\Schedule\Plugin events
['propFind', [$plugin, 'propFindDefaultCalendarUrl'], 90],
['afterWriteContent', [$plugin, 'dispatchSchedulingResponses']],
['afterCreateFile', [$plugin, 'dispatchSchedulingResponses']]
);

$plugin->initialize($this->server);
}
Expand Down Expand Up @@ -177,6 +186,15 @@ public function propFindDefaultCalendarUrlProvider(): array {
CalDavBackend::PERSONAL_CALENDAR_NAME,
true
],
[
'principals/users/myuser',
'calendars/myuser',
false,
CalDavBackend::PERSONAL_CALENDAR_URI,
CalDavBackend::PERSONAL_CALENDAR_NAME,
false,
true
],
[
'principals/users/myuser',
'calendars/myuser',
Expand All @@ -201,6 +219,7 @@ public function propFindDefaultCalendarUrlProvider(): array {
CalDavBackend::PERSONAL_CALENDAR_NAME,
true,
false,
false,
],
[
'principals/users/myuser',
Expand Down Expand Up @@ -240,14 +259,14 @@ public function propFindDefaultCalendarUrlProvider(): array {
/**
* @dataProvider propFindDefaultCalendarUrlProvider
* @param string $principalUri
* @param string $calendarHome
* @param string|null $calendarHome
* @param bool $isResource
* @param string $calendarUri
* @param string $displayName
* @param bool $exists
* @param bool $propertiesForPath
*/
public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $propertiesForPath = true) {
public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $hasExistingCalendars = false, bool $propertiesForPath = true) {
/** @var PropFind $propFind */
$propFind = new PropFind(
$principalUri,
Expand Down Expand Up @@ -290,6 +309,7 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca
$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
return;
}

if (!$isResource) {
$this->config->expects($this->once())
->method('getUserValue')
Expand All @@ -303,18 +323,47 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca
->with($calendarUri)
->willReturn($exists);

$calendarBackend = $this->createMock(CalDavBackend::class);
$calendarUri = $hasExistingCalendars ? 'custom' : $calendarUri;
$displayName = $hasExistingCalendars ? 'Custom Calendar' : $displayName;

$existingCalendars = $hasExistingCalendars ? [
new Calendar(
$calendarBackend,
['uri' => 'deleted', '{DAV:}displayname' => 'A deleted calendar', TrashbinPlugin::PROPERTY_DELETED_AT => 42],
$this->createMock(IL10N::class),
$this->config,
$this->createMock(LoggerInterface::class)
),
new Calendar(
$calendarBackend,
['uri' => $calendarUri, '{DAV:}displayname' => $displayName],
$this->createMock(IL10N::class),
$this->config,
$this->createMock(LoggerInterface::class)
)
] : [];

if (!$exists) {
$calendarBackend = $this->createMock(CalDavBackend::class);
$calendarBackend->expects($this->once())
if (!$hasExistingCalendars) {
$calendarBackend->expects($this->once())
->method('createCalendar')
->with($principalUri, $calendarUri, [
'{DAV:}displayname' => $displayName,
]);

$calendarHomeObject->expects($this->once())
->method('getCalDAVBackend')
->with()
->willReturn($calendarBackend);
$calendarHomeObject->expects($this->once())
->method('getCalDAVBackend')
->with()
->willReturn($calendarBackend);
}

if (!$isResource) {
$calendarHomeObject->expects($this->once())
->method('getChildren')
->with()
->willReturn($existingCalendars);
}
}

/** @var Tree|MockObject $tree */
Expand Down

0 comments on commit dcfdcf9

Please sign in to comment.