Skip to content

fix(calendar): RECURRENCE-ID exception handling + sync failure notifications#32

Open
icciaaron wants to merge 1 commit intoFreePBX:release/17.0from
icciaaron:fix/recurrence-id-exception-handling
Open

fix(calendar): RECURRENCE-ID exception handling + sync failure notifications#32
icciaaron wants to merge 1 commit intoFreePBX:release/17.0from
icciaaron:fix/recurrence-id-exception-handling

Conversation

@icciaaron
Copy link
Copy Markdown

@icciaaron icciaaron commented Apr 16, 2026

Summary

RECURRENCE-ID exception events (RFC 5545 §3.8.4.4) — used when a human moves or cancels a single occurrence of a recurring calendar event — were previously dropped silently. The original developer commented out the handling code and replaced it with a warning notification, causing:

  • Modified occurrences (e.g. moved meeting) to disappear entirely
  • Cancelled occurrences to remain active (original time still matched)
  • A DB write per exception per sync cycle (notification spam)
  • Cascading failures in time conditions and call routing

RECURRENCE-ID fix (IcalRangedParser.php)

Three code paths fixed:

  1. parseRecurrences() fast path: Filter RECURRENCE-ID replaced timestamps from the recurrence array (matching what the non-fast path already does)
  2. getEvents(): Include non-cancelled replacement VEVENTs as regular events with standard range checking. Drop STATUS:CANCELLED exceptions
  3. getEventsNow(): Same fix for fast-mode AGI call routing

The fix leverages the existing _RECURRENCE_IDS lookup table that om/icalparser already populates during parseString(). The original occurrence is already removed from the parent series' recurrence list by the RECURRENCE-ID filter in parseRecurrences(). The replacement VEVENT simply needs to not be dropped.

Sync failure notifications (Calendar.class.php + Base.php)

  • sync() now wraps processCalendar() in try/catch — one broken calendar no longer kills sync for all calendars
  • Sync failures produce a critical admin notification (visible in the notification tray and email if configured)
  • Successful sync clears any previous failure notification for that calendar
  • buildCache() parse failures also produce a critical notification

Known limitation

RECURRENCE-ID;RANGE=THISANDFUTURE is not supported by the upstream om/icalparser library and remains unhandled. This affects <1% of real-world usage — Google Calendar, Outlook, and iCloud all use series-splitting instead.

Test plan

  • Unit tests for timed RECURRENCE-ID events (moved, cancelled, unmodified)
  • Unit tests for all-day VALUE=DATE RECURRENCE-ID events
  • Unit tests for fast-mode getEventsNow() matching (4 scenarios)
  • Independent code review (security, correctness, edge cases)
  • Manual test on PBXact 17 with Google Calendar containing exceptions
  • Verify admin notification appears when calendar sync fails

…ilure notifications

RECURRENCE-ID events (RFC 5545 §3.8.4.4) — used when a human moves or
cancels a single occurrence of a recurring calendar event — were previously
dropped silently. The original developer commented out the handling code
and replaced it with a warning notification, causing:

- Modified occurrences (e.g. moved meeting) to disappear entirely
- Cancelled occurrences to remain active (original time still matched)
- A DB write per exception per sync cycle (notification spam)
- Cascading failures in time conditions and call routing

This commit fixes three code paths in IcalRangedParser:

1. parseRecurrences() fast path: filter out RECURRENCE-ID replaced
   timestamps (matching what the non-fast path already did)
2. getEvents(): include non-cancelled replacement VEVENTs as regular
   single-occurrence events with standard range checking
3. getEventsNow(): same treatment for fast-mode "is it now?" matching

The fix leverages the existing _RECURRENCE_IDS lookup table populated
by the upstream om/icalparser during parseString(). The original
occurrence is already removed from the parent series' recurrence list
by the RECURRENCE-ID filter in parseRecurrences() (lines 263-281).
The replacement VEVENT simply needs to not be dropped.

Additionally adds sync failure notifications:

- Calendar.class.php: try/catch around processCalendar() in sync() so
  one broken calendar no longer kills sync for all calendars. Failures
  produce a critical admin notification; success clears it.
- Base.php: try/catch around iCal parsing in buildCache() with a
  critical notification on parse failure.

Known limitation: RECURRENCE-ID with RANGE=THISANDFUTURE is not
supported by the upstream om/icalparser library and remains unhandled.
This covers <1% of real-world calendar usage (Google Calendar, Outlook,
and iCloud use series-splitting instead of THISANDFUTURE).

Includes unit tests for timed events, all-day (VALUE=DATE) events,
and fast-mode matching with moved/cancelled/unmodified occurrences.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@icciaaron
Copy link
Copy Markdown
Author

Hi @Akarsh04 @prasanthcode4 — could one of you take a look when you have a moment? This fixes a real-world sync issue we hit in production with Google Calendar exceptions (modified instances of recurring events being dropped). CLA is signed and the change is scoped. Happy to split or rework anything that doesn't fit your conventions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant