Upfront warning. This is a fairly advanced topic and requires basic knowledge of CakePHP framework and PHP. This article demonstrates how you can use existing components on the web and integrate them into your application.
I was looking for a way to export data in an iCalendar (RFC 2445) compatible format. To accomplish this I created a helper class that generates this code using the iCalcreator class.
There are two major iCalendar classes available for PHP, phpicalendar and iCalcreator. I chose the later since it was a single class file and slightly better from a object oriented point of view.
The first step is to download a copy of iCalcreator and place the iCalcreator.class.php file in your /vendors folder.
Now place the following helper class into your /views/helpers folder.
<?php require_once('vendors/iCalcreator.class.php'); class ICalHelper extends Helper { var $name = 'ICalHelper'; var $errorCode = null; var $errorMessage = null; var $calendar; function create($name, $description='', $tz='US/Eastern') { $v = new vcalendar(); $v->setConfig('unique_id', $name.'.'.'yourdomain.com'); $v->setProperty('method', 'PUBLISH'); $v->setProperty('x-wr-calname', $name.' Calendar'); $v->setProperty("X-WR-CALDESC", $description); $v->setProperty("X-WR-TIMEZONE", $tz); $this->calendar = $v; } function addEvent($start, $end=false, $summary, $description='', $extra=false) { $start = strtotime($start); $vevent = new vevent(); if(!$end) { $vevent->setProperty('dtstart', date('Ymd', $start), array('VALUE'=>'DATE')); } else { $end = strtotime($end); $end = getdate($end); $end = array ('year' => $end['year'], 'month' => $end['mon'], 'day' => $end['mday'], 'hour' => $end['hours'], 'min' => $end['minutes'], 'sec' => $end['seconds']); $start = getdate($start); $start = array ('year' => $start['year'], 'month' => $start['mon'], 'day' => $start['mday'], 'hour' => $start['hours'], 'min' => $start['minutes'], 'sec' => $start['seconds']); $vevent->setProperty('dtstart', $start); $vevent->setProperty('dtend', $end); } $vevent->setProperty('summary', $summary); $vevent->setProperty('description', $description); if(is_array($extra)) { foreach($extra as $key=>$value) { $vevent->setProperty($key, $value); } } $this->calendar->setComponent($vevent); } function getCalendar() { return $this->calendar; } function render() { $this->calendar->returnCalendar(); } } ?>
Once you have completed this step you may want to support ics extensions in your application, which is required by some calendar applications.
To do this, add ics to your router extensions in config/routes.php
Router::parseExtensions('ics');
Now setup a function in your controller for your calendar view. It might look something like:
<?php class Project extends AppController { var $helpers = array('Html', 'Text', 'ICal'); function due() { $this->Project->recursive = 0; $this->paginate = array( 'Project' => array( 'order' => 'due ASC', 'limit' => 5, 'scope' => array('complete = 0 AND due IS NOT NULL') ) ); $projects_due = $this->paginate(); return $projects_due; } } ?>
Now create a view for your data. In my case to speed up the operation I used the caching instructions from [url]http://bakery.cakephp.org/articles/view/optimizing-your-cakephp-elements-and-views-with-caching[/url] and created an element (elements/projects_due.ctp) to handle the conversion of to ics.
<?php $projects = $this->requestAction('projects/due'); $iCal->create('Activeprojects', 'Active outstanding projects', 'US/Eastern'); foreach($projects as $Project) { $iCal->addEvent($Project['Project']['due'], false, $Project['Project']['title'], $Project['Project']['description']."\n\n".$html->url('/Project/view/'.$Project['Project']['id'], true), array('UID'=>$Project['Project']['id'], 'attach'=>$html->url('/Project/view/'.$Project['Project']['id'], true), 'organizer'=>$Project['User']['username'], 'location'=>$Project['location'])); } $iCal->render(); ?>
Now include this element in your view (views/projects/due.ctp).
<?php echo $this->element('projects_due', array('cache'=>'+1 hour')); ?>
Now go to http://yourdomain.com/projects/due.ics and you should get an ics file download that you can open in any icalendar compatible program or you can paste that URL in Google calendar.
3 Comments
I got an “Array to string conversion [APP/vendors/iCalcreator.class.php, line 5290]” error with the above due to the start & end timestamps’ day-of-month value not being translated. i.e. require $start[‘day’] = $start[‘mday’];
Also, it seems that iCalcreator is very sensitive to the number of arguments, I found the following seems to work:
$end = strtotime($end);
$start = getdate($start);
// iCalcreator is sensitive to extra arguments, only pass what is required
$start = array (“year” => $start[‘year’], “month” => $start[‘mon’],
“day” => $start[‘mday’], “hour” => $start[‘hours’], “min” => $start[‘minutes’],
“sec” => $start[‘seconds’]);
$end = getdate($end);
$end = array (“year” => $end[‘year’], “month” => $end[‘mon’],
“day” => $end[‘mday’], “hour” => $end[‘hours’], “min” => $end[‘minutes’],
“sec” => $end[‘seconds’]);
$vevent->setProperty(‘dtstart’, $start);
$vevent->setProperty(‘dtend’, $end);
Thanks Ben. I updated the code to include the subset of parameters.
Thanks to Kory for pointing out an issue with this code and iCal. I updated the code with the fix.
Changed:
if(!$end)
{
$end = $start + 24*60*60;
$vevent->setProperty(‘dtstart’, date(‘Ymd’, $start), array(‘VALUE’=>’DATE’));
$vevent->setProperty(‘dtend’, date(‘Ymd’, $end), array(‘VALUE’=>’DATE’));
}
to
if(!$end)
{
$vevent->setProperty(‘dtstart’, date(‘Ymd’, $start), array(‘VALUE’=>’DATE’));
}
2 Trackbacks and Pingbacks
[…] here: Why bake a calendar when one already exists Related ArticlesBookmarksTags Why the UK’s tech hub should not be in London The PHP group […]
[…] Why bake a calendar when one already exists? […]