If there's no default calendar and we can't find anything with URI 'personal', instead of creating a new one, start by using the first "real personal calendar" available. If not, then we create the default one. Signed-off-by: Thomas Citharel <tcit@tcit.fr>tags/v25.0.0beta1
@@ -400,7 +400,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 +412,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 | |||
*/ |
@@ -84,7 +84,7 @@ class PublicCalendar extends Calendar { | |||
* public calendars are always shared | |||
* @return bool | |||
*/ | |||
protected function isShared() { | |||
public function isShared() { | |||
return true; | |||
} | |||
} |
@@ -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; | |||
@@ -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); |
@@ -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; | |||
@@ -177,6 +181,15 @@ class PluginTest extends TestCase { | |||
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', | |||
@@ -201,6 +214,7 @@ class PluginTest extends TestCase { | |||
CalDavBackend::PERSONAL_CALENDAR_NAME, | |||
true, | |||
false, | |||
false, | |||
], | |||
[ | |||
'principals/users/myuser', | |||
@@ -240,14 +254,14 @@ class PluginTest extends TestCase { | |||
/** | |||
* @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, | |||
@@ -290,6 +304,7 @@ class PluginTest extends TestCase { | |||
$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL)); | |||
return; | |||
} | |||
if (!$isResource) { | |||
$this->config->expects($this->once()) | |||
->method('getUserValue') | |||
@@ -303,18 +318,47 @@ class PluginTest extends TestCase { | |||
->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 */ |