From bf0561a1009d2799d492055963ed1a375ffc4112 Mon Sep 17 00:00:00 2001 From: Joachim van de Haterd Date: Mon, 16 Dec 2024 10:40:44 +0100 Subject: [PATCH] Ticket 33533: [Tasks] Fix .ics import --- CHANGELOG.md | 2 + www/go/core/fs/File.php | 11 ++-- www/go/core/util/StringUtil.php | 1 - .../community/tasks/convert/VCalendar.php | 57 +++++++++++++++---- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 551ef44fa4..8e2a308644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +- Tasks: fix several bugs in ICS import + 12-12-2024: 6.8.90 - Core: Show database error on upgrade - Addressbook: Check if comments module is installed diff --git a/www/go/core/fs/File.php b/www/go/core/fs/File.php index d497cb5824..d5f9c9e956 100644 --- a/www/go/core/fs/File.php +++ b/www/go/core/fs/File.php @@ -5,6 +5,7 @@ use go\core\ErrorHandler; use go\core\http\Response; use go\core\util\StringUtil; +use Throwable; /** @@ -58,10 +59,9 @@ public function getFolder(): ?Folder public function isWritable(): bool { try { - if($this->exists()) { + if ($this->exists()) { return is_writable($this->path); - }else - { + } else { return $this->getFolder()->isWritable(); } } catch(Throwable $e) { @@ -471,11 +471,12 @@ private function rangeDownload() { /** * Open file pointer - * + * * See php fopen function - * + * * @param string $mode * @return resource + * @throws Exception */ public function open(string $mode){ diff --git a/www/go/core/util/StringUtil.php b/www/go/core/util/StringUtil.php index a5dbb6fea3..82b647b3c5 100644 --- a/www/go/core/util/StringUtil.php +++ b/www/go/core/util/StringUtil.php @@ -105,7 +105,6 @@ public static function camelCaseToUnderscore(string $camelCasedString): string * * @param ?string $str * @param string|null $sourceCharset - * @param string * @return string */ public static function cleanUtf8(?string $str, string $sourceCharset = null): string diff --git a/www/go/modules/community/tasks/convert/VCalendar.php b/www/go/modules/community/tasks/convert/VCalendar.php index eeef1ae825..ad17cc6b0b 100644 --- a/www/go/modules/community/tasks/convert/VCalendar.php +++ b/www/go/modules/community/tasks/convert/VCalendar.php @@ -13,6 +13,8 @@ use go\modules\community\tasks\model\Category; use go\modules\community\tasks\model\Task; use Sabre\VObject\Component\VCalendar as VCalendarComponent; +use Sabre\VObject\InvalidDataException; +use Sabre\VObject\ParseException; use Sabre\VObject\Reader; use Sabre\VObject\Splitter\ICalendar as VCalendarSplitter; @@ -33,14 +35,15 @@ public function __construct() const EMPTY_NAME = '(no name)'; - /** * Parse an Event object to a VObject * * * @param task $task + * @throws \DateMalformedStringException */ - public function export(Task $task) { + public function export(Task $task): \Sabre\VObject\Document|VCalendarComponent + { if ($task->vcalendarBlobId) { //Contact has a stored VCard $blob = Blob::findById($task->vcalendarBlobId); @@ -187,18 +190,29 @@ public function getFileExtension(): string private $importSplitter; private $currentRecord; + + /** + * @throws ParseException + * @throws Exception + */ protected function initImport(File $file): void { - $contents = $file->getContents(); - $this->importSplitter = new VCalendarSplitter(StringUtil::cleanUtf8($contents), Reader::OPTION_FORGIVING + Reader::OPTION_IGNORE_INVALID_LINES); +// $contents = $file->getContents(); +// $this->importSplitter = new VCalendarSplitter(StringUtil::cleanUtf8($contents), Reader::OPTION_FORGIVING + Reader::OPTION_IGNORE_INVALID_LINES); + $this->importSplitter = new VCalendarSplitter($file->open('r'), Reader::OPTION_FORGIVING + Reader::OPTION_IGNORE_INVALID_LINES); } protected function nextImportRecord(): bool { $this->currentRecord = $this->importSplitter->getNext(); - return $this->currentRecord; + return isset($this->currentRecord); } - protected function importEntity() { + + /** + * @throws InvalidDataException + */ + protected function importEntity(): ?Task + { $vcal = $this->currentRecord; $tasklistId = $this->clientParams['values']['tasklistId']; @@ -279,15 +293,17 @@ private function importPriority($todo) { * @return Task * @throws \Sabre\VObject\InvalidDataException */ - public function vtodoToTask(VCalendarComponent $vcal, $tasklistId, $task = null) { + public function vtodoToTask(VCalendarComponent $vcal, $tasklistId, $task = null) + { $todo = $vcal->VTODO; $categoryIds = Category::find()->selectSingleValue('id') - ->where('name', 'IN', explode(",",(string)$todo->CATEGORIES)) + ->where('name', 'IN', explode(",", (string)$todo->CATEGORIES)) ->all(); - if(!empty($todo->RRULE) && !empty($todo->DTSTART)) + if (!empty($todo->RRULE) && !empty($todo->DTSTART)) { $rule = Recurrence::fromString((string)$todo->RRULE, $todo->DTSTART->getDateTime())->toArray(); - else - $rule = null; + } else { + $rule = null; + } if($task === null) { $task = new Task(); } @@ -355,6 +371,25 @@ public function vtodoToTask(VCalendarComponent $vcal, $tasklistId, $task = null) return $task; } + /** + * + * @param VCalendarComponent $VCalendarComponent + * @param int $taskListId + * @return Task | false + * @throws Exception + */ + private function findTask(VCalendarComponent $VCalendarComponent, int $taskListId): null|Task + { + $task = null; + + if (isset($VCalendarComponent->VTODO->uid)) { + $task = Task::find()->where(['tasklistId' => $taskListId, 'uid' => (string)$VCalendarComponent->VTODO->UID])->single(); + } + + return $task; + } + + public static function supportedExtensions(): array { return ['ics'];