ICS export option#732
Conversation
📝 WalkthroughWalkthroughAdds a configurable frontend ICS export option that generates ChangesICS Export
Estimated code review effort: 3 (Moderate) | ~20 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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 PHPStan (2.2.5)PHP Warning: require(/vendor/composer/../guzzlehttp/promises/src/functions_include.php): Failed to open stream: No such file or directory in /vendor/composer/autoload_real.php on line 39 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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
assets/css/default-calendar-grid.cssincludes/admin/metaboxes/settings.phpincludes/calendars/views/default-calendar-grid.phpincludes/calendars/views/default-calendar-list.phpincludes/ics-export.phpincludes/main.phpreadme.txt
| $post = get_post($calendar_id); | ||
| if (!$post || 'calendar' !== $post->post_type) { | ||
| wp_die(esc_html__('Calendar not found.', 'google-calendar-events'), 404); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Add permission check to prevent unauthorized exports of private calendars.
get_post() retrieves the calendar post without verifying if it is published or if the current user has access to it. This allows unauthorized users to export events from draft or private calendars by simply guessing the calendar ID.
🔒 Proposed fix to add permission checks
$post = get_post($calendar_id);
if (!$post || 'calendar' !== $post->post_type) {
wp_die(esc_html__('Calendar not found.', 'google-calendar-events'), 404);
}
+
+ if ($post->post_status !== 'publish' && !current_user_can('read_post', $calendar_id)) {
+ wp_die(esc_html__('You do not have permission to view this calendar.', 'google-calendar-events'), 403);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $post = get_post($calendar_id); | |
| if (!$post || 'calendar' !== $post->post_type) { | |
| wp_die(esc_html__('Calendar not found.', 'google-calendar-events'), 404); | |
| } | |
| $post = get_post($calendar_id); | |
| if (!$post || 'calendar' !== $post->post_type) { | |
| wp_die(esc_html__('Calendar not found.', 'google-calendar-events'), 404); | |
| } | |
| if ($post->post_status !== 'publish' && !current_user_can('read_post', $calendar_id)) { | |
| wp_die(esc_html__('You do not have permission to view this calendar.', 'google-calendar-events'), 403); | |
| } |
🤖 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 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.
| if ($event->end_dt) { | ||
| // Stored all-day end is last inclusive moment; ICS DTEND is exclusive next date. | ||
| $end_date = $event->end_dt->copy()->addSeconds(59)->format('Ymd'); | ||
| } else { | ||
| $end_date = $start_dt->copy()->startOfDay()->addDay()->format('Ymd'); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use startOfDay()->addDay() to safely calculate the exclusive end date.
Adding 59 seconds to the inclusive end time is brittle. If the time stored in end_dt is not exactly at the end of the minute (e.g., 23:59:00 instead of 23:59:59), adding 59 seconds will not cross over to the next calendar day (23:59:59 is still the same day). This truncates the all-day event in the ICS output by one day.
🐛 Proposed fix for robust date math
if ($event->end_dt) {
// Stored all-day end is last inclusive moment; ICS DTEND is exclusive next date.
- $end_date = $event->end_dt->copy()->addSeconds(59)->format('Ymd');
+ $end_date = $event->end_dt->copy()->startOfDay()->addDay()->format('Ymd');
} else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ($event->end_dt) { | |
| // Stored all-day end is last inclusive moment; ICS DTEND is exclusive next date. | |
| $end_date = $event->end_dt->copy()->addSeconds(59)->format('Ymd'); | |
| } else { | |
| $end_date = $start_dt->copy()->startOfDay()->addDay()->format('Ymd'); | |
| } | |
| if ($event->end_dt) { | |
| // Stored all-day end is last inclusive moment; ICS DTEND is exclusive next date. | |
| $end_date = $event->end_dt->copy()->startOfDay()->addDay()->format('Ymd'); | |
| } else { | |
| $end_date = $start_dt->copy()->startOfDay()->addDay()->format('Ymd'); | |
| } |
🤖 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 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.
| $folded = ''; | ||
| while (strlen($line) > 75) { | ||
| $folded .= substr($line, 0, 75) . "\r\n "; | ||
| $line = substr($line, 75); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use mb_strcut() to fold lines without breaking multibyte characters.
substr() splits the string rigidly by bytes. If a multibyte UTF-8 character (e.g., an emoji or foreign character) crosses the 75-byte boundary, it gets sliced in half. This produces invalid UTF-8 byte sequences that will break parsing in some calendar clients. mb_strcut() correctly slices strings up to a specific byte length without severing multibyte characters.
🐛 Proposed fix for multibyte line folding
$folded = '';
while (strlen($line) > 75) {
- $folded .= substr($line, 0, 75) . "\r\n ";
- $line = substr($line, 75);
+ if (function_exists('mb_strcut')) {
+ $chunk = mb_strcut($line, 0, 75, 'UTF-8');
+ } else {
+ $chunk = substr($line, 0, 75);
+ }
+ $folded .= $chunk . "\r\n ";
+ $line = substr($line, strlen($chunk));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $folded = ''; | |
| while (strlen($line) > 75) { | |
| $folded .= substr($line, 0, 75) . "\r\n "; | |
| $line = substr($line, 75); | |
| } | |
| $folded = ''; | |
| while (strlen($line) > 75) { | |
| if (function_exists('mb_strcut')) { | |
| $chunk = mb_strcut($line, 0, 75, 'UTF-8'); | |
| } else { | |
| $chunk = substr($line, 0, 75); | |
| } | |
| $folded .= $chunk . "\r\n "; | |
| $line = substr($line, strlen($chunk)); | |
| } |
🤖 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 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.
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.
Clickup: https://app.clickup.com/t/1867958/86d3e1phg
After:
Summary by CodeRabbit
New Features
.icsfiles for use in calendar applications.Documentation