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>
@Gtrujillo
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
Copy link
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
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
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, '...');
}

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
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
Copy link

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

@marvilmedia
Copy link

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
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
Copy link

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
Copy link
Author

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
Copy link

@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
Copy link
Author

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
Copy link

@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
Copy link

@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
Copy link
Author

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
Copy link

@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
Copy link

@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
Copy link
Author

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
Copy link

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
Copy link

@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
Copy link

@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
Copy link

@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
Copy link
Author

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
Copy link

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

@VIPStephan
Copy link

VIPStephan commented Mar 15, 2021

Hi @Jany-M,
A couple of things:

  1. It would be nice to document the code a little better. For example, there is no mention that ACF is required where you use get_field(), and it’s especially confusing since in your comments you mention the WP version, which has nothing to do with it (i. e. in the WP < 5.3.0 section you use get_post_meta() and in the WP >= 5.3.0 you use get_field().
  2. The function shorter_version() is being defined but never used. You could mention that it’s optional (I suppose for summary and content?).
  3. I’m getting an error “Uncaught Error: Call to undefined function isValidTimeStamp()” – yeah, where is that function defined in the first place? Or am I missing anything that the other people before me didn’t?
  4. The variable $timestamp you are using on line 108 is defined on line 50 within the section after the WP < 5.3.0 comment, so if you remove that because you’re using a version greater than 5.3.0 an error occurs. What’s the alternative for later versions?
  5. The variable $deadline_txt is used on line 90 but never defined before. On the other hand, there is a variable $summary defined on line 56 but never used. Is that simply an oversight?

@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