Feat/valarm suppression shared calendars#8006
Feat/valarm suppression shared calendars#8006howudodat wants to merge 4 commits intonextcloud:mainfrom
Conversation
Add migration, entity, and mapper for the calendar_share_alarms table. This stores per-share preferences for whether VALARM components should be stripped from CalDAV responses for shared calendars. Ref: nextcloud#7498
Register a SabreDAV plugin via SabrePluginAddEvent that intercepts CalDAV REPORT and GET responses. When alarm suppression is enabled for a share, VALARM components are stripped from the ICS data before it reaches the sharee's client. Hooks into propFind (priority 600) for REPORT responses and afterMethod:GET for direct .ics fetches. Uses an in-memory cache to avoid repeated DB queries within a single request. Ref: nextcloud#7498
Add ShareAlarmController with GET and POST endpoints at /v1/share-alarm for reading and toggling per-share alarm suppression. Resolves calendar DAV URLs to internal IDs via CalDavBackend and verifies calendar ownership. Ref: nextcloud#7498
Add "suppress alarms" checkbox to ShareItem in the EditCalendarModal. The owner can toggle alarm suppression per sharee. Settings are loaded when the modal opens and persisted via the share-alarm API. Also fixes a pre-existing Vue 3 migration bug where the isWriteable watcher fired on mount and toggled permissions unintentionally. Both checkboxes now use @update:modelValue instead of @update:checked. Ref: nextcloud#7498
|
TL;DR: doing things properly is much harder This is not a bad idea, but:
|
|
Hi, I would agree with @tcitworld. This should be fixed in the dav app which is the calendaring backend, but this will require some thought on implementation. |
|
Hello there, We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process. Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6 Thank you for contributing to Nextcloud and we hope to hear from you soon! (If you believe you should not receive this message, you can add yourself to the blocklist.) |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Owner-Controlled VALARM Suppression for Shared Calendars
Problem
When a Nextcloud calendar is shared with read-write access, VALARM (alarm/reminder) components from the owner's events are transmitted to the sharee via CalDAV. Android calendar apps synced via DAVx5 have no per-calendar notification suppression, causing unwanted alerts on the sharee's device.
Existing behavior: Nextcloud server's
CalendarObject::get()(inapps/dav) already strips VALARM for read-only shared calendars. For read-write shares, VALARM is preserved.Reference: Issue #7498
Solution
Register a SabreDAV plugin from the calendar app via the
SabrePluginAddEventmechanism (NC 28+; this app targets NC 32+). The plugin intercepts CalDAV responses and strips VALARM when the owner has enabled suppression for that share. A new database table stores the per-share preference, and the sharing UI exposes the toggle.The owner controls the setting: in the calendar edit/share modal, each sharee row has a "suppress alarms" checkbox alongside the existing "can edit" checkbox.
Plan
Architecture
Data Flow
POST /v1/share-alarm→ upserts record incalendar_share_alarmsStripAlarmsPluginintercepts, checks DB (cached), strips VALARM from ICS dataFiles Created
lib/Migration/Version5050Date20250701000005.phpcalendar_share_alarmstable withcalendar_id,principal_uri,suppress_alarmslib/Db/ShareAlarmSetting.phplib/Db/ShareAlarmSettingMapper.phpisSuppressed(),findAllByCalendarId(), and cleanup methodslib/Dav/StripAlarmsPlugin.phppropFind(priority 600) +afterMethod:GEThooks, in-memory cache, VALARM stripping viaSabre\VObject\Readerlib/Listener/SabrePluginAddListener.phpStripAlarmsPluginviaSabrePluginAddEventlib/Controller/ShareAlarmController.phpGET /v1/share-alarmandPOST /v1/share-alarm, ownership verification, calendar URL→ID resolution viaCalDavBackendsrc/services/shareAlarmService.js@nextcloud/axiosFiles Modified
lib/AppInfo/Application.phpSabrePluginAddListenerforSabrePluginAddEventappinfo/routes.phpGETandPOST /v1/share-alarmroutessrc/models/calendarShare.jssuppressAlarms: falseto default share objectsrc/store/calendars.jsloadShareAlarmSettingsandtoggleShareAlarmSuppressionactionssrc/components/AppNavigation/EditCalendarModal/ShareItem.vueNcCheckboxRadioSwitch, watcher, andupdateAlarmSuppression()methodsrc/components/AppNavigation/EditCalendarModal.vueDatabase Schema
API Endpoints
GET /apps/calendar/v1/share-alarm?calendarUrl=...Returns suppression state for all shares of a calendar.
{ "status": "success", "data": { "principals/users/alice": true, "principals/users/bob": false } }POST /apps/calendar/v1/share-alarmToggles suppression for one share.
{ "calendarUrl": "/remote.php/dav/calendars/owner/calname/", "principalUri": "principals/users/alice", "suppressAlarms": true }SabreDAV Plugin Details
StripAlarmsPluginhooks into two interception points:propFind(priority 600) — For REPORT requests (calendar-multiget, calendar-query). Runs after the CalDAV plugin (priority 150-550) has populated{urn:ietf:params:xml:ns:caldav}calendar-data. CallspropFind->get()to read the ICS, strips VALARM, callspropFind->set()to replace.afterMethod:GET— For direct GET on.icsfiles. Readsresponse->getBodyAsString(), strips VALARM, callsresponse->setBody().VALARM stripping follows the same pattern as the server's
CalendarObject::removeVAlarms():Performance: An in-memory
$suppressionCachearray (keyed bycalendarId:principalUri) avoids repeated DB lookups for objects in the same calendar during a single REPORT response.CalendarInfo access: Uses reflection as fallback to access the protected
calendarInfoproperty on Sabre'sCalendarObject, with a last-resort parent node lookup via the server tree.Known Caveats
calendarInfoaccess: The plugin uses reflection to access the protectedcalendarInfoproperty onCalendarObject. This should be tested against the actual Nextcloud server version. IfgetCalendarInfo()becomes public in a future NC version, the reflection fallback becomes unnecessary.Calendar ID resolution: The controller resolves the calendar URL to an internal ID via
CalDavBackend::getCalendarsForUser(). This adds a dependency on the DAV app's backend class (OCA\DAV\CalDAV\CalDavBackend).Principal URI format mismatch: Frontend uses
principal:principals/users/alice(cdav-library format), backend usesprincipals/users/alice. The store actions strip theprincipal:prefix before API calls.propFind->set()after lazy eval: ThePropFind::set()behavior afterget()triggers lazy evaluation needs verification against the SabreDAV version bundled with NC 32+.Verification
Unit Tests
ShareAlarmSettingMapper: CRUD operations,isSuppressed()returns false for missing recordsStripAlarmsPlugin: MockCalendarObjectnode withcalendarInfo, verify VALARM stripping when enabled and no-op when disabledManual End-to-End