|
8 | 8 | */ |
9 | 9 | namespace OCA\DAV\CalDAV; |
10 | 10 |
|
| 11 | +use Exception; |
| 12 | +use InvalidArgumentException; |
11 | 13 | use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin; |
12 | 14 | use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; |
| 15 | +use OCP\Calendar\CalendarImportOptions; |
13 | 16 | use OCP\Calendar\Exceptions\CalendarException; |
| 17 | +use OCP\Calendar\ICalendarImport; |
| 18 | +use OCP\Calendar\ICalendarIsShared; |
| 19 | +use OCP\Calendar\ICalendarIsWritable; |
14 | 20 | use OCP\Calendar\ICreateFromString; |
15 | 21 | use OCP\Calendar\IHandleImipMessage; |
16 | 22 | use OCP\Constants; |
|
20 | 26 | use Sabre\VObject\Component\VEvent; |
21 | 27 | use Sabre\VObject\Component\VTimeZone; |
22 | 28 | use Sabre\VObject\ITip\Message; |
| 29 | +use Sabre\VObject\Node; |
23 | 30 | use Sabre\VObject\Property; |
24 | 31 | use Sabre\VObject\Reader; |
| 32 | +use Sabre\VObject\UUIDUtil; |
| 33 | + |
25 | 34 | use function Sabre\Uri\split as uriSplit; |
26 | 35 |
|
27 | | -class CalendarImpl implements ICreateFromString, IHandleImipMessage { |
| 36 | +class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIsWritable, ICalendarIsShared, ICalendarImport { |
28 | 37 | public function __construct( |
29 | 38 | private Calendar $calendar, |
30 | 39 | /** @var array<string, mixed> */ |
@@ -257,4 +266,127 @@ public function handleIMipMessage(string $name, string $calendarData): void { |
257 | 266 | public function getInvitationResponseServer(): InvitationResponseServer { |
258 | 267 | return new InvitationResponseServer(false); |
259 | 268 | } |
| 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 | + } |
260 | 392 | } |
0 commit comments