Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
[WP] Generate a downloadable .ics file from any WordPress post or custom post type
<?php
// 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
/*global $wp_rewrite;
$wp_rewrite->flush_rules( false );*/
}
add_action('init', 'add_calendar_feed');
function export_ics(){
/* For a better understanding of ics requirements and time formats
please check https://gist.github.com/jakebellacera/635416 */
// Query the event
$the_event = new WP_Query(array(
'p' => $_REQUEST['id'],
'post_type' => 'any',
));
if($the_event->have_posts()) :
// Escapes a string of characters
function escapeString($string) {
return preg_replace('/([\,;])/','\\\$1', $string);
}
// Cut it
function shorter_version($string, $lenght) {
if (strlen($string) >= $lenght) {
return substr($string, 0, $lenght);
} else {
return $string;
}
}
while($the_event->have_posts()) : $the_event->the_post();
/* 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 */
$timestamp = date_i18n('Ymd\THis\Z',time(), true);
$uid = get_the_ID();
$created_date = get_post_time('Ymd\THis\Z', true, $uid );
$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
$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 = trim(preg_replace('/\s\s+/', ' ', get_the_content())); // removes newlines and double spaces
//Give the iCal export a filename
$filename = urlencode( get_the_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').$eol; ?> //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 shorter_version($content,70).$eol; ?>
SUMMARY:<?php echo escapeString(get_the_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>
@Gtrujillo

This comment has been minimized.

Copy link

Gtrujillo commented Sep 1, 2016

I'm not sure what I'm doing wrong but this does not work for me.

I've placed the code in my functions.php.

I corrected the callback function name so it calls the export_ics function. - Line 5

I put the link the the template file I want to download from.

Download provides a txt file that is empty. --

EDIT* I found why the file was empty. Since I am attempting to get a loop of posts and not a single post the ?id<php echo get_the_ID(); ?> should be removed from the link in the template file.

This edit started populating the download file with the correct information.

One small thing the that made the file unable to upload to a calendar when trying to loop several events:
Line 76 needs .$eol added to the end of it.

Thank you so much for this resource. It saved me time!

@Jany-M

This comment has been minimized.

Copy link
Owner Author

Jany-M commented Sep 26, 2016

Thanks for the input, I added the .$eol to to both lines 74 and 76.
Glad I could be of help!

@mastafu

This comment has been minimized.

Copy link

mastafu commented Mar 1, 2017

Jane-M: please corect function name from export_events to export_ics in line 5.
Otherwise great job, thx.

@L1lle

This comment has been minimized.

Copy link

L1lle commented Mar 8, 2017

Hi, thanks for the code sample.
I would recommend using multiline strings with <<<EOT (doesn't require output buffer or <?php commands).

$eventsical .= <<<EOT
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//$blogInfo//NONSGML Events //EN
CALSCALE:GREGORIAN
X-WR-CALNAME:$blogInfo
BEGIN:VEVENT
CREATED:$created_date
UID:$uid
DTEND;VALUE=DATE:$end_date
DTSTART;VALUE=DATE:$start_date
DTSTAMP:$timestamp
LOCATION:$address
DESCRIPTION:$description
SUMMARY:$title
ORGANIZER:$organiser
URL;VALUE=URI:$url
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DATE-TIME:$deadline
DESCRIPTION:Reminder for $title
END:VALARM
END:VEVENT
EOT;

Also using build in Wordpress function to shorten your strings by word count.

function shorter_version($string, $lenght) {
    return wp_trim_words($string, $lenght, '...');
}
@ghost

This comment has been minimized.

Copy link

ghost commented Dec 20, 2017

Hello thanks for your work,
I am not making a pull request but, on line 5 the feed function should have the name of the actual function i think:
add_feed('calendar', 'export_ics');

@LVilaj

This comment has been minimized.

Copy link

LVilaj commented Jan 30, 2018

Hey there,

Not sure what am I doing wrong but can not make it work! When added to template I don't get file it produces http://localhost/staging/feed/calendar/ link. Let me know if you came across with same issue and what would that cause it.

@lincolnlemos

This comment has been minimized.

Copy link

lincolnlemos commented Feb 3, 2018

Have you change the line 5 like as sacrista said? @LVilaj

@marvilmedia

This comment has been minimized.

Copy link

marvilmedia commented Feb 16, 2018

Hello thank you for the ical solution but like @LVilaj i'm getting this url http://localhost:8888/feed/calendar/?id=71 but no downloadable file and yes I did change line 5 and line 20 to the event custom post type

@BumblebeeGames

This comment has been minimized.

Copy link

BumblebeeGames commented Feb 22, 2019

Excellent! However, changes in the export_icns() are not being taken. Any need to reregister it or anything?
Changing
$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
to
$start_date = $eventDate['event_start']; // EDIT THIS WITH YOUR OWN VALUE
after the initial upload does not result in any changes in the .icf files.
Even deleting the whole content of export_ics() does still generate .ics-files.
Any idea?
Thanks!

@BumblebeeGames

This comment has been minimized.

Copy link

BumblebeeGames commented Feb 22, 2019

Just realized that this is only affecting already generated .ics'. When clicking/generating a new one, that's fine. So I guess I only have to find and delete the already generated files?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.