| 1 |
<?php
|
| 2 |
/* $Id: ical.inc,v 1.16 2008/09/06 22:20:28 killes Exp $ */
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 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
|
| 8 |
*
|
| 9 |
* This module is IN DEVELOPMENT and not a finished product
|
| 10 |
*/
|
| 11 |
|
| 12 |
/**
|
| 13 |
* Turn an array of events into a valid iCalendar file
|
| 14 |
*
|
| 15 |
* @param $events
|
| 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
|
| 26 |
*
|
| 27 |
* @param $calname
|
| 28 |
* Name of the calendar. Will use site name if none is specified.
|
| 29 |
*
|
| 30 |
* @return
|
| 31 |
* Text of a iCalendar file
|
| 32 |
*/
|
| 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";
|
| 46 |
}
|
| 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";
|
| 50 |
}
|
| 51 |
else if (!empty($event['start_utc'])) {
|
| 52 |
$output .= "DTSTART;VALUE=DATE-TIME:". event_format_date($event['start_utc'], 'custom', "Ymd\THis\Z") ."\n";
|
| 53 |
}
|
| 54 |
$output .= "UID:". ($event['uid'] ? $event['uid'] : $uid) ."\n";
|
| 55 |
if (!empty($event['url'])) {
|
| 56 |
$output .= "URL;VALUE=URI:" . $event['url'] ."\n";
|
| 57 |
}
|
| 58 |
if (!empty($event['location'])) {
|
| 59 |
$output .= "LOCATION:" . ical_escape_text($event['location']) ."\n";
|
| 60 |
}
|
| 61 |
$output .= "SUMMARY:" . ical_escape_text($event['summary']) ."\n";
|
| 62 |
if (!empty($event['description'])) {
|
| 63 |
$output .= "DESCRIPTION:" . ical_escape_text($event['description']) ."\n";
|
| 64 |
}
|
| 65 |
$output .= "END:VEVENT\n";
|
| 66 |
}
|
| 67 |
$output .= "END:VCALENDAR\n";
|
| 68 |
return $output;
|
| 69 |
}
|
| 70 |
|
| 71 |
/**
|
| 72 |
* Escape #text elements for safe iCal use
|
| 73 |
*
|
| 74 |
* @param $text
|
| 75 |
* Text to escape
|
| 76 |
*
|
| 77 |
* @return
|
| 78 |
* Escaped text
|
| 79 |
*
|
| 80 |
*/
|
| 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);
|
| 89 |
return $text;
|
| 90 |
}
|
| 91 |
|
| 92 |
/**
|
| 93 |
* Given the location of a valide iCalendar file, will return an array of event information
|
| 94 |
*
|
| 95 |
* @param $filename
|
| 96 |
* Location (local or remote) of a valid iCalendar file
|
| 97 |
*
|
| 98 |
* @return
|
| 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);
|
| 109 |
$items = array();
|
| 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) {
|
| 115 |
$line = $nextline;
|
| 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);
|
| 122 |
}
|
| 123 |
$line = trim($line);
|
| 124 |
switch ($line) {
|
| 125 |
case 'BEGIN:VEVENT':
|
| 126 |
unset($start_date, $start_time,
|
| 127 |
$end_date, $end_time,
|
| 128 |
$tz_dtstart, $tz_dtend,
|
| 129 |
$allday_start, $allday_end,
|
| 130 |
$uid,
|
| 131 |
$summary,
|
| 132 |
$description,
|
| 133 |
$url,
|
| 134 |
$location
|
| 135 |
);
|
| 136 |
break;
|
| 137 |
case 'END:VEVENT':
|
| 138 |
if (empty($uid)) {
|
| 139 |
$uid = $uid_counter;
|
| 140 |
$uid_counter++;
|
| 141 |
}
|
| 142 |
|
| 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,
|
| 152 |
'url' => $url,
|
| 153 |
'uid' => $uid);
|
| 154 |
break;
|
| 155 |
default:
|
| 156 |
unset($field, $data, $prop_pos, $property);
|
| 157 |
ereg("([^:]+):(.*)", $line, $line);
|
| 158 |
$field = $line[1];
|
| 159 |
$data = $line[2];
|
| 160 |
|
| 161 |
$property = $field;
|
| 162 |
$prop_pos = strpos($property, ';');
|
| 163 |
if ($prop_pos !== FALSE) {
|
| 164 |
$property = substr($property, 0, $prop_pos);
|
| 165 |
}
|
| 166 |
$property = strtoupper($property);
|
| 167 |
|
| 168 |
switch ($property) {
|
| 169 |
case 'DTSTART':
|
| 170 |
$zulu_time = FALSE;
|
| 171 |
if (substr($data, -1) == 'Z') {
|
| 172 |
$zulu_time = TRUE;
|
| 173 |
}
|
| 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;
|
| 181 |
}
|
| 182 |
else {
|
| 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]);
|
| 187 |
// Found TZ
|
| 188 |
if ($check['name'] === $tz_tmp[0]) {
|
| 189 |
$tz_dtstart = $tz_tmp[0];
|
| 190 |
}
|
| 191 |
else {
|
| 192 |
$messages['error'][] = t('Timezone %tz not found, using default timezone.', array('%tz' => $tz_tmp[0]));
|
| 193 |
}
|
| 194 |
unset($check, $tz_tmp);
|
| 195 |
}
|
| 196 |
elseif ($zulu_time) {
|
| 197 |
$tz_dtstart = 'Etc/GMT';
|
| 198 |
}
|
| 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'];
|
| 202 |
}
|
| 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';
|
| 206 |
unset($regs);
|
| 207 |
}
|
| 208 |
break;
|
| 209 |
case 'DTEND':
|
| 210 |
$zulu_time = FALSE;
|
| 211 |
if (substr($data, -1) == 'Z') {
|
| 212 |
$zulu_time = TRUE;
|
| 213 |
}
|
| 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))) {
|
| 218 |
$allday_end = $data;
|
| 219 |
$end_date = $allday_end;
|
| 220 |
}
|
| 221 |
else {
|
| 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];
|
| 228 |
}
|
| 229 |
else {
|
| 230 |
$messages['error'][] = t('Timezone %tz not found, using default timezone.', array('%tz' => $tz_tmp[0]));
|
| 231 |
}
|
| 232 |
unset($check, $tz_tmp);
|
| 233 |
}
|
| 234 |
elseif ($zulu_time) {
|
| 235 |
$tz_dtend = 'Etc/GMT';
|
| 236 |
}
|
| 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'];
|
| 240 |
}
|
| 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';
|
| 244 |
unset($regs);
|
| 245 |
}
|
| 246 |
break;
|
| 247 |
case 'SUMMARY':
|
| 248 |
$summary = ical_parse_text($field, $data);
|
| 249 |
break;
|
| 250 |
case 'DESCRIPTION':
|
| 251 |
$description = ical_parse_text($field, $data);
|
| 252 |
break;
|
| 253 |
case 'UID':
|
| 254 |
$uid = $data;
|
| 255 |
break;
|
| 256 |
case 'X-WR-CALNAME':
|
| 257 |
$actual_calname = ical_parse_text($field, $data);
|
| 258 |
break;
|
| 259 |
case 'X-WR-TIMEZONE':
|
| 260 |
$calendar_tz = ical_parse_text($field, $data);
|
| 261 |
break;
|
| 262 |
case 'LOCATION':
|
| 263 |
$location = ical_parse_text($field, $data);
|
| 264 |
break;
|
| 265 |
case 'URL':
|
| 266 |
$url = $data;
|
| 267 |
break;
|
| 268 |
}
|
| 269 |
}
|
| 270 |
}
|
| 271 |
return $items;
|
| 272 |
}
|
| 273 |
|
| 274 |
function ical_help($path, $arg) {
|
| 275 |
switch ($path) {
|
| 276 |
case 'admin/modules#description':
|
| 277 |
return t('iCalendar API for Events Modules');
|
| 278 |
break;
|
| 279 |
}
|
| 280 |
}
|
| 281 |
|
| 282 |
/**
|
| 283 |
* escape ical separators in quoted-printable encoded code
|
| 284 |
*/
|
| 285 |
function ical_quoted_printable_escaped($string) {
|
| 286 |
$replace = array(";" => "\;", ":" => "\:");
|
| 287 |
return strtr(ical_quoted_printable_encode($string), $replace);
|
| 288 |
}
|
| 289 |
|
| 290 |
/**
|
| 291 |
* encode text using quoted-printable standard
|
| 292 |
*/
|
| 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);
|
| 296 |
$eol = "\r\n";
|
| 297 |
$linebreak = "=0D=0A";
|
| 298 |
$escape = "=";
|
| 299 |
$output = "";
|
| 300 |
|
| 301 |
for ($x = 0; $x < count($lines); $x++) {
|
| 302 |
$line = $lines[$x];
|
| 303 |
$line_len = strlen($line);
|
| 304 |
$newline = "";
|
| 305 |
for ($i = 0; $i < $line_len; $i++) {
|
| 306 |
$c = substr($line, $i, 1);
|
| 307 |
$dec = ord($c);
|
| 308 |
// convert space at end of line
|
| 309 |
if ( ($dec == 32) && ($i == ($line_len - 1)) ) {
|
| 310 |
$c = $escape ."20";
|
| 311 |
}
|
| 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"];
|
| 317 |
}
|
| 318 |
// see if new output line is needed
|
| 319 |
if ( (strlen($newline) + strlen($c)) >= $line_max ) {
|
| 320 |
$output .= $newline . $escape . $eol;
|
| 321 |
$newline = "";
|
| 322 |
}
|
| 323 |
$newline .= $c;
|
| 324 |
}
|
| 325 |
$output .= $newline;
|
| 326 |
|
| 327 |
// skip last line feed
|
| 328 |
if ($x < count($lines) - 1) $output .= $linebreak;
|
| 329 |
}
|
| 330 |
return trim($output);
|
| 331 |
}
|
| 332 |
|
| 333 |
/**
|
| 334 |
* From date_ical_parse_text
|
| 335 |
*
|
| 336 |
* @param $field the field
|
| 337 |
* @param $text the text
|
| 338 |
*
|
| 339 |
* @return formatted text.
|
| 340 |
*/
|
| 341 |
function ical_parse_text($field, $text) {
|
| 342 |
if (strstr($field, 'QUOTED-PRINTABLE')) {
|
| 343 |
$text = quoted_printable_decode($text);
|
| 344 |
}
|
| 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);
|
| 351 |
return $text;
|
| 352 |
}
|
| 353 |
|
| 354 |
|