source: sipes/modules_contrib/date/date_api_ical.inc @ ef72343

stableversion-3.0
Last change on this file since ef72343 was f3eebcf, checked in by José Gregorio Puentes <jpuentes@…>, 8 años ago

se actualizo el modulo

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