diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 393dc33df5b7d..0708c400ee854 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -10,7 +10,7 @@ WebDAV WebDAV endpoint WebDAV endpoint - 2.0.0-dev.0 + 2.0.0-dev.1 agpl owncloud.org DAV diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 056e9fb9d5839..e2bf903181b1c 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -401,6 +401,8 @@ 'OCA\\DAV\\Migration\\Version1038Date20260302000000' => $baseDir . '/../lib/Migration/Version1038Date20260302000000.php', 'OCA\\DAV\\Migration\\Version1039Date20260408000000' => $baseDir . '/../lib/Migration/Version1039Date20260408000000.php', 'OCA\\DAV\\Migration\\Version1040Date20260805000000' => $baseDir . '/../lib/Migration/Version1040Date20260805000000.php', + 'OCA\\DAV\\Migration\\Version2000Date20260703100001' => $baseDir . '/../lib/Migration/Version2000Date20260703100001.php', + 'OCA\\DAV\\Migration\\Version2000Date20260703100002' => $baseDir . '/../lib/Migration/Version2000Date20260703100002.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 840df1461d056..318660f02f8b7 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -416,6 +416,8 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1038Date20260302000000' => __DIR__ . '/..' . '/../lib/Migration/Version1038Date20260302000000.php', 'OCA\\DAV\\Migration\\Version1039Date20260408000000' => __DIR__ . '/..' . '/../lib/Migration/Version1039Date20260408000000.php', 'OCA\\DAV\\Migration\\Version1040Date20260805000000' => __DIR__ . '/..' . '/../lib/Migration/Version1040Date20260805000000.php', + 'OCA\\DAV\\Migration\\Version2000Date20260703100001' => __DIR__ . '/..' . '/../lib/Migration/Version2000Date20260703100001.php', + 'OCA\\DAV\\Migration\\Version2000Date20260703100002' => __DIR__ . '/..' . '/../lib/Migration/Version2000Date20260703100002.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 59931eb6b0b53..d25fba96ab9b6 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -151,8 +151,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/Version2000Date20260703100001.php b/apps/dav/lib/Migration/Version2000Date20260703100001.php new file mode 100644 index 0000000000000..baa054c94c828 --- /dev/null +++ b/apps/dav/lib/Migration/Version2000Date20260703100001.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/Version2000Date20260703100002.php b/apps/dav/lib/Migration/Version2000Date20260703100002.php new file mode 100644 index 0000000000000..226243af26aa2 --- /dev/null +++ b/apps/dav/lib/Migration/Version2000Date20260703100002.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 75bd0f118906f..9ef955bbbd1fb 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -2081,46 +2081,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);