diff options
Diffstat (limited to 'apps/dav/lib/Service/ExampleEventService.php')
-rw-r--r-- | apps/dav/lib/Service/ExampleEventService.php | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/apps/dav/lib/Service/ExampleEventService.php b/apps/dav/lib/Service/ExampleEventService.php new file mode 100644 index 00000000000..3b2b07fe416 --- /dev/null +++ b/apps/dav/lib/Service/ExampleEventService.php @@ -0,0 +1,205 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Service; + +use OCA\DAV\AppInfo\Application; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Exception\ExampleEventException; +use OCA\DAV\Model\ExampleEvent; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IAppConfig; +use OCP\IL10N; +use OCP\Security\ISecureRandom; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VEvent; + +class ExampleEventService { + private const FOLDER_NAME = 'example_event'; + private const FILE_NAME = 'example_event.ics'; + private const ENABLE_CONFIG_KEY = 'create_example_event'; + + public function __construct( + private readonly CalDavBackend $calDavBackend, + private readonly ISecureRandom $random, + private readonly ITimeFactory $time, + private readonly IAppData $appData, + private readonly IAppConfig $appConfig, + private readonly IL10N $l10n, + ) { + } + + public function createExampleEvent(int $calendarId): void { + if (!$this->shouldCreateExampleEvent()) { + return; + } + + $exampleEvent = $this->getExampleEvent(); + $uid = $exampleEvent->getUid(); + $this->calDavBackend->createCalendarObject( + $calendarId, + "$uid.ics", + $exampleEvent->getIcs(), + ); + } + + private function getStartDate(): \DateTimeInterface { + return $this->time->now() + ->add(new \DateInterval('P7D')) + ->setTime(10, 00); + } + + private function getEndDate(): \DateTimeInterface { + return $this->time->now() + ->add(new \DateInterval('P7D')) + ->setTime(11, 00); + } + + private function getDefaultEvent(string $uid): VCalendar { + $defaultDescription = $this->l10n->t(<<<EOF +Welcome to Nextcloud Calendar! + +This is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want! + +With Nextcloud Calendar, you can: +- Create, edit, and manage events effortlessly. +- Create multiple calendars and share them with teammates, friends, or family. +- Check availability and display your busy times to others. +- Seamlessly integrate with apps and devices via CalDAV. +- Customize your experience: schedule recurring events, adjust notifications and other settings. +EOF); + + $vCalendar = new VCalendar(); + $props = [ + 'UID' => $uid, + 'DTSTAMP' => $this->time->now(), + 'SUMMARY' => $this->l10n->t('Example event - open me!'), + 'DTSTART' => $this->getStartDate(), + 'DTEND' => $this->getEndDate(), + 'DESCRIPTION' => $defaultDescription, + ]; + $vCalendar->add('VEVENT', $props); + return $vCalendar; + } + + /** + * @return string|null The ics of the custom example event or null if no custom event was uploaded. + * @throws ExampleEventException If reading the custom ics file fails. + */ + private function getCustomExampleEvent(): ?string { + try { + $folder = $this->appData->getFolder(self::FOLDER_NAME); + $icsFile = $folder->getFile(self::FILE_NAME); + } catch (NotFoundException $e) { + return null; + } + + try { + return $icsFile->getContent(); + } catch (NotFoundException|NotPermittedException $e) { + throw new ExampleEventException( + 'Failed to read custom example event', + 0, + $e, + ); + } + } + + /** + * Get the configured example event or the default one. + * + * @throws ExampleEventException If loading the custom example event fails. + */ + public function getExampleEvent(): ExampleEvent { + $uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC); + $customIcs = $this->getCustomExampleEvent(); + if ($customIcs === null) { + return new ExampleEvent($this->getDefaultEvent($uid), $uid); + } + + [$vCalendar, $vEvent] = $this->parseEvent($customIcs); + $vEvent->UID = $uid; + $vEvent->DTSTART = $this->getStartDate(); + $vEvent->DTEND = $this->getEndDate(); + $vEvent->remove('ORGANIZER'); + $vEvent->remove('ATTENDEE'); + return new ExampleEvent($vCalendar, $uid); + } + + /** + * @psalm-return list{VCalendar, VEvent} The VCALENDAR document and its VEVENT child component + * @throws ExampleEventException If parsing the event fails or if it is invalid. + */ + private function parseEvent(string $ics): array { + try { + $vCalendar = \Sabre\VObject\Reader::read($ics); + if (!($vCalendar instanceof VCalendar)) { + throw new ExampleEventException('Custom event does not contain a VCALENDAR component'); + } + + /** @var VEvent|null $vEvent */ + $vEvent = $vCalendar->getBaseComponent('VEVENT'); + if ($vEvent === null) { + throw new ExampleEventException('Custom event does not contain a VEVENT component'); + } + } catch (\Exception $e) { + throw new ExampleEventException('Failed to parse custom event: ' . $e->getMessage(), 0, $e); + } + + return [$vCalendar, $vEvent]; + } + + public function saveCustomExampleEvent(string $ics): void { + // Parse and validate the event before attempting to save it to prevent run time errors + $this->parseEvent($ics); + + try { + $folder = $this->appData->getFolder(self::FOLDER_NAME); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder(self::FOLDER_NAME); + } + + try { + $existingFile = $folder->getFile(self::FILE_NAME); + $existingFile->putContent($ics); + } catch (NotFoundException $e) { + $folder->newFile(self::FILE_NAME, $ics); + } + } + + public function deleteCustomExampleEvent(): void { + try { + $folder = $this->appData->getFolder(self::FOLDER_NAME); + $file = $folder->getFile(self::FILE_NAME); + } catch (NotFoundException $e) { + return; + } + + $file->delete(); + } + + public function hasCustomExampleEvent(): bool { + try { + return $this->getCustomExampleEvent() !== null; + } catch (ExampleEventException $e) { + return false; + } + } + + public function setCreateExampleEvent(bool $enable): void { + $this->appConfig->setValueBool(Application::APP_ID, self::ENABLE_CONFIG_KEY, $enable); + } + + public function shouldCreateExampleEvent(): bool { + return $this->appConfig->getValueBool(Application::APP_ID, self::ENABLE_CONFIG_KEY, true); + } +} |