[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/date/ -> date_api_ical.inc (source)

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


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7