Skip to content

Commit aa32cd8

Browse files
feat: Calendar Import
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent 0c51b09 commit aa32cd8

File tree

13 files changed

+1133
-1
lines changed

13 files changed

+1133
-1
lines changed

apps/dav/appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<command>OCA\DAV\Command\SendEventReminders</command>
7171
<command>OCA\DAV\Command\SyncBirthdayCalendar</command>
7272
<command>OCA\DAV\Command\SyncSystemAddressBook</command>
73+
<command>OCA\DAV\Command\ImportCalendar</command>
7374
</commands>
7475

7576
<settings>

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => $baseDir . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
6767
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
6868
'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php',
69+
'OCA\\DAV\\CalDAV\\Import\\ImportService' => $baseDir . '/../lib/CalDAV/Import/ImportService.php',
70+
'OCA\\DAV\\CalDAV\\Import\\TextImporter' => $baseDir . '/../lib/CalDAV/Import/TextImporter.php',
71+
'OCA\\DAV\\CalDAV\\Import\\XmlImporter' => $baseDir . '/../lib/CalDAV/Import/XmlImporter.php',
6972
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => $baseDir . '/../lib/CalDAV/Integration/ExternalCalendar.php',
7073
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => $baseDir . '/../lib/CalDAV/Integration/ICalendarProvider.php',
7174
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
@@ -159,6 +162,7 @@
159162
'OCA\\DAV\\Command\\DeleteCalendar' => $baseDir . '/../lib/Command/DeleteCalendar.php',
160163
'OCA\\DAV\\Command\\DeleteSubscription' => $baseDir . '/../lib/Command/DeleteSubscription.php',
161164
'OCA\\DAV\\Command\\FixCalendarSyncCommand' => $baseDir . '/../lib/Command/FixCalendarSyncCommand.php',
165+
'OCA\\DAV\\Command\\ImportCalendar' => $baseDir . '/../lib/Command/ImportCalendar.php',
162166
'OCA\\DAV\\Command\\ListAddressbooks' => $baseDir . '/../lib/Command/ListAddressbooks.php',
163167
'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php',
164168
'OCA\\DAV\\Command\\ListSubscriptions' => $baseDir . '/../lib/Command/ListSubscriptions.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class ComposerStaticInitDAV
8181
'OCA\\DAV\\CalDAV\\FreeBusy\\FreeBusyGenerator' => __DIR__ . '/..' . '/../lib/CalDAV/FreeBusy/FreeBusyGenerator.php',
8282
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
8383
'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php',
84+
'OCA\\DAV\\CalDAV\\Import\\ImportService' => __DIR__ . '/..' . '/../lib/CalDAV/Import/ImportService.php',
85+
'OCA\\DAV\\CalDAV\\Import\\TextImporter' => __DIR__ . '/..' . '/../lib/CalDAV/Import/TextImporter.php',
86+
'OCA\\DAV\\CalDAV\\Import\\XmlImporter' => __DIR__ . '/..' . '/../lib/CalDAV/Import/XmlImporter.php',
8487
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ExternalCalendar.php',
8588
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ICalendarProvider.php',
8689
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
@@ -174,6 +177,7 @@ class ComposerStaticInitDAV
174177
'OCA\\DAV\\Command\\DeleteCalendar' => __DIR__ . '/..' . '/../lib/Command/DeleteCalendar.php',
175178
'OCA\\DAV\\Command\\DeleteSubscription' => __DIR__ . '/..' . '/../lib/Command/DeleteSubscription.php',
176179
'OCA\\DAV\\Command\\FixCalendarSyncCommand' => __DIR__ . '/..' . '/../lib/Command/FixCalendarSyncCommand.php',
180+
'OCA\\DAV\\Command\\ImportCalendar' => __DIR__ . '/..' . '/../lib/Command/ImportCalendar.php',
177181
'OCA\\DAV\\Command\\ListAddressbooks' => __DIR__ . '/..' . '/../lib/Command/ListAddressbooks.php',
178182
'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php',
179183
'OCA\\DAV\\Command\\ListSubscriptions' => __DIR__ . '/..' . '/../lib/Command/ListSubscriptions.php',

apps/dav/lib/CalDAV/CalendarImpl.php

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@
88
*/
99
namespace OCA\DAV\CalDAV;
1010

11+
use Exception;
12+
use InvalidArgumentException;
1113
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
1214
use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
15+
use OCP\Calendar\CalendarImportOptions;
1316
use OCP\Calendar\Exceptions\CalendarException;
17+
use OCP\Calendar\ICalendarImport;
18+
use OCP\Calendar\ICalendarIsShared;
19+
use OCP\Calendar\ICalendarIsWritable;
1420
use OCP\Calendar\ICreateFromString;
1521
use OCP\Calendar\IHandleImipMessage;
1622
use OCP\Constants;
@@ -20,11 +26,14 @@
2026
use Sabre\VObject\Component\VEvent;
2127
use Sabre\VObject\Component\VTimeZone;
2228
use Sabre\VObject\ITip\Message;
29+
use Sabre\VObject\Node;
2330
use Sabre\VObject\Property;
2431
use Sabre\VObject\Reader;
32+
use Sabre\VObject\UUIDUtil;
33+
2534
use function Sabre\Uri\split as uriSplit;
2635

27-
class CalendarImpl implements ICreateFromString, IHandleImipMessage {
36+
class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIsWritable, ICalendarIsShared, ICalendarImport {
2837
public function __construct(
2938
private Calendar $calendar,
3039
/** @var array<string, mixed> */
@@ -257,4 +266,127 @@ public function handleIMipMessage(string $name, string $calendarData): void {
257266
public function getInvitationResponseServer(): InvitationResponseServer {
258267
return new InvitationResponseServer(false);
259268
}
269+
270+
/**
271+
* Import objects
272+
*
273+
* @since 32.0.0
274+
*
275+
* @param CalendarImportOptions $options
276+
* @param callable $generator<CalendarImportOptions>: Generator<\Sabre\VObject\Component\VCalendar>
277+
*
278+
* @return array<string,array<string,string|array<string>>>
279+
*/
280+
public function import(CalendarImportOptions $options, callable $generator): array {
281+
$calendarId = $this->getKey();
282+
$outcome = [];
283+
foreach ($generator($options) as $vObject) {
284+
$components = $vObject->getBaseComponents();
285+
// determine if the object has no base component types
286+
if (count($components) === 0) {
287+
$errorMessage = 'One or more objects discovered with no base component types';
288+
if ($options->getErrors() === $options::ERROR_FAIL) {
289+
throw new InvalidArgumentException('Error importing calendar data: ' . $errorMessage);
290+
}
291+
$outcome['nbct'] = ['outcome' => 'error', 'errors' => [$errorMessage]];
292+
continue;
293+
}
294+
// determine if the object has more than one base component type
295+
// object can have multiple base components with the same uid
296+
// but we need to make sure they are of the same type
297+
if (count($components) > 1) {
298+
$type = $components[0]->name;
299+
foreach ($components as $entry) {
300+
if ($type !== $entry->name) {
301+
$errorMessage = 'One or more objects discovered with multiple base component types';
302+
if ($options->getErrors() === $options::ERROR_FAIL) {
303+
throw new InvalidArgumentException('Error importing calendar data: ' . $errorMessage);
304+
}
305+
$outcome['mbct'] = ['outcome' => 'error', 'errors' => [$errorMessage]];
306+
continue 2;
307+
}
308+
}
309+
}
310+
// determine if the object has a uid
311+
if (!isset($components[0]->UID)) {
312+
$errorMessage = 'One or more objects discovered without a UID';
313+
if ($options->getErrors() === $options::ERROR_FAIL) {
314+
throw new InvalidArgumentException('Error importing calendar data: ' . $errorMessage);
315+
}
316+
$outcome['noid'] = ['outcome' => 'error', 'errors' => [$errorMessage]];
317+
continue;
318+
}
319+
$uid = $components[0]->UID->getValue();
320+
// validate object
321+
if ($options->getValidate() !== $options::VALIDATE_NONE) {
322+
$issues = $this->validateComponent($vObject, true, 3);
323+
if ($options->getValidate() === $options::VALIDATE_SKIP && $issues !== []) {
324+
$outcome[$uid] = ['outcome' => 'error', 'errors' => $issues];
325+
continue;
326+
} elseif ($options->getValidate() === $options::VALIDATE_FAIL && $issues !== []) {
327+
throw new InvalidArgumentException('Error importing calendar data: UID <' . $uid . '> - ' . $issues[0]);
328+
}
329+
}
330+
// create or update object in the data store
331+
$objectId = $this->backend->getCalendarObjectByUID($this->calendarInfo['principaluri'], $uid);
332+
$objectData = $vObject->serialize();
333+
try {
334+
if ($objectId === null) {
335+
$objectId = UUIDUtil::getUUID();
336+
$this->backend->createCalendarObject(
337+
$calendarId,
338+
$objectId,
339+
$objectData
340+
);
341+
$outcome[$uid] = ['outcome' => 'created'];
342+
} elseif ($objectId !== null) {
343+
[$cid, $oid] = explode('/', $objectId);
344+
if ($options->getSupersede()) {
345+
$this->backend->updateCalendarObject(
346+
$calendarId,
347+
$oid,
348+
$objectData
349+
);
350+
$outcome[$uid] = ['outcome' => 'updated'];
351+
} else {
352+
$outcome[$uid] = ['outcome' => 'exists'];
353+
}
354+
}
355+
} catch (Exception $e) {
356+
$errorMessage = $e->getMessage();
357+
if ($options->getErrors() === $options::ERROR_FAIL) {
358+
throw new Exception('Error importing calendar data: UID <' . $uid . '> - ' . $errorMessage, 0, $e);
359+
}
360+
$outcome[$uid] = ['outcome' => 'error', 'errors' => [$errorMessage]];
361+
}
362+
}
363+
364+
return $outcome;
365+
}
366+
367+
/**
368+
* Validate a component
369+
*
370+
* @param VCalendar $vObject
371+
* @param bool $repair attempt to repair the component
372+
* @param int $level minimum level of issues to return
373+
* @return list<mixed>
374+
*/
375+
private function validateComponent(VCalendar $vObject, bool $repair, int $level): array {
376+
// validate component(S)
377+
$issues = $vObject->validate(Node::PROFILE_CALDAV);
378+
// attempt to repair
379+
if ($repair && count($issues) > 0) {
380+
$issues = $vObject->validate(Node::REPAIR);
381+
}
382+
// filter out messages based on level
383+
$result = [];
384+
foreach ($issues as $key => $issue) {
385+
if (isset($issue['level']) && $issue['level'] >= $level) {
386+
$result[] = $issue['message'];
387+
}
388+
}
389+
390+
return $result;
391+
}
260392
}

0 commit comments

Comments
 (0)