Dynamic iCal generator outside/inside wordpress

So following on from this post: https://stackoverflow.com/q/1463480/1086990 I was wondering how exactly it would work integrating it into WordPress.

I have a custom post type setup, and so far I have tried both linking to a “ical.php” with the above link code (and changing the title into the_title() and the dates into GMT versions of my date(..) code). None work, because it can find the get_post_custom_values() which is obvious since not in WP loop, or a WP associated file.

Read More

Next I tried adding the headers into the header.php WP file, and adding a if(is_singular("event")), but the headers auto download. If I can stop the auto download then I’m ace, otherwise I come here for help!

Is there any reasonable way that is dynamically able to be populated via WP and only download when the link is clicked, not automatically?

I would host everything, but the events are going to be so often, that making and hosting would be a waste also is there maybe a href generator like Google Calendar that would automatically work?


EDIT (My working code):

Custom Taxonomy Loop

<form action="<?php echo get_feed_link('calendar-event'); ?>" method="post">
    <input hidden="hidden" name="eventID" value="<?php the_ID(); ?>">
    <button title="Add to iCal" type="submit" name="iCalForm">iCal</button>
</form>

iCal.php
I included this into the functions.php via a require().

<?php //Event ICAL feed
class SH_Event_ICAL_Export  {

    public function load() { add_feed('calendar-event', array(__CLASS__,'export_events')); }

    // Creates an ICAL file of events in the database
    public function export_events(){ 

        //Give the iCal export a filename
        $filename = urlencode( 'event-ical-' . date('Y-m-d') . '.ics' );

        //Collect output 
        ob_start();

        // File header
        header("Content-Description: File Transfer");
        header("Content-Disposition: attachment; filename=".$filename);
        header("Content-type: text/calendar");
        header("Pragma: 0");
        header("Expires: 0");
?>
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//<?php echo get_bloginfo('name'); ?> //NONSGML Events //EN
CALSCALE:GREGORIAN
X-WR-CALNAME:<?php echo get_bloginfo('name');?>: Events
<?php // Query for events

    if(isset($_POST['iCalForm'])) {
        $post_ID = $_POST['eventID'];
        $events = new WP_Query(array(
            'p' => $post_ID,
            'post_type' => 'event'  //Or whatever the name of your post type is
        ));

    if($events->have_posts()) : while($events->have_posts()) : $events->the_post();
        $uid = get_the_ID(); // Universal unique ID
        $dtstamp = date_i18n('YmdTHisZ',time(), true); // Date stamp for now.
        $created_date = get_post_time('YmdTHisZ', true, get_the_ID() ); // Time event created
        // Other pieces of "get_post_custom_values()" that make up for the StartDate, EndDate, EventOrganiser, Location, etc.
        // I also had a DeadlineDate which I included into the BEGIN:VALARM
        // Other notes I found while trying to figure this out was for All-Day events, you just need the date("Ymd"), assuming that Apple iCal is your main focus, and not Outlook, or others which I haven't tested :]
?>
BEGIN:VEVENT
CREATED:<?php echo $created_date;?>

UID:<?php echo $uid;?>

DTEND;VALUE=DATE:<?php echo $end_date; ?>

TRANSP:OPAQUE
SUMMARY:<?php echo $organiser; ?>

DTSTART;VALUE=DATE:<?php echo $start_date ; ?>

DTSTAMP:<?php echo $dtstamp;?>

LOCATION:<?php echo $location;?>

ORGANIZER:<?php echo $organiser;?>

URL;VALUE=URI:<?php echo "http://".$url; ?>

BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;VALUE=DATE-TIME:<?php echo $dLine; ?>

DESCRIPTION:Closing submission day of films for <?php echo $organiser; ?>! Enter quickly!
END:VALARM
END:VEVENT
<?php endwhile; endif; } ?>
END:VCALENDAR
<?php //Collect output and echo 
    $eventsical = ob_get_contents();
    ob_end_clean();
    echo $eventsical;
    exit();
    }   

} // end class
SH_Event_ICAL_Export::load();
?>

Hope this serves well to whoever needs it, as it is a fantastic piece of work, so again thanks Stephen Harris who helped more than the question, and additionally without suggesting I use his plugin.

Related posts

Leave a Reply

2 comments

  1. I would use WordPress’ feeds. You can create your own feed with add_feed. You specify a callback and this callback is responsible for displaying the output.

    Creating a feed

    add_feed('my-events','wpse56187_ical_cb');
    

    wpse56187_ical_cb is that responsible for getting the events (use WP_Query), looping through them and printing the ICAL fields.

    Downloading the ICAL file

    If your feed is ‘my-events’, then the ical file can then be downloaded by going to:

    www.yoursite.com?feed=my-events
    www.yoursite.com/feed/my-events //If you have pretty permalinks
    

    The link to the feed can be obtained by get_feed_link(). So if you wanted to create a link to you event’s feed in your template:

    <a href="<?php echo get_feed_link('my-events'); ?>" > Click here for ical file </a>
    

    Example

    I’ve omitted the details of filling in the event details in ICAL format (how you do this will depend on how you’ve stored the details).

    The following class implements the above method and creates a feed called ‘my-events’. You will need to make sure the details are formatted correctly and are valid (see this site)

    Please note, I’ve not tested the following so there may be syntax errors

    <?php
    /**
     * Event ICAL feed
     */
    class SH_Event_ICAL_Export  {
    
        public function load(){
            add_feed('my-events', array(__CLASS__,'export_events'));
        }
    
       /**
        * Creates an ICAL file of events in the database
        *  @param string filename - the name of the file to be created
        *  @param string filetype - the type of the file ('text/calendar')
        */ 
        public function export_events( ){ 
    
        //Give the ICAL a filename
        $filename = urlencode( 'event-ical-' . date('Y-m-d') . '.ics' );
    
        //Collect output 
        ob_start();
    
        // File header
        header( 'Content-Description: File Transfer' );
        header( 'Content-Disposition: attachment; filename=' . $filename );
        header('Content-type: text/calendar');
        header("Pragma: 0");
        header("Expires: 0");
    ?>
    BEGIN:VCALENDAR
    VERSION:2.0
    PRODID:-//<?php  get_bloginfo('name'); ?>//NONSGML Events //EN
    CALSCALE:GREGORIAN
    X-WR-CALNAME:<?php echo get_bloginfo('name');?> - Events
    
    <?php
      
        // Query for events
        $events = WP_Query(array(
             'post_type'=>'event' //Or whatever the name of your post type is
             'posts_per_page'=>-1 //Get all events
              ...
        ));
    
        if( $events->have_posts() ):
            while( $events->have_posts() ): $events->the_post();
                $uid=''; //Universal unique ID
                $dtstamp=date_i18n('YmdTHisZ',time(), true); //date stamp for now.
                $created_date=get_post_time('YmdTHisZ', true, get_the_ID() ); //time event created
                $start_date=""//event start date
                $end_date=""//event end date
                $reoccurrence_rule=false//event reoccurrence rule.
                $location=''//event location
                $organiser=''//event organiser
    ?>
    BEGIN:VEVENT
    UID:<?php echo $uid;?>
    
    DTSTAMP:<?php echo $dtstamp;?>
    
    CREATED:<?php echo $created_date;?>
    
    DTSTART:<?php echo $start_date ; ?>
    
    DTEND:<?php echo $end_date; ?>
    
    <?php if ($reoccurrence_rule):?>
    RRULE:<?php echo $reoccurrence_rule;?>
    
    <?php endif;?>
    
    LOCATION: <?php echo $location;?>
    
    ORGANIZER: <?php $organiser;?>
    
    END:VEVENT
    <?php
            endwhile;
        endif;
    ?>
    END:VCALENDAR
    <?php
    
        //Collect output and echo 
        $eventsical = ob_get_contents();
        ob_end_clean();
        echo $eventsical;
        exit();
        }   
    
    } // end class
    SH_Event_ICAL_Export::load();
    
  2. A slightly different version than Stephen’s.
    I integrated some things and changed others.

    Code is tested and works fine, as I’m using it in a project.

    function add_calendar_feed(){
        add_feed('calendar', 'export_events');
        // Only uncomment these 2 lines the first time
        /*global $wp_rewrite;
        $wp_rewrite->flush_rules( false );*/
    }
    add_action('init', 'add_calendar_feed');
    
    function export_events(){
    
       // Helper - Get the right time format, use this function or date_i18n('YmdTHisZ', $yourtimehere, true);
       /*function dateToCal($timestamp) {
           return date('YmdTHisZ', $timestamp);
       }*/
       // Helper - Escapes a string of characters
       function escapeString($string) {
           return preg_replace('/([,;])/','\$1', $string);
       }
       // Helper - Cut it
       function shorter_version($string, $lenght) {
            if (strlen($string) >= $lenght) {
                return substr($string, 0, $lenght);
            } else {
                return $string;
            }
       }
    
        // 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();
    
            $timestamp = date_i18n('YmdTHisZ',time(), true);
            $uid = get_the_ID();
            $created_date = get_post_time('YmdTHisZ', true, $uid ); // post creation
            $start_date = date_i18n('YmdTHisZ',time(), true); // EDIT THIS WITH START DATE VALUE
            $end_date = date_i18n('YmdTHisZ',time(), true); // EDIT THIS WITH END DATE VALUE
            $deadline = date_i18n('YmdTHisZ',time()-24, true); // deadline in BEGIN:VALARM
            $organiser = get_bloginfo('name'); // YOUR NAME OR SOME VALUE
            $address = ''; // EDIT THIS WITH SOME VALUE
            $url = get_the_permalink();
            $summary = get_the_excerpt();
            $content = trim(preg_replace('/ss+/', ' ', 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 = "rn";
    
            //Collect output
            ob_start();
    
            // File header
            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");
    ?>
    BEGIN:VCALENDAR
    VERSION:2.0
    PRODID:-//<?php echo get_bloginfo('name'); ?> //NONSGML Events //EN
    CALSCALE:GREGORIAN
    X-WR-CALNAME:<?php echo get_bloginfo('name');?>
    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:Fine / Scadenza di <?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;
    
    }
    ?>
    

    Then in your theme, use this link:

    <a href="<?php echo get_feed_link('calendar'); ?>?id=<?php echo get_the_ID(); ?>">Get the ical file </a>
    

    Made a GIST out of it (forked), with some more info.