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
/*
For a better understanding of ics requirements and time formats
please check https://gist.github.com/jakebellacera/635416
*/
// 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');
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();
// 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 */
$timestamp = date_i18n('Ymd\THis\Z',time(), true);
$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_field("custom-field-of-start-date", false, false); // EDIT THIS WITH YOUR OWN VALUE
$end_date = get_field("custom-field-of-end-date", false, false); // 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
$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 $deadline_txt.$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 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 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 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 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 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 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 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 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 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 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?

@Jany-M

This comment has been minimized.

Copy link
Owner Author

@Jany-M Jany-M commented Jan 17, 2020

Sorry just saw most of these comments, not getting any notifications. Please make sure you @ me, in case.
I have updated the script to work with wp_date() since as of WP >= 5.3.0 date_i18n() isn't working as expected.
@BumblebeeGames the script doesnt cache any file, so it would have to be either your host or your site's setup.
@ghost I had fixed that at the time, thanks!
@marvilmedia try to add AddType application/octet-stream .ics in your .htaccess (it could also be a browser or OS config though)

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 28, 2020

@Jany-M
I get a error like @marvilmedia i get send to http://localhost/wordpress/feed/calendar/?id=237 doesnt create a file but there is a error that says: error on line 3 at column 1: Extra content at the end of the document. What could this be as i just copied the code and changed the stuff that was needed also tried a clean copy of the code without changes but still get this error can you please help me with this.
Thank you!

@Jany-M

This comment has been minimized.

Copy link
Owner Author

@Jany-M Jany-M commented Jan 28, 2020

@erikvanblitterswijk it's possible that the issue isn't with this script, but something else. Try with WP's default theme first, if issue is still there, reactivate your theme and disable all plugins and try again.. then try enabling them one by one. The script is fine, I've used it on several sites.
Check your error log too, just in case.

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 28, 2020

@Jany-M oke i will try that and come back if i have something. Already checked the error log but there is nothing there also the error says Below is a rendering of the page up to the first error. but there is nothing there but i will try what you said Tnx!

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 28, 2020

@Jany-M Here again tried everything but nothing worked but i got a Internal server error it says at the log now what could this be you think because nothing is changed to the code or am i missing things in my files can you figure something out by this information because i dont really know whats wrong the image is what i see when i click the link
error

@Jany-M

This comment has been minimized.

Copy link
Owner Author

@Jany-M Jany-M commented Jan 28, 2020

An error 500 is the default php fatal error when no custom error pages have been set. Do you have errors set to log and display?
You could try to simply echo everything in a test page, so you could see if it's the script or something else with your setup, without having to click on link/generate the cap dynamically. I can't really help without more specific details.

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 28, 2020

@Jany-M i dont really know what you mean by your setup but i wil try and echo everything into a page and see what happens. Dont really understand why only i am having this problem and what could cause it because i pretty much didnt changed anything in my plugins and pages

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 28, 2020

@Jany-M I think i have some more information about the error here is what the debug says:

[28-Jan-2020 12:48:49 UTC] PHP Notice: Undefined index: id in C:\xampp\htdocs\wordpress\wp-content\themes\astra\functions.php on line 185
[28-Jan-2020 12:48:49 UTC] PHP Fatal error: Uncaught Error: Call to undefined function get_field() in C:\xampp\htdocs\wordpress\wp-content\themes\astra\functions.php:217
Stack trace:
#0 C:\xampp\htdocs\wordpress\wp-includes\class-wp-hook.php(288): export_ics(false, 'calendar')
#1 C:\xampp\htdocs\wordpress\wp-includes\class-wp-hook.php(312): WP_Hook->apply_filters('', Array)
#2 C:\xampp\htdocs\wordpress\wp-includes\plugin.php(478): WP_Hook->do_action(Array)
#3 C:\xampp\htdocs\wordpress\wp-includes\functions.php(1543): do_action('do_feed_calenda...', false, 'calendar')
#4 C:\xampp\htdocs\wordpress\wp-includes\template-loader.php(40): do_feed()
#5 C:\xampp\htdocs\wordpress\wp-blog-header.php(19): require_once('C:\xampp\htdocs...')
#6 C:\xampp\htdocs\wordpress\index.php(17): require('C:\xampp\htdocs...')
#7 {main}
thrown in C:\xampp\htdocs\wordpress\wp-content\themes\astra\functions.php on line 217

Something about the undefined get field is this useful?

@Jany-M

This comment has been minimized.

Copy link
Owner Author

@Jany-M Jany-M commented Jan 28, 2020

Are you not using ACF? It seems like you aren't, so you will need to use a different function than get_field() to retrieve custom fields and that should fix it :)

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 28, 2020

Thank you so much for saying this i downloaded the plugin and it worked the only thing is that it didnt put the right info in the ics so that i have to figure out think i have some things wrong in the code so if i need any help i will come back to you but thank you so much for helping!

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Jan 29, 2020

@Jany-M i have 1 last question i hope the only thing showing in the ics file is the description and i have some problems with the start time end time and the title from the event and as last the address how can i get this to work my meta keys are : _event_title , _event_address , _event_start_date , _event_send_date and as last i want to add the start time and end time to it but dont know how.

i am sorry that i ask so many question but i am kinda new to this so i need some help with it already thanks for helping me so much

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Feb 2, 2020

@Jany-M Could you please help me out with this were i have to put the meta-keys or something i cant figure it out what i have to do exactly it still doesnt show the title, address, start time and end time

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Feb 3, 2020

@Jany-M Can you please help me or help me where i can find the exact names of the tables so i can fill them in where you say to fill them in so i can finish this part of the website because i still cant figure out where to find the table names as when i fill in the meta-keys that the plugin says the field doesnt gets filled in when i download the ics file.

@Jany-M

This comment has been minimized.

Copy link
Owner Author

@Jany-M Jany-M commented Feb 3, 2020

@erikvanblitterswijk sorry but I really don't have time at the moment for one on one tutoring on WP or PHP. There are many guides and tutorials out there, besides the WP codex, that will teach you exactly how to get $post title and $post metas.

@erikvanblitterswijk

This comment has been minimized.

Copy link

@erikvanblitterswijk erikvanblitterswijk commented Feb 3, 2020

Oke thanks for telling then i hope i can figure it out what i have to fill in in the 'edit with own value spots to make it work because the its not the meta keys thats for sure and i already looked into the db but there is nothing there about the names so i hope i will figure it out thanks for helping as much as you could

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.