DateiCalParse.php in Date 8
Parse iCal data.
Namespace
Drupal\date_apiFile
date_api/lib/Drupal/date_api/DateiCalParse.phpView source
<?php
/**
* @file
* Parse iCal data.
*/
namespace Drupal\date_api;
use Drupal\Core\Datetime\DrupalDateTime;
/**
* Return an array of iCalendar information from an iCalendar file.
*
* No timezone adjustment is performed in the import since the timezone
* conversion needed will vary depending on whether the value is being
* imported into the database (when it needs to be converted to UTC), is being
* viewed on a site that has user-configurable timezones (when it needs to be
* converted to the user's timezone), if it needs to be converted to the
* site timezone, or if it is a date without a timezone which should not have
* any timezone conversion applied.
*
* Properties that have dates and times are converted to sub-arrays like:
* 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
* 'all_day' => whether this is an all-day event
* 'tz' => the timezone of the date, could be blank for absolute
* times that should get no timezone conversion.
*
* Exception dates can have muliple values and are returned as arrays
* like the above for each exception date.
*
* Most other properties are returned as PROPERTY => VALUE.
*
* Each item in the VCALENDAR will return an array like:
* [0] => Array (
* [TYPE] => VEVENT
* [UID] => 104
* [SUMMARY] => An example event
* [URL] => http://example.com/node/1
* [DTSTART] => Array (
* [datetime] => 1997-09-07 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [DTEND] => Array (
* [datetime] => 1997-09-07 11:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [RRULE] => Array (
* [FREQ] => Array (
* [0] => MONTHLY
* )
* [BYDAY] => Array (
* [0] => 1SU
* [1] => -1SU
* )
* )
* [EXDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [RDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* )
*
* @todo
* figure out how to handle this if subgroups are nested,
* like a VALARM nested inside a VEVENT.
*
* @param string $filename
* Location (local or remote) of a valid iCalendar file.
*
* @return array
* An array with all the elements from the ical.
*/
class DateiCalParse {
/**
* A regex string that will extract date parts from an ical date.
*/
public static $regex_ical_date = '/(\\d{4})(\\d{2})(\\d{2})/';
/**
* A regex string that will extract date and time parts from an ical datetime.
*/
public static $regex_ical_datetime = '/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})(Z)?/';
/**
* A regex string that will extract date and time parts from either
* a datetime string or an iso string, with or without missing date
* and time values.
*/
public static $regex_loose = '/(\\d{4})-?(\\d{1,2})-?(\\d{1,2})([T\\s]?(\\d{2}):?(\\d{2}):?(\\d{2})?(\\.\\d+)?(Z|[\\+\\-]\\d{2}:?\\d{2})?)?/';
/**
* The start day of the week.
*/
public static $week_start_day = 'MO';
/**
* The name of the timezone to use in these computations.
*/
public static $timezone_name = 'UTC';
public function import($filename) {
// Fetch the iCal data. If file is a URL, use drupal_http_request. fopen
// isn't always configured to allow network connections.
if (substr($filename, 0, 4) == 'http') {
// Fetch the ical data from the specified network location.
$icaldatafetch = drupal_http_request($filename);
// Check the return result.
if ($icaldatafetch->error) {
watchdog('date ical', 'HTTP Request Error importing %filename: @error', array(
'%filename' => $filename,
'@error' => $icaldatafetch->error,
));
return FALSE;
}
// Break the return result into one array entry per lines.
$icaldatafolded = explode("\n", $icaldatafetch->data);
}
else {
$icaldatafolded = @file($filename, FILE_IGNORE_NEW_LINES);
if ($icaldatafolded === FALSE) {
watchdog('date ical', 'Failed to open file: %filename', array(
'%filename' => $filename,
));
return FALSE;
}
}
// Verify this is iCal data.
if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
watchdog('date ical', 'Invalid calendar file: %filename', array(
'%filename' => $filename,
));
return FALSE;
}
return $this
->parse($icaldatafolded);
}
/**
* Returns an array of iCalendar information from an iCalendar file.
*
* As date_ical_import() but different param.
*
* @param array $icaldatafolded
* An array of lines from an ical feed.
*
* @return array
* An array with all the elements from the ical.
*/
public function parse($icaldatafolded = array()) {
$items = array();
// Verify this is iCal data.
if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
watchdog('date ical', 'Invalid calendar file.');
return FALSE;
}
// "Unfold" wrapped lines.
$icaldata = array();
foreach ($icaldatafolded as $line) {
$out = array();
// See if this looks like the beginning of a new property or value. If not,
// it is a continuation of the previous line. The regex is to ensure that
// wrapped QUOTED-PRINTABLE data is kept intact.
if (!preg_match('/([A-Z]+)[:;](.*)/', $line, $out)) {
// Trim up to 1 leading space from wrapped line per iCalendar standard.
$line = array_pop($icaldata) . (ltrim(substr($line, 0, 1)) . substr($line, 1));
}
$icaldata[] = $line;
}
unset($icaldatafolded);
// Parse the iCal information.
$parents = array();
$subgroups = array();
$vcal = '';
foreach ($icaldata as $line) {
$line = trim($line);
$vcal .= $line . "\n";
// Deal with begin/end tags separately.
if (preg_match('/(BEGIN|END):V(\\S+)/', $line, $matches)) {
$closure = $matches[1];
$type = 'V' . $matches[2];
if ($closure == 'BEGIN') {
array_push($parents, $type);
array_push($subgroups, array());
}
elseif ($closure == 'END') {
end($subgroups);
$subgroup =& $subgroups[key($subgroups)];
switch ($type) {
case 'VCALENDAR':
if (prev($subgroups) == FALSE) {
$items[] = array_pop($subgroups);
}
else {
$parent[array_pop($parents)][] = array_pop($subgroups);
}
break;
// Add the timezones in with their index their TZID.
case 'VTIMEZONE':
$subgroup = end($subgroups);
$id = $subgroup['TZID'];
unset($subgroup['TZID']);
// Append this subgroup onto the one above it.
prev($subgroups);
$parent =& $subgroups[key($subgroups)];
$parent[$type][$id] = $subgroup;
array_pop($subgroups);
array_pop($parents);
break;
// Do some fun stuff with durations and all_day events and then append
// to parent.
case 'VEVENT':
case 'VALARM':
case 'VTODO':
case 'VJOURNAL':
case 'VVENUE':
case 'VFREEBUSY':
default:
// Can't be sure whether DTSTART is before or after DURATION, so
// parse DURATION at the end.
if (isset($subgroup['DURATION'])) {
self::parse_duration($subgroup, 'DURATION');
}
// Add a top-level indication for the 'All day' condition. Leave it
// in the individual date components, too, so it is always available
// even when you are working with only a portion of the VEVENT
// array, like in Feed API parsers.
$subgroup['all_day'] = FALSE;
// iCal spec states 'The "DTEND" property for a "VEVENT" calendar
// component specifies the non-inclusive end of the event'. Adjust
// multi-day events to remove the extra day because the Date code
// assumes the end date is inclusive.
if (!empty($subgroup['DTEND']) && !empty($subgroup['DTEND']['all_day'])) {
// Make the end date one day earlier.
$date = new DrupalDateTime($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']);
date_modify($date, '-1 day');
$subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d');
}
// If a start datetime is defined AND there is no definition for
// the end datetime THEN make the end datetime equal the start
// datetime and if it is an all day event define the entire event
// as a single all day event.
if (!empty($subgroup['DTSTART']) && (empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT']))) {
$subgroup['DTEND'] = $subgroup['DTSTART'];
}
// Add this element to the parent as an array under the component
// name.
if (!empty($subgroup['DTSTART']['all_day'])) {
$subgroup['all_day'] = TRUE;
}
// Add this element to the parent as an array under the
prev($subgroups);
$parent =& $subgroups[key($subgroups)];
$parent[$type][] = $subgroup;
array_pop($subgroups);
array_pop($parents);
break;
}
}
}
else {
// Grab current subgroup.
end($subgroups);
$subgroup =& $subgroups[key($subgroups)];
// Split up the line into nice pieces for PROPERTYNAME,
// PROPERTYATTRIBUTES, and PROPERTYVALUE.
preg_match('/([^;:]+)(?:;([^:]*))?:(.+)/', $line, $matches);
$name = !empty($matches[1]) ? strtoupper(trim($matches[1])) : '';
$field = !empty($matches[2]) ? $matches[2] : '';
$data = !empty($matches[3]) ? $matches[3] : '';
$parse_result = '';
switch ($name) {
// Keep blank lines out of the results.
case '':
break;
// Lots of properties have date values that must be parsed out.
case 'CREATED':
case 'LAST-MODIFIED':
case 'DTSTART':
case 'DTEND':
case 'DTSTAMP':
case 'FREEBUSY':
case 'DUE':
case 'COMPLETED':
$parse_result = self::parse_date($data, $field);
break;
case 'EXDATE':
case 'RDATE':
$parse_result = self::parse_exceptions($data, $field);
break;
case 'TRIGGER':
// A TRIGGER can either be a date or in the form -PT1H.
if (!empty($field)) {
$parse_result = self::parse_date($data, $field);
}
else {
$parse_result = array(
'DATA' => $data,
);
}
break;
case 'DURATION':
// Can't be sure whether DTSTART is before or after DURATION in
// the VEVENT, so store the data and parse it at the end.
$parse_result = array(
'DATA' => $data,
);
break;
case 'RRULE':
case 'EXRULE':
$parse_result = self::parse_rrule($data, $field);
break;
case 'STATUS':
case 'SUMMARY':
case 'DESCRIPTION':
$parse_result = self::parse_text($data, $field);
break;
case 'LOCATION':
$parse_result = self::parse_location($data, $field);
break;
// For all other properties, just store the property and the value.
// This can be expanded on in the future if other properties should
// be given special treatment.
default:
$parse_result = $data;
break;
}
// Store the result of our parsing.
$subgroup[$name] = $parse_result;
}
}
return $items;
}
/**
* Parses a ical date element.
*
* Possible formats to parse include:
* PROPERTY:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;VALUE=DATE:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;VALUE=DATE-TIME:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;TZID=XXXXXXXX;VALUE=DATE:YYYYMMDD[T][HH][MM][SS]
* PROPERTY;TZID=XXXXXXXX:YYYYMMDD[T][HH][MM][SS]
*
* The property and the colon before the date are removed in the import
* process above and we are left with $field and $data.
*
* @param string $field
* The text before the colon and the date, i.e.
* ';VALUE=DATE:', ';VALUE=DATE-TIME:', ';TZID='
* @param string $data
* The date itself, after the colon, in the format YYYYMMDD[T][HH][MM][SS][Z]
* 'Z', if supplied, means the date is in UTC.
*
* @return array
* $items array, consisting of:
* 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
* 'all_day' => whether this is an all-day event with no time
* 'tz' => the timezone of the date, could be blank if the ical
* has no timezone; the ical specs say no timezone
* conversion should be done if no timezone info is
* supplied
* @todo
* Another option for dates is the format PROPERTY;VALUE=PERIOD:XXXX. The
* period may include a duration, or a date and a duration, or two dates, so
* would have to be split into parts and run through date_ical_parse_date()
* and date_ical_parse_duration(). This is not commonly used, so ignored for
* now. It will take more work to figure how to support that.
*/
public static function parse_date($data, $field = 'DATE:') {
$items = array(
'datetime' => '',
'all_day' => '',
'tz' => '',
);
if (empty($data)) {
return $items;
}
// Make this a little more whitespace independent.
$data = trim($data);
// Turn the properties into a nice indexed array of
// array(PROPERTYNAME => PROPERTYVALUE);
$field_parts = preg_split('/[;:]/', $field);
$properties = array();
foreach ($field_parts as $part) {
if (strpos($part, '=') !== FALSE) {
$tmp = explode('=', $part);
$properties[$tmp[0]] = $tmp[1];
}
}
// Make this a little more whitespace independent.
$data = trim($data);
// Record if a time has been found.
$has_time = FALSE;
// If a format is specified, parse it according to that format.
if (isset($properties['VALUE'])) {
switch ($properties['VALUE']) {
case 'DATE':
preg_match(self::$regex_ical_date, $data, $regs);
// Date.
$datetime = DrupalDateTime::datePad($regs[1]) . '-' . DrupalDateTime::datePad($regs[2]) . '-' . DrupalDateTime::datePad($regs[3]);
break;
case 'DATE-TIME':
preg_match(self::$regex_ical_datetime, $data, $regs);
// Date.
$datetime = DrupalDateTime::datePad($regs[1]) . '-' . DrupalDateTime::datePad($regs[2]) . '-' . DrupalDateTime::datePad($regs[3]);
// Time.
$datetime .= ' ' . DrupalDateTime::datePad($regs[4]) . ':' . DrupalDateTime::datePad($regs[5]) . ':' . DrupalDateTime::datePad($regs[6]);
$has_time = TRUE;
break;
}
}
else {
preg_match(self::$regex_loose, $data, $regs);
if (!empty($regs) && count($regs) > 2) {
// Date.
$datetime = DrupalDateTime::datePad($regs[1]) . '-' . DrupalDateTime::datePad($regs[2]) . '-' . DrupalDateTime::datePad($regs[3]);
if (isset($regs[4])) {
$has_time = TRUE;
// Time.
$datetime .= ' ' . (!empty($regs[5]) ? DrupalDateTime::datePad($regs[5]) : '00') . ':' . (!empty($regs[6]) ? DrupalDateTime::datePad($regs[6]) : '00') . ':' . (!empty($regs[7]) ? DrupalDateTime::datePad($regs[7]) : '00');
}
}
}
// Use timezone if explicitly declared.
if (isset($properties['TZID'])) {
$tz = $properties['TZID'];
// Fix alternatives like US-Eastern which should be US/Eastern.
$tz = str_replace('-', '/', $tz);
// Unset invalid timezone names.
module_load_include('inc', 'date_api', 'date_api.admin');
$tz = _date_timezone_replacement($tz);
if (!in_array($tz, array_keys(system_time_zones()))) {
$tz = '';
}
}
elseif (strpos($data, 'Z') !== FALSE) {
$tz = 'UTC';
}
else {
$tz = '';
}
$items['datetime'] = $datetime;
$items['all_day'] = $has_time ? FALSE : TRUE;
$items['tz'] = $tz;
return $items;
}
/**
* Parse exception dates (can be multiple values).
*
* @return array
* an array of date value arrays.
*/
public static function parse_exceptions($data, $field = 'EXDATE:') {
$data = str_replace($field . ':', '', $data);
$items = array(
'DATA' => $data,
);
$ex_dates = explode(',', $data);
foreach ($ex_dates as $ex_date) {
$items[] = self::parse_date($ex_date);
}
return $items;
}
/**
* Parses the duration of the event.
*
* Example:
* DURATION:PT1H30M
* DURATION:P1Y2M
*
* @param array $subgroup
* Array of other values in the vevent so we can check for DTSTART.
*/
public static function parse_duration(&$subgroup, $field = 'DURATION') {
$items = $subgroup[$field];
$data = $items['DATA'];
$interval = new \DateInterval($data);
$start_date = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['datetime'] : date_format(new DrupalDateTime(), DATE_FORMAT_ISO);
$timezone = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['tz'] : $this->timezone_name;
if (empty($timezone)) {
$timezone = 'UTC';
}
$date = new DrupalDateTime($start_date, $timezone);
$date2 = clone $date;
$date2
->add($interval);
$format = isset($subgroup['DTSTART']['type']) && $subgroup['DTSTART']['type'] == 'DATE' ? 'Y-m-d' : 'Y-m-d H:i:s';
$subgroup['DTEND'] = array(
'datetime' => date_format($date2, DATE_FORMAT_DATETIME),
'all_day' => isset($subgroup['DTSTART']['all_day']) ? $subgroup['DTSTART']['all_day'] : 0,
'tz' => $timezone,
);
$duration = date_format($date2, 'U') - date_format($date, 'U');
$subgroup['DURATION'] = array(
'DATA' => $data,
'DURATION' => $duration,
);
}
/**
* Parse and clean up ical text elements.
*/
public static function parse_text($data, $field = '') {
if (strstr($field, 'QUOTED-PRINTABLE')) {
$data = quoted_printable_decode($data);
}
// Strip line breaks within element.
$data = str_replace(array(
"\r\n ",
"\n ",
"\r ",
), '', $data);
// Put in line breaks where encoded.
$data = str_replace(array(
"\\n",
"\\N",
), "\n", $data);
// Remove other escaping.
$data = stripslashes($data);
return $data;
}
/**
* Parse location elements.
*
* Catch situations like the upcoming.org feed that uses
* LOCATION;VENUE-UID="http://upcoming.yahoo.com/venue/104/":First Street..
* or more normal LOCATION;UID=123:111 First Street...
* Upcoming feed would have been improperly broken on the ':' in http://
* so we paste the $field and $data back together first.
*
* Use non-greedy check for ':' in case there are more of them in the address.
*/
public static function parse_location($data, $field = 'LOCATION:') {
if (preg_match('/UID=[?"](.+)[?"][*?:](.+)/', $field . ':' . $data, $matches)) {
$location = array();
$location['UID'] = $matches[1];
$location['DESCRIPTION'] = stripslashes($matches[2]);
return $location;
}
else {
// Remove other escaping.
$location = stripslashes($data);
return $location;
}
}
/**
* Return a date object for the ical date, adjusted to its local timezone.
*
* @param array $ical_date
* An array of ical date information created in the ical import.
* @param string $to_tz
* The timezone to convert the date's value to.
*
* @return object
* A timezone-adjusted date object.
*/
public static function ical_date($ical_date, $to_tz = FALSE) {
// If the ical date has no timezone, must assume it is stateless
// so treat it as a local date.
if (empty($ical_date['datetime'])) {
return NULL;
}
elseif (empty($ical_date['tz'])) {
$from_tz = drupal_get_user_timezone();
}
else {
$from_tz = $ical_date['tz'];
}
if (strlen($ical_date['datetime']) < 11) {
$ical_date['datetime'] .= ' 00:00:00';
}
$date = new DrupalDateTime($ical_date['datetime'], new \DateTimeZone($from_tz));
if ($to_tz && $ical_date['tz'] != '' && $to_tz != $ical_date['tz']) {
date_timezone_set($date, timezone_open($to_tz));
}
return $date;
}
/**
* Escape #text elements for safe iCal use.
*
* @param string $text
* Text to escape
*
* @return string
* Escaped text
*
*/
public static function escape_text($text) {
$text = drupal_html_to_text($text);
$text = trim($text);
// TODO Per #38130 the iCal specs don't want : and " escaped
// but there was some reason for adding this in. Need to watch
// this and see if anything breaks.
// $text = str_replace('"', '\"', $text);
// $text = str_replace(":", "\:", $text);
$text = preg_replace("/\\\\b/", "\\\\", $text);
$text = str_replace(",", "\\,", $text);
$text = str_replace(";", "\\;", $text);
$text = str_replace("\n", "\\n ", $text);
return trim($text);
}
/**
* Explode a multiline entry into its lines.
*/
public static function split_multiline_entry($entry) {
return explode("\n", str_replace("\r\n", "\n", $entry));
}
/**
* Extract the RRULE from a multiline entry.
*/
public static function extract_rrule_string($rrule) {
$parts = self::split_multiline_entry($rrule);
foreach ($parts as $part) {
if (strstr($part, 'RRULE')) {
$rrule = $part;
}
}
return $rrule;
}
/**
* Parse an iCal rule into a parsed RRULE array, along with EXDATE
* and RDATE arrays.
*/
public static function split_rrule($rrule) {
$parts = self::split_multiline_entry($rrule);
$rrule = array();
$exceptions = array();
$additions = array();
$additions = array();
foreach ($parts as $part) {
if (strstr($part, 'RRULE')) {
$RRULE = str_replace('RRULE:', '', $part);
$rrule = (array) self::parse_rrule($RRULE);
}
elseif (strstr($part, 'EXDATE')) {
$EXDATE = str_replace('EXDATE:', '', $part);
$exceptions = (array) DateiCalParse::parse_exceptions($EXDATE);
unset($exceptions['DATA']);
}
elseif (strstr($part, 'RDATE')) {
$RDATE = str_replace('RDATE:', '', $part);
$additions = (array) DateiCalParse::parse_exceptions($RDATE);
unset($additions['DATA']);
}
}
return array(
$rrule,
$exceptions,
$additions,
);
}
/**
* Parse an ical repeat rule.
*
* @return array
* Array in the form of PROPERTY => array(VALUES)
* PROPERTIES are FREQ, INTERVAL, COUNT, BYDAY, BYMONTH, BYYEAR, UNTIL
*/
public static function parse_rrule($data, $field = 'RRULE:') {
$data = preg_replace("/RRULE.*:/", '', $data);
$items = array(
'DATA' => $data,
);
$rrule = explode(';', $data);
foreach ($rrule as $key => $value) {
$param = explode('=', $value);
// Must be some kind of invalid data.
if (count($param) != 2) {
continue;
}
if ($param[0] == 'UNTIL') {
$values = self::parse_date($param[1]);
}
else {
$values = explode(',', $param[1]);
}
// Treat items differently if they have multiple or single values.
if (in_array($param[0], array(
'FREQ',
'INTERVAL',
'COUNT',
'WKST',
))) {
$items[$param[0]] = $param[1];
}
else {
$items[$param[0]] = $values;
}
}
return $items;
}
/**
* The reverse of the parse_rrule() function.
*
* Build a string RRULE from the array structure created by parsing
* a RRule.
*
* @param array $ical_array
* An array constructed like the one created by parse_rrule().
* [RRULE] => Array (
* [FREQ] => Array (
* [0] => MONTHLY
* )
* [BYDAY] => Array (
* [0] => 1SU
* [1] => -1SU
* )
* [UNTIL] => Array (
* [datetime] => 1997-21-31 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [EXDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [RDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
*/
public static function build_rrule($ical_array) {
$RRULE = '';
if (empty($ical_array) || !is_array($ical_array)) {
return $RRULE;
}
// Grab the RRULE data and put them into iCal RRULE format.
$RRULE .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $ical_array) ? 'DAILY' : $ical_array['FREQ']);
$RRULE .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $ical_array) ? 1 : $ical_array['INTERVAL']);
// Unset the empty 'All' values.
if (array_key_exists('BYDAY', $ical_array) && is_array($ical_array['BYDAY'])) {
unset($ical_array['BYDAY']['']);
}
if (array_key_exists('BYMONTH', $ical_array) && is_array($ical_array['BYMONTH'])) {
unset($ical_array['BYMONTH']['']);
}
if (array_key_exists('BYMONTHDAY', $ical_array) && is_array($ical_array['BYMONTHDAY'])) {
unset($ical_array['BYMONTHDAY']['']);
}
if (array_key_exists('BYDAY', $ical_array) && is_array($ical_array['BYDAY']) && ($BYDAY = implode(",", $ical_array['BYDAY']))) {
$RRULE .= ';BYDAY=' . $BYDAY;
}
if (array_key_exists('BYMONTH', $ical_array) && is_array($ical_array['BYMONTH']) && ($BYMONTH = implode(",", $ical_array['BYMONTH']))) {
$RRULE .= ';BYMONTH=' . $BYMONTH;
}
if (array_key_exists('BYMONTHDAY', $ical_array) && is_array($ical_array['BYMONTHDAY']) && ($BYMONTHDAY = implode(",", $ical_array['BYMONTHDAY']))) {
$RRULE .= ';BYMONTHDAY=' . $BYMONTHDAY;
}
// The UNTIL date is supposed to always be expressed in UTC.
// The input date values may already have been converted to a
// date object on a previous pass, so check for that.
if (array_key_exists('UNTIL', $ical_array) && array_key_exists('datetime', $ical_array['UNTIL']) && !empty($ical_array['UNTIL']['datetime'])) {
// We only collect a date for UNTIL, but we need it to be
// inclusive, so force it to a full datetime element at the
// last second of the day.
if (!$ical_array['UNTIL']['datetime'] instanceof DrupalDateTime) {
// If this is a date without time, give it time.
if (strlen($ical_array['UNTIL']['datetime']) < 11) {
$ical_array['UNTIL']['datetime'] .= ' 23:59:59';
$ical_array['UNTIL']['granularity'] = serialize(drupal_map_assoc(array(
'year',
'month',
'day',
'hour',
'minute',
'second',
)));
$ical_array['UNTIL']['all_day'] = FALSE;
}
$until = self::ical_date($ical_array['UNTIL'], 'UTC');
}
else {
$until = $ical_array['UNTIL']['datetime'];
}
$RRULE .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z';
}
// Our form doesn't allow a value for COUNT, but it may be needed by
// modules using the API, so add it to the rule.
if (array_key_exists('COUNT', $ical_array)) {
$RRULE .= ';COUNT=' . $ical_array['COUNT'];
}
// iCal rules presume the week starts on Monday unless otherwise
// specified, so we'll specify it.
if (array_key_exists('WKST', $ical_array)) {
$RRULE .= ';WKST=' . $ical_array['WKST'];
}
else {
$RRULE .= ';WKST=' . self::$week_start_day;
}
// Exceptions dates go last, on their own line.
// The input date values may already have been converted to a date
// object on a previous pass, so check for that.
if (isset($ical_array['EXDATE']) && is_array($ical_array['EXDATE'])) {
$ex_dates = array();
foreach ($ical_array['EXDATE'] as $value) {
if (!empty($value['datetime'])) {
$date = !$value['datetime'] instanceof DrupalDateTime ? self::ical_date($value, 'UTC') : $value['datetime'];
$ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z' : '';
if (!empty($ex_date)) {
$ex_dates[] = $ex_date;
}
}
}
if (!empty($ex_dates)) {
sort($ex_dates);
$RRULE .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates);
}
}
elseif (!empty($ical_array['EXDATE'])) {
$RRULE .= chr(13) . chr(10) . 'EXDATE:' . $ical_array['EXDATE'];
}
// Exceptions dates go last, on their own line.
if (isset($ical_array['RDATE']) && is_array($ical_array['RDATE'])) {
$ex_dates = array();
foreach ($ical_array['RDATE'] as $value) {
$date = !$value['datetime'] instanceof DrupalDateTime ? self::ical_date($value, 'UTC') : $value['datetime'];
$ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z' : '';
if (!empty($ex_date)) {
$ex_dates[] = $ex_date;
}
}
if (!empty($ex_dates)) {
sort($ex_dates);
$RRULE .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates);
}
}
elseif (!empty($ical_array['RDATE'])) {
$RRULE .= chr(13) . chr(10) . 'RDATE:' . $ical_array['RDATE'];
}
return $RRULE;
}
}
Classes
Name | Description |
---|---|
DateiCalParse | Return an array of iCalendar information from an iCalendar file. |