/** * Parses a string containing vCalendar data. * * @param string $text * The data to parse. * @param string $base * The type of the base object. * @param string $charset * The encoding charset for $text. Defaults to * utf-8. * @param boolean $clear * If true clears the iCal object before parsing. * * @return boolean True on successful import, false otherwise. */ function parsevCalendar($text, $base = 'VCALENDAR', $charset = 'utf8', $clear = true) { if ($clear) { $this->clear(); } $matches = array(); if (preg_match('/(BEGIN:' . $base . '\\r?\\n?)([\\W\\w]*)(END:' . $base . '\\r?\\n?)/i', $text, $matches)) { $vCal = $matches[2]; } else { // Text isn't enclosed in BEGIN:VCALENDAR // .. END:VCALENDAR. We'll try to parse it anyway. $vCal = $text; } // All subcomponents. $matches = null; if (preg_match_all('/BEGIN:([\\W\\w]*)(\\r\\n|\\r|\\n)([\\W\\w]*)END:\\1(\\r\\n|\\r|\\n)/Ui', $vCal, $matches)) { // vTimezone components are processed first. They are // needed to process vEvents that may use a TZID. foreach ($matches[0] as $key => $data) { $type = trim($matches[1][$key]); if ($type != 'VTIMEZONE') { continue; } $component =& ICalendar::newComponent($type, $this); if ($component === false) { // return PEAR::raiseError("Unable to create object for type $type"); } $component->parsevCalendar($data); $this->addComponent($component); // Remove from the vCalendar data. $vCal = str_replace($data, '', $vCal); } // Now process the non-vTimezone components. foreach ($matches[0] as $key => $data) { $type = trim($matches[1][$key]); if ($type == 'VTIMEZONE') { continue; } $component =& ICalendar::newComponent($type, $this); if ($component === false) { // return PEAR::raiseError("Unable to create object for type $type"); } $component->parsevCalendar($data); $this->addComponent($component); // Remove from the vCalendar data. $vCal = str_replace($data, '', $vCal); } } // Unfold any folded lines. $vCal = preg_replace('/[\\r\\n]+[ \\t]/', '', $vCal); // 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); } } if (is_object($GLOBALS['LANG'])) { $csConvObj =& $GLOBALS['LANG']->csConvObj; } elseif (is_object($GLOBALS['TSFE'])) { $csConvObj =& $GLOBALS['TSFE']->csConvObj; } else { require_once \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('lang') . 'lang.php'; $LANG = new language(); if (TYPO3_MODE == 'BE') { $LANG->init($GLOBALS['BE_USER']->uc['lang']); $csConvObj =& $LANG->csConvObj; } else { $LANG->init($GLOBALS['TSFE']->config['config']['language']); $csConvObj =& $GLOBALS['TSFE']->csConvObj; } } $renderCharset = $csConvObj->parse_charset($GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'] ? $GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'] : $this->defaultCharSet); // Parse the remaining attributes. if (preg_match_all('/(.*):([^\\r\\n]*)[\\r\\n]+/', $vCal, $matches)) { foreach ($matches[0] as $attribute) { $parts = array(); preg_match('/([^;^:]*)((;[^:]*)?):([^\\r\\n]*)[\\r\\n]*/', $attribute, $parts); $tag = $parts[1]; $value = $parts[4]; $params = array(); // Parse parameters. if (!empty($parts[2])) { $param_parts = array(); preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts); foreach ($param_parts[2] as $key => $paramName) { $paramValue = $param_parts[4][$key]; $params[strtoupper($paramName)] = $paramValue; } } // Charset and encoding handling. if (isset($params['ENCODING']) && strtoupper($params['ENCODING']) == 'QUOTED-PRINTABLE' || isset($params['QUOTED-PRINTABLE'])) { $value = quoted_printable_decode($value); $value = $csConvObj->conv($value, $csConvObj->parse_charset[$params['CHARSET']] ? $csConvObj->parse_charset[$params['CHARSET']] : 'utf-8', $renderCharset, 1); } elseif (isset($params['CHARSET'])) { $value = $csConvObj->conv($value, $csConvObj->parse_charset[$params['CHARSET']], $renderCharset, 1); } else { // As per RFC 2279, assume UTF8 if we don't have an // explicit charset parameter. $value = $csConvObj->conv($value, 'utf-8', $renderCharset, 1); } // 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': $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params); break; case 'BDAY': $this->setAttribute($tag, $this->_parseDate($value), $params); break; case 'DTEND': case 'DTSTART': case 'DTSTAMP': case 'DUE': case 'AALARM': case 'RECURRENCE-ID': if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') { $this->setAttribute($tag, $this->_parseDate($value), $params); } else { $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params); } break; case 'TRIGGER': if (isset($params['VALUE'])) { if ($params['VALUE'] == 'DATE-TIME') { $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params); } else { $this->setAttribute($tag, $this->_parseDuration($value), $params); } } else { $this->setAttribute($tag, $this->_parseDuration($value), $params); } break; // Comma seperated dates. // Comma seperated dates. case 'EXDATE': case 'RDATE': $this->setAttribute($tag, $value, $params); 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': $floats = explode(';', $value); $value['latitude'] = floatval($floats[0]); $value['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->isOldFormat()) { // vCalendar 1.0 and vcCard 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; } } } return true; }