/[drupal]/contributions/modules/date/date_api_ical.inc
ViewVC logotype

Contents of /contributions/modules/date/date_api_ical.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.42 - (show annotations) (download) (as text)
Tue Sep 8 08:52:19 2009 UTC (2 months, 3 weeks ago) by karens
Branch: MAIN
CVS Tags: HEAD
Changes since 1.41: +3 -3 lines
File MIME type: text/x-php
Getting HEAD ready for D7.
1 <?php
2 /* $Id: date_api_ical.inc,v 1.41 2009/09/05 09:26:57 karens Exp $ */
3 /**
4 * @file
5 * Parse iCal data.
6 *
7 * This file must be included when these functions are needed.
8 */
9 /**
10 * Return an array of iCalendar information from an iCalendar file.
11 *
12 * No timezone adjustment is performed in the import since the timezone
13 * conversion needed will vary depending on whether the value is being
14 * imported into the database (when it needs to be converted to UTC), is being
15 * viewed on a site that has user-configurable timezones (when it needs to be
16 * converted to the user's timezone), if it needs to be converted to the
17 * site timezone, or if it is a date without a timezone which should not have
18 * any timezone conversion applied.
19 *
20 * Properties that have dates and times are converted to sub-arrays like:
21 * 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
22 * 'all_day' => whether this is an all-day event
23 * 'tz' => the timezone of the date, could be blank for absolute
24 * times that should get no timezone conversion.
25 *
26 * Exception dates can have muliple values and are returned as arrays
27 * like the above for each exception date.
28 *
29 * Most other properties are returned as PROPERTY => VALUE.
30 *
31 * Each item in the VCALENDAR will return an array like:
32 * [0] => Array (
33 * [TYPE] => VEVENT
34 * [UID] => 104
35 * [SUMMARY] => An example event
36 * [URL] => http://example.com/node/1
37 * [DTSTART] => Array (
38 * [datetime] => 1997-09-07 09:00:00
39 * [all_day] => 0
40 * [tz] => US/Eastern
41 * )
42 * [DTEND] => Array (
43 * [datetime] => 1997-09-07 11:00:00
44 * [all_day] => 0
45 * [tz] => US/Eastern
46 * )
47 * [RRULE] => Array (
48 * [FREQ] => Array (
49 * [0] => MONTHLY
50 * )
51 * [BYDAY] => Array (
52 * [0] => 1SU
53 * [1] => -1SU
54 * )
55 * )
56 * [EXDATE] => Array (
57 * [0] = Array (
58 * [datetime] => 1997-09-21 09:00:00
59 * [all_day] => 0
60 * [tz] => US/Eastern
61 * )
62 * [1] = Array (
63 * [datetime] => 1997-10-05 09:00:00
64 * [all_day] => 0
65 * [tz] => US/Eastern
66 * )
67 * )
68 * )
69 *
70 * @param $filename
71 * Location (local or remote) of a valid iCalendar file
72 * @return array
73 * An array with all the elements from the ical
74 * @todo
75 * figure out how to handle this if subgroups are nested,
76 * like a VALARM nested inside a VEVENT.
77 */
78 function date_ical_import($filename) {
79 // Fetch the iCal data. If file is a URL, use drupal_http_request. fopen
80 // isn't always configured to allow network connections.
81 if (substr($filename, 0, 4) == 'http') {
82 // Fetch the ical data from the specified network location
83 $icaldatafetch = drupal_http_request($filename);
84 // Check the return result
85 if ($icaldatafetch->error) {
86 watchdog('date ical', 'HTTP Request Error importing %filename: @error', array('%filename' => $filename, '@error' => $icaldatafetch->error));
87 return FALSE;
88 }
89 // Break the return result into one array entry per lines
90 $icaldatafolded = explode("\n", $icaldatafetch->data);
91 }
92 else {
93 $icaldatafolded = @file($filename, FILE_IGNORE_NEW_LINES);
94 if ($icaldatafolded === FALSE) {
95 watchdog('date ical', 'Failed to open file: %filename', array('%filename' => $filename));
96 return FALSE;
97 }
98 }
99 // Verify this is iCal data
100 if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
101 watchdog('date ical', 'Invalid calendar file: %filename', array('%filename' => $filename));
102 return FALSE;
103 }
104 return date_ical_parse($icaldatafolded);
105 }
106
107 /**
108 * Return an array of iCalendar information from an iCalendar file.
109 *
110 * As date_ical_import() but different param.
111 *
112 * @param $icaldatafolded
113 * an array of lines from an ical feed.
114 * @return array
115 * An array with all the elements from the ical.
116 */
117 function date_ical_parse($icaldatafolded = array()) {
118 $items = array();
119
120 // Verify this is iCal data
121 if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
122 watchdog('date ical', 'Invalid calendar file.');
123 return false;
124 }
125
126
127 // "unfold" wrapped lines
128 $icaldata = array();
129 foreach ($icaldatafolded as $line) {
130 $out = array();
131 // See if this looks like the beginning of a new property or value.
132 // If not, it is a continuation of the previous line.
133 // The regex is to ensure that wrapped QUOTED-PRINTABLE data
134 // is kept intact.
135 if (!preg_match('/([A-Z]+)[:;](.*)/', $line, $out)) {
136 $line = array_pop($icaldata) . ($line);
137 }
138 $icaldata[] = $line;
139 }
140 unset($icaldatafolded);
141
142 // Parse the iCal information
143 $parents = array();
144 $subgroups = array();
145 $vcal = '';
146 foreach ($icaldata as $line) {
147 $line = trim($line);
148 $vcal .= $line . "\n";
149 // Deal with begin/end tags separately
150 if (preg_match('/(BEGIN|END):V(\S+)/', $line, $matches)) {
151 $closure = $matches[1];
152 $type = 'V' . $matches[2];
153 if ($closure == 'BEGIN') {
154 array_push($parents, $type);
155 array_push($subgroups, array());
156 }
157 elseif ($closure == 'END') {
158 end($subgroups);
159 $subgroup =& $subgroups[key($subgroups)];
160 switch ($type) {
161 case 'VCALENDAR':
162 if (prev($subgroups) == FALSE) {
163 $items[] = array_pop($subgroups);
164 }
165 else {
166 $parent[array_pop($parents)][] = array_pop($subgroups);
167 }
168 break;
169 // Add the timezones in with their index their TZID
170 case 'VTIMEZONE':
171 $subgroup = end($subgroups);
172 $id = $subgroup['TZID'];
173 unset($subgroup['TZID']);
174
175 // Append this subgroup onto the one above it
176 prev($subgroups);
177 $parent =& $subgroups[key($subgroups)];
178
179 $parent[$type][$id] = $subgroup;
180
181 array_pop($subgroups);
182 array_pop($parents);
183 break;
184 // Do some fun stuff with durations and all_day events
185 // and then append to parent
186 case 'VEVENT':
187 case 'VALARM':
188 case 'VTODO':
189 case 'VJOURNAL':
190 case 'VVENUE':
191 case 'VFREEBUSY':
192 default:
193 // Can't be sure whether DTSTART is before or after DURATION,
194 // so parse DURATION at the end.
195 if (isset($subgroup['DURATION'])) {
196 date_ical_parse_duration($subgroup, 'DURATION');
197 }
198 // Add a top-level indication for the 'All day' condition.
199 // Leave it in the individual date components, too, so it
200 // is always available even when you are working with only
201 // a portion of the VEVENT array, like in Feed API parsers.
202 $subgroup['all_day'] = FALSE;
203 if (!empty($subgroup['DTSTART']) && (!empty($subgroup['DTSTART']['all_day']) ||
204 (empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT'])))) {
205 // Many programs output DTEND for an all day event as the
206 // following day, reset this to the same day for internal use.
207 $subgroup['all_day'] = TRUE;
208 $subgroup['DTEND'] = $subgroup['DTSTART'];
209 }
210 // Add this element to the parent as an array under the
211 // component name
212 default:
213 prev($subgroups);
214 $parent =& $subgroups[key($subgroups)];
215
216 $parent[$type][] = $subgroup;
217
218 array_pop($subgroups);
219 array_pop($parents);
220 break;
221 }
222 }
223 }
224 // Handle all other possibilities
225 else {
226 // Grab current subgroup
227 end($subgroups);
228 $subgroup =& $subgroups[key($subgroups)];
229
230 // Split up the line into nice pieces for PROPERTYNAME,
231 // PROPERTYATTRIBUTES, and PROPERTYVALUE
232 preg_match('/([^;:]+)(?:;([^:]*))?:(.+)/', $line, $matches);
233 $name = !empty($matches[1]) ? strtoupper(trim($matches[1])) : '';
234 $field = !empty($matches[2]) ? $matches[2] : '';
235 $data = !empty($matches[3]) ? $matches[3] : '';
236 $parse_result = '';
237 switch ($name) {
238 // Keep blank lines out of the results.
239 case '':
240 break;
241
242 // Lots of properties have date values that must be parsed out.
243 case 'CREATED':
244 case 'LAST-MODIFIED':
245 case 'DTSTART':
246 case 'DTEND':
247 case 'DTSTAMP':
248 case 'RDATE':
249 case 'FREEBUSY':
250 case 'DUE':
251 case 'COMPLETED':
252 $parse_result = date_ical_parse_date($field, $data);
253 break;
254
255 case 'EXDATE':
256 $parse_result = date_ical_parse_exceptions($field, $data);
257 break;
258
259 case 'TRIGGER':
260 // A TRIGGER can either be a date or in the form -PT1H.
261 if (!empty($field)) {
262 $parse_result = date_ical_parse_date($field, $data);
263 }
264 else {
265 $parse_result = array('DATA' => $data);
266 }
267 break;
268
269 case 'DURATION':
270 // Can't be sure whether DTSTART is before or after DURATION in
271 // the VEVENT, so store the data and parse it at the end.
272 $parse_result = array('DATA' => $data);
273 break;
274
275 case 'RRULE':
276 case 'EXRULE':
277 $parse_result = date_ical_parse_rrule($field, $data);
278 break;
279
280 case 'SUMMARY':
281 case 'DESCRIPTION':
282 $parse_result = date_ical_parse_text($field, $data);
283 break;
284
285 case 'LOCATION':
286 $parse_result = date_ical_parse_location($field, $data);
287 break;
288
289 // For all other properties, just store the property and the value.
290 // This can be expanded on in the future if other properties should
291 // be given special treatment.
292 default:
293 $parse_result = $data;
294 break;
295 }
296
297 // Store the result of our parsing
298 $subgroup[$name] = $parse_result;
299 }
300 }
301 return $items;
302 }
303
304 /**
305 * Parse a ical date element.
306 *
307 * Possible formats to parse include:
308 * PROPERTY:YYYYMMDD[T][HH][MM][SS][Z]
309 * PROPERTY;VALUE=DATE:YYYYMMDD[T][HH][MM][SS][Z]
310 * PROPERTY;VALUE=DATE-TIME:YYYYMMDD[T][HH][MM][SS][Z]
311 * PROPERTY;TZID=XXXXXXXX;VALUE=DATE:YYYYMMDD[T][HH][MM][SS]
312 * PROPERTY;TZID=XXXXXXXX:YYYYMMDD[T][HH][MM][SS]
313 *
314 * The property and the colon before the date are removed in the import
315 * process above and we are left with $field and $data.
316 *
317 * @param $field
318 * The text before the colon and the date, i.e.
319 * ';VALUE=DATE:', ';VALUE=DATE-TIME:', ';TZID='
320 * @param $data
321 * The date itself, after the colon, in the format YYYYMMDD[T][HH][MM][SS][Z]
322 * 'Z', if supplied, means the date is in UTC.
323 *
324 * @return array
325 * $items array, consisting of:
326 * 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
327 * 'all_day' => whether this is an all-day event with no time
328 * 'tz' => the timezone of the date, could be blank if the ical
329 * has no timezone; the ical specs say no timezone
330 * conversion should be done if no timezone info is
331 * supplied
332 * @todo
333 * Another option for dates is the format PROPERTY;VALUE=PERIOD:XXXX. The period
334 * may include a duration, or a date and a duration, or two dates, so would
335 * have to be split into parts and run through date_ical_parse_date() and
336 * date_ical_parse_duration(). This is not commonly used, so ignored for now.
337 * It will take more work to figure how to support that.
338 */
339 function date_ical_parse_date($field, $data) {
340 $items = array('datetime' => '', 'all_day' => '', 'tz' => '');
341 if (empty($data)) {
342 return $items;
343 }
344 // Make this a little more whitespace independent
345 $data = trim($data);
346
347 // Turn the properties into a nice indexed array of
348 // array(PROPERTYNAME => PROPERTYVALUE);
349 $field_parts = preg_split('/[;:]/', $field);
350 $properties = array();
351 foreach ($field_parts as $part) {
352 if (strpos($part, '=') !== false) {
353 $tmp = explode('=', $part);
354 $properties[$tmp[0]] = $tmp[1];
355 }
356 }
357
358 // Make this a little more whitespace independent
359 $data = trim($data);
360
361 // Record if a time has been found
362 $has_time = FALSE;
363
364 // If a format is specified, parse it according to that format
365 if (isset($properties['VALUE'])) {
366 switch ($properties['VALUE']) {
367 case 'DATE':
368 preg_match(DATE_REGEX_ICAL_DATE, $data, $regs);
369 $datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]); // Date
370 break;
371 case 'DATE-TIME':
372 preg_match(DATE_REGEX_ICAL_DATETIME, $data, $regs);
373 $datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]); // Date
374 $datetime .= ' ' . date_pad($regs[4]) . ':' . date_pad($regs[5]) . ':' . date_pad($regs[6]); // Time
375 $has_time = TRUE;
376 break;
377 }
378 }
379 // If no format is specified, attempt a loose match
380 else {
381 preg_match (DATE_REGEX_LOOSE, $data, $regs);
382 $datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]); // Date
383 if (isset($regs[4])) {
384 $has_time = TRUE;
385 $datetime .= ' ' .date_pad($regs[5]) . ':' . date_pad($regs[6]) . ':' . date_pad($regs[7]); // Time
386 }
387 }
388
389 // Use timezone if explicitly declared
390 if (isset($properties['TZID'])) {
391 $tz = $properties['TZID'];
392 // Fix commonly used alternatives like US-Eastern which should be US/Eastern.
393 $tz = str_replace('-', '/', $tz);
394 // Unset invalid timezone names.
395 if (!date_timezone_is_valid($tz)) {
396 $tz = '';
397 }
398 }
399 // If declared as UTC with terminating 'Z', use that timezone
400 elseif (strpos($data, 'Z') !== FALSE) {
401 $tz = 'UTC';
402 }
403 // Otherwise this date is floating...
404 else {
405 $tz = '';
406 }
407
408 $items['datetime'] = $datetime;
409 $items['all_day'] = $has_time ? FALSE : TRUE;
410 $items['tz'] = $tz;
411 return $items;
412 }
413
414 /**
415 * Parse an ical repeat rule.
416 *
417 * @return array
418 * Array in the form of PROPERTY => array(VALUES)
419 * PROPERTIES include FREQ, INTERVAL, COUNT, BYDAY, BYMONTH, BYYEAR, UNTIL
420 */
421 function date_ical_parse_rrule($field, $data) {
422 $data = str_replace('RRULE:', '', $data);
423 $items = array('DATA' => $data);
424 $rrule = explode(';', $data);
425 foreach ($rrule as $key => $value) {
426 $param = explode('=', $value);
427 // Must be some kind of invalid data.
428 if (count($param) != 2) {
429 continue;
430 }
431 if ($param[0] == 'UNTIL') {
432 $values = date_ical_parse_date('', $param[1]);
433 }
434 else {
435 $values = explode(',', $param[1]);
436 }
437 // Different treatment for items that can have multiple values and those that cannot.
438 if (in_array($param[0], array('FREQ', 'INTERVAL', 'COUNT', 'WKST'))) {
439 $items[$param[0]] = $param[1];
440 }
441 else {
442 $items[$param[0]] = $values;
443 }
444 }
445 return $items;
446 }
447
448 /**
449 * Parse exception dates (can be multiple values).
450 *
451 * @return array
452 * an array of date value arrays.
453 */
454 function date_ical_parse_exceptions($field, $data) {
455 $data = str_replace('EXDATE:', '', $data);
456 $items = array('DATA' => $data);
457 $ex_dates = explode(',', $data);
458 foreach ($ex_dates as $ex_date) {
459 $items[] = date_ical_parse_date('', $ex_date);
460 }
461 return $items;
462 }
463
464 /**
465 * Parse the duration of the event.
466 * Example:
467 * DURATION:PT1H30M
468 * DURATION:P1Y2M
469 *
470 * @param $subgroup
471 * array of other values in the vevent so we can check for DTSTART
472 */
473 function date_ical_parse_duration(&$subgroup, $field = 'DURATION') {
474 $items = $subgroup[$field];
475 $data = $items['DATA'];
476 preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $data, $duration);
477 $items['year'] = isset($duration[1]) ? str_replace('Y', '', $duration[1]) : '';
478 $items['month'] = isset($duration[2]) ?str_replace('M', '', $duration[2]) : '';
479 $items['week'] = isset($duration[3]) ?str_replace('W', '', $duration[3]) : '';
480 $items['day'] = isset($duration[4]) ?str_replace('D', '', $duration[4]) : '';
481 $items['hour'] = isset($duration[6]) ?str_replace('H', '', $duration[6]) : '';
482 $items['minute'] = isset($duration[7]) ?str_replace('M', '', $duration[7]) : '';
483 $items['second'] = isset($duration[8]) ?str_replace('S', '', $duration[8]) : '';
484 $start_date = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['datetime'] : date_format(date_now(), DATE_FORMAT_ISO);
485 $timezone = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['tz'] : variable_get('date_default_timezone', NULL);
486 if (empty($timezone)) {
487 $timezone = 'UTC';
488 }
489 $date = date_make_date($start_date, $timezone);
490 $date2 = clone($date);
491 foreach ($items as $item => $count) {
492 if ($count > 0) {
493 date_modify($date2, '+' . $count . ' ' . $item);
494 }
495 }
496 $format = isset($subgroup['DTSTART']['type']) && $subgroup['DTSTART']['type'] == 'DATE' ? 'Y-m-d' : 'Y-m-d H:i:s';
497 $subgroup['DTEND'] = array(
498 'datetime' => date_format($date2, DATE_FORMAT_DATETIME),
499 'all_day' => isset($subgroup['DTSTART']['all_day']) ? $subgroup['DTSTART']['all_day'] : 0,
500 'tz' => $timezone,
501 );
502 $duration = date_format($date2, 'U') - date_format($date, 'U');
503 $subgroup['DURATION'] = array('DATA' => $data, 'DURATION' => $duration);
504 }
505
506 /**
507 * Parse and clean up ical text elements.
508 */
509 function date_ical_parse_text($field, $data) {
510 if (strstr($field, 'QUOTED-PRINTABLE')) {
511 $data = quoted_printable_decode($data);
512 }
513 // Strip line breaks within element
514 $data = str_replace(array("\r\n ", "\n ", "\r "), '', $data);
515 // Put in line breaks where encoded
516 $data = str_replace(array("\\n", "\\N"), "\n", $data);
517 // Remove other escaping
518 $data = stripslashes($data);
519 return $data;
520 }
521
522 /**
523 * Parse location elements.
524 *
525 * Catch situations like the upcoming.org feed that uses
526 * LOCATION;VENUE-UID="http://upcoming.yahoo.com/venue/104/":111 First Street...
527 * or more normal LOCATION;UID=123:111 First Street...
528 * Upcoming feed would have been improperly broken on the ':' in http://
529 * so we paste the $field and $data back together first.
530 *
531 * Use non-greedy check for ':' in case there are more of them in the address.
532 */
533 function date_ical_parse_location($field, $data) {
534 if (preg_match('/UID=[?"](.+)[?"][*?:](.+)/', $field . ':' . $data, $matches)) {
535 $location = array();
536 $location['UID'] = $matches[1];
537 $location['DESCRIPTION'] = stripslashes($matches[2]);
538 return $location;
539 }
540 else {
541 // Remove other escaping
542 $location = stripslashes($data);
543 return $location;
544 }
545 }
546
547 /**
548 * Return a date object for the ical date, adjusted to its local timezone.
549 *
550 * @param $ical_date
551 * an array of ical date information created in the ical import.
552 * @param $to_tz
553 * the timezone to convert the date's value to.
554 * @return object
555 * a timezone-adjusted date object
556 */
557 function date_ical_date($ical_date, $to_tz = FALSE) {
558 // If the ical date has no timezone, must assume it is stateless
559 // so treat it as a local date.
560 if (empty($ical_date['datetime'])) {
561 return NULL;
562 }
563 elseif (empty($ical_date['tz'])) {
564 $from_tz = date_default_timezone();
565 }
566 else {
567 $from_tz = $ical_date['tz'];
568 }
569 $date = date_make_date($ical_date['datetime'], $from_tz);
570 if ($to_tz && $ical_date['tz'] != '' && $to_tz != $ical_date['tz']) {
571 date_timezone_set($date, timezone_open($to_tz));
572 }
573 return $date;
574 }
575
576 /**
577 * Escape #text elements for safe iCal use
578 *
579 * @param $text
580 * Text to escape
581 *
582 * @return
583 * Escaped text
584 *
585 */
586 function date_ical_escape_text($text) {
587 //$text = strip_tags($text);
588 // TODO Per #38130 the iCal specs don't want : and " escaped
589 // but there was some reason for adding this in. Need to watch
590 // this and see if anything breaks.
591 //$text = str_replace('"', '\"', $text);
592 //$text = str_replace(":", "\:", $text);
593 $text = str_replace("\\", "\\\\", $text);
594 $text = str_replace(",", "\,", $text);
595 $text = str_replace(";", "\;", $text);
596 $text = str_replace("\n", "\n ", $text);
597 return trim($text);
598 }
599
600 /**
601 * Build an iCal RULE from $form_values.
602 *
603 * @param $form_values
604 * an array constructed like the one created by date_ical_parse_rrule()
605 *
606 * [RRULE] => Array (
607 * [FREQ] => Array (
608 * [0] => MONTHLY
609 * )
610 * [BYDAY] => Array (
611 * [0] => 1SU
612 * [1] => -1SU
613 * )
614 * [UNTIL] => Array (
615 * [datetime] => 1997-21-31 09:00:00
616 * [all_day] => 0
617 * [tz] => US/Eastern
618 * )
619 * )
620 * [EXDATE] => Array (
621 * [0] = Array (
622 * [datetime] => 1997-09-21 09:00:00
623 * [all_day] => 0
624 * [tz] => US/Eastern
625 * )
626 * [1] = Array (
627 * [datetime] => 1997-10-05 09:00:00
628 * [all_day] => 0
629 * [tz] => US/Eastern
630 * )
631 * )
632 *
633 */
634 function date_api_ical_build_rrule($form_values) {
635 $RRULE = '';
636 if (empty($form_values) || !is_array($form_values)) {
637 return $RRULE;
638 }
639 //grab the RRULE data and put them into iCal RRULE format
640 $RRULE .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']);
641 $RRULE .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']);
642
643 // Unset the empty 'All' values.
644 if (array_key_exists('BYDAY', $form_values)) unset($form_values['BYDAY']['']);
645 if (array_key_exists('BYMONTH', $form_values)) unset($form_values['BYMONTH']['']);
646 if (array_key_exists('BYMONTHDAY', $form_values)) unset($form_values['BYMONTHDAY']['']);
647
648 if (array_key_exists('BYDAY', $form_values) && $BYDAY = implode(",", $form_values['BYDAY'])) {
649 $RRULE .= ';BYDAY=' . $BYDAY;
650 }
651 if (array_key_exists('BYMONTH', $form_values) && $BYMONTH = implode(",", $form_values['BYMONTH'])) {
652 $RRULE .= ';BYMONTH=' . $BYMONTH;
653 }
654 if (array_key_exists('BYMONTHDAY', $form_values) && $BYMONTHDAY = implode(",", $form_values['BYMONTHDAY'])) {
655 $RRULE .= ';BYMONTHDAY=' . $BYMONTHDAY;
656 }
657 // The UNTIL date is supposed to always be expressed in UTC.
658 if (array_key_exists('UNTIL', $form_values) && array_key_exists('datetime', $form_values['UNTIL']) && !empty($form_values['UNTIL']['datetime'])) {
659 $until = date_ical_date($form_values['UNTIL'], 'UTC');
660 $RRULE .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z';
661 }
662 // Our form doesn't allow a value for COUNT, but it may be needed by
663 // modules using the API, so add it to the rule.
664 if (array_key_exists('COUNT', $form_values)) {
665 $RRULE .= ';COUNT=' . $form_values['COUNT'];
666 }
667
668 // iCal rules presume the week starts on Monday unless otherwise specified,
669 // so we'll specify it.
670 if (array_key_exists('WKST', $form_values)) {
671 $RRULE .= ';WKST=' . $form_values['WKST'];
672 }
673 else {
674 $RRULE .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 1));
675 }
676
677 // Exceptions dates go last, on their own line.
678 if (isset($form_values['EXDATE']) && is_array($form_values['EXDATE'])) {
679 $ex_dates = array();
680 foreach ($form_values['EXDATE'] as $value) {
681 $ex_date = date_convert($value['datetime'], DATE_DATETIME, DATE_ICAL);
682 if (!empty($ex_date)) {
683 $ex_dates[] = $ex_date;
684 }
685 }
686 if (!empty($ex_dates)) {
687 sort($ex_dates);
688 $RRULE .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates);
689 }
690 }
691 elseif (!empty($form_values['EXDATE'])) {
692 $RRULE .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE'];
693 }
694 return $RRULE;
695 }

  ViewVC Help
Powered by ViewVC 1.1.2