2 /* $Id: ical.inc,v 1.17 2008/12/31 00:07:23 killes Exp $ */
6 * API for event import/export in iCalendar format as outlined in Internet Calendaring and Scheduling Core Object Specification
7 * http://www.ietf.org/rfc/rfc2445.txt
9 * This module is IN DEVELOPMENT and not a finished product
13 * Turn an array of events into a valid iCalendar file
16 * An array of associative arrays where
17 * 'start' => Start time (Required, if no allday_start)
18 * 'end' => End time (Optional)
19 * 'allday_start' => Start date of all-day event in YYYYMMDD format (Required, if no start)
20 * 'allday_end' => End date of all-day event in YYYYMMDD format (Optional)
21 * 'summary' => Title of event (Text)
22 * 'description' => Description of event (Text)
23 * 'location' => Location of event (Text)
24 * 'uid' => ID of the event for use by calendaring program. Recommend the url of the node
25 * 'url' => URL of event information
28 * Name of the calendar. Will use site name if none is specified.
31 * Text of a iCalendar file
33 function ical_export($events, $calname = NULL) {
34 $output = "BEGIN:VCALENDAR\nVERSION:2.0\n";
35 $output .= "METHOD:PUBLISH\n";
36 $output .= 'X-WR-CALNAME:'. variable_get('site_name', '') .' | '. ical_escape_text($calname) ."\n";
37 $output .= "PRODID:-//strange bird labs//Drupal iCal API//EN\n";
38 foreach ($events as $uid => $event) {
39 $output .= "BEGIN:VEVENT\n";
40 $output .= "DTSTAMP;VALUE=DATE-TIME:". gmdate("Ymd\THis\Z", time()) ."\n";
41 if (!$event['has_time']) { // all day event
42 $output .= "DTSTART;VALUE=DATE-TIME:" . event_format_date($event['start_utc'], 'custom', "Ymd\THis\Z") ."\n";
43 //If allday event, set to day after allday start
44 $end_date = event_date_later($event['start'], 1);
45 $output .= "DTEND;VALUE=DATE-TIME:" . event_format_date($end_date, 'custom', 'Ymd') ."\n";
47 else if (!empty($event['start_utc']) && !empty($event['end_utc'])) {
48 $output .= "DTSTART;VALUE=DATE-TIME:". event_format_date($event['start_utc'], 'custom', "Ymd\THis\Z") ."\n";
49 $output .= "DTEND;VALUE=DATE-TIME:". event_format_date($event['end_utc'], 'custom', "Ymd\THis\Z") ."\n";
51 else if (!empty($event['start_utc'])) {
52 $output .= "DTSTART;VALUE=DATE-TIME:". event_format_date($event['start_utc'], 'custom', "Ymd\THis\Z") ."\n";
54 $output .= "UID:". ($event['uid'] ? $event['uid'] : $uid) ."\n";
55 if (!empty($event['url'])) {
56 $output .= "URL;VALUE=URI:" . $event['url'] ."\n";
58 if (!empty($event['location'])) {
59 $output .= "LOCATION:" . ical_escape_text($event['location']) ."\n";
61 $output .= "SUMMARY:" . ical_escape_text($event['summary']) ."\n";
62 if (!empty($event['description'])) {
63 $output .= "DESCRIPTION:" . ical_escape_text($event['description']) ."\n";
65 $output .= "END:VEVENT\n";
67 $output .= "END:VCALENDAR\n";
72 * Escape #text elements for safe iCal use
81 function ical_escape_text($text) {
82 //$text = strip_tags($text);
83 $text = str_replace('"', '\"', $text);
84 $text = str_replace("\\", "\\\\", $text);
85 $text = str_replace(",", "\,", $text);
86 $text = str_replace(":", "\:", $text);
87 $text = str_replace(";", "\;", $text);
88 $text = str_replace("\n", "\n ", $text);
93 * Given the location of a valide iCalendar file, will return an array of event information
96 * Location (local or remote) of a valid iCalendar file
99 * An array of associative arrays where
100 * 'start' => start time as date array
101 * 'end' => end time as date array
102 * 'summary' => Title of event
103 * 'description' => Description of event
104 * 'location' => Location of event
105 * 'uid' => ID of the event in calendaring program
106 * 'url' => URL of event information */
107 function ical_import($ical) {
108 $ical = explode("\n", $ical);
110 # $ifile = @fopen($filename, "r");
111 # if ($ifile == FALSE) exit('Invalid input file');
112 $nextline = $ical[0];
113 if (trim($nextline) != 'BEGIN:VCALENDAR') exit('Invalid calendar file:'. $nextline);
114 foreach ($ical as $line) {
116 $nextline = next($ical);
117 $nextline = ereg_replace("[\r\n]", "", $nextline);
118 while (substr($nextline, 0, 1) == " ") {
119 $line .= substr($nextline, 1);
120 $nextline = next($ical);
121 $nextline = ereg_replace("[\r\n]", "", $nextline);
126 unset($start_date, $start_time,
127 $end_date, $end_time,
128 $tz_dtstart, $tz_dtend,
129 $allday_start, $allday_end,
143 $items[$uid] = array('start' => $start_date .' '. $start_time,
144 'end' => $end_date .' '. $end_time,
145 'allday_start' => $allday_start,
146 'tz_start' => $tz_dtstart,
147 'tz_end' => $tz_dtend,
148 'allday_end' => $allday_end,
149 'summary' => $summary,
150 'description' => $description,
151 'location' => $location,
156 unset($field, $data, $prop_pos, $property);
157 ereg("([^:]+):(.*)", $line, $line);
162 $prop_pos = strpos($property, ';');
163 if ($prop_pos !== FALSE) {
164 $property = substr($property, 0, $prop_pos);
166 $property = strtoupper($property);
171 if (substr($data, -1) == 'Z') {
174 $data = str_replace('T', '', $data);
175 $data = str_replace('Z', '', $data);
176 $field = str_replace(';VALUE=DATE-TIME', '', $field);
177 if ((preg_match("/^DTSTART;VALUE=DATE/i", $field)) || (ereg ('^([0-9]{4})([0-9]{2})([0-9]{2})$', $data))) {
178 ereg('([0-9]{4})([0-9]{2})([0-9]{2})', $data, $dtstart_check);
179 $allday_start = $data;
180 $start_date = $allday_start;
183 if (preg_match("/^DTSTART;TZID=/i", $field)) {
184 $tz_tmp = explode('=', $field);
185 $tz_tmp = explode(':', $tz_tmp[1]);
186 $check = event_zone_by_name($tz_tmp[0]);
188 if ($check['name'] === $tz_tmp[0]) {
189 $tz_dtstart = $tz_tmp[0];
192 $messages['error'][] = t('Timezone %tz not found, using default timezone.', array('%tz' => $tz_tmp[0]));
194 unset($check, $tz_tmp);
196 elseif ($zulu_time) {
197 $tz_dtstart = 'Etc/GMT';
199 if (!isset($tz_dtstart)) {
200 $tz_dtstart = event_zonelist_by_id(variable_get('date_default_timezone_id', 487));
201 $tz_dtstart = $tz_dtstart['name'];
203 preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
204 $start_date = $regs[1] .'-'. $regs[2] .'-'. $regs[3];
205 $start_time = $regs[4] .':'. $regs[5] .':00';
211 if (substr($data, -1) == 'Z') {
214 $data = str_replace('T', '', $data);
215 $data = str_replace('Z', '', $data);
216 $field = str_replace(';VALUE=DATE-TIME', '', $field);
217 if ((preg_match("/^DTEND;VALUE=DATE/i", $field)) || (ereg ('^([0-9]{4})([0-9]{2})([0-9]{2})$', $data))) {
219 $end_date = $allday_end;
222 if (preg_match("/^DTEND;TZID=/i", $field)) {
223 $tz_tmp = explode('=', $field);
224 $tz_tmp = explode(':', $tz_tmp[1]);
225 $check = event_zone_by_name($tz_tmp[0]);
226 if ($check['name'] === $tz_tmp[0]) {
227 $tz_dtend = $tz_tmp[0];
230 $messages['error'][] = t('Timezone %tz not found, using default timezone.', array('%tz' => $tz_tmp[0]));
232 unset($check, $tz_tmp);
234 elseif ($zulu_time) {
235 $tz_dtend = 'Etc/GMT';
237 if (!isset($tz_dtend)) {
238 $tz_dtend = event_zonelist_by_id(variable_get('date_default_timezone_id', 487));
239 $tz_dtend = $tz_dtend['name'];
241 preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
242 $end_date = $regs[1] .'-'. $regs[2] .'-'. $regs[3];
243 $end_time = $regs[4] .':'. $regs[5] .':00';
248 $summary = ical_parse_text($field, $data);
251 $description = ical_parse_text($field, $data);
257 $actual_calname = ical_parse_text($field, $data);
259 case 'X-WR-TIMEZONE':
260 $calendar_tz = ical_parse_text($field, $data);
263 $location = ical_parse_text($field, $data);
274 function ical_help($path, $arg) {
276 case 'admin/modules#description':
277 return t('iCalendar API for Events Modules');
283 * escape ical separators in quoted-printable encoded code
285 function ical_quoted_printable_escaped($string) {
286 $replace = array(";" => "\;", ":" => "\:");
287 return strtr(ical_quoted_printable_encode($string), $replace);
291 * encode text using quoted-printable standard
293 function ical_quoted_printable_encode($text, $line_max = 76) {
294 $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
295 $lines = preg_split("/(?:\r\n|\r|\n)/", $text);
297 $linebreak = "=0D=0A";
301 for ($x = 0; $x < count($lines); $x++) {
303 $line_len = strlen($line);
305 for ($i = 0; $i < $line_len; $i++) {
306 $c = substr($line, $i, 1);
308 // convert space at end of line
309 if ( ($dec == 32) && ($i == ($line_len - 1)) ) {
312 // convert tab and special chars
313 elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) {
314 $h2 = floor($dec/16);
315 $h1 = floor($dec%16);
316 $c = $escape . $hex["$h2"] . $hex["$h1"];
318 // see if new output line is needed
319 if ( (strlen($newline) + strlen($c)) >= $line_max ) {
320 $output .= $newline . $escape . $eol;
327 // skip last line feed
328 if ($x < count($lines) - 1) $output .= $linebreak;
330 return trim($output);
334 * From date_ical_parse_text
336 * @param $field the field
337 * @param $text the text
339 * @return formatted text.
341 function ical_parse_text($field, $text) {
342 if (strstr($field, 'QUOTED-PRINTABLE')) {
343 $text = quoted_printable_decode($text);
345 // Strip line breaks within element
346 $text = str_replace(array("\r\n ", "\n ", "\r "), '', $text);
347 // Put in line breaks where encoded
348 $text = str_replace(array("\\n", "\\N"), "\n", $text);
349 // Remove other escaping
350 $text = stripslashes($text);