public function DateiCalParse::parse in Date 8
Returns an array of iCalendar information from an iCalendar file.
As date_ical_import() but different param.
Parameters
array $icaldatafolded: An array of lines from an ical feed.
Return value
array An array with all the elements from the ical.
1 call to DateiCalParse::parse()
- DateiCalParse::import in date_api/
lib/ Drupal/ date_api/ DateiCalParse.php
File
- date_api/
lib/ Drupal/ date_api/ DateiCalParse.php, line 163 - Parse iCal data.
Class
- DateiCalParse
- Return an array of iCalendar information from an iCalendar file.
Namespace
Drupal\date_apiCode
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;
}