Browse Source
Merge pull request #35121 from nextcloud/feat/dav-wrap-app-calendars
Merge pull request #35121 from nextcloud/feat/dav-wrap-app-calendars
Feature: Provide access to app generated calendars through CalDAVpull/37935/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 758 additions and 19 deletions
-
3apps/dav/composer/composer/autoload_classmap.php
-
3apps/dav/composer/composer/autoload_static.php
-
14apps/dav/lib/AppInfo/Application.php
-
5apps/dav/lib/AppInfo/PluginManager.php
-
208apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php
-
74apps/dav/lib/CalDAV/AppCalendar/AppCalendarPlugin.php
-
153apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php
-
1apps/dav/lib/CalDAV/CalendarProvider.php
-
25apps/dav/tests/unit/AppInfo/PluginManagerTest.php
-
123apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php
-
166apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php
-
2apps/dav/tests/unit/Command/DeleteCalendarTest.php
@ -0,0 +1,208 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de> |
|||
* |
|||
* @author Ferdinand Thiessen <opensource@fthiessen.de> |
|||
* |
|||
* @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\CalDAV\AppCalendar; |
|||
|
|||
use OCA\DAV\CalDAV\Plugin; |
|||
use OCA\DAV\CalDAV\Integration\ExternalCalendar; |
|||
use OCP\Calendar\ICalendar; |
|||
use OCP\Calendar\ICreateFromString; |
|||
use OCP\Constants; |
|||
use Sabre\CalDAV\CalendarQueryValidator; |
|||
use Sabre\CalDAV\ICalendarObject; |
|||
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; |
|||
use Sabre\DAV\Exception\Forbidden; |
|||
use Sabre\DAV\Exception\NotFound; |
|||
use Sabre\DAV\PropPatch; |
|||
use Sabre\VObject\Component\VCalendar; |
|||
use Sabre\VObject\Reader; |
|||
|
|||
class AppCalendar extends ExternalCalendar { |
|||
protected string $principal; |
|||
protected ICalendar $calendar; |
|||
|
|||
public function __construct(string $appId, ICalendar $calendar, string $principal) { |
|||
parent::__construct($appId, $calendar->getUri()); |
|||
$this->principal = $principal; |
|||
$this->calendar = $calendar; |
|||
} |
|||
|
|||
/** |
|||
* Return permissions supported by the backend calendar |
|||
* @return int Permissions based on \OCP\Constants |
|||
*/ |
|||
public function getPermissions(): int { |
|||
// Make sure to only promote write support if the backend implement the correct interface
|
|||
if ($this->calendar instanceof ICreateFromString) { |
|||
return $this->calendar->getPermissions(); |
|||
} |
|||
return Constants::PERMISSION_READ; |
|||
} |
|||
|
|||
public function getOwner(): ?string { |
|||
return $this->principal; |
|||
} |
|||
|
|||
public function getGroup(): ?string { |
|||
return null; |
|||
} |
|||
|
|||
public function getACL(): array { |
|||
$acl = [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}write-properties', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
] |
|||
]; |
|||
if ($this->getPermissions() & Constants::PERMISSION_CREATE) { |
|||
$acl[] = [ |
|||
'privilege' => '{DAV:}write', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
]; |
|||
} |
|||
return $acl; |
|||
} |
|||
|
|||
public function setACL(array $acl): void { |
|||
throw new Forbidden('Setting ACL is not supported on this node'); |
|||
} |
|||
|
|||
public function getSupportedPrivilegeSet(): ?array { |
|||
// Use the default one
|
|||
return null; |
|||
} |
|||
|
|||
public function getLastModified(): ?int { |
|||
// unknown
|
|||
return null; |
|||
} |
|||
|
|||
public function delete(): void { |
|||
// No method for deleting a calendar in OCP\Calendar\ICalendar
|
|||
throw new Forbidden('Deleting an entry is not implemented'); |
|||
} |
|||
|
|||
public function createFile($name, $data = null) { |
|||
if ($this->calendar instanceof ICreateFromString) { |
|||
if (is_resource($data)) { |
|||
$data = stream_get_contents($data) ?: null; |
|||
} |
|||
$this->calendar->createFromString($name, is_null($data) ? '' : $data); |
|||
return null; |
|||
} else { |
|||
throw new Forbidden('Creating a new entry is not allowed'); |
|||
} |
|||
} |
|||
|
|||
public function getProperties($properties) { |
|||
return [ |
|||
'{DAV:}displayname' => $this->calendar->getDisplayName() ?: $this->calendar->getKey(), |
|||
'{http://apple.com/ns/ical/}calendar-color' => $this->calendar->getDisplayColor() ?: '#0082c9', |
|||
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VEVENT', 'VJOURNAL', 'VTODO']), |
|||
]; |
|||
} |
|||
|
|||
public function calendarQuery(array $filters) { |
|||
$result = []; |
|||
$objects = $this->getChildren(); |
|||
|
|||
foreach ($objects as $object) { |
|||
if ($this->validateFilterForObject($object, $filters)) { |
|||
$result[] = $object->getName(); |
|||
} |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
protected function validateFilterForObject(ICalendarObject $object, array $filters): bool { |
|||
/** @var \Sabre\VObject\Component\VCalendar */ |
|||
$vObject = Reader::read($object->get()); |
|||
|
|||
$validator = new CalendarQueryValidator(); |
|||
$result = $validator->validate($vObject, $filters); |
|||
|
|||
// Destroy circular references so PHP will GC the object.
|
|||
$vObject->destroy(); |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
public function childExists($name): bool { |
|||
try { |
|||
$this->getChild($name); |
|||
return true; |
|||
} catch (NotFound $error) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public function getChild($name) { |
|||
// Try to get calendar by filename
|
|||
$children = $this->calendar->search($name, ['X-FILENAME']); |
|||
if (count($children) === 0) { |
|||
// If nothing found try to get by UID from filename
|
|||
$pos = strrpos($name, '.ics'); |
|||
$children = $this->calendar->search(substr($name, 0, $pos ?: null), ['UID']); |
|||
} |
|||
|
|||
if (count($children) > 0) { |
|||
return new CalendarObject($this, $this->calendar, new VCalendar($children)); |
|||
} |
|||
|
|||
throw new NotFound('Node not found'); |
|||
} |
|||
|
|||
/** |
|||
* @return ICalendarObject[] |
|||
*/ |
|||
public function getChildren(): array { |
|||
$objects = $this->calendar->search(''); |
|||
// We need to group by UID (actually by filename but we do not have that information)
|
|||
$result = []; |
|||
foreach ($objects as $object) { |
|||
$uid = (string)$object['UID'] ?: uniqid(); |
|||
if (!isset($result[$uid])) { |
|||
$result[$uid] = []; |
|||
} |
|||
$result[$uid][] = $object; |
|||
} |
|||
|
|||
return array_map(function (array $children) { |
|||
return new CalendarObject($this, $this->calendar, new VCalendar($children)); |
|||
}, $result); |
|||
} |
|||
|
|||
public function propPatch(PropPatch $propPatch): void { |
|||
// no setDisplayColor or setDisplayName in \OCP\Calendar\ICalendar
|
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de> |
|||
* |
|||
* @author Ferdinand Thiessen <opensource@fthiessen.de> |
|||
* |
|||
* @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\CalDAV\AppCalendar; |
|||
|
|||
use OCA\DAV\CalDAV\Integration\ExternalCalendar; |
|||
use OCA\DAV\CalDAV\Integration\ICalendarProvider; |
|||
use OCP\Calendar\IManager; |
|||
use Psr\Log\LoggerInterface; |
|||
|
|||
/* Plugin for wrapping application generated calendars registered in nextcloud core (OCP\Calendar\ICalendarProvider) */ |
|||
class AppCalendarPlugin implements ICalendarProvider { |
|||
protected IManager $manager; |
|||
protected LoggerInterface $logger; |
|||
|
|||
public function __construct(IManager $manager, LoggerInterface $logger) { |
|||
$this->manager = $manager; |
|||
$this->logger = $logger; |
|||
} |
|||
|
|||
public function getAppID(): string { |
|||
return 'dav-wrapper'; |
|||
} |
|||
|
|||
public function fetchAllForCalendarHome(string $principalUri): array { |
|||
return array_map(function ($calendar) use (&$principalUri) { |
|||
return new AppCalendar($this->getAppID(), $calendar, $principalUri); |
|||
}, $this->getWrappedCalendars($principalUri)); |
|||
} |
|||
|
|||
public function hasCalendarInCalendarHome(string $principalUri, string $calendarUri): bool { |
|||
return count($this->getWrappedCalendars($principalUri, [ $calendarUri ])) > 0; |
|||
} |
|||
|
|||
public function getCalendarInCalendarHome(string $principalUri, string $calendarUri): ?ExternalCalendar { |
|||
$calendars = $this->getWrappedCalendars($principalUri, [ $calendarUri ]); |
|||
if (count($calendars) > 0) { |
|||
return new AppCalendar($this->getAppID(), $calendars[0], $principalUri); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
protected function getWrappedCalendars(string $principalUri, array $calendarUris = []): array { |
|||
return array_values( |
|||
array_filter($this->manager->getCalendarsForPrincipal($principalUri, $calendarUris), function ($c) { |
|||
// We must not provide a wrapper for DAV calendars
|
|||
return ! ($c instanceof \OCA\DAV\CalDAV\CalendarImpl); |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* @copyright 2023 Ferdinand Thiessen <opensource@fthiessen.de> |
|||
* |
|||
* @author Ferdinand Thiessen <opensource@fthiessen.de> |
|||
* |
|||
* @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\CalDAV\AppCalendar; |
|||
|
|||
use OCP\Calendar\ICalendar; |
|||
use OCP\Calendar\ICreateFromString; |
|||
use OCP\Constants; |
|||
use Sabre\CalDAV\ICalendarObject; |
|||
use Sabre\DAV\Exception\Forbidden; |
|||
use Sabre\DAV\Exception\NotFound; |
|||
use Sabre\DAVACL\IACL; |
|||
use Sabre\VObject\Component\VCalendar; |
|||
use Sabre\VObject\Property\ICalendar\DateTime; |
|||
|
|||
class CalendarObject implements ICalendarObject, IACL { |
|||
private VCalendar $vobject; |
|||
private AppCalendar $calendar; |
|||
private ICalendar|ICreateFromString $backend; |
|||
|
|||
public function __construct(AppCalendar $calendar, ICalendar $backend, VCalendar $vobject) { |
|||
$this->backend = $backend; |
|||
$this->calendar = $calendar; |
|||
$this->vobject = $vobject; |
|||
} |
|||
|
|||
public function getOwner() { |
|||
return $this->calendar->getOwner(); |
|||
} |
|||
|
|||
public function getGroup() { |
|||
return $this->calendar->getGroup(); |
|||
} |
|||
|
|||
public function getACL(): array { |
|||
$acl = [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
] |
|||
]; |
|||
if ($this->calendar->getPermissions() & Constants::PERMISSION_UPDATE) { |
|||
$acl[] = [ |
|||
'privilege' => '{DAV:}write-content', |
|||
'principal' => $this->getOwner(), |
|||
'protected' => true, |
|||
]; |
|||
} |
|||
return $acl; |
|||
} |
|||
|
|||
public function setACL(array $acl): void { |
|||
throw new Forbidden('Setting ACL is not supported on this node'); |
|||
} |
|||
|
|||
public function getSupportedPrivilegeSet(): ?array { |
|||
return null; |
|||
} |
|||
|
|||
public function put($data): void { |
|||
if ($this->backend instanceof ICreateFromString && $this->calendar->getPermissions() & Constants::PERMISSION_UPDATE) { |
|||
if (is_resource($data)) { |
|||
$data = stream_get_contents($data) ?: ''; |
|||
} |
|||
$this->backend->createFromString($this->getName(), $data); |
|||
} else { |
|||
throw new Forbidden('This calendar-object is read-only'); |
|||
} |
|||
} |
|||
|
|||
public function get(): string { |
|||
return $this->vobject->serialize(); |
|||
} |
|||
|
|||
public function getContentType(): string { |
|||
return 'text/calendar; charset=utf-8'; |
|||
} |
|||
|
|||
public function getETag(): ?string { |
|||
return null; |
|||
} |
|||
|
|||
public function getSize() { |
|||
return mb_strlen($this->vobject->serialize()); |
|||
} |
|||
|
|||
public function delete(): void { |
|||
if ($this->backend instanceof ICreateFromString && $this->calendar->getPermissions() & Constants::PERMISSION_DELETE) { |
|||
/** @var \Sabre\VObject\Component[] */ |
|||
$components = $this->vobject->getBaseComponents(); |
|||
foreach ($components as $key => $component) { |
|||
$components[$key]->STATUS = 'CANCELLED'; |
|||
$components[$key]->SEQUENCE = isset($component->SEQUENCE) ? ((int)$component->SEQUENCE->getValue()) + 1 : 1; |
|||
if ($component->name === 'VEVENT') { |
|||
$components[$key]->METHOD = 'CANCEL'; |
|||
} |
|||
} |
|||
$this->backend->createFromString($this->getName(), (new VCalendar($components))->serialize()); |
|||
} else { |
|||
throw new Forbidden('This calendar-object is read-only'); |
|||
} |
|||
} |
|||
|
|||
public function getName(): string { |
|||
// Every object is required to have an UID
|
|||
$base = $this->vobject->getBaseComponent(); |
|||
// This should never happen except the app provides invalid calendars (VEvent, VTodo... all require UID to be present)
|
|||
if ($base === null) { |
|||
throw new NotFound('Invalid node'); |
|||
} |
|||
if (isset($base->{'X-FILENAME'})) { |
|||
return (string)$base->{'X-FILENAME'}; |
|||
} |
|||
return (string)$base->UID . '.ics'; |
|||
} |
|||
|
|||
public function setName($name): void { |
|||
throw new Forbidden('This calendar-object is read-only'); |
|||
} |
|||
|
|||
public function getLastModified(): ?int { |
|||
$base = $this->vobject->getBaseComponent(); |
|||
if ($base !== null && $this->vobject->getBaseComponent()->{'LAST-MODIFIED'}) { |
|||
/** @var DateTime */ |
|||
$lastModified = $this->vobject->getBaseComponent()->{'LAST-MODIFIED'}; |
|||
return $lastModified->getDateTime()->getTimestamp(); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
<?php |
|||
/** |
|||
* @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\Tests\unit\CalDAV\AppCalendar; |
|||
|
|||
use OCA\DAV\CalDAV\AppCalendar\AppCalendar; |
|||
use OCP\Calendar\ICalendar; |
|||
use OCP\Calendar\ICreateFromString; |
|||
use OCP\Constants; |
|||
use PHPUnit\Framework\MockObject\MockObject; |
|||
use Test\TestCase; |
|||
|
|||
use function Safe\rewind; |
|||
|
|||
class AppCalendarTest extends TestCase { |
|||
private $principal = 'principals/users/foo'; |
|||
|
|||
private AppCalendar $appCalendar; |
|||
private AppCalendar $writeableAppCalendar; |
|||
|
|||
private ICalendar|MockObject $calendar; |
|||
private ICalendar|MockObject $writeableCalendar; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->calendar = $this->getMockBuilder(ICalendar::class)->getMock(); |
|||
$this->calendar->method('getPermissions') |
|||
->willReturn(Constants::PERMISSION_READ); |
|||
|
|||
$this->writeableCalendar = $this->getMockBuilder(ICreateFromString::class)->getMock(); |
|||
$this->writeableCalendar->method('getPermissions') |
|||
->willReturn(Constants::PERMISSION_READ | Constants::PERMISSION_CREATE); |
|||
|
|||
$this->appCalendar = new AppCalendar('dav-wrapper', $this->calendar, $this->principal); |
|||
$this->writeableAppCalendar = new AppCalendar('dav-wrapper', $this->writeableCalendar, $this->principal); |
|||
} |
|||
|
|||
public function testGetPrincipal():void { |
|||
// Check that the correct name is returned
|
|||
$this->assertEquals($this->principal, $this->appCalendar->getOwner()); |
|||
$this->assertEquals($this->principal, $this->writeableAppCalendar->getOwner()); |
|||
} |
|||
|
|||
public function testDelete(): void { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->expectExceptionMessage('Deleting an entry is not implemented'); |
|||
|
|||
$this->appCalendar->delete(); |
|||
} |
|||
|
|||
public function testCreateFile() { |
|||
$this->writeableCalendar->expects($this->exactly(3)) |
|||
->method('createFromString') |
|||
->withConsecutive(['some-name', 'data'], ['other-name', ''], ['name', 'some data']); |
|||
|
|||
// pass data
|
|||
$this->assertNull($this->writeableAppCalendar->createFile('some-name', 'data')); |
|||
// null is empty string
|
|||
$this->assertNull($this->writeableAppCalendar->createFile('other-name', null)); |
|||
// resource to data
|
|||
$fp = fopen('php://memory', 'r+'); |
|||
fwrite($fp, 'some data'); |
|||
rewind($fp); |
|||
$this->assertNull($this->writeableAppCalendar->createFile('name', $fp)); |
|||
fclose($fp); |
|||
} |
|||
|
|||
public function testCreateFile_readOnly() { |
|||
// If writing is not supported
|
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->expectExceptionMessage('Creating a new entry is not allowed'); |
|||
|
|||
$this->appCalendar->createFile('some-name', 'data'); |
|||
} |
|||
|
|||
public function testSetACL(): void { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->expectExceptionMessage('Setting ACL is not supported on this node'); |
|||
|
|||
$this->appCalendar->setACL([]); |
|||
} |
|||
|
|||
public function testGetACL():void { |
|||
$expectedRO = [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => $this->principal, |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}write-properties', |
|||
'principal' => $this->principal, |
|||
'protected' => true, |
|||
] |
|||
]; |
|||
$expectedRW = $expectedRO; |
|||
$expectedRW[] = [ |
|||
'privilege' => '{DAV:}write', |
|||
'principal' => $this->principal, |
|||
'protected' => true, |
|||
]; |
|||
|
|||
// Check that the correct ACL is returned (default be only readable)
|
|||
$this->assertEquals($expectedRO, $this->appCalendar->getACL()); |
|||
$this->assertEquals($expectedRW, $this->writeableAppCalendar->getACL()); |
|||
} |
|||
} |
|||
@ -0,0 +1,166 @@ |
|||
<?php |
|||
|
|||
namespace OCA\DAV\Tests\unit\CalDAV\AppCalendar; |
|||
|
|||
use OCA\DAV\CalDAV\AppCalendar\AppCalendar; |
|||
use OCA\DAV\CalDAV\AppCalendar\CalendarObject; |
|||
use OCP\Calendar\ICalendar; |
|||
use OCP\Calendar\ICreateFromString; |
|||
use OCP\Constants; |
|||
use PHPUnit\Framework\MockObject\MockObject; |
|||
use Sabre\VObject\Component\VCalendar; |
|||
use Sabre\VObject\Component\VEvent; |
|||
use Test\TestCase; |
|||
|
|||
class CalendarObjectTest extends TestCase { |
|||
private CalendarObject $calendarObject; |
|||
private AppCalendar|MockObject $calendar; |
|||
private ICalendar|MockObject $backend; |
|||
private VCalendar|MockObject $vobject; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->calendar = $this->createMock(AppCalendar::class); |
|||
$this->calendar->method('getOwner')->willReturn('owner'); |
|||
$this->calendar->method('getGroup')->willReturn('group'); |
|||
|
|||
$this->backend = $this->createMock(ICalendar::class); |
|||
$this->vobject = $this->createMock(VCalendar::class); |
|||
$this->calendarObject = new CalendarObject($this->calendar, $this->backend, $this->vobject); |
|||
} |
|||
|
|||
public function testGetOwner() { |
|||
$this->assertEquals($this->calendarObject->getOwner(), 'owner'); |
|||
} |
|||
|
|||
public function testGetGroup() { |
|||
$this->assertEquals($this->calendarObject->getGroup(), 'group'); |
|||
} |
|||
|
|||
public function testGetACL() { |
|||
$this->calendar->expects($this->exactly(2)) |
|||
->method('getPermissions') |
|||
->willReturnOnConsecutiveCalls(Constants::PERMISSION_READ, Constants::PERMISSION_ALL); |
|||
|
|||
// read only
|
|||
$this->assertEquals($this->calendarObject->getACL(), [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'owner', |
|||
'protected' => true, |
|||
] |
|||
]); |
|||
|
|||
// write permissions
|
|||
$this->assertEquals($this->calendarObject->getACL(), [ |
|||
[ |
|||
'privilege' => '{DAV:}read', |
|||
'principal' => 'owner', |
|||
'protected' => true, |
|||
], |
|||
[ |
|||
'privilege' => '{DAV:}write-content', |
|||
'principal' => 'owner', |
|||
'protected' => true, |
|||
] |
|||
]); |
|||
} |
|||
|
|||
public function testSetACL() { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->calendarObject->setACL([]); |
|||
} |
|||
|
|||
public function testPut_readOnlyBackend() { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->calendarObject->put('foo'); |
|||
} |
|||
|
|||
public function testPut_noPermissions() { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
|
|||
$backend = $this->createMock(ICreateFromString::class); |
|||
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); |
|||
|
|||
$this->calendar->expects($this->once()) |
|||
->method('getPermissions') |
|||
->willReturn(Constants::PERMISSION_READ); |
|||
|
|||
$calendarObject->put('foo'); |
|||
} |
|||
|
|||
public function testPut() { |
|||
$backend = $this->createMock(ICreateFromString::class); |
|||
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); |
|||
|
|||
$this->vobject->expects($this->once()) |
|||
->method('getBaseComponent') |
|||
->willReturn((object)['UID' => 'someid']); |
|||
$this->calendar->expects($this->once()) |
|||
->method('getPermissions') |
|||
->willReturn(Constants::PERMISSION_ALL); |
|||
|
|||
$backend->expects($this->once()) |
|||
->method('createFromString') |
|||
->with('someid.ics', 'foo'); |
|||
$calendarObject->put('foo'); |
|||
} |
|||
|
|||
public function testGet() { |
|||
$this->vobject->expects($this->once()) |
|||
->method('serialize') |
|||
->willReturn('foo'); |
|||
$this->assertEquals($this->calendarObject->get(), 'foo'); |
|||
} |
|||
|
|||
public function testDelete_notWriteable() { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->calendarObject->delete(); |
|||
} |
|||
|
|||
public function testDelete_noPermission() { |
|||
$backend = $this->createMock(ICreateFromString::class); |
|||
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); |
|||
|
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$calendarObject->delete(); |
|||
} |
|||
|
|||
public function testDelete() { |
|||
$backend = $this->createMock(ICreateFromString::class); |
|||
$calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); |
|||
|
|||
$components = [(new VCalendar(['VEVENT' => ['UID' => 'someid']]))->getBaseComponent()]; |
|||
|
|||
$this->calendar->expects($this->once()) |
|||
->method('getPermissions') |
|||
->willReturn(Constants::PERMISSION_DELETE); |
|||
$this->vobject->expects($this->once()) |
|||
->method('getBaseComponents') |
|||
->willReturn($components); |
|||
$this->vobject->expects($this->once()) |
|||
->method('getBaseComponent') |
|||
->willReturn($components[0]); |
|||
|
|||
$backend->expects($this->once()) |
|||
->method('createFromString') |
|||
->with('someid.ics', self::callback(fn($data): bool => preg_match('/BEGIN:VEVENT(.|\r\n)+STATUS:CANCELLED/', $data) === 1)); |
|||
|
|||
$calendarObject->delete(); |
|||
} |
|||
|
|||
public function testGetName() { |
|||
$this->vobject->expects($this->exactly(2)) |
|||
->method('getBaseComponent') |
|||
->willReturnOnConsecutiveCalls((object)['UID' => 'someid'], (object)['UID' => 'someid', 'X-FILENAME' => 'real-filename.ics']); |
|||
|
|||
$this->assertEquals($this->calendarObject->getName(), 'someid.ics'); |
|||
$this->assertEquals($this->calendarObject->getName(), 'real-filename.ics'); |
|||
} |
|||
|
|||
public function testSetName() { |
|||
$this->expectException(\Sabre\DAV\Exception\Forbidden::class); |
|||
$this->calendarObject->setName('Some name'); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue