public function parsevCalendar($text, $base = 'VCALENDAR', $clear = true)
{
if ($clear) {
$this->clear();
}
$text = Horde_String::trimUtf8Bom($text);
if (preg_match('/^BEGIN:' . $base . '(.*)^END:' . $base . '/ism', $text, $matches)) {
$container = true;
$vCal = $matches[1];
} else {
// Text isn't enclosed in BEGIN:VCALENDAR
// .. END:VCALENDAR. We'll try to parse it anyway.
$container = false;
$vCal = $text;
}
$vCal = trim($vCal);
// Extract all subcomponents.
$matches = $components = null;
if (preg_match_all('/^BEGIN:(.*)\\s*?(\\r\\n|\\r|\\n)(.*)^END:\\1\\s*?/Uims', $vCal, $components)) {
foreach ($components[0] as $key => $data) {
// Remove from the vCalendar data.
$vCal = str_replace($data, '', $vCal);
}
} elseif (!$container) {
return false;
}
// Unfold "quoted printable" folded lines like:
// BODY;ENCODING=QUOTED-PRINTABLE:=
// another=20line=
// last=20line
while (preg_match_all('/^([^:]+;\\s*(ENCODING=)?QUOTED-PRINTABLE(.*=\\r?\\n)+(.*[^=])?(\\r?\\n|$))/mU', $vCal, $matches)) {
foreach ($matches[1] as $s) {
$r = preg_replace('/=\\r?\\n/', '', $s);
$vCal = str_replace($s, $r, $vCal);
}
}
// Unfold any folded lines.
$vCal = preg_replace('/[\\r\\n]+[ \\t]/', '', $vCal);
// Parse the remaining attributes.
if (preg_match_all('/^((?:[^":]+|(?:"[^"]*")+)*):([^\\r\\n]*)\\r?$/m', $vCal, $matches)) {
foreach ($matches[0] as $attribute) {
preg_match('/([^;^:]*)((;(?:[^":]+|(?:"[^"]*")+)*)?):([^\\r\\n]*)[\\r\\n]*/', $attribute, $parts);
$tag = trim(preg_replace('/^.*\\./', '', Horde_String::upper($parts[1])));
$value = $parts[4];
$params = array();
// Parse parameters.
if (!empty($parts[2])) {
preg_match_all('/;(([^;=]*)(=("[^"]*"|[^;]*))?)/', $parts[2], $param_parts);
foreach ($param_parts[2] as $key => $paramName) {
$paramName = Horde_String::upper($paramName);
$paramValue = $param_parts[4][$key];
if ($paramName == 'TYPE') {
$paramValue = preg_split('/(?<!\\\\),/', $paramValue);
if (count($paramValue) == 1) {
$paramValue = $paramValue[0];
}
}
if (is_string($paramValue)) {
if (preg_match('/"([^"]*)"/', $paramValue, $parts)) {
$paramValue = $parts[1];
}
} else {
foreach ($paramValue as $k => $tmp) {
if (preg_match('/"([^"]*)"/', $tmp, $parts)) {
$paramValue[$k] = $parts[1];
}
}
}
if (isset($params[$paramName])) {
if (is_array($params[$paramName])) {
$params[$paramName][] = $paramValue;
} else {
$params[$paramName] = array($params[$paramName], $paramValue);
}
} else {
$params[$paramName] = $paramValue;
}
}
}
// Charset and encoding handling.
if (isset($params['ENCODING']) && Horde_String::upper($params['ENCODING']) == 'QUOTED-PRINTABLE' || isset($params['QUOTED-PRINTABLE'])) {
$value = quoted_printable_decode($value);
if (isset($params['CHARSET'])) {
$value = Horde_String::convertCharset($value, $params['CHARSET'], 'UTF-8');
}
} elseif (isset($params['CHARSET'])) {
$value = Horde_String::convertCharset($value, $params['CHARSET'], 'UTF-8');
}
// Get timezone info for date fields from $params.
$tzid = isset($params['TZID']) ? trim($params['TZID'], '\\"') : false;
switch ($tag) {
// Date fields.
case 'COMPLETED':
case 'CREATED':
case 'LAST-MODIFIED':
case 'X-MOZ-LASTACK':
case 'X-MOZ-SNOOZE-TIME':
$this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
break;
case 'BDAY':
case 'X-ANNIVERSARY':
$this->setAttribute($tag, $this->_parseDate($value), $params);
break;
case 'DTEND':
case 'DTSTART':
case 'DTSTAMP':
case 'DUE':
case 'AALARM':
case 'RECURRENCE-ID':
// types like AALARM may contain additional data after a ;
// ignore these.
$ts = explode(';', $value);
if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') {
$this->setAttribute($tag, $this->_parseDate($ts[0]), $params);
} else {
$this->setAttribute($tag, $this->_parseDateTime($ts[0], $tzid), $params);
}
break;
case 'TRIGGER':
if (isset($params['VALUE']) && $params['VALUE'] == 'DATE-TIME') {
$this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
} else {
$this->setAttribute($tag, $this->_parseDuration($value), $params);
}
break;
// Comma seperated dates.
// Comma seperated dates.
case 'EXDATE':
case 'RDATE':
if (!strlen($value)) {
break;
}
$dates = array();
$separator = $this->_oldFormat ? ';' : ',';
preg_match_all('/' . $separator . '([^' . $separator . ']*)/', $separator . $value, $values);
foreach ($values[1] as $value) {
$stamp = $this->_parseDateTime($value);
if (!is_int($stamp)) {
continue;
}
$dates[] = array('year' => date('Y', $stamp), 'month' => date('m', $stamp), 'mday' => date('d', $stamp));
}
$this->setAttribute($tag, isset($dates[0]) ? $dates[0] : null, $params, true, $dates);
break;
// Duration fields.
// Duration fields.
case 'DURATION':
$this->setAttribute($tag, $this->_parseDuration($value), $params);
break;
// Period of time fields.
// Period of time fields.
case 'FREEBUSY':
$periods = array();
preg_match_all('/,([^,]*)/', ',' . $value, $values);
foreach ($values[1] as $value) {
$periods[] = $this->_parsePeriod($value);
}
$this->setAttribute($tag, isset($periods[0]) ? $periods[0] : null, $params, true, $periods);
break;
// UTC offset fields.
// UTC offset fields.
case 'TZOFFSETFROM':
case 'TZOFFSETTO':
$this->setAttribute($tag, $this->_parseUtcOffset($value), $params);
break;
// Integer fields.
// Integer fields.
case 'PERCENT-COMPLETE':
case 'PRIORITY':
case 'REPEAT':
case 'SEQUENCE':
$this->setAttribute($tag, intval($value), $params);
break;
// Geo fields.
// Geo fields.
case 'GEO':
if ($value) {
if ($this->_oldFormat) {
$floats = explode(',', $value);
$value = array('latitude' => floatval($floats[1]), 'longitude' => floatval($floats[0]));
} else {
$floats = explode(';', $value);
$value = array('latitude' => floatval($floats[0]), 'longitude' => floatval($floats[1]));
}
}
$this->setAttribute($tag, $value, $params);
break;
// Recursion fields.
// Recursion fields.
case 'EXRULE':
case 'RRULE':
$this->setAttribute($tag, trim($value), $params);
break;
// ADR, ORG and N are lists seperated by unescaped semicolons
// with a specific number of slots.
// ADR, ORG and N are lists seperated by unescaped semicolons
// with a specific number of slots.
case 'ADR':
case 'N':
case 'ORG':
$value = trim($value);
// As of rfc 2426 2.4.2 semicolon, comma, and colon must
// be escaped (comma is unescaped after splitting below).
$value = str_replace(array('\\n', '\\N', '\\;', '\\:'), array($this->_newline, $this->_newline, ';', ':'), $value);
// Split by unescaped semicolons:
$values = preg_split('/(?<!\\\\);/', $value);
$value = str_replace('\\;', ';', $value);
$values = str_replace('\\;', ';', $values);
$this->setAttribute($tag, trim($value), $params, true, $values);
break;
// String fields.
// String fields.
default:
if ($this->_oldFormat) {
// vCalendar 1.0 and vCard 2.1 only escape semicolons
// and use unescaped semicolons to create lists.
$value = trim($value);
// Split by unescaped semicolons:
$values = preg_split('/(?<!\\\\);/', $value);
$value = str_replace('\\;', ';', $value);
$values = str_replace('\\;', ';', $values);
$this->setAttribute($tag, trim($value), $params, true, $values);
} else {
$value = trim($value);
// As of rfc 2426 2.4.2 semicolon, comma, and colon
// must be escaped (comma is unescaped after splitting
// below).
$value = str_replace(array('\\n', '\\N', '\\;', '\\:', '\\\\'), array($this->_newline, $this->_newline, ';', ':', '\\'), $value);
// Split by unescaped commas.
$values = preg_split('/(?<!\\\\),/', $value);
$value = str_replace('\\,', ',', $value);
$values = str_replace('\\,', ',', $values);
$this->setAttribute($tag, trim($value), $params, true, $values);
}
break;
}
}
}
// Process all components.
if ($components) {
// vTimezone components are processed first. They are
// needed to process vEvents that may use a TZID.
foreach ($components[0] as $key => $data) {
$type = trim($components[1][$key]);
if ($type != 'VTIMEZONE') {
continue;
}
$component = $this->newComponent($type, $this);
if ($component === false) {
throw new Horde_Icalendar_Exception('Unable to create object for type ' . $type);
}
$component->parsevCalendar($data, $type);
$this->addComponent($component);
// Remove from the vCalendar data.
$vCal = str_replace($data, '', $vCal);
}
// Now process the non-vTimezone components.
foreach ($components[0] as $key => $data) {
$type = trim($components[1][$key]);
if ($type == 'VTIMEZONE') {
continue;
}
$component = $this->newComponent($type, $this);
if ($component === false) {
throw new Horde_Icalendar_Exception('Unable to create object for type ' . $type);
}
$component->parsevCalendar($data, $type);
$this->addComponent($component);
}
}
return true;
}