Skip to content

ICS export option - #732

Merged
Akhill2020 merged 16 commits into
mainfrom
Export-ICS-feed
Aug 3, 2026
Merged

Akhill2020 merged 16 commits into
mainfrom
Export-ICS-feed

Conversation

@Akhill2020

@Akhill2020 Akhill2020 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Description: I have added ICS Export button similar to print calendar button user can check this from appearance and button will display below the calendar in grid/list view by pressing button user can download the ICS file. Also fix the print calendar issue when we have multiple calendar on same page.

Clickup: https://app.clickup.com/t/1867958/86d3e1phg
https://app.clickup.com/t/1867958/86d3uxq5y
After: https://drive.google.com/file/d/1ZNIDXfrQaRbe_DZN-E_2Lsi454A3Nisc/view?usp=drivesdk

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Added an ICS Export option in calendar admin, enabling Download .ics actions on the frontend.
    • Added frontend Calendar actions with optional ICS URL for Pro, including copy-to-clipboard behavior and extensible action buttons.
  • Bug Fixes
    • Improved Print button rendering and ensured the action bar is hidden during printing.
    • Enhanced recurrence handling for expanded Google events to preserve RRULEs.
    • Improved cache invalidation for Pro ICS subscription feed data when deleting calendars.
  • Documentation
    • Updated the changelog for 4.1.0 with ICS Export and frontend extension hook details.
  • Style
    • Refreshed styling for the shared calendar actions bar.

@Akhill2020
Akhill2020 requested a review from rosinghal July 16, 2026 09:13
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds configurable frontend ICS export, generates .ics downloads from calendar events, centralizes calendar actions, adds print and URL interactions, preserves recurrence rules, invalidates related feed caches, and documents the 4.1.0 feature.

Changes

ICS Export

Layer / File(s) Summary
Export configuration and plugin wiring
includes/admin/metaboxes/settings.php, includes/main.php, readme.txt
Adds and persists ICS export settings, registers the export handler, updates print-label text, and documents the export options.
ICS request and event generation
includes/ics-export.php
Validates requests, builds VCALENDAR/VEVENT content, escapes and folds RFC 5545 lines, and returns downloadable files.
Shared calendar action controls
includes/ics-export.php, includes/calendars/views/default-calendar-*.php, assets/css/default-calendar-grid.css, assets/js/default-calendar.js, assets/css/admin.css
Centralizes action rendering, styles and hides the action bar for print, updates print-button binding, and adds clipboard copying with temporary feedback.
Recurrence and ICS feed support
includes/feeds/google.php, includes/functions/shared.php
Preserves recurrence rules for expanded Google events and fires ICS feed cache-clearing actions for bulk and single-calendar transient deletion.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant CalendarView
  participant Ics_Export
  participant Calendar
  Visitor->>CalendarView: Select ICS export
  CalendarView->>Ics_Export: Request export URL
  Ics_Export->>Calendar: Validate setting and load events
  Calendar-->>Ics_Export: Calendar event groups
  Ics_Export-->>Visitor: Download .ics file
Loading

Suggested reviewers: rosinghal

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding an ICS export option for calendars.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Export-ICS-feed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@includes/ics-export.php`:
- Around line 275-279: Update the line-folding loop to use mb_strcut() instead
of substr() when extracting each 75-byte segment, preserving the existing
CRLF-and-space folding format and remaining-line handling while ensuring
multibyte characters are never split.
- Around line 195-200: Update the event end-date calculation in the
`$event->end_dt` branch to normalize `end_dt` with `startOfDay()->addDay()`
before formatting it as `Ymd`. Remove the brittle `addSeconds(59)` logic while
preserving the existing fallback calculation for events without an end date.
- Around line 51-54: Update the calendar validation around
get_post($calendar_id) to allow exports only when the calendar is published or
the current user is authorized to access its private or draft status. Preserve
the existing invalid-post and wrong-post-type rejection, and terminate
unauthorized requests with the established 404 response without exposing
calendar events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 186f69c9-9df8-46e3-82e8-c50d967d3f61

📥 Commits

Reviewing files that changed from the base of the PR and between 6756744 and 2d65ff9.

📒 Files selected for processing (7)
  • assets/css/default-calendar-grid.css
  • includes/admin/metaboxes/settings.php
  • includes/calendars/views/default-calendar-grid.php
  • includes/calendars/views/default-calendar-list.php
  • includes/ics-export.php
  • includes/main.php
  • readme.txt

Comment thread includes/ics-export.php
Comment thread includes/ics-export.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
assets/js/default-calendar.js (1)

434-439: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make Copied! translatableassets/js/default-calendar.js hardcodes the copy feedback label; pass it through the localized script strings so non-English sites can translate it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/js/default-calendar.js` around lines 434 - 439, Update showCopied in
the default calendar script to obtain the “Copied!” feedback text from the
existing localized script strings instead of hardcoding it, while preserving the
timeout that restores originalLabel. Add or reuse the appropriate localization
key and ensure the localized value is displayed through $label.text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/css/default-calendar-grid.css`:
- Around line 610-617: Update the stroke declaration in .simcal-calendar-actions
.simcal-calendar-action-icon to use the lowercase currentcolor keyword required
by stylelint, leaving the surrounding icon styles unchanged.
- Around line 558-608: Update the shared focus styling for
.simcal-calendar-actions links and buttons to provide a clearly visible keyboard
focus indicator, replacing the outline: none !important rule with an accessible
focus treatment while preserving the existing hover appearance and other button
styling.

In `@assets/js/default-calendar.js`:
- Around line 454-459: Update copyWithTextarea so the temporary textarea is
visually hidden/offscreen before appending it, preventing flashes and scroll
jumps, then explicitly focus the created textarea before calling select().
Preserve the existing execCommand('copy') and cleanup behavior.

---

Nitpick comments:
In `@assets/js/default-calendar.js`:
- Around line 434-439: Update showCopied in the default calendar script to
obtain the “Copied!” feedback text from the existing localized script strings
instead of hardcoding it, while preserving the timeout that restores
originalLabel. Add or reuse the appropriate localization key and ensure the
localized value is displayed through $label.text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 05ee2bed-8274-4252-b072-4b39e16757ea

📥 Commits

Reviewing files that changed from the base of the PR and between 2d65ff9 and cee5146.

📒 Files selected for processing (5)
  • assets/css/default-calendar-grid.css
  • assets/js/default-calendar.js
  • includes/calendars/views/default-calendar-grid.php
  • includes/calendars/views/default-calendar-list.php
  • includes/ics-export.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • includes/calendars/views/default-calendar-grid.php

Comment thread assets/css/default-calendar-grid.css
Comment thread assets/css/default-calendar-grid.css
Comment thread assets/js/default-calendar.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
includes/ics-export.php (3)

269-273: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not collapse ID-less events that share a start time.

When both uid and ical_id are empty, the deduplication key becomes -{$event->start}. Two distinct events at the same start time are therefore silently omitted, even though build_vevent() has a fallback identity based on title/start/end at Lines 300-303. Use the same fallback or another stable per-event identifier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/ics-export.php` around lines 269 - 273, Update the deduplication key
logic near $dedupe_key so events without both uid and ical_id use the same
stable fallback identity as build_vevent(), including title, start, and end,
instead of relying only on the shared start time. Preserve existing uid and
ical_id-based deduplication for identified events.

322-326: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not emit DTEND equal to DTSTART.

When end_utc/end is missing, this path emits an explicit zero-length VEVENT, which is invalid. Omit DTEND for an event with no valid end, or derive a positive duration from the event model.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/ics-export.php` around lines 322 - 326, Update the event export
logic around $end_utc so missing or invalid end values do not fall back to
$start_utc and emit a zero-length DTEND. Omit the DTEND line when no valid end
exists, or derive and emit a strictly later end time from the event model;
preserve DTSTART generation.

Source: MCP tools


347-348: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use URI-specific escaping for the URL property.

URL takes a URI value, and RFC 5545 does not apply the TEXT backslash escaping rules to URI values. escape_text() rewrites commas, semicolons, and backslashes inside the URL, so links like https://example.com/a,b?x=1 are exported as https://example.com/a\,b?x=1. Sanitize the link without adding backslash escapes before using it in URL:.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/ics-export.php` around lines 347 - 348, Update the URL export in the
event serialization block to use URI-specific sanitization instead of
escape_text(), preserving commas, semicolons, and backslashes as valid URI
content. Keep the existing empty-link guard and URL: prefix unchanged.

Source: MCP tools

🧹 Nitpick comments (1)
includes/ics-export.php (1)

275-275: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid repeatedly copying the accumulated ICS payload.

array_merge($lines, ...) copies the growing $lines array for every event, which becomes quadratic for large calendars. Append each VEVENT line directly instead.

Proposed refactor
-					$lines = array_merge($lines, $this->build_vevent($event));
+					foreach ($this->build_vevent($event) as $vevent_line) {
+						$lines[] = $vevent_line;
+					}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/ics-export.php` at line 275, Update the event loop around
build_vevent to append each generated VEVENT line directly to the existing
$lines array instead of repeatedly calling array_merge, preserving the same
output order and contents without copying the accumulated payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@includes/ics-export.php`:
- Around line 269-273: Update the deduplication key logic near $dedupe_key so
events without both uid and ical_id use the same stable fallback identity as
build_vevent(), including title, start, and end, instead of relying only on the
shared start time. Preserve existing uid and ical_id-based deduplication for
identified events.
- Around line 322-326: Update the event export logic around $end_utc so missing
or invalid end values do not fall back to $start_utc and emit a zero-length
DTEND. Omit the DTEND line when no valid end exists, or derive and emit a
strictly later end time from the event model; preserve DTSTART generation.
- Around line 347-348: Update the URL export in the event serialization block to
use URI-specific sanitization instead of escape_text(), preserving commas,
semicolons, and backslashes as valid URI content. Keep the existing empty-link
guard and URL: prefix unchanged.

---

Nitpick comments:
In `@includes/ics-export.php`:
- Line 275: Update the event loop around build_vevent to append each generated
VEVENT line directly to the existing $lines array instead of repeatedly calling
array_merge, preserving the same output order and contents without copying the
accumulated payload.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e4466c2-0be5-4887-8afb-f7d988e46e56

📥 Commits

Reviewing files that changed from the base of the PR and between cee5146 and 38c0013.

📒 Files selected for processing (3)
  • includes/functions/shared.php
  • includes/ics-export.php
  • readme.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • readme.txt

@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
includes/ics-export.php (1)

246-295: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Anchor recurring master VEVENTs to the original series start.

When repeated with expanded recurring events, Google returns occurrences ordered by startTime in the requested time window, and repeated queries may not return the first series occurrence. build_ics() currently emits one RRULE per series using whichever event is encountered first, while fetch_recurrence_map() only stores rule lines without the master’s getStart(). For interval-based or finite COUNT rules, setting DTSTART on a later occurrence can export a recurrence set that does not match the original series. Persist the master:start time in the recurrence mapping and use it to construct recurring VEVENTs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/ics-export.php` around lines 246 - 295, Update
fetch_recurrence_map() to persist each recurring series’ original master start
time alongside its rule lines, rather than storing only the RRULE. In
build_ics(), resolve that stored master start for each series and use it when
constructing the recurring VEVENT through build_vevent(), while preserving the
existing one-master-per-series deduplication.
🧹 Nitpick comments (1)
includes/feeds/google.php (1)

633-654: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider skipping fetch_recurrence_map() when no expanded instance actually needs it.

The extra master-events request runs on every cache refresh whenever singleEvents is set, even for calendars with zero recurring events. A quick scan of $response->getItems() for any item with getRecurringEventId() set and empty getRecurrence() before calling fetch_recurrence_map() would avoid the unnecessary API round-trip (and quota usage) in the common case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@includes/feeds/google.php` around lines 633 - 654, Before invoking
fetch_recurrence_map() in the singleEvents branch, scan $response->getItems()
for at least one expanded instance with a recurring event ID and no recurrence
data. Only populate recurrence_map when such an item exists; preserve the
existing behavior and return structure otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@includes/ics-export.php`:
- Around line 59-62: Update the guard around get_post($calendar_id) to first
verify that $post exists and is the expected calendar post type, returning the
existing not-found 404 response for invalid or missing calendars; only
dereference post_status and apply the read_post permission check after that
validation.
- Around line 274-291: Update the recurring-event deduplication block around
get_event_rrule() so events missing both ical_id and uid fall back to the same
UID strategy used by build_vevent(), rather than being skipped when series_key
is empty. Preserve duplicate suppression for valid series keys and ensure
recurring events without either identifier are still exported.

---

Outside diff comments:
In `@includes/ics-export.php`:
- Around line 246-295: Update fetch_recurrence_map() to persist each recurring
series’ original master start time alongside its rule lines, rather than storing
only the RRULE. In build_ics(), resolve that stored master start for each series
and use it when constructing the recurring VEVENT through build_vevent(), while
preserving the existing one-master-per-series deduplication.

---

Nitpick comments:
In `@includes/feeds/google.php`:
- Around line 633-654: Before invoking fetch_recurrence_map() in the
singleEvents branch, scan $response->getItems() for at least one expanded
instance with a recurring event ID and no recurrence data. Only populate
recurrence_map when such an item exists; preserve the existing behavior and
return structure otherwise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 147ca4b3-5a99-4153-94b9-508ca593dc59

📥 Commits

Reviewing files that changed from the base of the PR and between d56f5fb and 1166a8b.

📒 Files selected for processing (6)
  • assets/css/admin.css
  • assets/css/default-calendar-grid.css
  • assets/js/default-calendar.js
  • includes/admin/metaboxes/settings.php
  • includes/feeds/google.php
  • includes/ics-export.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • assets/js/default-calendar.js
  • includes/admin/metaboxes/settings.php
  • assets/css/default-calendar-grid.css

Comment thread includes/ics-export.php
Comment thread includes/ics-export.php
Comment thread includes/admin/metaboxes/settings.php
Comment thread readme.txt Outdated
Comment thread readme.txt Outdated
@Akhill2020
Akhill2020 merged commit 4519203 into main Aug 3, 2026
3 checks passed
@Akhill2020
Akhill2020 deleted the Export-ICS-feed branch August 3, 2026 11:23
@coderabbitai coderabbitai Bot mentioned this pull request Aug 12, 2026
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.

2 participants