Skip to content

Commit

Permalink
fixup! 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 19, 2025
1 parent e9b0cfe commit 719db50
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 116 deletions.
6 changes: 4 additions & 2 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -967,17 +967,19 @@ public function exportCalendar(int $calendarId, int $calendarType = self::CALEND
->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
->andWhere($qb->expr()->isNull('deleted_at'));
if ($options?->getRangeStart() !== null) {
$qb->setFirstResult($options->getRangeStart());
$qb->andWhere($qb->expr()->gt('uid', $qb->createNamedParameter($options->getRangeStart())));
}
if ($options?->getRangeCount() !== null) {
$qb->setMaxResults($options->getRangeCount());
}
if ($options?->getRangeStart() !== null || $options?->getRangeCount() !== null) {
$qb->orderBy('uid', 'ASC');
}
$rs = $qb->executeQuery();

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

$rs->closeCursor();
}

Expand Down
10 changes: 4 additions & 6 deletions apps/dav/lib/CalDAV/CalendarImpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,9 @@ public function import(CalendarImportOptions $options, callable $generator): arr
throw new InvalidArgumentException('Error importing calendar object <' . $uid . '>, ' . $issues[0]);
}
}

// create or update object in the data store
$objectId = $this->backend->getCalendarObjectByUID($this->calendarInfo['principaluri'], $uid);
$objectData = $vObject->serialize();

// create or update object
if ($objectId === null) {
$objectId = UUIDUtil::getUUID();
$this->backend->createCalendarObject(
Expand Down Expand Up @@ -383,8 +381,8 @@ public function import(CalendarImportOptions $options, callable $generator): arr
* Validate a component
*
* @param VCalendar $vObject
* @param bool $repair Attempt to repair the component
* @param int $level Minimum level of issues to return
* @param bool $repair attempt to repair the component
* @param int $level minimum level of issues to return
* @return list<mixed>
*/
private function validateComponent(VCalendar $vObject, bool $repair, int $level): array {
Expand All @@ -401,7 +399,7 @@ private function validateComponent(VCalendar $vObject, bool $repair, int $level)
$result[] = $issue['message'];
}
}

return $result;
}

Expand Down
15 changes: 0 additions & 15 deletions apps/dav/lib/CalDAV/Export/ExportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,14 @@

/**
* Calendar Export Service
*
* @since 32.0.0
*/
class ExportService {

public const FORMATS = ['ical', 'jcal', 'xcal'];

public function __construct() {
}

/**
* Generates serialized content stream for a calendar and objects based in selected format
*
* @since 32.0.0
*
* @return Generator<string>
*/
public function export(ICalendarExport $calendar, CalendarExportOptions $options): Generator {
Expand Down Expand Up @@ -64,8 +57,6 @@ public function export(ICalendarExport $calendar, CalendarExportOptions $options

/**
* Generates serialized content start based on selected format
*
* @since 32.0.0
*/
private function exportStart(string $format): string {
return match ($format) {
Expand All @@ -77,8 +68,6 @@ private function exportStart(string $format): string {

/**
* Generates serialized content end based on selected format
*
* @since 32.0.0
*/
private function exportFinish(string $format): string {
return match ($format) {
Expand All @@ -90,8 +79,6 @@ private function exportFinish(string $format): string {

/**
* Generates serialized content for a component based on selected format
*
* @since 32.0.0
*/
private function exportObject(Component $vobject, string $format, bool $consecutive): string {
return match ($format) {
Expand All @@ -103,8 +90,6 @@ private function exportObject(Component $vobject, string $format, bool $consecut

/**
* Generates serialized content for a component in xml format
*
* @since 32.0.0
*/
private function exportObjectXml(Component $vobject): string {
$writer = new \Sabre\Xml\Writer();
Expand Down
29 changes: 9 additions & 20 deletions apps/dav/lib/CalDAV/Import/ImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

/**
* Calendar Import Service
*
* @since 32.0.0
*/
class ImportService {

Expand All @@ -31,20 +29,17 @@ public function __construct() {
/**
* Executes import with appropriate object generator based on format
*
* @since 32.0.0
*
* @param resource $source
*
* @return array<string,array<string,string|array<string>>>
*
* @throws \InvalidArgumentException
*/
public function import($source, ICalendarImport $calendar, CalendarImportOptions $options): array {

if (!is_resource($source)) {
throw new \InvalidArgumentException('Invalid import source must be a file resource');
}

$this->source = $source;

switch ($options->getFormat()) {
Expand All @@ -65,8 +60,6 @@ public function import($source, ICalendarImport $calendar, CalendarImportOptions
/**
* Generates object stream from a text formatted source (ical)
*
* @since 32.0.0
*
* @return Generator<\Sabre\VObject\Component\VCalendar>
*/
private function importText(CalendarImportOptions $options): Generator {
Expand All @@ -85,13 +78,16 @@ private function importText(CalendarImportOptions $options): Generator {
$timezones = [];
foreach ($structure['VTIMEZONE'] as $tid => $collection) {
$instance = $collection[0];
$sObjectContents = $importer->extract($instance[2], $instance[3]);
$sObjectContents = $importer->extract((int)$instance[2], (int)$instance[3]);
$vObject = Reader::read($sObjectPrefix . $sObjectContents . $sObjectSuffix);
$timezones[$tid] = clone $vObject->VTIMEZONE;
}
// calendar components
// for each component type, construct a full calendar object with all components
// that match the same UID and appropriate time zones that are used in the components
foreach (['VEVENT', 'VTODO', 'VJOURNAL'] as $type) {
foreach ($structure[$type] as $cid => $instances) {
/** @var array<int,VCalendar> $instances */
// extract all instances of component and unserialize to object
$sObjectContents = '';
foreach ($instances as $instance) {
Expand All @@ -105,7 +101,6 @@ private function importText(CalendarImportOptions $options): Generator {
$vObject->add(clone $timezones[$zone]);
}
}
// return object
yield $vObject;
}
}
Expand All @@ -114,8 +109,6 @@ private function importText(CalendarImportOptions $options): Generator {
/**
* Generates object stream from a xml formatted source (xcal)
*
* @since 32.0.0
*
* @return Generator<\Sabre\VObject\Component\VCalendar>
*/
private function importXml(CalendarImportOptions $options): Generator {
Expand All @@ -127,13 +120,16 @@ private function importXml(CalendarImportOptions $options): Generator {
$timezones = [];
foreach ($structure['VTIMEZONE'] as $tid => $collection) {
$instance = $collection[0];
$sObjectContents = $importer->extract($instance[2], $instance[3]);
$sObjectContents = $importer->extract((int)$instance[2], (int)$instance[3]);
$vObject = Reader::readXml($sObjectPrefix . $sObjectContents . $sObjectSuffix);
$timezones[$tid] = clone $vObject->VTIMEZONE;
}
// calendar components
// for each component type, construct a full calendar object with all components
// that match the same UID and appropriate time zones that are used in the components
foreach (['VEVENT', 'VTODO', 'VJOURNAL'] as $type) {
foreach ($structure[$type] as $cid => $instances) {
/** @var array<int,VCalendar> $instances */
// extract all instances of component and unserialize to object
$sObjectContents = '';
foreach ($instances as $instance) {
Expand All @@ -147,7 +143,6 @@ private function importXml(CalendarImportOptions $options): Generator {
$vObject->add(clone $timezones[$zone]);
}
}
// return object
yield $vObject;
}
}
Expand All @@ -156,8 +151,6 @@ private function importXml(CalendarImportOptions $options): Generator {
/**
* Generates object stream from a json formatted source (jcal)
*
* @since 32.0.0
*
* @return Generator<\Sabre\VObject\Component\VCalendar>
*/
private function importJson(CalendarImportOptions $options): Generator {
Expand All @@ -173,7 +166,6 @@ private function importJson(CalendarImportOptions $options): Generator {
}
// calendar components
foreach ($importer->getBaseComponents() as $base) {
/** @var VCalendar $vObject */
$vObject = new VCalendar;
$vObject->VERSION = clone $importer->VERSION;
$vObject->PRODID = clone $importer->PRODID;
Expand All @@ -187,16 +179,13 @@ private function importJson(CalendarImportOptions $options): Generator {
$vObject->add(clone $timezones[$zone]);
}
}
// return object
yield $vObject;
}
}

/**
* Searches through all component properties looking for defined timezones
*
* @since 32.0.0
*
* @return array<string>
*/
private function findTimeZones(VCalendar $vObject): array {
Expand Down
51 changes: 32 additions & 19 deletions apps/dav/lib/CalDAV/Import/TextImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,26 @@ public function __construct(
}
}

/**
* Analyzes the source data and creates a structure of components
*/
protected function analyze() {

$componentStart = null;
$componentEnd = null;
$componentId = null;
$componentType = null;

// iterate through the source data line by line
fseek($this->source, 0);
while (!feof($this->source)) {
$data = fgets($this->source);

// skip empty lines
if ($data === false || empty(trim($data))) {
continue;
}

// check for withspace at the beginning of the line
// lines with whitespace at the beginning are continuations of the pervious line
if (ctype_space($data[0]) === false) {

// check line for component start, remember the position and determine the type
if (str_starts_with($data, 'BEGIN:')) {
$type = trim(substr($data, 6));
if (in_array($type, $this->types)) {
Expand All @@ -53,32 +56,30 @@ protected function analyze() {
}
unset($type);
}

// check line for component end, remember the position
if (str_starts_with($data, 'END:')) {
$type = trim(substr($data, 4));
if ($componentType === $type) {
$componentEnd = ftell($this->source);
}
unset($type);
}

// check line for component id
if ($componentStart !== null && str_starts_with($data, 'UID:')) {
$componentId = trim(substr($data, 5));
}

if ($componentStart !== null && str_starts_with($data, 'TZID:')) {
$componentId = trim(substr($data, 5));
}

}

// any line(s) not inside a component are VCALENDAR properties
if ($componentStart === null) {
if (!str_starts_with($data, 'BEGIN:VCALENDAR') && !str_starts_with($data, 'END:VCALENDAR')) {
$components['VCALENDAR'][] = $data;
}
}

if ($componentEnd !== null) {
// if component start and end are found, add the component to the structure
if ($componentStart !== null && $componentEnd !== null) {
if ($componentId !== null) {
$this->structure[$componentType][$componentId][] = [
$componentType,
Expand All @@ -99,25 +100,37 @@ protected function analyze() {
$componentStart = null;
$componentEnd = null;
}

}

}

/**
* Returns the analyzed structure of the source data
* the analyzed structure is a collection of components organized by type,
* each entry is a collection of instances
* [
* 'VEVENT' => [
* '7456f141-b478-4cb9-8efc-1427ba0d6839' => [
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 0, 100 ],
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 100, 200 ]
* ]
* ]
* ]
*/
public function structure(): array {

if (!$this->analyzed) {
$this->analyze();
}

return $this->structure;
}

/**
* Extracts a string chuck from the source data
*
* @param int $start starting byte position
* @param int $end ending byte position
*/
public function extract(int $start, int $end): string {

fseek($this->source, $start);
return fread($this->source, $end - $start);

}

}
Loading

0 comments on commit 719db50

Please sign in to comment.