Loop through posts of a custom-post-type (event) and create .ics (iCal) file?

I really need your help with a feature I have never worked with so far.

I have a custom-post-type named wr_event. And I created this custom WP_Query to retrieve all posts for this post-type that are “younger” than yesterday. Fairly simple and this works like a charm.

Read More
function event_list_iCal() {

    $yesterday = time() - 24*60*60;
    $args = array(
        'post_type' => 'wr_event',
        'posts_per_page' => -1, // show all posts
        'meta_key' => 'event_date',
        'orderby' => 'meta_value_num',
        'order' => 'ASC',
        'meta_value' => $yesterday,
        'meta_compare' => '>',
    );

    $loop = new WP_Query( $args );
    $ical = "BEGIN:VCALENDAR
    VERSION:2.0
    PRODID:-//hacksw/handcal//NONSGML v1.0//EN";

    $posts = get_posts( $args );
    foreach( $posts as $post ) : setup_postdata($post);
        $ical .= "BEGIN:VEVENT
        UID:" . md5(uniqid(mt_rand(), true)) . "mysite.com
        DTSTAMP:" . gmdate('Ymd').'T'. gmdate('His') . "Z
        DTSTART:".unixToiCal(get_event_date($post), get_event_time($post))."00Z
        DTEND:".unixToiCal(get_event_end_date($post), get_event_end_time($post))."00Z
        SUMMARY:".get_the_title($post->ID)."
        DESCRIPTION:".get_the_content($post->ID)."
        END:VEVENT";
    endforeach;

    $ical .= "END:VCALENDAR";

    header('Content-type: text/calendar; charset=utf-8');
    header('Content-Disposition: inline; filename=calendar.ics');
    echo $ical;
    exit;
}

function unixToiCal($uStamp = 0, $tzone = 0.0) {
    $uStampUTC = $uStamp + ($tzone * 3600);       
    $stamp  = date("YmdTHisZ", $uStampUTC);
    return $stamp;       
} 

I use this function call in my index.php to list all “upcoming” events.

Creating a .ics file.

There is another feature that I’d like to have. I want to create a .ics (iCal) file on the fly with all “upcoming” events. So I guess this shoudln’t be to hard as I already have the query.

Any ideas on that matter? I would really appreciate some help with this.

Update:

I have two more problems with the .ics Calendar file.

I have a function get_event_date($timestamp) that returns a timestamp of the event-date. However there is a (for me) rather complicated part in it.

There are two variables $date[0] and $time[0] that hold different formats.
The $date[0] holds a timestamp 1347667200 and the $time[0] holds a string e.g. 14:00. I now need to calculate the final timestamp of the “date” plus “time” to pass it along to the unixToical() function.

if ( $timestamp ) { 
            if ( !empty( $time[0]) ) {
                $time = explode(':', $time[0]);
                $hours = $time[0];
                $minutes = $time[1];
            } else { 
                //$hours = "00";
                //$minutes = "00";
            }
            $seconds = "00";
            return $date[0] + ($hours * 60)  + $minutes;
            exit;
        }

The part where I set $hours to “00” is for when there is no time set. In that case I want to time in the final .ics to be “00:00” (midnight).

Any idea what I’m doing wrong here. I guess this might be the problem why when importing the calendar file to iCal only the first event is imported. (When opening the file with a text-editor all events are in there)

Related posts

Leave a Reply

2 comments

  1. This is entirely based on Event Organiser (a plug-in I’ve developed). The code is lifted almost straight from the source but with alterations. As such I’ve not tested the code as given.

    Step 1: Create a feed

    This is simple:

    add_action('init','wpse63611_add_events_feed');
    function wpse63611_add_events_feed(){
         add_feed('my-events','wpse63611_events_feed_output');
    }
    

    This adds a feed to your site: www.site.com?feed=my-events or www.site.com/feed/my-events if you have pretty permalinks. The wpse63611_events_feed_output() callback will output the contents of the ICAL file. But first…

    Step 2: Alter the query

    WordPress doesn’t know what this feed is meant to contain. Here we use the pre_get_posts to tell WordPress, that for this feed we want posts of post type ‘wr_event’. We could get events from a particular category, venue or between certain dates too.

    By separating out the query from the output, you can have multiple feeds which use the same output function – but query different events (based on time, location, category etc).

    add_action( 'pre_get_posts', 'wpse63611_event_feed_query' );
    function wpse63611_event_feed_query( $query ) {
    
         $yesterday = current_time('timestamp') - 24*60*60;
         $compare = $latest ? '>' : '<';
    
         if( $query->is_feed('eo-events') ){
             $query->set('post_type', 'wr_event');
             $query->set('posts_per_page', -1);
             $query->set('meta_key', 'event_date');
             $query->set('orderby', 'meta_value_num');
             $query->set('order', 'ASC');
             $query->set('meta_compare', $compare);
             $query->set('meta_value', $value);
         }
    }
    

    Step 3: The contents of the ICS file

    As mentioned earlier, wpse63611_events_feed_output() is responsible for printing the output of our feed.

     function wpse63611_events_feed_output(){
          //Let's give it a name;
          $filename = urlencode( 'my_events' . 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
    
        // Loop through events
        if ( have_posts() ):
    
             $now = new DateTime();
             $datestamp =$now->format('YmdTHisZ');
    
             while( have_posts() ): the_post();
                  global $post;
    
                  $uid = md5(uniqid(mt_rand(), true))."@mysite.com";
    
                  $start = unixToiCal(get_event_date($post), get_event_time($post));
                  $end = unixToiCal(get_event_end_date($post), get_event_end_time($post));
    
                  $summary = wpse63611_escape_icalText(get_the_title())
                  $description = apply_filters('the_excerpt_rss',  get_the_content());
                  $description = wpse63611_escape_icalText($description);
    
    BEGIN:VEVENT
    UID:<?php echo $uid;?>
    
    DTSTAMP:<?php echo $datestamp;?>
    
    DTSTART:<?php echo $start; ?>
    
    DTEND:<?php echo $end; ?>
    
    SUMMARY:<?php echo wpse63611_esc_ical_text($summary);?>
    
    DESCRIPTION:<?php echo wpse63611_esc_ical_text($description);?>
    
    END:VEVENT
    
             endwhile;
    
        endif;
    ?>
    END:VCALENDAR
    <?php
    
        //Collect output and echo 
        $eventsical = ob_get_contents();
        ob_end_clean();
        echo $eventsical;
        exit();
    }   
    

    I’ve used the unixToiCal function you’ve defined in your question. I’ve also used the following to remove anything that might upset an ICAL parser:

     function wpse63611_esc_ical_text( $text='' ){
    
        $text = str_replace("", "", $text);
        $text = str_replace(",", ",", $text);
        $text = str_replace(";", ";", $text);
        $text = str_replace("n", "n ", $text);
    
        return $text;
     }
    
  2. Try this:

    First add this functions to your theme’s functions.php

    //this will call the download function if needed
    function Ical_download() {
        global $wp;
        global $wp_query;
        if (isset($wp->query_vars["ical_download"])){
            event_list_iCal();
            exit();
        }
    }
    
    add_action('template_redirect', 'Ical_download');
    
    
    //this will add ical_download to the list of query vars
    function ical_download_query_val() {
        global $wp;
        $wp->add_query_var('ical_download');
    }
    
    add_filter('init', 'ical_download_query_val');
    function event_list_iCal( $latest = true ) {
    
        $yesterday = time() - 24*60*60;
        $compare = $latest ? '>' : '<';
    
        $args = array(
            'post_type' => 'wr_event',
            'posts_per_page' => -1, // show all posts
            'meta_key' => 'event_date',
            'orderby' => 'meta_value_num',
            'order' => 'ASC',
            'meta_value' => $yesterday,
            'meta_compare' => $compare,
        );
    
        $loop = new WP_Query( $args );
        $ical = "BEGIN:VCALENDAR
    VERSION:2.0
    PRODID:-//hacksw/handcal//NONSGML v1.0//EN
    ";
        while ( $loop->have_posts() ) : $loop->the_post();
    
        $ical .= "BEGIN:VEVENT
    UID:" . md5(uniqid(mt_rand(), true)) . "example.com
    DTSTAMP:" . gmdate('Ymd').'T'. gmdate('His') . "Z
    DTSTART:".get_event_date($post)."00Z
    DTEND:".get_event_end_date($post);."00Z
    SUMMARY:".$post->title."
    DESCRIPTION:".$post->content."
    END:VEVENT
    ";
    
        endwhile;
    
        $ical .= "END:VCALENDAR";
    
        //set correct content-type-header
        header('Content-type: text/calendar; charset=utf-8');
        header('Content-Disposition: inline; filename=calendar.ics');
        echo $ical;
        exit;
    
    }
    

    So just update the summery and description fields as well as the start and end times to the right format and your download link should be something like:

    <a href="<?php echo get_bloginfo('url').'?ical_download';?>">Download iCal</a>