Skip to content

Commit 24485e2

Browse files
committed
feat(dav): store multiple default calendar alarms as JSON
Add default_alarms_pday/fday TEXT columns and CalDAV properties default-alarms-part-day/full-day for alarm templates with trigger and DISPLAY|EMAIL action. Legacy integer columns remain in sync for NC34 clients. Includes migration from existing single-int defaults. Assisted-by: Grok:grok-4 Signed-off-by: Richard Freeman <rich@rich0.org>
1 parent ff020ad commit 24485e2

6 files changed

Lines changed: 470 additions & 8 deletions

File tree

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php',
6262
'OCA\\DAV\\CalDAV\\CalendarProvider' => $baseDir . '/../lib/CalDAV/CalendarProvider.php',
6363
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
64+
'OCA\\DAV\\CalDAV\\DefaultCalendarAlarms' => $baseDir . '/../lib/CalDAV/DefaultCalendarAlarms.php',
6465
'OCA\\DAV\\CalDAV\\DefaultCalendarValidator' => $baseDir . '/../lib/CalDAV/DefaultCalendarValidator.php',
6566
'OCA\\DAV\\CalDAV\\EmbeddedCalDavServer' => $baseDir . '/../lib/CalDAV/EmbeddedCalDavServer.php',
6667
'OCA\\DAV\\CalDAV\\EventComparisonService' => $baseDir . '/../lib/CalDAV/EventComparisonService.php',
@@ -396,6 +397,7 @@
396397
'OCA\\DAV\\Migration\\Version1036Date20251202000000' => $baseDir . '/../lib/Migration/Version1036Date20251202000000.php',
397398
'OCA\\DAV\\Migration\\Version1038Date20260302000000' => $baseDir . '/../lib/Migration/Version1038Date20260302000000.php',
398399
'OCA\\DAV\\Migration\\Version1039Date20260408000000' => $baseDir . '/../lib/Migration/Version1039Date20260408000000.php',
400+
'OCA\\DAV\\Migration\\Version1040Date20260703120000' => $baseDir . '/../lib/Migration/Version1040Date20260703120000.php',
399401
'OCA\\DAV\\Model\\ExampleEvent' => $baseDir . '/../lib/Model/ExampleEvent.php',
400402
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php',
401403
'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class ComposerStaticInitDAV
7676
'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php',
7777
'OCA\\DAV\\CalDAV\\CalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarProvider.php',
7878
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
79+
'OCA\\DAV\\CalDAV\\DefaultCalendarAlarms' => __DIR__ . '/..' . '/../lib/CalDAV/DefaultCalendarAlarms.php',
7980
'OCA\\DAV\\CalDAV\\DefaultCalendarValidator' => __DIR__ . '/..' . '/../lib/CalDAV/DefaultCalendarValidator.php',
8081
'OCA\\DAV\\CalDAV\\EmbeddedCalDavServer' => __DIR__ . '/..' . '/../lib/CalDAV/EmbeddedCalDavServer.php',
8182
'OCA\\DAV\\CalDAV\\EventComparisonService' => __DIR__ . '/..' . '/../lib/CalDAV/EventComparisonService.php',
@@ -411,6 +412,7 @@ class ComposerStaticInitDAV
411412
'OCA\\DAV\\Migration\\Version1036Date20251202000000' => __DIR__ . '/..' . '/../lib/Migration/Version1036Date20251202000000.php',
412413
'OCA\\DAV\\Migration\\Version1038Date20260302000000' => __DIR__ . '/..' . '/../lib/Migration/Version1038Date20260302000000.php',
413414
'OCA\\DAV\\Migration\\Version1039Date20260408000000' => __DIR__ . '/..' . '/../lib/Migration/Version1039Date20260408000000.php',
415+
'OCA\\DAV\\Migration\\Version1040Date20260703120000' => __DIR__ . '/..' . '/../lib/Migration/Version1040Date20260703120000.php',
414416
'OCA\\DAV\\Model\\ExampleEvent' => __DIR__ . '/..' . '/../lib/Model/ExampleEvent.php',
415417
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php',
416418
'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php',

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
138138
public const CLASSIFICATION_PRIVATE = 1;
139139
public const CLASSIFICATION_CONFIDENTIAL = 2;
140140

141+
public const DEFAULT_ALARMS_PART_DAY_PROPERTY = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}default-alarms-part-day';
142+
public const DEFAULT_ALARMS_FULL_DAY_PROPERTY = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}default-alarms-full-day';
143+
144+
/** @var string[] DB columns for calendar properties outside propertyMap (custom serialization). */
145+
private const SERIALIZED_CALENDAR_DB_COLUMNS = [
146+
'default_alarms_pday',
147+
'default_alarms_fday',
148+
];
149+
141150
/**
142151
* List of CalDAV properties, and how they map to database field names and their type
143152
* Add your own properties by simply adding on to this array.
@@ -326,7 +335,7 @@ public function getCalendarsForUser($principalUri) {
326335
return $this->atomic(function () use ($principalUri) {
327336
$principalUriOriginal = $principalUri;
328337
$principalUri = $this->convertPrincipal($principalUri, true);
329-
$fields = array_column($this->propertyMap, 0);
338+
$fields = $this->getCalendarMergeSerializedProperties();
330339
$fields[] = 'id';
331340
$fields[] = 'uri';
332341
$fields[] = 'synctoken';
@@ -382,10 +391,9 @@ public function getCalendarsForUser($principalUri) {
382391
$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
383392
$principals[] = $principalUri;
384393

385-
$fields = array_column($this->propertyMap, 0);
386394
$fields = array_map(function (string $field) {
387395
return 'a.' . $field;
388-
}, $fields);
396+
}, $this->getCalendarMergeSerializedProperties());
389397
$fields[] = 'a.id';
390398
$fields[] = 'a.uri';
391399
$fields[] = 'a.synctoken';
@@ -468,7 +476,7 @@ public function getCalendarsForUser($principalUri) {
468476
*/
469477
public function getUsersOwnCalendars($principalUri) {
470478
$principalUri = $this->convertPrincipal($principalUri, true);
471-
$fields = array_column($this->propertyMap, 0);
479+
$fields = $this->getCalendarMergeSerializedProperties();
472480
$fields[] = 'id';
473481
$fields[] = 'uri';
474482
$fields[] = 'synctoken';
@@ -514,7 +522,7 @@ public function getUsersOwnCalendars($principalUri) {
514522
* @return array
515523
*/
516524
public function getPublicCalendars() {
517-
$fields = array_column($this->propertyMap, 0);
525+
$fields = array_map(static fn (string $field): string => 'a.' . $field, $this->getCalendarMergeSerializedProperties());
518526
$fields[] = 'a.id';
519527
$fields[] = 'a.uri';
520528
$fields[] = 'a.synctoken';
@@ -572,7 +580,7 @@ public function getPublicCalendars() {
572580
* @throws NotFound
573581
*/
574582
public function getPublicCalendar($uri) {
575-
$fields = array_column($this->propertyMap, 0);
583+
$fields = array_map(static fn (string $field): string => 'a.' . $field, $this->getCalendarMergeSerializedProperties());
576584
$fields[] = 'a.id';
577585
$fields[] = 'a.uri';
578586
$fields[] = 'a.synctoken';
@@ -631,7 +639,7 @@ public function getPublicCalendar($uri) {
631639
* @return array|null
632640
*/
633641
public function getCalendarByUri($principal, $uri) {
634-
$fields = array_column($this->propertyMap, 0);
642+
$fields = $this->getCalendarMergeSerializedProperties();
635643
$fields[] = 'id';
636644
$fields[] = 'uri';
637645
$fields[] = 'synctoken';
@@ -681,7 +689,7 @@ public function getCalendarByUri($principal, $uri) {
681689
* @return array|null
682690
*/
683691
public function getCalendarById(int $calendarId): ?array {
684-
$fields = array_column($this->propertyMap, 0);
692+
$fields = $this->getCalendarMergeSerializedProperties();
685693
$fields[] = 'id';
686694
$fields[] = 'uri';
687695
$fields[] = 'synctoken';
@@ -850,9 +858,29 @@ public function createCalendar($principalUri, $calendarUri, array $properties) {
850858
foreach ($this->propertyMap as $xmlName => [$dbName, $type]) {
851859
if (isset($properties[$xmlName])) {
852860
$values[$dbName] = $properties[$xmlName];
861+
if ($dbName === 'default_alarm_pday') {
862+
$values['default_alarms_pday'] = DefaultCalendarAlarms::encodeFromLegacyInt(
863+
$properties[$xmlName] !== null ? (int)$properties[$xmlName] : null,
864+
);
865+
} elseif ($dbName === 'default_alarm_fday') {
866+
$values['default_alarms_fday'] = DefaultCalendarAlarms::encodeFromLegacyInt(
867+
$properties[$xmlName] !== null ? (int)$properties[$xmlName] : null,
868+
);
869+
}
853870
}
854871
}
855872

873+
if (isset($properties[self::DEFAULT_ALARMS_PART_DAY_PROPERTY])) {
874+
$encoded = DefaultCalendarAlarms::validateAndEncode($properties[self::DEFAULT_ALARMS_PART_DAY_PROPERTY]);
875+
$values['default_alarms_pday'] = $encoded;
876+
$values['default_alarm_pday'] = DefaultCalendarAlarms::legacyIntFromJson($encoded);
877+
}
878+
if (isset($properties[self::DEFAULT_ALARMS_FULL_DAY_PROPERTY])) {
879+
$encoded = DefaultCalendarAlarms::validateAndEncode($properties[self::DEFAULT_ALARMS_FULL_DAY_PROPERTY]);
880+
$values['default_alarms_fday'] = $encoded;
881+
$values['default_alarm_fday'] = DefaultCalendarAlarms::legacyIntFromJson($encoded);
882+
}
883+
856884
[$calendarId, $calendarData] = $this->atomic(function () use ($values) {
857885
$query = $this->db->getQueryBuilder();
858886
$query->insert('calendars');
@@ -891,18 +919,42 @@ public function createCalendar($principalUri, $calendarUri, array $properties) {
891919
public function updateCalendar($calendarId, PropPatch $propPatch) {
892920
$supportedProperties = array_keys($this->propertyMap);
893921
$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
922+
$supportedProperties[] = self::DEFAULT_ALARMS_PART_DAY_PROPERTY;
923+
$supportedProperties[] = self::DEFAULT_ALARMS_FULL_DAY_PROPERTY;
894924

895925
$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
926+
$storedDefaultAlarmsJson = $this->getCalendarDefaultAlarmsJson($calendarId);
896927
$newValues = [];
897928
foreach ($mutations as $propertyName => $propertyValue) {
898929
switch ($propertyName) {
899930
case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
900931
$fieldName = 'transparent';
901932
$newValues[$fieldName] = (int)($propertyValue->getValue() === 'transparent');
902933
break;
934+
case self::DEFAULT_ALARMS_PART_DAY_PROPERTY:
935+
$encoded = DefaultCalendarAlarms::validateAndEncode($propertyValue);
936+
$newValues['default_alarms_pday'] = $encoded;
937+
$newValues['default_alarm_pday'] = DefaultCalendarAlarms::legacyIntFromJson($encoded);
938+
break;
939+
case self::DEFAULT_ALARMS_FULL_DAY_PROPERTY:
940+
$encoded = DefaultCalendarAlarms::validateAndEncode($propertyValue);
941+
$newValues['default_alarms_fday'] = $encoded;
942+
$newValues['default_alarm_fday'] = DefaultCalendarAlarms::legacyIntFromJson($encoded);
943+
break;
903944
default:
904945
$fieldName = $this->propertyMap[$propertyName][0];
905946
$newValues[$fieldName] = $propertyValue;
947+
if ($fieldName === 'default_alarm_pday') {
948+
$newValues['default_alarms_pday'] = DefaultCalendarAlarms::mergeLegacyIntIntoJson(
949+
$storedDefaultAlarmsJson['default_alarms_pday'],
950+
$propertyValue !== null ? (int)$propertyValue : null,
951+
);
952+
} elseif ($fieldName === 'default_alarm_fday') {
953+
$newValues['default_alarms_fday'] = DefaultCalendarAlarms::mergeLegacyIntIntoJson(
954+
$storedDefaultAlarmsJson['default_alarms_fday'],
955+
$propertyValue !== null ? (int)$propertyValue : null,
956+
);
957+
}
906958
break;
907959
}
908960
}
@@ -3968,9 +4020,56 @@ private function rowToCalendar($row, array $calendar): array {
39684020
}
39694021
$calendar[$xmlName] = $value;
39704022
}
4023+
4024+
$calendar[self::DEFAULT_ALARMS_PART_DAY_PROPERTY] = DefaultCalendarAlarms::formatForCalDav(
4025+
isset($row['default_alarms_pday']) ? (string)$row['default_alarms_pday'] : null,
4026+
isset($row['default_alarm_pday']) && $row['default_alarm_pday'] !== null ? (int)$row['default_alarm_pday'] : null,
4027+
);
4028+
$calendar[self::DEFAULT_ALARMS_FULL_DAY_PROPERTY] = DefaultCalendarAlarms::formatForCalDav(
4029+
isset($row['default_alarms_fday']) ? (string)$row['default_alarms_fday'] : null,
4030+
isset($row['default_alarm_fday']) && $row['default_alarm_fday'] !== null ? (int)$row['default_alarm_fday'] : null,
4031+
);
4032+
39714033
return $calendar;
39724034
}
39734035

4036+
/**
4037+
* calendars-table SELECT columns: propertyMap DB fields plus columns for
4038+
* CalDAV properties stored outside propertyMap (custom serialization).
4039+
*
4040+
* @return string[]
4041+
*/
4042+
private function getCalendarMergeSerializedProperties(): array {
4043+
return array_merge(
4044+
array_column($this->propertyMap, 0),
4045+
self::SERIALIZED_CALENDAR_DB_COLUMNS,
4046+
);
4047+
}
4048+
4049+
/**
4050+
* @return array{default_alarms_pday: ?string, default_alarms_fday: ?string}
4051+
*/
4052+
private function getCalendarDefaultAlarmsJson(int $calendarId): array {
4053+
$query = $this->db->getQueryBuilder();
4054+
$query->select('default_alarms_pday', 'default_alarms_fday')
4055+
->from('calendars')
4056+
->where($query->expr()->eq('id', $query->createNamedParameter($calendarId, IQueryBuilder::PARAM_INT)))
4057+
->setMaxResults(1);
4058+
$row = $query->executeQuery()->fetchAssociative();
4059+
4060+
if ($row === false) {
4061+
return [
4062+
'default_alarms_pday' => null,
4063+
'default_alarms_fday' => null,
4064+
];
4065+
}
4066+
4067+
return [
4068+
'default_alarms_pday' => $row['default_alarms_pday'] !== null ? (string)$row['default_alarms_pday'] : null,
4069+
'default_alarms_fday' => $row['default_alarms_fday'] !== null ? (string)$row['default_alarms_fday'] : null,
4070+
];
4071+
}
4072+
39744073
/**
39754074
* Amend the subscription info with database row data
39764075
*

0 commit comments

Comments
 (0)