Skip to content

Instantly share code, notes, and snippets.

@Jany-M
Forked from jakebellacera/ICS.php
Last active September 19, 2023 03:15
Show Gist options
  • Save Jany-M/af50d5c4a0eec2692734d76383ed4dd8 to your computer and use it in GitHub Desktop.
Save Jany-M/af50d5c4a0eec2692734d76383ed4dd8 to your computer and use it in GitHub Desktop.
[WP] Generate a downloadable .ics file from any WordPress post or custom post type
<?php
/*
For a better understanding of ics requirements and time formats
please check https://gist.github.com/jakebellacera/635416
*/
// UTILS
// Check if string is a timestamp
function isValidTimeStamp($timestamp) {
//if($timestamp == '') return;
return ((string) (int) $timestamp === $timestamp)
&& ($timestamp <= PHP_INT_MAX)
&& ($timestamp >= ~PHP_INT_MAX);
}
// Escapes a string of characters
function escapeString($string) {
return preg_replace('/([\,;])/','\\\$1', $string);
}
// Shorten a string to desidered characters lenght - eg. shorter_version($string, 100);
function shorter_version($string, $lenght) {
if (strlen($string) >= $lenght) {
return substr($string, 0, $lenght);
} else {
return $string;
}
}
// Add a custom endpoint "calendar"
function add_calendar_feed(){
add_feed('calendar', 'export_ics');
// Only uncomment these 2 lines the first time you load this script, to update WP rewrite rules, or in case you see a 404
/*global $wp_rewrite;
$wp_rewrite->flush_rules( false );*/
}
add_action('init', 'add_calendar_feed');
// Calendar function
function export_ics(){
// Query the event
$the_event = new WP_Query(array(
'p' => $_REQUEST['id'],
'post_type' => 'any',
));
if($the_event->have_posts()) :
while($the_event->have_posts()) : $the_event->the_post();
// If your version of WP < 5.3.0 use the code below
/* The correct date format, for ALL dates is date_i18n('Ymd\THis\Z',time(), true)
So if your date is not in this format, use that function */
$start_date = date_i18n("Ymd\THis\Z", get_post_meta( get_the_ID(), 'custom-field-of-start-date', true )); // EDIT THIS WITH YOUR OWN VALUE
$end_date = date_i18n("Ymd\THis\Z", get_post_meta( get_the_ID(), 'custom-field-of-end-date', true )); // EDIT THIS WITH YOUR OWN VALUE
$deadline = date_i18n("Ymd\THis\Z", get_post_meta( get_the_ID(), 'custom-field-of-deadline-date', true )); // EDIT THIS WITH YOUR OWN VALUE
// Otherwise, if your version of WP >= 5.3.0 use this code
$start_date = get_post_meta( get_the_ID(), 'custom-field-of-start-date', true ); // EDIT THIS WITH YOUR OWN VALUE
$end_date = get_post_meta( get_the_ID(), 'custom-field-of-end-date', true ); // EDIT THIS WITH YOUR OWN VALUE
if($start_date != '' && !isValidTimeStamp($start_date)) {
$start_date = strtotime($start_date);
$start_date = wp_date("Ymd\THis", $start_date);
}
if($end_date != '' && !isValidTimeStamp($end_date)) {
$end_date = strtotime($end_date);
$end_date = wp_date('Ymd\THis', $end_date);
} else {
$end_date = wp_date("Ymd\THis", $start_date + (1 * 60 * 60)); // 1 hour after
}
$deadline = date_i18n("Ymd\THis\Z", get_post_meta( get_the_ID(), 'custom-field-of-deadline-date', true )); // EDIT THIS WITH YOUR OWN VALUE
// The rest is the same for any version
$timestamp = date_i18n('Ymd\THis\Z',time(), true);
$uid = get_the_ID();
$created_date = get_post_time('Ymd\THis\Z', true, $uid );
$organiser = get_bloginfo('name'); // EDIT THIS WITH YOUR OWN VALUE
$address = ''; // EDIT THIS WITH YOUR OWN VALUE
$url = get_the_permalink();
$summary = get_the_excerpt();
$content = html_entity_decode(trim(preg_replace('/\s\s+/', ' ', get_the_content()))); // removes newlines and double spaces
$title = html_entity_decode(get_the_title());
//Give the iCal export a filename
$filename = urlencode( $title.'-ical-' . date('Y-m-d') . '.ics' );
$eol = "\r\n";
//Collect output
ob_start();
// Set the correct headers for this file
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=".$filename);
header('Content-type: text/calendar; charset=utf-8');
header("Pragma: 0");
header("Expires: 0");
// The below ics structure MUST NOT have spaces before each line
// Credit for the .ics structure goes to https://gist.github.com/jakebellacera/635416
?>
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//<?php echo get_bloginfo('name'); ?> //NONSGML Events //EN
CALSCALE:GREGORIAN
X-WR-CALNAME:<?php echo get_bloginfo('name').$eol;?>
BEGIN:VEVENT
CREATED:<?php echo $created_date.$eol;?>
UID:<?php echo $uid.$eol;?>
DTEND;VALUE=DATE:<?php echo $end_date.$eol; ?>
DTSTART;VALUE=DATE:<?php echo $start_date.$eol; ?>
DTSTAMP:<?php echo $timestamp.$eol; ?>
LOCATION:<?php echo escapeString($address).$eol; ?>
DESCRIPTION:<?php echo $content.$eol; ?>
SUMMARY:<?php echo $title.$eol; ?>
ORGANIZER:<?php echo escapeString($organiser).$eol;?>
URL;VALUE=URI:<?php echo escapeString($url).$eol; ?>
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DATE-TIME:<?php echo $deadline.$eol; ?>
DESCRIPTION:Reminder for <?php echo escapeString(get_the_title()); echo $eol; ?>
END:VALARM
END:VEVENT
<?php
endwhile;
?>
END:VCALENDAR
<?php
//Collect output and echo
$eventsical = ob_get_contents();
ob_end_clean();
echo $eventsical;
exit();
endif;
}
?>
USAGE in TEMPLATE
<a href="<?php echo get_feed_link('calendar'); ?>?id=<?php echo get_the_ID(); ?>"> Download the ics/ical </a>
@Jany-M
Copy link
Author

Jany-M commented Mar 27, 2021

@VIPStephan

  1. I removed ACF get_field, in favor of the standard WP get_post_meta
  2. I left it there in case anyone needed it, function's name is pretty self_explanatory, but I've added a better explanation
  3. You're right! Added it to script (I had it defined elsewhere)
  4. $timestamp should be ok as is, I've moved below it so it's not confusing
  5. Correct. In my original script there's more about deadline, which uses ACF true/false and conditional functions in case one is set. I've removed the reference here.

Keep in mind this is a script I used in a real project and added it here mostly for myself, I removed most of project-specific references, it's possible that small parts are not perfect... So, good observations, but remember this isn't a paid php or wp tutorial service, it's just a snippet that other developers can use and manipulate according to need and if they can't figure out the small changes needed, maybe they should hire a dev ;)

@christophsanz
Copy link

On line 66 there is a "g" missing in the function name

@Jany-M
Copy link
Author

Jany-M commented Oct 15, 2021

On line 66 there is a "g" missing in the function name
Fixed, thank you!

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