Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OCC and OCS Calendar Import/Export #49995

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 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\CalendarExportRange;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -950,6 +952,29 @@ public function restoreCalendar(int $id): void {
}, $this->db);
}


public function exportCalendar($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR, ?CalendarExportRange $range = 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 ($range?->start !== null) {
$qb->setFirstResult($range->start);
}
if ($range?->count !== null) {
$qb->setMaxResults($range->count);
}
$rs = $qb->executeQuery();

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

$rs->closeCursor();
}

/**
* Returns all calendar objects with limited metadata for a calendar
*
Expand Down
74 changes: 73 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\CalendarExportRange;
use OCP\Calendar\CalendarImportSettings;
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 @@ -22,9 +30,11 @@
use Sabre\VObject\ITip\Message;
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 +267,66 @@
public function getInvitationResponseServer(): InvitationResponseServer {
return new InvitationResponseServer(false);
}

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

Check failure on line 276 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:276: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(?CalendarExportRange $range = null): Generator {
foreach (
$this->backend->exportCalendar(
$this->calendarInfo['id'],
$this->backend::CALENDAR_TYPE_CALENDAR,
$range
) as $event
) {
yield Reader::read($event['calendardata']);
}
}

/**
* Import objects
*
* @since 31.0.0
*
*/
public function import(CalendarImportSettings $settings, VCalendar ...$vObjects): array {

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

$components = $vObject->getBaseComponents();
if (count($components) > 1) {
throw new InvalidArgumentException('Import failure: objects can not contain more than one base instance object');
}
$uid = $components[0]->UID->getValue();

$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
);
} elseif ($objectId !== null && $settings->supersede) {
$this->backend->updateCalendarObject(
$calendarId,
$objectId,
$objectData
);
}
}

return $outcome;

}

}
5 changes: 5 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php',
'OCP\\AppFramework\\Http\\Response' => $baseDir . '/lib/public/AppFramework/Http/Response.php',
'OCP\\AppFramework\\Http\\StandaloneTemplateResponse' => $baseDir . '/lib/public/AppFramework/Http/StandaloneTemplateResponse.php',
'OCP\\AppFramework\\Http\\StreamGeneratorResponse' => $baseDir . '/lib/public/AppFramework/Http/StreamGeneratorResponse.php',
'OCP\\AppFramework\\Http\\StreamResponse' => $baseDir . '/lib/public/AppFramework/Http/StreamResponse.php',
'OCP\\AppFramework\\Http\\StrictContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\StrictEvalContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php',
Expand Down Expand Up @@ -190,10 +191,14 @@
'OCP\\Broadcast\\Events\\IBroadcastEvent' => $baseDir . '/lib/public/Broadcast/Events/IBroadcastEvent.php',
'OCP\\Cache\\CappedMemoryCache' => $baseDir . '/lib/public/Cache/CappedMemoryCache.php',
'OCP\\Calendar\\BackendTemporarilyUnavailableException' => $baseDir . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php',
'OCP\\Calendar\\CalendarExportRange' => $baseDir . '/lib/public/Calendar/CalendarExportRange.php',
'OCP\\Calendar\\CalendarImportSettings' => $baseDir . '/lib/public/Calendar/CalendarImportSettings.php',
'OCP\\Calendar\\Exceptions\\CalendarException' => $baseDir . '/lib/public/Calendar/Exceptions/CalendarException.php',
'OCP\\Calendar\\IAvailabilityResult' => $baseDir . '/lib/public/Calendar/IAvailabilityResult.php',
'OCP\\Calendar\\ICalendar' => $baseDir . '/lib/public/Calendar/ICalendar.php',
'OCP\\Calendar\\ICalendarEventBuilder' => $baseDir . '/lib/public/Calendar/ICalendarEventBuilder.php',
'OCP\\Calendar\\ICalendarExport' => $baseDir . '/lib/public/Calendar/ICalendarExport.php',
'OCP\\Calendar\\ICalendarImport' => $baseDir . '/lib/public/Calendar/ICalendarImport.php',
'OCP\\Calendar\\ICalendarIsShared' => $baseDir . '/lib/public/Calendar/ICalendarIsShared.php',
'OCP\\Calendar\\ICalendarIsWritable' => $baseDir . '/lib/public/Calendar/ICalendarIsWritable.php',
'OCP\\Calendar\\ICalendarProvider' => $baseDir . '/lib/public/Calendar/ICalendarProvider.php',
Expand Down
5 changes: 5 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php',
'OCP\\AppFramework\\Http\\Response' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Response.php',
'OCP\\AppFramework\\Http\\StandaloneTemplateResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StandaloneTemplateResponse.php',
'OCP\\AppFramework\\Http\\StreamGeneratorResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StreamGeneratorResponse.php',
'OCP\\AppFramework\\Http\\StreamResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StreamResponse.php',
'OCP\\AppFramework\\Http\\StrictContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StrictContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\StrictEvalContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/StrictEvalContentSecurityPolicy.php',
Expand Down Expand Up @@ -239,10 +240,14 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Broadcast\\Events\\IBroadcastEvent' => __DIR__ . '/../../..' . '/lib/public/Broadcast/Events/IBroadcastEvent.php',
'OCP\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/public/Cache/CappedMemoryCache.php',
'OCP\\Calendar\\BackendTemporarilyUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php',
'OCP\\Calendar\\CalendarExportRange' => __DIR__ . '/../../..' . '/lib/public/Calendar/CalendarExportRange.php',
'OCP\\Calendar\\CalendarImportSettings' => __DIR__ . '/../../..' . '/lib/public/Calendar/CalendarImportSettings.php',
'OCP\\Calendar\\Exceptions\\CalendarException' => __DIR__ . '/../../..' . '/lib/public/Calendar/Exceptions/CalendarException.php',
'OCP\\Calendar\\IAvailabilityResult' => __DIR__ . '/../../..' . '/lib/public/Calendar/IAvailabilityResult.php',
'OCP\\Calendar\\ICalendar' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendar.php',
'OCP\\Calendar\\ICalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarEventBuilder.php',
'OCP\\Calendar\\ICalendarExport' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarExport.php',
'OCP\\Calendar\\ICalendarImport' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarImport.php',
'OCP\\Calendar\\ICalendarIsShared' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsShared.php',
'OCP\\Calendar\\ICalendarIsWritable' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsWritable.php',
'OCP\\Calendar\\ICalendarProvider' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarProvider.php',
Expand Down
57 changes: 57 additions & 0 deletions lib/public/AppFramework/Http/StreamGeneratorResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCP\AppFramework\Http;

use OCP\AppFramework\Http;

/**
* Class StreamResponse
*
* @since 31.0.0
*
* @template-extends Response<int, array<string, mixed>>
*/
class StreamGeneratorResponse extends Response implements ICallbackResponse {

Check failure on line 20 in lib/public/AppFramework/Http/StreamGeneratorResponse.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidTemplateParam

lib/public/AppFramework/Http/StreamGeneratorResponse.php:20:7: InvalidTemplateParam: Extended template param S expects type OCP\AppFramework\Http::STATUS_*, type int given (see https://psalm.dev/183)

Check failure on line 20 in lib/public/AppFramework/Http/StreamGeneratorResponse.php

View workflow job for this annotation

GitHub Actions / static-code-analysis-ocp

InvalidTemplateParam

lib/public/AppFramework/Http/StreamGeneratorResponse.php:20:7: InvalidTemplateParam: Extended template param S expects type OCP\AppFramework\Http::STATUS_*, type int given (see https://psalm.dev/183)
protected $generator;

/**
* @since 31.0.0
*
* @param \Generator $generator the function to call to generate the response
* @param String $contentType http response content type e.g. 'application/json; charset=UTF-8'
* @param int $status http response status
*/
public function __construct($generator, $contentType, $status = Http::STATUS_OK) {
parent::__construct();

$this->generator = $generator;

$this->setStatus($status);
$this->cacheFor(0);
$this->addHeader('Content-Type', $contentType);

}

/**
* Streams content directly to client
*
* @since 31.0.0
*
* @param IOutput $output a small wrapper that handles output
*/
public function callback(IOutput $output) {

foreach ($this->generator as $chunk) {
print($chunk);
flush();
}

}

}
21 changes: 21 additions & 0 deletions lib/public/Calendar/CalendarExportRange.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Calendar;

/**
* Calendar Export Limit Range
*
* @since 31.0.0
*/
class CalendarExportRange {

public ?int $start = null;
public ?int $count = null;

}
25 changes: 25 additions & 0 deletions lib/public/Calendar/CalendarImportSettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Calendar;

/**
* Calendar Export Limit Range
*
* @since 31.0.0
*/
class CalendarImportSettings {

public string $format = 'ical';
public bool $supersede = false;
public bool $emitEvent = false;
public bool $emitITip = false;
public int $bulk = 32;
public int $validate = 1; // 0 - no validation, 1 - validate and skip on issue, 2 - validate and fail on issue

}
29 changes: 29 additions & 0 deletions lib/public/Calendar/ICalendarExport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Calendar;

use Generator;

/**
* ICalendar Interface Extension to export data
*
* @since 31.0.0
*/
interface ICalendarExport {

/**
* Export objects
*
* @since 31.0.0
*
* @return Generator<\Sabre\VObject\Component\VCalendar>
*/
public function export(?CalendarExportRange $range = null): Generator;

}
30 changes: 30 additions & 0 deletions lib/public/Calendar/ICalendarImport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Calendar;

use OCP\Calendar\CalendarImportSettings;
use Sabre\VObject\Component\VCalendar;

/**
* ICalendar Interface Extension to import data
*
* @since 31.0.0
*/
interface ICalendarImport {

/**
* Import objects
*
* @since 31.0.0
*
* @param VCalendar $vObjects
*/
public function import(CalendarImportSettings $settings, VCalendar ...$vObjects): array;

}
Loading