mirror of
https://github.com/nextcloud/server.git
synced 2026-04-24 23:59:27 -04:00
Merge pull request #35121 from nextcloud/feat/dav-wrap-app-calendars
Feature: Provide access to app generated calendars through CalDAV
This commit is contained in:
commit
5d29a96b1c
12 changed files with 758 additions and 19 deletions
|
|
@ -37,6 +37,9 @@ return array(
|
|||
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Setting/Calendar.php',
|
||||
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Event' => $baseDir . '/../lib/CalDAV/Activity/Setting/Event.php',
|
||||
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Setting/Todo.php',
|
||||
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendar' => $baseDir . '/../lib/CalDAV/AppCalendar/AppCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendarPlugin' => $baseDir . '/../lib/CalDAV/AppCalendar/AppCalendarPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\AppCalendar\\CalendarObject' => $baseDir . '/../lib/CalDAV/AppCalendar/CalendarObject.php',
|
||||
'OCA\\DAV\\CalDAV\\Auth\\CustomPrincipalPlugin' => $baseDir . '/../lib/CalDAV/Auth/CustomPrincipalPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\Auth\\PublicPrincipalPlugin' => $baseDir . '/../lib/CalDAV/Auth/PublicPrincipalPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => $baseDir . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php',
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ class ComposerStaticInitDAV
|
|||
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Calendar.php',
|
||||
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Event' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Event.php',
|
||||
'OCA\\DAV\\CalDAV\\Activity\\Setting\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Todo.php',
|
||||
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/AppCalendar/AppCalendar.php',
|
||||
'OCA\\DAV\\CalDAV\\AppCalendar\\AppCalendarPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/AppCalendar/AppCalendarPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\AppCalendar\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/AppCalendar/CalendarObject.php',
|
||||
'OCA\\DAV\\CalDAV\\Auth\\CustomPrincipalPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Auth/CustomPrincipalPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\Auth\\PublicPrincipalPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/Auth/PublicPrincipalPlugin.php',
|
||||
'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => __DIR__ . '/..' . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php',
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace OCA\DAV\AppInfo;
|
|||
use Exception;
|
||||
use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob;
|
||||
use OCA\DAV\CalDAV\Activity\Backend;
|
||||
use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
|
||||
use OCA\DAV\CalDAV\CalendarManager;
|
||||
use OCA\DAV\CalDAV\CalendarProvider;
|
||||
use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
|
||||
|
|
@ -44,7 +45,6 @@ use OCA\DAV\CalDAV\Reminder\NotificationProviderManager;
|
|||
use OCA\DAV\CalDAV\Reminder\Notifier;
|
||||
|
||||
use OCA\DAV\Capabilities;
|
||||
use OCA\DAV\CardDAV\CardDavBackend;
|
||||
use OCA\DAV\CardDAV\ContactsManager;
|
||||
use OCA\DAV\CardDAV\PhotoCache;
|
||||
use OCA\DAV\CardDAV\SyncService;
|
||||
|
|
@ -100,6 +100,7 @@ use OCP\Calendar\IManager as ICalendarManager;
|
|||
use OCP\Config\BeforePreferenceDeletedEvent;
|
||||
use OCP\Config\BeforePreferenceSetEvent;
|
||||
use OCP\Contacts\IManager as IContactsManager;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUser;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
|
@ -119,14 +120,17 @@ class Application extends App implements IBootstrap {
|
|||
public function register(IRegistrationContext $context): void {
|
||||
$context->registerServiceAlias('CardDAVSyncService', SyncService::class);
|
||||
$context->registerService(PhotoCache::class, function (ContainerInterface $c) {
|
||||
/** @var IServerContainer $server */
|
||||
$server = $c->get(IServerContainer::class);
|
||||
|
||||
return new PhotoCache(
|
||||
$server->getAppDataDir('dav-photocache'),
|
||||
$c->get(IAppDataFactory::class)->get('dav-photocache'),
|
||||
$c->get(LoggerInterface::class)
|
||||
);
|
||||
});
|
||||
$context->registerService(AppCalendarPlugin::class, function(ContainerInterface $c) {
|
||||
return new AppCalendarPlugin(
|
||||
$c->get(ICalendarManager::class),
|
||||
$c->get(LoggerInterface::class)
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
* Register capabilities
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ declare(strict_types=1);
|
|||
namespace OCA\DAV\AppInfo;
|
||||
|
||||
use OC\ServerContainer;
|
||||
use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
|
||||
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
|
||||
use OCA\DAV\CardDAV\Integration\IAddressBookProvider;
|
||||
use OCP\App\IAppManager;
|
||||
|
|
@ -144,6 +145,8 @@ class PluginManager {
|
|||
}
|
||||
$this->populated = true;
|
||||
|
||||
$this->calendarPlugins[] = $this->container->get(AppCalendarPlugin::class);
|
||||
|
||||
foreach ($this->appManager->getInstalledApps() as $app) {
|
||||
// load plugins and collections from info.xml
|
||||
$info = $this->appManager->getAppInfo($app);
|
||||
|
|
@ -253,7 +256,7 @@ class PluginManager {
|
|||
|
||||
private function createClass(string $className): object {
|
||||
try {
|
||||
return $this->container->query($className);
|
||||
return $this->container->get($className);
|
||||
} catch (QueryException $e) {
|
||||
if (class_exists($className)) {
|
||||
return new $className();
|
||||
|
|
|
|||
208
apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php
Normal file
208
apps/dav/lib/CalDAV/AppCalendar/AppCalendar.php
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
74
apps/dav/lib/CalDAV/AppCalendar/AppCalendarPlugin.php
Normal file
74
apps/dav/lib/CalDAV/AppCalendar/AppCalendarPlugin.php
Normal file
|
|
@ -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);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
153
apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php
Normal file
153
apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ declare(strict_types=1);
|
|||
namespace OCA\DAV\CalDAV;
|
||||
|
||||
use OCP\Calendar\ICalendarProvider;
|
||||
use OCP\Calendar\ICreateFromString;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ namespace OCA\DAV\Tests\unit\AppInfo;
|
|||
use OC\App\AppManager;
|
||||
use OC\ServerContainer;
|
||||
use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
|
||||
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
|
||||
use Sabre\DAV\Collection;
|
||||
use Sabre\DAV\ServerPlugin;
|
||||
|
|
@ -43,7 +44,6 @@ class PluginManagerTest extends TestCase {
|
|||
public function test(): void {
|
||||
$server = $this->createMock(ServerContainer::class);
|
||||
|
||||
|
||||
$appManager = $this->createMock(AppManager::class);
|
||||
$appManager->method('getInstalledApps')
|
||||
->willReturn(['adavapp', 'adavapp2']);
|
||||
|
|
@ -94,6 +94,7 @@ class PluginManagerTest extends TestCase {
|
|||
|
||||
$pluginManager = new PluginManager($server, $appManager);
|
||||
|
||||
$appCalendarPlugin = $this->createMock(AppCalendarPlugin::class);
|
||||
$calendarPlugin1 = $this->createMock(ICalendarProvider::class);
|
||||
$calendarPlugin2 = $this->createMock(ICalendarProvider::class);
|
||||
$calendarPlugin3 = $this->createMock(ICalendarProvider::class);
|
||||
|
|
@ -106,17 +107,18 @@ class PluginManagerTest extends TestCase {
|
|||
$dummyCollection2 = $this->createMock(Collection::class);
|
||||
$dummy2Collection1 = $this->createMock(Collection::class);
|
||||
|
||||
$server->method('query')
|
||||
$server->method('get')
|
||||
->willReturnMap([
|
||||
['\OCA\DAV\ADavApp\PluginOne', true, $dummyPlugin1],
|
||||
['\OCA\DAV\ADavApp\PluginTwo', true, $dummyPlugin2],
|
||||
['\OCA\DAV\ADavApp\CalendarPluginOne', true, $calendarPlugin1],
|
||||
['\OCA\DAV\ADavApp\CalendarPluginTwo', true, $calendarPlugin2],
|
||||
['\OCA\DAV\ADavApp\CollectionOne', true, $dummyCollection1],
|
||||
['\OCA\DAV\ADavApp\CollectionTwo', true, $dummyCollection2],
|
||||
['\OCA\DAV\ADavApp2\PluginOne', true, $dummy2Plugin1],
|
||||
['\OCA\DAV\ADavApp2\CalendarPluginOne', true, $calendarPlugin3],
|
||||
['\OCA\DAV\ADavApp2\CollectionOne', true, $dummy2Collection1],
|
||||
[AppCalendarPlugin::class, $appCalendarPlugin],
|
||||
['\OCA\DAV\ADavApp\PluginOne', $dummyPlugin1],
|
||||
['\OCA\DAV\ADavApp\PluginTwo', $dummyPlugin2],
|
||||
['\OCA\DAV\ADavApp\CalendarPluginOne', $calendarPlugin1],
|
||||
['\OCA\DAV\ADavApp\CalendarPluginTwo', $calendarPlugin2],
|
||||
['\OCA\DAV\ADavApp\CollectionOne', $dummyCollection1],
|
||||
['\OCA\DAV\ADavApp\CollectionTwo', $dummyCollection2],
|
||||
['\OCA\DAV\ADavApp2\PluginOne', $dummy2Plugin1],
|
||||
['\OCA\DAV\ADavApp2\CalendarPluginOne', $calendarPlugin3],
|
||||
['\OCA\DAV\ADavApp2\CollectionOne', $dummy2Collection1],
|
||||
]);
|
||||
|
||||
$expectedPlugins = [
|
||||
|
|
@ -125,6 +127,7 @@ class PluginManagerTest extends TestCase {
|
|||
$dummy2Plugin1,
|
||||
];
|
||||
$expectedCalendarPlugins = [
|
||||
$appCalendarPlugin,
|
||||
$calendarPlugin1,
|
||||
$calendarPlugin2,
|
||||
$calendarPlugin3,
|
||||
|
|
|
|||
123
apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php
Normal file
123
apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php
Normal file
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
166
apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php
Normal file
166
apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php
Normal file
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ declare(strict_types=1);
|
|||
namespace OCA\DAV\Tests\Command;
|
||||
|
||||
use OCA\DAV\CalDAV\BirthdayService;
|
||||
use OCA\DAV\CalDav\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\Command\DeleteCalendar;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
|
|
|
|||
Loading…
Reference in a new issue