⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.78
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-pear
/
pear
/
php
/
Horde
/
Edit File: Icalendar.php
<?php /** * Copyright 2003-2017 Horde LLC (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.horde.org/licenses/lgpl21. * * @author Mike Cochrane <mike@graftonhall.co.nz> * @category Horde * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 * @package Icalendar */ /** * Class representing iCalendar files. * * @author Mike Cochrane <mike@graftonhall.co.nz> * @category Horde * @copyright 2003-2017 Horde LLC * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 * @package Icalendar */ class Horde_Icalendar { /** * The component type of this class. * * @var string */ public $type = 'vcalendar'; /** * The parent (containing) iCalendar object. * * @var Horde_Icalendar */ protected $_container = false; /** * The name/value pairs of attributes for this object (UID, * DTSTART, etc.). Which are present depends on the object and on * what kind of component it is. * * @var array */ protected $_attributes = array(); /** * Any children (contained) iCalendar components of this object. * * @var array */ protected $_components = array(); /** * According to RFC 2425, we should always use CRLF-terminated lines. * * @var string */ protected $_newline = "\r\n"; /** * iCalendar format version (different behavior for 1.0 and 2.0 especially * with recurring events). * * @var string */ protected $_version; /** * Whether entry is vcalendar 1.0, vcard 2.1 or vnote 1.1. * * These 'old' formats are defined by www.imc.org. The 'new' (non-old) * formats icalendar 2.0 and vcard 3.0 are defined in rfc2426 and rfc2445 * respectively. */ protected $_oldFormat = true; /** * Constructor. * * @var string $version Version. */ public function __construct($version = '2.0') { $this->setAttribute('VERSION', $version); } /** * Return a reference to a new component. * * @param string $type The type of component to return * @param Horde_Icalendar $container A container that this component * will be associated with. * * @return object Reference to a Horde_Icalendar_* object as specified. */ public static function newComponent($type, $container) { $type = Horde_String::lower($type); $class = __CLASS__ . '_' . Horde_String::ucfirst($type); if (class_exists($class)) { $component = new $class(); if ($container !== false) { $component->_container = $container; // Use version of container, not default set by component // constructor. $component->setVersion($container->getAttribute('VERSION')); } } else { // Should return an dummy x-unknown type class here. $component = false; } return $component; } /** * Sets the version of this component. * * @see $version * @see $oldFormat * * @param string $version A float-like version string. */ public function setVersion($version) { $this->_oldFormat = $version < 2; $this->_version = $version; } /** * Sets the value of an attribute. * * @param string $name The name of the attribute. * @param string $value The value of the attribute. * @param array $params Array containing any addition parameters for * this attribute. * @param boolean $append True to append the attribute, False to replace * the first matching attribute found. * @param array $values Array representation of $value. For * comma/semicolon seperated lists of values. If * not set use $value as single array element. */ public function setAttribute($name, $value, $params = array(), $append = true, $values = false) { // Make sure we update the internal format version if // setAttribute('VERSION', ...) is called. if ($name == 'VERSION') { $this->setVersion($value); if ($this->_container !== false) { $this->_container->setVersion($value); } } if (!$values) { $values = array($value); } $found = false; if (!$append) { foreach (array_keys($this->_attributes) as $key) { if ($this->_attributes[$key]['name'] == Horde_String::upper($name)) { $this->_attributes[$key]['params'] = $params; $this->_attributes[$key]['value'] = $value; $this->_attributes[$key]['values'] = $values; $found = true; break; } } } if ($append || !$found) { $this->_attributes[] = array( 'name' => Horde_String::upper($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. */ public function setParameter($name, $params = array()) { $keys = array_keys($this->_attributes); foreach ($keys as $key) { if ($this->_attributes[$key]['name'] == $name) { $this->_attributes[$key]['params'] = array_merge($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 (string) The value of the attribute. * (array) The parameters for the attribute or * multiple values for an attribute. * @throws Horde_Icalendar_Exception */ public function getAttribute($name, $params = false) { if ($name == 'VERSION') { return $this->_version; } $result = array(); foreach ($this->_attributes as $attribute) { if ($attribute['name'] == $name) { $result[] = $params ? $attribute['params'] : $attribute['value']; } } if (!count($result)) { throw new Horde_Icalendar_Exception('Attribute "' . $name . '" Not Found'); } elseif (count($result) == 1 && !$params) { return $result[0]; } return $result; } /** * Get a single value of an attribute. * * If multiple values, is auto-determined by library which is preferred * value to return. * * @since 2.1.0 * * @param string $name The name of the attribute. * * @return string The value of the attribute. * @throws Horde_Icalendar_Exception */ public function getAttributeSingle($name) { $out = $this->getAttribute($name, false); return is_array($out) ? reset($out) : $out; } /** * Gets the values of an attribute as an array. Multiple values * are possible due to: * * a) multiple 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 array Multiple values for an attribute. * @throws Horde_Icalendar_Exception */ public function getAttributeValues($name) { $result = array(); foreach ($this->_attributes as $attribute) { if ($attribute['name'] == $name) { $result = array_merge($attribute['values'], $result); } } if (!count($result)) { throw new Horde_Icalendar_Exception('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 What to return if the attribute specified by * $name does not exist. * * @return mixed (mixed) The value of $name. * (mixed) $default if $name does not exist. */ public function getAttributeDefault($name, $default = '') { try { return $this->getAttribute($name); } catch (Horde_Icalendar_Exception $e) { return $default; } } /** * Remove all occurences of an attribute. * * @param string $name The name of the attribute. */ public function removeAttribute($name) { foreach (array_keys($this->_attributes) 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 Return attributes for this tag, or all attributes * if not given. * * @return array An array containing all the attributes and their types. */ public 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 mixed Either a Horde_Icalendar component (subclass) or an array * of them. */ public function addComponent($components) { if (!is_array($components)) { $components = array($components); } foreach ($components as $component) { if ($component instanceof Horde_Icalendar) { $component->_container = $this; $this->_components[] = $component; } } } /** * Retrieve all the components. * * @return array Array of Horde_Icalendar objects. */ public function getComponents() { return $this->_components; } /** * TODO * * @return TODO */ public function getType() { return $this->type; } /** * 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. */ public function getComponentClasses() { $r = array(); foreach ($this->_components as $c) { $cn = Horde_String::lower(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. */ public 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. */ public function getComponent($idx) { return isset($this->_components[$idx]) ? $this->_components[$idx] : false; } /** * Locates the first child component of the specified class, and returns a * reference to it. * * @param string $type The type of component to find. * * @return boolean|Horde_Icalendar_* False if no subcomponent of the * specified class exists or the * requested component. */ public function findComponent($childclass) { $childclass = __CLASS__ . '_' . Horde_String::lower($childclass); foreach (array_keys($this->_components) as $key) { if ($this->_components[$key] instanceof $childclass) { return $this->_components[$key]; } } return false; } /** * Locates the first matching child component of the specified class, and * returns a reference to it. * * @param string $childclass The type of component to find. * @param string $attribute This attribute must be set in the component * for it to match. * @param string $value Optional value that $attribute must match. * * @return boolean|Horde_Icalendar_* False if no matching subcomponent * of the specified class exists, or * the requested component. */ public function findComponentByAttribute($childclass, $attribute, $value = null) { $childclass = __CLASS__ . '_' . Horde_String::lower($childclass); foreach (array_keys($this->_components) as $key) { if ($this->_components[$key] instanceof $childclass) { try { $attr = $this->_components[$key]->getAttribute($attribute); } catch (Horde_Icalendar_Exception $e) { continue; } if (is_null($value) || $value == $attr) { return $this->_components[$key]; } } } return false; } /** * Clears the iCalendar object (resets the components and attributes * arrays). */ public function clear() { $this->_attributes = $this->_components = array(); } public function toString() { return $this->exportvCalendar(); } /** * Export as vCalendar format. * * @return TODO */ public function exportvCalendar() { // Default values. // TODO: HORDE_VERSION does not exist. $requiredAttributes['PRODID'] = '-//The Horde Project//Horde iCalendar Library' . (defined('HORDE_VERSION') ? ', Horde ' . constant('HORDE_VERSION') : '') . '//EN'; foreach ($requiredAttributes as $name => $default_value) { try { $this->getAttribute($name); } catch (Horde_Icalendar_Exception $e) { $this->setAttribute($name, $default_value); } } return $this->_exportvData('VCALENDAR'); } /** * Export this entry as a hash array with tag names as keys. * * @param boolean $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. */ public 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; } /** * Parses a string containing vCalendar data. * * @todo This method doesn't work well at all, if $base is VCARD. * * @param string $text The data to parse. * @param string $base The type of the base object. * @param boolean $clear If true clears this object before parsing. * * @return boolean True on successful import, false otherwise. * @throws Horde_Icalendar_Exception */ 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. 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. case 'DURATION': $this->setAttribute($tag, $this->_parseDuration($value), $params); break; // 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. 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': 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. 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. 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( array('\\;', '\\,'), array(';', ','), $value ); $values = str_replace( array('\\;', '\\,'), array(';', ','), $values ); $this->setAttribute($tag, trim($value), $params, true, $values); break; // 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; } /** * Export this component in vCal format. * * @param string $base The type of the base object. * * @return string vCal format data. */ protected function _exportvData($base = 'VCALENDAR') { $result = 'BEGIN:' . Horde_String::upper($base) . $this->_newline; // VERSION is not allowed for entries enclosed in VCALENDAR/ICALENDAR, // as it is part of the enclosing VCALENDAR/ICALENDAR. See rfc2445 if ($base !== 'VEVENT' && $base !== 'VTODO' && $base !== 'VALARM' && $base !== 'VJOURNAL' && $base !== 'VFREEBUSY' && $base != 'VTIMEZONE' && $base != 'STANDARD' && $base != 'DAYLIGHT') { // Ensure that version is the first attribute. $result .= 'VERSION:' . $this->_version . $this->_newline; } foreach ($this->_attributes as $attribute) { $name = $attribute['name']; if ($name == 'VERSION') { // Already done. continue; } $params_str = ''; $params = $attribute['params']; if ($params) { foreach ($params as $param_name => $param_value) { /* Skip CHARSET for iCalendar 2.0 data, not allowed. */ if ($param_name == 'CHARSET' && !$this->_oldFormat) { continue; } /* Skip VALUE=DATE for vCalendar 1.0 data, not allowed. */ if ($this->_oldFormat && $param_name == 'VALUE' && $param_value == 'DATE') { continue; } if ($param_value === null) { $params_str .= ";$param_name"; } else { if (!is_array($param_value)) { $param_value = array($param_value); } foreach ($param_value as &$one_param_value) { $len = strlen($one_param_value); $safe_value = ''; $quote = false; for ($i = 0; $i < $len; ++$i) { $ord = ord($one_param_value[$i]); // Accept only valid characters. if ($ord == 9 || $ord == 32 || $ord == 33 || ($ord >= 35 && $ord <= 126) || $ord >= 128) { $safe_value .= $one_param_value[$i]; // Characters above 128 do not need to be // quoted as per RFC2445 but Outlook requires // this. if ($ord == 44 || $ord == 58 || $ord == 59 || $ord >= 128) { $quote = true; } } } if ($quote) { $safe_value = '"' . $safe_value . '"'; } $one_param_value = $safe_value; } $params_str .= ";$param_name=" . implode(',', $param_value); } } } $value = $attribute['value']; switch ($name) { // Date fields. case 'COMPLETED': case 'CREATED': case 'DCREATED': case 'LAST-MODIFIED': case 'X-MOZ-LASTACK': case 'X-MOZ-SNOOZE-TIME': $value = $this->_exportDateTime($value); break; case 'DTEND': case 'DTSTART': case 'DTSTAMP': case 'DUE': case 'AALARM': case 'RECURRENCE-ID': $floating = $base == 'STANDARD' || $base == 'DAYLIGHT' || isset($params['TZID']); if (isset($params['VALUE'])) { if ($params['VALUE'] == 'DATE') { // VCALENDAR 1.0 uses T000000 - T235959 for all day events: if ($this->_oldFormat && $name == 'DTEND') { $d = new Horde_Date($value); $value = new Horde_Date(array( 'year' => $d->year, 'month' => $d->month, 'mday' => $d->mday - 1)); $value = $this->_exportDate($value, '235959'); } else { $value = $this->_exportDate($value, '000000'); } } else { $value = $this->_exportDateTime($value, $floating); } } else { $value = $this->_exportDateTime($value, $floating); } break; // Comma seperated dates. case 'EXDATE': case 'RDATE': $floating = $base == 'STANDARD' || $base == 'DAYLIGHT'; $dates = array(); foreach ($value as $date) { if (isset($params['VALUE'])) { if ($params['VALUE'] == 'DATE') { $dates[] = $this->_exportDate($date, '000000'); } elseif ($params['VALUE'] == 'PERIOD') { $dates[] = $this->_exportPeriod($date); } else { $dates[] = $this->_exportDateTime($date, $floating); } } else { $dates[] = $this->_exportDateTime($date, $floating); } } $value = implode($this->_oldFormat ? ';' : ',', $dates); 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': if ($this->_oldFormat) { $value = $value['longitude'] . ',' . $value['latitude']; } else { $value = $value['latitude'] . ';' . $value['longitude']; } break; // Recurrence fields. case 'EXRULE': case 'RRULE': break; default: if ($this->_oldFormat) { /* vcard 2.1 and vcalendar 1.0 escape only * semicolons */ if (is_array($attribute['values']) && count($attribute['values'])) { $values = $attribute['values']; if ($name == 'N' || $name == 'ADR' || $name == 'ORG') { $glue = ';'; } else { $glue = ','; } $values = str_replace(';', '\\;', $values); $value = implode($glue, $values); } else { $value = str_replace(';', '\\;', $value); } // Text containing newlines or ASCII >= 127 must be BASE64 // or QUOTED-PRINTABLE encoded. Currently we use // QUOTED-PRINTABLE as default. if (preg_match("/[^\x20-\x7F]/", $value) && empty($params['ENCODING'])) { $params['ENCODING'] = 'QUOTED-PRINTABLE'; $params_str .= ';ENCODING=QUOTED-PRINTABLE'; // Add CHARSET as well. At least the synthesis client // gets confused otherwise if (empty($params['CHARSET'])) { $params['CHARSET'] = 'UTF-8'; $params_str .= ';CHARSET=' . $params['CHARSET']; } } } else { if (is_array($attribute['values']) && count($attribute['values'])) { $values = $attribute['values']; if ($name == 'N' || $name == 'ADR' || $name == 'ORG') { $glue = ';'; } else { $glue = ','; } // As of rfc 2426 2.5 semicolon and comma must be // escaped. $values = str_replace(array('\\', ';', ','), array('\\\\', '\\;', '\\,'), $values); $value = implode($glue, $values); } else { // As of rfc 2426 2.5 semicolon and comma must be // escaped. $value = str_replace(array('\\', ';', ','), array('\\\\', '\\;', '\\,'), $value); } $value = preg_replace('/\r?\n/', '\n', $value); } break; } $value = str_replace("\r", '', $value); if (!empty($params['ENCODING']) && $params['ENCODING'] == 'QUOTED-PRINTABLE' && strlen(trim($value))) { $result .= $name . $params_str . ':' . preg_replace(array('/(?<!\r)\n/', '/(?<!=)\r\n/'), array("\r\n", "=0D=0A=\r\n "), quoted_printable_encode($value)) . $this->_newline; } else { $attr_string = $name . $params_str . ':' . $value; if (!$this->_oldFormat) { if (isset($params['ENCODING']) && $params['ENCODING'] == 'b') { $attr_string = trim(chunk_split($attr_string, 75, $this->_newline . ' ')); } else { $attr_string = Horde_String::wordwrap($attr_string, 75, $this->_newline . ' ', true, true); } } $result .= $attr_string . $this->_newline; } } $tzs = array(); foreach ($this->_components as $component) { if (!($component instanceof Horde_Icalendar_Vtimezone) || !isset($tzs[$component->getAttribute('TZID')])) { $result .= $component->exportvCalendar(); if ($component instanceof Horde_Icalendar_Vtimezone) { $tzs[$component->getAttribute('TZID')] = true; } } } return $result . 'END:' . $base . $this->_newline; } /** * Parse a UTC Offset field. * * @param $text TODO * * @return TODO */ protected function _parseUtcOffset($text) { $offset = array(); if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) { $offset['ahead'] = (bool)($timeParts[1] == '+'); $offset['hour'] = intval($timeParts[2]); $offset['minute'] = intval($timeParts[3]); if (isset($timeParts[4])) { $offset['second'] = intval($timeParts[4]); } return $offset; } return false; } /** * Export a UTC Offset field. * * @param $value TODO * * @return TODO */ function _exportUtcOffset($value) { $offset = ($value['ahead'] ? '+' : '-') . sprintf('%02d%02d', $value['hour'], $value['minute']); if (isset($value['second'])) { $offset .= sprintf('%02d', $value['second']); } return $offset; } /** * Parse a Time Period field. * * @param $text TODO * * @return array TODO */ protected function _parsePeriod($text) { $periodParts = explode('/', $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. * * @param $value TODO * * @return TODO */ protected function _exportPeriod($value) { $period = $this->_exportDateTime($value['start']) . '/'; return isset($value['duration']) ? $period . $this->_exportDuration($value['duration']) : $period . $this->_exportDateTime($value['end']); } /** * Groks the TZID and returns an offset in seconds from UTC for this * date and time. * * @param array $date A date hash. * @param array $time A time hash. * @param string $tzid A timezone ID. * * @return integer The offset from UTC in seconds for the provided * timezone and date/time. */ protected function _parseTZID($date, $time, $tzid) { $vtimezone = $this->_container ->findComponentByAttribute('vtimezone', 'TZID', $tzid); if (!$vtimezone) { return false; } $change_times = array(); foreach ($vtimezone->getComponents() as $o) { $change_times = array_merge( $change_times, $vtimezone->parseChild($o, $date['year']) ); } if (!$change_times) { return false; } usort( $change_times, function($a, $b) { if (!$a['end']) { if (!$b['end']) { return $a['time'] - $b['time']; } return 1; } if (!$b['end']) { return -1; } return Horde_Icalendar::_getEndDifference($a['end'], $b['end']); } ); // Time is arbitrarily based on UTC for comparison. $t = @gmmktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']); // First check for the first change time that isn't expired (from POV of // $time) and is after $t. $n = count($change_times); for ($i = 0, $n = count($change_times); $i < $n -1; $i++) { if (!$this->_checkEndDate($t, $change_times[$i])) { continue; } if ($t < $change_times[$i]['time']) { return $change_times[$i]['from']; } else { break; } } for ($i = 0, $n = count($change_times); $i < $n - 1; $i++) { // See Bug: 14153. Some timezone definitions may be such that a // transition will incorrectly match due to the way we parse the // 'end' times. There *may* be a more correct way to do this by // sorting the transitions/handling 'end' values differently. if (($t >= $change_times[$i]['time']) && ($t < $change_times[$i + 1]['time']) && $this->_checkEndDate($t, $change_times[$i + 1])) { return $change_times[$i]['to']; } } if ($t >= $change_times[$n - 1]['time']) { return $change_times[$n - 1]['to']; } return false; } /** * Utility method to aid in checking the end date of a transition. * * @param integer $t The timestamp of the date we are checking. * @param array $times A transition array. * * @return boolean True if $t is before the end date of the transition * otherwise false. */ protected function _checkEndDate($t, $times) { if (empty($times['end'])) { return true; } if (strlen($times['end']) == 4) { $date = @gmmktime(0, 0, 0, 1, 1, $times['end']); return ($date && $t < $date); } return ($t < $times['end']); } /** * Returns the difference between the datetime indicated by $a and the * datetime indicated by $b after normalizing both values to a unix * timestamp. Used when sorting timezone transitions that may contain * mixed format end times. * * @param mixed Either a string representing a 4 digit year, or unix * timestamp. * @param mixed Either a string representing a 4 digit year, or unix * timestamp. * * @return boolean True if $a < $b otherwise false. * @todo This needs to be public/static due to it being called from a * anonymous function. See PR: 213. This can be removed once we * no longer support PHP 5.3. */ public static function _getEndDifference($a, $b) { if (strlen($a) == 4) { $a = @gmmktime(0, 0, 0, 1, 1, $a); } if (strlen($b) == 4) { $b = @gmmktime(0, 0, 0, 1, 1, $b); } return $a - $b; } /** * Parses a DateTime field and returns a unix timestamp. If the * field cannot be parsed then the original text is returned * unmodified. * * @todo This function should be moved to Horde_Date and made public. * * @param string $text The Icalendar datetime field value. * @param string $tzid A timezone identifier. * * @return integer A unix timestamp. */ public function _parseDateTime($text, $tzid = false) { $dateParts = explode('T', $text); if (count($dateParts) != 2 && !empty($text)) { // Not a datetime field but may be just a date field. if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text)) { // Or not return $text; } $dateParts = array($text, '000000'); } if (!($date = $this->_parseDate($dateParts[0])) || !($time = $this->_parseTime($dateParts[1]))) { return $text; } // Get timezone info for date fields from $tzid and container. $tzoffset = ($time['zone'] == 'Local' && $tzid && ($this->_container instanceof Horde_Icalendar)) ? $this->_parseTZID($date, $time, $tzid) : false; if ($time['zone'] == 'UTC' || $tzoffset !== false) { $result = @gmmktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']); if ($result !== false && $tzoffset) { $result -= $tzoffset; } } else { // We don't know the timezone so assume local timezone. $result = @mktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']); } return ($result !== false) ? $result : $text; } /** * Export a DateTime field. * * @todo A bunch of code calls this function outside this class, so it * needs to be marked public for now. * * @param integer|object|array $value The time value to export (either a * Horde_Date, array, or timestamp). * @param boolean $floating Whether to return a floating * date-time (without time zone * information). * * @return string The string representation of the datetime value. */ public function _exportDateTime($value, $floating = false) { $date = new Horde_Date($value); return $date->toICalendar($floating); } /** * Parses a Time field. * * @param $text TODO * * @return TODO */ protected function _parseTime($text) { if (!preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) { return false; } return array( 'hour' => $timeParts[1], 'minute' => $timeParts[2], 'second' => $timeParts[3], 'zone' => isset($timeParts[4]) ? 'UTC' : 'Local' ); } /** * Parses a Date field. * * @param $text TODO * * @return array TODO */ public function _parseDate($text) { $parts = explode('T', $text); if (count($parts) == 2) { $text = $parts[0]; } if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) { return false; } return array( 'year' => $match[1], 'month' => $match[2], 'mday' => $match[3] ); } /** * Exports a date field. * * @param object|array $value Date object or hash. * @param string $autoconvert If set, use this as time part to export the * date as datetime when exporting to Vcalendar * 1.0. Examples: '000000' or '235959' * * @return TODO */ protected function _exportDate($value, $autoconvert = false) { if (is_object($value)) { $value = array('year' => $value->year, 'month' => $value->month, 'mday' => $value->mday); } return ($autoconvert !== false && $this->_oldFormat) ? sprintf('%04d%02d%02dT%s', $value['year'], $value['month'], $value['mday'], $autoconvert) : sprintf('%04d%02d%02d', $value['year'], $value['month'], $value['mday']); } /** * Parses a DURATION value field. * * @param string $text A DURATION value. * * @return integer The duration in seconds. */ protected 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)) { return false; } // 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; } /** * Export a duration value. * * @param $value TODO */ protected 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'; } } elseif ($duration === 'P') { // Duration without time ("P") is NOT valid, append 0 seconds // ("T0S"). $duration .= 'T0S'; } return $duration; } }
Simpan