diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 2ca5cf66f901f..6178526d7f4ff 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -396,6 +396,8 @@ 'OCA\\DAV\\Migration\\Version1036Date20251202000000' => $baseDir . '/../lib/Migration/Version1036Date20251202000000.php', 'OCA\\DAV\\Migration\\Version1038Date20260302000000' => $baseDir . '/../lib/Migration/Version1038Date20260302000000.php', 'OCA\\DAV\\Migration\\Version1039Date20260408000000' => $baseDir . '/../lib/Migration/Version1039Date20260408000000.php', + 'OCA\\DAV\\Migration\\Version1040Date20260703120000' => $baseDir . '/../lib/Migration/Version1040Date20260703120000.php', + 'OCA\\DAV\\Migration\\Version1041Date20260703120100' => $baseDir . '/../lib/Migration/Version1041Date20260703120100.php', 'OCA\\DAV\\Model\\ExampleEvent' => $baseDir . '/../lib/Model/ExampleEvent.php', 'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php', 'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index c35dd97c02c0e..69ee65b084647 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -411,6 +411,8 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1036Date20251202000000' => __DIR__ . '/..' . '/../lib/Migration/Version1036Date20251202000000.php', 'OCA\\DAV\\Migration\\Version1038Date20260302000000' => __DIR__ . '/..' . '/../lib/Migration/Version1038Date20260302000000.php', 'OCA\\DAV\\Migration\\Version1039Date20260408000000' => __DIR__ . '/..' . '/../lib/Migration/Version1039Date20260408000000.php', + 'OCA\\DAV\\Migration\\Version1040Date20260703120000' => __DIR__ . '/..' . '/../lib/Migration/Version1040Date20260703120000.php', + 'OCA\\DAV\\Migration\\Version1041Date20260703120100' => __DIR__ . '/..' . '/../lib/Migration/Version1041Date20260703120100.php', 'OCA\\DAV\\Model\\ExampleEvent' => __DIR__ . '/..' . '/../lib/Model/ExampleEvent.php', 'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php', 'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php', diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 9254ba2ad102a..d499060ae5b02 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -152,8 +152,8 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription '{http://apple.com/ns/ical/}calendar-order' => ['calendarorder', 'int'], '{http://apple.com/ns/ical/}calendar-color' => ['calendarcolor', 'string'], '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => ['deleted_at', 'int'], - '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}default-alarm-part-day' => ['default_alarm_pday', 'int'], - '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}default-alarm-full-day' => ['default_alarm_fday', 'int'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}default-alarms-part-day' => ['default_alarms_pday', 'string'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}default-alarms-full-day' => ['default_alarms_fday', 'string'], ]; /** diff --git a/apps/dav/lib/Migration/Version1040Date20260703120000.php b/apps/dav/lib/Migration/Version1040Date20260703120000.php new file mode 100644 index 0000000000000..24a9bd68014a6 --- /dev/null +++ b/apps/dav/lib/Migration/Version1040Date20260703120000.php @@ -0,0 +1,90 @@ +getTable('calendars'); + + if (!$calendarsTable->hasColumn('default_alarms_pday')) { + $calendarsTable->addColumn('default_alarms_pday', Types::TEXT, [ + 'notnull' => false, + 'default' => null, + ]); + } + + if (!$calendarsTable->hasColumn('default_alarms_fday')) { + $calendarsTable->addColumn('default_alarms_fday', Types::TEXT, [ + 'notnull' => false, + 'default' => null, + ]); + } + + return $schema; + } + + #[Override] + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $calendarsTable = $schema->getTable('calendars'); + + if ($calendarsTable->hasColumn('default_alarm_pday') && $calendarsTable->hasColumn('default_alarms_pday')) { + $this->migrateLegacyIntColumn('default_alarm_pday', 'default_alarms_pday'); + } + + if ($calendarsTable->hasColumn('default_alarm_fday') && $calendarsTable->hasColumn('default_alarms_fday')) { + $this->migrateLegacyIntColumn('default_alarm_fday', 'default_alarms_fday'); + } + } + + /** + * Encode a single legacy trigger int as [{"trigger":N,"action":"DISPLAY"}] + * for all rows still missing the JSON column value. + */ + private function migrateLegacyIntColumn(string $legacyColumn, string $jsonColumn): void { + $qb = $this->db->getQueryBuilder(); + $qb->update('calendars') + ->set($jsonColumn, $qb->func()->concat( + $qb->expr()->literal('[{"trigger":'), + $legacyColumn, + $qb->expr()->literal(',"action":"DISPLAY"}]'), + )) + ->where($qb->expr()->isNotNull($legacyColumn)) + ->andWhere($qb->expr()->isNull($jsonColumn)); + $qb->executeStatement(); + } +} diff --git a/apps/dav/lib/Migration/Version1041Date20260703120100.php b/apps/dav/lib/Migration/Version1041Date20260703120100.php new file mode 100644 index 0000000000000..8816684b5dc1a --- /dev/null +++ b/apps/dav/lib/Migration/Version1041Date20260703120100.php @@ -0,0 +1,42 @@ +getTable('calendars'); + + if ($calendarsTable->hasColumn('default_alarm_pday')) { + $calendarsTable->dropColumn('default_alarm_pday'); + } + + if ($calendarsTable->hasColumn('default_alarm_fday')) { + $calendarsTable->dropColumn('default_alarm_fday'); + } + + return $schema; + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index e0f507fbe358d..9faed5a80ca41 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -1944,46 +1944,64 @@ public function testUnshare(): void { } - public function testDefaultAlarmProperties(): void { + public function testDefaultAlarmsProperties(): void { $calendarId = $this->createTestCalendar(); - // Test setting both default alarm properties + $partDayProperty = '{http://nextcloud.com/ns}default-alarms-part-day'; + $fullDayProperty = '{http://nextcloud.com/ns}default-alarms-full-day'; + + $partDayJson = json_encode([ + ['trigger' => -86400, 'action' => 'EMAIL'], + ['trigger' => -900, 'action' => 'DISPLAY'], + ], JSON_THROW_ON_ERROR); + $fullDayJson = json_encode([ + ['trigger' => -3600, 'action' => 'EMAIL'], + ], JSON_THROW_ON_ERROR); + + // Set plural default-alarms properties $patch = new PropPatch([ - '{http://nextcloud.com/ns}default-alarm-part-day' => -900, - '{http://nextcloud.com/ns}default-alarm-full-day' => -3600, + $partDayProperty => $partDayJson, + $fullDayProperty => $fullDayJson, ]); $this->backend->updateCalendar($calendarId, $patch); $patch->commit(); - // Verify the properties were set $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); $this->assertCount(1, $calendars); - $this->assertEquals(-900, $calendars[0]['{http://nextcloud.com/ns}default-alarm-part-day']); - $this->assertEquals(-3600, $calendars[0]['{http://nextcloud.com/ns}default-alarm-full-day']); - - // Test updating to different values + $this->assertEquals($partDayJson, $calendars[0][$partDayProperty]); + $this->assertEquals($fullDayJson, $calendars[0][$fullDayProperty]); + $this->assertArrayNotHasKey('{http://nextcloud.com/ns}default-alarm-part-day', $calendars[0]); + $this->assertArrayNotHasKey('{http://nextcloud.com/ns}default-alarm-full-day', $calendars[0]); + + // Update to different values + $updatedPartDayJson = json_encode([ + ['trigger' => -1800, 'action' => 'DISPLAY'], + ], JSON_THROW_ON_ERROR); + $updatedFullDayJson = json_encode([ + ['trigger' => -43200, 'action' => 'DISPLAY'], + ], JSON_THROW_ON_ERROR); $patch = new PropPatch([ - '{http://nextcloud.com/ns}default-alarm-part-day' => -86400, - '{http://nextcloud.com/ns}default-alarm-full-day' => -43200, + $partDayProperty => $updatedPartDayJson, + $fullDayProperty => $updatedFullDayJson, ]); $this->backend->updateCalendar($calendarId, $patch); $patch->commit(); $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - $this->assertEquals(-86400, $calendars[0]['{http://nextcloud.com/ns}default-alarm-part-day']); - $this->assertEquals(-43200, $calendars[0]['{http://nextcloud.com/ns}default-alarm-full-day']); + $this->assertEquals($updatedPartDayJson, $calendars[0][$partDayProperty]); + $this->assertEquals($updatedFullDayJson, $calendars[0][$fullDayProperty]); - // Test setting to null + // Clear properties $patch = new PropPatch([ - '{http://nextcloud.com/ns}default-alarm-part-day' => null, - '{http://nextcloud.com/ns}default-alarm-full-day' => null, + $partDayProperty => null, + $fullDayProperty => null, ]); $this->backend->updateCalendar($calendarId, $patch); $patch->commit(); $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - $this->assertNull($calendars[0]['{http://nextcloud.com/ns}default-alarm-part-day']); - $this->assertNull($calendars[0]['{http://nextcloud.com/ns}default-alarm-full-day']); + $this->assertNull($calendars[0][$partDayProperty]); + $this->assertNull($calendars[0][$fullDayProperty]); // Clean up $this->backend->deleteCalendar($calendars[0]['id'], true);