diff --git a/phpgwapi/inc/horde/Horde/iCalendar.php b/phpgwapi/inc/horde/Horde/iCalendar.php new file mode 100644 index 0000000000..9ba13ad0f3 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/iCalendar.php @@ -0,0 +1,1421 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Mike Cochrane + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_iCalendar + */ +class Horde_iCalendar { + + /** + * The parent (containing) iCalendar object. + * + * @var object Horde_iCalendar $_container + */ + var $_container = false; + + var $_attributes = array(); + + var $_components = array(); + + /** + * According to RFC 2425, we should always use CRLF-terminated + * lines. + * + * @var string $_newline + */ + var $_newline = "\r\n"; + + /** + * Return a reference to a new component. + * + * @param string $type The type of component to return + * @param object $container A container that this component + * will be associated with. + * + * @return object Reference to a Horde_iCalendar_* object as specified. + */ + function &newComponent($type, &$container) + { +# require_once 'Horde/String.php'; + $type = strtolower($type); + $class = 'Horde_iCalendar_' . strtolower($type); + if (!class_exists($class)) { + include_once dirname(__FILE__) . '/iCalendar/' . $type . '.php'; + } + if (class_exists($class)) { + $component = &new $class(); + if ($container !== false) { + $component->_container = &$container; + } + return $component; + } else { + // Should return an dummy x-unknown type class here. + return false; + } + } + + /** + * Set the value of an attribute. + * + * @param string $name The name of the attribute. + * @param string $value The value of the attribute. + * @param array $params (optional) Array containing any addition + * parameters for this attribute. + * @param boolean $append (optional) True to append the attribute, False + * to replace the first matching attribute found. + * @param array $values (optional) array representation of $value. + * For comma/semicolon seperated lists of values. + * If not set use $value as single array element. + */ + function setAttribute($name, $value, $params = array(), $append = true, $values = false) + { + $found = $append; + if (!$values) { + $values = array($value); + } + $keys = array_keys($this->_attributes); + foreach ($keys as $key) { + if ($found) break; + if ($this->_attributes[$key]['name'] == $name) { + $this->_attributes[$key]['params'] = $params; + $this->_attributes[$key]['value'] = $value; + $this->_attributes[$key]['values'] = $values; + $found = true; + } + } + + if ($append || !$found) { + $this->_attributes[] = array( + 'name' => $name, + 'params' => $params, + 'value' => $value, + 'values' => $values + ); + } + } + + /** + * Sets parameter(s) for an (already existing) attribute. The + * parameter set is merged into the existing set. + * + * @param string $name The name of the attribute. + * @param array $params Array containing any additional + * parameters for this attribute. + * @return boolean True on success, false if no attribute $name exists. + */ + function setParameter($name, $params) + { + $keys = array_keys($this->_attributes); + foreach ($keys as $key) { + if ($this->_attributes[$key]['name'] == $name) { + $this->_attributes[$key]['params'] = + array_merge((array)$this->_attributes[$key]['params'] , $params); + return true; + } + } + + return false; + } + + /** + * Get the value of an attribute. + * + * @param string $name The name of the attribute. + * @param boolean $params Return the parameters for this attribute + * instead of its value. + * + * @return mixed (object) PEAR_Error if the attribute does not exist. + * (string) The value of the attribute. + * (array) The parameters for the attribute or + * multiple values for an attribute. + */ + function getAttribute($name, $params = false) + { + $result = array(); + foreach ($this->_attributes as $attribute) { + if ($attribute['name'] == $name) { + if ($params) { + $result[] = $attribute['params']; + } else { + $result[] = $attribute['value']; + } + } + } + if (count($result) == 0) { + require_once 'PEAR.php'; + return PEAR::raiseError('Attribute "' . $name . '" Not Found'); + } if (count($result) == 1 && !$params) { + return $result[0]; + } else { + return $result; + } + } + + /** + * Gets the values of an attribute as an array. Multiple values + * are possible due to: + * + * a) multiplce occurences of 'name' + * b) (unsecapd) comma seperated lists. + * + * So for a vcard like "KEY:a,b\nKEY:c" getAttributesValues('KEY') + * will return array('a','b','c'). + * + * @param string $name The name of the attribute. + * @return mixed (object) PEAR_Error if the attribute does not exist. + * (array) Multiple values for an attribute. + */ + function getAttributeValues($name) + { + $result = array(); + foreach ($this->_attributes as $attribute) { + if ($attribute['name'] == $name) { + $result = array_merge($attribute['values'], $result); + } + } + if (!count($result)) { + return PEAR::raiseError('Attribute "' . $name . '" Not Found'); + } + return $result; + } + + /** + * Returns the value of an attribute, or a specified default value + * if the attribute does not exist. + * + * @param string $name The name of the attribute. + * @param mixed $default (optional) What to return if the attribute + * specified by $name does not exist. + * + * @return mixed (string) The value of $name. + * (mixed) $default if $name does not exist. + */ + function getAttributeDefault($name, $default = '') + { + $value = $this->getAttribute($name); + return is_a($value, 'PEAR_Error') ? $default : $value; + } + + /** + * Remove all occurences of an attribute. + * + * @param string $name The name of the attribute. + */ + function removeAttribute($name) + { + $keys = array_keys($this->_attributes); + foreach ($keys as $key) { + if ($this->_attributes[$key]['name'] == $name) { + unset($this->_attributes[$key]); + } + } + } + + /** + * Get attributes for all tags or for a given tag. + * + * @param string $tag (optional) return attributes for this tag. + * or all attributes if not given + * @return array Array containing all the attributes and their types. + */ + function getAllAttributes($tag = false) + { + if ($tag === false) { + return $this->_attributes; + } + $result = array(); + foreach ($this->_attributes as $attribute) { + if ($attribute['name'] == $tag) { + $result[] = $attribute; + } + } + return $result; + } + + /** + * Add a vCalendar component (eg vEvent, vTimezone, etc.). + * + * @param object Horde_iCalendar $component Component (subclass) to add. + */ + function addComponent($component) + { + if (is_a($component, 'Horde_iCalendar')) { + $component->_container = &$this; + $this->_components[] = &$component; + } + } + + /** + * Retrieve all the components. + * + * @return array Array of Horde_iCalendar objects. + */ + function getComponents() + { + return $this->_components; + } + + /** + * Return the classes (entry types) we have. + * + * @return array Hash with class names Horde_iCalendar_xxx as keys + * and number of components of this class as value. + */ + function getComponentClasses() + { + $r = array(); + foreach ($this->_components as $c) { + $cn = strtolower(get_class($c)); + if (empty($r[$cn])) { + $r[$cn] = 1; + } else { + $r[$cn]++; + } + } + + return $r; + } + + /** + * Number of components in this container. + * + * @return integer Number of components in this container. + */ + function getComponentCount() + { + return count($this->_components); + } + + /** + * Retrieve a specific component. + * + * @param integer $idx The index of the object to retrieve. + * + * @return mixed (boolean) False if the index does not exist. + * (Horde_iCalendar_*) The requested component. + */ + function getComponent($idx) + { + if (isset($this->_components[$idx])) { + return $this->_components[$idx]; + } else { + return false; + } + } + + /** + * Locates the first child component of the specified class, and + * returns a reference to this component. + * + * @param string $type The type of component to find. + * + * @return mixed (boolean) False if no subcomponent of the specified + * class exists. + * (Horde_iCalendar_*) A reference to the requested component. + */ + function &findComponent($childclass) + { +# require_once 'Horde/String.php'; +# $childclass = 'Horde_iCalendar_' . String::lower($childclass); + $childclass = 'Horde_iCalendar_' . strtolower($childclass); + $keys = array_keys($this->_components); + foreach ($keys as $key) { + if (is_a($this->_components[$key], $childclass)) { + return $this->_components[$key]; + } + } + + return false; + } + + /** + * Clears the iCalendar object (resets the components and + * attributes arrays). + */ + function clear() + { + $this->_components = array(); + $this->_attributes = array(); + } + + /** + * Export as vCalendar format. + */ + function exportvCalendar() + { + // Default values. + $requiredAttributes['VERSION'] = '2.0'; + $requiredAttributes['PRODID'] = '-//The Horde Project//Horde_iCalendar Library, Horde 3.0-cvs //EN'; + $requiredAttributes['METHOD'] = 'PUBLISH'; + + foreach ($requiredAttributes as $name => $default_value) { + if (is_a($this->getattribute($name), 'PEAR_Error')) { + $this->setAttribute($name, $default_value); + } + } + + return $this->_exportvData('VCALENDAR') . $this->_newline; + } + + /** + * Export this entry as a hash array with tag names as keys. + * + * @param boolean (optional) $paramsInKeys + * If false, the operation can be quite lossy as the + * parameters are ignored when building the array keys. + * So if you export a vcard with + * LABEL;TYPE=WORK:foo + * LABEL;TYPE=HOME:bar + * the resulting hash contains only one label field! + * If set to true, array keys look like 'LABEL;TYPE=WORK' + * @return array A hash array with tag names as keys. + */ + function toHash($paramsInKeys = false) + { + $hash = array(); + foreach ($this->_attributes as $a) { + $k = $a['name']; + if ($paramsInKeys && is_array($a['params'])) { + foreach ($a['params'] as $p => $v) { + $k .= ";$p=$v"; + } + } + $hash[$k] = $a['value']; + } + + return $hash; + } + + /** + * Parse a string containing vCalendar data. + * + * @param string $text The data to parse. + * @param string $base The type of the base object. + * @param string $charset (optional) The encoding charset for $text. Defaults to utf-8 + * @param boolean $clear (optional) True to clear() the iCal object before parsing. + * + * @return boolean True on successful import, false otherwise. + */ + function parsevCalendar($text, $base = 'VCALENDAR', $charset = 'utf-8', $clear = true) + { + if ($clear) { + $this->clear(); + } + 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:([\S]*)(\r\n|\r|\n)([\W\w]*)END:\1(\r\n|\r|\n)/U', $vCal, $matches)) { + foreach ($matches[0] as $key => $data) { + $type = $matches[1][$key]; + $component = &Horde_iCalendar::newComponent(trim($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)+ /', ' ', $vCal); + + // Unfold "quoted printable" folded lines like: + // BODY;ENCODING=QUOTED-PRINTABLE:= + // another=20line= + // last=20line +# Horde::logMessage("SymcML: match 1", __FILE__, __LINE__, PEAR_LOG_DEBUG); +# if (preg_match_all('/^([^:]+;\s*ENCODING=QUOTED-PRINTABLE(.*=[\r\n|\r|\n])+(.*[^=])[\r\n|\r|\n])/mU', $vCal, $matches)) { +## if (preg_match_all('/^(BODY;ENCODING=QUOTED-PRINTABLE(.*=\r\n)+(.*)?\r?\n)/mU', $vCal, $matches)) { +# Horde::logMessage("SymcML: match 2", __FILE__, __LINE__, PEAR_LOG_DEBUG); +# foreach ($matches[1] as $s) { +# Horde::logMessage("SymcML: match 3 $s", __FILE__, __LINE__, PEAR_LOG_DEBUG); +# $r = preg_replace('/=[\r\n|\r|\n]/', '', $s); +# Horde::logMessage("SymcML: match 4 $r", __FILE__, __LINE__, PEAR_LOG_DEBUG); +# $vCal = str_replace($s, $r, $vCal); +# } +# } + + // Unfold "quoted printable" folded lines like: + // BODY;ENCODING=QUOTED-PRINTABLE:= + // another=20line= + // last=20line + #if (preg_match_all('/^([^:]+;\s*ENCODING=QUOTED-PRINTABLE(.*=*\s))/mU', $vCal, $matches)) { + # $matches = preg_split('/=+\s/',$vCal); + # $vCal = implode('',$matches); + #} + if (preg_match_all('/^([^:]+;\s*ENCODING=QUOTED-PRINTABLE(.*=*\s))/mU', $vCal, $matches)) { + $matches = preg_split('/=(\r\n|\r|\n)/',$vCal); + $vCal = implode('',$matches); + } + + // Parse the remaining attributes. + + if (preg_match_all('/(.*):([^\r\n]*)[\r\n]+/', $vCal, $matches)) { + foreach ($matches[0] as $attribute) { + preg_match('/([^;^:]*)((;[^:]*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts); + $tag = $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) { + $paramValue = $param_parts[4][$key]; + $params[$paramName] = $paramValue; + } + } + + // Charset and encoding handling. + if ((isset($params['ENCODING']) + && $params['ENCODING'] == 'QUOTED-PRINTABLE') + || isset($params['QUOTED-PRINTABLE'])) { + + $value = quoted_printable_decode($value); + // Quoted printable is normally encoded as utf-8. + if (isset($params['CHARSET'])) { + $value = $GLOBALS['egw']->translation->convert($value, $params['CHARSET']); + } else { + $value = $GLOBALS['egw']->translation->convert($value, 'utf-8'); + } + } + + if (isset($params['CHARSET'])) { + $value = $GLOBALS['egw']->translation->convert($value, $params['CHARSET']); + } else { + // As per RFC 2279, assume UTF8 if we don't have + // an explicit charset parameter. + $value = $GLOBALS['egw']->translation->convert($value, 'utf-8'); + } + + switch ($tag) { + // Date fields. + case 'DTSTAMP': + case 'COMPLETED': + case 'CREATED': + case 'LAST-MODIFIED': + case 'BDAY': + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + break; + + case 'DTEND': + case 'DTSTART': + case 'DUE': + case 'RECURRENCE-ID': + if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') { + $this->setAttribute($tag, $this->_parseDate($value), $params); + } else { + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + } + break; + + case 'RDATE': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE') { + $this->setAttribute($tag, $this->_parseDate($value), $params); + } elseif ($params['VALUE'] == 'PERIOD') { + $this->setAttribute($tag, $this->_parsePeriod($value), $params); + } else { + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + } + } else { + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + } + break; + + case 'TRIGGER': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE-TIME') { + $this->setAttribute($tag, $this->_parseDateTime($value), $params); + } else { + $this->setAttribute($tag, $this->_parseDuration($value), $params); + } + } else { + $this->setAttribute($tag, $this->_parseDuration($value), $params); + } + break; + + // Comma seperated dates. + case 'EXDATE': + $values = array(); + $dates = array(); + preg_match_all('/,([^,]*)/', ',' . $value, $values); + + foreach ($values[1] as $value) { + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE-TIME') { + $dates[] = $this->_parseDateTime($value); + } elseif ($params['VALUE'] == 'DATE') { + $dates[] = $this->_parseDate($value); + } + } else { + $dates[] = $this->_parseDateTime($value); + } + } + $this->setAttribute($tag, $dates, $params); + break; + + // Duration fields. + case 'DURATION': + $this->setAttribute($tag, $this->_parseDuration($value), $params); + break; + + // Period of time fields. + case 'FREEBUSY': + $values = array(); + $periods = array(); + preg_match_all('/,([^,]*)/', ',' . $value, $values); + foreach ($values[1] as $value) { + $periods[] = $this->_parsePeriod($value); + } + + $this->setAttribute($tag, $periods, $params); + break; + + // UTC offset fields. + case 'TZOFFSETFROM': + case 'TZOFFSETTO': + $this->setAttribute($tag, $this->_parseUtcOffset($value), $params); + break; + + // Integer fields. + case 'PERCENT-COMPLETE': + case 'PRIORITY': + case 'REPEAT': + case 'SEQUENCE': + $this->setAttribute($tag, intval($value), $params); + break; + + // Geo fields. + case 'GEO': + $floats = split(';', $value); + $value['latitude'] = floatval($floats[0]); + $value['longitude'] = floatval($floats[1]); + $this->setAttribute($tag, $value, $params); + break; + + // Recursion fields. + case 'EXRULE': + case 'RRULE': + $this->setAttribute($tag, trim($value), $params); + break; + + // ADR an N are lists seperated by unescaped semi-colons. + case 'ADR': + case 'N': + case 'ORG': + + $value = trim($value); + // As of rfc 2426 2.4.2 semi-colon, comma, and + // colon must be escaped. + $value = str_replace('\\n', $this->_newline, $value); + $value = str_replace('\\,', ',', $value); + $value = str_replace('\\:', ':', $value); + + // Split by unescaped semi-colons: + $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + + break; + + // String fields. + default: + $value = trim($value); + // As of rfc 2426 2.4.2 semi-colon, comma, and + // colon must be escaped. + $value = str_replace('\\n', $this->_newline, $value); + $value = str_replace('\\;', ';', $value); + $value = str_replace('\\:', ':', $value); + + // Split by unescaped commas: + $values = preg_split('/(?setAttribute($tag, trim($value), $params, true, $values); + break; + } + } + } + + return true; + } + + /** + * Export this component in vCal format. + * + * @param string $base (optional) The type of the base object. + * + * @return string vCal format data. + */ + function _exportvData($base = 'VCALENDAR') + { + $result = 'BEGIN:' . strtoupper($base) . $this->_newline; + + // Ensure that version is the first attribute. + $v = $this->getAttributeDefault('VERSION', false); + if ($v) { + $result .= 'VERSION:' . $v. $this->_newline; + } + + foreach ($this->_attributes as $attribute) { + $name = $attribute['name']; + if ($name == 'VERSION') { + // Already done. + continue; + } + + $params = $attribute['params']; + $params_str = ''; + + if (count($params)) { + foreach ($params as $param_name => $param_value) { + $params_str .= ";$param_name=$param_value"; + } + } + + $value = $attribute['value']; + switch ($name) { + // Date fields. + case 'DTSTAMP': + case 'COMPLETED': + case 'CREATED': + case 'DCREATED': + case 'LAST-MODIFIED': + $value = $this->_exportDateTime($value); + break; + + case 'DTEND': + case 'DTSTART': + case 'DUE': + case 'RECURRENCE-ID': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE') { + $value = $this->_exportDate($value); + } else { + $value = $this->_exportDateTime($value); + } + } else { + $value = $this->_exportDateTime($value); + } + break; + + case 'RDATE': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE') { + $value = $this->_exportDate($value); + } elseif ($params['VALUE'] == 'PERIOD') { + $value = $this->_exportPeriod($value); + } else { + $value = $this->_exportDateTime($value); + } + } else { + $value = $this->_exportDateTime($value); + } + break; + + case 'TRIGGER': + if (isset($params['VALUE'])) { + if ($params['VALUE'] == 'DATE-TIME') { + $value = $this->_exportDateTime($value); + } elseif ($params['VALUE'] == 'DURATION') { + $value = $this->_exportDuration($value); + } + } else { + $value = $this->_exportDuration($value); + } + break; + + // Duration fields. + case 'DURATION': + $value = $this->_exportDuration($value); + break; + + // Period of time fields. + case 'FREEBUSY': + $value_str = ''; + foreach ($value as $period) { + $value_str .= empty($value_str) ? '' : ','; + $value_str .= $this->_exportPeriod($period); + } + $value = $value_str; + break; + + // UTC offset fields. + case 'TZOFFSETFROM': + case 'TZOFFSETTO': + $value = $this->_exportUtcOffset($value); + break; + + // Integer fields. + case 'PERCENT-COMPLETE': + case 'PRIORITY': + case 'REPEAT': + case 'SEQUENCE': + $value = "$value"; + break; + + // Geo fields. + case 'GEO': + $value = $value['latitude'] . ',' . $value['longitude']; + break; + + // Recurrence fields. + case 'EXRULE': + case 'RRULE': + + default: + break; + } + + if (!empty($params['ENCODING']) && + $params['ENCODING'] == 'QUOTED-PRINTABLE' && strlen(trim($value)) > 0) { + $value = str_replace("\r", '', $value); +# $result .= "$name$params_str:=" . $this->_newline +# . $this->_quotedPrintableEncode($value) +# . $this->_newline; + $result .= "$name$params_str:" + . $this->_quotedPrintableEncode($value) + . $this->_newline; + } else { +# JVL: prevent : for empty values +# $attr_string = "$name$params_str:$value"; + $attr_string = "$name$params_str"; + $attr_string .= (!empty($value)) ? ":$value" : ';'; + + $result .= $this->_foldLine($attr_string) . $this->_newline; + } + } + + foreach ($this->getComponents() as $component) { + $result .= $component->exportvCalendar() . $this->_newline; + } + + $result .= 'END:' . $base; + + return $result; + } + + /** + * Parse a UTC Offset field. + */ + function _parseUtcOffset($text) + { + $offset = array(); + if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) { + $offset['ahead'] = (boolean)($timeParts[1] == '+'); + $offset['hour'] = intval($timeParts[2]); + $offset['minute'] = intval($timeParts[3]); + if (isset($timeParts[4])) { + $offset['second'] = intval($timeParts[4]); + } + return $offset; + } else { + return false; + } + } + + /** + * Export a UTC Offset field. + */ + function _exportUtcOffset($value) + { + $offset = $value['ahead'] ? '+' : '-'; + $offset .= sprintf('%02d%02d', + $value['hour'], $value['minute']); + if (isset($value['second'])) { + $offset .= sprintf('%02d', $value['second']); + } + + return $offset; + } + + /** + * Parse a Time Period field. + */ + function _parsePeriod($text) + { + $periodParts = split('/', $text); + + $start = $this->_parseDateTime($periodParts[0]); + + if ($duration = $this->_parseDuration($periodParts[1])) { + return array('start' => $start, 'duration' => $duration); + } elseif ($end = $this->_parseDateTime($periodParts[1])) { + return array('start' => $start, 'end' => $end); + } + } + + /** + * Export a Time Period field. + */ + function _exportPeriod($value) + { + $period = $this->_exportDateTime($value['start']); + $period .= '/'; + if (isset($value['duration'])) { + $period .= $this->_exportDuration($value['duration']); + } else { + $period .= $this->_exportDateTime($value['end']); + } + return $period; + } + + /** + * Parse a DateTime field into a unix timestamp. + */ + function _parseDateTime($text) + { + $dateParts = split('T', $text); + if (count($dateParts) != 2 && !empty($text)) { + // Not a datetime field but may be just a date field. + if (!$date = $this->_parseDate($text)) { + return $date; + } + return @gmmktime(0, 0, 0, $date['month'], $date['mday'], $date['year']); + } + + if (!$date = $this->_parseDate($dateParts[0])) { + return $date; + } + if (!$time = $this->_parseTime($dateParts[1])) { + return $time; + } + + if ($time['zone'] == 'UTC') { + return @gmmktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); + } else { + return @mktime($time['hour'], $time['minute'], $time['second'], + $date['month'], $date['mday'], $date['year']); + } + } + + /** + * Export a DateTime field. + */ + function _exportDateTime($value) + { + $temp = array(); + if (!is_object($value) || is_array($value)) { + $TZOffset = 3600 * substr(date('O'), 0, 3); + $TZOffset += 60 * substr(date('O'), 3, 2); + $value -= $TZOffset; + + $temp['zone'] = 'UTC'; + $temp['year'] = date('Y', $value); + $temp['month'] = date('n', $value); + $temp['mday'] = date('j', $value); + $temp['hour'] = date('G', $value); + $temp['minute'] = date('i', $value); + $temp['second'] = date('s', $value); + } else { + $dateOb = (object)$value; + + // Minutes. + $TZOffset = substr(date('O'), 3, 2); + $thisMin = $dateOb->min - $TZOffset; + + // Hours. + $TZOffset = substr(date('O'), 0, 3); + $thisHour = $dateOb->hour - $TZOffset; + + if ($thisMin < 0) { + $thisHour -= 1; + $thisMin += 60; + } + + if ($thisHour < 0) { + require_once 'Date/Calc.php'; + $prevday = Date_Calc::prevDay($dateOb->mday, $dateOb->month, $dateOb->year); + $dateOb->mday = substr($prevday, 6, 2); + $dateOb->month = substr($prevday, 4, 2); + $dateOb->year = substr($prevday, 0, 4); + $thisHour += 24; + } + + $temp['zone'] = 'UTC'; + $temp['year'] = $dateOb->year; + $temp['month'] = $dateOb->month; + $temp['mday'] = $dateOb->mday; + $temp['hour'] = $thisHour; + $temp['minute'] = $dateOb->min; + $temp['second'] = $dateOb->sec; + } + + return Horde_iCalendar::_exportDate($temp) . 'T' . Horde_iCalendar::_exportTime($temp); + } + + /** + * Parse a Time field. + */ + function _parseTime($text) + { + if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) { + $time['hour'] = intval($timeParts[1]); + $time['minute'] = intval($timeParts[2]); + $time['second'] = intval($timeParts[3]); + if (isset($timeParts[4])) { + $time['zone'] = 'UTC'; + } else { + $time['zone'] = 'Local'; + } + return $time; + } else { + return false; + } + } + + /** + * Export a Time field. + */ + function _exportTime($value) + { + $time = sprintf('%02d%02d%02d', + $value['hour'], $value['minute'], $value['second']); + if ($value['zone'] == 'UTC') { + $time .= 'Z'; + } + return $time; + } + + /** + * Parse a Date field. + */ + function _parseDate($text) + { + if (strlen($text) != 8) { + return false; + } + + $date['year'] = intval(substr($text, 0, 4)); + $date['month'] = intval(substr($text, 4, 2)); + $date['mday'] = intval(substr($text, 6, 2)); + + return $date; + } + + /** + * Export a Date field. + */ + function _exportDate($value) + { + return sprintf('%04d%02d%02d', + $value['year'], $value['month'], $value['mday']); + } + + /** + * Parse a Duration Value field. + */ + function _parseDuration($text) + { + if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $durvalue)) { + // Weeks. + $duration = 7 * 86400 * intval($durvalue[3]); + + if (count($durvalue) > 4) { + // Days. + $duration += 86400 * intval($durvalue[4]); + } + if (count($durvalue) > 5) { + // Hours. + $duration += 3600 * intval($durvalue[7]); + + // Mins. + if (isset($durvalue[8])) { + $duration += 60 * intval($durvalue[8]); + } + + // Secs. + if (isset($durvalue[9])) { + $duration += intval($durvalue[9]); + } + } + + // Sign. + if ($durvalue[1] == "-") { + $duration *= -1; + } + + return $duration; + } else { + return false; + } + } + + /** + * Export a duration value. + */ + function _exportDuration($value) + { + $duration = ''; + if ($value < 0) { + $value *= -1; + $duration .= '-'; + } + $duration .= 'P'; + + $weeks = floor($value / (7 * 86400)); + $value = $value % (7 * 86400); + if ($weeks) { + $duration .= $weeks . 'W'; + } + + $days = floor($value / (86400)); + $value = $value % (86400); + if ($days) { + $duration .= $days . 'D'; + } + + if ($value) { + $duration .= 'T'; + + $hours = floor($value / 3600); + $value = $value % 3600; + if ($hours) { + $duration .= $hours . 'H'; + } + + $mins = floor($value / 60); + $value = $value % 60; + if ($mins) { + $duration .= $mins . 'M'; + } + + if ($value) { + $duration .= $value . 'S'; + } + } + + return $duration; + } + + /** + * Return the folded version of a line. + * JVL rewritten to fold on any ; or: or = if present before column 75 + * this is still rfc2445 section 4.1 compliant + */ + function _foldLine($line) + { + $line = preg_replace("/\r\n|\n|\r/", '\n', $line); + if (strlen($line) > 75) { + $foldedline = ''; + while (!empty($line)) { + $maxLine = substr($line, 0, 75); + $cutPoint = 1+max(is_numeric($p1 = strrpos($maxLine,';')) ? $p1 : -1, + is_numeric($p1 = strrpos($maxLine,':')) ? $p1 : -1, + is_numeric($p1 = strrpos($maxLine,'=')) ? $p1 : -1); + if ($cutPoint < 1) // nothing found, then fold complete maxLine + $cutPoint = 75; + // now fold [0..(cutPoint-1)] + $foldedline .= (empty($foldedline)) + ? substr($line, 0, $cutPoint) + : $this->_newline . ' ' . substr($line, 0, $cutPoint); + + $line = (strlen($line) <= $cutPoint) + ? '' + : substr($line, $cutPoint); + + if (strlen($line) < 75) { + $foldedline .= $this->_newline . ' ' . $line; + $line = ''; + } + + } + return $foldedline; + } + return $line; + } + + /** + * Convert an 8bit string to a quoted-printable string according + * to RFC2045, section 6.7. + * + * Uses imap_8bit if available. + * + * @param string $input The string to be encoded. + * + * @return string The quoted-printable encoded string. + */ + function _quotedPrintableEncode($input = '') + { + return $this->EncodeQP($input); + + #$input = preg_replace('!(\r\n|\r|\n)!',"\n",$input); + + // If imap_8bit() is available, use it. + if (function_exists('imap_8bit')) { + $retValue = imap_8bit($input); + #$retValue = preg_replace('/=0A/',"=0D=0A=\r\n",$retValue); + return $retValue; + } + + // Rather dumb replacment: just encode everything. + $hex = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F'); + + $output = ''; + $len = strlen($input); + for ($i = 0; $i < $len; ++$i) { + $c = substr($input, $i, 1); + $dec = ord($c); + $output .= '=' . $hex[floor($dec / 16)] . $hex[floor($dec % 16)]; + if (($i + 1) % 25 == 0) { + $output .= "=\r\n"; + } + } + return $output; + } + var $LE = "\r\n"; + + /** + * Encode string to quoted-printable. + * @access private + * @return string + */ + function EncodeQP_old ($str) { + $encoded = $this->FixEOL($str); + #$encoded = $str; + #if (substr($encoded, -(strlen($this->LE))) != $this->LE) + # $encoded .= $this->LE; + + // Replace every high ascii, control and = characters + #$encoded = preg_replace('/([\000-\010\013\014\016-\037\075\177-\377])/e', + # "'='.sprintf('%02X', ord('\\1'))", $encoded); + $encoded = preg_replace('/([\000-\012\015\016\020-\037\075\177-\377])/e', + "'='.sprintf('%02X', ord('\\1'))", $encoded); + // Replace every spaces and tabs when it's the last character on a line + #$encoded = preg_replace("/([\011\040])".$this->LE."/e", + # "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); + $encoded = preg_replace("/([\011\040])".$this->LE."/e", + "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); + + // Maximum line length of 76 characters before CRLF (74 + space + '=') + $encoded = $this->WrapText($encoded, 74, true); + + return $encoded; + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @access private + * @return string + */ + function WrapText_old($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? "=\r\n" : $this->LE; + + #$message = $this->FixEOL($message); + if (substr($message, -1) == $this->LE) + $message = substr($message, 0, -1); + + $line = explode("=0D=0A", $message); + $message = ""; + for ($i=0 ;$i < count($line); $i++) + { + $line_part = explode(" ", $line[$i]); + $buf = ""; + for ($e = 0; $e $length)) + { + $space_left = $length - strlen($buf) - 1; + if ($e != 0) + { + if ($space_left > 20) + { + $len = $space_left; + if (substr($word, $len - 1, 1) == "=") + $len--; + elseif (substr($word, $len - 2, 1) == "=") + $len -= 2; + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= " " . $part; + $message .= $buf . sprintf("=%s", $this->LE); + } + else + { + $message .= $buf . $soft_break; + } + $buf = ""; + } + while (strlen($word) > 0) + { + $len = $length; + if (substr($word, $len - 1, 1) == "=") + $len--; + elseif (substr($word, $len - 2, 1) == "=") + $len -= 2; + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) + $message .= $part . sprintf("=%s", $this->LE); + else + $buf = $part; + } + } + else + { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (" " . $word); + + if (strlen($buf) > $length and $buf_o != "") + { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf; + if((count($line)-1) > $i) + $message .= "=0D=0A=\r\n"; + } + + return $message; + } + /** + * Changes every end of line from CR or LF to CRLF. + * @access private + * @return string + */ + function FixEOL($str) { + $str = str_replace("\r\n", "\n", $str); + $str = str_replace("\r", "\n", $str); + $str = str_replace("\n", $this->LE, $str); + return $str; + } + + /** + * Encode string to quoted-printable. + * @access private + * @return string + */ + function EncodeQP ($str) { + $encoded = $this->FixEOL($str); + if (substr($encoded, -(strlen($this->LE))) != $this->LE) + $encoded .= $this->LE; + + // Replace every high ascii, control and = characters + #$encoded = preg_replace('/([\000-\010\013\014\016-\037\075\177-\377])/e', + # "'='.sprintf('%02X', ord('\\1'))", $encoded); + $encoded = preg_replace('/([\000-\012\015\016\020-\037\075\177-\377])/e', + "'='.sprintf('%02X', ord('\\1'))", $encoded); + // Replace every spaces and tabs when it's the last character on a line + $encoded = preg_replace("/([\011\040])".$this->LE."/e", + "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); + + // Maximum line length of 76 characters before CRLF (74 + space + '=') + #$encoded = $this->WrapText($encoded, 74, true); + + return $encoded; + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. + * @access private + * @return string + */ + function WrapText($message, $length, $qp_mode = false) { + $soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE; + $soft_break = "..="; + + $message = $this->FixEOL($message); + if (substr($message, -1) == $this->LE) + $message = substr($message, 0, -1); + + $line = explode($this->LE, $message); + $message = ""; + for ($i=0 ;$i < count($line); $i++) + { + $line_part = explode(" ", $line[$i]); + $buf = ""; + for ($e = 0; $e $length)) + { + $space_left = $length - strlen($buf) - 1; + if ($e != 0) + { + if ($space_left > 20) + { + $len = $space_left; + if (substr($word, $len - 1, 1) == "=") + $len--; + elseif (substr($word, $len - 2, 1) == "=") + $len -= 2; + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= " " . $part; + $message .= $buf . sprintf("=%s", $this->LE); + } + else + { + $message .= $buf . $soft_break; + } + $buf = ""; + } + while (strlen($word) > 0) + { + $len = $length; + if (substr($word, $len - 1, 1) == "=") + $len--; + elseif (substr($word, $len - 2, 1) == "=") + $len -= 2; + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) + $message .= $part . sprintf("=%s", $this->LE); + else + $buf = $part; + } + } + else + { + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (" " . $word); + + if (strlen($buf) > $length and $buf_o != "") + { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . $this->LE; + } + + return $message; + } + +}