Overview

Packages

  • Icalendar

Classes

  • Horde_Icalendar
  • Horde_Icalendar_Daylight
  • Horde_Icalendar_Exception
  • Horde_Icalendar_Standard
  • Horde_Icalendar_Translation
  • Horde_Icalendar_Valarm
  • Horde_Icalendar_Vcard
  • Horde_Icalendar_Vevent
  • Horde_Icalendar_Vfreebusy
  • Horde_Icalendar_Vjournal
  • Horde_Icalendar_Vnote
  • Horde_Icalendar_Vtimezone
  • Horde_Icalendar_Vtodo
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * @category Horde
   4:  * @package  Icalendar
   5:  */
   6: 
   7: /**
   8:  * Class representing iCalendar files.
   9:  *
  10:  * Copyright 2003-2012 Horde LLC (http://www.horde.org/)
  11:  *
  12:  * See the enclosed file COPYING for license information (LGPL). If you
  13:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  14:  *
  15:  * @author   Mike Cochrane <mike@graftonhall.co.nz>
  16:  * @category Horde
  17:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
  18:  * @package  Icalendar
  19:  */
  20: class Horde_Icalendar
  21: {
  22:     /**
  23:      * The component type of this class.
  24:      *
  25:      * @var string
  26:      */
  27:     public $type = 'vcalendar';
  28: 
  29:     /**
  30:      * The parent (containing) iCalendar object.
  31:      *
  32:      * @var Horde_Icalendar
  33:      */
  34:     protected $_container = false;
  35: 
  36:     /**
  37:      * The name/value pairs of attributes for this object (UID,
  38:      * DTSTART, etc.). Which are present depends on the object and on
  39:      * what kind of component it is.
  40:      *
  41:      * @var array
  42:      */
  43:     protected $_attributes = array();
  44: 
  45:     /**
  46:      * Any children (contained) iCalendar components of this object.
  47:      *
  48:      * @var array
  49:      */
  50:     protected $_components = array();
  51: 
  52:     /**
  53:      * According to RFC 2425, we should always use CRLF-terminated lines.
  54:      *
  55:      * @var string
  56:      */
  57:     protected $_newline = "\r\n";
  58: 
  59:     /**
  60:      * iCalendar format version (different behavior for 1.0 and 2.0 especially
  61:      * with recurring events).
  62:      *
  63:      * @var string
  64:      */
  65:     protected $_version;
  66: 
  67:     /**
  68:      * Whether entry is vcalendar 1.0, vcard 2.1 or vnote 1.1.
  69:      *
  70:      * These 'old' formats are defined by www.imc.org. The 'new' (non-old)
  71:      * formats icalendar 2.0 and vcard 3.0 are defined in rfc2426 and rfc2445
  72:      * respectively.
  73:      */
  74:     protected $_oldFormat = true;
  75: 
  76:     /**
  77:      * Constructor.
  78:      *
  79:      * @var string $version  Version.
  80:      */
  81:     public function __construct($version = '2.0')
  82:     {
  83:         $this->setAttribute('VERSION', $version);
  84:     }
  85: 
  86:     /**
  87:      * Return a reference to a new component.
  88:      *
  89:      * @param string $type                The type of component to return
  90:      * @param Horde_Icalendar $container  A container that this component
  91:      *                                    will be associated with.
  92:      *
  93:      * @return object  Reference to a Horde_Icalendar_* object as specified.
  94:      */
  95:     static public function newComponent($type, $container)
  96:     {
  97:         $type = Horde_String::lower($type);
  98:         $class = __CLASS__ . '_' . Horde_String::ucfirst($type);
  99: 
 100:         if (class_exists($class)) {
 101:             $component = new $class();
 102:             if ($container !== false) {
 103:                 $component->_container = $container;
 104:                 // Use version of container, not default set by component
 105:                 // constructor.
 106:                 $component->setVersion($container->getAttribute('VERSION'));
 107:             }
 108:         } else {
 109:             // Should return an dummy x-unknown type class here.
 110:             $component = false;
 111:         }
 112: 
 113:         return $component;
 114:     }
 115: 
 116:     /**
 117:      * Sets the version of this component.
 118:      *
 119:      * @see $version
 120:      * @see $oldFormat
 121:      *
 122:      * @param string $version  A float-like version string.
 123:      */
 124:     public function setVersion($version)
 125:     {
 126:         $this->_oldFormat = $version < 2;
 127:         $this->_version = $version;
 128:     }
 129: 
 130:     /**
 131:      * Sets the value of an attribute.
 132:      *
 133:      * @param string $name     The name of the attribute.
 134:      * @param string $value    The value of the attribute.
 135:      * @param array $params    Array containing any addition parameters for
 136:      *                         this attribute.
 137:      * @param boolean $append  True to append the attribute, False to replace
 138:      *                         the first matching attribute found.
 139:      * @param array $values    Array representation of $value.  For
 140:      *                         comma/semicolon seperated lists of values.  If
 141:      *                         not set use $value as single array element.
 142:      */
 143:     public function setAttribute($name, $value, $params = array(),
 144:                                  $append = true, $values = false)
 145:     {
 146:         // Make sure we update the internal format version if
 147:         // setAttribute('VERSION', ...) is called.
 148:         if ($name == 'VERSION') {
 149:             $this->setVersion($value);
 150:             if ($this->_container !== false) {
 151:                 $this->_container->setVersion($value);
 152:             }
 153:         }
 154: 
 155:         if (!$values) {
 156:             $values = array($value);
 157:         }
 158:         $found = false;
 159: 
 160:         if (!$append) {
 161:             foreach (array_keys($this->_attributes) as $key) {
 162:                 if ($this->_attributes[$key]['name'] == Horde_String::upper($name)) {
 163:                     $this->_attributes[$key]['params'] = $params;
 164:                     $this->_attributes[$key]['value'] = $value;
 165:                     $this->_attributes[$key]['values'] = $values;
 166:                     $found = true;
 167:                     break;
 168:                 }
 169:             }
 170:         }
 171: 
 172:         if ($append || !$found) {
 173:             $this->_attributes[] = array(
 174:                 'name'      => Horde_String::upper($name),
 175:                 'params'    => $params,
 176:                 'value'     => $value,
 177:                 'values'    => $values
 178:             );
 179:         }
 180:     }
 181: 
 182:     /**
 183:      * Sets parameter(s) for an (already existing) attribute.  The
 184:      * parameter set is merged into the existing set.
 185:      *
 186:      * @param string $name   The name of the attribute.
 187:      * @param array $params  Array containing any additional parameters for
 188:      *                       this attribute.
 189:      *
 190:      * @return boolean  True on success, false if no attribute $name exists.
 191:      */
 192:     public function setParameter($name, $params = array())
 193:     {
 194:         $keys = array_keys($this->_attributes);
 195:         foreach ($keys as $key) {
 196:             if ($this->_attributes[$key]['name'] == $name) {
 197:                 $this->_attributes[$key]['params'] = array_merge($this->_attributes[$key]['params'], $params);
 198:                 return true;
 199:             }
 200:         }
 201: 
 202:         return false;
 203:     }
 204: 
 205:     /**
 206:      * Get the value of an attribute.
 207:      *
 208:      * @param string $name     The name of the attribute.
 209:      * @param boolean $params  Return the parameters for this attribute instead
 210:      *                         of its value.
 211:      *
 212:      * @return mixed (string)  The value of the attribute.
 213:      *               (array)   The parameters for the attribute or
 214:      *                         multiple values for an attribute.
 215:      * @throws Horde_Icalendar_Exception
 216:      */
 217:     public function getAttribute($name, $params = false)
 218:     {
 219:         if ($name == 'VERSION') {
 220:             return $this->_version;
 221:         }
 222: 
 223:         $result = array();
 224:         foreach ($this->_attributes as $attribute) {
 225:             if ($attribute['name'] == $name) {
 226:                 $result[] = $params
 227:                     ? $attribute['params']
 228:                     : $attribute['value'];
 229:             }
 230:         }
 231: 
 232:         if (!count($result)) {
 233:             throw new Horde_Icalendar_Exception('Attribute "' . $name . '" Not Found');
 234:         } elseif (count($result) == 1 && !$params) {
 235:             return $result[0];
 236:         }
 237: 
 238:         return $result;
 239:     }
 240: 
 241:     /**
 242:      * Gets the values of an attribute as an array.  Multiple values
 243:      * are possible due to:
 244:      *
 245:      *  a) multiple occurences of 'name'
 246:      *  b) (unsecapd) comma seperated lists.
 247:      *
 248:      * So for a vcard like "KEY:a,b\nKEY:c" getAttributesValues('KEY')
 249:      * will return array('a', 'b', 'c').
 250:      *
 251:      * @param string $name  The name of the attribute.
 252:      *
 253:      * @return array  Multiple values for an attribute.
 254:      * @throws Horde_Icalendar_Exception
 255:      */
 256:     public function getAttributeValues($name)
 257:     {
 258:         $result = array();
 259:         foreach ($this->_attributes as $attribute) {
 260:             if ($attribute['name'] == $name) {
 261:                 $result = array_merge($attribute['values'], $result);
 262:             }
 263:         }
 264: 
 265:         if (!count($result)) {
 266:             throw new Horde_Icalendar_Exception('Attribute "' . $name . '" Not Found');
 267:         }
 268: 
 269:         return $result;
 270:     }
 271: 
 272:     /**
 273:      * Returns the value of an attribute, or a specified default value
 274:      * if the attribute does not exist.
 275:      *
 276:      * @param string $name    The name of the attribute.
 277:      * @param mixed $default  What to return if the attribute specified by
 278:      *                        $name does not exist.
 279:      *
 280:      * @return mixed (string) The value of $name.
 281:      *               (mixed)  $default if $name does not exist.
 282:      */
 283:     public function getAttributeDefault($name, $default = '')
 284:     {
 285:         try {
 286:             return $this->getAttribute($name);
 287:         } catch (Horde_Icalendar_Exception $e) {
 288:             return $default;
 289:         }
 290:     }
 291: 
 292:     /**
 293:      * Remove all occurences of an attribute.
 294:      *
 295:      * @param string $name  The name of the attribute.
 296:      */
 297:     public function removeAttribute($name)
 298:     {
 299:         foreach (array_keys($this->_attributes) as $key) {
 300:             if ($this->_attributes[$key]['name'] == $name) {
 301:                 unset($this->_attributes[$key]);
 302:             }
 303:         }
 304:     }
 305: 
 306:     /**
 307:      * Get attributes for all tags or for a given tag.
 308:      *
 309:      * @param string $tag  Return attributes for this tag, or all attributes
 310:      *                     if not given.
 311:      *
 312:      * @return array  An array containing all the attributes and their types.
 313:      */
 314:     public function getAllAttributes($tag = false)
 315:     {
 316:         if ($tag === false) {
 317:             return $this->_attributes;
 318:         }
 319: 
 320:         $result = array();
 321:         foreach ($this->_attributes as $attribute) {
 322:             if ($attribute['name'] == $tag) {
 323:                 $result[] = $attribute;
 324:             }
 325:         }
 326: 
 327:         return $result;
 328:     }
 329: 
 330:     /**
 331:      * Add a vCalendar component (eg vEvent, vTimezone, etc.).
 332:      *
 333:      * @param mixed  Either a Horde_Icalendar component (subclass) or an array
 334:      *               of them.
 335:      */
 336:     public function addComponent($components)
 337:     {
 338:         if (!is_array($components)) {
 339:             $components = array($components);
 340:         }
 341: 
 342:         foreach ($components as $component) {
 343:             if ($component instanceof Horde_Icalendar) {
 344:                 $component->_container = $this;
 345:                 $this->_components[] = $component;
 346:             }
 347:         }
 348:     }
 349: 
 350:     /**
 351:      * Retrieve all the components.
 352:      *
 353:      * @return array  Array of Horde_Icalendar objects.
 354:      */
 355:     public function getComponents()
 356:     {
 357:         return $this->_components;
 358:     }
 359: 
 360:     /**
 361:      * TODO
 362:      *
 363:      * @return TODO
 364:      */
 365:     public function getType()
 366:     {
 367:         return $this->type;
 368:     }
 369: 
 370:     /**
 371:      * Return the classes (entry types) we have.
 372:      *
 373:      * @return array  Hash with class names Horde_Icalendar_xxx as keys
 374:      *                and number of components of this class as value.
 375:      */
 376:     public function getComponentClasses()
 377:     {
 378:         $r = array();
 379: 
 380:         foreach ($this->_components as $c) {
 381:             $cn = strtolower(get_class($c));
 382:             if (empty($r[$cn])) {
 383:                 $r[$cn] = 1;
 384:             } else {
 385:                 ++$r[$cn];
 386:             }
 387:         }
 388: 
 389:         return $r;
 390:     }
 391: 
 392:     /**
 393:      * Number of components in this container.
 394:      *
 395:      * @return integer  Number of components in this container.
 396:      */
 397:     public function getComponentCount()
 398:     {
 399:         return count($this->_components);
 400:     }
 401: 
 402:     /**
 403:      * Retrieve a specific component.
 404:      *
 405:      * @param integer $idx  The index of the object to retrieve.
 406:      *
 407:      * @return mixed  (boolean) False if the index does not exist.
 408:      *                (Horde_Icalendar_*) The requested component.
 409:      */
 410:     public function getComponent($idx)
 411:     {
 412:         return isset($this->_components[$idx])
 413:             ? $this->_components[$idx]
 414:             : false;
 415:     }
 416: 
 417:     /**
 418:      * Locates the first child component of the specified class, and returns a
 419:      * reference to it.
 420:      *
 421:      * @param string $type  The type of component to find.
 422:      *
 423:      * @return boolean|Horde_Icalendar_*  False if no subcomponent of the
 424:      *                                    specified class exists or the
 425:      *                                    requested component.
 426:      */
 427:     public function findComponent($childclass)
 428:     {
 429:         $childclass = __CLASS__ . '_' . Horde_String::lower($childclass);
 430: 
 431:         foreach (array_keys($this->_components) as $key) {
 432:             if ($this->_components[$key] instanceof $childclass) {
 433:                 return $this->_components[$key];
 434:             }
 435:         }
 436: 
 437:         return false;
 438:     }
 439: 
 440:     /**
 441:      * Locates the first matching child component of the specified class, and
 442:      * returns a reference to it.
 443:      *
 444:      * @param string $childclass  The type of component to find.
 445:      * @param string $attribute   This attribute must be set in the component
 446:      *                            for it to match.
 447:      * @param string $value       Optional value that $attribute must match.
 448:      *
 449:      * @return boolean|Horde_Icalendar_*  False if no matching subcomponent
 450:      *                                    of the specified class exists, or
 451:      *                                    the requested component.
 452:      */
 453:     public function findComponentByAttribute($childclass, $attribute,
 454:                                              $value = null)
 455:     {
 456:         $childclass = __CLASS__ . '_' . Horde_String::lower($childclass);
 457: 
 458:         foreach (array_keys($this->_components) as $key) {
 459:             if ($this->_components[$key] instanceof $childclass) {
 460:                 try {
 461:                     $attr = $this->_components[$key]->getAttribute($attribute);
 462:                 } catch (Horde_Icalendar_Exception $e) {
 463:                     continue;
 464:                 }
 465: 
 466:                 if (is_null($value) || $value == $attr) {
 467:                     return $this->_components[$key];
 468:                 }
 469:             }
 470:         }
 471: 
 472:         return false;
 473:     }
 474: 
 475:     /**
 476:      * Clears the iCalendar object (resets the components and attributes
 477:      * arrays).
 478:      */
 479:     public function clear()
 480:     {
 481:         $this->_attributes = $this->_components = array();
 482:     }
 483: 
 484:     /**
 485:      * Export as vCalendar format.
 486:      *
 487:      * @return TODO
 488:      */
 489:     public function exportvCalendar()
 490:     {
 491:         // Default values.
 492:         // TODO: HORDE_VERSION does not exist.
 493:         $requiredAttributes['PRODID'] = '-//The Horde Project//Horde iCalendar Library' . (defined('HORDE_VERSION') ? ', Horde ' . constant('HORDE_VERSION') : '') . '//EN';
 494:         $requiredAttributes['METHOD'] = 'PUBLISH';
 495: 
 496:         foreach ($requiredAttributes as $name => $default_value) {
 497:             try {
 498:                 $this->getAttribute($name);
 499:             } catch (Horde_Icalendar_Exception $e) {
 500:                 $this->setAttribute($name, $default_value);
 501:             }
 502:         }
 503: 
 504:         return $this->_exportvData('VCALENDAR');
 505:     }
 506: 
 507:     /**
 508:      * Export this entry as a hash array with tag names as keys.
 509:      *
 510:      * @param boolean $paramsInKeys  If false, the operation can be quite
 511:      *                               lossy as the parameters are ignored when
 512:      *                               building the array keys.
 513:      *                               So if you export a vcard with
 514:      *                               LABEL;TYPE=WORK:foo
 515:      *                               LABEL;TYPE=HOME:bar
 516:      *                               the resulting hash contains only one
 517:      *                               label field!
 518:      *                               If set to true, array keys look like
 519:      *                               'LABEL;TYPE=WORK'
 520:      *
 521:      * @return array  A hash array with tag names as keys.
 522:      */
 523:     public function toHash($paramsInKeys = false)
 524:     {
 525:         $hash = array();
 526: 
 527:         foreach ($this->_attributes as $a)  {
 528:             $k = $a['name'];
 529:             if ($paramsInKeys && is_array($a['params'])) {
 530:                 foreach ($a['params'] as $p => $v) {
 531:                     $k .= ";$p=$v";
 532:                 }
 533:             }
 534:             $hash[$k] = $a['value'];
 535:         }
 536: 
 537:         return $hash;
 538:     }
 539: 
 540:     /**
 541:      * Parses a string containing vCalendar data.
 542:      *
 543:      * @todo This method doesn't work well at all, if $base is VCARD.
 544:      *
 545:      * @param string $text     The data to parse.
 546:      * @param string $base     The type of the base object.
 547:      * @param boolean $clear   If true clears this object before parsing.
 548:      *
 549:      * @return boolean  True on successful import, false otherwise.
 550:      * @throws Horde_Icalendar_Exception
 551:      */
 552:     public function parsevCalendar($text, $base = 'VCALENDAR', $clear = true)
 553:     {
 554:         if ($clear) {
 555:             $this->clear();
 556:         }
 557: 
 558:         $text = Horde_String::trimUtf8Bom($text);
 559: 
 560:         if (preg_match('/^BEGIN:' . $base . '(.*)^END:' . $base . '/ism', $text, $matches)) {
 561:             $container = true;
 562:             $vCal = $matches[1];
 563:         } else {
 564:             // Text isn't enclosed in BEGIN:VCALENDAR
 565:             // .. END:VCALENDAR. We'll try to parse it anyway.
 566:             $container = false;
 567:             $vCal = $text;
 568:         }
 569:         $vCal = trim($vCal);
 570: 
 571:         // Extract all subcomponents.
 572:         $matches = $components = null;
 573:         if (preg_match_all('/^BEGIN:(.*)\s*?(\r\n|\r|\n)(.*)^END:\1\s*?/Uims', $vCal, $components)) {
 574:             foreach ($components[0] as $key => $data) {
 575:                 // Remove from the vCalendar data.
 576:                 $vCal = str_replace($data, '', $vCal);
 577:             }
 578:         } elseif (!$container) {
 579:             return false;
 580:         }
 581: 
 582:         // Unfold "quoted printable" folded lines like:
 583:         //  BODY;ENCODING=QUOTED-PRINTABLE:=
 584:         //  another=20line=
 585:         //  last=20line
 586:         while (preg_match_all('/^([^:]+;\s*(ENCODING=)?QUOTED-PRINTABLE(.*=\r?\n)+(.*[^=])?(\r?\n|$))/mU', $vCal, $matches)) {
 587:             foreach ($matches[1] as $s) {
 588:                 $r = preg_replace('/=\r?\n/', '', $s);
 589:                 $vCal = str_replace($s, $r, $vCal);
 590:             }
 591:         }
 592: 
 593:         // Unfold any folded lines.
 594:         $vCal = preg_replace('/[\r\n]+[ \t]/', '', $vCal);
 595: 
 596:         // Parse the remaining attributes.
 597:         if (preg_match_all('/^((?:[^":]+|(?:"[^"]*")+)*):([^\r\n]*)\r?$/m', $vCal, $matches)) {
 598:             foreach ($matches[0] as $attribute) {
 599:                 preg_match('/([^;^:]*)((;(?:[^":]+|(?:"[^"]*")+)*)?):([^\r\n]*)[\r\n]*/', $attribute, $parts);
 600:                 $tag = trim(Horde_String::upper($parts[1]));
 601:                 $value = $parts[4];
 602:                 $params = array();
 603: 
 604:                 // Parse parameters.
 605:                 if (!empty($parts[2])) {
 606:                     preg_match_all('/;(([^;=]*)(=("[^"]*"|[^;]*))?)/', $parts[2], $param_parts);
 607:                     foreach ($param_parts[2] as $key => $paramName) {
 608:                         $paramName = Horde_String::upper($paramName);
 609:                         $paramValue = $param_parts[4][$key];
 610:                         if ($paramName == 'TYPE') {
 611:                             $paramValue = preg_split('/(?<!\\\\),/', $paramValue);
 612:                             if (count($paramValue) == 1) {
 613:                                 $paramValue = $paramValue[0];
 614:                             }
 615:                         }
 616:                         if (is_string($paramValue)) {
 617:                             if (preg_match('/"([^"]*)"/', $paramValue, $parts)) {
 618:                                 $paramValue = $parts[1];
 619:                             }
 620:                         } else {
 621:                             foreach ($paramValue as $k => $tmp) {
 622:                                 if (preg_match('/"([^"]*)"/', $tmp, $parts)) {
 623:                                     $paramValue[$k] = $parts[1];
 624:                                 }
 625:                             }
 626:                         }
 627:                         $params[$paramName] = $paramValue;
 628:                     }
 629:                 }
 630: 
 631:                 // Charset and encoding handling.
 632:                 if ((isset($params['ENCODING']) &&
 633:                      Horde_String::upper($params['ENCODING']) == 'QUOTED-PRINTABLE') ||
 634:                     isset($params['QUOTED-PRINTABLE'])) {
 635: 
 636:                     $value = quoted_printable_decode($value);
 637:                     if (isset($params['CHARSET'])) {
 638:                         $value = Horde_String::convertCharset($value, $params['CHARSET'], 'UTF-8');
 639:                      }
 640:                 } elseif (isset($params['CHARSET'])) {
 641:                     $value = Horde_String::convertCharset($value, $params['CHARSET'], 'UTF-8');
 642:                 }
 643: 
 644:                 // Get timezone info for date fields from $params.
 645:                 $tzid = isset($params['TZID']) ? trim($params['TZID'], '\"') : false;
 646: 
 647:                 switch ($tag) {
 648:                 // Date fields.
 649:                 case 'COMPLETED':
 650:                 case 'CREATED':
 651:                 case 'LAST-MODIFIED':
 652:                 case 'X-MOZ-LASTACK':
 653:                 case 'X-MOZ-SNOOZE-TIME':
 654:                     $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
 655:                     break;
 656: 
 657:                 case 'BDAY':
 658:                 case 'X-ANNIVERSARY':
 659:                     $this->setAttribute($tag, $this->_parseDate($value), $params);
 660:                     break;
 661: 
 662:                 case 'DTEND':
 663:                 case 'DTSTART':
 664:                 case 'DTSTAMP':
 665:                 case 'DUE':
 666:                 case 'AALARM':
 667:                 case 'RECURRENCE-ID':
 668:                     // types like AALARM may contain additional data after a ;
 669:                     // ignore these.
 670:                     $ts = explode(';', $value);
 671:                     if (isset($params['VALUE']) && $params['VALUE'] == 'DATE') {
 672:                         $this->setAttribute($tag, $this->_parseDate($ts[0]), $params);
 673:                     } else {
 674:                         $this->setAttribute($tag, $this->_parseDateTime($ts[0], $tzid), $params);
 675:                     }
 676:                     break;
 677: 
 678:                 case 'TRIGGER':
 679:                     if (isset($params['VALUE']) &&
 680:                         $params['VALUE'] == 'DATE-TIME') {
 681:                             $this->setAttribute($tag, $this->_parseDateTime($value, $tzid), $params);
 682:                     } else {
 683:                         $this->setAttribute($tag, $this->_parseDuration($value), $params);
 684:                     }
 685:                     break;
 686: 
 687:                 // Comma seperated dates.
 688:                 case 'EXDATE':
 689:                 case 'RDATE':
 690:                     if (!strlen($value)) {
 691:                         break;
 692:                     }
 693:                     $dates = array();
 694:                     $separator = $this->_oldFormat ? ';' : ',';
 695:                     preg_match_all('/' . $separator . '([^' . $separator . ']*)/', $separator . $value, $values);
 696: 
 697:                     foreach ($values[1] as $value) {
 698:                         $stamp = $this->_parseDateTime($value);
 699:                         $dates[] = array('year' => date('Y', $stamp),
 700:                                          'month' => date('m', $stamp),
 701:                                          'mday' => date('d', $stamp));
 702:                     }
 703:                     $this->setAttribute($tag, isset($dates[0]) ? $dates[0] : null, $params, true, $dates);
 704:                     break;
 705: 
 706:                 // Duration fields.
 707:                 case 'DURATION':
 708:                     $this->setAttribute($tag, $this->_parseDuration($value), $params);
 709:                     break;
 710: 
 711:                 // Period of time fields.
 712:                 case 'FREEBUSY':
 713:                     $periods = array();
 714:                     preg_match_all('/,([^,]*)/', ',' . $value, $values);
 715:                     foreach ($values[1] as $value) {
 716:                         $periods[] = $this->_parsePeriod($value);
 717:                     }
 718: 
 719:                     $this->setAttribute($tag, isset($periods[0]) ? $periods[0] : null, $params, true, $periods);
 720:                     break;
 721: 
 722:                 // UTC offset fields.
 723:                 case 'TZOFFSETFROM':
 724:                 case 'TZOFFSETTO':
 725:                     $this->setAttribute($tag, $this->_parseUtcOffset($value), $params);
 726:                     break;
 727: 
 728:                 // Integer fields.
 729:                 case 'PERCENT-COMPLETE':
 730:                 case 'PRIORITY':
 731:                 case 'REPEAT':
 732:                 case 'SEQUENCE':
 733:                     $this->setAttribute($tag, intval($value), $params);
 734:                     break;
 735: 
 736:                 // Geo fields.
 737:                 case 'GEO':
 738:                     if ($value) {
 739:                         if ($this->_oldFormat) {
 740:                             $floats = explode(',', $value);
 741:                             $value = array('latitude' => floatval($floats[1]),
 742:                                            'longitude' => floatval($floats[0]));
 743:                         } else {
 744:                             $floats = explode(';', $value);
 745:                             $value = array('latitude' => floatval($floats[0]),
 746:                                            'longitude' => floatval($floats[1]));
 747:                         }
 748:                     }
 749:                     $this->setAttribute($tag, $value, $params);
 750:                     break;
 751: 
 752:                 // Recursion fields.
 753:                 case 'EXRULE':
 754:                 case 'RRULE':
 755:                     $this->setAttribute($tag, trim($value), $params);
 756:                     break;
 757: 
 758:                 // ADR, ORG and N are lists seperated by unescaped semicolons
 759:                 // with a specific number of slots.
 760:                 case 'ADR':
 761:                 case 'N':
 762:                 case 'ORG':
 763:                     $value = trim($value);
 764:                     // As of rfc 2426 2.4.2 semicolon, comma, and colon must
 765:                     // be escaped (comma is unescaped after splitting below).
 766:                     $value = str_replace(array('\\n', '\\N', '\\;', '\\:'),
 767:                                          array($this->_newline, $this->_newline, ';', ':'),
 768:                                          $value);
 769: 
 770:                     // Split by unescaped semicolons:
 771:                     $values = preg_split('/(?<!\\\\);/', $value);
 772:                     $value = str_replace('\\;', ';', $value);
 773:                     $values = str_replace('\\;', ';', $values);
 774:                     $this->setAttribute($tag, trim($value), $params, true, $values);
 775:                     break;
 776: 
 777:                 // String fields.
 778:                 default:
 779:                     if ($this->_oldFormat) {
 780:                         // vCalendar 1.0 and vCard 2.1 only escape semicolons
 781:                         // and use unescaped semicolons to create lists.
 782:                         $value = trim($value);
 783:                         // Split by unescaped semicolons:
 784:                         $values = preg_split('/(?<!\\\\);/', $value);
 785:                         $value = str_replace('\\;', ';', $value);
 786:                         $values = str_replace('\\;', ';', $values);
 787:                         $this->setAttribute($tag, trim($value), $params, true, $values);
 788:                     } else {
 789:                         $value = trim($value);
 790:                         // As of rfc 2426 2.4.2 semicolon, comma, and colon
 791:                         // must be escaped (comma is unescaped after splitting
 792:                         // below).
 793:                         $value = str_replace(array('\\n', '\\N', '\\;', '\\:', '\\\\'),
 794:                                              array($this->_newline, $this->_newline, ';', ':', '\\'),
 795:                                              $value);
 796: 
 797:                         // Split by unescaped commas.
 798:                         $values = preg_split('/(?<!\\\\),/', $value);
 799:                         $value = str_replace('\\,', ',', $value);
 800:                         $values = str_replace('\\,', ',', $values);
 801: 
 802:                         $this->setAttribute($tag, trim($value), $params, true, $values);
 803:                     }
 804:                     break;
 805:                 }
 806:             }
 807:         }
 808: 
 809:         // Process all components.
 810:         if ($components) {
 811:             // vTimezone components are processed first. They are
 812:             // needed to process vEvents that may use a TZID.
 813:             foreach ($components[0] as $key => $data) {
 814:                 $type = trim($components[1][$key]);
 815:                 if ($type != 'VTIMEZONE') {
 816:                     continue;
 817:                 }
 818:                 $component = $this->newComponent($type, $this);
 819:                 if ($component === false) {
 820:                     throw new Horde_Icalendar_Exception('Unable to create object for type ' . $type);
 821:                 }
 822:                 $component->parsevCalendar($data, $type);
 823: 
 824:                 $this->addComponent($component);
 825: 
 826:                 // Remove from the vCalendar data.
 827:                 $vCal = str_replace($data, '', $vCal);
 828:             }
 829: 
 830:             // Now process the non-vTimezone components.
 831:             foreach ($components[0] as $key => $data) {
 832:                 $type = trim($components[1][$key]);
 833:                 if ($type == 'VTIMEZONE') {
 834:                     continue;
 835:                 }
 836:                 $component = $this->newComponent($type, $this);
 837:                 if ($component === false) {
 838:                     throw new Horde_Icalendar_Exception('Unable to create object for type ' . $type);
 839:                 }
 840:                 $component->parsevCalendar($data, $type);
 841: 
 842:                 $this->addComponent($component);
 843:             }
 844:         }
 845: 
 846:         return true;
 847:     }
 848: 
 849:     /**
 850:      * Export this component in vCal format.
 851:      *
 852:      * @param string $base  The type of the base object.
 853:      *
 854:      * @return string  vCal format data.
 855:      */
 856:     protected function _exportvData($base = 'VCALENDAR')
 857:     {
 858:         $result = 'BEGIN:' . Horde_String::upper($base) . $this->_newline;
 859: 
 860:         // VERSION is not allowed for entries enclosed in VCALENDAR/ICALENDAR,
 861:         // as it is part of the enclosing VCALENDAR/ICALENDAR. See rfc2445
 862:         if ($base !== 'VEVENT' && $base !== 'VTODO' && $base !== 'VALARM' &&
 863:             $base !== 'VJOURNAL' && $base !== 'VFREEBUSY' &&
 864:             $base != 'VTIMEZONE' && $base != 'STANDARD' && $base != 'DAYLIGHT') {
 865:             // Ensure that version is the first attribute.
 866:             $result .= 'VERSION:' . $this->_version . $this->_newline;
 867:         }
 868:         foreach ($this->_attributes as $attribute) {
 869:             $name = $attribute['name'];
 870:             if ($name == 'VERSION') {
 871:                 // Already done.
 872:                 continue;
 873:             }
 874: 
 875:             $params_str = '';
 876:             $params = $attribute['params'];
 877:             if ($params) {
 878:                 foreach ($params as $param_name => $param_value) {
 879:                     /* Skip CHARSET for iCalendar 2.0 data, not allowed. */
 880:                     if ($param_name == 'CHARSET' && !$this->_oldFormat) {
 881:                         continue;
 882:                     }
 883:                     /* Skip VALUE=DATE for vCalendar 1.0 data, not allowed. */
 884:                     if ($this->_oldFormat &&
 885:                         $param_name == 'VALUE' && $param_value == 'DATE') {
 886:                         continue;
 887:                     }
 888: 
 889:                     if ($param_value === null) {
 890:                         $params_str .= ";$param_name";
 891:                     } else {
 892:                         if (!is_array($param_value)) {
 893:                             $param_value = array($param_value);
 894:                         }
 895:                         foreach ($param_value as &$one_param_value) {
 896:                             $len = strlen($one_param_value);
 897:                             $safe_value = '';
 898:                             $quote = false;
 899:                             for ($i = 0; $i < $len; ++$i) {
 900:                                 $ord = ord($one_param_value[$i]);
 901:                                 // Accept only valid characters.
 902:                                 if ($ord == 9 || $ord == 32 || $ord == 33 ||
 903:                                     ($ord >= 35 && $ord <= 126) ||
 904:                                     $ord >= 128) {
 905:                                     $safe_value .= $one_param_value[$i];
 906:                                     // Characters above 128 do not need to be
 907:                                     // quoted as per RFC2445 but Outlook requires
 908:                                     // this.
 909:                                     if ($ord == 44 || $ord == 58 || $ord == 59 ||
 910:                                         $ord >= 128) {
 911:                                         $quote = true;
 912:                                     }
 913:                                 }
 914:                             }
 915:                             if ($quote) {
 916:                                 $safe_value = '"' . $safe_value . '"';
 917:                             }
 918:                             $one_param_value = $safe_value;
 919:                         }
 920:                         $params_str .= ";$param_name=" . implode(',', $param_value);
 921:                     }
 922:                 }
 923:             }
 924: 
 925:             $value = $attribute['value'];
 926:             switch ($name) {
 927:             // Date fields.
 928:             case 'COMPLETED':
 929:             case 'CREATED':
 930:             case 'DCREATED':
 931:             case 'LAST-MODIFIED':
 932:             case 'X-MOZ-LASTACK':
 933:             case 'X-MOZ-SNOOZE-TIME':
 934:                 $value = $this->_exportDateTime($value);
 935:                 break;
 936: 
 937:             case 'DTEND':
 938:             case 'DTSTART':
 939:             case 'DTSTAMP':
 940:             case 'DUE':
 941:             case 'AALARM':
 942:             case 'RECURRENCE-ID':
 943:                 $floating = $base == 'STANDARD' || $base == 'DAYLIGHT';
 944:                 if (isset($params['VALUE'])) {
 945:                     if ($params['VALUE'] == 'DATE') {
 946:                         // VCALENDAR 1.0 uses T000000 - T235959 for all day events:
 947:                         if ($this->_oldFormat && $name == 'DTEND') {
 948:                             $d = new Horde_Date($value);
 949:                             $value = new Horde_Date(array(
 950:                                 'year' => $d->year,
 951:                                 'month' => $d->month,
 952:                                 'mday' => $d->mday - 1));
 953:                             $value = $this->_exportDate($value, '235959');
 954:                         } else {
 955:                             $value = $this->_exportDate($value, '000000');
 956:                         }
 957:                     } else {
 958:                         $value = $this->_exportDateTime($value, $floating);
 959:                     }
 960:                 } else {
 961:                     $value = $this->_exportDateTime($value, $floating);
 962:                 }
 963:                 break;
 964: 
 965:             // Comma seperated dates.
 966:             case 'EXDATE':
 967:             case 'RDATE':
 968:                 $floating = $base == 'STANDARD' || $base == 'DAYLIGHT';
 969:                 $dates = array();
 970:                 foreach ($value as $date) {
 971:                     if (isset($params['VALUE'])) {
 972:                         if ($params['VALUE'] == 'DATE') {
 973:                             $dates[] = $this->_exportDate($date, '000000');
 974:                         } elseif ($params['VALUE'] == 'PERIOD') {
 975:                             $dates[] = $this->_exportPeriod($date);
 976:                         } else {
 977:                             $dates[] = $this->_exportDateTime($date, $floating);
 978:                         }
 979:                     } else {
 980:                         $dates[] = $this->_exportDateTime($date, $floating);
 981:                     }
 982:                 }
 983:                 $value = implode($this->_oldFormat ? ';' : ',', $dates);
 984:                 break;
 985: 
 986:             case 'TRIGGER':
 987:                 if (isset($params['VALUE'])) {
 988:                     if ($params['VALUE'] == 'DATE-TIME') {
 989:                         $value = $this->_exportDateTime($value);
 990:                     } elseif ($params['VALUE'] == 'DURATION') {
 991:                         $value = $this->_exportDuration($value);
 992:                     }
 993:                 } else {
 994:                     $value = $this->_exportDuration($value);
 995:                 }
 996:                 break;
 997: 
 998:             // Duration fields.
 999:             case 'DURATION':
1000:                 $value = $this->_exportDuration($value);
1001:                 break;
1002: 
1003:             // Period of time fields.
1004:             case 'FREEBUSY':
1005:                 $value_str = '';
1006:                 foreach ($value as $period) {
1007:                     $value_str .= empty($value_str) ? '' : ',';
1008:                     $value_str .= $this->_exportPeriod($period);
1009:                 }
1010:                 $value = $value_str;
1011:                 break;
1012: 
1013:             // UTC offset fields.
1014:             case 'TZOFFSETFROM':
1015:             case 'TZOFFSETTO':
1016:                 $value = $this->_exportUtcOffset($value);
1017:                 break;
1018: 
1019:             // Integer fields.
1020:             case 'PERCENT-COMPLETE':
1021:             case 'PRIORITY':
1022:             case 'REPEAT':
1023:             case 'SEQUENCE':
1024:                 $value = "$value";
1025:                 break;
1026: 
1027:             // Geo fields.
1028:             case 'GEO':
1029:                 if ($this->_oldFormat) {
1030:                     $value = $value['longitude'] . ',' . $value['latitude'];
1031:                 } else {
1032:                     $value = $value['latitude'] . ';' . $value['longitude'];
1033:                 }
1034:                 break;
1035: 
1036:             // Recurrence fields.
1037:             case 'EXRULE':
1038:             case 'RRULE':
1039:                 break;
1040: 
1041:             default:
1042:                 if ($this->_oldFormat) {
1043:                     if (is_array($attribute['values']) &&
1044:                         count($attribute['values']) > 1) {
1045:                         $values = $attribute['values'];
1046:                         if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
1047:                             $glue = ';';
1048:                         } else {
1049:                             $glue = ',';
1050:                         }
1051:                         $values = str_replace(';', '\\;', $values);
1052:                         $value = implode($glue, $values);
1053:                     } else {
1054:                         /* vcard 2.1 and vcalendar 1.0 escape only
1055:                          * semicolons */
1056:                         $value = str_replace(';', '\\;', $value);
1057:                     }
1058:                     // Text containing newlines or ASCII >= 127 must be BASE64
1059:                     // or QUOTED-PRINTABLE encoded. Currently we use
1060:                     // QUOTED-PRINTABLE as default.
1061:                     if (preg_match("/[^\x20-\x7F]/", $value) &&
1062:                         empty($params['ENCODING']))  {
1063:                         $params['ENCODING'] = 'QUOTED-PRINTABLE';
1064:                         $params_str .= ';ENCODING=QUOTED-PRINTABLE';
1065:                         // Add CHARSET as well. At least the synthesis client
1066:                         // gets confused otherwise
1067:                         if (empty($params['CHARSET'])) {
1068:                             $params['CHARSET'] = 'UTF-8';
1069:                             $params_str .= ';CHARSET=' . $params['CHARSET'];
1070:                         }
1071:                     }
1072:                 } else {
1073:                     if (is_array($attribute['values']) &&
1074:                         count($attribute['values'])) {
1075:                         $values = $attribute['values'];
1076:                         if ($name == 'N' || $name == 'ADR' || $name == 'ORG') {
1077:                             $glue = ';';
1078:                         } else {
1079:                             $glue = ',';
1080:                         }
1081:                         // As of rfc 2426 2.5 semicolon and comma must be
1082:                         // escaped.
1083:                         $values = str_replace(array('\\', ';', ','),
1084:                                               array('\\\\', '\\;', '\\,'),
1085:                                               $values);
1086:                         $value = implode($glue, $values);
1087:                     } else {
1088:                         // As of rfc 2426 2.5 semicolon and comma must be
1089:                         // escaped.
1090:                         $value = str_replace(array('\\', ';', ','),
1091:                                              array('\\\\', '\\;', '\\,'),
1092:                                              $value);
1093:                     }
1094:                     $value = preg_replace('/\r?\n/', '\n', $value);
1095:                 }
1096:                 break;
1097:             }
1098: 
1099:             $value = str_replace("\r", '', $value);
1100:             if (!empty($params['ENCODING']) &&
1101:                 $params['ENCODING'] == 'QUOTED-PRINTABLE' &&
1102:                 strlen(trim($value))) {
1103:                 $result .= $name . $params_str . ':'
1104:                     . preg_replace(array('/(?<!\r)\n/', '/(?<!=)\r\n/'),
1105:                                    array("\r\n", "=0D=0A=\r\n "),
1106:                                    Horde_Mime::quotedPrintableEncode($value))
1107:                     . $this->_newline;
1108:             } else {
1109:                 $attr_string = $name . $params_str . ':' . $value;
1110:                 if (!$this->_oldFormat) {
1111:                     if (isset($params['ENCODING']) && $params['ENCODING'] == 'b') {
1112:                         $attr_string = chunk_split($attr_string, 75, $this->_newline . ' ');
1113:                     } else {
1114:                         $attr_string = Horde_String::wordwrap($attr_string, 75, $this->_newline . ' ', true, true);
1115:                     }
1116:                 }
1117:                 $result .= $attr_string . $this->_newline;
1118:             }
1119:         }
1120: 
1121:         foreach ($this->_components as $component) {
1122:             $result .= $component->exportvCalendar();
1123:         }
1124: 
1125:         return $result . 'END:' . $base . $this->_newline;
1126:     }
1127: 
1128:     /**
1129:      * Parse a UTC Offset field.
1130:      *
1131:      * @param $text TODO
1132:      *
1133:      * @return TODO
1134:      */
1135:     protected function _parseUtcOffset($text)
1136:     {
1137:         $offset = array();
1138: 
1139:         if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $text, $timeParts)) {
1140:             $offset['ahead']  = (bool)($timeParts[1] == '+');
1141:             $offset['hour']   = intval($timeParts[2]);
1142:             $offset['minute'] = intval($timeParts[3]);
1143:             if (isset($timeParts[4])) {
1144:                 $offset['second'] = intval($timeParts[4]);
1145:             }
1146:             return $offset;
1147:         }
1148: 
1149:         return false;
1150:     }
1151: 
1152:     /**
1153:      * Export a UTC Offset field.
1154:      *
1155:      * @param $value TODO
1156:      *
1157:      * @return TODO
1158:      */
1159:     function _exportUtcOffset($value)
1160:     {
1161:         $offset = ($value['ahead'] ? '+' : '-') .
1162:             sprintf('%02d%02d', $value['hour'], $value['minute']);
1163: 
1164:         if (isset($value['second'])) {
1165:             $offset .= sprintf('%02d', $value['second']);
1166:         }
1167: 
1168:         return $offset;
1169:     }
1170: 
1171:     /**
1172:      * Parse a Time Period field.
1173:      *
1174:      * @param $text TODO
1175:      *
1176:      * @return array  TODO
1177:      */
1178:     protected function _parsePeriod($text)
1179:     {
1180:         $periodParts = explode('/', $text);
1181:         $start = $this->_parseDateTime($periodParts[0]);
1182: 
1183:         if ($duration = $this->_parseDuration($periodParts[1])) {
1184:             return array('start' => $start, 'duration' => $duration);
1185:         } elseif ($end = $this->_parseDateTime($periodParts[1])) {
1186:             return array('start' => $start, 'end' => $end);
1187:         }
1188:     }
1189: 
1190:     /**
1191:      * Export a Time Period field.
1192:      *
1193:      * @param $value TODO
1194:      *
1195:      * @return TODO
1196:      */
1197:     protected function _exportPeriod($value)
1198:     {
1199:         $period = $this->_exportDateTime($value['start']) . '/';
1200: 
1201:         return isset($value['duration'])
1202:             ? $period . $this->_exportDuration($value['duration'])
1203:             : $period . $this->_exportDateTime($value['end']);
1204:     }
1205: 
1206:     /**
1207:      * Grok the TZID and return an offset in seconds from UTC for this
1208:      * date and time.
1209:      *
1210:      * @param $date TODO
1211:      * @param $time TODO
1212:      * @param $tzid TODO
1213:      *
1214:      * @return TODO
1215:      */
1216:     protected function _parseTZID($date, $time, $tzid)
1217:     {
1218:         $vtimezone = $this->_container->findComponentByAttribute('vtimezone', 'TZID', $tzid);
1219:         if (!$vtimezone) {
1220:             return false;
1221:         }
1222: 
1223:         $change_times = array();
1224:         foreach ($vtimezone->getComponents() as $o) {
1225:             $t = $vtimezone->parseChild($o, $date['year']);
1226:             if ($t !== false) {
1227:                 $change_times[] = $t;
1228:             }
1229:         }
1230: 
1231:         if (!$change_times) {
1232:             return false;
1233:         }
1234: 
1235:         sort($change_times);
1236: 
1237:         // Time is arbitrarily based on UTC for comparison.
1238:         $t = @gmmktime($time['hour'], $time['minute'], $time['second'],
1239:                        $date['month'], $date['mday'], $date['year']);
1240: 
1241:         if ($t < $change_times[0]['time']) {
1242:             return $change_times[0]['from'];
1243:         }
1244: 
1245:         for ($i = 0, $n = count($change_times); $i < $n - 1; $i++) {
1246:             if (($t >= $change_times[$i]['time']) &&
1247:                 ($t < $change_times[$i + 1]['time'])) {
1248:                 return $change_times[$i]['to'];
1249:             }
1250:         }
1251: 
1252:         if ($t >= $change_times[$n - 1]['time']) {
1253:             return $change_times[$n - 1]['to'];
1254:         }
1255: 
1256:         return false;
1257:     }
1258: 
1259:     /**
1260:      * Parses a DateTime field and returns a unix timestamp. If the
1261:      * field cannot be parsed then the original text is returned
1262:      * unmodified.
1263:      *
1264:      * @todo This function should be moved to Horde_Date and made public.
1265:      *
1266:      * @param $time TODO
1267:      * @param $tzid TODO
1268:      *
1269:      * @return TODO
1270:      */
1271:     public function _parseDateTime($text, $tzid = false)
1272:     {
1273:         $dateParts = explode('T', $text);
1274:         if (count($dateParts) != 2 && !empty($text)) {
1275:             // Not a datetime field but may be just a date field.
1276:             if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) {
1277:                 // Or not
1278:                 return $text;
1279:             }
1280:             $dateParts = array($text, '000000');
1281:         }
1282: 
1283:         if (!($date = $this->_parseDate($dateParts[0])) ||
1284:             !($time = $this->_parseTime($dateParts[1]))) {
1285:             return $text;
1286:         }
1287: 
1288:         // Get timezone info for date fields from $tzid and container.
1289:         $tzoffset = ($time['zone'] == 'Local' && $tzid &&
1290:                      ($this->_container instanceof Horde_Icalendar))
1291:                      ? $this->_parseTZID($date, $time, $tzid)
1292:                      : false;
1293:         if ($time['zone'] == 'UTC' || $tzoffset !== false) {
1294:             $result = @gmmktime($time['hour'], $time['minute'], $time['second'],
1295:                                 $date['month'], $date['mday'], $date['year']);
1296:             if ($tzoffset) {
1297:                 $result -= $tzoffset;
1298:             }
1299:         } else {
1300:             // We don't know the timezone so assume local timezone.
1301:             // FIXME: shouldn't this be based on the user's timezone
1302:             // preference rather than the server's timezone?
1303:             $result = @mktime($time['hour'], $time['minute'], $time['second'],
1304:                               $date['month'], $date['mday'], $date['year']);
1305:         }
1306: 
1307:         return ($result !== false) ? $result : $text;
1308:     }
1309: 
1310:     /**
1311:      * Export a DateTime field.
1312:      *
1313:      * @todo A bunch of code calls this function outside this class, so it
1314:      * needs to be marked public for now.
1315:      *
1316:      * @param integer|object|array $value  The time value to export (either a
1317:      *                                     Horde_Date, array, or timestamp).
1318:      * @param boolean $floating            Whether to return a floating
1319:      *                                     date-time (without time zone
1320:      *                                     information). @since Horde_Icalendar
1321:      *                                     1.0.5.
1322:      *
1323:      * @return string  The string representation of the datetime value.
1324:      */
1325:     public function _exportDateTime($value, $floating = false)
1326:     {
1327:         $date = new Horde_Date($value);
1328:         return $date->toICalendar($floating);
1329:     }
1330: 
1331:     /**
1332:      * Parses a Time field.
1333:      *
1334:      * @param $text  TODO
1335:      *
1336:      * @return TODO
1337:      */
1338:     protected function _parseTime($text)
1339:     {
1340:         if (!preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $timeParts)) {
1341:             return false;
1342:         }
1343: 
1344:         return array(
1345:             'hour' => $timeParts[1],
1346:             'minute' => $timeParts[2],
1347:             'second' => $timeParts[3],
1348:             'zone' => isset($timeParts[4]) ? 'UTC' : 'Local'
1349:         );
1350:     }
1351: 
1352:     /**
1353:      * Parses a Date field.
1354:      *
1355:      * @param $text TODO
1356:      *
1357:      * @return array TODO
1358:      */
1359:     public function _parseDate($text)
1360:     {
1361:         $parts = explode('T', $text);
1362:         if (count($parts) == 2) {
1363:             $text = $parts[0];
1364:         }
1365: 
1366:         if (!preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $text, $match)) {
1367:             return false;
1368:         }
1369: 
1370:         return array(
1371:             'year' => $match[1],
1372:             'month' => $match[2],
1373:             'mday' => $match[3]
1374:         );
1375:     }
1376: 
1377:     /**
1378:      * Exports a date field.
1379:      *
1380:      * @param object|array $value  Date object or hash.
1381:      * @param string $autoconvert  If set, use this as time part to export the
1382:      *                             date as datetime when exporting to Vcalendar
1383:      *                             1.0. Examples: '000000' or '235959'
1384:      *
1385:      * @return TODO
1386:      */
1387:     protected function _exportDate($value, $autoconvert = false)
1388:     {
1389:         if (is_object($value)) {
1390:             $value = array('year' => $value->year, 'month' => $value->month, 'mday' => $value->mday);
1391:         }
1392: 
1393:         return ($autoconvert !== false && $this->_oldFormat)
1394:             ? sprintf('%04d%02d%02dT%s', $value['year'], $value['month'], $value['mday'], $autoconvert)
1395:             : sprintf('%04d%02d%02d', $value['year'], $value['month'], $value['mday']);
1396:     }
1397: 
1398:     /**
1399:      * Parses a DURATION value field.
1400:      *
1401:      * @param string $text  A DURATION value.
1402:      *
1403:      * @return integer  The duration in seconds.
1404:      */
1405:     protected function _parseDuration($text)
1406:     {
1407:         if (!preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $durvalue)) {
1408:             return false;
1409:         }
1410: 
1411:         // Weeks.
1412:         $duration = 7 * 86400 * intval($durvalue[3]);
1413: 
1414:         if (count($durvalue) > 4) {
1415:             // Days.
1416:             $duration += 86400 * intval($durvalue[4]);
1417:         }
1418: 
1419:         if (count($durvalue) > 5) {
1420:             // Hours.
1421:             $duration += 3600 * intval($durvalue[7]);
1422: 
1423:             // Mins.
1424:             if (isset($durvalue[8])) {
1425:                 $duration += 60 * intval($durvalue[8]);
1426:             }
1427: 
1428:             // Secs.
1429:             if (isset($durvalue[9])) {
1430:                 $duration += intval($durvalue[9]);
1431:             }
1432:         }
1433: 
1434:         // Sign.
1435:         if ($durvalue[1] == "-") {
1436:             $duration *= -1;
1437:         }
1438: 
1439:         return $duration;
1440:     }
1441: 
1442:     /**
1443:      * Export a duration value.
1444:      *
1445:      * @param $value TODO
1446:      */
1447:     protected function _exportDuration($value)
1448:     {
1449:         $duration = '';
1450:         if ($value < 0) {
1451:             $value *= -1;
1452:             $duration .= '-';
1453:         }
1454:         $duration .= 'P';
1455: 
1456:         $weeks = floor($value / (7 * 86400));
1457:         $value = $value % (7 * 86400);
1458:         if ($weeks) {
1459:             $duration .= $weeks . 'W';
1460:         }
1461: 
1462:         $days = floor($value / (86400));
1463:         $value = $value % (86400);
1464:         if ($days) {
1465:             $duration .= $days . 'D';
1466:         }
1467: 
1468:         if ($value) {
1469:             $duration .= 'T';
1470: 
1471:             $hours = floor($value / 3600);
1472:             $value = $value % 3600;
1473:             if ($hours) {
1474:                 $duration .= $hours . 'H';
1475:             }
1476: 
1477:             $mins = floor($value / 60);
1478:             $value = $value % 60;
1479:             if ($mins) {
1480:                 $duration .= $mins . 'M';
1481:             }
1482: 
1483:             if ($value) {
1484:                 $duration .= $value . 'S';
1485:             }
1486:         }
1487: 
1488:         return $duration;
1489:     }
1490: 
1491: }
1492: 
API documentation generated by ApiGen