Skip to content

Commit 719db50

Browse files
fixup! feat: OCC and OCS Calendar Import/Export
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent e9b0cfe commit 719db50

File tree

12 files changed

+108
-116
lines changed

12 files changed

+108
-116
lines changed

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,17 +967,19 @@ public function exportCalendar(int $calendarId, int $calendarType = self::CALEND
967967
->andWhere($qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType)))
968968
->andWhere($qb->expr()->isNull('deleted_at'));
969969
if ($options?->getRangeStart() !== null) {
970-
$qb->setFirstResult($options->getRangeStart());
970+
$qb->andWhere($qb->expr()->gt('uid', $qb->createNamedParameter($options->getRangeStart())));
971971
}
972972
if ($options?->getRangeCount() !== null) {
973973
$qb->setMaxResults($options->getRangeCount());
974974
}
975+
if ($options?->getRangeStart() !== null || $options?->getRangeCount() !== null) {
976+
$qb->orderBy('uid', 'ASC');
977+
}
975978
$rs = $qb->executeQuery();
976979

977980
while (($row = $rs->fetch()) !== false) {
978981
yield $row;
979982
}
980-
981983
$rs->closeCursor();
982984
}
983985

apps/dav/lib/CalDAV/CalendarImpl.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,9 @@ public function import(CalendarImportOptions $options, callable $generator): arr
348348
throw new InvalidArgumentException('Error importing calendar object <' . $uid . '>, ' . $issues[0]);
349349
}
350350
}
351-
351+
// create or update object in the data store
352352
$objectId = $this->backend->getCalendarObjectByUID($this->calendarInfo['principaluri'], $uid);
353353
$objectData = $vObject->serialize();
354-
355-
// create or update object
356354
if ($objectId === null) {
357355
$objectId = UUIDUtil::getUUID();
358356
$this->backend->createCalendarObject(
@@ -383,8 +381,8 @@ public function import(CalendarImportOptions $options, callable $generator): arr
383381
* Validate a component
384382
*
385383
* @param VCalendar $vObject
386-
* @param bool $repair Attempt to repair the component
387-
* @param int $level Minimum level of issues to return
384+
* @param bool $repair attempt to repair the component
385+
* @param int $level minimum level of issues to return
388386
* @return list<mixed>
389387
*/
390388
private function validateComponent(VCalendar $vObject, bool $repair, int $level): array {
@@ -401,7 +399,7 @@ private function validateComponent(VCalendar $vObject, bool $repair, int $level)
401399
$result[] = $issue['message'];
402400
}
403401
}
404-
402+
405403
return $result;
406404
}
407405

apps/dav/lib/CalDAV/Export/ExportService.php

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,14 @@
1616

1717
/**
1818
* Calendar Export Service
19-
*
20-
* @since 32.0.0
2119
*/
2220
class ExportService {
2321

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

26-
public function __construct() {
27-
}
28-
2924
/**
3025
* Generates serialized content stream for a calendar and objects based in selected format
3126
*
32-
* @since 32.0.0
33-
*
3427
* @return Generator<string>
3528
*/
3629
public function export(ICalendarExport $calendar, CalendarExportOptions $options): Generator {
@@ -64,8 +57,6 @@ public function export(ICalendarExport $calendar, CalendarExportOptions $options
6457

6558
/**
6659
* Generates serialized content start based on selected format
67-
*
68-
* @since 32.0.0
6960
*/
7061
private function exportStart(string $format): string {
7162
return match ($format) {
@@ -77,8 +68,6 @@ private function exportStart(string $format): string {
7768

7869
/**
7970
* Generates serialized content end based on selected format
80-
*
81-
* @since 32.0.0
8271
*/
8372
private function exportFinish(string $format): string {
8473
return match ($format) {
@@ -90,8 +79,6 @@ private function exportFinish(string $format): string {
9079

9180
/**
9281
* Generates serialized content for a component based on selected format
93-
*
94-
* @since 32.0.0
9582
*/
9683
private function exportObject(Component $vobject, string $format, bool $consecutive): string {
9784
return match ($format) {
@@ -103,8 +90,6 @@ private function exportObject(Component $vobject, string $format, bool $consecut
10390

10491
/**
10592
* Generates serialized content for a component in xml format
106-
*
107-
* @since 32.0.0
10893
*/
10994
private function exportObjectXml(Component $vobject): string {
11095
$writer = new \Sabre\Xml\Writer();

apps/dav/lib/CalDAV/Import/ImportService.php

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
/**
1818
* Calendar Import Service
19-
*
20-
* @since 32.0.0
2119
*/
2220
class ImportService {
2321

@@ -31,20 +29,17 @@ public function __construct() {
3129
/**
3230
* Executes import with appropriate object generator based on format
3331
*
34-
* @since 32.0.0
35-
*
3632
* @param resource $source
3733
*
3834
* @return array<string,array<string,string|array<string>>>
3935
*
4036
* @throws \InvalidArgumentException
4137
*/
4238
public function import($source, ICalendarImport $calendar, CalendarImportOptions $options): array {
43-
4439
if (!is_resource($source)) {
4540
throw new \InvalidArgumentException('Invalid import source must be a file resource');
4641
}
47-
42+
4843
$this->source = $source;
4944

5045
switch ($options->getFormat()) {
@@ -65,8 +60,6 @@ public function import($source, ICalendarImport $calendar, CalendarImportOptions
6560
/**
6661
* Generates object stream from a text formatted source (ical)
6762
*
68-
* @since 32.0.0
69-
*
7063
* @return Generator<\Sabre\VObject\Component\VCalendar>
7164
*/
7265
private function importText(CalendarImportOptions $options): Generator {
@@ -85,13 +78,16 @@ private function importText(CalendarImportOptions $options): Generator {
8578
$timezones = [];
8679
foreach ($structure['VTIMEZONE'] as $tid => $collection) {
8780
$instance = $collection[0];
88-
$sObjectContents = $importer->extract($instance[2], $instance[3]);
81+
$sObjectContents = $importer->extract((int)$instance[2], (int)$instance[3]);
8982
$vObject = Reader::read($sObjectPrefix . $sObjectContents . $sObjectSuffix);
9083
$timezones[$tid] = clone $vObject->VTIMEZONE;
9184
}
9285
// calendar components
86+
// for each component type, construct a full calendar object with all components
87+
// that match the same UID and appropriate time zones that are used in the components
9388
foreach (['VEVENT', 'VTODO', 'VJOURNAL'] as $type) {
9489
foreach ($structure[$type] as $cid => $instances) {
90+
/** @var array<int,VCalendar> $instances */
9591
// extract all instances of component and unserialize to object
9692
$sObjectContents = '';
9793
foreach ($instances as $instance) {
@@ -105,7 +101,6 @@ private function importText(CalendarImportOptions $options): Generator {
105101
$vObject->add(clone $timezones[$zone]);
106102
}
107103
}
108-
// return object
109104
yield $vObject;
110105
}
111106
}
@@ -114,8 +109,6 @@ private function importText(CalendarImportOptions $options): Generator {
114109
/**
115110
* Generates object stream from a xml formatted source (xcal)
116111
*
117-
* @since 32.0.0
118-
*
119112
* @return Generator<\Sabre\VObject\Component\VCalendar>
120113
*/
121114
private function importXml(CalendarImportOptions $options): Generator {
@@ -127,13 +120,16 @@ private function importXml(CalendarImportOptions $options): Generator {
127120
$timezones = [];
128121
foreach ($structure['VTIMEZONE'] as $tid => $collection) {
129122
$instance = $collection[0];
130-
$sObjectContents = $importer->extract($instance[2], $instance[3]);
123+
$sObjectContents = $importer->extract((int)$instance[2], (int)$instance[3]);
131124
$vObject = Reader::readXml($sObjectPrefix . $sObjectContents . $sObjectSuffix);
132125
$timezones[$tid] = clone $vObject->VTIMEZONE;
133126
}
134127
// calendar components
128+
// for each component type, construct a full calendar object with all components
129+
// that match the same UID and appropriate time zones that are used in the components
135130
foreach (['VEVENT', 'VTODO', 'VJOURNAL'] as $type) {
136131
foreach ($structure[$type] as $cid => $instances) {
132+
/** @var array<int,VCalendar> $instances */
137133
// extract all instances of component and unserialize to object
138134
$sObjectContents = '';
139135
foreach ($instances as $instance) {
@@ -147,7 +143,6 @@ private function importXml(CalendarImportOptions $options): Generator {
147143
$vObject->add(clone $timezones[$zone]);
148144
}
149145
}
150-
// return object
151146
yield $vObject;
152147
}
153148
}
@@ -156,8 +151,6 @@ private function importXml(CalendarImportOptions $options): Generator {
156151
/**
157152
* Generates object stream from a json formatted source (jcal)
158153
*
159-
* @since 32.0.0
160-
*
161154
* @return Generator<\Sabre\VObject\Component\VCalendar>
162155
*/
163156
private function importJson(CalendarImportOptions $options): Generator {
@@ -173,7 +166,6 @@ private function importJson(CalendarImportOptions $options): Generator {
173166
}
174167
// calendar components
175168
foreach ($importer->getBaseComponents() as $base) {
176-
/** @var VCalendar $vObject */
177169
$vObject = new VCalendar;
178170
$vObject->VERSION = clone $importer->VERSION;
179171
$vObject->PRODID = clone $importer->PRODID;
@@ -187,16 +179,13 @@ private function importJson(CalendarImportOptions $options): Generator {
187179
$vObject->add(clone $timezones[$zone]);
188180
}
189181
}
190-
// return object
191182
yield $vObject;
192183
}
193184
}
194185

195186
/**
196187
* Searches through all component properties looking for defined timezones
197188
*
198-
* @since 32.0.0
199-
*
200189
* @return array<string>
201190
*/
202191
private function findTimeZones(VCalendar $vObject): array {

apps/dav/lib/CalDAV/Import/TextImporter.php

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,26 @@ public function __construct(
2828
}
2929
}
3030

31+
/**
32+
* Analyzes the source data and creates a structure of components
33+
*/
3134
protected function analyze() {
32-
3335
$componentStart = null;
3436
$componentEnd = null;
3537
$componentId = null;
3638
$componentType = null;
37-
39+
// iterate through the source data line by line
3840
fseek($this->source, 0);
3941
while (!feof($this->source)) {
4042
$data = fgets($this->source);
41-
43+
// skip empty lines
4244
if ($data === false || empty(trim($data))) {
4345
continue;
4446
}
45-
47+
// check for withspace at the beginning of the line
48+
// lines with whitespace at the beginning are continuations of the pervious line
4649
if (ctype_space($data[0]) === false) {
47-
50+
// check line for component start, remember the position and determine the type
4851
if (str_starts_with($data, 'BEGIN:')) {
4952
$type = trim(substr($data, 6));
5053
if (in_array($type, $this->types)) {
@@ -53,32 +56,30 @@ protected function analyze() {
5356
}
5457
unset($type);
5558
}
56-
59+
// check line for component end, remember the position
5760
if (str_starts_with($data, 'END:')) {
5861
$type = trim(substr($data, 4));
5962
if ($componentType === $type) {
6063
$componentEnd = ftell($this->source);
6164
}
6265
unset($type);
6366
}
64-
67+
// check line for component id
6568
if ($componentStart !== null && str_starts_with($data, 'UID:')) {
6669
$componentId = trim(substr($data, 5));
6770
}
68-
6971
if ($componentStart !== null && str_starts_with($data, 'TZID:')) {
7072
$componentId = trim(substr($data, 5));
7173
}
72-
7374
}
74-
75+
// any line(s) not inside a component are VCALENDAR properties
7576
if ($componentStart === null) {
7677
if (!str_starts_with($data, 'BEGIN:VCALENDAR') && !str_starts_with($data, 'END:VCALENDAR')) {
7778
$components['VCALENDAR'][] = $data;
7879
}
7980
}
80-
81-
if ($componentEnd !== null) {
81+
// if component start and end are found, add the component to the structure
82+
if ($componentStart !== null && $componentEnd !== null) {
8283
if ($componentId !== null) {
8384
$this->structure[$componentType][$componentId][] = [
8485
$componentType,
@@ -99,25 +100,37 @@ protected function analyze() {
99100
$componentStart = null;
100101
$componentEnd = null;
101102
}
102-
103103
}
104-
105104
}
106105

106+
/**
107+
* Returns the analyzed structure of the source data
108+
* the analyzed structure is a collection of components organized by type,
109+
* each entry is a collection of instances
110+
* [
111+
* 'VEVENT' => [
112+
* '7456f141-b478-4cb9-8efc-1427ba0d6839' => [
113+
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 0, 100 ],
114+
* ['VEVENT', '7456f141-b478-4cb9-8efc-1427ba0d6839', 100, 200 ]
115+
* ]
116+
* ]
117+
* ]
118+
*/
107119
public function structure(): array {
108-
109120
if (!$this->analyzed) {
110121
$this->analyze();
111122
}
112-
113123
return $this->structure;
114124
}
115125

126+
/**
127+
* Extracts a string chuck from the source data
128+
*
129+
* @param int $start starting byte position
130+
* @param int $end ending byte position
131+
*/
116132
public function extract(int $start, int $end): string {
117-
118133
fseek($this->source, $start);
119134
return fread($this->source, $end - $start);
120-
121135
}
122-
123136
}

0 commit comments

Comments
 (0)