Skip to content

Commit

Permalink
feat: OCC and OCS Calendar Import/Export
Browse files Browse the repository at this point in the history
Signed-off-by: SebastianKrupinski <[email protected]>
  • Loading branch information
SebastianKrupinski committed Feb 4, 2025
1 parent a2e05ee commit 213c1aa
Show file tree
Hide file tree
Showing 23 changed files with 1,851 additions and 15 deletions.
2 changes: 2 additions & 0 deletions apps/dav/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
<command>OCA\DAV\Command\SyncBirthdayCalendar</command>
<command>OCA\DAV\Command\SyncSystemAddressBook</command>
<command>OCA\DAV\Command\RemoveInvalidShares</command>
<command>OCA\DAV\Command\ImportCalendar</command>
<command>OCA\DAV\Command\ExportCalendar</command>
</commands>

<settings>
Expand Down
8 changes: 8 additions & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,13 @@
'OCA\\DAV\\CalDAV\\EventReader' => $baseDir . '/../lib/CalDAV/EventReader.php',
'OCA\\DAV\\CalDAV\\EventReaderRDate' => $baseDir . '/../lib/CalDAV/EventReaderRDate.php',
'OCA\\DAV\\CalDAV\\EventReaderRRule' => $baseDir . '/../lib/CalDAV/EventReaderRRule.php',
'OCA\\DAV\\CalDAV\\Export\\ExportService' => $baseDir . '/../lib/CalDAV/Export/ExportService.php',
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => $baseDir . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php',
'OCA\\DAV\\CalDAV\\Import\\ImportService' => $baseDir . '/../lib/CalDAV/Import/ImportService.php',
'OCA\\DAV\\CalDAV\\Import\\TextImporter' => $baseDir . '/../lib/CalDAV/Import/TextImporter.php',
'OCA\\DAV\\CalDAV\\Import\\XmlImporter' => $baseDir . '/../lib/CalDAV/Import/XmlImporter.php',
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => $baseDir . '/../lib/CalDAV/Integration/ExternalCalendar.php',
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => $baseDir . '/../lib/CalDAV/Integration/ICalendarProvider.php',
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
Expand Down Expand Up @@ -157,7 +161,9 @@
'OCA\\DAV\\Command\\CreateCalendar' => $baseDir . '/../lib/Command/CreateCalendar.php',
'OCA\\DAV\\Command\\CreateSubscription' => $baseDir . '/../lib/Command/CreateSubscription.php',
'OCA\\DAV\\Command\\DeleteCalendar' => $baseDir . '/../lib/Command/DeleteCalendar.php',
'OCA\\DAV\\Command\\ExportCalendar' => $baseDir . '/../lib/Command/ExportCalendar.php',
'OCA\\DAV\\Command\\FixCalendarSyncCommand' => $baseDir . '/../lib/Command/FixCalendarSyncCommand.php',
'OCA\\DAV\\Command\\ImportCalendar' => $baseDir . '/../lib/Command/ImportCalendar.php',
'OCA\\DAV\\Command\\ListAddressbooks' => $baseDir . '/../lib/Command/ListAddressbooks.php',
'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php',
'OCA\\DAV\\Command\\MoveCalendar' => $baseDir . '/../lib/Command/MoveCalendar.php',
Expand Down Expand Up @@ -218,6 +224,8 @@
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\ZipFolderPlugin' => $baseDir . '/../lib/Connector/Sabre/ZipFolderPlugin.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\CalendarExportController' => $baseDir . '/../lib/Controller/CalendarExportController.php',
'OCA\\DAV\\Controller\\CalendarImportController' => $baseDir . '/../lib/Controller/CalendarImportController.php',
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',
'OCA\\DAV\\Controller\\OutOfOfficeController' => $baseDir . '/../lib/Controller/OutOfOfficeController.php',
Expand Down
8 changes: 8 additions & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,13 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\EventReader' => __DIR__ . '/..' . '/../lib/CalDAV/EventReader.php',
'OCA\\DAV\\CalDAV\\EventReaderRDate' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRDate.php',
'OCA\\DAV\\CalDAV\\EventReaderRRule' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRRule.php',
'OCA\\DAV\\CalDAV\\Export\\ExportService' => __DIR__ . '/..' . '/../lib/CalDAV/Export/ExportService.php',
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => __DIR__ . '/..' . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php',
'OCA\\DAV\\CalDAV\\Import\\ImportService' => __DIR__ . '/..' . '/../lib/CalDAV/Import/ImportService.php',
'OCA\\DAV\\CalDAV\\Import\\TextImporter' => __DIR__ . '/..' . '/../lib/CalDAV/Import/TextImporter.php',
'OCA\\DAV\\CalDAV\\Import\\XmlImporter' => __DIR__ . '/..' . '/../lib/CalDAV/Import/XmlImporter.php',
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ExternalCalendar.php',
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ICalendarProvider.php',
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
Expand Down Expand Up @@ -172,7 +176,9 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Command\\CreateCalendar' => __DIR__ . '/..' . '/../lib/Command/CreateCalendar.php',
'OCA\\DAV\\Command\\CreateSubscription' => __DIR__ . '/..' . '/../lib/Command/CreateSubscription.php',
'OCA\\DAV\\Command\\DeleteCalendar' => __DIR__ . '/..' . '/../lib/Command/DeleteCalendar.php',
'OCA\\DAV\\Command\\ExportCalendar' => __DIR__ . '/..' . '/../lib/Command/ExportCalendar.php',
'OCA\\DAV\\Command\\FixCalendarSyncCommand' => __DIR__ . '/..' . '/../lib/Command/FixCalendarSyncCommand.php',
'OCA\\DAV\\Command\\ImportCalendar' => __DIR__ . '/..' . '/../lib/Command/ImportCalendar.php',
'OCA\\DAV\\Command\\ListAddressbooks' => __DIR__ . '/..' . '/../lib/Command/ListAddressbooks.php',
'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php',
'OCA\\DAV\\Command\\MoveCalendar' => __DIR__ . '/..' . '/../lib/Command/MoveCalendar.php',
Expand Down Expand Up @@ -233,6 +239,8 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\ZipFolderPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ZipFolderPlugin.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\CalendarExportController' => __DIR__ . '/..' . '/../lib/Controller/CalendarExportController.php',
'OCA\\DAV\\Controller\\CalendarImportController' => __DIR__ . '/..' . '/../lib/Controller/CalendarImportController.php',
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',
'OCA\\DAV\\Controller\\OutOfOfficeController' => __DIR__ . '/..' . '/../lib/Controller/OutOfOfficeController.php',
Expand Down
31 changes: 31 additions & 0 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Generator;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\Sharing\Backend;
use OCA\DAV\Connector\Sabre\Principal;
Expand All @@ -34,6 +35,7 @@
use OCA\DAV\Events\SubscriptionDeletedEvent;
use OCA\DAV\Events\SubscriptionUpdatedEvent;
use OCP\AppFramework\Db\TTransactional;
use OCP\Calendar\CalendarExportOptions;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -950,6 +952,35 @@ public function restoreCalendar(int $id): void {
}, $this->db);
}

/**
* Returns all calendar entries as a stream of data
*
* @since 32.0.0
*
* @return Generator<array>
*/
public function exportCalendar(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR, ?CalendarExportOptions $options = null): Generator {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('calendarobjects')
->where($qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)))
->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
->andWhere($qb->expr()->isNull('deleted_at'));
if ($options?->rangeStart !== null) {
$qb->setFirstResult($options?->rangeStart);
}
if ($options?->rangeCount !== null) {
$qb->setMaxResults($options->rangeCount);
}
$rs = $qb->executeQuery();

while (($row = $rs->fetch()) !== false) {
yield $row;
}

$rs->closeCursor();
}

/**
* Returns all calendar objects with limited metadata for a calendar
*
Expand Down
130 changes: 129 additions & 1 deletion apps/dav/lib/CalDAV/CalendarImpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@
*/
namespace OCA\DAV\CalDAV;

use Generator;
use InvalidArgumentException;
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
use OCP\Calendar\CalendarExportOptions;
use OCP\Calendar\CalendarImportOptions;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICalendarExport;
use OCP\Calendar\ICalendarImport;
use OCP\Calendar\ICalendarIsShared;
use OCP\Calendar\ICalendarIsWritable;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IHandleImipMessage;
use OCP\Constants;
Expand All @@ -20,11 +28,14 @@
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Component\VTimeZone;
use Sabre\VObject\ITip\Message;
use Sabre\VObject\Node;
use Sabre\VObject\Property;
use Sabre\VObject\Reader;
use Sabre\VObject\UUIDUtil;

use function Sabre\Uri\split as uriSplit;

class CalendarImpl implements ICreateFromString, IHandleImipMessage {
class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIsWritable, ICalendarIsShared, ICalendarImport, ICalendarExport {
public function __construct(
private Calendar $calendar,
/** @var array<string, mixed> */
Expand Down Expand Up @@ -257,4 +268,121 @@ public function handleIMipMessage(string $name, string $calendarData): void {
public function getInvitationResponseServer(): InvitationResponseServer {
return new InvitationResponseServer(false);
}

/**
* Export objects
*
* @since 32.0.0
*
* @return Generator<\Sabre\VObject\Component\VCalendar>

Check failure on line 277 in apps/dav/lib/CalDAV/CalendarImpl.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

MoreSpecificReturnType

apps/dav/lib/CalDAV/CalendarImpl.php:277:13: MoreSpecificReturnType: The declared return type 'Generator<mixed, Sabre\VObject\Component\VCalendar, mixed, mixed>' for OCA\DAV\CalDAV\CalendarImpl::export is more specific than the inferred return type 'Generator<int, Sabre\VObject\Document, mixed, void>' (see https://psalm.dev/070)
*/
public function export(?CalendarExportOptions $options = null): Generator {
foreach (
$this->backend->exportCalendar(
$this->calendarInfo['id'],
$this->backend::CALENDAR_TYPE_CALENDAR,
$options
) as $event
) {
yield Reader::read($event['calendardata']);
}
}

/**
* Import objects
*
* @since 32.0.0
*/
public function import(CalendarImportOptions $options, callable $generator): array {

Check failure on line 296 in apps/dav/lib/CalDAV/CalendarImpl.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidReturnType

apps/dav/lib/CalDAV/CalendarImpl.php:296:79: InvalidReturnType: The declared return type 'array<string, array<string, string>>' for OCA\DAV\CalDAV\CalendarImpl::import is incorrect, got 'array<array-key, array{errors?: non-empty-array<array-key, mixed>, outcome: 'created'|'error'|'exists'|'updated'}>' (see https://psalm.dev/011)

$calendarId = $this->getKey();
$outcome = [];
foreach ($generator($options) as $vObject) {

$components = $vObject->getBaseComponents();
// determine if the object has no base component types
if (count($components) === 0) {
if ($options->errors === 1) {
throw new InvalidArgumentException('Error importing calendar object, discovered object with no base component types');
}
$outcome['nbct'] = ['outcome' => 'error', 'errors' => ['One or more objects discovered with no base component types']];
continue;
}
// determine if the object has more than one base component type
if (count($components) > 1) {
if ($options->errors === 1) {
throw new InvalidArgumentException('Error importing calendar object, discovered object with multiple base component types');
}
$outcome['mbct'] = ['outcome' => 'error', 'errors' => ['One or more objects discovered with multiple base component types']];
continue;
}
// determine if the object has a uid
if (!isset($components[0]->UID)) {
if ($options->errors === 1) {
throw new InvalidArgumentException('Error importing calendar object, discovered object without a UID');
}
$outcome['noid'] = ['outcome' => 'error', 'errors' => ['One or more objects discovered without a UID']];
continue;
}
$uid = $components[0]->UID->getValue();
// validate object
if ($options->validate !== 0) {
$issues = $this->validateComponent($vObject, true, 3);
if ($options->validate === 1 && $issues !== []) {
$outcome[$uid] = ['outcome' => 'error', 'errors' => $issues];
continue;
} elseif ($options->validate === 2 && $issues !== []) {
throw new InvalidArgumentException('Error importing calendar object <' . $uid . '>, ' . $issues[0]);
}
}

$objectId = $this->backend->getCalendarObjectByUID($this->calendarInfo['principaluri'], $uid);
$objectData = $vObject->serialize();

// create or update object
if ($objectId === null) {
$objectId = UUIDUtil::getUUID();
$this->backend->createCalendarObject(
$calendarId,
$objectId,
$objectData
);
$outcome[$uid] = ['outcome' => 'created'];
} elseif ($objectId !== null) {
[$cid, $oid] = explode('/', $objectId);
if ($options->supersede) {
$this->backend->updateCalendarObject(
$calendarId,
$oid,
$objectData
);
$outcome[$uid] = ['outcome' => 'updated'];
} else {
$outcome[$uid] = ['outcome' => 'exists'];
}
}
}

return $outcome;

Check failure on line 366 in apps/dav/lib/CalDAV/CalendarImpl.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidReturnStatement

apps/dav/lib/CalDAV/CalendarImpl.php:366:10: InvalidReturnStatement: The inferred type 'array<array-key, array{errors?: non-empty-array<array-key, mixed>, outcome: 'created'|'error'|'exists'|'updated'}>' does not match the declared return type 'array<string, array<string, string>>' for OCA\DAV\CalDAV\CalendarImpl::import (see https://psalm.dev/128)

}

private function validateComponent(VCalendar $vObject, bool $repair, int $level): array {
// validate component(S)
$issues = $vObject->validate(Node::PROFILE_CALDAV);
// attempt to repair
if ($repair && count($issues) > 0) {
$issues = $vObject->validate(Node::REPAIR);
}
// filter out messages based on level
$result = [];
foreach ($issues as $key => $issue) {
if (isset($issue['level']) && $issue['level'] >= $level) {
$result[] = $issue['message'];
}
}

return $result;
}

}
Loading

0 comments on commit 213c1aa

Please sign in to comment.