Changeset 70419 in spip-zone


Ignore:
Timestamp:
Mar 5, 2013, 1:41:11 PM (6 years ago)
Author:
rastapopoulos@…
Message:

Mise à jour de la librairie iCalcreator qui a eu 10 versions depuis et qui a, entre autre, doublée de volume (je suppose que ça rajoute des fonctions utiles).

Location:
_plugins_/icalendar
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • _plugins_/icalendar/lib/iCalcreator.class.php

    r43138 r70419  
    22/*********************************************************************************/
    33/**
    4  * iCalcreator class v2.6
    5  * copyright (c) 2007-2008 Kjell-Inge Gustafsson kigkonsult
    6  * www.kigkonsult.se/iCalcreator/index.php
     4 * iCalcreator v2.16.12
     5 * copyright (c) 2007-2013 Kjell-Inge Gustafsson kigkonsult
     6 * kigkonsult.se/iCalcreator/index.php
    77 * ical@kigkonsult.se
    88 *
    99 * Description:
    10  * This file is a PHP implementation of RFC 2445.
     10 * This file is a PHP implementation of rfc2445/rfc5545.
    1111 *
    1212 * This library is free software; you can redistribute it and/or
     
    4444}
    4545*/
    46             /* only for phpversion 5.x, date management, default timezone setting */
    47 if( substr( phpversion(), 0, 1) >= '5' ) // && ( 'UTC' == date_default_timezone_get() )) {
    48   date_default_timezone_set( 'Europe/Stockholm' );
    49             /* version string, do NOT remove!! */
    50 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.6' );
     46/*********************************************************************************/
     47/*         version, do NOT remove!!                                              */
     48define( 'ICALCREATOR_VERSION', 'iCalcreator 2.16.12' );
    5149/*********************************************************************************/
    5250/*********************************************************************************/
     
    5452 * vcalendar class
    5553 *
    56  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    57  * @since 2.2.13 - 2007-12-30
     54 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     55 * @since 2.9.6 - 2011-05-14
    5856 */
    5957class vcalendar {
     
    7674  var $nl;
    7775  var $format;
     76  var $dtzid;
    7877            //  component internal variables
    7978  var $attributeDelimiter;
     
    8180            //  component xCal declaration container
    8281  var $xcaldecl;
    83 /*
     82/**
    8483 * constructor for calendar object
    8584 *
    86  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    87  * @since 2.2.13 - 2007-12-30
     85 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     86 * @since 2.9.6 - 2011-05-14
     87 * @param array $config
    8888 * @return void
    8989 */
    90   function vcalendar () {
     90  function vcalendar ( $config = array()) {
    9191    $this->_makeVersion();
    9292    $this->calscale   = null;
     
    9595    $this->prodid     = null;
    9696    $this->xprop      = array();
    97 /**
    98  *   language = <Text identifying a language, as defined in [RFC 1766]>
    99  */
    100     if( defined( 'ICAL_LANG' ))
    101       $this->setConfig( 'language', ICAL_LANG );
    102     $this->setConfig( 'allowEmpty', TRUE );
    103     $this->setConfig( 'nl',         "\n" );
    104     $this->setConfig( 'format',     'iCal');
     97    $this->language   = null;
    10598    $this->directory  = null;
    10699    $this->filename   = null;
    107100    $this->url        = null;
    108     $this->setConfig( 'delimiter',  DIRECTORY_SEPARATOR );
     101    $this->dtzid      = null;
     102/**
     103 *   language = <Text identifying a language, as defined in [RFC 1766]>
     104 */
     105    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
     106                                          $config['language']   = ICAL_LANG;
     107    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
     108    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
     109    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
     110    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
     111    $this->setConfig( $config );
     112
    109113    $this->xcaldecl   = array();
    110114    $this->components = array();
     
    117121 * creates formatted output for calendar property calscale
    118122 *
    119  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    120  * @since 2.4.8 - 2008-10-21
     123 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     124 * @since 2.10.16 - 2011-10-28
    121125 * @return string
    122126 */
     
    125129    switch( $this->format ) {
    126130      case 'xcal':
    127         return ' calscale="'.$this->calscale.'"'.$this->nl;
     131        return $this->nl.' calscale="'.$this->calscale.'"';
    128132        break;
    129133      default:
     
    135139 * set calendar property calscale
    136140 *
    137  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     141 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    138142 * @since 2.4.8 - 2008-10-21
    139143 * @param string $value
     
    151155 * creates formatted output for calendar property method
    152156 *
    153  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    154  * @since 0.9.7 - 2006-11-20
     157 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     158 * @since 2.10.16 - 2011-10-28
    155159 * @return string
    156160 */
     
    159163    switch( $this->format ) {
    160164      case 'xcal':
    161         return ' method="'.$this->method.'"'.$this->nl;
     165        return $this->nl.' method="'.$this->method.'"';
    162166        break;
    163167      default:
     
    169173 * set calendar property method
    170174 *
    171  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     175 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    172176 * @since 2.4.8 - 2008-20-23
    173177 * @param string $value
     
    190194 * creates formatted output for calendar property prodid
    191195 *
    192  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    193  * @since 0.9.7 - 2006-11-20
     196 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     197 * @since 2.12.11 - 2012-05-13
    194198 * @return string
    195199 */
     
    199203    switch( $this->format ) {
    200204      case 'xcal':
    201         return ' prodid="'.$this->prodid.'"'.$this->nl;
     205        return $this->nl.' prodid="'.$this->prodid.'"';
    202206        break;
    203207      default:
    204         return 'PRODID:'.$this->prodid.$this->nl;
     208        $toolbox = new calendarComponent();
     209        $toolbox->setConfig( $this->getConfig());
     210        return $toolbox->_createElement( 'PRODID', '', $this->prodid );
    205211        break;
    206212    }
     
    209215 * make default value for calendar prodid
    210216 *
    211  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    212  * @since 0.3.0 - 2006-08-10
     217 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     218 * @since 2.6.8 - 2009-12-30
    213219 * @return void
    214220 */
    215221  function _makeProdid() {
    216     $this->prodid  = '-//'.$this->unique_id.'//NONSGML '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
     222    $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
    217223  }
    218224/**
     
    225231 * make default unique_id for calendar prodid
    226232 *
    227  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     233 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    228234 * @since 0.3.0 - 2006-08-10
    229235 * @return void
     
    242248
    243249 *
    244  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    245  * @since 0.9.7 - 2006-11-20
     250 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     251 * @since 2.10.16 - 2011-10-28
    246252 * @return string
    247253 */
     
    251257    switch( $this->format ) {
    252258      case 'xcal':
    253         return ' version="'.$this->version.'"'.$this->nl;
     259        return $this->nl.' version="'.$this->version.'"';
    254260        break;
    255261      default:
     
    261267 * set default calendar version
    262268 *
    263  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     269 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    264270 * @since 0.3.0 - 2006-08-10
    265271 * @return void
     
    271277 * set calendar version
    272278 *
    273  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     279 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    274280 * @since 2.4.8 - 2008-10-23
    275281 * @param string $value
     
    288294 * creates formatted output for calendar property x-prop, iCal format only
    289295 *
    290  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    291  * @since 2.4.11 - 2008-11-03
     296 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     297 * @since 2.16.2 - 2012-12-18
    292298 * @return string
    293299 */
    294300  function createXprop() {
    295     if( 'xcal' == $this->format )
    296       return false;
    297     if( 0 >= count( $this->xprop ))
    298       return;
     301    if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
    299302    $output = null;
    300303    $toolbox = new calendarComponent();
    301     $toolbox->setConfig( 'language', $this->getConfig( 'language' ));
    302     $toolbox->setConfig( 'nl',       $this->getConfig( 'nl' ));
    303     $toolbox->_createFormat(         $this->getConfig( 'format' ));
     304    $toolbox->setConfig( $this->getConfig());
    304305    foreach( $this->xprop as $label => $xpropPart ) {
    305       if( empty( $xpropPart['value'] )) {
     306      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
    306307        $output  .= $toolbox->_createElement( $label );
    307308        continue;
     
    310311      if( is_array( $xpropPart['value'] )) {
    311312        foreach( $xpropPart['value'] as $pix => $theXpart )
    312           $xpropPart['value'][$pix] = $toolbox->_strrep( $theXpart );
     313          $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->nl );
    313314        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
    314315      }
    315316      else
    316         $xpropPart['value'] = $toolbox->_strrep( $xpropPart['value'] );
     317        $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
    317318      $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
     319      if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
     320        foreach( $toolbox->xcaldecl as $localxcaldecl )
     321          $this->xcaldecl[] = $localxcaldecl;
     322      }
    318323    }
    319324    return $output;
     
    322327 * set calendar property x-prop
    323328 *
    324  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    325  * @since 2.4.11 - 2008-11-04
     329 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     330 * @since 2.11.9 - 2012-01-16
    326331 * @param string $label
    327332 * @param string $value
     
    330335 */
    331336  function setXprop( $label, $value, $params=FALSE ) {
    332     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    333     if( empty( $label )) return FALSE;
     337    if( empty( $label ))
     338      return FALSE;
     339    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
     340      return FALSE;
     341    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    334342    $xprop           = array( 'value' => $value );
    335     $toolbox         = new calendarComponent();
    336     $xprop['params'] = $toolbox->_setParams( $params );
     343    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
    337344    if( !is_array( $this->xprop )) $this->xprop = array();
    338345    $this->xprop[strtoupper( $label )] = $xprop;
     
    343350 * delete calendar property value
    344351 *
    345  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    346  * @since 2.4.5 - 2008-11-14
     352 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     353 * @since 2.8.8 - 2011-03-15
    347354 * @param mixed $propName, bool FALSE => X-property
    348  * @param int @propix, optional, if specific property is wanted in case of multiply occurences
     355 * @param int $propix, optional, if specific property is wanted in case of multiply occurences
    349356 * @return bool, if successfull delete
    350357 */
    351   function deleteProperty( $propName, $propix=FALSE ) {
     358  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
    352359    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
    353360    if( !$propix )
    354       $propix = ( isset( $this->propdelix[$propName] )) ? $this->propdelix[$propName] + 2 : 1;
     361      $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
    355362    $this->propdelix[$propName] = --$propix;
    356363    $return = FALSE;
     
    371378        $reduced = array();
    372379        if( $propName != 'X-PROP' ) {
    373           if( !isset( $this->xprop[$propName] )) return FALSE;
     380          if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
    374381          foreach( $this->xprop as $k => $a ) {
    375382            if(( $k != $propName ) && !empty( $a ))
     
    387394        }
    388395        $this->xprop = $reduced;
     396        if( empty( $this->xprop )) {
     397          unset( $this->propdelix[$propName] );
     398          return FALSE;
     399        }
    389400        return TRUE;
    390401    }
     
    394405 * get calendar property value/params
    395406 *
    396  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    397  * @since 2.5.1 - 2008-11-02
     407 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     408 * @since 2.13.4 - 2012-08-08
    398409 * @param string $propName, optional
    399  * @param int @propix, optional, if specific property is wanted in case of multiply occurences
     410 * @param int $propix, optional, if specific property is wanted in case of multiply occurences
    400411 * @param bool $inclParam=FALSE
    401412 * @return mixed
     
    405416    if( 'X-PROP' == $propName ) {
    406417      if( !$propix )
    407         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
     418        $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
    408419      $this->propix[$propName] = --$propix;
    409420    }
     421    else
     422      $mProps    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
    410423    switch( $propName ) {
     424      case 'ATTENDEE':
     425      case 'CATEGORIES':
     426      case 'CONTACT':
     427      case 'DTSTART':
     428      case 'GEOLOCATION':
     429      case 'LOCATION':
     430      case 'ORGANIZER':
     431      case 'PRIORITY':
     432      case 'RESOURCES':
     433      case 'STATUS':
     434      case 'SUMMARY':
     435      case 'RECURRENCE-ID-UID':
     436      case 'RELATED-TO':
     437      case 'R-UID':
     438      case 'UID':
     439      case 'URL':
     440        $output  = array();
     441        foreach ( $this->components as $cix => $component) {
     442          if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
     443            continue;
     444          if( in_array( strtoupper( $propName ), $mProps )) {
     445            $component->_getProperties( $propName, $output );
     446            continue;
     447          }
     448          elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
     449            if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
     450              $content = $component->getProperty( 'UID' );
     451          }
     452          elseif( 'GEOLOCATION' == $propName ) {
     453            $content = $component->getProperty( 'LOCATION' );
     454            $content = ( !empty( $content )) ? $content.' ' : '';
     455            if(( FALSE === ( $geo     = $component->getProperty( 'GEO' ))) || empty( $geo ))
     456              continue;
     457            if( 0.0 < $geo['latitude'] )
     458              $sign   = '+';
     459            else
     460              $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
     461            $content .= ' '.$sign.sprintf( "%09.6f", abs( $geo['latitude'] ));
     462            $content  = rtrim( rtrim( $content, '0' ), '.' );
     463            if( 0.0 < $geo['longitude'] )
     464              $sign   = '+';
     465            else
     466              $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
     467            $content .= $sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';
     468          }
     469          elseif( FALSE === ( $content = $component->getProperty( $propName )))
     470            continue;
     471          if(( FALSE === $content ) || empty( $content ))
     472            continue;
     473          elseif( is_array( $content )) {
     474            if( isset( $content['year'] )) {
     475              $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
     476              if( !isset( $output[$key] ))
     477                $output[$key] = 1;
     478              else
     479                $output[$key] += 1;
     480            }
     481            else {
     482              foreach( $content as $partValue => $partCount ) {
     483                if( !isset( $output[$partValue] ))
     484                  $output[$partValue] = $partCount;
     485                else
     486                  $output[$partValue] += $partCount;
     487              }
     488            }
     489          } // end elseif( is_array( $content )) {
     490          elseif( !isset( $output[$content] ))
     491            $output[$content] = 1;
     492          else
     493            $output[$content] += 1;
     494        } // end foreach ( $this->components as $cix => $component)
     495        if( !empty( $output ))
     496          ksort( $output );
     497        return $output;
     498        break;
    411499      case 'CALSCALE':
    412         return ( !empty( $this->calscale )) ? $this->calscale : null;
     500        return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
    413501        break;
    414502      case 'METHOD':
    415         return ( !empty( $this->method )) ? $this->method : null;
     503        return ( !empty( $this->method )) ? $this->method : FALSE;
    416504        break;
    417505      case 'PRODID':
     
    421509        break;
    422510      case 'VERSION':
    423         return ( !empty( $this->version )) ? $this->version : null;
     511        return ( !empty( $this->version )) ? $this->version : FALSE;
    424512        break;
    425513      default:
     
    439527              $xpropno++;
    440528          }
     529          unset( $this->propix[$propName] );
    441530          return FALSE; // not found ??
    442531        }
     
    447536 * general vcalendar property setting
    448537 *
    449  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     538 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    450539 * @since 2.2.13 - 2007-11-04
    451540 * @param mixed $args variable number of function arguments,
     
    478567 * get vcalendar config values or * calendar components
    479568 *
    480  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    481  * @since 2.4.10 - 2008-10-23
    482  * @param string $config
     569 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     570 * @since 2.11.7 - 2012-01-12
     571 * @param mixed $config
    483572 * @return value
    484573 */
    485   function getConfig( $config ) {
     574  function getConfig( $config = FALSE ) {
     575    if( !$config ) {
     576      $return = array();
     577      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
     578      $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
     579      $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
     580      $return['FILENAME']    = $this->getConfig( 'FILENAME' );
     581      $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
     582      $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
     583      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
     584      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
     585        $return['LANGUAGE']  = $lang;
     586      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
     587      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
     588      if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
     589        $return['URL']       = $url;
     590      $return['TZID']        = $this->getConfig( 'TZID' );
     591      return $return;
     592    }
    486593    switch( strtoupper( $config )) {
    487594      case 'ALLOWEMPTY':
     
    493600        foreach( $this->components as $cix => $component ) {
    494601          if( empty( $component )) continue;
    495           unset( $component->propix );
    496602          $info[$cix]['ordno'] = $cix + 1;
    497603          $info[$cix]['type']  = $component->objName;
     
    499605          $info[$cix]['props'] = $component->getConfig( 'propinfo' );
    500606          $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
    501           unset( $component->propix );
    502607        }
    503608        return $info;
     
    507612        break;
    508613      case 'DIRECTORY':
    509         if( empty( $this->directory ))
     614        if( empty( $this->directory ) && ( '0' != $this->directory ))
    510615          $this->directory = '.';
    511616        return $this->directory;
     
    520625        break;
    521626      case 'FILENAME':
    522         if( empty( $this->filename )) {
     627        if( empty( $this->filename ) && ( '0' != $this->filename )) {
    523628          if( 'xcal' == $this->format )
    524629            $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
     
    532637        if( empty( $this->url )) {
    533638          $dirfile = $this->getConfig( 'dirfile' );
    534           if( FALSE === ( $size = filesize( $dirfile )))
     639          if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
    535640            $size = 0;
    536641          clearstatcache();
     
    539644        break;
    540645      case 'FORMAT':
    541         return $this->format;
     646        return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
    542647        break;
    543648      case 'LANGUAGE':
     
    548653      case 'NEWLINECHAR':
    549654        return $this->nl;
     655        break;
     656      case 'TZID':
     657        return $this->dtzid;
    550658        break;
    551659      case 'UNIQUE_ID':
     
    563671 * general vcalendar config setting
    564672 *
    565  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    566  * @since 2.4.8 - 2008-10-24
    567  * @param string $config
     673 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     674 * @since 2.16.7 - 2013-01-11
     675 * @param mixed $config
    568676 * @param string $value
    569677 * @return void
    570678 */
    571   function setConfig( $config, $value ) {
     679  function setConfig( $config, $value = FALSE) {
     680    if( is_array( $config )) {
     681      $ak = array_keys( $config );
     682      foreach( $ak as $k ) {
     683        if( 'DIRECTORY' == strtoupper( $k )) {
     684          if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
     685            return FALSE;
     686          unset( $config[$k] );
     687        }
     688        elseif( 'NEWLINECHAR' == strtoupper( $k )) {
     689          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
     690            return FALSE;
     691          unset( $config[$k] );
     692        }
     693      }
     694      foreach( $config as $cKey => $cValue ) {
     695        if( FALSE === $this->setConfig( $cKey, $cValue ))
     696          return FALSE;
     697      }
     698      return TRUE;
     699    }
    572700    $res = FALSE;
    573701    switch( strtoupper( $config )) {
     
    583711      case 'DIRECTORY':
    584712        $value   = trim( $value );
    585         $nl      = $this->getConfig('delimiter');
    586         if( $nl == substr( $value, ( 0 - strlen( $nl ))))
    587           $value = substr( $value, 0, ( strlen( $value ) - strlen( $nl )));
     713        $del     = $this->getConfig('delimiter');
     714        if( $del == substr( $value, ( 0 - strlen( $del ))))
     715          $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
    588716        if( is_dir( $value )) {
    589717            /* local directory */
     
    599727        $value   = trim( $value );
    600728        if( !empty( $this->url )) {
    601             /* remote directory+file - URL */
     729            /* remote directory+file -> URL */
    602730          $this->filename = $value;
    603731          return TRUE;
     
    605733        $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
    606734        if( file_exists( $dirfile )) {
    607             /* local existing file */
     735            /* local file exists */
    608736          if( is_readable( $dirfile ) || is_writable( $dirfile )) {
    609737            clearstatcache();
     
    614742            return FALSE;
    615743        }
    616         elseif( FALSE !== touch( $dirfile )) {
    617             /* new local file created */
     744        elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
     745            /* read- or writable directory */
    618746          $this->filename = $value;
    619747          return TRUE;
     
    623751        break;
    624752      case 'FORMAT':
    625         $value   = trim( $value );
    626         if( 'xcal' == strtolower( $value )) {
     753        $value   = trim( strtolower( $value ));
     754        if( 'xcal' == $value ) {
    627755          $this->format             = 'xcal';
    628756          $this->attributeDelimiter = $this->nl;
     
    637765        $res = TRUE;
    638766        break;
    639       case 'LANGUAGE':
    640          // set language for calendar component as defined in [RFC 1766]
     767      case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
    641768        $value   = trim( $value );
    642769        $this->language = $value;
     770        $this->_makeProdid();
    643771        $subcfg  = array( 'LANGUAGE' => $value );
    644772        $res = TRUE;
     
    647775      case 'NEWLINECHAR':
    648776        $this->nl = $value;
     777        if( 'xcal' == $value ) {
     778          $this->attributeDelimiter = $this->nl;
     779          $this->valueInit          = null;
     780        }
     781        else {
     782          $this->attributeDelimiter = ';';
     783          $this->valueInit          = ':';
     784        }
    649785        $subcfg  = array( 'NL' => $value );
     786        $res = TRUE;
     787        break;
     788      case 'TZID':
     789        $this->dtzid = $value;
     790        $subcfg  = array( 'TZID' => $value );
    650791        $res = TRUE;
    651792        break;
     
    653794        $value   = trim( $value );
    654795        $this->unique_id = $value;
     796        $this->_makeProdid();
    655797        $subcfg  = array( 'UNIQUE_ID' => $value );
    656798        $res = TRUE;
     
    658800      case 'URL':
    659801            /* remote file - URL */
    660         $value     = trim( $value );
    661         $value     = str_replace( 'HTTP://',   'http://', $value );
    662         $value     = str_replace( 'WEBCAL://', 'http://', $value );
    663         $value     = str_replace( 'webcal://', 'http://', $value );
     802        $value     = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
     803        if( 'http://' != substr( $value, 0, 7 ))
     804          return FALSE;
     805        $s1        = $this->url;
    664806        $this->url = $value;
     807        $s2        = $this->directory;
    665808        $this->directory = null;
    666809        $parts     = pathinfo( $value );
    667         return $this->setConfig( 'filename',  $parts['basename'] );
    668         break;
     810        if( FALSE === $this->setConfig( 'filename',  $parts['basename'] )) {
     811          $this->url       = $s1;
     812          $this->directory = $s2;
     813          return FALSE;
     814        }
     815        else
     816          return TRUE;
     817        break;
     818      default:  // any unvalid config key.. .
     819        return TRUE;
    669820    }
    670821    if( !$res ) return FALSE;
     
    672823      foreach( $subcfg as $cfgkey => $cfgvalue ) {
    673824        foreach( $this->components as $cix => $component ) {
    674           $res = $component->setConfig( $cfgkey, $cfgvalue );
     825          $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
    675826          if( !$res )
    676827            break 2;
     
    687838 * alias to setComponent
    688839 *
    689  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     840 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    690841 * @since 1.x.x - 2007-04-24
    691842 * @param object $component calendar component
     
    698849 * delete calendar component from container
    699850 *
    700  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    701  * @since 2.4.10 - 2008-08-05
     851 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     852 * @since 2.8.8 - 2011-03-15
    702853 * @param mixed $arg1 ordno / component type / component uid
    703854 * @param mixed $arg2 optional, ordno if arg1 = component type
     
    717868    foreach ( $this->components as $cix => $component) {
    718869      if( empty( $component )) continue;
    719       unset( $component->propix );
    720870      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
    721871        unset( $this->components[$cix] );
     
    739889 * get calendar component from container
    740890 *
    741  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    742  * @since 2.4.10 - 2008-08-06
     891 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     892 * @since 2.13.5 - 2012-08-08
    743893 * @param mixed $arg1 optional, ordno/component type/ component uid
    744894 * @param mixed $arg2 optional, ordno if arg1 = component type
     
    747897  function getComponent( $arg1=FALSE, $arg2=FALSE ) {
    748898    $index = $argType = null;
    749     if ( !$arg1 ) {
     899    if ( !$arg1 ) { // first or next in component chain
    750900      $argType = 'INDEX';
    751       $index   = $this->compix['INDEX'] =
    752         ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
    753     }
    754     elseif ( ctype_digit( (string) $arg1 )) {
     901      $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
     902    }
     903    elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
    755904      $argType = 'INDEX';
    756905      $index   = (int) $arg1;
    757906      unset( $this->compix );
    758907    }
    759     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
     908    elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
     909      $arg2  = implode( '-', array_keys( $arg1 ));
     910      $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
     911      $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
     912      $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
     913      $mProps     = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
     914    }
     915    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
    760916      unset( $this->compix['INDEX'] );
    761917      $argType = strtolower( $arg1 );
    762918      if( !$arg2 )
    763         $index = $this->compix[$argType] =
    764         ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
    765       else
     919        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
     920      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
    766921        $index = (int) $arg2;
    767922    }
    768     $index  -= 1;
    769     $ckeys =  array_keys( $this->components );
     923    elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
     924      if( !$arg2 )
     925        $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
     926      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
     927        $index = (int) $arg2;
     928    }
     929    if( isset( $index ))
     930      $index  -= 1;
     931    $ckeys = array_keys( $this->components );
    770932    if( !empty( $index) && ( $index > end(  $ckeys )))
    771933      return FALSE;
     
    773935    foreach ( $this->components as $cix => $component) {
    774936      if( empty( $component )) continue;
    775       unset( $component->propix );
    776937      if(( 'INDEX' == $argType ) && ( $index == $cix ))
    777938        return $component->copy();
    778939      elseif( $argType == $component->objName ) {
    779          if( $index == $cix1gC )
    780            return $component->copy();
    781          $cix1gC++;
    782       }
    783       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
    784         unset( $component->propix );
    785         return $component->copy();
    786       }
    787     }
     940        if( $index == $cix1gC )
     941          return $component->copy();
     942        $cix1gC++;
     943      }
     944      elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
     945        $hit = array();
     946        foreach( $arg1 as $pName => $pValue ) {
     947          $pName = strtoupper( $pName );
     948          if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
     949            continue;
     950          if( in_array( $pName, $mProps )) { // multiple occurrence
     951            $propValues = array();
     952            $component->_getProperties( $pName, $propValues );
     953            $propValues = array_keys( $propValues );
     954            $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
     955            continue;
     956          } // end   if(.. .// multiple occurrence
     957          if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
     958            $hit[] = FALSE; // missing property
     959            continue;
     960          }
     961          if( 'SUMMARY' == $pName ) { // exists within (any case)
     962            $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
     963            continue;
     964          }
     965          if( in_array( strtoupper( $pName ), $dateProps )) {
     966            $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
     967            if( 8 < strlen( $pValue )) {
     968              if( isset( $value['hour'] )) {
     969                if( 'T' == substr( $pValue, 8, 1 ))
     970                  $pValue = str_replace( 'T', '', $pValue );
     971                $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
     972              }
     973              else
     974                $pValue = substr( $pValue, 0, 8 );
     975            }
     976            $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
     977            continue;
     978          }
     979          elseif( !is_array( $value ))
     980            $value = array( $value );
     981          foreach( $value as $part ) {
     982            $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
     983            foreach( $part as $subPart ) {
     984              if( $pValue == $subPart ) {
     985                $hit[] = TRUE;
     986                continue 3;
     987              }
     988            }
     989          } // end foreach( $value as $part )
     990          $hit[] = FALSE; // no hit in property
     991        } // end  foreach( $arg1 as $pName => $pValue )
     992        if( in_array( TRUE, $hit )) {
     993          if( $index == $cix1gC )
     994            return $component->copy();
     995          $cix1gC++;
     996        }
     997      } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
     998      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
     999        if( $index == $cix1gC )
     1000          return $component->copy();
     1001        $cix1gC++;
     1002      }
     1003    } // end foreach ( $this->components.. .
    7881004            /* not found.. . */
    7891005    unset( $this->compix );
     
    7911007  }
    7921008/**
    793  * select components from calendar on date basis
     1009 * create new calendar component, already included within calendar
     1010 *
     1011 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1012 * @since 2.6.33 - 2011-01-03
     1013 * @param string $compType component type
     1014 * @return object (reference)
     1015 */
     1016  function & newComponent( $compType ) {
     1017    $config = $this->getConfig();
     1018    $keys   = array_keys( $this->components );
     1019    $ix     = end( $keys) + 1;
     1020    switch( strtoupper( $compType )) {
     1021      case 'EVENT':
     1022      case 'VEVENT':
     1023        $this->components[$ix] = new vevent( $config );
     1024        break;
     1025      case 'TODO':
     1026      case 'VTODO':
     1027        $this->components[$ix] = new vtodo( $config );
     1028        break;
     1029      case 'JOURNAL':
     1030      case 'VJOURNAL':
     1031        $this->components[$ix] = new vjournal( $config );
     1032        break;
     1033      case 'FREEBUSY':
     1034      case 'VFREEBUSY':
     1035        $this->components[$ix] = new vfreebusy( $config );
     1036        break;
     1037      case 'TIMEZONE':
     1038      case 'VTIMEZONE':
     1039        array_unshift( $this->components, new vtimezone( $config ));
     1040        $ix = 0;
     1041        break;
     1042      default:
     1043        return FALSE;
     1044    }
     1045    return $this->components[$ix];
     1046  }
     1047/**
     1048 * select components from calendar on date or selectOption basis
    7941049 *
    7951050 * Ensure DTSTART is set for every component.
    7961051 * No date controls occurs.
    7971052 *
    798  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    799  * @since 2.4.16 - 2008-10-18
    800  * @param int $startY optional,  start Year, default current Year
    801  * @param int $startM optional, start Month, default current Month
    802  * @param int $startD optional,  start Day, default current Day
    803  * @param int $endY optional,    end Year, default $startY
    804  * @param int $endY optional,    end Month, default $startM
    805  * @param int $endY optional,    end Day, default $startD
    806  * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s)
    807  * @param bool $flat optional,  FALSE (default) => output : array[Year][Month][Day][]
    808  *                               TRUE => output : array[] (ignores split)
    809  * @param bool $any optional,    TRUE (default) - select component that take place within period
    810  *                               FALSE - only components that starts within period
    811  * @param bool $split optional,  TRUE (default) - one component copy every day it take place during the
    812  *                                       period (implies flat=FALSE)
    813  *                               FALSE - one occurance of component only in output array</tr>
     1053 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1054 * @since 2.16.12 - 2013-02-10
     1055 * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
     1056 * @param int   $startM optional, start Month, default current Month
     1057 * @param int   $startD optional, start Day,  default current Day
     1058 * @param int   $endY   optional, end   Year, default $startY
     1059 * @param int   $endY   optional, end  Month, default $startM
     1060 * @param int   $endY   optional, end   Day,  default $startD
     1061 * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
     1062 * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
     1063 *                                TRUE            => output : array[] (ignores split)
     1064 * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
     1065 *                                FALSE          - only component(-s) that starts within period
     1066 * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
     1067 *                                                 period (implies flat=FALSE)
     1068 *                                FALSE          - one occurance of component only in output array
    8141069 * @return array or FALSE
    8151070 */
     
    8171072            /* check  if empty calendar */
    8181073    if( 0 >= count( $this->components )) return FALSE;
     1074    if( is_array( $startY ))
     1075      return $this->selectComponents2( $startY );
    8191076            /* check default dates */
    8201077    if( !$startY ) $startY = date( 'Y' );
     
    8261083    if( !$endD )   $endD   = $startD;
    8271084    $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
     1085// echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
    8281086            /* check component types */
    8291087    $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
     
    8471105    if( 0 >= count( $cType ))
    8481106      $cType = $validTypes;
     1107    if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
     1108      $split = FALSE;
     1109    if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
     1110      $split = FALSE;
    8491111            /* iterate components */
    850     $result = array();
     1112    $result       = array();
     1113    $this->sort( 'UID' );
     1114    $compUIDcmp   = null;
     1115    $recurridList = array();
    8511116    foreach ( $this->components as $cix => $component ) {
    8521117      if( empty( $component )) continue;
    853       unset( $component->propix, $start );
     1118      unset( $start );
    8541119            /* deselect unvalid type components */
    855       if( !in_array( $component->objName, $cType )) continue;
    856             /* deselect components without dtstart set */
    857       if( FALSE === ( $start = $component->getProperty( 'dtstart' ))) continue;
    858       $dtendExist = $dueExist = $durationExist = $endAllDayEvent = FALSE;
    859       unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend ); // clean up
    860       $startWdate = $component->_date2timestamp( $start );
     1120      if( !in_array( $component->objName, $cType ))
     1121        continue;
     1122      $start = $component->getProperty( 'dtstart' );
     1123            /* select due when dtstart is missing */
     1124      if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
     1125        continue;
     1126      if( empty( $start ))
     1127        continue;
     1128      $compUID      = $component->getProperty( 'UID' );
     1129      if( $compUIDcmp != $compUID ) {
     1130        $compUIDcmp = $compUID;
     1131        unset( $exdatelist, $recurridList );
     1132      }
     1133      $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
     1134      unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend, $endDateFormat ); // clean up
     1135      $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
    8611136      $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
    8621137            /* get end date from dtend/due/duration properties */
     
    8661141        $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
    8671142      }
    868    // if( !empty($end))  echo 'selectComp 1 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
    869       if( empty($end) && ( $component->objName == 'vtodo' )) {
     1143      if( empty( $end ) && ( $component->objName == 'vtodo' )) {
    8701144        $end = $component->getProperty( 'due' );
    8711145        if( !empty( $end )) {
     
    8731147          $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
    8741148        }
    875    // if( !empty($end))  echo 'selectComp 2 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
    8761149      }
    8771150      if( !empty( $end ) && !isset( $end['hour'] )) {
     
    8851158        $end['hour']  = 23;
    8861159        $end['min']   = $end['sec'] = 59;
    887    // if( !empty($end))  echo 'selectComp 3 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
    8881160      }
    8891161      if( empty( $end )) {
     
    8911163        if( !empty( $end ))
    8921164          $durationExist = TRUE;
    893    // if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
     1165          $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
     1166// if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
    8941167      }
    8951168      if( empty( $end )) { // assume one day duration if missing end date
    8961169        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
    897    // if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
    898       }
    899       $endWdate = $component->_date2timestamp( $end );
     1170      }
     1171// if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
     1172      $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
    9001173      if( $endWdate < $startWdate ) { // MUST be after start date!!
    9011174        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
    902         $endWdate = $component->_date2timestamp( $end );
    903       }
    904       $rdurWsecs  = $endWdate - $startWdate; // compute component duration in seconds
    905       $rdur       = $component->_date2duration( $start, $end ); // compute component duration, array
     1175        $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
     1176      }
     1177      $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
    9061178            /* make a list of optional exclude dates for component occurence from exrule and exdate */
    9071179      $exdatelist = array();
    908       $workstart  = $component->_timestamp2date(( $startDate - $rdurWsecs ), 6);
    909       $workend    = $component->_timestamp2date(( $endDate + $rdurWsecs ), 6);
     1180      $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
     1181      $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
    9101182      while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
    911         $component->_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
     1183        iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
    9121184      while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
    9131185        foreach( $exdate as $theExdate ) {
    914           $exWdate = $component->_date2timestamp( $theExdate );
     1186          $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
     1187          $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
    9151188          if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
    9161189            $exdatelist[$exWdate] = TRUE;
    917         }
    918       }
    919             /* if 'any' components, check repeating components, removing all excluding dates */
     1190        } // end - foreach( $exdate as $theExdate )
     1191      }  // end - check exdate
     1192            /* check recurrence-id (note, a missing sequence=0, don't test foer sequence), remove hit with reccurr-id date */
     1193      if( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) {
     1194// echo "adding ".$recurrid['year'].'-'.$recurrid['month'].'-'.$recurrid['day']." to recurridList<br>\n"; // test ###
     1195        $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
     1196        $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
     1197        $recurridList[$recurrid] = TRUE; // no recurring to start this day
     1198      } // end recurrence-id/sequence test
     1199            /* select only components with.. . */
     1200      if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
     1201         (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
     1202            /* add the selected component (WITHIN valid dates) to output array */
     1203        if( $flat ) { // any=true/false, ignores split
     1204          if( !$recurrid )
     1205            $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
     1206        }
     1207        elseif( $split ) { // split the original component
     1208          if( $endWdate > $endDate )
     1209            $endWdate = $endDate;     // use period end date
     1210          $rstart   = $startWdate;
     1211          if( $rstart < $startDate )
     1212            $rstart = $startDate; // use period start date
     1213          $startYMD = $rstartYMD = date( 'Ymd', $rstart );
     1214          $endYMD   = date( 'Ymd', $endWdate );
     1215          $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
     1216// echo "start org comp = $rstartYMD, endYMD=$endYMD<br />\n"; // test ###
     1217          if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
     1218            while( $rstartYMD <= $endYMD ) { // iterate
     1219              if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
     1220                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
     1221                $rstartYMD = date( 'Ymd', $rstart );
     1222                continue;
     1223              }
     1224              if( $rstartYMD > $startYMD ) // date after dtstart
     1225                $datestring = date( $startDateFormat, $checkDate ); // mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
     1226              else
     1227                $datestring = date( $startDateFormat, $rstart );
     1228              if( isset( $start['tz'] ))
     1229                $datestring .= ' '.$start['tz'];
     1230// echo "split org comp rstartYMD=$rstartYMD (datestring=$datestring)<br />\n"; // test ###
     1231              $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
     1232              if( $dtendExist || $dueExist || $durationExist ) {
     1233                if( $rstartYMD < $endYMD ) // not the last day
     1234                  $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
     1235                else
     1236                  $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
     1237                if( $endAllDayEvent && $dtendExist )
     1238                  $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
     1239                $datestring = date( $endDateFormat, $tend );
     1240                if( isset( $end['tz'] ))
     1241                  $datestring .= ' '.$end['tz'];
     1242                $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
     1243                $component->setProperty( $propName, $datestring );
     1244              } // end if( $dtendExist || $dueExist || $durationExist )
     1245              $wd        = getdate( $rstart );
     1246              $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
     1247              $rstart    = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
     1248              $rstartYMD = date( 'Ymd', $rstart );
     1249              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
     1250            } // end while( $rstart <= $endWdate )
     1251          }
     1252        } // end elseif( $split )   -  else use component date
     1253        elseif( $recurrid && !$flat && !$any && !$split )
     1254          $continue = TRUE;
     1255        else { // !$flat && !$split, i.e. no flat array and DTSTART within period
     1256          $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
     1257          if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
     1258            $wd = getdate( $startWdate );
     1259            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
     1260          }
     1261        }
     1262      } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
     1263            /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
    9201264      if( TRUE === $any ) {
    9211265            /* make a list of optional repeating dates for component occurence, rrule, rdate */
    9221266        $recurlist = array();
    9231267        while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
    924           $component->_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
     1268          iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
    9251269        foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
    9261270          $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
     
    9291273            if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&  // all days within PERIOD
    9301274                   array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
    931               $rstart = $component->_date2timestamp( $theRdate[0] );
     1275              $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
    9321276              if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
    9331277                continue;
    9341278              if( isset( $theRdate[1]['year'] )) // date-date period
    935                 $rend = $component->_date2timestamp( $theRdate[1] );
     1279                $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
    9361280              else {                             // date-duration period
    937                 $rend = $component->duration2date( $theRdate[0], $theRdate[1] );
    938                 $rend = $component->_date2timestamp( $rend );
     1281                $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
     1282                $rend = iCalUtilityFunctions::_date2timestamp( $rend );
    9391283              }
    940               if((( $startDate - $rdurWsecs ) <= $rstart ) && ( $endDate >= $rstart ))
    941                 $recurlist[$rstart] = ( $rstart - $rend ); // set start date + rdate duration in seconds
     1284              while( $rstart < $rend ) {
     1285                $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
     1286                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
     1287              }
    9421288            } // PERIOD end
    9431289            else { // single date
    944               $theRdate = $component->_date2timestamp( $theRdate );
     1290              $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
    9451291              if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
    946                 $recurlist[$theRdate] = $rdurWsecs; // set start date + event duration in seconds
     1292                $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
    9471293            }
    9481294          }
     1295        }  // end - check rdate
     1296        foreach( $recurlist as $recurkey => $durvalue ) { // remove all recurrence START dates found in the exdatelist
     1297          $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
     1298          if( isset( $exdatelist[$checkDate] )) // no recurring to start this day
     1299            unset( $recurlist[$recurkey] );
    9491300        }
    9501301        if( 0 < count( $recurlist )) {
    9511302          ksort( $recurlist );
     1303          $xRecurrence = 1;
     1304          $component2  = $component->copy();
     1305          $compUID     = $component2->getProperty( 'UID' );
    9521306          foreach( $recurlist as $recurkey => $durvalue ) {
     1307// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
    9531308            if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
    9541309              continue;
    955             if( isset( $exdatelist[$recurkey] )) // check excluded dates
     1310            $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
     1311            if( isset( $recurridList[$checkDate] )) // no recurring to start this day
     1312              continue;
     1313            if( isset( $exdatelist[$checkDate] )) // check excluded dates
    9561314              continue;
    9571315            if( $startWdate >= $recurkey ) // exclude component start date
    9581316              continue;
    959             $component2   = $component->copy();
    960             $rstart       = $component2->_timestamp2date( $recurkey, 6);
    961             $datevalue    = $rstart['month'].'/'.$rstart['day'].'/'.$rstart['year'];
    962             if( isset( $start['hour'] ) || isset( $start['min'] ) || isset( $start['sec'] )) {
    963               $datevalue .= ( isset( $rstart['hour'] )) ? ' '.$rstart['hour'] : ' 00';
    964               $datevalue .= ( isset( $rstart['min'] ))  ? ':'.$rstart['min']  : ':00';
    965               $datevalue .= ( isset( $rstart['sec'] ))  ? ':'.$rstart['sec']  : ':00';
     1317            $rstart = $recurkey;
     1318            $rend   = $recurkey + $durvalue;
     1319           /* add repeating components within valid dates to output array, only start date set */
     1320            if( $flat ) {
     1321              if( !isset( $result[$compUID] )) // only one comp
     1322                $result[$compUID] = $component2->copy(); // copy to output
    9661323            }
    967             $datestring = date( $startDateFormat, strtotime( $datevalue ));
    968             if( isset( $start['tz'] ))
    969               $datestring .= ' '.$start['tz'];
    970             $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
    971             $rend   = $component2->_timestamp2date(( $recurkey + $durvalue ), 6);
    972             if( $dtendExist || $dueExist ) {
    973               if( $endAllDayEvent ) {
    974                 $rend2 = mktime( 0, 0, 0, $rend['month'], ($rend['day'] + 1), $rend['year'] );
    975                 $datevalue  = date( 'm', $rend2 ).'/'.date( 'd', $rend2 ).'/'.date( 'Y', $rend2 );
    976               }
    977               else {
    978                 $datevalue  = $rend['month'].'/'.$rend['day'].'/'.$rend['year'];
    979                 if( isset( $end['hour'] ) || isset( $end['min'] ) || isset( $end['sec'] )) {
    980                   $datevalue .= ( isset( $rend['hour'] )) ? ' '.$rend['hour'] : ' 00';
    981                   $datevalue .= ( isset( $rend['min'] ))  ? ':'.$rend['min']  : ':00';
    982                   $datevalue .= ( isset( $rend['sec'] ))  ? ':'.$rend['sec']  : ':00';
    983                 }
    984               }
    985               $datestring = date( $endDateFormat, strtotime( $datevalue ));
    986               if( isset( $end['tz'] ))
    987                 $datestring .= ' '.$end['tz'];
    988               if( $dtendExist )
    989                 $component2->setProperty( 'X-CURRENT-DTEND', $datestring );
    990               elseif( $dueExist )
    991                 $component2->setProperty( 'X-CURRENT-DUE', $datestring );
    992             }
    993             $rend   = $component2->_date2timestamp( $rend );
    994             $rstart = $recurkey;
    995             /* add repeating components within valid dates to output array, only start date */
    996             if( $flat )
    997               $result[] = $component2->copy(); // copy to output
     1324           /* add repeating components within valid dates to output array, one each day */
    9981325            elseif( $split ) {
     1326              $xRecurrence += 1;
    9991327              if( $rend > $endDate )
    10001328                $rend = $endDate;
    1001               while( $rstart <= $rend ) { // iterate
     1329              $startYMD = $rstartYMD = date( 'Ymd', $rstart );
     1330              $endYMD   = date( 'Ymd', $rend );
     1331// echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
     1332              while( $rstart <= $rend ) { // iterate.. .
     1333                $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
     1334                if( isset( $recurridList[$checkDate] )) // no recurring to start this day
     1335                  break;
     1336                if( isset( $exdatelist[$checkDate] ))  // exclude any recurrence START date, found in exdatelist
     1337                  break;
     1338// echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
     1339                if( $rstart >= $startDate ) {    // date after dtstart
     1340                  if( $rstartYMD > $startYMD ) // date after dtstart
     1341                    $datestring = date( $startDateFormat, $checkDate );
     1342                  else
     1343                    $datestring = date( $startDateFormat, $rstart );
     1344                  if( isset( $start['tz'] ))
     1345                    $datestring .= ' '.$start['tz'];
     1346// echo "spliting = $datestring<BR>\n"; // test ###
     1347                  $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
     1348                  if( $dtendExist || $dueExist || $durationExist ) {
     1349                    if( $rstartYMD < $endYMD ) // not the last day
     1350                      $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
     1351                    else
     1352                      $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
     1353                    if( $endAllDayEvent && $dtendExist )
     1354                      $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
     1355                    $datestring = date( $endDateFormat, $tend );
     1356                    if( isset( $end['tz'] ))
     1357                      $datestring .= ' '.$end['tz'];
     1358                    $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
     1359                    $component2->setProperty( $propName, $datestring );
     1360                  } // end if( $dtendExist || $dueExist || $durationExist )
     1361                  $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
     1362                  $wd = getdate( $rstart );
     1363                  $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
     1364                } // end if( $checkDate > $startYMD ) {    // date after dtstart
     1365                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
     1366                $rstartYMD = date( 'Ymd', $rstart );
     1367              } // end while( $rstart <= $rend )
     1368            } // end elseif( $split )
     1369            elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
     1370              $xRecurrence += 1;
     1371              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
     1372              if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
     1373                $datestring = date( $startDateFormat, $rstart );
     1374                if( isset( $start['tz'] ))
     1375                  $datestring .= ' '.$start['tz'];
     1376//echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
     1377                $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
     1378                if( $dtendExist || $dueExist || $durationExist ) {
     1379                  $tend = $rstart + $rdurWsecs;
     1380                  if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
     1381                    $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
     1382                  else
     1383                    $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
     1384                  if( $endAllDayEvent && $dtendExist )
     1385                    $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
     1386                  $datestring = date( $endDateFormat, $tend );
     1387                  if( isset( $end['tz'] ))
     1388                    $datestring .= ' '.$end['tz'];
     1389                  $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
     1390                  $component2->setProperty( $propName, $datestring );
     1391                } // end if( $dtendExist || $dueExist || $durationExist )
     1392                $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
    10021393                $wd = getdate( $rstart );
    1003                 if(( $rstart > $startDate ) &&      // date after dtstart
    1004                     !isset( $exdatelist[$rstart] )) // check exclude date
    1005                   $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component2->copy(); // copy to output
    1006                 $rstart += ( 24*60*60 ); // step one day
    1007               }
    1008             }
    1009             elseif(( $rstart >= $startDate ) &&     // date within period
    1010                   !isset( $exdatelist[$rstart] )) { // check exclude date
    1011               $wd = getdate( $rstart );
    1012               $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component2->copy(); // copy to output
    1013             }
    1014           }
    1015         }
     1394                $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
     1395              } // end if( !isset( $exdatelist[$checkDate] ))
     1396            } // end elseif( $rstart >= $startDate )
     1397          } // end foreach( $recurlist as $recurkey => $durvalue )
     1398          unset( $component2 );
     1399        } // end if( 0 < count( $recurlist ))
    10161400            /* deselect components with startdate/enddate not within period */
    1017         if(( $endWdate < $startDate ) || ( $startWdate > $endDate )) continue;
    1018       }
    1019             /* deselect components with startdate not within period */
    1020       elseif(( $startWdate < $startDate ) || ( $startWdate > $endDate )) continue;
    1021             /* add selected components within valid dates to output array */
    1022       if( $flat )
    1023         $result[] = $component->copy(); // copy to output;
    1024       elseif( $split ) {
    1025         if( $endWdate > $endDate )
    1026           $endWdate = $endDate;     // use period end date
    1027         if( !isset( $exdatelist[$startWdate] ))  { // check excluded dates
    1028           if( $startWdate < $startDate )
    1029             $startWdate = $startDate; // use period start date
    1030           while( $startWdate <= $endWdate ) { // iterate
    1031             $wd = getdate( $startWdate );
    1032             $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component->copy(); // copy to output
    1033             $startWdate += ( 24*60*60 ); // step one day
    1034           }
    1035         }
    1036       } // use component date
    1037       elseif( !isset( $exdatelist[$startWdate] ) &&   // check excluded dates
    1038             ( $startWdate >= $startDate )) {          // within period
    1039         $wd = getdate( $startWdate );
    1040         $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component->copy(); // copy to output
    1041       }
    1042     }
     1401        if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
     1402          continue;
     1403      } // end if( TRUE === $any )
     1404    } // end foreach ( $this->components as $cix => $component )
     1405    unset( $dtendExist, $dueExist, $durationExist, $endAllDayEvent, $recurrid, $recurridList,
     1406           $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $endDateFormat ); // clean up
    10431407    if( 0 >= count( $result )) return FALSE;
    10441408    elseif( !$flat ) {
    10451409      foreach( $result as $y => $yeararr ) {
    10461410        foreach( $yeararr as $m => $montharr ) {
    1047           ksort( $result[$y][$m] );
    1048         }
    1049         ksort( $result[$y] );
    1050       }
    1051       ksort( $result );
    1052     }
     1411          foreach( $montharr as $d => $dayarr ) {
     1412            if( empty( $result[$y][$m][$d] ))
     1413                unset( $result[$y][$m][$d] );
     1414            else
     1415              $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
     1416          }
     1417          if( empty( $result[$y][$m] ))
     1418              unset( $result[$y][$m] );
     1419          else
     1420            ksort( $result[$y][$m] );
     1421        }
     1422        if( empty( $result[$y] ))
     1423            unset( $result[$y] );
     1424        else
     1425          ksort( $result[$y] );
     1426      }
     1427      if( empty( $result ))
     1428          unset( $result );
     1429      else
     1430        ksort( $result );
     1431    } // end elseif( !$flat )
     1432    if( 0 >= count( $result ))
     1433      return FALSE;
    10531434    return $result;
    10541435  }
    10551436/**
     1437 * select components from calendar on based on specific property value(-s)
     1438 *
     1439 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1440 * @since 2.16.6 - 2012-12-26
     1441 * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
     1442 * @return array
     1443 */
     1444  function selectComponents2( $selectOptions ) {
     1445    $output = array();
     1446    $allowedComps      = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
     1447    $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
     1448    foreach( $this->components as $cix => $component3 ) {
     1449      if( !in_array( $component3->objName, $allowedComps ))
     1450        continue;
     1451      $uid = $component3->getProperty( 'UID' );
     1452      foreach( $selectOptions as $propName => $pvalue ) {
     1453        $propName = strtoupper( $propName );
     1454        if( !in_array( $propName, $allowedProperties ))
     1455          continue;
     1456        if( !is_array( $pvalue ))
     1457          $pvalue = array( $pvalue );
     1458        if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
     1459          $output[$uid][] = $component3->copy();
     1460          continue;
     1461        }
     1462        elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence?
     1463          $propValues = array();
     1464          $component3->_getProperties( $propName, $propValues );
     1465          $propValues = array_keys( $propValues );
     1466          foreach( $pvalue as $theValue ) {
     1467            if( in_array( $theValue, $propValues )) { //  && !isset( $output[$uid] )) {
     1468              $output[$uid][] = $component3->copy();
     1469              break;
     1470            }
     1471          }
     1472          continue;
     1473        } // end   elseif( // multiple occurrence?
     1474        elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
     1475          continue;
     1476        if( is_array( $d )) {
     1477          foreach( $d as $part ) {
     1478            if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
     1479              $output[$uid][] = $component3->copy();
     1480          }
     1481        }
     1482        elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
     1483          foreach( $pvalue as $pval ) {
     1484            if( FALSE !== stripos( $d, $pval )) {
     1485              $output[$uid][] = $component3->copy();
     1486              break;
     1487            }
     1488          }
     1489        }
     1490        elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
     1491          $output[$uid][] = $component3->copy();
     1492      } // end foreach( $selectOptions as $propName => $pvalue ) {
     1493    } // end foreach( $this->components as $cix => $component3 ) {
     1494    if( !empty( $output )) {
     1495      ksort( $output ); // uid order
     1496      $output2 = array();
     1497      foreach( $output as $uid => $components ) {
     1498        foreach( $components as $component )
     1499          $output2[] = $component;
     1500      }
     1501      $output = $output2;
     1502    }
     1503    return $output;
     1504  }
     1505/**
    10561506 * add calendar component to container
    10571507 *
    1058  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1059  * @since 2.4.10 - 2008-08-06
     1508 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1509 * @since 2.8.8 - 2011-03-15
    10601510 * @param object $component calendar component
    10611511 * @param mixed $arg1 optional, ordno/component type/ component uid
     
    10641514 */
    10651515  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
    1066     if( '' >= $component->getConfig( 'language'))
    1067       $component->setConfig( 'language',  $this->getConfig( 'language' ));
    1068     $component->setConfig( 'allowEmpty',  $this->getConfig( 'allowEmpty' ));
    1069     $component->setConfig( 'nl',          $this->getConfig( 'nl' ));
    1070     $component->setConfig( 'unique_id',   $this->getConfig( 'unique_id' ));
    1071     $component->setConfig( 'format',      $this->getConfig( 'format' ));
     1516    $component->setConfig( $this->getConfig(), FALSE, TRUE );
    10721517    if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
    1073       unset( $component->propix );
    10741518            /* make sure dtstamp and uid is set */
    10751519      $dummy1 = $component->getProperty( 'dtstamp' );
    10761520      $dummy2 = $component->getProperty( 'uid' );
    10771521    }
    1078     if( !$arg1 ) {
     1522    if( !$arg1 ) { // plain insert, last in chain
    10791523      $this->components[] = $component->copy();
    10801524      return TRUE;
    10811525    }
    10821526    $argType = $index = null;
    1083     if ( ctype_digit( (string) $arg1 )) {
     1527    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
    10841528      $argType = 'INDEX';
    10851529      $index   = (int) $arg1 - 1;
    10861530    }
    1087     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
     1531    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
    10881532      $argType = strtolower( $arg1 );
    10891533      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
    10901534    }
     1535    // else if arg1 is set, arg1 must be an UID
    10911536    $cix1sC = 0;
    10921537    foreach ( $this->components as $cix => $component2) {
    10931538      if( empty( $component2 )) continue;
    1094       unset( $component2->propix );
    1095       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
     1539      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
    10961540        $this->components[$cix] = $component->copy();
    10971541        return TRUE;
    10981542      }
    1099       elseif( $argType == $component2->objName ) {
     1543      elseif( $argType == $component2->objName ) { // component Type index insert/replace
    11001544        if( $index == $cix1sC ) {
    11011545          $this->components[$cix] = $component->copy();
     
    11041548        $cix1sC++;
    11051549      }
    1106       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) {
     1550      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
    11071551        $this->components[$cix] = $component->copy();
    11081552        return TRUE;
    11091553      }
    11101554    }
    1111             /* not found.. . insert last in chain anyway .. .*/
    1112     $this->components[] = $component->copy();
     1555            /* arg1=index and not found.. . insert at index .. .*/
     1556    if( 'INDEX' == $argType ) {
     1557      $this->components[$index] = $component->copy();
     1558      ksort( $this->components, SORT_NUMERIC );
     1559    }
     1560    else    /* not found.. . insert last in chain anyway .. .*/
     1561      $this->components[] = $component->copy();
    11131562    return TRUE;
    11141563  }
    11151564/**
    1116  * sort iCal compoments, only local date sort
     1565 * sort iCal compoments
    11171566 *
    11181567 * ascending sort on properties (if exist) x-current-dtstart, dtstart,
    1119  * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid
    1120  *
    1121  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1122  * @since 2.4.10 - 2008-09-24
    1123  * @return sort param
    1124  *
    1125  */
    1126   function sort() {
     1568 * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
     1569 * otherwise sorting on specific (argument) property values
     1570 *
     1571 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1572 * @since 2.16.4 - 2012-12-17
     1573 * @param string $sortArg, optional
     1574 * @return void
     1575 *
     1576 */
     1577  function sort( $sortArg=FALSE ) {
    11271578    if( is_array( $this->components )) {
    1128       $this->_sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
    1129       usort( $this->components, array( $this, '_cmpfcn' ));
    1130     }
    1131   }
    1132   function _cmpfcn( $a, $b ) {
    1133     if( empty( $a ))                                   return -1;
    1134     if( empty( $b ))                                   return  1;
    1135     if(  'vtimezone' == $a->objName)                   return -1;
    1136     if(  'vtimezone' == $b->objName)                   return  1;
    1137     $astart = ( isset( $a->xprop['X-CURRENT-DTSTART']['value'] )) ? $a->_date_time_string( $a->xprop['X-CURRENT-DTSTART']['value'] ) : null;
    1138     if( empty( $astart ) && isset( $a->dtstart['value'] ))
    1139       $astart = & $a->dtstart['value'];
    1140     $bstart = ( isset( $b->xprop['X-CURRENT-DTSTART']['value'] )) ? $b->_date_time_string( $b->xprop['X-CURRENT-DTSTART']['value'] ) : null;
    1141     if( empty( $bstart ) && isset( $b->dtstart['value'] ))
    1142       $bstart = & $b->dtstart['value'];
    1143     if(     empty( $astart ))                          return -1;
    1144     elseif( empty( $bstart ))                          return  1;
    1145     foreach( $this->_sortkeys as $key ) {
    1146       if    ( empty( $astart[$key] ))                  return -1;
    1147       elseif( empty( $bstart[$key] ))                  return  1;
    1148       if    (        $astart[$key] == $bstart[$key])   continue;
    1149       if    (( (int) $astart[$key] ) < ((int) $bstart[$key] ))
    1150                                                        return -1;
    1151       elseif(( (int) $astart[$key] ) > ((int) $bstart[$key] ))
    1152                                                        return  1;
    1153     }
    1154     $c   = ( isset( $a->xprop['X-CURRENT-DTEND']['value'] )) ? $a->_date_time_string( $a->xprop['X-CURRENT-DTEND']['value'] ) : null;
    1155     if(     empty( $c ) && !empty( $a->dtend['value'] ))
    1156       $c = & $a->dtend['value'];
    1157     if(     empty( $c ) && isset( $a->xprop['X-CURRENT-DUE']['value'] ))
    1158       $c = $a->_date_time_string( $a->xprop['X-CURRENT-DUE']['value'] );
    1159     if(     empty( $c ) && !empty( $a->due['value'] ))
    1160       $c = & $a->due['value'];
    1161     if(     empty( $c ) && !empty( $a->duration['value'] ))
    1162       $c = $a->duration2date();
    1163     $d   = ( isset( $b->xprop['X-CURRENT-DTEND']['value'] )) ? $b->_date_time_string( $b->xprop['X-CURRENT-DTEND']['value'] ) : null;
    1164     if(     empty( $d ) && !empty( $b->dtend['value'] ))
    1165       $d = & $b->dtend['value'];
    1166     if(     empty( $d ) && isset( $b->xprop['X-CURRENT-DUE']['value'] ))
    1167       $d = $b->_date_time_string( $b->xprop['X-CURRENT-DUE']['value'] );
    1168     if(     empty( $d ) && !empty( $b->due['value'] ))
    1169       $d = & $b->due['value'];
    1170     if(     empty( $d ) && !empty( $b->duration['value'] ))
    1171       $d = $b->duration2date();
    1172     if(     empty( $c ))                               return -1;
    1173     elseif( empty( $d ))                               return  1;
    1174     foreach( $this->_sortkeys as $key ) {
    1175       if    ( !isset( $c[$key] ))                      return -1;
    1176       elseif( !isset( $d[$key] ))                      return  1;
    1177       if    (         $c[$key] == $d[$key] )           continue;
    1178       if    ((  (int) $c[$key] ) < ((int) $d[$key]))   return -1;
    1179       elseif((  (int) $c[$key] ) > ((int) $d[$key]))   return  1;
    1180     }
    1181     if( isset( $a->created['value'] ))
    1182      $e = & $a->created['value'];
    1183     else
    1184      $e = & $a->dtstamp['value'];
    1185     if( isset( $b->created['value'] ))
    1186       $f = & $b->created['value'];
    1187     else
    1188       $f = & $b->dtstamp['value'];
    1189     foreach( $this->_sortkeys as $key ) {
    1190       if(       !isset( $e[$key] ))                    return -1;
    1191       elseif(   !isset( $f[$key] ))                    return  1;
    1192       if    (           $e[$key] == $f[$key] )         continue;
    1193       if    ((    (int) $e[$key] ) < ((int) $f[$key])) return -1;
    1194       elseif((    (int) $e[$key] ) > ((int) $f[$key])) return  1;
    1195     }
    1196     if    ((            $a->uid['value'] ) <
    1197            (            $b->uid['value'] ))            return -1;
    1198     elseif((            $a->uid['value'] ) >
    1199            (            $b->uid['value'] ))            return  1;
    1200     return 0;
    1201   }
    1202 /**
    1203  * parse iCal file into vcalendar, components, properties and parameters
    1204  *
    1205  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1206  * @since 2.4.10 - 2008-08-06
    1207  * @param string $filename optional filname (incl. opt. directory/path) or URL
     1579      if( $sortArg ) {
     1580        $sortArg = strtoupper( $sortArg );
     1581        if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' )))
     1582          $sortArg = FALSE;
     1583      }
     1584            /* set sort parameters for each component */
     1585      foreach( $this->components as $cix => & $c ) {
     1586        $c->srtk = array( '0', '0', '0', '0' );
     1587        if( 'vtimezone' == $c->objName ) {
     1588          if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
     1589            $c->srtk[0] = 0;
     1590          continue;
     1591        }
     1592        elseif( $sortArg ) {
     1593          if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
     1594            $propValues = array();
     1595            $c->_getProperties( $sortArg, $propValues );
     1596            if( !empty( $propValues )) {
     1597              $sk         = array_keys( $propValues );
     1598              $c->srtk[0] = $sk[0];
     1599              if( 'RELATED-TO'  == $sortArg )
     1600                $c->srtk[0] .= $c->getProperty( 'uid' );
     1601            }
     1602            elseif( 'RELATED-TO'  == $sortArg )
     1603              $c->srtk[0] = $c->getProperty( 'uid' );
     1604          }
     1605          elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
     1606            $c->srtk[0] = $d;
     1607            if( 'UID' == $sortArg ) {
     1608              if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
     1609                $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
     1610                if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
     1611                  $c->srtk[2] = PHP_INT_MAX;
     1612              }
     1613              else
     1614                $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
     1615            }
     1616          }
     1617          continue;
     1618        }
     1619        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
     1620          $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
     1621          unset( $c->srtk[0]['unparsedtext'] );
     1622        }
     1623        elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
     1624          $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart
     1625        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
     1626          $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration)
     1627          unset( $c->srtk[1]['unparsedtext'] );
     1628        }
     1629        elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
     1630          if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
     1631            $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
     1632            unset( $c->srtk[1]['unparsedtext'] );
     1633          }
     1634          elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
     1635            if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
     1636              $c->srtk[1] = 0;
     1637        }
     1638        if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
     1639          if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
     1640            $c->srtk[2] = 0;
     1641        if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
     1642          $c->srtk[3] = 0;
     1643      } // end foreach( $this->components as & $c
     1644            /* sort */
     1645      usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
     1646    }
     1647  }
     1648/**
     1649 * parse iCal text/file into vcalendar, components, properties and parameters
     1650 *
     1651 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1652 * @since 2.16.2 - 2012-12-18
     1653 * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
    12081654 * @return bool FALSE if error occurs during parsing
    12091655 *
    12101656 */
    1211   function parse( $filename=FALSE ) {
    1212     if( !$filename ) {
    1213             /* directory/filename previous set via setConfig directory+filename / url */
     1657  function parse( $unparsedtext=FALSE ) {
     1658    $nl = $this->getConfig( 'nl' );
     1659    if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
     1660            /* directory+filename is set previously via setConfig directory+filename or url */
    12141661      if( FALSE === ( $filename = $this->getConfig( 'url' )))
    12151662        $filename = $this->getConfig( 'dirfile' );
    1216     }
    1217     elseif(( 'http://'   == strtolower( substr( $filename, 0, 7 ))) ||
    1218            ( 'webcal://' == strtolower( substr( $filename, 0, 9 ))))  {
    1219             /* remote file - URL */
    1220       $this->setConfig( 'URL', $filename );
    1221       if( !$filename = $this->getConfig( 'url' ))
    1222         return FALSE;                 /* err 2 */
    1223     }
    1224     else {
    1225             /* local directory/filename */
    1226       $parts = pathinfo( $filename );
    1227       if( !empty( $parts['dirname'] ) && ( '.' != $parts['dirname'] )) {
    1228         if( !$this->setConfig( 'directory', $parts['dirname'] ))
    1229           return FALSE;               /* err 3 */
    1230       }
    1231       if( !$this->setConfig( 'filename', $parts['basename'] ))
    1232         return FALSE;                 /* err 4 */
    1233     }
    1234     if( 'http://' != substr( $filename, 0, 7 )) {
    1235             /* local file error tests */
    1236       if( !is_file( $filename ))      /* err 5 */
    1237         return FALSE;
    1238       if( !is_readable( $filename ))
    1239         return FALSE;                 /* err 6 */
    1240       if( !filesize( $filename ))
    1241         return FALSE;                 /* err 7 */
    1242       clearstatcache();
    1243     }
    12441663            /* READ FILE */
    1245     if( FALSE === ( $rows = file( $filename )))
    1246       return FALSE;                   /* err 1 */
    1247             /* identify BEGIN:VCALENDAR, MUST be first row */
    1248     if( 'BEGIN:VCALENDAR' != strtoupper( trim( $rows[0] )))
    1249       return FALSE;                   /* err 8 */
    1250             /* remove empty trailing lines */
    1251     while( '' == trim( $rows[count( $rows ) - 1] )) {
    1252       unset( $rows[count( $rows ) - 1] );
    1253       $rows  = array_values( $rows );
    1254     }
    1255             /* identify ending END:VCALENDAR row */
    1256     if( 'END:VCALENDAR'   != strtoupper( trim( $rows[count( $rows ) - 1] ))) {
    1257       return FALSE;                   /* err 9 */
    1258     }
    1259     if( 3 > count( $rows ))
    1260       return FALSE;                   /* err 10 */
    1261     $comp    = $subcomp = null;
    1262     $actcomp = & $this;
    1263     $nl      = $this->getConfig( 'nl' );
    1264     $calsync = 0;
     1664      if( FALSE === ( $rows = file_get_contents( $filename )))
     1665        return FALSE;                 /* err 1 */
     1666    }
     1667    elseif( is_array( $unparsedtext ))
     1668      $rows =  implode( '\n'.$nl, $unparsedtext );
     1669    else
     1670      $rows = & $unparsedtext;
     1671            /* fix line folding */
     1672    $rows = explode( $nl, iCalUtilityFunctions::convEolChar( $rows, $nl ));
     1673            /* skip leading (empty/invalid) lines */
     1674    foreach( $rows as $lix => $line ) {
     1675      if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' ))
     1676        break;
     1677      unset( $rows[$lix] );
     1678    }
     1679    $rcnt = count( $rows );
     1680    if( 3 > $rcnt )                  /* err 10 */
     1681      return FALSE;
     1682            /* skip trailing empty lines and ensure an end row */
     1683    $lix  = array_keys( $rows );
     1684    $lix  = end( $lix );
     1685    while( 3 < $lix ) {
     1686      $tst = trim( $rows[$lix] );
     1687      if(( '\n' == $tst ) || empty( $tst )) {
     1688        unset( $rows[$lix] );
     1689        $lix--;
     1690        continue;
     1691      }
     1692      if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
     1693        $rows[] = 'END:VCALENDAR';
     1694      break;
     1695    }
     1696    $comp    = & $this;
     1697    $calsync = $compsync = 0;
    12651698            /* identify components and update unparsed data within component */
    1266     foreach( $rows as $line ) {
    1267       if( '' == trim( $line ))
    1268         continue;
    1269       if( $nl == substr( $line, 0 - strlen( $nl )))
    1270         $line = substr( $line, 0, ( strlen( $line ) - strlen( $nl ))).'\n';
    1271       if( 'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
     1699    $config = $this->getConfig();
     1700    $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
     1701    foreach( $rows as $lix => $line ) {
     1702      if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
    12721703        $calsync++;
    12731704        continue;
    12741705      }
    1275       elseif( 'END:VCALENDAR' == strtoupper( substr( $line, 0, 13 ))) {
     1706      elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
     1707        if( 0 < $compsync )
     1708          $this->components[] = $comp->copy();
     1709        $compsync--;
    12761710        $calsync--;
    1277         continue;
     1711        break;
    12781712      }
    12791713      elseif( 1 != $calsync )
    12801714        return FALSE;                 /* err 20 */
    1281       if( 'END:' == strtoupper( substr( $line, 0, 4 ))) {
    1282         if( null != $subcomp ) {
    1283           $comp->setComponent( $subcomp );
    1284           $subcomp = null;
    1285         }
    1286         else {
    1287           $this->setComponent( $comp );
    1288           $comp = null;
    1289         }
    1290         $actcomp = null;
     1715      elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
     1716        $this->components[] = $comp->copy();
     1717        $compsync--;
    12911718        continue;
    1292       } // end - if ( 'END:' ==.. .
    1293       elseif( 'BEGIN:' == strtoupper( substr( $line, 0, 6 ))) {
    1294         $line = str_replace( '\n', '', $line );
    1295         $compname = trim (strtoupper( substr( $line, 6 )));
    1296         if( null != $comp ) {
    1297           if( 'VALARM' == $compname )
    1298             $subcomp = new valarm();
    1299           elseif( 'STANDARD' == $compname )
    1300             $subcomp = new vtimezone( 'STANDARD' );
    1301           elseif( 'DAYLIGHT' == $compname )
    1302             $subcomp = new vtimezone( 'DAYLIGHT' );
    1303           else
    1304             return FALSE; /* err 6 */
    1305           $actcomp = & $subcomp;
    1306         }
    1307         else {
    1308           switch( $compname ) {
    1309             case 'VALARM':
    1310               $comp = new valarm();
    1311               break;
    1312             case 'VEVENT':
    1313               $comp = new vevent();
    1314               break;
    1315             case 'VFREEBUSY';
    1316               $comp = new vfreebusy();
    1317               break;
    1318             case 'VJOURNAL':
    1319               $comp = new vjournal();
    1320               break;
    1321             case 'VTODO':
    1322               $comp = new vtodo();
    1323               break;
    1324             case 'VTIMEZONE':
    1325               $comp = new vtimezone();
    1326               break;
    1327             default:
    1328               return FALSE; // err 7
    1329               break;
    1330           } // end - switch
    1331           $actcomp = & $comp;
    1332         }
    1333         continue;
    1334       } // end - elsif ( 'BEGIN:'.. .
    1335             /* update selected component with unparsed data */
    1336       $actcomp->unparsed[] = $line;
    1337     } // end - foreach( rows.. .
     1719      }
     1720      if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) {
     1721        $comp = new vevent( $config );
     1722        $compsync++;
     1723      }
     1724      elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
     1725        $comp = new vfreebusy( $config );
     1726        $compsync++;
     1727      }
     1728      elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) {
     1729        $comp = new vjournal( $config );
     1730        $compsync++;
     1731      }
     1732      elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) {
     1733        $comp = new vtodo( $config );
     1734        $compsync++;
     1735      }
     1736      elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
     1737        $comp = new vtimezone( $config );
     1738        $compsync++;
     1739      }
     1740      else { /* update component with unparsed data */
     1741        $comp->unparsed[] = $line;
     1742      }
     1743    } // end foreach( $rows as $line )
     1744    unset( $config, $endtxt );
    13381745            /* parse data for calendar (this) object */
    1339     if( is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
     1746    if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
    13401747            /* concatenate property values spread over several lines */
    1341       $lastix    = -1;
    13421748      $propnames = array( 'calscale','method','prodid','version','x-' );
    13431749      $proprows  = array();
    1344       foreach( $this->unparsed as $line ) {
    1345         $newProp = FALSE;
    1346         foreach ( $propnames as $propname ) {
    1347           if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
    1348             $newProp = TRUE;
    1349             break;
    1350           }
    1351         }
    1352         if( $newProp ) {
    1353           $newProp = FALSE;
    1354           $lastix++;
    1355           $proprows[$lastix]  = $line;
    1356         }
    1357         else {
    1358             /* remove line breaks */
    1359           if(( '\n' == substr( $proprows[$lastix], -2 )) &&
    1360              (  ' ' == substr( $line, 0, 1 ))) {
    1361             $proprows[$lastix] = substr( $proprows[$lastix], 0, strlen( $proprows[$lastix] ) - 2 );
    1362             $line = substr( $line, 1 );
    1363           }
    1364           $proprows[$lastix] .= $line;
    1365         }
    1366       }
    1367       $toolbox = new calendarComponent();
     1750      for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
     1751        $line = rtrim( $this->unparsed[$i], $nl );
     1752        while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
     1753          $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
     1754        $proprows[] = $line;
     1755      }
     1756      $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
     1757      $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
     1758      $paramProto4 = array( 'crid:', 'news:', 'pres:' );
    13681759      foreach( $proprows as $line ) {
    13691760        if( '\n' == substr( $line, -2 ))
    1370           $line = substr( $line, 0, strlen( $line ) - 2 );
    1371             /* get propname */
    1372         $cix = $propname = null;
    1373         for( $cix=0; $cix < strlen( $line ); $cix++ ) {
    1374           if( in_array( $line{$cix}, array( ':', ';' )))
     1761          $line = substr( $line, 0, -2 );
     1762            /* get property name */
     1763        $propname  = '';
     1764        $cix       = 0;
     1765        while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
     1766          if( in_array( $char, array( ':', ';' )))
    13751767            break;
    13761768          else
    1377             $propname .= $line{$cix};
    1378         }
     1769            $propname .= $char;
     1770          $cix++;
     1771        }
     1772            /* skip non standard property names */
     1773        if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
     1774          continue;
    13791775            /* ignore version/prodid properties */
    1380         if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' )))
     1776        if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
    13811777          continue;
     1778            /* rest of the line is opt.params and value */
    13821779        $line = substr( $line, $cix);
    13831780            /* separate attributes from value */
    1384         $attr   = array();
    1385         $attrix = -1;
    1386         $strlen = strlen( $line );
    1387         for( $cix=0; $cix < $strlen; $cix++ ) {
    1388           if((       ':'   == $line{$cix} )             &&
    1389                    ( '://' != substr( $line, $cix, 3 )) &&
    1390              ( 'mailto:'   != strtolower( substr( $line, $cix - 6, 7 )))) {
     1781        $attr         = array();
     1782        $attrix       = -1;
     1783        $strlen       = strlen( $line );
     1784        $WithinQuotes = FALSE;
     1785        $cix          = 0;
     1786        while( FALSE !== substr( $line, $cix, 1 )) {
     1787          if(                       ( ':'  == $line[$cix] )                         &&
     1788                                    ( substr( $line,$cix,     3 )  != '://' )       &&
     1789             ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
     1790             ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
     1791             ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
     1792                        ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
     1793               !$WithinQuotes ) {
    13911794            $attrEnd = TRUE;
    13921795            if(( $cix < ( $strlen - 4 )) &&
     
    14001803            }
    14011804            if( $attrEnd) {
    1402               $line = substr( $line, $cix + 1 );
     1805              $line = substr( $line, ( $cix + 1 ));
    14031806              break;
    14041807            }
    14051808          }
    1406           if( ';' == $line{$cix} )
     1809          if( '"' == $line[$cix] )
     1810            $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
     1811          if( ';' == $line[$cix] )
    14071812            $attr[++$attrix] = null;
    14081813          else
    1409             $attr[$attrix] .= $line{$cix};
    1410         }
    1411 
     1814            $attr[$attrix] .= $line[$cix];
     1815          $cix++;
     1816        }
    14121817            /* make attributes in array format */
    14131818        $propattr = array();
     
    14211826            /* update Property */
    14221827        if( FALSE !== strpos( $line, ',' )) {
    1423           $content  = explode( ',', $line );
    1424           $clen     = count( $content );
    1425           for( $cix = 0; $cix < $clen; $cix++ ) {
    1426             if( "\\" == substr( $content[$cix], -1 )) {
    1427               $content[$cix] .= ','.$content[$cix + 1];
    1428               unset( $content[$cix + 1] );
     1828          $content  = array( 0 => '' );
     1829          $cix = $lix = 0;
     1830          while( FALSE !== substr( $line, $lix, 1 )) {
     1831            if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
    14291832              $cix++;
     1833              $content[$cix] = '';
    14301834            }
     1835            else
     1836              $content[$cix] .= $line[$lix];
     1837            $lix++;
    14311838          }
    14321839          if( 1 < count( $content )) {
    14331840            foreach( $content as $cix => $contentPart )
    1434               $content[$cix] = $toolbox->_strunrep( $contentPart );
     1841              $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
    14351842            $this->setProperty( $propname, $content, $propattr );
    14361843            continue;
     
    14381845          else
    14391846            $line = reset( $content );
    1440           $line = $toolbox->_strunrep( $line );
    1441         }
    1442         $this->setProperty( $propname, trim( $line ), $propattr );
     1847          $line = iCalUtilityFunctions::_strunrep( $line );
     1848        }
     1849        $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
    14431850      } // end - foreach( $this->unparsed.. .
    14441851    } // end - if( is_array( $this->unparsed.. .
     1852    unset( $unparsedtext, $rows, $this->unparsed, $proprows );
    14451853            /* parse Components */
    14461854    if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
    1447       for( $six = 0; $six < count( $this->components ); $six++ ) {
    1448         if( !empty( $this->components[$six] ))
    1449           $this->components[$six]->parse();
     1855      $ckeys = array_keys( $this->components );
     1856      foreach( $ckeys as $ckey ) {
     1857        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
     1858          $this->components[$ckey]->parse();
     1859        }
    14501860      }
    14511861    }
     
    14581868 * creates formatted output for calendar object instance
    14591869 *
    1460  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1461  * @since 2.4.10 - 2008-08-06
     1870 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1871 * @since 2.10.16 - 2011-10-28
    14621872 * @return string
    14631873 */
    14641874  function createCalendar() {
    1465     $calendarInit1 = $calendarInit2 = $calendarxCaldecl = $calendarStart = $calendar = null;
     1875    $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
    14661876    switch( $this->format ) {
    14671877      case 'xcal':
    1468         $calendarInit1 = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
    1469                          '<!DOCTYPE iCalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
     1878        $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
     1879                         '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
    14701880                         '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
    1471         $calendarInit2 = '>'.$this->nl;
    1472         $calendarStart = '<vcalendar';
     1881        $calendarStart = '>'.$this->nl.'<vcalendar';
    14731882        break;
    14741883      default:
     
    14761885        break;
    14771886    }
     1887    $calendarStart .= $this->createVersion();
     1888    $calendarStart .= $this->createProdid();
    14781889    $calendarStart .= $this->createCalscale();
    14791890    $calendarStart .= $this->createMethod();
    1480     $calendarStart .= $this->createProdid();
    1481     $calendarStart .= $this->createVersion();
    1482     switch( $this->format ) {
    1483       case 'xcal':
    1484         $nlstrlen = strlen( $this->nl );
    1485         if( $this->nl == substr( $calendarStart, ( 0 - $nlstrlen )))
    1486           $calendarStart = substr( $calendarStart, 0, ( strlen( $calendarStart ) - $nlstrlen ));
    1487         $calendarStart .= '>'.$this->nl;
    1488         break;
    1489       default:
    1490         break;
    1491     }
     1891    if( 'xcal' == $this->format )
     1892      $calendarStart .= '>'.$this->nl;
    14921893    $calendar .= $this->createXprop();
     1894
    14931895    foreach( $this->components as $component ) {
    14941896      if( empty( $component )) continue;
    1495       if( '' >= $component->getConfig( 'language'))
    1496         $component->setConfig( 'language',  $this->getConfig( 'language' ));
    1497       $component->setConfig( 'allowEmpty',  $this->getConfig( 'allowEmpty' ));
    1498       $component->setConfig( 'nl',          $this->getConfig( 'nl' ));
    1499       $component->setConfig( 'unique_id',   $this->getConfig( 'unique_id' ));
    1500       $component->setConfig( 'format',      $this->getConfig( 'format' ));
     1897      $component->setConfig( $this->getConfig(), FALSE, TRUE );
    15011898      $calendar .= $component->createComponent( $this->xcaldecl );
    15021899    }
    1503     if(( 0 < count( $this->xcaldecl )) && ( 'xcal' == $this->format )) { // xCal only
    1504       $calendarInit1 .= $this->nl.'['.$this->nl;
    1505       $old_xcaldecl = array();
     1900    if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
     1901      $calendarInit .= ' [';
     1902      $old_xcaldecl  = array();
    15061903      foreach( $this->xcaldecl as $declix => $declPart ) {
    1507         if(( 0 < count( $old_xcaldecl)) &&
    1508            ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] )) &&
     1904        if(( 0 < count( $old_xcaldecl))    &&
     1905             isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
     1906             isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
     1907           ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
    15091908           ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
    15101909          continue; // no duplicate uri and ext. references
    1511         $calendarxCaldecl .= '<!';
     1910        if(( 0 < count( $old_xcaldecl))    &&
     1911            !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
     1912             isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
     1913           ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
     1914          continue; // no duplicate element declarations
     1915        $calendarxCaldecl .= $this->nl.'<!';
    15121916        foreach( $declPart as $declKey => $declValue ) {
    15131917          switch( $declKey ) {                    // index
     
    15211925            case 'ref':                           // no 3
    15221926              $calendarxCaldecl .= $declValue.' ';
     1927              $old_xcaldecl['ref'][] = $declValue;
    15231928              break;
    15241929            case 'external':                      // no 4
     
    15341939          }
    15351940        }
    1536         $calendarxCaldecl .= '>'.$this->nl;
    1537       }
    1538       $calendarInit2 = ']'.$calendarInit2;
     1941        $calendarxCaldecl .= '>';
     1942      }
     1943      $calendarxCaldecl .= $this->nl.']';
    15391944    }
    15401945    switch( $this->format ) {
     
    15461951        break;
    15471952    }
    1548     return $calendarInit1.$calendarxCaldecl.$calendarInit2.$calendarStart.$calendar;
     1953    return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
    15491954  }
    15501955/**
    15511956 * a HTTP redirect header is sent with created, updated and/or parsed calendar
    15521957 *
    1553  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1554  * @since 2.2.12 - 2007-10-23
     1958 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     1959 * @since 2.10.24 - 2011-12-23
     1960 * @param bool $utf8Encode
     1961 * @param bool $gzip
    15551962 * @return redirect
    15561963 */
    1557   function returnCalendar() {
     1964  function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
    15581965    $filename = $this->getConfig( 'filename' );
    15591966    $output   = $this->createCalendar();
    1560     $filesize = strlen( $output );
    1561 //    if( headers_sent( $filename, $linenum ))
    1562 //      die( "Headers already sent in $filename on line $linenum\n" );
     1967    if( $utf8Encode )
     1968      $output = utf8_encode( $output );
     1969    if( $gzip ) {
     1970      $output = gzencode( $output, 9 );
     1971      header( 'Content-Encoding: gzip' );
     1972      header( 'Vary: *' );
     1973      header( 'Content-Length: '.strlen( $output ));
     1974    }
    15631975    if( 'xcal' == $this->format )
    15641976      header( 'Content-Type: application/calendar+xml; charset=utf-8' );
    15651977    else
    15661978      header( 'Content-Type: text/calendar; charset=utf-8' );
    1567     header( 'Content-Length: '.$filesize );
    15681979    header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
    15691980    header( 'Cache-Control: max-age=10' );
    1570     echo $output;
    1571     die();
     1981    die( $output );
    15721982  }
    15731983/**
    15741984 * save content in a file
    15751985 *
    1576  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     1986 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    15771987 * @since 2.2.12 - 2007-12-30
    15781988 * @param string $directory optional
     
    16042014 * else FALSE is returned
    16052015 *
    1606  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2016 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    16072017 * @since 2.2.12 - 2007-10-28
    16082018 * @param string $directory optional alt. int timeout
     
    16402050      header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
    16412051      header( 'Cache-Control: max-age=10' );
    1642       $fp = @$fopen( $dirfile, 'r' );
     2052      $fp = @fopen( $dirfile, 'r' );
    16432053      if( $fp ) {
    16442054        fpassthru( $fp );
     
    16562066 *  abstract class for calendar components
    16572067 *
    1658  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1659  * @since 2.4.19 - 2008-10-12
     2068 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2069 * @since 2.9.6 - 2011-05-14
    16602070 */
    16612071class calendarComponent {
     
    16712081  var $format;
    16722082  var $objName; // created automatically at instance creation
     2083  var $dtzid;   // default (local) timezone
    16732084            //  component internal variables
    16742085  var $componentStart1;
     
    16882099 * constructor for calendar component object
    16892100 *
    1690  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1691  * @since 2.4.19 - 2008-10-23
     2101 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2102 * @since 2.9.6 - 2011-05-17
    16922103 */
    16932104  function calendarComponent() {
     
    17012112    $this->unique_id       = null;
    17022113    $this->format          = null;
     2114    $this->dtzid           = null;
    17032115    $this->allowEmpty      = TRUE;
    17042116    $this->xcaldecl        = array();
     
    17142126 * creates formatted output for calendar component property action
    17152127 *
    1716  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2128 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    17172129 * @since 2.4.8 - 2008-10-22
    17182130 * @return string
     
    17282140 * set calendar component property action
    17292141 *
    1730  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2142 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    17312143 * @since 2.4.8 - 2008-11-04
    17322144 * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
     
    17362148  function setAction( $value, $params=FALSE ) {
    17372149    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    1738     $this->action = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     2150    $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    17392151    return TRUE;
    17402152  }
     
    17462158 * creates formatted output for calendar component property attach
    17472159 *
    1748  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1749  * @since 0.9.7 - 2006-11-23
     2160 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2161 * @since 2.11.16 - 2012-02-04
    17502162 * @return string
    17512163 */
     
    17542166    $output       = null;
    17552167    foreach( $this->attach as $attachPart ) {
    1756       if(! empty( $attachPart['value'] )) {
     2168      if( !empty( $attachPart['value'] )) {
    17572169        $attributes = $this->_createParams( $attachPart['params'] );
     2170        if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
     2171          $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
     2172          $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
     2173          $output     = substr( $str, 0, 75 ).$this->nl;
     2174          $str        = substr( $str, 75 );
     2175          $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
     2176          if( ' ' == substr( $output, -1 ))
     2177            $output   = rtrim( $output );
     2178          if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
     2179            $output  .= $this->nl;
     2180          return $output;
     2181        }
    17582182        $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
    17592183      }
     
    17652189 * set calendar component property attach
    17662190 *
    1767  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2191 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    17682192 * @since 2.5.1 - 2008-11-06
    17692193 * @param string $value
     
    17742198  function setAttach( $value, $params=FALSE, $index=FALSE ) {
    17752199    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    1776     $this->_setMval( $this->attach, $value, $params, FALSE, $index );
     2200    iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
    17772201    return TRUE;
    17782202  }
     
    17842208 * creates formatted output for calendar component property attendee
    17852209 *
    1786  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1787  * @since 2.4.8 - 2008-09-23
     2210 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2211 * @since 2.11.12 - 2012-01-31
    17882212 * @return string
    17892213 */
     
    17972221        continue;
    17982222      }
    1799       $attendee1 = $attendee2 = $attendeeLANG = $attendeeCN = null;
     2223      $attendee1 = $attendee2 = null;
    18002224      foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
    18012225        if( 'value' == $paramlabel )
    1802           $attendee2  .= 'MAILTO:'.$paramvalue;
     2226          $attendee2     .= $paramvalue;
    18032227        elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
     2228          $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
     2229          foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
     2230            if( is_array( $pValue ) || in_array( $pKey, $mParams ))
     2231              continue;
     2232            if(( FALSE !== strpos( $pValue, ':' )) ||
     2233               ( FALSE !== strpos( $pValue, ';' )) ||
     2234               ( FALSE !== strpos( $pValue, ',' )))
     2235              $paramvalue[$pKey] = '"'.$pValue.'"';
     2236          }
     2237        // set attenddee parameters in rfc2445 order
     2238          if( isset( $paramvalue['CUTYPE'] ))
     2239            $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
     2240          if( isset( $paramvalue['MEMBER'] )) {
     2241            $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
     2242            foreach( $paramvalue['MEMBER'] as $cix => $opv )
     2243              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
     2244          }
     2245          if( isset( $paramvalue['ROLE'] ))
     2246            $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
     2247          if( isset( $paramvalue['PARTSTAT'] ))
     2248            $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
     2249          if( isset( $paramvalue['RSVP'] ))
     2250            $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
     2251          if( isset( $paramvalue['DELEGATED-TO'] )) {
     2252            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
     2253            foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
     2254              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
     2255          }
     2256          if( isset( $paramvalue['DELEGATED-FROM'] )) {
     2257            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
     2258            foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
     2259              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
     2260          }
     2261          if( isset( $paramvalue['SENT-BY'] ))
     2262            $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
     2263          if( isset( $paramvalue['CN'] ))
     2264            $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
     2265          if( isset( $paramvalue['DIR'] )) {
     2266            $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
     2267            $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
     2268          }
     2269          if( isset( $paramvalue['LANGUAGE'] ))
     2270            $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
     2271          $xparams = array();
    18042272          foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
    1805             $attendee11 = $attendee12 = null;
    1806             if( is_int( $optparamlabel )) {
    1807               $attendee1 .= $this->intAttrDelimiter.$optparamvalue;
     2273            if( ctype_digit( (string) $optparamlabel )) {
     2274              $xparams[]  = $optparamvalue;
    18082275              continue;
    18092276            }
    1810             switch( $optparamlabel ) {                                 // start switch
    1811               case 'CUTYPE':
    1812               case 'PARTSTAT':
    1813               case 'ROLE':
    1814               case 'RSVP':
    1815                 $attendee1 .= $this->intAttrDelimiter.$optparamlabel.'="'.$optparamvalue.'"';
    1816                 break;
    1817               case 'SENT-BY':
    1818                 $attendee1 .= $this->intAttrDelimiter.'SENT-BY="MAILTO:'.$optparamvalue.'"';
    1819                 break;
    1820               case 'MEMBER':
    1821                 $attendee11 = $this->intAttrDelimiter.'MEMBER=';
    1822               case 'DELEGATED-TO':
    1823                 $attendee11 = ( !$attendee11 ) ? $this->intAttrDelimiter.'DELEGATED-TO='   : $attendee11;
    1824               case 'DELEGATED-FROM':
    1825                 $attendee11 = ( !$attendee11 ) ? $this->intAttrDelimiter.'DELEGATED-FROM=' : $attendee11;
    1826                 foreach( $optparamvalue  as $cix => $calUserAddress ) {
    1827                   $attendee12 .= ( $cix ) ? ',' : null;
    1828                   $attendee12 .= '"MAILTO:'.$calUserAddress.'"';
    1829                 }
    1830                 $attendee1  .= $attendee11.$attendee12;
    1831                 break;
    1832               case 'CN':
    1833                 $attendeeCN .= $this->intAttrDelimiter.'CN="'.$optparamvalue.'"';
    1834                 break;
    1835               case 'DIR':
    1836                 $attendee1 .= $this->intAttrDelimiter.'DIR="'.$optparamvalue.'"';
    1837                 break;
    1838               case 'LANGUAGE':
    1839                 $attendeeLANG .= $this->intAttrDelimiter.'LANGUAGE='.$optparamvalue;
    1840                 break;
    1841               default:
    1842                 $attendee1 .= $this->intAttrDelimiter."$optparamlabel=$optparamvalue";
    1843                 break;
    1844             }    // end switch
     2277            if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
     2278              $xparams[$optparamlabel] = $optparamvalue;
     2279          } // end foreach 3
     2280          ksort( $xparams, SORT_STRING );
     2281          foreach( $xparams as $paramKey => $paramValue ) {
     2282            if( ctype_digit( (string) $paramKey ))
     2283              $attendee1 .= $this->intAttrDelimiter.$paramValue;
     2284            else
     2285              $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
    18452286          }      // end foreach 3
    1846         }        // end elseif
     2287        }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
    18472288      }          // end foreach 2
    1848       $output .= $this->_createElement( 'ATTENDEE', $attendee1.$attendeeLANG.$attendeeCN, $attendee2 );
     2289      $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
    18492290    }              // end foreach 1
    18502291    return $output;
     
    18532294 * set calendar component property attach
    18542295 *
    1855  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1856  * @since 2.5.1 - 2008-11-05
     2296 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2297 * @since 2.12.18 - 2012-07-13
    18572298 * @param string $value
    18582299 * @param array $params, optional
     
    18622303  function setAttendee( $value, $params=FALSE, $index=FALSE ) {
    18632304    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    1864     $value = str_replace ( 'MAILTO:', '', $value );
    1865     $value = str_replace ( 'mailto:', '', $value );
     2305          // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
     2306    if( !empty( $value )) {
     2307      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
     2308        $value = 'MAILTO:'.$value;
     2309      elseif( !empty( $value ))
     2310        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
     2311      $value = str_replace( 'mailto:', 'MAILTO:', $value );
     2312    }
    18662313    $params2 = array();
    18672314    if( is_array($params )) {
     
    18732320          case 'DELEGATED-TO':
    18742321          case 'DELEGATED-FROM':
    1875             if( is_array( $optparamvalue )) {
    1876               foreach( $optparamvalue as $part ) {
    1877                 $part = str_replace( 'MAILTO:', '', $part );
    1878                 $part = str_replace( 'mailto:', '', $part );
    1879                 if(( '"' == $part{0} ) && ( '"' == $part{strlen($part)-1} ))
    1880                   $part = substr( $part, 1, ( strlen($part)-2 ));
    1881                 $optarrays[$optparamlabel][] = $part;
    1882               }
    1883             }
    1884             else {
    1885               $part = str_replace( 'MAILTO:', '', $optparamvalue );
    1886               $part = str_replace( 'mailto:', '', $part );
    1887               if(( '"' == $part{0} ) && ( '"' == $part{strlen($part)-1} ))
    1888                 $part = substr( $part, 1, ( strlen($part)-2 ));
     2322            if( !is_array( $optparamvalue ))
     2323              $optparamvalue = array( $optparamvalue );
     2324            foreach( $optparamvalue as $part ) {
     2325              $part = trim( $part );
     2326              if(( '"' == substr( $part, 0, 1 )) &&
     2327                 ( '"' == substr( $part, -1 )))
     2328                $part = substr( $part, 1, ( strlen( $part ) - 2 ));
     2329              if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
     2330                $part = "MAILTO:$part";
     2331              else
     2332                $part = 'MAILTO:'.substr( $part, 7 );
    18892333              $optarrays[$optparamlabel][] = $part;
    18902334            }
    18912335            break;
    18922336          default:
    1893             if( 'SENT-BY' ==  $optparamlabel ) {
    1894               $optparamvalue = str_replace( 'MAILTO:', '', $optparamvalue );
    1895               $optparamvalue = str_replace( 'mailto:', '', $optparamvalue );
    1896             }
    18972337            if(( '"' == substr( $optparamvalue, 0, 1 )) &&
    18982338               ( '"' == substr( $optparamvalue, -1 )))
    18992339              $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
     2340            if( 'SENT-BY' ==  $optparamlabel ) {
     2341              if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
     2342                $optparamvalue = "MAILTO:$optparamvalue";
     2343              else
     2344                $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
     2345            }
    19002346            $params2[$optparamlabel] = $optparamvalue;
    19012347            break;
     
    19062352    }
    19072353        // remove defaults
    1908     $this->_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
    1909     $this->_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
    1910     $this->_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
    1911     $this->_existRem( $params2, 'RSVP',     'FALSE' );
     2354    iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
     2355    iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
     2356    iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
     2357    iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
    19122358        // check language setting
    19132359    if( isset( $params2['CN' ] )) {
     
    19162362        $params2['LANGUAGE' ] = $lang;
    19172363    }
    1918     $this->_setMval( $this->attendee, $value, $params2, FALSE, $index );
     2364    iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
    19192365    return TRUE;
    19202366  }
     
    19262372 * creates formatted output for calendar component property categories
    19272373 *
    1928  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    1929  * @since 2.4.8 - 2008-10-22
     2374 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2375 * @since 2.16.2 - 2012-12-18
    19302376 * @return string
    19312377 */
     
    19422388      if( is_array( $category['value'] )) {
    19432389        foreach( $category['value'] as $cix => $categoryPart )
    1944           $category['value'][$cix] = $this->_strrep( $categoryPart );
     2390          $category['value'][$cix] = iCalUtilityFunctions::_strrep( $categoryPart, $this->format, $this->nl );
    19452391        $content  = implode( ',', $category['value'] );
    19462392      }
    19472393      else
    1948         $content  = $this->_strrep( $category['value'] );
     2394        $content  = iCalUtilityFunctions::_strrep( $category['value'], $this->format, $this->nl );
    19492395      $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
    19502396    }
     
    19542400 * set calendar component property categories
    19552401 *
    1956  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2402 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    19572403 * @since 2.5.1 - 2008-11-06
    19582404 * @param mixed $value
     
    19632409  function setCategories( $value, $params=FALSE, $index=FALSE ) {
    19642410    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    1965     $this->_setMval( $this->categories, $value, $params, FALSE, $index );
     2411    iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
    19662412    return TRUE;
    19672413 }
     
    19732419 * creates formatted output for calendar component property class
    19742420 *
    1975  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2421 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    19762422 * @since 0.9.7 - 2006-11-20
    19772423 * @return string
     
    19872433 * set calendar component property class
    19882434 *
    1989  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2435 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    19902436 * @since 2.4.8 - 2008-11-04
    19912437 * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
     
    19952441  function setClass( $value, $params=FALSE ) {
    19962442    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    1997     $this->class = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     2443    $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    19982444    return TRUE;
    19992445  }
     
    20052451 * creates formatted output for calendar component property comment
    20062452 *
    2007  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2008  * @since 2.4.8 - 2008-10-22
     2453 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2454 * @since 2.16.2 - 2012-12-18
    20092455 * @return string
    20102456 */
     
    20182464      }
    20192465      $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
    2020       $content    = $this->_strrep( $commentPart['value'] );
     2466      $content    = iCalUtilityFunctions::_strrep( $commentPart['value'], $this->format, $this->nl );
    20212467      $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
    20222468    }
     
    20262472 * set calendar component property comment
    20272473 *
    2028  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2474 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    20292475 * @since 2.5.1 - 2008-11-06
    20302476 * @param string $value
     
    20352481  function setComment( $value, $params=FALSE, $index=FALSE ) {
    20362482    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    2037     $this->_setMval( $this->comment, $value, $params, FALSE, $index );
     2483    iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
    20382484    return TRUE;
    20392485  }
     
    20452491 * creates formatted output for calendar component property completed
    20462492 *
    2047  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2493 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    20482494 * @since 2.4.8 - 2008-10-22
    20492495 * @return string
     
    20602506        return $this->_createElement( 'COMPLETED' );
    20612507      else return FALSE;
    2062     $formatted  = $this->_format_date_time( $this->completed['value'], 7 );
     2508    $formatted  = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 );
    20632509    $attributes = $this->_createParams( $this->completed['params'] );
    20642510    return $this->_createElement( 'COMPLETED', $attributes, $formatted );
     
    20672513 * set calendar component property completed
    20682514 *
    2069  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2515 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    20702516 * @since 2.4.8 - 2008-10-23
    20712517 * @param mixed $year
     
    20812527    if( empty( $year )) {
    20822528      if( $this->getConfig( 'allowEmpty' )) {
    2083         $this->completed = array( 'value' => null, 'params' => $this->_setParams( $params ));
     2529        $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
    20842530        return TRUE;
    20852531      }
     
    20872533        return FALSE;
    20882534    }
    2089     $this->completed = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
     2535    $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
    20902536    return TRUE;
    20912537  }
     
    20972543 * creates formatted output for calendar component property contact
    20982544 *
    2099  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2100  * @since 2.4.8 - 2008-10-23
     2545 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2546 * @since 2.16.2 - 2012-12-18
    21012547 * @return string
    21022548 */
     
    21072553      if( !empty( $contact['value'] )) {
    21082554        $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
    2109         $content    = $this->_strrep( $contact['value'] );
     2555        $content    = iCalUtilityFunctions::_strrep( $contact['value'], $this->format, $this->nl );
    21102556        $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
    21112557      }
     
    21172563 * set calendar component property contact
    21182564 *
    2119  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2565 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    21202566 * @since 2.5.1 - 2008-11-05
    21212567 * @param string $value
     
    21262572  function setContact( $value, $params=FALSE, $index=FALSE ) {
    21272573    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    2128     $this->_setMval( $this->contact, $value, $params, FALSE, $index );
     2574    iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
    21292575    return TRUE;
    21302576  }
     
    21362582 * creates formatted output for calendar component property created
    21372583 *
    2138  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2584 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    21392585 * @since 2.4.8 - 2008-10-21
    21402586 * @return string
     
    21422588  function createCreated() {
    21432589    if( empty( $this->created )) return FALSE;
    2144     $formatted  = $this->_format_date_time( $this->created['value'], 7 );
     2590    $formatted  = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 );
    21452591    $attributes = $this->_createParams( $this->created['params'] );
    21462592    return $this->_createElement( 'CREATED', $attributes, $formatted );
     
    21492595 * set calendar component property created
    21502596 *
    2151  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2597 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    21522598 * @since 2.4.8 - 2008-10-23
    21532599 * @param mixed $year optional
     
    21642610      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
    21652611    }
    2166     $this->created = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
     2612    $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
    21672613    return TRUE;
    21682614  }
     
    21742620 * creates formatted output for calendar component property description
    21752621 *
    2176  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2177  * @since 2.4.8 - 2008-10-22
     2622 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2623 * @since 2.16.2 - 2012-12-18
    21782624 * @return string
    21792625 */
     
    21842630      if( !empty( $description['value'] )) {
    21852631        $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
    2186         $content    = $this->_strrep( $description['value'] );
     2632        $content    = iCalUtilityFunctions::_strrep( $description['value'], $this->format, $this->nl );
    21872633        $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
    21882634      }
     
    21942640 * set calendar component property description
    21952641 *
    2196  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2197  * @since 2.5.1 - 2008-11-05
     2642 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2643 * @since 2.6.24 - 2010-11-06
    21982644 * @param string $value
    21992645 * @param array $params, optional
     
    22032649  function setDescription( $value, $params=FALSE, $index=FALSE ) {
    22042650    if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
    2205     $this->_setMval( $this->description, $value, $params, FALSE, $index );
     2651    if( 'vjournal' != $this->objName )
     2652      $index = 1;
     2653    iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
    22062654    return TRUE;
    22072655  }
     
    22132661 * creates formatted output for calendar component property dtend
    22142662 *
    2215  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2216  * @since 2.4.8 - 2008-10-21
     2663 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2664 * @since 2.14.4 - 2012-09-26
    22172665 * @return string
    22182666 */
     
    22282676        return $this->_createElement( 'DTEND' );
    22292677      else return FALSE;
    2230     $formatted  = $this->_format_date_time( $this->dtend['value'] );
     2678    $parno      = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null;
     2679    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno );
    22312680    $attributes = $this->_createParams( $this->dtend['params'] );
    22322681    return $this->_createElement( 'DTEND', $attributes, $formatted );
     
    22352684 * set calendar component property dtend
    22362685 *
    2237  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2238  * @since 2.4.8 - 2008-10-23
     2686 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2687 * @since 2.9.6 - 2011-05-14
    22392688 * @param mixed $year
    22402689 * @param mixed $month optional
     
    22502699    if( empty( $year )) {
    22512700      if( $this->getConfig( 'allowEmpty' )) {
    2252         $this->dtend = array( 'value' => null, 'params' => $this->_setParams( $params ));
     2701        $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
    22532702        return TRUE;
    22542703      }
     
    22562705        return FALSE;
    22572706    }
    2258     $this->dtend = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params );
     2707    $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
    22592708    return TRUE;
    22602709  }
     
    22662715 * creates formatted output for calendar component property dtstamp
    22672716 *
    2268  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2717 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    22692718 * @since 2.4.4 - 2008-03-07
    22702719 * @return string
     
    22782727        !isset( $this->dtstamp['value']['sec'] ))
    22792728      $this->_makeDtstamp();
    2280     $formatted  = $this->_format_date_time( $this->dtstamp['value'], 7 );
     2729    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 );
    22812730    $attributes = $this->_createParams( $this->dtstamp['params'] );
    22822731    return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
     
    22852734 * computes datestamp for calendar component object instance dtstamp
    22862735 *
    2287  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2288  * @since 1.x.x - 2007-05-13
     2736 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2737 * @since 2.14.1 - 2012-09-29
    22892738 * @return void
    22902739 */
    22912740  function _makeDtstamp() {
    2292     $this->dtstamp['value'] = array( 'year'  => date( 'Y' )
    2293                                    , 'month' => date( 'm' )
    2294                                    , 'day'   => date( 'd' )
    2295                                    , 'hour'  => date( 'H' )
    2296                                    , 'min'   => date( 'i' )
    2297                                    , 'sec'   => date( 's' ) - date( 'Z' ));
     2741    $d    = date( 'Y-m-d-H-i-s', mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')));
     2742    $date = explode( '-', $d );
     2743    $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' );
    22982744    $this->dtstamp['params'] = null;
    22992745  }
     
    23012747 * set calendar component property dtstamp
    23022748 *
    2303  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2749 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    23042750 * @since 2.4.8 - 2008-10-23
    23052751 * @param mixed $year
     
    23162762      $this->_makeDtstamp();
    23172763    else
    2318       $this->dtstamp = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
     2764      $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
    23192765    return TRUE;
    23202766  }
     
    23262772 * creates formatted output for calendar component property dtstart
    23272773 *
    2328  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2329  * @since 2.4.16 - 2008-10-26
     2774 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2775 * @since 2.14.4 - 2012-09-26
    23302776 * @return string
    23312777 */
     
    23372783        !isset( $this->dtstart['value']['hour'] )  &&
    23382784        !isset( $this->dtstart['value']['min'] )   &&
    2339         !isset( $this->dtstart['value']['sec'] ))
    2340     if( $this->getConfig( 'allowEmpty' ))
    2341       return $this->_createElement( 'DTSTART' );
    2342     else return FALSE;
     2785        !isset( $this->dtstart['value']['sec'] )) {
     2786      if( $this->getConfig( 'allowEmpty' ))
     2787        return $this->_createElement( 'DTSTART' );
     2788      else return FALSE;
     2789    }
    23432790    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
    2344       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
    2345     $formatted  = $this->_format_date_time( $this->dtstart['value'] );
     2791       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
     2792    $parno      = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null;
     2793    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno );
    23462794    $attributes = $this->_createParams( $this->dtstart['params'] );
    23472795    return $this->_createElement( 'DTSTART', $attributes, $formatted );
     
    23502798 * set calendar component property dtstart
    23512799 *
    2352  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2353  * @since 2.4.16 - 2008-11-04
     2800 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2801 * @since 2.6.22 - 2010-09-22
    23542802 * @param mixed $year
    23552803 * @param mixed $month optional
     
    23652813    if( empty( $year )) {
    23662814      if( $this->getConfig( 'allowEmpty' )) {
    2367         $this->dtstart = array( 'value' => null, 'params' => $this->_setParams( $params ));
     2815        $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
    23682816        return TRUE;
    23692817      }
     
    23712819        return FALSE;
    23722820    }
    2373     $this->dtstart = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart' );
     2821    $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
    23742822    return TRUE;
    23752823  }
     
    23812829 * creates formatted output for calendar component property due
    23822830 *
    2383  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2384  * @since 2.4.8 - 2008-10-22
     2831 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2832 * @since 2.14.4 - 2012-09-26
    23852833 * @return string
    23862834 */
     
    23922840        !isset( $this->due['value']['hour'] )  &&
    23932841        !isset( $this->due['value']['min'] )   &&
    2394         !isset( $this->due['value']['sec'] ))
     2842        !isset( $this->due['value']['sec'] )) {
    23952843      if( $this->getConfig( 'allowEmpty' ))
    23962844        return $this->_createElement( 'DUE' );
    2397       else return FALSE;
    2398     $formatted  = $this->_format_date_time( $this->due['value'] );
     2845      else
     2846       return FALSE;
     2847    }
     2848    $parno      = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null;
     2849    $formatted  = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno );
    23992850    $attributes = $this->_createParams( $this->due['params'] );
    24002851    return $this->_createElement( 'DUE', $attributes, $formatted );
     
    24032854 * set calendar component property due
    24042855 *
    2405  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2856 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    24062857 * @since 2.4.8 - 2008-11-04
    24072858 * @param mixed $year
     
    24172868    if( empty( $year )) {
    24182869      if( $this->getConfig( 'allowEmpty' )) {
    2419         $this->due = array( 'value' => null, 'params' => $this->_setParams( $params ));
     2870        $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
    24202871        return TRUE;
    24212872      }
     
    24232874        return FALSE;
    24242875    }
    2425     $this->due = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params );
     2876    $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
    24262877    return TRUE;
    24272878  }
     
    24332884 * creates formatted output for calendar component property duration
    24342885 *
    2435  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2886 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    24362887 * @since 2.4.8 - 2008-10-21
    24372888 * @return string
     
    24482899      else return FALSE;
    24492900    $attributes = $this->_createParams( $this->duration['params'] );
    2450     return $this->_createElement( 'DURATION', $attributes, $this->_format_duration( $this->duration['value'] ));
     2901    return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] ));
    24512902  }
    24522903/**
    24532904 * set calendar component property duration
    24542905 *
    2455  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     2906 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    24562907 * @since 2.4.8 - 2008-11-04
    24572908 * @param mixed $week
     
    24662917    if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
    24672918    if( is_array( $week ) && ( 1 <= count( $week )))
    2468       $this->duration = array( 'value' => $this->_duration_array( $week ), 'params' => $this->_setParams( $day ));
     2919      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
    24692920    elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
    24702921      $week = trim( $week );
    24712922      if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
    24722923        $week = substr( $week, 1 );
    2473       $this->duration = array( 'value' => $this->_duration_string( $week ), 'params' => $this->_setParams( $day ));
     2924      $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
    24742925    }
    24752926    elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
    24762927      return FALSE;
    24772928    else
    2478       $this->duration = array( 'value' => $this->_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => $this->_setParams( $params ));
     2929      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params ));
    24792930    return TRUE;
    24802931  }
     
    24862937 * creates formatted output for calendar component property exdate
    24872938 *
    2488  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2489  * @since 2.4.8 - 2008-10-22
     2939 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2940 * @since 2.16.5 - 2012-12-28
    24902941 * @return string
    24912942 */
    24922943  function createExdate() {
    24932944    if( empty( $this->exdate )) return FALSE;
    2494     $output = null;
    2495     foreach( $this->exdate as $ex => $theExdate ) {
     2945    $output  = null;
     2946    $exdates = array();
     2947    foreach( $this->exdate as $theExdate ) {
    24962948      if( empty( $theExdate['value'] )) {
    2497         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'EXDATE' );
     2949        if( $this->getConfig( 'allowEmpty' ))
     2950          $output .= $this->_createElement( 'EXDATE' );
    24982951        continue;
    24992952      }
     2953      if( 1 < count( $theExdate['value'] ))
     2954        usort( $theExdate['value'], array( 'iCalUtilityFunctions', '_sortExdate1' ));
     2955      $exdates[] = $theExdate;
     2956    }
     2957    if( 1 < count( $exdates ))
     2958      usort( $exdates, array( 'iCalUtilityFunctions', '_sortExdate2' ));
     2959    foreach( $exdates as $theExdate ) {
    25002960      $content = $attributes = null;
    25012961      foreach( $theExdate['value'] as $eix => $exdatePart ) {
    25022962        $parno = count( $exdatePart );
    2503         $formatted = $this->_format_date_time( $exdatePart, $parno );
     2963        $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno );
    25042964        if( isset( $theExdate['params']['TZID'] ))
    25052965          $formatted = str_replace( 'Z', '', $formatted);
     
    25162976          else
    25172977            $formatted = str_replace( 'Z', '', $formatted );
    2518         }
     2978        } // end if( 0 < $eix )
    25192979        $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
    2520       }
     2980      } // end foreach( $theExdate['value'] as $eix => $exdatePart )
    25212981      $attributes .= $this->_createParams( $theExdate['params'] );
    25222982      $output .= $this->_createElement( 'EXDATE', $attributes, $content );
    2523     }
     2983    } // end foreach( $exdates as $theExdate )
    25242984    return $output;
    25252985  }
     
    25272987 * set calendar component property exdate
    25282988 *
    2529  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2530  * @since 2.5.1 - 2008-11-05
     2989 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     2990 * @since 2.14.1 - 2012-10-02
    25312991 * @param array exdates
    25322992 * @param array $params, optional
     
    25372997    if( empty( $exdates )) {
    25382998      if( $this->getConfig( 'allowEmpty' )) {
    2539         $this->_setMval( $this->exdate, null, $params, FALSE, $index );
     2999        iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
    25403000        return TRUE;
    25413001      }
     
    25433003        return FALSE;
    25443004    }
    2545     $input  = array( 'params' => $this->_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
     3005    $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
     3006    $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
    25463007            /* ev. check 1:st date and save ev. timezone **/
    2547     $this->_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
    2548     $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
     3008    iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
     3009    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
    25493010    foreach( $exdates as $eix => $theExdate ) {
    2550       if( $this->_isArrayTimestampDate( $theExdate ))
    2551         $exdatea = $this->_timestamp2date( $theExdate, $parno );
    2552       elseif(  is_array( $theExdate ))
    2553         $exdatea = $this->_date_time_array( $theExdate, $parno );
    2554       elseif( 8 <= strlen( trim( $theExdate ))) // ex. 2006-08-03 10:12:18
    2555         $exdatea = $this->_date_time_string( $theExdate, $parno );
     3011      iCalUtilityFunctions::_strDate2arr( $theExdate );
     3012      if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) {
     3013        if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) {
     3014          if( isset( $input['params']['TZID'] ))
     3015            $theExdate['tz'] = $input['params']['TZID'];
     3016          else
     3017            $input['params']['TZID'] = $theExdate['tz'];
     3018        }
     3019        $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
     3020      }
     3021      elseif(  is_array( $theExdate )) {
     3022        $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno );
     3023        if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
     3024          $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
     3025          $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
     3026          unset( $exdatea['unparsedtext'] );
     3027        }
     3028        else
     3029          $exdatea = $d;
     3030      }
     3031      elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
     3032        $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno );
     3033        unset( $exdatea['unparsedtext'] );
     3034      }
    25563035      if( 3 == $parno )
    25573036        unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
     
    25593038        $exdatea['tz'] = (string) $exdatea['tz'];
    25603039      if(  isset( $input['params']['TZID'] ) ||
    2561          ( isset( $exdatea['tz'] ) && !$this->_isOffset( $exdatea['tz'] )) ||
     3040         ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
    25623041         ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
    2563          ( isset( $input['value'][0]['tz'] ) && !$this->_isOffset( $input['value'][0]['tz'] )))
     3042         ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
    25643043        unset( $exdatea['tz'] );
     3044      if( $toZ ) // time zone Z
     3045        $exdatea['tz'] = 'Z';
    25653046      $input['value'][] = $exdatea;
    25663047    }
     
    25713052      unset( $input['params']['TZID'] );
    25723053    }
    2573     $this->_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
     3054    if( $toZ ) // time zone Z
     3055      unset( $input['params']['TZID'] );
     3056    iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
    25743057    return TRUE;
    25753058  }
     
    25813064 * creates formatted output for calendar component property exrule
    25823065 *
    2583  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3066 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    25843067 * @since 2.4.8 - 2008-10-22
    25853068 * @return string
     
    25923075 * set calendar component property exdate
    25933076 *
    2594  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3077 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    25953078 * @since 2.5.1 - 2008-11-05
    25963079 * @param array $exruleset
     
    26013084  function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
    26023085    if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
    2603     $this->_setMval( $this->exrule, $this->_setRexrule( $exruleset ), $params, FALSE, $index );
     3086    iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
    26043087    return TRUE;
    26053088  }
     
    26113094 * creates formatted output for calendar component property freebusy
    26123095 *
    2613  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2614  * @since 2.4.8 - 2008-10-22
     3096 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3097 * @since 2.1.23 - 2012-02-16
    26153098 * @return string
    26163099 */
     
    26193102    $output = null;
    26203103    foreach( $this->freebusy as $freebusyPart ) {
    2621       if( empty( $freebusyPart['value'] )) {
     3104      if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
    26223105        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
    26233106        continue;
     
    26253108      $attributes = $content = null;
    26263109      if( isset( $freebusyPart['value']['fbtype'] )) {
    2627         $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
     3110          $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
    26283111        unset( $freebusyPart['value']['fbtype'] );
    26293112        $freebusyPart['value'] = array_values( $freebusyPart['value'] );
     
    26353118      $cnt = count( $freebusyPart['value']);
    26363119      foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
    2637         $formatted   = $this->_format_date_time( $freebusyPeriod[0] );
     3120        $formatted   = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] );
    26383121        $content .= $formatted;
    26393122        $content .= '/';
     
    26473130            isset( $freebusyPeriod[1]['month'] ) &&
    26483131            isset( $freebusyPeriod[1]['day'] )) {
    2649           $content .= $this->_format_date_time( $freebusyPeriod[1] );
     3132          $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] );
    26503133        }
    26513134        else {                                  // period=  -> dur-time
    2652           $content .= $this->_format_duration( $freebusyPeriod[1] );
     3135          $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] );
    26533136        }
    26543137        if( $fno < $cnt )
     
    26633146 * set calendar component property freebusy
    26643147 *
    2665  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2666  * @since 2.5.1 - 2008-11-05
     3148 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3149 * @since 2.10.30 - 2012-01-16
    26673150 * @param string $fbType
    26683151 * @param array $fbValues
     
    26743157    if( empty( $fbValues )) {
    26753158      if( $this->getConfig( 'allowEmpty' )) {
    2676         $this->_setMval( $this->freebusy, null, $params, FALSE, $index );
     3159        iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
    26773160        return TRUE;
    26783161      }
     
    26863169    $input = array( 'fbtype' => $fbType );
    26873170    foreach( $fbValues as $fbPeriod ) {   // periods => period
     3171      if( empty( $fbPeriod ))
     3172        continue;
    26883173      $freebusyPeriod = array();
    26893174      foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
    26903175        $freebusyPairMember = array();
    26913176        if( is_array( $fbMember )) {
    2692           if( $this->_isArrayDate( $fbMember )) { // date-time value
    2693             $freebusyPairMember       = $this->_date_time_array( $fbMember, 7 );
     3177          if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
     3178            $freebusyPairMember       = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 );
    26943179            $freebusyPairMember['tz'] = 'Z';
    26953180          }
    2696           elseif( $this->_isArrayTimestampDate( $fbMember )) { // timestamp value
    2697             $freebusyPairMember       = $this->_timestamp2date( $fbMember['timestamp'], 7 );
     3181          elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
     3182            $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
    26983183            $freebusyPairMember['tz'] = 'Z';
    26993184          }
    27003185          else {                                         // array format duration
    2701             $freebusyPairMember = $this->_duration_array( $fbMember );
     3186            $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember );
    27023187          }
    27033188        }
     
    27063191          if( 'P' != $fbMember{0} )
    27073192            $fbmember = substr( $fbMember, 1 );
    2708           $freebusyPairMember = $this->_duration_string( $fbMember );
     3193          $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember );
    27093194        }
    27103195        elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
    2711           $freebusyPairMember       = $this->_date_time_string( $fbMember, 7 );
     3196          $freebusyPairMember       = iCalUtilityFunctions::_strdate2date( $fbMember, 7 );
     3197          unset( $freebusyPairMember['unparsedtext'] );
    27123198          $freebusyPairMember['tz'] = 'Z';
    27133199        }
     
    27163202      $input[]              = $freebusyPeriod;
    27173203    }
    2718     $this->_setMval( $this->freebusy, $input, $params, FALSE, $index );
     3204    iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
    27193205    return TRUE;
    27203206  }
     
    27263212 * creates formatted output for calendar component property geo
    27273213 *
    2728  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2729  * @since 2.4.8 - 2008-10-21
     3214 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3215 * @since 2.12.6 - 2012-04-21
    27303216 * @return string
    27313217 */
     
    27353221      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
    27363222    $attributes = $this->_createParams( $this->geo['params'] );
    2737     $content    = null;
    2738     $content   .= number_format( (float) $this->geo['value']['latitude'], 6, '.', '');
    2739     $content   .= ';';
    2740     $content   .= number_format( (float) $this->geo['value']['longitude'], 6, '.', '');
     3223    if( 0.0 < $this->geo['value']['latitude'] )
     3224      $sign   = '+';
     3225    else
     3226      $sign   = ( 0.0 > $this->geo['value']['latitude'] ) ? '-' : '';
     3227    $content  = $sign.sprintf( "%09.6f", abs( $this->geo['value']['latitude'] ));       // sprintf && lpad && float && sign !"#¤%&/(
     3228    $content  = rtrim( rtrim( $content, '0' ), '.' );
     3229    if( 0.0 < $this->geo['value']['longitude'] )
     3230      $sign   = '+';
     3231    else
     3232      $sign   = ( 0.0 > $this->geo['value']['longitude'] ) ? '-' : '';
     3233    $content .= ';'.$sign.sprintf( '%8.6f', abs( $this->geo['value']['longitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
     3234    $content  = rtrim( rtrim( $content, '0' ), '.' );
    27413235    return $this->_createElement( 'GEO', $attributes, $content );
    27423236  }
     
    27443238 * set calendar component property geo
    27453239 *
    2746  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2747  * @since 2.4.8 - 2008-11-04
     3240 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3241 * @since 2.12.5 - 2012-04-21
    27483242 * @param float $latitude
    27493243 * @param float $longitude
     
    27523246 */
    27533247  function setGeo( $latitude, $longitude, $params=FALSE ) {
    2754     if( !empty( $latitude ) && !empty( $longitude )) {
     3248    if(( !empty( $latitude )  || ( 0 == $latitude )) &&
     3249       ( !empty( $longitude ) || ( 0 == $longitude ))) {
    27553250      if( !is_array( $this->geo )) $this->geo = array();
    2756       $this->geo['value']['latitude']  = $latitude;
    2757       $this->geo['value']['longitude'] = $longitude;
    2758       $this->geo['params'] = $this->_setParams( $params );
     3251      $this->geo['value']['latitude']  = (float) $latitude;
     3252      $this->geo['value']['longitude'] = (float) $longitude;
     3253      $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
    27593254    }
    27603255    elseif( $this->getConfig( 'allowEmpty' ))
    2761       $this->geo = array( 'value' => null, 'params' => $this->_setParams( $params ) );
     3256      $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
    27623257    else
    27633258      return FALSE;
     
    27713266 * creates formatted output for calendar component property last-modified
    27723267 *
    2773  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3268 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    27743269 * @since 2.4.8 - 2008-10-21
    27753270 * @return string
     
    27783273    if( empty( $this->lastmodified )) return FALSE;
    27793274    $attributes = $this->_createParams( $this->lastmodified['params'] );
    2780     $formatted  = $this->_format_date_time( $this->lastmodified['value'], 7 );
     3275    $formatted  = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 );
    27813276    return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
    27823277  }
     
    27843279 * set calendar component property completed
    27853280 *
    2786  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3281 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    27873282 * @since 2.4.8 - 2008-10-23
    27883283 * @param mixed $year optional
     
    27983293    if( empty( $year ))
    27993294      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
    2800     $this->lastmodified = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
     3295    $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
    28013296    return TRUE;
    28023297  }
     
    28083303 * creates formatted output for calendar component property location
    28093304 *
    2810  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2811  * @since 2.4.8 - 2008-10-22
     3305 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3306 * @since 2.16.2 - 2012-12-18
    28123307 * @return string
    28133308 */
     
    28173312      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
    28183313    $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
    2819     $content    = $this->_strrep( $this->location['value'] );
     3314    $content    = iCalUtilityFunctions::_strrep( $this->location['value'], $this->format, $this->nl );
    28203315    return $this->_createElement( 'LOCATION', $attributes, $content );
    28213316  }
     
    28233318 * set calendar component property location
    28243319 '
    2825  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3320 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    28263321 * @since 2.4.8 - 2008-11-04
    28273322 * @param string $value
     
    28313326  function setLocation( $value, $params=FALSE ) {
    28323327    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    2833     $this->location = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     3328    $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    28343329    return TRUE;
    28353330  }
     
    28413336 * creates formatted output for calendar component property organizer
    28423337 *
    2843  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2844  * @since 2.4.8 - 2008-10-21
     3338 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3339 * @since 2.6.33 - 2010-12-17
    28453340 * @return string
    28463341 */
     
    28503345      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
    28513346    $attributes = $this->_createParams( $this->organizer['params']
    2852                                       , array( 'CN', 'DIR', 'LANGUAGE', 'SENT-BY' ));
    2853     $content    = 'MAILTO:'.$this->organizer['value'];
    2854     return $this->_createElement( 'ORGANIZER', $attributes, $content );
     3347                                      , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
     3348    return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
    28553349  }
    28563350/**
    28573351 * set calendar component property organizer
    28583352 *
    2859  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2860  * @since 2.4.8 - 2008-11-04
     3353 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3354 * @since 2.12.18 - 2012-07-13
    28613355 * @param string $value
    28623356 * @param array params optional
     
    28653359  function setOrganizer( $value, $params=FALSE ) {
    28663360    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    2867     $value = str_replace ( 'MAILTO:', '', $value );
    2868     $value = str_replace ( 'mailto:', '', $value );
    2869     $this->organizer = array( 'value' => $value, 'params' => $this->_setParams( $params ));
    2870     if( isset( $this->organizer['params']['SENT-BY'] )) {
    2871       if( 'MAILTO' == strtoupper( substr( $this->organizer['params']['SENT-BY'], 0, 6 )))
    2872         $this->organizer['params']['SENT-BY'] = substr( $this->organizer['params']['SENT-BY'], 7 );
     3361    if( !empty( $value )) {
     3362      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
     3363        $value = 'MAILTO:'.$value;
     3364      elseif( !empty( $value ))
     3365        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
     3366      $value = str_replace( 'mailto:', 'MAILTO:', $value );
     3367    }
     3368    $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
     3369    if( isset( $this->organizer['params']['SENT-BY'] )){
     3370      if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
     3371        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
     3372      else
     3373        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
    28733374    }
    28743375    return TRUE;
     
    28813382 * creates formatted output for calendar component property percent-complete
    28823383 *
    2883  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2884  * @since 2.4.8 - 2008-10-22
     3384 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3385 * @since 2.9.3 - 2011-05-14
    28853386 * @return string
    28863387 */
    28873388  function createPercentComplete() {
    2888     if( empty( $this->percentcomplete )) return FALSE;
    2889     if( empty( $this->percentcomplete['value'] ))
     3389    if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
     3390    if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
    28903391      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
    28913392    $attributes = $this->_createParams( $this->percentcomplete['params'] );
     
    28953396 * set calendar component property percent-complete
    28963397 *
    2897  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2898  * @since 2.4.8 - 2008-11-04
     3398 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3399 * @since 2.9.3 - 2011-05-14
    28993400 * @param int $value
    29003401 * @param array $params optional
     
    29023403 */
    29033404  function setPercentComplete( $value, $params=FALSE ) {
    2904     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    2905     $this->percentcomplete = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     3405    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
     3406    $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    29063407    return TRUE;
    29073408  }
     
    29133414 * creates formatted output for calendar component property priority
    29143415 *
    2915  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2916  * @since 2.4.8 - 2008-10-21
     3416 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3417 * @since 2.9.3 - 2011-05-14
    29173418 * @return string
    29183419 */
    29193420  function createPriority() {
    2920     if( empty( $this->priority )) return FALSE;
    2921     if( empty( $this->priority['value'] ))
     3421    if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
     3422    if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
    29223423      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
    29233424    $attributes = $this->_createParams( $this->priority['params'] );
     
    29273428 * set calendar component property priority
    29283429 *
    2929  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2930  * @since 2.4.8 - 2008-11-04
     3430 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3431 * @since 2.9.3 - 2011-05-14
    29313432 * @param int $value
    29323433 * @param array $params optional
     
    29343435 */
    29353436  function setPriority( $value, $params=FALSE  ) {
    2936     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    2937     $this->priority = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     3437    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
     3438    $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    29383439    return TRUE;
    29393440  }
     
    29453446 * creates formatted output for calendar component property rdate
    29463447 *
    2947  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    2948  * @since 2.4.16 - 2008-10-26
     3448 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3449 * @since 2.16.9 - 2013-01-09
    29493450 * @return string
    29503451 */
     
    29533454    $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
    29543455    $output = null;
    2955     if( $utctime  )
    2956       unset( $this->rdate['params']['TZID'] );
    2957     foreach( $this->rdate as $theRdate ) {
     3456    $rdates = array();
     3457    foreach( $this->rdate as $rpix => $theRdate ) {
    29583458      if( empty( $theRdate['value'] )) {
    29593459        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
     
    29623462      if( $utctime  )
    29633463        unset( $theRdate['params']['TZID'] );
     3464      if( 1 < count( $theRdate['value'] ))
     3465        usort( $theRdate['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
     3466      $rdates[] = $theRdate;
     3467    }
     3468    if( 1 < count( $rdates ))
     3469      usort( $rdates, array( 'iCalUtilityFunctions', '_sortRdate2' ));
     3470    foreach( $rdates as $rpix => $theRdate ) {
    29643471      $attributes = $this->_createParams( $theRdate['params'] );
    29653472      $cnt = count( $theRdate['value'] );
    29663473      $content = null;
    29673474      $rno = 1;
    2968       foreach( $theRdate['value'] as $rpix => $rdatePart ) {
     3475      foreach( $theRdate['value'] as $rix => $rdatePart ) {
    29693476        $contentPart = null;
    29703477        if( is_array( $rdatePart ) &&
     
    29723479          if( $utctime )
    29733480            unset( $rdatePart[0]['tz'] );
    2974           $formatted = $this->_format_date_time( $rdatePart[0]); // PERIOD part 1
     3481          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1
    29753482          if( $utctime || !empty( $theRdate['params']['TZID'] ))
    29763483            $formatted = str_replace( 'Z', '', $formatted);
    2977           if( 0 < $rpix ) {
    2978             if( !empty( $rdatePart[0]['tz'] ) && $this->_isOffset( $rdatePart[0]['tz'] )) {
    2979               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
    2980             }
    2981             else
    2982               $formatted = str_replace( 'Z', '', $formatted );
    2983           }
    29843484          $contentPart .= $formatted;
    29853485          $contentPart .= '/';
     
    29993499            if( $utctime )
    30003500              unset( $rdatePart[1]['tz'] );
    3001             $formatted = $this->_format_date_time( $rdatePart[1] ); // PERIOD part 2
     3501            $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2
    30023502            if( $utctime || !empty( $theRdate['params']['TZID'] ))
    3003               $formatted = str_replace( 'Z', '', $formatted);
    3004             if( !empty( $rdatePart[0]['tz'] ) && $this->_isOffset( $rdatePart[0]['tz'] )) {
    3005               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
    3006             }
    3007             else
    30083503              $formatted = str_replace( 'Z', '', $formatted );
    30093504           $contentPart .= $formatted;
    30103505          }
    30113506          else {                                  // period=  -> dur-time
    3012             $contentPart .= $this->_format_duration( $rdatePart[1] );
     3507            $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] );
    30133508          }
    30143509        } // PERIOD end
     
    30163511          if( $utctime )
    30173512            unset( $rdatePart['tz'] );
    3018           $formatted = $this->_format_date_time( $rdatePart);
     3513          $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null;
     3514          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno );
    30193515          if( $utctime || !empty( $theRdate['params']['TZID'] ))
    30203516            $formatted = str_replace( 'Z', '', $formatted);
    3021           if( !$utctime && ( 0 < $rpix )) {
    3022             if( !empty( $theRdate['value'][0]['tz'] ) && $this->_isOffset( $theRdate['value'][0]['tz'] )) {
    3023               if( 'Z' != substr( $formatted, -1 ))
    3024                 $formatted .= 'Z';
    3025             }
    3026             else
    3027               $formatted = str_replace( 'Z', '', $formatted );
    3028           }
    30293517          $contentPart .= $formatted;
    30303518        }
     
    30413529 * set calendar component property rdate
    30423530 *
    3043  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3044  * @since 2.5.1 - 2008-11-07
     3531 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3532 * @since 2.14.1 - 2012-10-04
    30453533 * @param array $rdates
    30463534 * @param array $params, optional
     
    30513539    if( empty( $rdates )) {
    30523540      if( $this->getConfig( 'allowEmpty' )) {
    3053         $this->_setMval( $this->rdate, null, $params, FALSE, $index );
     3541        iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
    30543542        return TRUE;
    30553543      }
     
    30573545        return FALSE;
    30583546    }
    3059     $input = array( 'params' => $this->_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
     3547    $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
    30603548    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
    30613549      unset( $input['params']['TZID'] );
    30623550      $input['params']['VALUE'] = 'DATE-TIME';
    30633551    }
     3552    $zArr = array( 'GMT', 'UTC', 'Z' );
     3553    $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
    30643554            /*  check if PERIOD, if not set */
    30653555    if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
     
    30673557          isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
    30683558    (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
    3069                                       $this->_isArrayDate( $rdates[0][0] ))) ||
     3559                                      iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
    30703560                                    ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
    30713561     ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
     
    30753565    if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
    30763566      $date  = reset( $date );
    3077     $this->_chkdatecfg( $date, $parno, $input['params'] );
    3078     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
    3079       unset( $input['params']['TZID'] );
    3080     $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
     3567    iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
     3568    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
    30813569    foreach( $rdates as $rpix => $theRdate ) {
    30823570      $inputa = null;
     3571      iCalUtilityFunctions::_strDate2arr( $theRdate );
    30833572      if( is_array( $theRdate )) {
    30843573        if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
    30853574          foreach( $theRdate as $rix => $rPeriod ) {
     3575            iCalUtilityFunctions::_strDate2arr( $theRdate );
    30863576            if( is_array( $rPeriod )) {
    3087               if( $this->_isArrayTimestampDate( $rPeriod ))      // timestamp
    3088                 $inputab  = ( isset( $rPeriod['tz'] )) ? $this->_timestamp2date( $rPeriod, $parno ) : $this->_timestamp2date( $rPeriod, 6 );
    3089               elseif( $this->_isArrayDate( $rPeriod ))
    3090                 $inputab  = ( 3 < count ( $rPeriod )) ? $this->_date_time_array( $rPeriod, $parno ) : $this->_date_time_array( $rPeriod, 6 );
    3091               elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod ))))  // text-date
    3092                 $inputab  = $this->_date_time_string( reset( $rPeriod ), $parno );
     3577              if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) {    // timestamp
     3578                if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) {
     3579                  if( isset( $input['params']['TZID'] ))
     3580                    $rPeriod['tz'] = $input['params']['TZID'];
     3581                  else
     3582                    $input['params']['TZID'] = $rPeriod['tz'];
     3583                }
     3584                $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno );
     3585              }
     3586              elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) {
     3587                $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 );
     3588                if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
     3589                  $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
     3590                  $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
     3591                  unset( $inputab['unparsedtext'] );
     3592                }
     3593                else
     3594                  $inputab = $d;
     3595              }
     3596              elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
     3597                $inputab   = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno );
     3598                unset( $inputab['unparsedtext'] );
     3599              }
    30933600              else                                               // array format duration
    3094                 $inputab  = $this->_duration_array( $rPeriod );
     3601                $inputab   = iCalUtilityFunctions::_duration2arr( $rPeriod );
    30953602            }
    30963603            elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
    3097                    ( in_array( $rPeriod{0}, array( 'P', '+', '-' )))) {
    3098               if( 'P' != $rPeriod{0} )
    3099                 $rPeriod  = substr( $rPeriod, 1 );
    3100               $inputab    = $this->_duration_string( $rPeriod );
     3604                   ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
     3605              if( 'P' != $rPeriod[0] )
     3606                $rPeriod   = substr( $rPeriod, 1 );
     3607              $inputab     = iCalUtilityFunctions::_durationStr2arr( $rPeriod );
    31013608            }
    3102             elseif( 8 <= strlen( trim( $rPeriod )))              // text date ex. 2006-08-03 10:12:18
    3103               $inputab    = $this->_date_time_string( $rPeriod, $parno );
    3104             if(  isset( $input['params']['TZID'] ) ||
    3105                ( isset( $inputab['tz'] )   && !$this->_isOffset( $inputab['tz'] )) ||
    3106                ( isset( $inputa[0] )       && ( !isset( $inputa[0]['tz'] )))       ||
    3107                ( isset( $inputa[0]['tz'] ) && !$this->_isOffset( $inputa[0]['tz'] )))
    3108               unset( $inputab['tz'] );
    3109             $inputa[]     = $inputab;
     3609            elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
     3610              $inputab     = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno );
     3611              unset( $inputab['unparsedtext'] );
     3612            }
     3613            if(( 0 == $rpix ) && ( 0 == $rix )) {
     3614              if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) {
     3615                $inputab['tz'] = 'Z';
     3616                $toZ = TRUE;
     3617              }
     3618            }
     3619            else {
     3620              if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] ))
     3621                $inputab['tz'] = 'Z';
     3622              else
     3623                unset( $inputab['tz'] );
     3624            }
     3625            if( $toZ && isset( $inputab['year'] ) )
     3626              $inputab['tz'] = 'Z';
     3627            $inputa[]      = $inputab;
    31103628          }
    31113629        } // PERIOD end
    3112         elseif ( $this->_isArrayTimestampDate( $theRdate ))      // timestamp
    3113           $inputa = $this->_timestamp2date( $theRdate, $parno );
    3114         else                                                     // date[-time]
    3115           $inputa = $this->_date_time_array( $theRdate, $parno );
    3116       }
    3117       elseif( 8 <= strlen( trim( $theRdate )))                   // text date ex. 2006-08-03 10:12:18
    3118         $inputa       = $this->_date_time_string( $theRdate, $parno );
     3630        elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
     3631          if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) {
     3632            if( isset( $input['params']['TZID'] ))
     3633              $theRdate['tz'] = $input['params']['TZID'];
     3634            else
     3635              $input['params']['TZID'] = $theRdate['tz'];
     3636          }
     3637          $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
     3638        }
     3639        else {                                                                  // date[-time]
     3640          $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno );
     3641          if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) {
     3642            $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
     3643            $inputa  = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
     3644            unset( $inputa['unparsedtext'] );
     3645          }
     3646        }
     3647      }
     3648      elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
     3649        $inputa       = iCalUtilityFunctions::_strdate2date( $theRdate, $parno );
     3650        unset( $inputa['unparsedtext'] );
     3651        if( $toZ )
     3652          $inputa['tz'] = 'Z';
     3653      }
    31193654      if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
     3655        if(( 0 == $rpix ) && !$toZ )
     3656          $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
     3657        if( $toZ )
     3658          $inputa['tz']    = 'Z';
    31203659        if( 3 == $parno )
    31213660          unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
    31223661        elseif( isset( $inputa['tz'] ))
    3123           $inputa['tz'] = (string) $inputa['tz'];
    3124         if(  isset( $input['params']['TZID'] ) ||
    3125            ( isset( $inputa['tz'] )            && !$this->_isOffset( $inputa['tz'] ))     ||
    3126            ( isset( $input['value'][0] )       && ( !isset( $input['value'][0]['tz'] )))  ||
    3127            ( isset( $input['value'][0]['tz'] ) && !$this->_isOffset( $input['value'][0]['tz'] )))
    3128           unset( $inputa['tz'] );
    3129       }
    3130       $input['value'][] = $inputa;
     3662          $inputa['tz']    = (string) $inputa['tz'];
     3663        if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))))
     3664          if( !$toZ )
     3665            unset( $inputa['tz'] );
     3666      }
     3667      $input['value'][]    = $inputa;
    31313668    }
    31323669    if( 3 == $parno ) {
     
    31343671      unset( $input['params']['TZID'] );
    31353672    }
    3136     $this->_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
     3673    if( $toZ )
     3674      unset( $input['params']['TZID'] );
     3675    iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
    31373676    return TRUE;
    31383677  }
     
    31443683 * creates formatted output for calendar component property recurrence-id
    31453684 *
    3146  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3147  * @since 2.4.8 - 2008-10-21
     3685 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3686 * @since 2.14.4 - 2012-09-26
    31483687 * @return string
    31493688 */
     
    31523691    if( empty( $this->recurrenceid['value'] ))
    31533692      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
    3154     $formatted  = $this->_format_date_time( $this->recurrenceid['value'] );
     3693    $parno      = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null;
     3694    $formatted  = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno );
    31553695    $attributes = $this->_createParams( $this->recurrenceid['params'] );
    31563696    return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
     
    31593699 * set calendar component property recurrence-id
    31603700 *
    3161  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3162  * @since 2.4.8 - 2008-10-23
     3701 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3702 * @since 2.9.6 - 2011-05-15
    31633703 * @param mixed $year
    31643704 * @param mixed $month optional
     
    31793719        return FALSE;
    31803720    }
    3181     $this->recurrenceid = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params );
     3721    $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
    31823722    return TRUE;
    31833723  }
     
    31893729 * creates formatted output for calendar component property related-to
    31903730 *
    3191  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3192  * @since 2.4.8 - 2008-10-23
     3731 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3732 * @since 2.16.2 - 2012-12-18
    31933733 * @return string
    31943734 */
     
    31973737    $output = null;
    31983738    foreach( $this->relatedto as $relation ) {
    3199       if( empty( $relation['value'] )) {
    3200         if( $this->getConfig( 'allowEmpty' )) $output.= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
    3201         continue;
    3202       }
    3203       $attributes = $this->_createParams( $relation['params'] );
    3204       $content    = ( 'xcal' != $this->format ) ? '<' : '';
    3205       $content   .= $this->_strrep( $relation['value'] );
    3206       $content   .= ( 'xcal' != $this->format ) ? '>' : '';
    3207       $output    .= $this->_createElement( 'RELATED-TO', $attributes, $content );
     3739      if( !empty( $relation['value'] ))
     3740        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), iCalUtilityFunctions::_strrep( $relation['value'], $this->format, $this->nl ));
     3741      elseif( $this->getConfig( 'allowEmpty' ))
     3742        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
    32083743    }
    32093744    return $output;
     
    32123747 * set calendar component property related-to
    32133748 *
    3214  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3215  * @since 2.5.1 - 2008-11-07
     3749 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3750 * @since 2.11.24 - 2012-02-23
    32163751 * @param float $relid
    32173752 * @param array $params, optional
     
    32213756  function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
    32223757    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3223     if(( '<' == substr( $value, 0, 1 )) && ( '>' == substr( $value, -1 )))
    3224       $value = substr( $value, 1, ( strlen( $value ) - 2 ));
    3225     $this->_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
    3226     $this->_setMval( $this->relatedto, $value, $params, FALSE, $index );
     3758    iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
     3759    iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
    32273760    return TRUE;
    32283761  }
     
    32343767 * creates formatted output for calendar component property repeat
    32353768 *
    3236  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3237  * @since 2.4.8 - 2008-10-21
     3769 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3770 * @since 2.9.3 - 2011-05-14
    32383771 * @return string
    32393772 */
    32403773  function createRepeat() {
    3241     if( empty( $this->repeat )) return FALSE;
    3242     if( empty( $this->repeat['value'] ))
     3774    if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
     3775    if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
    32433776      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
    32443777    $attributes = $this->_createParams( $this->repeat['params'] );
     
    32463779  }
    32473780/**
    3248  * set calendar component property transp
    3249  *
    3250  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3251  * @since 2.4.8 - 2008-11-04
     3781 * set calendar component property repeat
     3782 *
     3783 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3784 * @since 2.9.3 - 2011-05-14
    32523785 * @param string $value
    32533786 * @param array $params optional
     
    32553788 */
    32563789  function setRepeat( $value, $params=FALSE ) {
    3257     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3258     $this->repeat = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     3790    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
     3791    $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    32593792    return TRUE;
    32603793  }
     
    32653798/**
    32663799 * creates formatted output for calendar component property request-status
    3267  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3268  * @since 2.4.8 - 2008-10-23
     3800 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3801 * @since 2.16.2 - 2012-12-18
    32693802 * @return string
    32703803 */
     
    32793812      $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
    32803813      $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
    3281       $content    .= ';'.$this->_strrep( $rstat['value']['text'] );
     3814      $content    .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['text'], $this->format, $this->nl );
    32823815      if( isset( $rstat['value']['extdata'] ))
    3283         $content  .= ';'.$this->_strrep( $rstat['value']['extdata'] );
     3816        $content  .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['extdata'], $this->format, $this->nl );
    32843817      $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
    32853818    }
     
    32893822 * set calendar component property request-status
    32903823 *
    3291  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3824 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    32923825 * @since 2.5.1 - 2008-11-05
    32933826 * @param float $statcode
     
    33033836    if( $extdata )
    33043837      $input['extdata'] = $extdata;
    3305     $this->_setMval( $this->requeststatus, $input, $params, FALSE, $index );
     3838    iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
    33063839    return TRUE;
    33073840  }
     
    33133846 * creates formatted output for calendar component property resources
    33143847 *
    3315  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3316  * @since 2.4.8 - 2008-10-23
     3848 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3849 * @since 2.16.2 - 2012-12-18
    33173850 * @return string
    33183851 */
     
    33283861      if( is_array( $resource['value'] )) {
    33293862        foreach( $resource['value'] as $rix => $resourcePart )
    3330           $resource['value'][$rix] = $this->_strrep( $resourcePart );
     3863          $resource['value'][$rix] = iCalUtilityFunctions::_strrep( $resourcePart, $this->format, $this->nl );
    33313864        $content   = implode( ',', $resource['value'] );
    33323865      }
    33333866      else
    3334         $content   = $this->_strrep( $resource['value'] );
     3867        $content   = iCalUtilityFunctions::_strrep( $resource['value'], $this->format, $this->nl );
    33353868      $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
    33363869    }
     
    33403873 * set calendar component property recources
    33413874 *
    3342  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3875 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    33433876 * @since 2.5.1 - 2008-11-05
    33443877 * @param mixed $value
     
    33493882  function setResources( $value, $params=FALSE, $index=FALSE ) {
    33503883    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3351     $this->_setMval( $this->resources, $value, $params, FALSE, $index );
     3884    iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
    33523885    return TRUE;
    33533886  }
     
    33593892 * creates formatted output for calendar component property rrule
    33603893 *
    3361  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3894 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    33623895 * @since 2.4.8 - 2008-10-21
    33633896 * @return string
     
    33703903 * set calendar component property rrule
    33713904 *
    3372  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3905 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    33733906 * @since 2.5.1 - 2008-11-05
    33743907 * @param array $rruleset
     
    33793912  function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
    33803913    if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
    3381     $this->_setMval( $this->rrule, $this->_setRexrule( $rruleset ), $params, FALSE, $index );
     3914    iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
    33823915    return TRUE;
    33833916  }
     
    33883921/**
    33893922 * creates formatted output for calendar component property sequence
    3390  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3391  * @since 0.9.7 - 2006-11-20
     3923 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3924 * @since 2.9.3 - 2011-05-14
    33923925 * @return string
    33933926 */
    33943927  function createSequence() {
    3395     if( empty( $this->sequence )) return FALSE;
    3396     if( empty( $this->sequence['value'] ))
     3928    if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
     3929    if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
     3930       ( '0' != $this->sequence['value'] ))
    33973931      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
    33983932    $attributes = $this->_createParams( $this->sequence['params'] );
     
    34013935/**
    34023936 * set calendar component property sequence
    3403  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3404  * @since 2.4.8 - 2008-11-04
     3937 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3938 * @since 2.10.8 - 2011-09-19
    34053939 * @param int $value optional
    34063940 * @param array $params optional
     
    34083942 */
    34093943  function setSequence( $value=FALSE, $params=FALSE ) {
    3410     if( empty( $value ))
    3411       $value = ( isset( $this->sequence['value'] ) && ( 0 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : 1;
    3412     $this->sequence = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     3944    if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
     3945      $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
     3946    $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    34133947    return TRUE;
    34143948  }
     
    34203954 * creates formatted output for calendar component property status
    34213955 *
    3422  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3956 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    34233957 * @since 2.4.8 - 2008-10-21
    34243958 * @return string
     
    34343968 * set calendar component property status
    34353969 *
    3436  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     3970 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    34373971 * @since 2.4.8 - 2008-11-04
    34383972 * @param string $value
     
    34423976  function setStatus( $value, $params=FALSE ) {
    34433977    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3444     $this->status = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     3978    $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    34453979    return TRUE;
    34463980  }
     
    34523986 * creates formatted output for calendar component property summary
    34533987 *
    3454  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3455  * @since 2.4.8 - 2008-10-21
     3988 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     3989 * @since 2.16.2 - 2012-12-18
    34563990 * @return string
    34573991 */
     
    34613995      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
    34623996    $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
    3463     $content    = $this->_strrep( $this->summary['value'] );
     3997    $content    = iCalUtilityFunctions::_strrep( $this->summary['value'], $this->format, $this->nl );
    34643998    return $this->_createElement( 'SUMMARY', $attributes, $content );
    34653999  }
     
    34674001 * set calendar component property summary
    34684002 *
    3469  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4003 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    34704004 * @since 2.4.8 - 2008-11-04
    34714005 * @param string $value
     
    34754009  function setSummary( $value, $params=FALSE ) {
    34764010    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3477     $this->summary = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4011    $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    34784012    return TRUE;
    34794013  }
     
    34854019 * creates formatted output for calendar component property transp
    34864020 *
    3487  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4021 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    34884022 * @since 2.4.8 - 2008-10-21
    34894023 * @return string
     
    34994033 * set calendar component property transp
    35004034 *
    3501  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4035 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    35024036 * @since 2.4.8 - 2008-11-04
    35034037 * @param string $value
     
    35074041  function setTransp( $value, $params=FALSE ) {
    35084042    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3509     $this->transp = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4043    $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    35104044    return TRUE;
    35114045  }
     
    35174051 * creates formatted output for calendar component property trigger
    35184052 *
    3519  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4053 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    35204054 * @since 2.4.16 - 2008-10-21
    35214055 * @return string
     
    35294063        isset( $this->trigger['value']['month'] )  &&
    35304064        isset( $this->trigger['value']['day'] ))
    3531       $content      .= $this->_format_date_time( $this->trigger['value'] );
     4065      $content      .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] );
    35324066    else {
    35334067      if( TRUE !== $this->trigger['value']['relatedStart'] )
     
    35354069      if( $this->trigger['value']['before'] )
    35364070        $content    .= '-';
    3537       $content      .= $this->_format_duration( $this->trigger['value'] );
     4071      $content      .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] );
    35384072    }
    35394073    $attributes     .= $this->_createParams( $this->trigger['params'] );
     
    35434077 * set calendar component property trigger
    35444078 *
    3545  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3546  * @since 2.4.16 - 2008-11-04
     4079 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4080 * @since 2.14.1 - 2012-09-20
    35474081 * @param mixed $year
    35484082 * @param mixed $month optional
     
    35604094    if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
    35614095      if( $this->getConfig( 'allowEmpty' )) {
    3562         $this->trigger = array( 'value' => null, 'params' => $this->_setParams( $params ) );
     4096        $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
    35634097        return TRUE;
    35644098      }
    35654099      else
    35664100        return FALSE;
    3567     if( $this->_isArrayTimestampDate( $year )) { // timestamp
    3568       $params = $this->_setParams( $month );
    3569       $date   = $this->_timestamp2date( $year, 7 );
     4101    if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC
     4102      $params = iCalUtilityFunctions::_setParams( $month );
     4103      $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
    35704104      foreach( $date as $k => $v )
    35714105        $$k = $v;
    35724106    }
    35734107    elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
    3574       $params = $this->_setParams( $month );
     4108      $params = iCalUtilityFunctions::_setParams( $month );
    35754109      if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
    35764110           array_key_exists( 'month', $year ) &&
    3577            array_key_exists( 'day',   $year ))) {  // so this must be a duration
    3578         if( isset( $params['RELATED'] ) && ( 'END' == $params['RELATED'] ))
     4111           array_key_exists( 'day',   $year ))) {  // when this must be a duration
     4112        if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
    35794113          $relatedStart = FALSE;
    35804114        else
     
    35924126    }
    35934127    elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
    3594       $params = $this->_setParams( $month );
    3595       if( in_array( $year{0}, array( 'P', '+', '-' ))) { // duration
    3596         $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == $params['RELATED'] )) ? FALSE : TRUE;
    3597         $before       = ( '-'  == $year{0} ) ? TRUE : FALSE;
    3598         if(     'P'  != $year{0} )
     4128      $params = iCalUtilityFunctions::_setParams( $month );
     4129      if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
     4130        $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
     4131        $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
     4132        if(     'P'  != $year[0] )
    35994133          $year       = substr( $year, 1 );
    3600         $date         = $this->_duration_string( $year);
     4134        $date         = iCalUtilityFunctions::_durationStr2arr( $year);
    36014135      }
    36024136      else   // date
    3603         $date    = $this->_date_time_string( $year, 7 );
    3604       unset( $year, $month, $day );
    3605       foreach( $date as $k => $v )
    3606         $$k = $v;
     4137        $date    = iCalUtilityFunctions::_strdate2date( $year, 7 );
     4138      unset( $year, $month, $day, $date['unparsedtext'] );
     4139      if( empty( $date ))
     4140        $sec = 0;
     4141      else
     4142        foreach( $date as $k => $v )
     4143          $$k = $v;
    36074144    }
    36084145    else // single values in function input parameters
    3609       $params = $this->_setParams( $params );
     4146      $params = iCalUtilityFunctions::_setParams( $params );
    36104147    if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
    36114148      $params['VALUE'] = 'DATE-TIME';
     
    36244161    }
    36254162    elseif(( empty( $year ) && empty( $month )) &&    // duration
    3626            (!empty( $week ) || !empty( $day ) || !empty( $hour ) || !empty( $min ) || !empty( $sec ))) {
     4163           (( !empty( $week ) || ( 0 == $week )) ||
     4164            ( !empty( $day )  || ( 0 == $day  )) ||
     4165            ( !empty( $hour ) || ( 0 == $hour )) ||
     4166            ( !empty( $min )  || ( 0 == $min  )) ||
     4167            ( !empty( $sec )  || ( 0 == $sec  )))) {
    36274168      unset( $params['RELATED'] ); // set at output creation (END only)
    36284169      unset( $params['VALUE'] );   // 'DURATION' default
    36294170      $this->trigger = array( 'params' => $params );
    3630       $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
    3631       $before       = ( FALSE !== $before )       ? TRUE : FALSE;
    3632       $this->trigger['value']  = array( 'relatedStart' => $relatedStart
    3633                                       , 'before'       => $before );
     4171      $this->trigger['value']  = array();
    36344172      if( !empty( $week )) $this->trigger['value']['week'] = $week;
    36354173      if( !empty( $day  )) $this->trigger['value']['day']  = $day;
     
    36374175      if( !empty( $min  )) $this->trigger['value']['min']  = $min;
    36384176      if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
     4177      if( empty( $this->trigger['value'] )) {
     4178        $this->trigger['value']['sec'] = 0;
     4179        $before                        = FALSE;
     4180      }
     4181      $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
     4182      $before       = ( FALSE !== $before )       ? TRUE : FALSE;
     4183      $this->trigger['value']['relatedStart'] = $relatedStart;
     4184      $this->trigger['value']['before']       = $before;
    36394185      return TRUE;
    36404186    }
     
    36484194 * creates formatted output for calendar component property tzid
    36494195 *
    3650  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3651  * @since 2.4.8 - 2008-10-21
     4196 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4197 * @since 2.16.2 - 2012-12-18
    36524198 * @return string
    36534199 */
     
    36574203      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
    36584204    $attributes = $this->_createParams( $this->tzid['params'] );
    3659     return $this->_createElement( 'TZID', $attributes, $this->_strrep( $this->tzid['value'] ));
     4205    return $this->_createElement( 'TZID', $attributes, iCalUtilityFunctions::_strrep( $this->tzid['value'], $this->format, $this->nl ));
    36604206  }
    36614207/**
    36624208 * set calendar component property tzid
    36634209 *
    3664  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4210 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    36654211 * @since 2.4.8 - 2008-11-04
    36664212 * @param string $value
     
    36704216  function setTzid( $value, $params=FALSE ) {
    36714217    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3672     $this->tzid = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4218    $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    36734219    return TRUE;
    36744220  }
     
    36814227 * creates formatted output for calendar component property tzname
    36824228 *
    3683  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3684  * @since 2.4.8 - 2008-10-21
     4229 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4230 * @since 2.16.2 - 2012-12-18
    36854231 * @return string
    36864232 */
     
    36914237      if( !empty( $theName['value'] )) {
    36924238        $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
    3693         $output    .= $this->_createElement( 'TZNAME', $attributes, $this->_strrep( $theName['value'] ));
     4239        $output    .= $this->_createElement( 'TZNAME', $attributes, iCalUtilityFunctions::_strrep( $theName['value'], $this->format, $this->nl ));
    36944240      }
    36954241      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
     
    37004246 * set calendar component property tzname
    37014247 *
    3702  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4248 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    37034249 * @since 2.5.1 - 2008-11-05
    37044250 * @param string $value
     
    37094255  function setTzname( $value, $params=FALSE, $index=FALSE ) {
    37104256    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3711     $this->_setMval( $this->tzname, $value, $params, FALSE, $index );
     4257    iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
    37124258    return TRUE;
    37134259  }
     
    37194265 * creates formatted output for calendar component property tzoffsetfrom
    37204266 *
    3721  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4267 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    37224268 * @since 2.4.8 - 2008-10-21
    37234269 * @return string
     
    37334279 * set calendar component property tzoffsetfrom
    37344280 *
    3735  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4281 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    37364282 * @since 2.4.8 - 2008-11-04
    37374283 * @param string $value
     
    37414287  function setTzoffsetfrom( $value, $params=FALSE ) {
    37424288    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3743     $this->tzoffsetfrom = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4289    $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    37444290    return TRUE;
    37454291  }
     
    37514297 * creates formatted output for calendar component property tzoffsetto
    37524298 *
    3753  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4299 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    37544300 * @since 2.4.8 - 2008-10-21
    37554301 * @return string
     
    37654311 * set calendar component property tzoffsetto
    37664312 *
    3767  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4313 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    37684314 * @since 2.4.8 - 2008-11-04
    37694315 * @param string $value
     
    37734319  function setTzoffsetto( $value, $params=FALSE ) {
    37744320    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3775     $this->tzoffsetto = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4321    $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    37764322    return TRUE;
    37774323  }
     
    37834329 * creates formatted output for calendar component property tzurl
    37844330 *
    3785  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4331 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    37864332 * @since 2.4.8 - 2008-10-21
    37874333 * @return string
     
    37974343 * set calendar component property tzurl
    37984344 *
    3799  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4345 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    38004346 * @since 2.4.8 - 2008-11-04
    38014347 * @param string $value
     
    38054351  function setTzurl( $value, $params=FALSE ) {
    38064352    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3807     $this->tzurl = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4353    $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    38084354    return TRUE;
    38094355  }
     
    38154361 * creates formatted output for calendar component property uid
    38164362 *
    3817  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4363 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    38184364 * @since 0.9.7 - 2006-11-20
    38194365 * @return string
     
    38284374 * create an unique id for this calendar component object instance
    38294375 *
    3830  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4376 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    38314377 * @since 2.2.7 - 2007-09-04
    38324378 * @return void
     
    38484394 * set calendar component property uid
    38494395 *
    3850  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4396 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    38514397 * @since 2.4.8 - 2008-11-04
    38524398 * @param string $value
     
    38564402  function setUid( $value, $params=FALSE ) {
    38574403    if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
    3858     $this->uid = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4404    $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    38594405    return TRUE;
    38604406  }
     
    38664412 * creates formatted output for calendar component property url
    38674413 *
    3868  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4414 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    38694415 * @since 2.4.8 - 2008-10-21
    38704416 * @return string
     
    38804426 * set calendar component property url
    38814427 *
    3882  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3883  * @since 2.4.8 - 2008-11-04
     4428 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4429 * @since 2.16.7 - 2013-01-11
    38844430 * @param string $value
    38854431 * @param string $params optional
     
    38874433 */
    38884434  function setUrl( $value, $params=FALSE ) {
    3889     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    3890     $this->url = array( 'value' => $value, 'params' => $this->_setParams( $params ));
     4435    if( !empty( $value )) {
     4436      if( !filter_var( $value, FILTER_VALIDATE_URL ) && ( 'urn' != strtolower( substr( $value, 0, 3 ))))
     4437        return FALSE;
     4438    }
     4439    elseif( $this->getConfig( 'allowEmpty' ))
     4440      $value = null;
     4441    else
     4442      return FALSE;
     4443    $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
    38914444    return TRUE;
    38924445  }
     
    38984451 * creates formatted output for calendar component property x-prop
    38994452 *
    3900  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3901  * @since 2.4.11 - 2008-10-22
     4453 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4454 * @since 2.16.2 - 2012-12-18
    39024455 * @return string
    39034456 */
     
    39064459    $output = null;
    39074460    foreach( $this->xprop as $label => $xpropPart ) {
    3908       if( empty( $xpropPart['value'] )) {
     4461      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
    39094462        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
    39104463        continue;
     
    39134466      if( is_array( $xpropPart['value'] )) {
    39144467        foreach( $xpropPart['value'] as $pix => $theXpart )
    3915           $xpropPart['value'][$pix] = $this->_strrep( $theXpart );
     4468          $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->format );
    39164469        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
    39174470      }
    39184471      else
    3919         $xpropPart['value'] = $this->_strrep( $xpropPart['value'] );
     4472        $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
    39204473      $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
    39214474    }
     
    39254478 * set calendar component property x-prop
    39264479 *
    3927  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3928         * @since 2.4.11 - 2008-11-04
     4480 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4481 * @since 2.11.9 - 2012-01-16
    39294482 * @param string $label
    39304483 * @param mixed $value
     
    39334486 */
    39344487  function setXprop( $label, $value, $params=FALSE ) {
    3935     if( empty( $label )) return;
    3936     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
     4488    if( empty( $label ))
     4489      return FALSE;
     4490    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
     4491      return FALSE;
     4492    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
    39374493    $xprop           = array( 'value' => $value );
    3938     $toolbox         = new calendarComponent();
    3939     $xprop['params'] = $toolbox->_setParams( $params );
     4494    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
    39404495    if( !is_array( $this->xprop )) $this->xprop = array();
    39414496    $this->xprop[strtoupper( $label )] = $xprop;
     
    39474502 * create element format parts
    39484503 *
    3949  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
     4504 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
    39504505 * @since 2.0.6 - 2006-06-20
    39514506 * @return string
     
    39864541 * creates formatted output for calendar component property
    39874542 *
    3988  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    3989  * @since 2.4.8 - 2008-10-23
     4543 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4544 * @since 2.16.2 - 2012-12-18
    39904545 * @param string $label property name
    39914546 * @param string $attributes property attributes
     
    39944549 */
    39954550  function _createElement( $label, $attributes=null, $content=FALSE ) {
    3996     $label  = $this->_formatPropertyName( $label );
     4551    switch( $this->format ) {
     4552      case 'xcal':
     4553        $label = strtolower( $label );
     4554        break;
     4555      default:
     4556        $label = strtoupper( $label );
     4557        break;
     4558    }
    39974559    $output = $this->elementStart1.$label;
    39984560    $categoriesAttrLang = null;
    39994561    $attachInlineBinary = FALSE;
    40004562    $attachfmttype      = null;
     4563    if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
     4564      $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
     4565                               , 'ref'      => $label
     4566                               , 'type2'    => '(#PCDATA)' );
     4567    }
    40014568    if( !empty( $attributes ))  {
    40024569      $attributes  = trim( $attributes );
    4003       if ( 'xcal' == $this->format) {
     4570      if ( 'xcal' == $this->format ) {
    40044571        $attributes2 = explode( $this->intAttrDelimiter, $attributes );
    40054572        $attributes  = null;
    4006         foreach( $attributes2 as $attribute ) {
     4573        foreach( $attributes2 as $aix => $attribute ) {
    40074574          $attrKVarr = explode( '=', $attribute );
    40084575          if( empty( $attrKVarr[0] ))
     
    40104577          if( !isset( $attrKVarr[1] )) {
    40114578            $attrValue = $attrKVarr[0];
    4012             $attrKey   = null;
     4579            $attrKey   = $aix;
    40134580          }
    40144581          elseif( 2 == count( $attrKVarr)) {
     
    40444611      }
    40454612    }
    4046     if(((( 'attach' == $label ) && !$attachInlineBinary ) ||
    4047          ( in_array( $label, array( 'tzurl', 'url' ))))      && ( 'xcal' == $this->format)) {
     4613    if(( 'xcal' == $this->format) &&
     4614       ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
    40484615      $pos = strrpos($content, "/");
    40494616      $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
     
    40594626      if( 'attach' == $label ) {
    40604627        $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
    4061         $content = $this->_createElement( 'extref', $attributes, null );
     4628        $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
    40624629        $attributes = null;
    40634630      }
    40644631    }
    4065     elseif(( 'attach' == $label ) && $attachInlineBinary && ( 'xcal' == $this->format)) {
     4632    elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
    40664633      $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
    40674634    }
    40684635    $output .= $attributes;
    4069     if( !$content ) {
     4636    if( !$content && ( '0' != $content )) {
    40704637      switch( $this->format ) {
    40714638        case 'xcal':
    40724639          $output .= ' /';
    4073           $output .= $this->elementStart2;
     4640          $output .= $this->elementStart2.$this->nl;
    40744641          return $output;
    40754642          break;
    40764643        default:
    40774644          $output .= $this->elementStart2.$this->valueInit;
    4078           return $this->_size75( $output );
     4645          return iCalUtilityFunctions::_size75( $output, $this->nl );
    40794646          break;
    40804647      }
     
    40874654        break;
    40884655      default:
    4089         return $this->_size75( $output );
     4656        return iCalUtilityFunctions::_size75( $output, $this->nl );
    40904657        break;
    40914658    }
     
    40944661 * creates formatted output for calendar component property parameters
    40954662 *
    4096  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    4097  * @since 0.9.22 - 2007-04-10
     4663 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
     4664 * @since 2.10.27 - 2012-01-16
    40984665 * @param array $params  optional
    40994666 * @param array $ctrKeys optional
     
    41014668 */
    41024669  function _createParams( $params=array(), $ctrKeys=array() ) {
    4103     $attrLANG = $attr1 = $attr2 = null;
     4670    if( !is_array( $params ) || empty( $params ))
     4671      $params = array();
     4672    $attrLANG = $attr1 = $attr2 = $lang = null;
    41044673    $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
    41054674    $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
    41064675    $CNattrExist = $LANGattrExist = FALSE;
    4107     if( is_array( $params )) {
    4108       foreach( $params as $paramKey => $paramValue ) {
    4109         if( is_int( $paramKey ))
    4110           $attr2            .= $this->intAttrDelimiter.$paramValue;
    4111         elseif(( 'LANGUAGE' == $paramKey ) && $LANGattrKey ) {
    4112           $attrLANG         .= $this->intAttrDelimiter."LANGUAGE=$paramValue";
    4113           $LANGattrExist     = TRUE;
    4114         }
    4115         elseif(( 'CN'       == $paramKey ) && $CNattrKey ) {
    4116           $attr1             = $this->intAttrDelimiter.'CN="'.$paramValue.'"';
    4117           $CNattrExist       = TRUE;
    4118         }
    4119         elseif(( 'ALTREP'   == $paramKey ) && in_array( $paramKey, $ctrKeys ))
    4120           $attr2            .= $this->intAttrDelimiter.'ALTREP="'.$paramValue.'"';
    4121         elseif(( 'DIR'      == $paramKey ) && in_array( $paramKey, $ctrKeys ))
    4122           $attr2            .= $this->intAttrDelimiter.'DIR="'.$paramValue.'"';
    4123         elseif(( 'SENT-BY'  == $paramKey ) && in_array( $paramKey, $ctrKeys ))
    4124           $attr2            .= $this->intAttrDelimiter.'SENT-BY="MAILTO:'.$paramValue.'"';
    4125         else
    4126           $attr2            .= $this->intAttrDelimiter."$paramKey=$paramValue";
    4127       }
     4676    $xparams = array();
     4677    foreach( $params as $paramKey => $paramValue ) {
     4678      if(( FALSE !== strpos( $paramValue, ':' )) ||
     4679         ( FALSE !== strpos( $paramValue, ';' )) ||
     4680         ( FALSE !== strpos( $paramValue, ',' )))
     4681        $paramValue = '"'.$paramValue.'"';
     4682      if( ctype_digit( (string) $paramKey )) {
     4683        $xparams[]          = $paramValue;
     4684        continue;
     4685      }
     4686      $paramKey             = strtoupper( $paramKey );
     4687      if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
     4688        $xparams[$paramKey] = $paramValue;
     4689      else
     4690        $params[$paramKey]  = $paramValue;
     4691    }
     4692    ksort( $xparams, SORT_STRING );
     4693    foreach( $xparams as $paramKey => $paramValue ) {
     4694      if( ctype_digit( (string) $paramKey ))
     4695        $attr2             .= $this->intAttrDelimiter.$paramValue;
     4696      else
     4697        $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
     4698    }
     4699    if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
     4700      $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
     4701      $attr2                = null;
     4702    }
     4703    if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
     4704      if( !empty( $attr2 )) {
     4705        $attr1             .= $attr2;
     4706        $attr2              = null;
     4707      }
     4708      $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
     4709    }
     4710    if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
     4711      $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
     4712    if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
     4713      $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
     4714    }
     4715    if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
     4716      $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
     4717    if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
     4718      $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
     4719    if( isset( $params['CN'] )       && $CNattrKey ) {
     4720      $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
     4721      $CNattrExist          = TRUE;
     4722    }
     4723    if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
     4724      $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
     4725      $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
     4726    }
     4727    if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
     4728      $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
     4729    if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
     4730      $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
     4731      $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
     4732    }
     4733    if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
     4734      $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
     4735      $LANGattrExist        = TRUE;
    41284736    }
    41294737    if( !$LANGattrExist ) {
     
    41324740        $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
    41334741    }
    4134     return $attrLANG.$attr1.$attr2;
    4135   }
    4136 /**
    4137  * check a date(-time) for an opt. timezone and if it is a DATE-TIME or DATE
    4138  *
    4139  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    4140  * @since 2.4.16 - 2008-10-25
    4141  * @param array $date, date to check
    4142  * @param int $parno, no of date parts (i.e. year, month.. .)
    4143  * @return array $params, property parameters
    4144  */
    4145   function _chkdatecfg( $theDate, & $parno, & $params ) {
    4146     if( isset( $params['TZID'] ))
    4147       $parno = 6;
    4148     elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
    4149       $parno = 3;
    4150     else {
    4151       if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
    4152         $parno = 7;
    4153       if( is_array( $theDate )) {
    4154         if( isset( $theDate['timestamp'] ))
    4155           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
    4156         else
    4157           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
    4158         if( !empty( $tzid )) {
    4159           $parno = 7;
    4160           if( !$this->_isOffset( $tzid ))
    4161             $params['TZID'] = $tzid; // save only timezone
    4162         }
    4163         elseif( !$parno && ( 3 == count( $theDate )) &&
    4164           ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
    4165           $parno = 3;
    4166         else
    4167           $parno = 6;
    4168       }
    4169       else { // string
    4170         $date = trim( $theDate );
    4171         if( 'Z' == substr( $date, -1 ))
    4172           $parno = 7; // UTC DATE-TIME
    4173         elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
    4174           ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
    4175           $parno = 3; // DATE
    4176         $date = $this->_date_time_string( $date, $parno );
    4177         if( !empty( $date['tz'] )) {
    4178           $parno = 7;
    4179           if( !$this->_isOffset( $date['tz'] ))
    4180             $params['TZID'] = $date['tz']; // save only timezone
    4181         }
    4182         elseif( empty( $parno ))
    4183           $parno = 6;
    4184       }
    4185       if( isset( $params['TZID'] ))
    4186         $parno = 6;
    4187     }
    4188   }
    4189 /**
    4190  * convert local startdate/enddate (Ymd[His]) to duration
    4191  *
    4192  * uses this component dates if missing input dates
    4193  *
    4194  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    4195  * @since 2.2.11 - 2007-11-03
    4196  * @param array $startdate, optional
    4197  * @param array $duration, optional
    4198  * @return array duration
    4199  */
    4200   function _date2duration( $startdate=FALSE, $enddate=FALSE ) {
    4201     if( !$startdate || !$enddate ) {
    4202       if(   FALSE === ( $startdate = $this->getProperty( 'dtstart' )))
    4203         return null;
    4204       if(   FALSE === ( $enddate   = $this->getProperty( 'dtend' )))    // vevent/vfreebusy
    4205         if( FALSE === ( $enddate   = $this->getProperty( 'due' )))      // vtodo
    4206           return null;
    4207     }
    4208     if( !$startdate || !$enddate )
    4209       return null;
    4210     $startWdate  = mktime( 0, 0, 0, $startdate['month'], $startdate['day'], $startdate['year'] );
    4211     $endWdate    = mktime( 0, 0, 0, $enddate['month'],   $enddate['day'],   $enddate['year'] );
    4212     $wduration   = $endWdate - $startWdate;
    4213     $dur         = array();
    4214     $dur['week'] = (int) floor( $wduration / ( 7 * 24 * 60 * 60 ));
    4215     $wduration   =              $wduration % ( 7 * 24 * 60 * 60 );
    4216     $dur['day']  = (int) floor( $wduration / ( 24 * 60 * 60 ));
    4217     $wduration   =              $wduration % ( 24 * 60 * 60 );
    4218     $dur['hour'] = (int) floor( $wduration / ( 60 * 60 ));
    4219     $wduration   =              $wduration % ( 60 * 60 );
    4220     $dur['min']  = (int) floor( $wduration / ( 60 ));
    4221     $dur['sec']  = (int)        $wduration % ( 60 );
    4222     return $dur;
    4223   }
    4224 /**
    4225  * convert date/datetime to timestamp
    4226  *
    4227  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    4228  * @since 2.4.8 - 2008-10-30
    4229  * @param array  $datetime  datetime/(date)
    4230  * @param string $tz        timezone
    4231  * @return timestamp
    4232  */
    4233   function _date2timestamp( $datetime, $tz=null ) {
    4234     $output = null;
    4235     if( !isset( $datetime['hour'] )) $datetime['hour'] = '0';
    4236     if( !isset( $datetime['min'] ))  $datetime['min']  = '0';
    4237     if( !isset( $datetime['sec'] ))  $datetime['sec']  = '0';
    4238     foreach( $datetime as $dkey => $dvalue ) {
    4239       if( 'tz' != $dkey )
    4240         $datetime[$dkey] = (integer) $dvalue;
    4241     }
    4242     if( $tz )
    4243       $datetime['tz'] = $tz;
    4244     $offset = ( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) ? $this->_tz2offset( $datetime['tz'] ) : 0;
    4245     $output = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year'] );
    4246     return $output;
    4247   }
    4248 /**
    4249  * ensures internal date-time/date format for input date-time/date in array format
    4250  *
    4251  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    4252  * @since 0.3.0 - 2006-08-15
    4253  * @param array $datetime
    4254  * @param int $parno optional, default FALSE
    4255  * @return array
    4256  */
    4257   function _date_time_array( $datetime, $parno=FALSE ) {
    4258     $output = array();
    4259     foreach( $datetime as $dateKey => $datePart ) {
    4260       switch ( $dateKey ) {
    4261         case '0': case 'year':   $output['year']  = $datePart; break;
    4262         case '1': case 'month':  $output['month'] = $datePart; break;
    4263         case '2': case 'day':    $output['day']   = $datePart; break;
    4264       }
    4265       if( 3 != $parno ) {
    4266         switch ( $dateKey ) {
    4267           case '0':
    4268           case '1':
    4269           case '2': break;
    4270           case '3': case 'hour': $output['hour']  = $datePart; break;
    4271           case '4': case 'min' : $output['min']   = $datePart; break;
    4272           case '5': case 'sec' : $output['sec']   = $datePart; break;
    4273           case '6': case 'tz'  : $output['tz']    = $datePart; break;
    4274         }
    4275       }
    4276     }
    4277     if( 3 != $parno ) {
    4278       if( !isset( $output['hour'] ))
    4279         $output['hour'] = 0;
    4280       if( !isset( $output['min']  ))
    4281         $output['min'] = 0;
    4282       if( !isset( $output['sec']  ))
    4283         $output['sec'] = 0;
    4284     }
    4285     return $output;
    4286   }
    4287 /**
    4288  * ensures internal date-time/date format for input date-time/date in string fromat
    4289  *
    4290  * @author Kjell-Inge Gustafsson <ical@kigkonsult.se>
    4291  * @since 2.2.10 - 2007-10-19
    4292  * @param array $datetime
    4293  * @param int $parno optional, default FALSE
    4294  * @return array
    4295  */
    4296   function _date_time_string( $datetime, $parno=FALSE ) {
    4297     $datetime = (string) trim( $datetime );
    4298     $tz  = null;
    4299     $len = strlen( $datetime ) - 1;
    4300     if( 'Z' == substr( $datetime, -1 )) {
    4301       $tz = 'Z';
    4302       $datetime = trim( substr( $datetime, 0, $len ));
    4303     }
    4304     elseif( ( ctype_digit( substr( $datetime, -2, 2 ))) && // time or date
    4305                   ( '-' == substr( $datetime, -3, 1 )) ||
    4306                   ( ':' == substr( $datetime, -3, 1 )) ||
    4307                   ( '.' == substr( $datetime, -3, 1 ))) {
    4308       $continue = TRUE;