]> git.mxchange.org Git - friendica-addons.git/blob - dav/iCalcreator/iCalcreator.class.php
partially remove forum limit on profile page
[friendica-addons.git] / dav / iCalcreator / iCalcreator.class.php
1 <?php
2 /*********************************************************************************/
3 /**
4  * iCalcreator v2.12
5  * copyright (c) 2007-2011 Kjell-Inge Gustafsson kigkonsult
6  * kigkonsult.se/iCalcreator/index.php
7  * ical@kigkonsult.se
8  *
9  * Description:
10  * This file is a PHP implementation of RFC 2445.
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25  */
26 /*********************************************************************************/
27 /*********************************************************************************/
28 /*         A little setup                                                        */
29 /*********************************************************************************/
30             /* your local language code */
31 // define( 'ICAL_LANG', 'sv' );
32             // alt. autosetting
33 /*
34 $langstr     = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
35 $pos         = strpos( $langstr, ';' );
36 if ($pos   !== false) {
37   $langstr   = substr( $langstr, 0, $pos );
38   $pos       = strpos( $langstr, ',' );
39   if ($pos !== false) {
40     $pos     = strpos( $langstr, ',' );
41     $langstr = substr( $langstr, 0, $pos );
42   }
43   define( 'ICAL_LANG', $langstr );
44 }
45 */
46 /*********************************************************************************/
47 /*         only for phpversion 5.1 and later,                                    */
48 /*         date management, default timezone setting                             */
49 /*         since 2.6.36 - 2010-12-31 */
50 if( substr( phpversion(), 0, 3 ) >= '5.1' )
51   // && ( 'UTC' == date_default_timezone_get()))
52   date_default_timezone_set( 'Europe/Stockholm' );
53 /*********************************************************************************/
54 /*         version, do NOT remove!!                                              */
55 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.12' );
56 /*********************************************************************************/
57 /*********************************************************************************/
58 /**
59  * vcalendar class
60  *
61  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
62  * @since 2.9.6 - 2011-05-14
63  */
64 class vcalendar {
65             //  calendar property variables
66   var $calscale;
67   var $method;
68   var $prodid;
69   var $version;
70   var $xprop;
71             //  container for calendar components
72   var $components;
73             //  component config variables
74   var $allowEmpty;
75   var $unique_id;
76   var $language;
77   var $directory;
78   var $filename;
79   var $url;
80   var $delimiter;
81   var $nl;
82   var $format;
83   var $dtzid;
84             //  component internal variables
85   var $attributeDelimiter;
86   var $valueInit;
87             //  component xCal declaration container
88   var $xcaldecl;
89 /**
90  * constructor for calendar object
91  *
92  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
93  * @since 2.9.6 - 2011-05-14
94  * @param array $config
95  * @return void
96  */
97   function vcalendar ( $config = array()) {
98     $this->_makeVersion();
99     $this->calscale   = null;
100     $this->method     = null;
101     $this->_makeUnique_id();
102     $this->prodid     = null;
103     $this->xprop      = array();
104     $this->language   = null;
105     $this->directory  = null;
106     $this->filename   = null;
107     $this->url        = null;
108     $this->dtzid      = null;
109 /**
110  *   language = <Text identifying a language, as defined in [RFC 1766]>
111  */
112     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
113                                           $config['language']   = ICAL_LANG;
114     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
115     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
116     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
117     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
118     $this->setConfig( $config );
119
120     $this->xcaldecl   = array();
121     $this->components = array();
122   }
123 /*********************************************************************************/
124 /**
125  * Property Name: CALSCALE
126  */
127 /**
128  * creates formatted output for calendar property calscale
129  *
130  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
131  * @since 2.10.16 - 2011-10-28
132  * @return string
133  */
134   function createCalscale() {
135     if( empty( $this->calscale )) return FALSE;
136     switch( $this->format ) {
137       case 'xcal':
138         return $this->nl.' calscale="'.$this->calscale.'"';
139         break;
140       default:
141         return 'CALSCALE:'.$this->calscale.$this->nl;
142         break;
143     }
144   }
145 /**
146  * set calendar property calscale
147  *
148  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
149  * @since 2.4.8 - 2008-10-21
150  * @param string $value
151  * @return void
152  */
153   function setCalscale( $value ) {
154     if( empty( $value )) return FALSE;
155     $this->calscale = $value;
156   }
157 /*********************************************************************************/
158 /**
159  * Property Name: METHOD
160  */
161 /**
162  * creates formatted output for calendar property method
163  *
164  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
165  * @since 2.10.16 - 2011-10-28
166  * @return string
167  */
168   function createMethod() {
169     if( empty( $this->method )) return FALSE;
170     switch( $this->format ) {
171       case 'xcal':
172         return $this->nl.' method="'.$this->method.'"';
173         break;
174       default:
175         return 'METHOD:'.$this->method.$this->nl;
176         break;
177     }
178   }
179 /**
180  * set calendar property method
181  *
182  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
183  * @since 2.4.8 - 2008-20-23
184  * @param string $value
185  * @return bool
186  */
187   function setMethod( $value ) {
188     if( empty( $value )) return FALSE;
189     $this->method = $value;
190     return TRUE;
191   }
192 /*********************************************************************************/
193 /**
194  * Property Name: PRODID
195  *
196  *  The identifier is RECOMMENDED to be the identical syntax to the
197  * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
198  * domain name or a domain literal IP address of the host on which.. .
199  */
200 /**
201  * creates formatted output for calendar property prodid
202  *
203  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
204  * @since 2.10.16 - 2011-10-28
205  * @return string
206  */
207   function createProdid() {
208     if( !isset( $this->prodid ))
209       $this->_makeProdid();
210     switch( $this->format ) {
211       case 'xcal':
212         return $this->nl.' prodid="'.$this->prodid.'"';
213         break;
214       default:
215         return 'PRODID:'.$this->prodid.$this->nl;
216         break;
217     }
218   }
219 /**
220  * make default value for calendar prodid
221  *
222  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
223  * @since 2.6.8 - 2009-12-30
224  * @return void
225  */
226   function _makeProdid() {
227     $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
228   }
229 /**
230  * Conformance: The property MUST be specified once in an iCalendar object.
231  * Description: The vendor of the implementation SHOULD assure that this
232  * is a globally unique identifier; using some technique such as an FPI
233  * value, as defined in [ISO 9070].
234  */
235 /**
236  * make default unique_id for calendar prodid
237  *
238  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
239  * @since 0.3.0 - 2006-08-10
240  * @return void
241  */
242   function _makeUnique_id() {
243     $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
244   }
245 /*********************************************************************************/
246 /**
247  * Property Name: VERSION
248  *
249  * Description: A value of "2.0" corresponds to this memo.
250  */
251 /**
252  * creates formatted output for calendar property version
253
254  *
255  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
256  * @since 2.10.16 - 2011-10-28
257  * @return string
258  */
259   function createVersion() {
260     if( empty( $this->version ))
261       $this->_makeVersion();
262     switch( $this->format ) {
263       case 'xcal':
264         return $this->nl.' version="'.$this->version.'"';
265         break;
266       default:
267         return 'VERSION:'.$this->version.$this->nl;
268         break;
269     }
270   }
271 /**
272  * set default calendar version
273  *
274  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
275  * @since 0.3.0 - 2006-08-10
276  * @return void
277  */
278   function _makeVersion() {
279     $this->version = '2.0';
280   }
281 /**
282  * set calendar version
283  *
284  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
285  * @since 2.4.8 - 2008-10-23
286  * @param string $value
287  * @return void
288  */
289   function setVersion( $value ) {
290     if( empty( $value )) return FALSE;
291     $this->version = $value;
292     return TRUE;
293   }
294 /*********************************************************************************/
295 /**
296  * Property Name: x-prop
297  */
298 /**
299  * creates formatted output for calendar property x-prop, iCal format only
300  *
301  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
302  * @since 2.10.16 - 2011-11-01
303  * @return string
304  */
305   function createXprop() {
306     if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
307     $output = null;
308     $toolbox = new calendarComponent();
309     $toolbox->setConfig( $this->getConfig());
310     foreach( $this->xprop as $label => $xpropPart ) {
311       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
312         $output  .= $toolbox->_createElement( $label );
313         continue;
314       }
315       $attributes = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
316       if( is_array( $xpropPart['value'] )) {
317         foreach( $xpropPart['value'] as $pix => $theXpart )
318           $xpropPart['value'][$pix] = $toolbox->_strrep( $theXpart );
319         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
320       }
321       else
322         $xpropPart['value'] = $toolbox->_strrep( $xpropPart['value'] );
323       $output    .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
324       if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
325         foreach( $toolbox->xcaldecl as $localxcaldecl )
326           $this->xcaldecl[] = $localxcaldecl;
327       }
328     }
329     return $output;
330   }
331 /**
332  * set calendar property x-prop
333  *
334  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
335  * @since 2.11.9 - 2012-01-16
336  * @param string $label
337  * @param string $value
338  * @param array $params optional
339  * @return bool
340  */
341   function setXprop( $label, $value, $params=FALSE ) {
342     if( empty( $label ))
343       return FALSE;
344     if( 'X-' != strtoupper( substr( $label, 0, 2 )))
345       return FALSE;
346     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
347     $xprop           = array( 'value' => $value );
348     $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
349     if( !is_array( $this->xprop )) $this->xprop = array();
350     $this->xprop[strtoupper( $label )] = $xprop;
351     return TRUE;
352   }
353 /*********************************************************************************/
354 /**
355  * delete calendar property value
356  *
357  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
358  * @since 2.8.8 - 2011-03-15
359  * @param mixed $propName, bool FALSE => X-property
360  * @param int $propix, optional, if specific property is wanted in case of multiply occurences
361  * @return bool, if successfull delete
362  */
363   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
364     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
365     if( !$propix )
366       $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
367     $this->propdelix[$propName] = --$propix;
368     $return = FALSE;
369     switch( $propName ) {
370       case 'CALSCALE':
371         if( isset( $this->calscale )) {
372           $this->calscale = null;
373           $return = TRUE;
374         }
375         break;
376       case 'METHOD':
377         if( isset( $this->method )) {
378           $this->method   = null;
379           $return = TRUE;
380         }
381         break;
382       default:
383         $reduced = array();
384         if( $propName != 'X-PROP' ) {
385           if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
386           foreach( $this->xprop as $k => $a ) {
387             if(( $k != $propName ) && !empty( $a ))
388               $reduced[$k] = $a;
389           }
390         }
391         else {
392           if( count( $this->xprop ) <= $propix )  return FALSE;
393           $xpropno = 0;
394           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
395             if( $propix != $xpropno )
396               $reduced[$xpropkey] = $xpropvalue;
397             $xpropno++;
398           }
399         }
400         $this->xprop = $reduced;
401         if( empty( $this->xprop )) {
402           unset( $this->propdelix[$propName] );
403           return FALSE;
404         }
405         return TRUE;
406     }
407     return $return;
408   }
409 /**
410  * get calendar property value/params
411  *
412  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
413  * @since 2.8.8 - 2011-04-16
414  * @param string $propName, optional
415  * @param int $propix, optional, if specific property is wanted in case of multiply occurences
416  * @param bool $inclParam=FALSE
417  * @return mixed
418  */
419   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
420     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
421     if( 'X-PROP' == $propName ) {
422       if( !$propix )
423         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
424       $this->propix[$propName] = --$propix;
425     }
426     switch( $propName ) {
427       case 'ATTENDEE':
428       case 'CATEGORIES':
429       case 'DTSTART':
430       case 'LOCATION':
431       case 'ORGANIZER':
432       case 'PRIORITY':
433       case 'RESOURCES':
434       case 'STATUS':
435       case 'SUMMARY':
436       case 'RECURRENCE-ID-UID':
437       case 'R-UID':
438       case 'UID':
439         $output = array();
440         foreach ( $this->components as $cix => $component) {
441           if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
442             continue;
443           if(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) {
444             $component->_getProperties( $propName, $output );
445             continue;
446           }
447           elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
448             if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
449               $content = $component->getProperty( 'UID' );
450           }
451           elseif( FALSE === ( $content = $component->getProperty( $propName )))
452             continue;
453           if( FALSE === $content )
454             continue;
455           elseif( is_array( $content )) {
456             if( isset( $content['year'] )) {
457               $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
458               if( !isset( $output[$key] ))
459                 $output[$key] = 1;
460               else
461                 $output[$key] += 1;
462             }
463             else {
464               foreach( $content as $partValue => $partCount ) {
465                 if( !isset( $output[$partValue] ))
466                   $output[$partValue] = $partCount;
467                 else
468                   $output[$partValue] += $partCount;
469               }
470             }
471           } // end elseif( is_array( $content )) {
472           elseif( !isset( $output[$content] ))
473             $output[$content] = 1;
474           else
475             $output[$content] += 1;
476         } // end foreach ( $this->components as $cix => $component)
477         if( !empty( $output ))
478           ksort( $output );
479         return $output;
480         break;
481
482       case 'CALSCALE':
483         return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
484         break;
485       case 'METHOD':
486         return ( !empty( $this->method )) ? $this->method : FALSE;
487         break;
488       case 'PRODID':
489         if( empty( $this->prodid ))
490           $this->_makeProdid();
491         return $this->prodid;
492         break;
493       case 'VERSION':
494         return ( !empty( $this->version )) ? $this->version : FALSE;
495         break;
496       default:
497         if( $propName != 'X-PROP' ) {
498           if( !isset( $this->xprop[$propName] )) return FALSE;
499           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
500                                 : array( $propName, $this->xprop[$propName]['value'] );
501         }
502         else {
503           if( empty( $this->xprop )) return FALSE;
504           $xpropno = 0;
505           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
506             if( $propix == $xpropno )
507               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
508                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
509             else
510               $xpropno++;
511           }
512           unset( $this->propix[$propName] );
513           return FALSE; // not found ??
514         }
515     }
516     return FALSE;
517   }
518 /**
519  * general vcalendar property setting
520  *
521  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
522  * @since 2.2.13 - 2007-11-04
523  * @param mixed $args variable number of function arguments,
524  *                    first argument is ALWAYS component name,
525  *                    second ALWAYS component value!
526  * @return bool
527  */
528   function setProperty () {
529     $numargs    = func_num_args();
530     if( 1 > $numargs )
531       return FALSE;
532     $arglist    = func_get_args();
533     $arglist[0] = strtoupper( $arglist[0] );
534     switch( $arglist[0] ) {
535       case 'CALSCALE':
536         return $this->setCalscale( $arglist[1] );
537       case 'METHOD':
538         return $this->setMethod( $arglist[1] );
539       case 'VERSION':
540         return $this->setVersion( $arglist[1] );
541       default:
542         if( !isset( $arglist[1] )) $arglist[1] = null;
543         if( !isset( $arglist[2] )) $arglist[2] = null;
544         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
545     }
546     return FALSE;
547   }
548 /*********************************************************************************/
549 /**
550  * get vcalendar config values or * calendar components
551  *
552  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
553  * @since 2.11.7 - 2012-01-12
554  * @param mixed $config
555  * @return value
556  */
557   function getConfig( $config = FALSE ) {
558     if( !$config ) {
559       $return = array();
560       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
561       $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
562       $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
563       $return['FILENAME']    = $this->getConfig( 'FILENAME' );
564       $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
565       $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
566       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
567       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
568         $return['LANGUAGE']  = $lang;
569       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
570       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
571       if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
572         $return['URL']       = $url;
573       $return['TZID']        = $this->getConfig( 'TZID' );
574       return $return;
575     }
576     switch( strtoupper( $config )) {
577       case 'ALLOWEMPTY':
578         return $this->allowEmpty;
579         break;
580       case 'COMPSINFO':
581         unset( $this->compix );
582         $info = array();
583         foreach( $this->components as $cix => $component ) {
584           if( empty( $component )) continue;
585           $info[$cix]['ordno'] = $cix + 1;
586           $info[$cix]['type']  = $component->objName;
587           $info[$cix]['uid']   = $component->getProperty( 'uid' );
588           $info[$cix]['props'] = $component->getConfig( 'propinfo' );
589           $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
590         }
591         return $info;
592         break;
593       case 'DELIMITER':
594         return $this->delimiter;
595         break;
596       case 'DIRECTORY':
597         if( empty( $this->directory ) && ( '0' != $this->directory ))
598           $this->directory = '.';
599         return $this->directory;
600         break;
601       case 'DIRFILE':
602         return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
603         break;
604       case 'FILEINFO':
605         return array( $this->getConfig( 'directory' )
606                     , $this->getConfig( 'filename' )
607                     , $this->getConfig( 'filesize' ));
608         break;
609       case 'FILENAME':
610         if( empty( $this->filename ) && ( '0' != $this->filename )) {
611           if( 'xcal' == $this->format )
612             $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
613           else
614             $this->filename = date( 'YmdHis' ).'.ics';
615         }
616         return $this->filename;
617         break;
618       case 'FILESIZE':
619         $size    = 0;
620         if( empty( $this->url )) {
621           $dirfile = $this->getConfig( 'dirfile' );
622           if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
623             $size = 0;
624           clearstatcache();
625         }
626         return $size;
627         break;
628       case 'FORMAT':
629         return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
630         break;
631       case 'LANGUAGE':
632          /* get language for calendar component as defined in [RFC 1766] */
633         return $this->language;
634         break;
635       case 'NL':
636       case 'NEWLINECHAR':
637         return $this->nl;
638         break;
639       case 'TZID':
640         return $this->dtzid;
641         break;
642       case 'UNIQUE_ID':
643         return $this->unique_id;
644         break;
645       case 'URL':
646         if( !empty( $this->url ))
647           return $this->url;
648         else
649           return FALSE;
650         break;
651     }
652   }
653 /**
654  * general vcalendar config setting
655  *
656  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
657  * @since 2.11.11 - 2011-01-16
658  * @param mixed  $config
659  * @param string $value
660  * @return void
661  */
662   function setConfig( $config, $value = FALSE) {
663     if( is_array( $config )) {
664       $ak = array_keys( $config );
665       foreach( $ak as $k ) {
666         if( 'DIRECTORY' == strtoupper( $k )) {
667           if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
668             return FALSE;
669           unset( $config[$k] );
670         }
671         elseif( 'NEWLINECHAR' == strtoupper( $k )) {
672           if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
673             return FALSE;
674           unset( $config[$k] );
675         }
676       }
677       foreach( $config as $cKey => $cValue ) {
678         if( FALSE === $this->setConfig( $cKey, $cValue ))
679           return FALSE;
680       }
681       return TRUE;
682     }
683     $res = FALSE;
684     switch( strtoupper( $config )) {
685       case 'ALLOWEMPTY':
686         $this->allowEmpty = $value;
687         $subcfg  = array( 'ALLOWEMPTY' => $value );
688         $res = TRUE;
689         break;
690       case 'DELIMITER':
691         $this->delimiter = $value;
692         return TRUE;
693         break;
694       case 'DIRECTORY':
695         $value   = trim( $value );
696         $del     = $this->getConfig('delimiter');
697         if( $del == substr( $value, ( 0 - strlen( $del ))))
698           $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
699         if( is_dir( $value )) {
700             /* local directory */
701           clearstatcache();
702           $this->directory = $value;
703           $this->url       = null;
704           return TRUE;
705         }
706         else
707           return FALSE;
708         break;
709       case 'FILENAME':
710         $value   = trim( $value );
711         if( !empty( $this->url )) {
712             /* remote directory+file -> URL */
713           $this->filename = $value;
714           return TRUE;
715         }
716         $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
717         if( file_exists( $dirfile )) {
718             /* local file exists */
719           if( is_readable( $dirfile ) || is_writable( $dirfile )) {
720             clearstatcache();
721             $this->filename = $value;
722             return TRUE;
723           }
724           else
725             return FALSE;
726         }
727         elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
728             /* read- or writable directory */
729           $this->filename = $value;
730           return TRUE;
731         }
732         else
733           return FALSE;
734         break;
735       case 'FORMAT':
736         $value   = trim( strtolower( $value ));
737         if( 'xcal' == $value ) {
738           $this->format             = 'xcal';
739           $this->attributeDelimiter = $this->nl;
740           $this->valueInit          = null;
741         }
742         else {
743           $this->format             = null;
744           $this->attributeDelimiter = ';';
745           $this->valueInit          = ':';
746         }
747         $subcfg  = array( 'FORMAT' => $value );
748         $res = TRUE;
749         break;
750       case 'LANGUAGE':
751          // set language for calendar component as defined in [RFC 1766]
752         $value   = trim( $value );
753         $this->language = $value;
754         $subcfg  = array( 'LANGUAGE' => $value );
755         $res = TRUE;
756         break;
757       case 'NL':
758       case 'NEWLINECHAR':
759         $this->nl = $value;
760         if( 'xcal' == $value ) {
761           $this->attributeDelimiter = $this->nl;
762           $this->valueInit          = null;
763         }
764         else {
765           $this->attributeDelimiter = ';';
766           $this->valueInit          = ':';
767         }
768         $subcfg  = array( 'NL' => $value );
769         $res = TRUE;
770         break;
771       case 'TZID':
772         $this->dtzid = $value;
773         $subcfg  = array( 'TZID' => $value );
774         $res = TRUE;
775         break;
776       case 'UNIQUE_ID':
777         $value   = trim( $value );
778         $this->unique_id = $value;
779         $this->_makeProdid();
780         $subcfg  = array( 'UNIQUE_ID' => $value );
781         $res = TRUE;
782         break;
783       case 'URL':
784             /* remote file - URL */
785         $value     = trim( $value );
786         $value     = str_replace( 'HTTP://',   'http://', $value );
787         $value     = str_replace( 'WEBCAL://', 'http://', $value );
788         $value     = str_replace( 'webcal://', 'http://', $value );
789         $this->url = $value;
790         $this->directory = null;
791         $parts     = pathinfo( $value );
792         return $this->setConfig( 'filename',  $parts['basename'] );
793         break;
794       default:  // any unvalid config key.. .
795         return TRUE;
796     }
797     if( !$res ) return FALSE;
798     if( isset( $subcfg ) && !empty( $this->components )) {
799       foreach( $subcfg as $cfgkey => $cfgvalue ) {
800         foreach( $this->components as $cix => $component ) {
801           $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
802           if( !$res )
803             break 2;
804           $this->components[$cix] = $component->copy(); // PHP4 compliant
805         }
806       }
807     }
808     return $res;
809   }
810 /*********************************************************************************/
811 /**
812  * add calendar component to container
813  *
814  * alias to setComponent
815  *
816  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
817  * @since 1.x.x - 2007-04-24
818  * @param object $component calendar component
819  * @return void
820  */
821   function addComponent( $component ) {
822     $this->setComponent( $component );
823   }
824 /**
825  * delete calendar component from container
826  *
827  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
828  * @since 2.8.8 - 2011-03-15
829  * @param mixed $arg1 ordno / component type / component uid
830  * @param mixed $arg2 optional, ordno if arg1 = component type
831  * @return void
832  */
833   function deleteComponent( $arg1, $arg2=FALSE  ) {
834     $argType = $index = null;
835     if ( ctype_digit( (string) $arg1 )) {
836       $argType = 'INDEX';
837       $index   = (int) $arg1 - 1;
838     }
839     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
840       $argType = strtolower( $arg1 );
841       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
842     }
843     $cix1dC = 0;
844     foreach ( $this->components as $cix => $component) {
845       if( empty( $component )) continue;
846       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
847         unset( $this->components[$cix] );
848         return TRUE;
849       }
850       elseif( $argType == $component->objName ) {
851         if( $index == $cix1dC ) {
852           unset( $this->components[$cix] );
853           return TRUE;
854         }
855         $cix1dC++;
856       }
857       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
858         unset( $this->components[$cix] );
859         return TRUE;
860       }
861     }
862     return FALSE;
863   }
864 /**
865  * get calendar component from container
866  *
867  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
868  * @since 2.9.1 - 2011-04-16
869  * @param mixed $arg1 optional, ordno/component type/ component uid
870  * @param mixed $arg2 optional, ordno if arg1 = component type
871  * @return object
872  */
873   function getComponent( $arg1=FALSE, $arg2=FALSE ) {
874     $index = $argType = null;
875     if ( !$arg1 ) { // first or next in component chain
876       $argType = 'INDEX';
877       $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
878     }
879     elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
880       $argType = 'INDEX';
881       $index   = (int) $arg1;
882       unset( $this->compix );
883     }
884     elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
885       $arg2  = implode( '-', array_keys( $arg1 ));
886       $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
887       $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
888       $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID' );
889     }
890     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
891       unset( $this->compix['INDEX'] );
892       $argType = strtolower( $arg1 );
893       if( !$arg2 )
894         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
895       elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
896         $index = (int) $arg2;
897     }
898     elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
899       if( !$arg2 )
900         $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
901       elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
902         $index = (int) $arg2;
903     }
904     if( isset( $index ))
905       $index  -= 1;
906     $ckeys = array_keys( $this->components );
907     if( !empty( $index) && ( $index > end(  $ckeys )))
908       return FALSE;
909     $cix1gC = 0;
910     foreach ( $this->components as $cix => $component) {
911       if( empty( $component )) continue;
912       if(( 'INDEX' == $argType ) && ( $index == $cix ))
913         return $component->copy();
914       elseif( $argType == $component->objName ) {
915         if( $index == $cix1gC )
916           return $component->copy();
917         $cix1gC++;
918       }
919       elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
920         $hit = FALSE;
921         foreach( $arg1 as $pName => $pValue ) {
922           $pName = strtoupper( $pName );
923           if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
924             continue;
925           if(( 'ATTENDEE' == $pName ) || ( 'CATEGORIES' == $pName ) || ( 'RESOURCES' == $pName )) { // multiple ocurrence may occur
926             $propValues = array();
927             $component->_getProperties( $pName, $propValues );
928             $propValues = array_keys( $propValues );
929             $hit = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
930             continue;
931           } // end   if(( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple ocurrence may occur
932           if( FALSE === ( $value = $component->getProperty( $pName ))) { // single ocurrency
933             $hit = FALSE; // missing property
934             continue;
935           }
936           if( 'SUMMARY' == $pName ) { // exists within (any case)
937             $hit = ( FALSE !== stripos( $d, $pValue )) ? TRUE : FALSE;
938             continue;
939           }
940           if( in_array( strtoupper( $pName ), $dateProps )) {
941             $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
942             if( 8 < strlen( $pValue )) {
943               if( isset( $value['hour'] )) {
944                 if( 'T' == substr( $pValue, 8, 1 ))
945                   $pValue = str_replace( 'T', '', $pValue );
946                 $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
947               }
948               else
949                 $pValue = substr( $pValue, 0, 8 );
950             }
951             $hit = ( $pValue == $valuedate ) ? TRUE : FALSE;
952             continue;
953           }
954           elseif( !is_array( $value ))
955             $value = array( $value );
956           foreach( $value as $part ) {
957             $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
958             foreach( $part as $subPart ) {
959               if( $pValue == $subPart ) {
960                 $hit = TRUE;
961                 continue 2;
962               }
963             }
964           }
965           $hit = FALSE; // no hit in property
966         } // end  foreach( $arg1 as $pName => $pValue )
967         if( $hit ) {
968           if( $index == $cix1gC )
969             return $component->copy();
970           $cix1gC++;
971         }
972       } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
973       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
974         if( $index == $cix1gC )
975           return $component->copy();
976         $cix1gC++;
977       }
978     } // end foreach ( $this->components.. .
979             /* not found.. . */
980     unset( $this->compix );
981     return FALSE;
982   }
983 /**
984  * create new calendar component, already included within calendar
985  *
986  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
987  * @since 2.6.33 - 2011-01-03
988  * @param string $compType component type
989  * @return object (reference)
990  */
991   function & newComponent( $compType ) {
992     $config = $this->getConfig();
993     $keys   = array_keys( $this->components );
994     $ix     = end( $keys) + 1;
995     switch( strtoupper( $compType )) {
996       case 'EVENT':
997       case 'VEVENT':
998         $this->components[$ix] = new vevent( $config );
999         break;
1000       case 'TODO':
1001       case 'VTODO':
1002         $this->components[$ix] = new vtodo( $config );
1003         break;
1004       case 'JOURNAL':
1005       case 'VJOURNAL':
1006         $this->components[$ix] = new vjournal( $config );
1007         break;
1008       case 'FREEBUSY':
1009       case 'VFREEBUSY':
1010         $this->components[$ix] = new vfreebusy( $config );
1011         break;
1012       case 'TIMEZONE':
1013       case 'VTIMEZONE':
1014         array_unshift( $this->components, new vtimezone( $config ));
1015         $ix = 0;
1016         break;
1017       default:
1018         return FALSE;
1019     }
1020     return $this->components[$ix];
1021   }
1022 /**
1023  * select components from calendar on date or selectOption basis
1024  *
1025  * Ensure DTSTART is set for every component.
1026  * No date controls occurs.
1027  *
1028  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1029  * @since 2.11.22 - 2012-02-13
1030  * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
1031  * @param int   $startM optional, start Month, default current Month
1032  * @param int   $startD optional, start Day,   default current Day
1033  * @param int   $endY   optional, end   Year,  default $startY
1034  * @param int   $endY   optional, end   Month, default $startM
1035  * @param int   $endY   optional, end   Day,   default $startD
1036  * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
1037  * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
1038  *                                TRUE            => output : array[] (ignores split)
1039  * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
1040  *                                FALSE          - only component(-s) that starts within period
1041  * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
1042  *                                                 period (implies flat=FALSE)
1043  *                                FALSE          - one occurance of component only in output array
1044  * @return array or FALSE
1045  */
1046   function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
1047             /* check  if empty calendar */
1048     if( 0 >= count( $this->components )) return FALSE;
1049     if( is_array( $startY ))
1050       return $this->selectComponents2( $startY );
1051             /* check default dates */
1052     if( !$startY ) $startY = date( 'Y' );
1053     if( !$startM ) $startM = date( 'm' );
1054     if( !$startD ) $startD = date( 'd' );
1055     $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
1056     if( !$endY )   $endY   = $startY;
1057     if( !$endM )   $endM   = $startM;
1058     if( !$endD )   $endD   = $startD;
1059     $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
1060 //echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br />\n"; $tcnt = 0;// test ###
1061             /* check component types */
1062     $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
1063     if( is_array( $cType )) {
1064       foreach( $cType as $cix => $theType ) {
1065         $cType[$cix] = $theType = strtolower( $theType );
1066         if( !in_array( $theType, $validTypes ))
1067           $cType[$cix] = 'vevent';
1068       }
1069       $cType = array_unique( $cType );
1070     }
1071     elseif( !empty( $cType )) {
1072       $cType = strtolower( $cType );
1073       if( !in_array( $cType, $validTypes ))
1074         $cType = array( 'vevent' );
1075       else
1076         $cType = array( $cType );
1077     }
1078     else
1079       $cType = $validTypes;
1080     if( 0 >= count( $cType ))
1081       $cType = $validTypes;
1082     if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
1083       $split = FALSE;
1084     if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
1085       $split = FALSE;
1086             /* iterate components */
1087     $result = array();
1088     foreach ( $this->components as $cix => $component ) {
1089       if( empty( $component )) continue;
1090       unset( $start );
1091             /* deselect unvalid type components */
1092       if( !in_array( $component->objName, $cType ))
1093         continue;
1094       $start = $component->getProperty( 'dtstart' );
1095             /* select due when dtstart is missing */
1096       if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
1097         continue;
1098       if( empty( $start ))
1099         continue;
1100       $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
1101       unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend, $endDateFormat ); // clean up
1102       $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
1103       $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1104             /* get end date from dtend/due/duration properties */
1105       $end = $component->getProperty( 'dtend' );
1106       if( !empty( $end )) {
1107         $dtendExist = TRUE;
1108         $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1109       }
1110       if( empty( $end ) && ( $component->objName == 'vtodo' )) {
1111         $end = $component->getProperty( 'due' );
1112         if( !empty( $end )) {
1113           $dueExist = TRUE;
1114           $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1115         }
1116       }
1117       if( !empty( $end ) && !isset( $end['hour'] )) {
1118           /* a DTEND without time part regards an event that ends the day before,
1119              for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
1120         $endAllDayEvent = TRUE;
1121         $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
1122         $end['year']  = date( 'Y', $endWdate );
1123         $end['month'] = date( 'm', $endWdate );
1124         $end['day']   = date( 'd', $endWdate );
1125         $end['hour']  = 23;
1126         $end['min']   = $end['sec'] = 59;
1127       }
1128       if( empty( $end )) {
1129         $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
1130         if( !empty( $end ))
1131           $durationExist = TRUE;
1132           $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1133 // if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
1134       }
1135       if( empty( $end )) { // assume one day duration if missing end date
1136         $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
1137       }
1138 // if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br />\n"; // test ###
1139       $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
1140       if( $endWdate < $startWdate ) { // MUST be after start date!!
1141         $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
1142         $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
1143       }
1144       $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
1145             /* make a list of optional exclude dates for component occurence from exrule and exdate */
1146       $exdatelist = array();
1147       $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
1148       $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
1149       while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
1150         iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
1151       while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
1152         foreach( $exdate as $theExdate ) {
1153           $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
1154           $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
1155           if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
1156             $exdatelist[$exWdate] = TRUE;
1157         } // end - foreach( $exdate as $theExdate )
1158       }  // end - check exdate
1159       $compUID    = $component->getProperty( 'UID' );
1160             /* check recurrence-id (with sequence), remove hit with reccurr-id date */
1161       if(( FALSE !== ( $recurrid = $component->getProperty( 'recurrence-id' ))) &&
1162          ( FALSE !== ( $sequence = $component->getProperty( 'sequence' )))   ) {
1163         $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
1164         $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
1165         $endD     = $recurrid + $rdurWsecs;
1166         do {
1167           if( date( 'Ymd', $startWdate ) != date( 'Ymd', $recurrid ))
1168             $exdatelist[$recurrid] = TRUE; // exclude all other days than startdate
1169           $wd = getdate( $recurrid );
1170           if( isset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] ))
1171               unset( $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] ); // remove from output, dtstart etc added below
1172           if( $split && ( $recurrid <= $endD ))
1173             $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ) + 1, date( 'Y', $recurrid )); // step one day
1174           else
1175             break;
1176         } while( TRUE );
1177       } // end recurrence-id test
1178             /* select only components with.. . */
1179       if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
1180          (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
1181             /* add the selected component (WITHIN valid dates) to output array */
1182         if( $flat ) { // any=true/false, ignores split
1183           if( !$recurrid )
1184             $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
1185         }
1186         elseif( $split ) { // split the original component
1187           if( $endWdate > $endDate )
1188             $endWdate = $endDate;     // use period end date
1189           $rstart   = $startWdate;
1190           if( $rstart < $startDate )
1191             $rstart = $startDate; // use period start date
1192           $startYMD = date( 'Ymd', $rstart );
1193           $endYMD   = date( 'Ymd', $endWdate );
1194           $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1195           while( date( 'Ymd', $rstart ) <= $endYMD ) { // iterate
1196             $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1197             if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
1198               $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1199               continue;
1200             }
1201             if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart
1202               $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
1203             else
1204               $datestring = date( $startDateFormat, $rstart );
1205             if( isset( $start['tz'] ))
1206               $datestring .= ' '.$start['tz'];
1207 // echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component->setProperty( 'X-CNT', $tcnt ); // test ###
1208             $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
1209             if( $dtendExist || $dueExist || $durationExist ) {
1210               if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day
1211                 $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1212               else
1213                 $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1214               if( $endAllDayEvent && $dtendExist )
1215                 $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1216               $datestring = date( $endDateFormat, $tend );
1217               if( isset( $end['tz'] ))
1218                 $datestring .= ' '.$end['tz'];
1219               $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1220               $component->setProperty( $propName, $datestring );
1221             } // end if( $dtendExist || $dueExist || $durationExist )
1222             $wd = getdate( $rstart );
1223             $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
1224             $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1225           } // end while( $rstart <= $endWdate )
1226         } // end if( $split )   -  else use component date
1227         elseif( $recurrid && !$flat && !$any && !$split )
1228           $continue = TRUE;
1229         else { // !$flat && !$split, i.e. no flat array and DTSTART within period
1230           $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
1231           if( !$any || !isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist
1232             $wd = getdate( $startWdate );
1233             $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
1234           }
1235         }
1236       } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
1237
1238             /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
1239       if( TRUE === $any ) {
1240             /* make a list of optional repeating dates for component occurence, rrule, rdate */
1241         $recurlist = array();
1242         while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
1243           iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
1244         foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp
1245           $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds
1246         while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) {  // check rdate
1247           foreach( $rdate as $theRdate ) {
1248             if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&  // all days within PERIOD
1249                    array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
1250               $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
1251               if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
1252                 continue;
1253               if( isset( $theRdate[1]['year'] )) // date-date period
1254                 $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
1255               else {                             // date-duration period
1256                 $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
1257                 $rend = iCalUtilityFunctions::_date2timestamp( $rend );
1258               }
1259               while( $rstart < $rend ) {
1260                 $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
1261                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1262               }
1263             } // PERIOD end
1264             else { // single date
1265               $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
1266               if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
1267                 $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
1268             }
1269           }
1270         }  // end - check rdate
1271         if( 0 < count( $recurlist )) {
1272           ksort( $recurlist );
1273           $xRecurrence = 1;
1274           $component2  = $component->copy();
1275           $compUID     = $component2->getProperty( 'UID' );
1276           foreach( $recurlist as $recurkey => $durvalue ) {
1277 // echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br />\n"; // test ###;
1278             if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
1279               continue;
1280             $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
1281             if( isset( $exdatelist[$checkDate] )) // check excluded dates
1282               continue;
1283             if( $startWdate >= $recurkey ) // exclude component start date
1284               continue;
1285             $rstart = $recurkey;
1286             $rend   = $recurkey + $durvalue;
1287            /* add repeating components within valid dates to output array, only start date set */
1288             if( $flat ) {
1289               if( !isset( $result[$compUID] )) // only one comp
1290                 $result[$compUID] = $component2->copy(); // copy to output
1291             }
1292            /* add repeating components within valid dates to output array, one each day */
1293             elseif( $split ) {
1294               if( $rend > $endDate )
1295                 $rend = $endDate;
1296               $startYMD = date( 'Ymd', $rstart );
1297               $endYMD   = date( 'Ymd', $rend );
1298 // echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br />\n"; // test ###;
1299               while( $rstart <= $rend ) { // iterate.. .
1300                 $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1301                 if( isset( $exdatelist[$checkDate] ))  // exclude any recurrence START date, found in exdatelist
1302                   break;
1303 // echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br />"; // test ###;
1304                 if( $rstart >= $startDate ) {    // date after dtstart
1305                   if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart
1306                     $datestring = date( $startDateFormat, $checkDate );
1307                   else
1308                     $datestring = date( $startDateFormat, $rstart );
1309                   if( isset( $start['tz'] ))
1310                     $datestring .= ' '.$start['tz'];
1311 //echo "X-CURRENT-DTSTART 1 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1312                   $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1313                   if( $dtendExist || $dueExist || $durationExist ) {
1314                     if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day
1315                       $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1316                     else
1317                       $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1318                     if( $endAllDayEvent && $dtendExist )
1319                       $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1320                     $datestring = date( $endDateFormat, $tend );
1321                     if( isset( $end['tz'] ))
1322                       $datestring .= ' '.$end['tz'];
1323                     $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1324                     $component2->setProperty( $propName, $datestring );
1325                   } // end if( $dtendExist || $dueExist || $durationExist )
1326                   $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1327                   $wd = getdate( $rstart );
1328                   $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
1329                 } // end if( $checkDate > $startYMD ) {    // date after dtstart
1330                 $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1331               } // end while( $rstart <= $rend )
1332               $xRecurrence += 1;
1333             } // end elseif( $split )
1334             elseif( $rstart >= $startDate ) {     // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
1335               $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1336               if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
1337                 $xRecurrence += 1;
1338                 $datestring = date( $startDateFormat, $rstart );
1339                 if( isset( $start['tz'] ))
1340                   $datestring .= ' '.$start['tz'];
1341 //echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br />";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1342                 $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1343                 if( $dtendExist || $dueExist || $durationExist ) {
1344                   $tend = $rstart + $rdurWsecs;
1345                   if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
1346                     $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
1347                   else
1348                     $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
1349                   if( $endAllDayEvent && $dtendExist )
1350                     $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1351                   $datestring = date( $endDateFormat, $tend );
1352                   if( isset( $end['tz'] ))
1353                     $datestring .= ' '.$end['tz'];
1354                   $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1355                   $component2->setProperty( $propName, $datestring );
1356                 } // end if( $dtendExist || $dueExist || $durationExist )
1357                 $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1358                 $wd = getdate( $rstart );
1359                 $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
1360               } // end if( !isset( $exdatelist[$checkDate] ))
1361             } // end elseif( $rstart >= $startDate )
1362           } // end foreach( $recurlist as $recurkey => $durvalue )
1363         } // end if( 0 < count( $recurlist ))
1364             /* deselect components with startdate/enddate not within period */
1365         if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
1366           continue;
1367       } // end if( TRUE === $any )
1368     } // end foreach ( $this->components as $cix => $component )
1369     if( 0 >= count( $result )) return FALSE;
1370     elseif( !$flat ) {
1371       foreach( $result as $y => $yeararr ) {
1372         foreach( $yeararr as $m => $montharr ) {
1373           foreach( $montharr as $d => $dayarr ) {
1374             if( empty( $result[$y][$m][$d] ))
1375                 unset( $result[$y][$m][$d] );
1376             else
1377               $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
1378           }
1379           if( empty( $result[$y][$m] ))
1380               unset( $result[$y][$m] );
1381           else
1382             ksort( $result[$y][$m] );
1383         }
1384         if( empty( $result[$y] ))
1385             unset( $result[$y] );
1386         else
1387           ksort( $result[$y] );
1388       }
1389       if( empty( $result ))
1390           unset( $result );
1391       else
1392         ksort( $result );
1393     } // end elseif( !$flat )
1394     if( 0 >= count( $result ))
1395       return FALSE;
1396     return $result;
1397   }
1398 /**
1399  * select components from calendar on based on Categories, Location, Resources and/or Summary
1400  *
1401  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1402  * @since 2.8.8 - 2011-05-03
1403  * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
1404  * @return array
1405  */
1406   function selectComponents2( $selectOptions ) {
1407     $output = array();
1408     $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY', 'UID' );
1409     foreach( $this->components as $cix => $component3 ) {
1410       if( !in_array( $component3->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
1411         continue;
1412       $uid = $component3->getProperty( 'UID' );
1413       foreach( $selectOptions as $propName => $pvalue ) {
1414         $propName = strtoupper( $propName );
1415         if( !in_array( $propName, $allowedProperties ))
1416           continue;
1417         if( !is_array( $pvalue ))
1418           $pvalue = array( $pvalue );
1419         if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
1420           $output[] = $component3->copy();
1421           continue;
1422         }
1423         elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) {
1424           $propValues = array();
1425           $component3->_getProperties( $propName, $propValues );
1426           $propValues = array_keys( $propValues );
1427           foreach( $pvalue as $theValue ) {
1428             if( in_array( $theValue, $propValues ) && !isset( $output[$uid] )) {
1429               $output[$uid] = $component3->copy();
1430               break;
1431             }
1432           }
1433           continue;
1434         } // end   elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName ))
1435         elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single ocurrence
1436           continue;
1437         if( is_array( $d )) {
1438           foreach( $d as $part ) {
1439             if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
1440               $output[$uid] = $component3->copy();
1441           }
1442         }
1443         elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
1444           foreach( $pvalue as $pval ) {
1445             if( FALSE !== stripos( $d, $pval )) {
1446               $output[$uid] = $component3->copy();
1447               break;
1448             }
1449           }
1450         }
1451         elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
1452           $output[$uid] = $component3->copy();
1453       } // end foreach( $selectOptions as $propName => $pvalue ) {
1454     } // end foreach( $this->components as $cix => $component3 ) {
1455     if( !empty( $output )) {
1456       ksort( $output );
1457       $output = array_values( $output );
1458     }
1459     return $output;
1460   }
1461 /**
1462  * add calendar component to container
1463  *
1464  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1465  * @since 2.8.8 - 2011-03-15
1466  * @param object $component calendar component
1467  * @param mixed $arg1 optional, ordno/component type/ component uid
1468  * @param mixed $arg2 optional, ordno if arg1 = component type
1469  * @return void
1470  */
1471   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
1472     $component->setConfig( $this->getConfig(), FALSE, TRUE );
1473     if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
1474             /* make sure dtstamp and uid is set */
1475       $dummy1 = $component->getProperty( 'dtstamp' );
1476       $dummy2 = $component->getProperty( 'uid' );
1477     }
1478     if( !$arg1 ) { // plain insert, last in chain
1479       $this->components[] = $component->copy();
1480       return TRUE;
1481     }
1482     $argType = $index = null;
1483     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
1484       $argType = 'INDEX';
1485       $index   = (int) $arg1 - 1;
1486     }
1487     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
1488       $argType = strtolower( $arg1 );
1489       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
1490     }
1491     // else if arg1 is set, arg1 must be an UID
1492     $cix1sC = 0;
1493     foreach ( $this->components as $cix => $component2) {
1494       if( empty( $component2 )) continue;
1495       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
1496         $this->components[$cix] = $component->copy();
1497         return TRUE;
1498       }
1499       elseif( $argType == $component2->objName ) { // component Type index insert/replace
1500         if( $index == $cix1sC ) {
1501           $this->components[$cix] = $component->copy();
1502           return TRUE;
1503         }
1504         $cix1sC++;
1505       }
1506       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
1507         $this->components[$cix] = $component->copy();
1508         return TRUE;
1509       }
1510     }
1511             /* arg1=index and not found.. . insert at index .. .*/
1512     if( 'INDEX' == $argType ) {
1513       $this->components[$index] = $component->copy();
1514       ksort( $this->components, SORT_NUMERIC );
1515     }
1516     else    /* not found.. . insert last in chain anyway .. .*/
1517       $this->components[] = $component->copy();
1518     return TRUE;
1519   }
1520 /**
1521  * sort iCal compoments
1522  *
1523  * ascending sort on properties (if exist) x-current-dtstart, dtstart,
1524  * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid
1525  * if no arguments, otherwise sorting on argument CATEGORIES, LOCATION, SUMMARY or RESOURCES
1526  *
1527  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1528  * @since 2.8.4 - 2011-06-02
1529  * @param string $sortArg, optional
1530  * @return void
1531  *
1532  */
1533   function sort( $sortArg=FALSE ) {
1534     if( is_array( $this->components )) {
1535       if( $sortArg ) {
1536         $sortArg = strtoupper( $sortArg );
1537         if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY' )))
1538           $sortArg = FALSE;
1539       }
1540             /* set sort parameters for each component */
1541       foreach( $this->components as $cix => & $c ) {
1542         $c->srtk = array( '0', '0', '0', '0' );
1543         if( 'vtimezone' == $c->objName ) {
1544           if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
1545             $c->srtk[0] = 0;
1546           continue;
1547         }
1548         elseif( $sortArg ) {
1549           if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'RESOURCES'  == $sortArg )) {
1550             $propValues = array();
1551             $c->_getProperties( $sortArg, $propValues );
1552             $c->srtk[0] = reset( array_keys( $propValues ));
1553           }
1554           elseif( FALSE !== ( $d = $c->getProperty( $sortArg )))
1555             $c->srtk[0] = $d;
1556           continue;
1557         }
1558         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
1559           $c->srtk[0] = iCalUtilityFunctions::_date_time_string( $d[1] );
1560           unset( $c->srtk[0]['unparsedtext'] );
1561         }
1562         elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
1563           $c->srtk[1] = 0;                                                  // sortkey 0 : dtstart
1564         if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
1565           $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] );   // sortkey 1 : dtend/due(/dtstart+duration)
1566           unset( $c->srtk[1]['unparsedtext'] );
1567         }
1568         elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
1569           if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
1570             $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] );
1571             unset( $c->srtk[1]['unparsedtext'] );
1572           }
1573           elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
1574             if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
1575               $c->srtk[1] = 0;
1576         }
1577         if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
1578           if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
1579             $c->srtk[2] = 0;
1580         if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
1581           $c->srtk[3] = 0;
1582       } // end foreach( $this->components as & $c
1583             /* sort */
1584       usort( $this->components, array( $this, '_cmpfcn' ));
1585     }
1586   }
1587   function _cmpfcn( $a, $b ) {
1588     if(        empty( $a ))                       return -1;
1589     if(        empty( $b ))                       return  1;
1590     if( 'vtimezone' == $a->objName ) {
1591       if( 'vtimezone' != $b->objName )            return -1;
1592       elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
1593       else                                        return  1;
1594     }
1595     elseif( 'vtimezone' == $b->objName )          return  1;
1596     $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
1597     for( $k = 0; $k < 4 ; $k++ ) {
1598       if(        empty( $a->srtk[$k] ))           return -1;
1599       elseif(    empty( $b->srtk[$k] ))           return  1;
1600       if( is_array( $a->srtk[$k] )) {
1601         if( is_array( $b->srtk[$k] )) {
1602           foreach( $sortkeys as $key ) {
1603             if    (  empty( $a->srtk[$k][$key] )) return -1;
1604             elseif(  empty( $b->srtk[$k][$key] )) return  1;
1605             if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
1606                                                   continue;
1607             if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
1608                                                   return -1;
1609             elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
1610                                                   return  1;
1611           }
1612         }
1613         else                                      return -1;
1614       }
1615       elseif( is_array( $b->srtk[$k] ))           return  1;
1616       elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
1617       elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
1618     }
1619     return 0;
1620   }
1621 /**
1622  * parse iCal text/file into vcalendar, components, properties and parameters
1623  *
1624  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1625  * @since 2.11.10 - 2012-01-31
1626  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
1627  * @return bool FALSE if error occurs during parsing
1628  *
1629  */
1630   function parse( $unparsedtext=FALSE ) {
1631     $nl = $this->getConfig( 'nl' );
1632     if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
1633             /* directory+filename is set previously via setConfig directory+filename or url */
1634       if( FALSE === ( $filename = $this->getConfig( 'url' )))
1635         $filename = $this->getConfig( 'dirfile' );
1636             /* READ FILE */
1637       if( FALSE === ( $rows = file_get_contents( $filename )))
1638         return FALSE;                 /* err 1 */
1639     }
1640     elseif( is_array( $unparsedtext ))
1641       $rows =  implode( '\n'.$nl, $unparsedtext );
1642     else
1643       $rows = & $unparsedtext;
1644             /* identify BEGIN:VCALENDAR, MUST be first row */
1645     if( 'BEGIN:VCALENDAR' != strtoupper( substr( $rows, 0, 15 )))
1646       return FALSE;                   /* err 8 */
1647             /* fix line folding */
1648     $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings
1649     $EOLmark = FALSE;
1650     foreach( $eolchars as $eolchar ) {
1651       if( !$EOLmark  && ( FALSE !== strpos( $rows, $eolchar ))) {
1652         $rows = str_replace( $eolchar." ",  '',  $rows );
1653         $rows = str_replace( $eolchar."\t", '',  $rows );
1654         if( $eolchar != $nl )
1655           $rows = str_replace( $eolchar,    $nl, $rows );
1656         $EOLmark = TRUE;
1657       }
1658     }
1659     $rows = explode( $nl, $rows );
1660             /* skip trailing empty lines */
1661     $lix = count( $rows ) - 1;
1662     while( empty( $rows[$lix] ) && ( 0 < $lix ))
1663       $lix -= 1;
1664             /* identify ending END:VCALENDAR row, MUST be last row */
1665     if( 'END:VCALENDAR'   != strtoupper( substr( $rows[$lix], 0, 13 )))
1666       return FALSE;                   /* err 9 */
1667     if( 3 > count( $rows ))
1668       return FALSE;                   /* err 10 */
1669     $comp    = & $this;
1670     $calsync = 0;
1671             /* identify components and update unparsed data within component */
1672     $config = $this->getConfig();
1673     foreach( $rows as $line ) {
1674       if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
1675         $calsync++;
1676         continue;
1677       }
1678       elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
1679         $calsync--;
1680         break;
1681       }
1682       elseif( 1 != $calsync )
1683         return FALSE;                 /* err 20 */
1684       elseif( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ))) {
1685         $this->components[] = $comp->copy();
1686         continue;
1687       }
1688       if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 )))
1689         $comp = new vevent( $config );
1690       elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 )))
1691         $comp = new vfreebusy( $config );
1692       elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 )))
1693         $comp = new vjournal( $config );
1694       elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 )))
1695         $comp = new vtodo( $config );
1696       elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 )))
1697         $comp = new vtimezone( $config );
1698       else { /* update component with unparsed data */
1699         $comp->unparsed[] = $line;
1700       }
1701     } // end foreach( $rows as $line )
1702     unset( $config );
1703             /* parse data for calendar (this) object */
1704     if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
1705             /* concatenate property values spread over several lines */
1706       $lastix    = -1;
1707       $propnames = array( 'calscale','method','prodid','version','x-' );
1708       $proprows  = array();
1709       foreach( $this->unparsed as $line ) {
1710         $newProp = FALSE;
1711         foreach ( $propnames as $propname ) {
1712           if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
1713             $newProp = TRUE;
1714             break;
1715           }
1716         }
1717         if( $newProp ) {
1718           $newProp = FALSE;
1719           $lastix++;
1720           $proprows[$lastix]  = $line;
1721         }
1722         else
1723           $proprows[$lastix] .= '!"#¤%&/()=?'.$line;
1724       }
1725       $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
1726       $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
1727       $paramProto4 = array( 'crid:', 'news:', 'pres:' );
1728       foreach( $proprows as $line ) {
1729         $line = str_replace( '!"#¤%&/()=? ', '', $line );
1730         $line = str_replace( '!"#¤%&/()=?', '', $line );
1731         if( '\n' == substr( $line, -2 ))
1732           $line = substr( $line, 0, strlen( $line ) - 2 );
1733             /* get property name */
1734         $cix = $propname = null;
1735         for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) {
1736           if( in_array( $line[$cix], array( ':', ';' )))
1737             break;
1738           else
1739             $propname .= $line[$cix];
1740         }
1741             /* ignore version/prodid properties */
1742         if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' )))
1743           continue;
1744         $line = substr( $line, $cix);
1745             /* separate attributes from value */
1746         $attr         = array();
1747         $attrix       = -1;
1748         $strlen       = strlen( $line );
1749         $WithinQuotes = FALSE;
1750         for( $cix=0; $cix < $strlen; $cix++ ) {
1751           if(                       ( ':'  == $line[$cix] )                         &&
1752                                     ( substr( $line,$cix,     3 )  != '://' )       &&
1753              ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
1754              ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
1755              ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
1756                         ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
1757                !$WithinQuotes ) {
1758             $attrEnd = TRUE;
1759             if(( $cix < ( $strlen - 4 )) &&
1760                  ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
1761               for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
1762                 if( '://' == substr( $line, $c2ix - 2, 3 )) {
1763                   $attrEnd = FALSE;
1764                   break; // an URI with a portnr!!
1765                 }
1766               }
1767             }
1768             if( $attrEnd) {
1769               $line = substr( $line, ( $cix + 1 ));
1770               break;
1771             }
1772           }
1773           if( '"' == $line[$cix] )
1774             $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
1775           if( ';' == $line[$cix] )
1776             $attr[++$attrix] = null;
1777           else
1778             $attr[$attrix] .= $line[$cix];
1779         }
1780             /* make attributes in array format */
1781         $propattr = array();
1782         foreach( $attr as $attribute ) {
1783           $attrsplit = explode( '=', $attribute, 2 );
1784           if( 1 < count( $attrsplit ))
1785             $propattr[$attrsplit[0]] = $attrsplit[1];
1786           else
1787             $propattr[] = $attribute;
1788         }
1789             /* update Property */
1790         if( FALSE !== strpos( $line, ',' )) {
1791           $llen     = strlen( $line );
1792           $content  = array( 0 => '' );
1793           $cix      = 0;
1794           for( $lix = 0; $lix < $llen; $lix++ ) {
1795             if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
1796               $cix++;
1797               $content[$cix] = '';
1798             }
1799             else
1800               $content[$cix] .= $line[$lix];
1801           }
1802           if( 1 < count( $content )) {
1803             foreach( $content as $cix => $contentPart )
1804               $content[$cix] = calendarComponent::_strunrep( $contentPart );
1805             $this->setProperty( $propname, $content, $propattr );
1806             continue;
1807           }
1808           else
1809             $line = reset( $content );
1810           $line = calendarComponent::_strunrep( $line );
1811         }
1812         $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
1813       } // end - foreach( $this->unparsed.. .
1814     } // end - if( is_array( $this->unparsed.. .
1815     unset( $unparsedtext, $rows, $this->unparsed, $proprows );
1816             /* parse Components */
1817     if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
1818       $ckeys = array_keys( $this->components );
1819       foreach( $ckeys as $ckey ) {
1820         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
1821           $this->components[$ckey]->parse();
1822         }
1823       }
1824     }
1825     else
1826       return FALSE;                   /* err 91 or something.. . */
1827     return TRUE;
1828   }
1829 /*********************************************************************************/
1830 /**
1831  * creates formatted output for calendar object instance
1832  *
1833  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1834  * @since 2.10.16 - 2011-10-28
1835  * @return string
1836  */
1837   function createCalendar() {
1838     $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
1839     switch( $this->format ) {
1840       case 'xcal':
1841         $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
1842                          '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
1843                          '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
1844         $calendarStart = '>'.$this->nl.'<vcalendar';
1845         break;
1846       default:
1847         $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
1848         break;
1849     }
1850     $calendarStart .= $this->createVersion();
1851     $calendarStart .= $this->createProdid();
1852     $calendarStart .= $this->createCalscale();
1853     $calendarStart .= $this->createMethod();
1854     if( 'xcal' == $this->format )
1855       $calendarStart .= '>'.$this->nl;
1856     $calendar .= $this->createXprop();
1857
1858     foreach( $this->components as $component ) {
1859       if( empty( $component )) continue;
1860       $component->setConfig( $this->getConfig(), FALSE, TRUE );
1861       $calendar .= $component->createComponent( $this->xcaldecl );
1862     }
1863     if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
1864       $calendarInit .= ' [';
1865       $old_xcaldecl  = array();
1866       foreach( $this->xcaldecl as $declix => $declPart ) {
1867         if(( 0 < count( $old_xcaldecl))    &&
1868              isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
1869              isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
1870            ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
1871            ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
1872           continue; // no duplicate uri and ext. references
1873         if(( 0 < count( $old_xcaldecl))    &&
1874             !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
1875              isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
1876            ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
1877           continue; // no duplicate element declarations
1878         $calendarxCaldecl .= $this->nl.'<!';
1879         foreach( $declPart as $declKey => $declValue ) {
1880           switch( $declKey ) {                    // index
1881             case 'xmldecl':                       // no 1
1882               $calendarxCaldecl .= $declValue.' ';
1883               break;
1884             case 'uri':                           // no 2
1885               $calendarxCaldecl .= $declValue.' ';
1886               $old_xcaldecl['uri'][] = $declValue;
1887               break;
1888             case 'ref':                           // no 3
1889               $calendarxCaldecl .= $declValue.' ';
1890               $old_xcaldecl['ref'][] = $declValue;
1891               break;
1892             case 'external':                      // no 4
1893               $calendarxCaldecl .= '"'.$declValue.'" ';
1894               $old_xcaldecl['external'][] = $declValue;
1895               break;
1896             case 'type':                          // no 5
1897               $calendarxCaldecl .= $declValue.' ';
1898               break;
1899             case 'type2':                         // no 6
1900               $calendarxCaldecl .= $declValue;
1901               break;
1902           }
1903         }
1904         $calendarxCaldecl .= '>';
1905       }
1906       $calendarxCaldecl .= $this->nl.']';
1907     }
1908     switch( $this->format ) {
1909       case 'xcal':
1910         $calendar .= '</vcalendar>'.$this->nl;
1911         break;
1912       default:
1913         $calendar .= 'END:VCALENDAR'.$this->nl;
1914         break;
1915     }
1916     return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
1917   }
1918 /**
1919  * a HTTP redirect header is sent with created, updated and/or parsed calendar
1920  *
1921  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1922  * @since 2.10.24 - 2011-12-23
1923  * @param bool $utf8Encode
1924  * @param bool $gzip
1925  * @return redirect
1926  */
1927   function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
1928     $filename = $this->getConfig( 'filename' );
1929     $output   = $this->createCalendar();
1930     if( $utf8Encode )
1931       $output = utf8_encode( $output );
1932     if( $gzip ) {
1933       $output = gzencode( $output, 9 );
1934       header( 'Content-Encoding: gzip' );
1935       header( 'Vary: *' );
1936       header( 'Content-Length: '.strlen( $output ));
1937     }
1938     if( 'xcal' == $this->format )
1939       header( 'Content-Type: application/calendar+xml; charset=utf-8' );
1940     else
1941       header( 'Content-Type: text/calendar; charset=utf-8' );
1942     header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
1943     header( 'Cache-Control: max-age=10' );
1944     die( $output );
1945   }
1946 /**
1947  * save content in a file
1948  *
1949  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1950  * @since 2.2.12 - 2007-12-30
1951  * @param string $directory optional
1952  * @param string $filename optional
1953  * @param string $delimiter optional
1954  * @return bool
1955  */
1956   function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
1957     if( $directory )
1958       $this->setConfig( 'directory', $directory );
1959     if( $filename )
1960       $this->setConfig( 'filename',  $filename );
1961     if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
1962       $this->setConfig( 'delimiter', $delimiter );
1963     if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
1964       $dirfile = $this->getConfig( 'dirfile' );
1965     $iCalFile = @fopen( $dirfile, 'w' );
1966     if( $iCalFile ) {
1967       if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
1968         return FALSE;
1969       fclose( $iCalFile );
1970       return TRUE;
1971     }
1972     else
1973       return FALSE;
1974   }
1975 /**
1976  * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
1977  * else FALSE is returned
1978  *
1979  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1980  * @since 2.2.12 - 2007-10-28
1981  * @param string $directory optional alt. int timeout
1982  * @param string $filename optional
1983  * @param string $delimiter optional
1984  * @param int timeout optional, default 3600 sec
1985  * @return redirect/FALSE
1986  */
1987   function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
1988     if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
1989       $timeout   = (int) $directory;
1990       $directory = FALSE;
1991     }
1992     if( $directory )
1993       $this->setConfig( 'directory', $directory );
1994     if( $filename )
1995       $this->setConfig( 'filename',  $filename );
1996     if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
1997       $this->setConfig( 'delimiter', $delimiter );
1998     $filesize    = $this->getConfig( 'filesize' );
1999     if( 0 >= $filesize )
2000       return FALSE;
2001     $dirfile     = $this->getConfig( 'dirfile' );
2002     if( time() - filemtime( $dirfile ) < $timeout) {
2003       clearstatcache();
2004       $dirfile   = $this->getConfig( 'dirfile' );
2005       $filename  = $this->getConfig( 'filename' );
2006 //    if( headers_sent( $filename, $linenum ))
2007 //      die( "Headers already sent in $filename on line $linenum\n" );
2008       if( 'xcal' == $this->format )
2009         header( 'Content-Type: application/calendar+xml; charset=utf-8' );
2010       else
2011         header( 'Content-Type: text/calendar; charset=utf-8' );
2012       header( 'Content-Length: '.$filesize );
2013       header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
2014       header( 'Cache-Control: max-age=10' );
2015       $fp = @fopen( $dirfile, 'r' );
2016       if( $fp ) {
2017         fpassthru( $fp );
2018         fclose( $fp );
2019       }
2020       die();
2021     }
2022     else
2023       return FALSE;
2024   }
2025 }
2026 /*********************************************************************************/
2027 /*********************************************************************************/
2028 /**
2029  *  abstract class for calendar components
2030  *
2031  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2032  * @since 2.9.6 - 2011-05-14
2033  */
2034 class calendarComponent {
2035             //  component property variables
2036   var $uid;
2037   var $dtstamp;
2038
2039             //  component config variables
2040   var $allowEmpty;
2041   var $language;
2042   var $nl;
2043   var $unique_id;
2044   var $format;
2045   var $objName; // created automatically at instance creation
2046   var $dtzid;   // default (local) timezone
2047             //  component internal variables
2048   var $componentStart1;
2049   var $componentStart2;
2050   var $componentEnd1;
2051   var $componentEnd2;
2052   var $elementStart1;
2053   var $elementStart2;
2054   var $elementEnd1;
2055   var $elementEnd2;
2056   var $intAttrDelimiter;
2057   var $attributeDelimiter;
2058   var $valueInit;
2059             //  component xCal declaration container
2060   var $xcaldecl;
2061 /**
2062  * constructor for calendar component object
2063  *
2064  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2065  * @since 2.9.6 - 2011-05-17
2066  */
2067   function calendarComponent() {
2068     $this->objName         = ( isset( $this->timezonetype )) ?
2069                           strtolower( $this->timezonetype )  :  get_class ( $this );
2070     $this->uid             = array();
2071     $this->dtstamp         = array();
2072
2073     $this->language        = null;
2074     $this->nl              = null;
2075     $this->unique_id       = null;
2076     $this->format          = null;
2077     $this->dtzid           = null;
2078     $this->allowEmpty      = TRUE;
2079     $this->xcaldecl        = array();
2080
2081     $this->_createFormat();
2082     $this->_makeDtstamp();
2083   }
2084 /*********************************************************************************/
2085 /**
2086  * Property Name: ACTION
2087  */
2088 /**
2089  * creates formatted output for calendar component property action
2090  *
2091  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2092  * @since 2.4.8 - 2008-10-22
2093  * @return string
2094  */
2095   function createAction() {
2096     if( empty( $this->action )) return FALSE;
2097     if( empty( $this->action['value'] ))
2098       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
2099     $attributes = $this->_createParams( $this->action['params'] );
2100     return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
2101   }
2102 /**
2103  * set calendar component property action
2104  *
2105  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2106  * @since 2.4.8 - 2008-11-04
2107  * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
2108  * @param mixed $params
2109  * @return bool
2110  */
2111   function setAction( $value, $params=FALSE ) {
2112     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2113     $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
2114     return TRUE;
2115   }
2116 /*********************************************************************************/
2117 /**
2118  * Property Name: ATTACH
2119  */
2120 /**
2121  * creates formatted output for calendar component property attach
2122  *
2123  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2124  * @since 2.11.16 - 2012-02-04
2125  * @return string
2126  */
2127   function createAttach() {
2128     if( empty( $this->attach )) return FALSE;
2129     $output       = null;
2130     foreach( $this->attach as $attachPart ) {
2131       if( !empty( $attachPart['value'] )) {
2132         $attributes = $this->_createParams( $attachPart['params'] );
2133         if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
2134           $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
2135           $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
2136           $output     = substr( $str, 0, 75 ).$this->nl;
2137           $str        = substr( $str, 75 );
2138           $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
2139           if( ' ' == substr( $output, -1 ))
2140             $output   = rtrim( $output );
2141           if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
2142             $output  .= $this->nl;
2143           return $output;
2144         }
2145         $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
2146       }
2147       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
2148     }
2149     return $output;
2150   }
2151 /**
2152  * set calendar component property attach
2153  *
2154  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2155  * @since 2.5.1 - 2008-11-06
2156  * @param string $value
2157  * @param array $params, optional
2158  * @param integer $index, optional
2159  * @return bool
2160  */
2161   function setAttach( $value, $params=FALSE, $index=FALSE ) {
2162     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2163     iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
2164     return TRUE;
2165   }
2166 /*********************************************************************************/
2167 /**
2168  * Property Name: ATTENDEE
2169  */
2170 /**
2171  * creates formatted output for calendar component property attendee
2172  *
2173  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2174  * @since 2.11.12 - 2012-01-31
2175  * @return string
2176  */
2177   function createAttendee() {
2178     if( empty( $this->attendee )) return FALSE;
2179     $output = null;
2180     foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
2181       if( empty( $attendeePart['value'] )) {
2182         if( $this->getConfig( 'allowEmpty' ))
2183           $output .= $this->_createElement( 'ATTENDEE' );
2184         continue;
2185       }
2186       $attendee1 = $attendee2 = null;
2187       foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
2188         if( 'value' == $paramlabel )
2189           $attendee2     .= $paramvalue;
2190         elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
2191           $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
2192           foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
2193             if( is_array( $pValue ) || in_array( $pKey, $mParams ))
2194               continue;
2195             if(( FALSE !== strpos( $pValue, ':' )) ||
2196                ( FALSE !== strpos( $pValue, ';' )) ||
2197                ( FALSE !== strpos( $pValue, ',' )))
2198               $paramvalue[$pKey] = '"'.$pValue.'"';
2199           }
2200         // set attenddee parameters in rfc2445 order
2201           if( isset( $paramvalue['CUTYPE'] ))
2202             $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
2203           if( isset( $paramvalue['MEMBER'] )) {
2204             $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
2205             foreach( $paramvalue['MEMBER'] as $cix => $opv )
2206               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2207           }
2208           if( isset( $paramvalue['ROLE'] ))
2209             $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
2210           if( isset( $paramvalue['PARTSTAT'] ))
2211             $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
2212           if( isset( $paramvalue['RSVP'] ))
2213             $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
2214           if( isset( $paramvalue['DELEGATED-TO'] )) {
2215             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
2216             foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
2217               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2218           }
2219           if( isset( $paramvalue['DELEGATED-FROM'] )) {
2220             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
2221             foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
2222               $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2223           }
2224           if( isset( $paramvalue['SENT-BY'] ))
2225             $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
2226           if( isset( $paramvalue['CN'] ))
2227             $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
2228           if( isset( $paramvalue['DIR'] )) {
2229             $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
2230             $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
2231           }
2232           if( isset( $paramvalue['LANGUAGE'] ))
2233             $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
2234           $xparams = array();
2235           foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
2236             if( ctype_digit( (string) $optparamlabel )) {
2237               $xparams[]  = $optparamvalue;
2238               continue;
2239             }
2240             if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
2241               $xparams[$optparamlabel] = $optparamvalue;
2242           } // end foreach 3
2243           ksort( $xparams, SORT_STRING );
2244           foreach( $xparams as $paramKey => $paramValue ) {
2245             if( ctype_digit( (string) $paramKey ))
2246               $attendee1 .= $this->intAttrDelimiter.$paramValue;
2247             else
2248               $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
2249           }      // end foreach 3
2250         }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
2251       }          // end foreach 2
2252       $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
2253     }              // end foreach 1
2254     return $output;
2255   }
2256 /**
2257  * set calendar component property attach
2258  *
2259  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2260  * @since 2.11.17 - 2012-02-03
2261  * @param string $value
2262  * @param array $params, optional
2263  * @param integer $index, optional
2264  * @return bool
2265  */
2266   function setAttendee( $value, $params=FALSE, $index=FALSE ) {
2267     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2268           // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
2269     if( FALSE !== ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
2270       $value = strtoupper( substr( $value, 0, $pos )).substr( $value, $pos );
2271     elseif( !empty( $value ))
2272       $value = 'MAILTO:'.$value;
2273     $params2 = array();
2274     if( is_array($params )) {
2275       $optarrays = array();
2276       foreach( $params as $optparamlabel => $optparamvalue ) {
2277         $optparamlabel = strtoupper( $optparamlabel );
2278         switch( $optparamlabel ) {
2279           case 'MEMBER':
2280           case 'DELEGATED-TO':
2281           case 'DELEGATED-FROM':
2282             if( !is_array( $optparamvalue ))
2283               $optparamvalue = array( $optparamvalue );
2284             foreach( $optparamvalue as $part ) {
2285               $part = trim( $part );
2286               if(( '"' == substr( $part, 0, 1 )) &&
2287                  ( '"' == substr( $part, -1 )))
2288                 $part = substr( $part, 1, ( strlen( $part ) - 2 ));
2289               if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
2290                 $part = "MAILTO:$part";
2291               else
2292                 $part = 'MAILTO:'.substr( $part, 7 );
2293               $optarrays[$optparamlabel][] = $part;
2294             }
2295             break;
2296           default:
2297             if(( '"' == substr( $optparamvalue, 0, 1 )) &&
2298                ( '"' == substr( $optparamvalue, -1 )))
2299               $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
2300             if( 'SENT-BY' ==  $optparamlabel ) {
2301               if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
2302                 $optparamvalue = "MAILTO:$optparamvalue";
2303               else
2304                 $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
2305             }
2306             $params2[$optparamlabel] = $optparamvalue;
2307             break;
2308         } // end switch( $optparamlabel.. .
2309       } // end foreach( $optparam.. .
2310       foreach( $optarrays as $optparamlabel => $optparams )
2311         $params2[$optparamlabel] = $optparams;
2312     }
2313         // remove defaults
2314     iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
2315     iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
2316     iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
2317     iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
2318         // check language setting
2319     if( isset( $params2['CN' ] )) {
2320       $lang = $this->getConfig( 'language' );
2321       if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
2322         $params2['LANGUAGE' ] = $lang;
2323     }
2324     iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
2325     return TRUE;
2326   }
2327 /*********************************************************************************/
2328 /**
2329  * Property Name: CATEGORIES
2330  */
2331 /**
2332  * creates formatted output for calendar component property categories
2333  *
2334  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2335  * @since 2.4.8 - 2008-10-22
2336  * @return string
2337  */
2338   function createCategories() {
2339     if( empty( $this->categories )) return FALSE;
2340     $output = null;
2341     foreach( $this->categories as $category ) {
2342       if( empty( $category['value'] )) {
2343         if ( $this->getConfig( 'allowEmpty' ))
2344           $output .= $this->_createElement( 'CATEGORIES' );
2345         continue;
2346       }
2347       $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
2348       if( is_array( $category['value'] )) {
2349         foreach( $category['value'] as $cix => $categoryPart )
2350           $category['value'][$cix] = $this->_strrep( $categoryPart );
2351         $content  = implode( ',', $category['value'] );
2352       }
2353       else
2354         $content  = $this->_strrep( $category['value'] );
2355       $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
2356     }
2357     return $output;
2358   }
2359 /**
2360  * set calendar component property categories
2361  *
2362  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2363  * @since 2.5.1 - 2008-11-06
2364  * @param mixed $value
2365  * @param array $params, optional
2366  * @param integer $index, optional
2367  * @return bool
2368  */
2369   function setCategories( $value, $params=FALSE, $index=FALSE ) {
2370     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2371     iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
2372     return TRUE;
2373  }
2374 /*********************************************************************************/
2375 /**
2376  * Property Name: CLASS
2377  */
2378 /**
2379  * creates formatted output for calendar component property class
2380  *
2381  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2382  * @since 0.9.7 - 2006-11-20
2383  * @return string
2384  */
2385   function createClass() {
2386     if( empty( $this->class )) return FALSE;
2387     if( empty( $this->class['value'] ))
2388       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
2389     $attributes = $this->_createParams( $this->class['params'] );
2390     return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
2391   }
2392 /**
2393  * set calendar component property class
2394  *
2395  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2396  * @since 2.4.8 - 2008-11-04
2397  * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
2398  * @param array $params optional
2399  * @return bool
2400  */
2401   function setClass( $value, $params=FALSE ) {
2402     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2403     $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
2404     return TRUE;
2405   }
2406 /*********************************************************************************/
2407 /**
2408  * Property Name: COMMENT
2409  */
2410 /**
2411  * creates formatted output for calendar component property comment
2412  *
2413  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2414  * @since 2.4.8 - 2008-10-22
2415  * @return string
2416  */
2417   function createComment() {
2418     if( empty( $this->comment )) return FALSE;
2419     $output = null;
2420     foreach( $this->comment as $commentPart ) {
2421       if( empty( $commentPart['value'] )) {
2422         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
2423         continue;
2424       }
2425       $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
2426       $content    = $this->_strrep( $commentPart['value'] );
2427       $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
2428     }
2429     return $output;
2430   }
2431 /**
2432  * set calendar component property comment
2433  *
2434  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2435  * @since 2.5.1 - 2008-11-06
2436  * @param string $value
2437  * @param array $params, optional
2438  * @param integer $index, optional
2439  * @return bool
2440  */
2441   function setComment( $value, $params=FALSE, $index=FALSE ) {
2442     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2443     iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
2444     return TRUE;
2445   }
2446 /*********************************************************************************/
2447 /**
2448  * Property Name: COMPLETED
2449  */
2450 /**
2451  * creates formatted output for calendar component property completed
2452  *
2453  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2454  * @since 2.4.8 - 2008-10-22
2455  * @return string
2456  */
2457   function createCompleted( ) {
2458     if( empty( $this->completed )) return FALSE;
2459     if( !isset( $this->completed['value']['year'] )  &&
2460         !isset( $this->completed['value']['month'] ) &&
2461         !isset( $this->completed['value']['day'] )   &&
2462         !isset( $this->completed['value']['hour'] )  &&
2463         !isset( $this->completed['value']['min'] )   &&
2464         !isset( $this->completed['value']['sec'] ))
2465       if( $this->getConfig( 'allowEmpty' ))
2466         return $this->_createElement( 'COMPLETED' );
2467       else return FALSE;
2468     $formatted  = iCalUtilityFunctions::_format_date_time( $this->completed['value'], 7 );
2469     $attributes = $this->_createParams( $this->completed['params'] );
2470     return $this->_createElement( 'COMPLETED', $attributes, $formatted );
2471   }
2472 /**
2473  * set calendar component property completed
2474  *
2475  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2476  * @since 2.4.8 - 2008-10-23
2477  * @param mixed $year
2478  * @param mixed $month optional
2479  * @param int $day optional
2480  * @param int $hour optional
2481  * @param int $min optional
2482  * @param int $sec optional
2483  * @param array $params optional
2484  * @return bool
2485  */
2486   function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2487     if( empty( $year )) {
2488       if( $this->getConfig( 'allowEmpty' )) {
2489         $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2490         return TRUE;
2491       }
2492       else
2493         return FALSE;
2494     }
2495     $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2496     return TRUE;
2497   }
2498 /*********************************************************************************/
2499 /**
2500  * Property Name: CONTACT
2501  */
2502 /**
2503  * creates formatted output for calendar component property contact
2504  *
2505  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2506  * @since 2.4.8 - 2008-10-23
2507  * @return string
2508  */
2509   function createContact() {
2510     if( empty( $this->contact )) return FALSE;
2511     $output = null;
2512     foreach( $this->contact as $contact ) {
2513       if( !empty( $contact['value'] )) {
2514         $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
2515         $content    = $this->_strrep( $contact['value'] );
2516         $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
2517       }
2518       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
2519     }
2520     return $output;
2521   }
2522 /**
2523  * set calendar component property contact
2524  *
2525  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2526  * @since 2.5.1 - 2008-11-05
2527  * @param string $value
2528  * @param array $params, optional
2529  * @param integer $index, optional
2530  * @return bool
2531  */
2532   function setContact( $value, $params=FALSE, $index=FALSE ) {
2533     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2534     iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
2535     return TRUE;
2536   }
2537 /*********************************************************************************/
2538 /**
2539  * Property Name: CREATED
2540  */
2541 /**
2542  * creates formatted output for calendar component property created
2543  *
2544  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2545  * @since 2.4.8 - 2008-10-21
2546  * @return string
2547  */
2548   function createCreated() {
2549     if( empty( $this->created )) return FALSE;
2550     $formatted  = iCalUtilityFunctions::_format_date_time( $this->created['value'], 7 );
2551     $attributes = $this->_createParams( $this->created['params'] );
2552     return $this->_createElement( 'CREATED', $attributes, $formatted );
2553   }
2554 /**
2555  * set calendar component property created
2556  *
2557  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2558  * @since 2.4.8 - 2008-10-23
2559  * @param mixed $year optional
2560  * @param mixed $month optional
2561  * @param int $day optional
2562  * @param int $hour optional
2563  * @param int $min optional
2564  * @param int $sec optional
2565  * @param mixed $params optional
2566  * @return bool
2567  */
2568   function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2569     if( !isset( $year )) {
2570       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
2571     }
2572     $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2573     return TRUE;
2574   }
2575 /*********************************************************************************/
2576 /**
2577  * Property Name: DESCRIPTION
2578  */
2579 /**
2580  * creates formatted output for calendar component property description
2581  *
2582  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2583  * @since 2.4.8 - 2008-10-22
2584  * @return string
2585  */
2586   function createDescription() {
2587     if( empty( $this->description )) return FALSE;
2588     $output       = null;
2589     foreach( $this->description as $description ) {
2590       if( !empty( $description['value'] )) {
2591         $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
2592         $content    = $this->_strrep( $description['value'] );
2593         $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
2594       }
2595       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
2596     }
2597     return $output;
2598   }
2599 /**
2600  * set calendar component property description
2601  *
2602  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2603  * @since 2.6.24 - 2010-11-06
2604  * @param string $value
2605  * @param array $params, optional
2606  * @param integer $index, optional
2607  * @return bool
2608  */
2609   function setDescription( $value, $params=FALSE, $index=FALSE ) {
2610     if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
2611     if( 'vjournal' != $this->objName )
2612       $index = 1;
2613     iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
2614     return TRUE;
2615   }
2616 /*********************************************************************************/
2617 /**
2618  * Property Name: DTEND
2619  */
2620 /**
2621  * creates formatted output for calendar component property dtend
2622  *
2623  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2624  * @since 2.9.6 - 2011-05-14
2625  * @return string
2626  */
2627   function createDtend() {
2628     if( empty( $this->dtend )) return FALSE;
2629     if( !isset( $this->dtend['value']['year'] )  &&
2630         !isset( $this->dtend['value']['month'] ) &&
2631         !isset( $this->dtend['value']['day'] )   &&
2632         !isset( $this->dtend['value']['hour'] )  &&
2633         !isset( $this->dtend['value']['min'] )   &&
2634         !isset( $this->dtend['value']['sec'] ))
2635       if( $this->getConfig( 'allowEmpty' ))
2636         return $this->_createElement( 'DTEND' );
2637       else return FALSE;
2638     $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtend['value'] );
2639     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
2640        ( !isset( $this->dtend['params']['VALUE'] )        || ( $this->dtend['params']['VALUE'] != 'DATE' )) &&
2641          !isset( $this->dtend['params']['TZID'] ))
2642       $this->dtend['params']['TZID'] = $tzid;
2643     $attributes = $this->_createParams( $this->dtend['params'] );
2644     return $this->_createElement( 'DTEND', $attributes, $formatted );
2645   }
2646 /**
2647  * set calendar component property dtend
2648  *
2649  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2650  * @since 2.9.6 - 2011-05-14
2651  * @param mixed $year
2652  * @param mixed $month optional
2653  * @param int $day optional
2654  * @param int $hour optional
2655  * @param int $min optional
2656  * @param int $sec optional
2657  * @param string $tz optional
2658  * @param array $params optional
2659  * @return bool
2660  */
2661   function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2662     if( empty( $year )) {
2663       if( $this->getConfig( 'allowEmpty' )) {
2664         $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2665         return TRUE;
2666       }
2667       else
2668         return FALSE;
2669     }
2670     $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
2671     return TRUE;
2672   }
2673 /*********************************************************************************/
2674 /**
2675  * Property Name: DTSTAMP
2676  */
2677 /**
2678  * creates formatted output for calendar component property dtstamp
2679  *
2680  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2681  * @since 2.4.4 - 2008-03-07
2682  * @return string
2683  */
2684   function createDtstamp() {
2685     if( !isset( $this->dtstamp['value']['year'] )  &&
2686         !isset( $this->dtstamp['value']['month'] ) &&
2687         !isset( $this->dtstamp['value']['day'] )   &&
2688         !isset( $this->dtstamp['value']['hour'] )  &&
2689         !isset( $this->dtstamp['value']['min'] )   &&
2690         !isset( $this->dtstamp['value']['sec'] ))
2691       $this->_makeDtstamp();
2692     $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtstamp['value'], 7 );
2693     $attributes = $this->_createParams( $this->dtstamp['params'] );
2694     return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
2695   }
2696 /**
2697  * computes datestamp for calendar component object instance dtstamp
2698  *
2699  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2700  * @since 2.10.9 - 2011-08-10
2701  * @return void
2702  */
2703   function _makeDtstamp() {
2704     $d = mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y'));
2705     $this->dtstamp['value'] = array( 'year'  => date( 'Y', $d )
2706                                    , 'month' => date( 'm', $d )
2707                                    , 'day'   => date( 'd', $d )
2708                                    , 'hour'  => date( 'H', $d )
2709                                    , 'min'   => date( 'i', $d )
2710                                    , 'sec'   => date( 's', $d ));
2711     $this->dtstamp['params'] = null;
2712   }
2713 /**
2714  * set calendar component property dtstamp
2715  *
2716  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2717  * @since 2.4.8 - 2008-10-23
2718  * @param mixed $year
2719  * @param mixed $month optional
2720  * @param int $day optional
2721  * @param int $hour optional
2722  * @param int $min optional
2723  * @param int $sec optional
2724  * @param array $params optional
2725  * @return TRUE
2726  */
2727   function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2728     if( empty( $year ))
2729       $this->_makeDtstamp();
2730     else
2731       $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2732     return TRUE;
2733   }
2734 /*********************************************************************************/
2735 /**
2736  * Property Name: DTSTART
2737  */
2738 /**
2739  * creates formatted output for calendar component property dtstart
2740  *
2741  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2742  * @since 2.9.6 - 2011-05-15
2743  * @return string
2744  */
2745   function createDtstart() {
2746     if( empty( $this->dtstart )) return FALSE;
2747     if( !isset( $this->dtstart['value']['year'] )  &&
2748         !isset( $this->dtstart['value']['month'] ) &&
2749         !isset( $this->dtstart['value']['day'] )   &&
2750         !isset( $this->dtstart['value']['hour'] )  &&
2751         !isset( $this->dtstart['value']['min'] )   &&
2752         !isset( $this->dtstart['value']['sec'] )) {
2753       if( $this->getConfig( 'allowEmpty' ))
2754         return $this->_createElement( 'DTSTART' );
2755       else return FALSE;
2756     }
2757     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
2758        unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
2759     elseif(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
2760        ( !isset( $this->dtstart['params']['VALUE'] ) || ( $this->dtstart['params']['VALUE'] != 'DATE' ))  &&
2761          !isset( $this->dtstart['params']['TZID'] ))
2762       $this->dtstart['params']['TZID'] = $tzid;
2763     $formatted  = iCalUtilityFunctions::_format_date_time( $this->dtstart['value'] );
2764     $attributes = $this->_createParams( $this->dtstart['params'] );
2765     return $this->_createElement( 'DTSTART', $attributes, $formatted );
2766   }
2767 /**
2768  * set calendar component property dtstart
2769  *
2770  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2771  * @since 2.6.22 - 2010-09-22
2772  * @param mixed $year
2773  * @param mixed $month optional
2774  * @param int $day optional
2775  * @param int $hour optional
2776  * @param int $min optional
2777  * @param int $sec optional
2778  * @param string $tz optional
2779  * @param array $params optional
2780  * @return bool
2781  */
2782   function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2783     if( empty( $year )) {
2784       if( $this->getConfig( 'allowEmpty' )) {
2785         $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2786         return TRUE;
2787       }
2788       else
2789         return FALSE;
2790     }
2791     $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
2792     return TRUE;
2793   }
2794 /*********************************************************************************/
2795 /**
2796  * Property Name: DUE
2797  */
2798 /**
2799  * creates formatted output for calendar component property due
2800  *
2801  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2802  * @since 2.4.8 - 2008-10-22
2803  * @return string
2804  */
2805   function createDue() {
2806     if( empty( $this->due )) return FALSE;
2807     if( !isset( $this->due['value']['year'] )  &&
2808         !isset( $this->due['value']['month'] ) &&
2809         !isset( $this->due['value']['day'] )   &&
2810         !isset( $this->due['value']['hour'] )  &&
2811         !isset( $this->due['value']['min'] )   &&
2812         !isset( $this->due['value']['sec'] )) {
2813       if( $this->getConfig( 'allowEmpty' ))
2814         return $this->_createElement( 'DUE' );
2815       else
2816        return FALSE;
2817     }
2818     $formatted  = iCalUtilityFunctions::_format_date_time( $this->due['value'] );
2819     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
2820        ( !isset( $this->due['params']['VALUE'] ) || ( $this->due['params']['VALUE'] != 'DATE' ))  &&
2821          !isset( $this->due['params']['TZID'] ))
2822       $this->due['params']['TZID'] = $tzid;
2823     $attributes = $this->_createParams( $this->due['params'] );
2824     return $this->_createElement( 'DUE', $attributes, $formatted );
2825   }
2826 /**
2827  * set calendar component property due
2828  *
2829  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2830  * @since 2.4.8 - 2008-11-04
2831  * @param mixed $year
2832  * @param mixed $month optional
2833  * @param int $day optional
2834  * @param int $hour optional
2835  * @param int $min optional
2836  * @param int $sec optional
2837  * @param array $params optional
2838  * @return bool
2839  */
2840   function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2841     if( empty( $year )) {
2842       if( $this->getConfig( 'allowEmpty' )) {
2843         $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ));
2844         return TRUE;
2845       }
2846       else
2847         return FALSE;
2848     }
2849     $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
2850     return TRUE;
2851   }
2852 /*********************************************************************************/
2853 /**
2854  * Property Name: DURATION
2855  */
2856 /**
2857  * creates formatted output for calendar component property duration
2858  *
2859  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2860  * @since 2.4.8 - 2008-10-21
2861  * @return string
2862  */
2863   function createDuration() {
2864     if( empty( $this->duration )) return FALSE;
2865     if( !isset( $this->duration['value']['week'] ) &&
2866         !isset( $this->duration['value']['day'] )  &&
2867         !isset( $this->duration['value']['hour'] ) &&
2868         !isset( $this->duration['value']['min'] )  &&
2869         !isset( $this->duration['value']['sec'] ))
2870       if( $this->getConfig( 'allowEmpty' ))
2871         return $this->_createElement( 'DURATION', array(), null );
2872       else return FALSE;
2873     $attributes = $this->_createParams( $this->duration['params'] );
2874     return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_format_duration( $this->duration['value'] ));
2875   }
2876 /**
2877  * set calendar component property duration
2878  *
2879  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2880  * @since 2.4.8 - 2008-11-04
2881  * @param mixed $week
2882  * @param mixed $day optional
2883  * @param int $hour optional
2884  * @param int $min optional
2885  * @param int $sec optional
2886  * @param array $params optional
2887  * @return bool
2888  */
2889   function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2890     if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
2891     if( is_array( $week ) && ( 1 <= count( $week )))
2892       $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
2893     elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
2894       $week = trim( $week );
2895       if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
2896         $week = substr( $week, 1 );
2897       $this->duration = array( 'value' => iCalUtilityFunctions::_duration_string( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
2898     }
2899     elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
2900       return FALSE;
2901     else
2902       $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params ));
2903     return TRUE;
2904   }
2905 /*********************************************************************************/
2906 /**
2907  * Property Name: EXDATE
2908  */
2909 /**
2910  * creates formatted output for calendar component property exdate
2911  *
2912  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2913  * @since 2.4.8 - 2008-10-22
2914  * @return string
2915  */
2916   function createExdate() {
2917     if( empty( $this->exdate )) return FALSE;
2918     $output = null;
2919     foreach( $this->exdate as $ex => $theExdate ) {
2920       if( empty( $theExdate['value'] )) {
2921         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'EXDATE' );
2922         continue;
2923       }
2924       $content = $attributes = null;
2925       foreach( $theExdate['value'] as $eix => $exdatePart ) {
2926         $parno = count( $exdatePart );
2927         $formatted = iCalUtilityFunctions::_format_date_time( $exdatePart, $parno );
2928         if( isset( $theExdate['params']['TZID'] ))
2929           $formatted = str_replace( 'Z', '', $formatted);
2930         if( 0 < $eix ) {
2931           if( isset( $theExdate['value'][0]['tz'] )) {
2932             if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
2933                ( 'Z' == $theExdate['value'][0]['tz'] )) {
2934               if( 'Z' != substr( $formatted, -1 ))
2935                 $formatted .= 'Z';
2936             }
2937             else
2938               $formatted = str_replace( 'Z', '', $formatted );
2939           }
2940           else
2941             $formatted = str_replace( 'Z', '', $formatted );
2942         }
2943         $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
2944       }
2945       $attributes .= $this->_createParams( $theExdate['params'] );
2946       $output .= $this->_createElement( 'EXDATE', $attributes, $content );
2947     }
2948     return $output;
2949   }
2950 /**
2951  * set calendar component property exdate
2952  *
2953  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2954  * @since 2.11.8 - 2012-01-19
2955  * @param array exdates
2956  * @param array $params, optional
2957  * @param integer $index, optional
2958  * @return bool
2959  */
2960   function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
2961     if( empty( $exdates )) {
2962       if( $this->getConfig( 'allowEmpty' )) {
2963         iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
2964         return TRUE;
2965       }
2966       else
2967         return FALSE;
2968     }
2969     $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
2970     $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
2971             /* ev. check 1:st date and save ev. timezone **/
2972     iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
2973     iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
2974     foreach( $exdates as $eix => $theExdate ) {
2975       iCalUtilityFunctions::_strDate2arr( $theExdate );
2976       if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate ))
2977         $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
2978       elseif(  is_array( $theExdate ))
2979         $exdatea = iCalUtilityFunctions::_date_time_array( $theExdate, $parno );
2980       elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
2981         $exdatea = iCalUtilityFunctions::_date_time_string( $theExdate, $parno );
2982         unset( $exdatea['unparsedtext'] );
2983       }
2984       if( 3 == $parno )
2985         unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
2986       elseif( isset( $exdatea['tz'] ))
2987         $exdatea['tz'] = (string) $exdatea['tz'];
2988       if(  isset( $input['params']['TZID'] ) ||
2989          ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
2990          ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
2991          ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
2992         unset( $exdatea['tz'] );
2993       if( $toZ ) // time zone Z
2994         $exdatea['tz'] = 'Z';
2995       $input['value'][] = $exdatea;
2996     }
2997     if( 0 >= count( $input['value'] ))
2998       return FALSE;
2999     if( 3 == $parno ) {
3000       $input['params']['VALUE'] = 'DATE';
3001       unset( $input['params']['TZID'] );
3002     }
3003     if( $toZ ) // time zone Z
3004       unset( $input['params']['TZID'] );
3005     iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
3006     return TRUE;
3007   }
3008 /*********************************************************************************/
3009 /**
3010  * Property Name: EXRULE
3011  */
3012 /**
3013  * creates formatted output for calendar component property exrule
3014  *
3015  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3016  * @since 2.4.8 - 2008-10-22
3017  * @return string
3018  */
3019   function createExrule() {
3020     if( empty( $this->exrule )) return FALSE;
3021     return $this->_format_recur( 'EXRULE', $this->exrule );
3022   }
3023 /**
3024  * set calendar component property exdate
3025  *
3026  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3027  * @since 2.5.1 - 2008-11-05
3028  * @param array $exruleset
3029  * @param array $params, optional
3030  * @param integer $index, optional
3031  * @return bool
3032  */
3033   function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
3034     if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
3035     iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
3036     return TRUE;
3037   }
3038 /*********************************************************************************/
3039 /**
3040  * Property Name: FREEBUSY
3041  */
3042 /**
3043  * creates formatted output for calendar component property freebusy
3044  *
3045  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3046  * @since 2.1.23 - 2012-02-16
3047  * @return string
3048  */
3049   function createFreebusy() {
3050     if( empty( $this->freebusy )) return FALSE;
3051     $output = null;
3052     foreach( $this->freebusy as $freebusyPart ) {
3053       if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
3054         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
3055         continue;
3056       }
3057       $attributes = $content = null;
3058       if( isset( $freebusyPart['value']['fbtype'] )) {
3059           $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
3060         unset( $freebusyPart['value']['fbtype'] );
3061         $freebusyPart['value'] = array_values( $freebusyPart['value'] );
3062       }
3063       else
3064         $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
3065       $attributes .= $this->_createParams( $freebusyPart['params'] );
3066       $fno = 1;
3067       $cnt = count( $freebusyPart['value']);
3068       foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
3069         $formatted   = iCalUtilityFunctions::_format_date_time( $freebusyPeriod[0] );
3070         $content .= $formatted;
3071         $content .= '/';
3072         $cnt2 = count( $freebusyPeriod[1]);
3073         if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
3074           $cnt2 = 7;
3075         elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
3076           $cnt2 = 5;
3077         if(( 7 == $cnt2 )   &&    // period=  -> date-time
3078             isset( $freebusyPeriod[1]['year'] )  &&
3079             isset( $freebusyPeriod[1]['month'] ) &&
3080             isset( $freebusyPeriod[1]['day'] )) {
3081           $content .= iCalUtilityFunctions::_format_date_time( $freebusyPeriod[1] );
3082         }
3083         else {                                  // period=  -> dur-time
3084           $content .= iCalUtilityFunctions::_format_duration( $freebusyPeriod[1] );
3085         }
3086         if( $fno < $cnt )
3087           $content .= ',';
3088         $fno++;
3089       }
3090       $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
3091     }
3092     return $output;
3093   }
3094 /**
3095  * set calendar component property freebusy
3096  *
3097  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3098  * @since 2.10.30 - 2012-01-16
3099  * @param string $fbType
3100  * @param array $fbValues
3101  * @param array $params, optional
3102  * @param integer $index, optional
3103  * @return bool
3104  */
3105   function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
3106     if( empty( $fbValues )) {
3107       if( $this->getConfig( 'allowEmpty' )) {
3108         iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
3109         return TRUE;
3110       }
3111       else
3112         return FALSE;
3113     }
3114     $fbType = strtoupper( $fbType );
3115     if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
3116        ( 'X-' != substr( $fbType, 0, 2 )))
3117       $fbType = 'BUSY';
3118     $input = array( 'fbtype' => $fbType );
3119     foreach( $fbValues as $fbPeriod ) {   // periods => period
3120       if( empty( $fbPeriod ))
3121         continue;
3122       $freebusyPeriod = array();
3123       foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
3124         $freebusyPairMember = array();
3125         if( is_array( $fbMember )) {
3126           if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
3127             $freebusyPairMember       = iCalUtilityFunctions::_date_time_array( $fbMember, 7 );
3128             $freebusyPairMember['tz'] = 'Z';
3129           }
3130           elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
3131             $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
3132             $freebusyPairMember['tz'] = 'Z';
3133           }
3134           else {                                         // array format duration
3135             $freebusyPairMember = iCalUtilityFunctions::_duration_array( $fbMember );
3136           }
3137         }
3138         elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
3139                ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
3140           if( 'P' != $fbMember{0} )
3141             $fbmember = substr( $fbMember, 1 );
3142           $freebusyPairMember = iCalUtilityFunctions::_duration_string( $fbMember );
3143         }
3144         elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
3145           $freebusyPairMember       = iCalUtilityFunctions::_date_time_string( $fbMember, 7 );
3146           unset( $freebusyPairMember['unparsedtext'] );
3147           $freebusyPairMember['tz'] = 'Z';
3148         }
3149         $freebusyPeriod[]   = $freebusyPairMember;
3150       }
3151       $input[]              = $freebusyPeriod;
3152     }
3153     iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
3154     return TRUE;
3155   }
3156 /*********************************************************************************/
3157 /**
3158  * Property Name: GEO
3159  */
3160 /**
3161  * creates formatted output for calendar component property geo
3162  *
3163  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3164  * @since 2.4.8 - 2008-10-21
3165  * @return string
3166  */
3167   function createGeo() {
3168     if( empty( $this->geo )) return FALSE;
3169     if( empty( $this->geo['value'] ))
3170       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
3171     $attributes = $this->_createParams( $this->geo['params'] );
3172     $content    = null;
3173     $content   .= number_format( (float) $this->geo['value']['latitude'], 6, '.', '');
3174     $content   .= ';';
3175     $content   .= number_format( (float) $this->geo['value']['longitude'], 6, '.', '');
3176     return $this->_createElement( 'GEO', $attributes, $content );
3177   }
3178 /**
3179  * set calendar component property geo
3180  *
3181  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3182  * @since 2.4.8 - 2008-11-04
3183  * @param float $latitude
3184  * @param float $longitude
3185  * @param array $params optional
3186  * @return bool
3187  */
3188   function setGeo( $latitude, $longitude, $params=FALSE ) {
3189     if( !empty( $latitude ) && !empty( $longitude )) {
3190       if( !is_array( $this->geo )) $this->geo = array();
3191       $this->geo['value']['latitude']  = $latitude;
3192       $this->geo['value']['longitude'] = $longitude;
3193       $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
3194     }
3195     elseif( $this->getConfig( 'allowEmpty' ))
3196       $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
3197     else
3198       return FALSE;
3199     return TRUE;
3200   }
3201 /*********************************************************************************/
3202 /**
3203  * Property Name: LAST-MODIFIED
3204  */
3205 /**
3206  * creates formatted output for calendar component property last-modified
3207  *
3208  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3209  * @since 2.4.8 - 2008-10-21
3210  * @return string
3211  */
3212   function createLastModified() {
3213     if( empty( $this->lastmodified )) return FALSE;
3214     $attributes = $this->_createParams( $this->lastmodified['params'] );
3215     $formatted  = iCalUtilityFunctions::_format_date_time( $this->lastmodified['value'], 7 );
3216     return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
3217   }
3218 /**
3219  * set calendar component property completed
3220  *
3221  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3222  * @since 2.4.8 - 2008-10-23
3223  * @param mixed $year optional
3224  * @param mixed $month optional
3225  * @param int $day optional
3226  * @param int $hour optional
3227  * @param int $min optional
3228  * @param int $sec optional
3229  * @param array $params optional
3230  * @return boll
3231  */
3232   function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
3233     if( empty( $year ))
3234       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
3235     $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
3236     return TRUE;
3237   }
3238 /*********************************************************************************/
3239 /**
3240  * Property Name: LOCATION
3241  */
3242 /**
3243  * creates formatted output for calendar component property location
3244  *
3245  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3246  * @since 2.4.8 - 2008-10-22
3247  * @return string
3248  */
3249   function createLocation() {
3250     if( empty( $this->location )) return FALSE;
3251     if( empty( $this->location['value'] ))
3252       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
3253     $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
3254     $content    = $this->_strrep( $this->location['value'] );
3255     return $this->_createElement( 'LOCATION', $attributes, $content );
3256   }
3257 /**
3258  * set calendar component property location
3259  '
3260  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3261  * @since 2.4.8 - 2008-11-04
3262  * @param string $value
3263  * @param array params optional
3264  * @return bool
3265  */
3266   function setLocation( $value, $params=FALSE ) {
3267     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3268     $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3269     return TRUE;
3270   }
3271 /*********************************************************************************/
3272 /**
3273  * Property Name: ORGANIZER
3274  */
3275 /**
3276  * creates formatted output for calendar component property organizer
3277  *
3278  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3279  * @since 2.6.33 - 2010-12-17
3280  * @return string
3281  */
3282   function createOrganizer() {
3283     if( empty( $this->organizer )) return FALSE;
3284     if( empty( $this->organizer['value'] ))
3285       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
3286     $attributes = $this->_createParams( $this->organizer['params']
3287                                       , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
3288     return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
3289   }
3290 /**
3291  * set calendar component property organizer
3292  *
3293  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3294  * @since 2.6.27 - 2010-11-29
3295  * @param string $value
3296  * @param array params optional
3297  * @return bool
3298  */
3299   function setOrganizer( $value, $params=FALSE ) {
3300     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3301     if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
3302       $value = 'MAILTO:'.$value;
3303     else
3304       $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
3305     $value = str_replace( 'mailto:', 'MAILTO:', $value );
3306     $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3307     if( isset( $this->organizer['params']['SENT-BY'] )){
3308       if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
3309         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
3310       else
3311         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
3312     }
3313     return TRUE;
3314   }
3315 /*********************************************************************************/
3316 /**
3317  * Property Name: PERCENT-COMPLETE
3318  */
3319 /**
3320  * creates formatted output for calendar component property percent-complete
3321  *
3322  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3323  * @since 2.9.3 - 2011-05-14
3324  * @return string
3325  */
3326   function createPercentComplete() {
3327     if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
3328     if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
3329       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
3330     $attributes = $this->_createParams( $this->percentcomplete['params'] );
3331     return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
3332   }
3333 /**
3334  * set calendar component property percent-complete
3335  *
3336  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3337  * @since 2.9.3 - 2011-05-14
3338  * @param int $value
3339  * @param array $params optional
3340  * @return bool
3341  */
3342   function setPercentComplete( $value, $params=FALSE ) {
3343     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3344     $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3345     return TRUE;
3346   }
3347 /*********************************************************************************/
3348 /**
3349  * Property Name: PRIORITY
3350  */
3351 /**
3352  * creates formatted output for calendar component property priority
3353  *
3354  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3355  * @since 2.9.3 - 2011-05-14
3356  * @return string
3357  */
3358   function createPriority() {
3359     if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
3360     if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
3361       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
3362     $attributes = $this->_createParams( $this->priority['params'] );
3363     return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
3364   }
3365 /**
3366  * set calendar component property priority
3367  *
3368  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3369  * @since 2.9.3 - 2011-05-14
3370  * @param int $value
3371  * @param array $params optional
3372  * @return bool
3373  */
3374   function setPriority( $value, $params=FALSE  ) {
3375     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3376     $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3377     return TRUE;
3378   }
3379 /*********************************************************************************/
3380 /**
3381  * Property Name: RDATE
3382  */
3383 /**
3384  * creates formatted output for calendar component property rdate
3385  *
3386  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3387  * @since 2.4.16 - 2008-10-26
3388  * @return string
3389  */
3390   function createRdate() {
3391     if( empty( $this->rdate )) return FALSE;
3392     $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
3393     $output = null;
3394     if( $utctime  )
3395       unset( $this->rdate['params']['TZID'] );
3396     foreach( $this->rdate as $theRdate ) {
3397       if( empty( $theRdate['value'] )) {
3398         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
3399         continue;
3400       }
3401       if( $utctime  )
3402         unset( $theRdate['params']['TZID'] );
3403       $attributes = $this->_createParams( $theRdate['params'] );
3404       $cnt = count( $theRdate['value'] );
3405       $content = null;
3406       $rno = 1;
3407       foreach( $theRdate['value'] as $rpix => $rdatePart ) {
3408         $contentPart = null;
3409         if( is_array( $rdatePart ) &&
3410             isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
3411           if( $utctime )
3412             unset( $rdatePart[0]['tz'] );
3413           $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[0]); // PERIOD part 1
3414           if( $utctime || !empty( $theRdate['params']['TZID'] ))
3415             $formatted = str_replace( 'Z', '', $formatted);
3416           if( 0 < $rpix ) {
3417             if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) {
3418               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
3419             }
3420             else
3421               $formatted = str_replace( 'Z', '', $formatted );
3422           }
3423           $contentPart .= $formatted;
3424           $contentPart .= '/';
3425           $cnt2 = count( $rdatePart[1]);
3426           if( array_key_exists( 'year', $rdatePart[1] )) {
3427             if( array_key_exists( 'hour', $rdatePart[1] ))
3428               $cnt2 = 7;                                      // date-time
3429             else
3430               $cnt2 = 3;                                      // date
3431           }
3432           elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
3433             $cnt2 = 5;
3434           if(( 7 == $cnt2 )   &&    // period=  -> date-time
3435               isset( $rdatePart[1]['year'] )  &&
3436               isset( $rdatePart[1]['month'] ) &&
3437               isset( $rdatePart[1]['day'] )) {
3438             if( $utctime )
3439               unset( $rdatePart[1]['tz'] );
3440             $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[1] ); // PERIOD part 2
3441             if( $utctime || !empty( $theRdate['params']['TZID'] ))
3442               $formatted = str_replace( 'Z', '', $formatted);
3443             if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) {
3444               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
3445             }
3446             else
3447               $formatted = str_replace( 'Z', '', $formatted );
3448            $contentPart .= $formatted;
3449           }
3450           else {                                  // period=  -> dur-time
3451             $contentPart .= iCalUtilityFunctions::_format_duration( $rdatePart[1] );
3452           }
3453         } // PERIOD end
3454         else { // SINGLE date start
3455           if( $utctime )
3456             unset( $rdatePart['tz'] );
3457           $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart);
3458           if( $utctime || !empty( $theRdate['params']['TZID'] ))
3459             $formatted = str_replace( 'Z', '', $formatted);
3460           if( !$utctime && ( 0 < $rpix )) {
3461             if( !empty( $theRdate['value'][0]['tz'] ) && iCalUtilityFunctions::_isOffset( $theRdate['value'][0]['tz'] )) {
3462               if( 'Z' != substr( $formatted, -1 ))
3463                 $formatted .= 'Z';
3464             }
3465             else
3466               $formatted = str_replace( 'Z', '', $formatted );
3467           }
3468           $contentPart .= $formatted;
3469         }
3470         $content .= $contentPart;
3471         if( $rno < $cnt )
3472           $content .= ',';
3473         $rno++;
3474       }
3475       $output    .= $this->_createElement( 'RDATE', $attributes, $content );
3476     }
3477     return $output;
3478   }
3479 /**
3480  * set calendar component property rdate
3481  *
3482  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3483  * @since 2.11.8 - 2012-01-31
3484  * @param array $rdates
3485  * @param array $params, optional
3486  * @param integer $index, optional
3487  * @return bool
3488  */
3489   function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
3490     if( empty( $rdates )) {
3491       if( $this->getConfig( 'allowEmpty' )) {
3492         iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
3493         return TRUE;
3494       }
3495       else
3496         return FALSE;
3497     }
3498     $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
3499     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
3500       unset( $input['params']['TZID'] );
3501       $input['params']['VALUE'] = 'DATE-TIME';
3502     }
3503     $zArr = array( 'GMT', 'UTC', 'Z' );
3504     $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
3505             /*  check if PERIOD, if not set */
3506     if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
3507           isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
3508           isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
3509     (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
3510                                       iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
3511                                     ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
3512      ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
3513       $input['params']['VALUE'] = 'PERIOD';
3514             /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
3515     $date  = reset( $rdates );
3516     if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
3517       $date  = reset( $date );
3518     iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
3519     iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
3520     foreach( $rdates as $rpix => $theRdate ) {
3521       $inputa = null;
3522       iCalUtilityFunctions::_strDate2arr( $theRdate );
3523       if( is_array( $theRdate )) {
3524         if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
3525           foreach( $theRdate as $rix => $rPeriod ) {
3526             iCalUtilityFunctions::_strDate2arr( $theRdate );
3527             if( is_array( $rPeriod )) {
3528               if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod ))      // timestamp
3529                 $inputab  = ( isset( $rPeriod['tz'] )) ? iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno ) : iCalUtilityFunctions::_timestamp2date( $rPeriod, 6 );
3530               elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod ))
3531                 $inputab  = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_date_time_array( $rPeriod, $parno ) : iCalUtilityFunctions::_date_time_array( $rPeriod, 6 );
3532               elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
3533                 $inputab  = iCalUtilityFunctions::_date_time_string( reset( $rPeriod ), $parno );
3534                 unset( $inputab['unparsedtext'] );
3535               }
3536               else                                               // array format duration
3537                 $inputab  = iCalUtilityFunctions::_duration_array( $rPeriod );
3538             }
3539             elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
3540                    ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
3541               if( 'P' != $rPeriod[0] )
3542                 $rPeriod  = substr( $rPeriod, 1 );
3543               $inputab    = iCalUtilityFunctions::_duration_string( $rPeriod );
3544             }
3545             elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
3546               $inputab    = iCalUtilityFunctions::_date_time_string( $rPeriod, $parno );
3547               unset( $inputab['unparsedtext'] );
3548             }
3549             if(  isset( $input['params']['TZID'] ) ||
3550                ( isset( $inputab['tz'] )   && !iCalUtilityFunctions::_isOffset( $inputab['tz'] )) ||
3551                ( isset( $inputa[0] )       && ( !isset( $inputa[0]['tz'] )))       ||
3552                ( isset( $inputa[0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa[0]['tz'] )))
3553               unset( $inputab['tz'] );
3554             if( $toZ )
3555               $inputab['tz']   = 'Z';
3556             $inputa[]     = $inputab;
3557           }
3558         } // PERIOD end
3559         elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
3560           $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
3561           if( $toZ )
3562             $inputa['tz']   = 'Z';
3563         }
3564         else {                                                                  // date[-time]
3565           $inputa = iCalUtilityFunctions::_date_time_array( $theRdate, $parno );
3566           $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
3567           if( $toZ )
3568             $inputa['tz']   = 'Z';
3569         }
3570       }
3571       elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
3572         $inputa       = iCalUtilityFunctions::_date_time_string( $theRdate, $parno );
3573         unset( $inputa['unparsedtext'] );
3574         if( $toZ )
3575           $inputa['tz']   = 'Z';
3576       }
3577       if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
3578         if( 3 == $parno )
3579           unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
3580         elseif( isset( $inputa['tz'] ))
3581           $inputa['tz'] = (string) $inputa['tz'];
3582         if(  isset( $input['params']['TZID'] ) ||
3583            ( isset( $inputa['tz'] )            && !iCalUtilityFunctions::_isOffset( $inputa['tz'] ))     ||
3584            ( isset( $input['value'][0] )       && ( !isset( $input['value'][0]['tz'] )))                 ||
3585            ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
3586           if( !$toZ )
3587             unset( $inputa['tz'] );
3588       }
3589       $input['value'][] = $inputa;
3590     }
3591     if( 3 == $parno ) {
3592       $input['params']['VALUE'] = 'DATE';
3593       unset( $input['params']['TZID'] );
3594     }
3595     if( $toZ )
3596       unset( $input['params']['TZID'] );
3597     iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
3598     return TRUE;
3599   }
3600 /*********************************************************************************/
3601 /**
3602  * Property Name: RECURRENCE-ID
3603  */
3604 /**
3605  * creates formatted output for calendar component property recurrence-id
3606  *
3607  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3608  * @since 2.9.6 - 2011-05-15
3609  * @return string
3610  */
3611   function createRecurrenceid() {
3612     if( empty( $this->recurrenceid )) return FALSE;
3613     if( empty( $this->recurrenceid['value'] ))
3614       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
3615     $formatted  = iCalUtilityFunctions::_format_date_time( $this->recurrenceid['value'] );
3616     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
3617        ( !isset( $this->recurrenceid['params']['VALUE'] ) || ( $this->recurrenceid['params']['VALUE'] != 'DATE' ))  &&
3618          !isset( $this->recurrenceid['params']['TZID'] ))
3619       $this->recurrenceid['params']['TZID'] = $tzid;
3620     $attributes = $this->_createParams( $this->recurrenceid['params'] );
3621     return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
3622   }
3623 /**
3624  * set calendar component property recurrence-id
3625  *
3626  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3627  * @since 2.9.6 - 2011-05-15
3628  * @param mixed $year
3629  * @param mixed $month optional
3630  * @param int $day optional
3631  * @param int $hour optional
3632  * @param int $min optional
3633  * @param int $sec optional
3634  * @param array $params optional
3635  * @return bool
3636  */
3637   function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
3638     if( empty( $year )) {
3639       if( $this->getConfig( 'allowEmpty' )) {
3640         $this->recurrenceid = array( 'value' => null, 'params' => null );
3641         return TRUE;
3642       }
3643       else
3644         return FALSE;
3645     }
3646     $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
3647     return TRUE;
3648   }
3649 /*********************************************************************************/
3650 /**
3651  * Property Name: RELATED-TO
3652  */
3653 /**
3654  * creates formatted output for calendar component property related-to
3655  *
3656  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3657  * @since 2.11.24 - 2012-02-23
3658  * @return string
3659  */
3660   function createRelatedTo() {
3661     if( empty( $this->relatedto )) return FALSE;
3662     $output = null;
3663     foreach( $this->relatedto as $relation ) {
3664       if( !empty( $relation['value'] ))
3665         $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), $this->_strrep( $relation['value'] ) );
3666       elseif( $this->getConfig( 'allowEmpty' ))
3667         $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
3668     }
3669     return $output;
3670   }
3671 /**
3672  * set calendar component property related-to
3673  *
3674  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3675  * @since 2.11.24 - 2012-02-23
3676  * @param float $relid
3677  * @param array $params, optional
3678  * @param index $index, optional
3679  * @return bool
3680  */
3681   function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
3682     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3683     iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
3684     iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
3685     return TRUE;
3686   }
3687 /*********************************************************************************/
3688 /**
3689  * Property Name: REPEAT
3690  */
3691 /**
3692  * creates formatted output for calendar component property repeat
3693  *
3694  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3695  * @since 2.9.3 - 2011-05-14
3696  * @return string
3697  */
3698   function createRepeat() {
3699     if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
3700     if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
3701       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
3702     $attributes = $this->_createParams( $this->repeat['params'] );
3703     return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
3704   }
3705 /**
3706  * set calendar component property repeat
3707  *
3708  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3709  * @since 2.9.3 - 2011-05-14
3710  * @param string $value
3711  * @param array $params optional
3712  * @return void
3713  */
3714   function setRepeat( $value, $params=FALSE ) {
3715     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3716     $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3717     return TRUE;
3718   }
3719 /*********************************************************************************/
3720 /**
3721  * Property Name: REQUEST-STATUS
3722  */
3723 /**
3724  * creates formatted output for calendar component property request-status
3725  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3726  * @since 2.4.8 - 2008-10-23
3727  * @return string
3728  */
3729   function createRequestStatus() {
3730     if( empty( $this->requeststatus )) return FALSE;
3731     $output = null;
3732     foreach( $this->requeststatus as $rstat ) {
3733       if( empty( $rstat['value']['statcode'] )) {
3734         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
3735         continue;
3736       }
3737       $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
3738       $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
3739       $content    .= ';'.$this->_strrep( $rstat['value']['text'] );
3740       if( isset( $rstat['value']['extdata'] ))
3741         $content  .= ';'.$this->_strrep( $rstat['value']['extdata'] );
3742       $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
3743     }
3744     return $output;
3745   }
3746 /**
3747  * set calendar component property request-status
3748  *
3749  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3750  * @since 2.5.1 - 2008-11-05
3751  * @param float $statcode
3752  * @param string $text
3753  * @param string $extdata, optional
3754  * @param array $params, optional
3755  * @param integer $index, optional
3756  * @return bool
3757  */
3758   function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
3759     if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE;
3760     $input              = array( 'statcode' => $statcode, 'text' => $text );
3761     if( $extdata )
3762       $input['extdata'] = $extdata;
3763     iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
3764     return TRUE;
3765   }
3766 /*********************************************************************************/
3767 /**
3768  * Property Name: RESOURCES
3769  */
3770 /**
3771  * creates formatted output for calendar component property resources
3772  *
3773  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3774  * @since 2.4.8 - 2008-10-23
3775  * @return string
3776  */
3777   function createResources() {
3778     if( empty( $this->resources )) return FALSE;
3779     $output = null;
3780     foreach( $this->resources as $resource ) {
3781       if( empty( $resource['value'] )) {
3782         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
3783         continue;
3784       }
3785       $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
3786       if( is_array( $resource['value'] )) {
3787         foreach( $resource['value'] as $rix => $resourcePart )
3788           $resource['value'][$rix] = $this->_strrep( $resourcePart );
3789         $content   = implode( ',', $resource['value'] );
3790       }
3791       else
3792         $content   = $this->_strrep( $resource['value'] );
3793       $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
3794     }
3795     return $output;
3796   }
3797 /**
3798  * set calendar component property recources
3799  *
3800  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3801  * @since 2.5.1 - 2008-11-05
3802  * @param mixed $value
3803  * @param array $params, optional
3804  * @param integer $index, optional
3805  * @return bool
3806  */
3807   function setResources( $value, $params=FALSE, $index=FALSE ) {
3808     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3809     iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
3810     return TRUE;
3811   }
3812 /*********************************************************************************/
3813 /**
3814  * Property Name: RRULE
3815  */
3816 /**
3817  * creates formatted output for calendar component property rrule
3818  *
3819  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3820  * @since 2.4.8 - 2008-10-21
3821  * @return string
3822  */
3823   function createRrule() {
3824     if( empty( $this->rrule )) return FALSE;
3825     return $this->_format_recur( 'RRULE', $this->rrule );
3826   }
3827 /**
3828  * set calendar component property rrule
3829  *
3830  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3831  * @since 2.5.1 - 2008-11-05
3832  * @param array $rruleset
3833  * @param array $params, optional
3834  * @param integer $index, optional
3835  * @return void
3836  */
3837   function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
3838     if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
3839     iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
3840     return TRUE;
3841   }
3842 /*********************************************************************************/
3843 /**
3844  * Property Name: SEQUENCE
3845  */
3846 /**
3847  * creates formatted output for calendar component property sequence
3848  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3849  * @since 2.9.3 - 2011-05-14
3850  * @return string
3851  */
3852   function createSequence() {
3853     if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
3854     if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
3855        ( '0' != $this->sequence['value'] ))
3856       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
3857     $attributes = $this->_createParams( $this->sequence['params'] );
3858     return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
3859   }
3860 /**
3861  * set calendar component property sequence
3862  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3863  * @since 2.10.8 - 2011-09-19
3864  * @param int $value optional
3865  * @param array $params optional
3866  * @return bool
3867  */
3868   function setSequence( $value=FALSE, $params=FALSE ) {
3869     if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
3870       $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
3871     $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3872     return TRUE;
3873   }
3874 /*********************************************************************************/
3875 /**
3876  * Property Name: STATUS
3877  */
3878 /**
3879  * creates formatted output for calendar component property status
3880  *
3881  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3882  * @since 2.4.8 - 2008-10-21
3883  * @return string
3884  */
3885   function createStatus() {
3886     if( empty( $this->status )) return FALSE;
3887     if( empty( $this->status['value'] ))
3888       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
3889     $attributes = $this->_createParams( $this->status['params'] );
3890     return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
3891   }
3892 /**
3893  * set calendar component property status
3894  *
3895  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3896  * @since 2.4.8 - 2008-11-04
3897  * @param string $value
3898  * @param array $params optional
3899  * @return bool
3900  */
3901   function setStatus( $value, $params=FALSE ) {
3902     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3903     $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3904     return TRUE;
3905   }
3906 /*********************************************************************************/
3907 /**
3908  * Property Name: SUMMARY
3909  */
3910 /**
3911  * creates formatted output for calendar component property summary
3912  *
3913  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3914  * @since 2.4.8 - 2008-10-21
3915  * @return string
3916  */
3917   function createSummary() {
3918     if( empty( $this->summary )) return FALSE;
3919     if( empty( $this->summary['value'] ))
3920       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
3921     $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
3922     $content    = $this->_strrep( $this->summary['value'] );
3923     return $this->_createElement( 'SUMMARY', $attributes, $content );
3924   }
3925 /**
3926  * set calendar component property summary
3927  *
3928  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3929  * @since 2.4.8 - 2008-11-04
3930  * @param string $value
3931  * @param string $params optional
3932  * @return bool
3933  */
3934   function setSummary( $value, $params=FALSE ) {
3935     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3936     $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3937     return TRUE;
3938   }
3939 /*********************************************************************************/
3940 /**
3941  * Property Name: TRANSP
3942  */
3943 /**
3944  * creates formatted output for calendar component property transp
3945  *
3946  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3947  * @since 2.4.8 - 2008-10-21
3948  * @return string
3949  */
3950   function createTransp() {
3951     if( empty( $this->transp )) return FALSE;
3952     if( empty( $this->transp['value'] ))
3953       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
3954     $attributes = $this->_createParams( $this->transp['params'] );
3955     return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
3956   }
3957 /**
3958  * set calendar component property transp
3959  *
3960  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3961  * @since 2.4.8 - 2008-11-04
3962  * @param string $value
3963  * @param string $params optional
3964  * @return bool
3965  */
3966   function setTransp( $value, $params=FALSE ) {
3967     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
3968     $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3969     return TRUE;
3970   }
3971 /*********************************************************************************/
3972 /**
3973  * Property Name: TRIGGER
3974  */
3975 /**
3976  * creates formatted output for calendar component property trigger
3977  *
3978  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3979  * @since 2.4.16 - 2008-10-21
3980  * @return string
3981  */
3982   function createTrigger() {
3983     if( empty( $this->trigger )) return FALSE;
3984     if( empty( $this->trigger['value'] ))
3985       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
3986     $content = $attributes = null;
3987     if( isset( $this->trigger['value']['year'] )   &&
3988         isset( $this->trigger['value']['month'] )  &&
3989         isset( $this->trigger['value']['day'] ))
3990       $content      .= iCalUtilityFunctions::_format_date_time( $this->trigger['value'] );
3991     else {
3992       if( TRUE !== $this->trigger['value']['relatedStart'] )
3993         $attributes .= $this->intAttrDelimiter.'RELATED=END';
3994       if( $this->trigger['value']['before'] )
3995         $content    .= '-';
3996       $content      .= iCalUtilityFunctions::_format_duration( $this->trigger['value'] );
3997     }
3998     $attributes     .= $this->_createParams( $this->trigger['params'] );
3999     return $this->_createElement( 'TRIGGER', $attributes, $content );
4000   }
4001 /**
4002  * set calendar component property trigger
4003  *
4004  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4005  * @since 2.10.30 - 2012-01-16
4006  * @param mixed $year
4007  * @param mixed $month optional
4008  * @param int $day optional
4009  * @param int $week optional
4010  * @param int $hour optional
4011  * @param int $min optional
4012  * @param int $sec optional
4013  * @param bool $relatedStart optional
4014  * @param bool $before optional
4015  * @param array $params optional
4016  * @return bool
4017  */
4018   function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
4019     if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
4020       if( $this->getConfig( 'allowEmpty' )) {
4021         $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) );
4022         return TRUE;
4023       }
4024       else
4025         return FALSE;
4026     if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp
4027       $params = iCalUtilityFunctions::_setParams( $month );
4028       $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
4029       foreach( $date as $k => $v )
4030         $$k = $v;
4031     }
4032     elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
4033       $params = iCalUtilityFunctions::_setParams( $month );
4034       if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
4035            array_key_exists( 'month', $year ) &&
4036            array_key_exists( 'day',   $year ))) {  // when this must be a duration
4037         if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
4038           $relatedStart = FALSE;
4039         else
4040           $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
4041         $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
4042       }
4043       $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
4044       $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
4045       $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
4046       $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
4047       $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
4048       $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
4049       $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
4050       $year  = $SSYY;
4051     }
4052     elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
4053       $params = iCalUtilityFunctions::_setParams( $month );
4054       if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
4055         $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
4056         $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
4057         if(     'P'  != $year[0] )
4058           $year       = substr( $year, 1 );
4059         $date         = iCalUtilityFunctions::_duration_string( $year);
4060       }
4061       else   // date
4062         $date    = iCalUtilityFunctions::_date_time_string( $year, 7 );
4063       unset( $year, $month, $day, $date['unparsedtext'] );
4064       if( empty( $date ))
4065         $sec = 0;
4066       else
4067         foreach( $date as $k => $v )
4068           $$k = $v;
4069     }
4070     else // single values in function input parameters
4071       $params = iCalUtilityFunctions::_setParams( $params );
4072     if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
4073       $params['VALUE'] = 'DATE-TIME';
4074       $hour = ( $hour ) ? $hour : 0;
4075       $min  = ( $min  ) ? $min  : 0;
4076       $sec  = ( $sec  ) ? $sec  : 0;
4077       $this->trigger = array( 'params' => $params );
4078       $this->trigger['value'] = array( 'year'  => $year
4079                                      , 'month' => $month
4080                                      , 'day'   => $day
4081                                      , 'hour'  => $hour
4082                                      , 'min'   => $min
4083                                      , 'sec'   => $sec
4084                                      , 'tz'    => 'Z' );
4085       return TRUE;
4086     }
4087     elseif(( empty( $year ) && empty( $month )) &&    // duration
4088            (( !empty( $week ) || ( 0 == $week )) ||
4089             ( !empty( $day )  || ( 0 == $day  )) ||
4090             ( !empty( $hour ) || ( 0 == $hour )) ||
4091             ( !empty( $min )  || ( 0 == $min  )) ||
4092             ( !empty( $sec )  || ( 0 == $sec  )))) {
4093       unset( $params['RELATED'] ); // set at output creation (END only)
4094       unset( $params['VALUE'] );   // 'DURATION' default
4095       $this->trigger = array( 'params' => $params );
4096       $this->trigger['value']  = array();
4097       if( !empty( $week )) $this->trigger['value']['week'] = $week;
4098       if( !empty( $day  )) $this->trigger['value']['day']  = $day;
4099       if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
4100       if( !empty( $min  )) $this->trigger['value']['min']  = $min;
4101       if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
4102       if( empty( $this->trigger['value'] )) {
4103         $this->trigger['value']['sec'] = 0;
4104         $before                        = FALSE;
4105       }
4106       $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
4107       $before       = ( FALSE !== $before )       ? TRUE : FALSE;
4108       $this->trigger['value']['relatedStart'] = $relatedStart;
4109       $this->trigger['value']['before']       = $before;
4110       return TRUE;
4111     }
4112     return FALSE;
4113   }
4114 /*********************************************************************************/
4115 /**
4116  * Property Name: TZID
4117  */
4118 /**
4119  * creates formatted output for calendar component property tzid
4120  *
4121  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4122  * @since 2.4.8 - 2008-10-21
4123  * @return string
4124  */
4125   function createTzid() {
4126     if( empty( $this->tzid )) return FALSE;
4127     if( empty( $this->tzid['value'] ))
4128       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
4129     $attributes = $this->_createParams( $this->tzid['params'] );
4130     return $this->_createElement( 'TZID', $attributes, $this->_strrep( $this->tzid['value'] ));
4131   }
4132 /**
4133  * set calendar component property tzid
4134  *
4135  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4136  * @since 2.4.8 - 2008-11-04
4137  * @param string $value
4138  * @param array $params optional
4139  * @return bool
4140  */
4141   function setTzid( $value, $params=FALSE ) {
4142     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4143     $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4144     return TRUE;
4145   }
4146 /*********************************************************************************/
4147 /**
4148  * .. .
4149  * Property Name: TZNAME
4150  */
4151 /**
4152  * creates formatted output for calendar component property tzname
4153  *
4154  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4155  * @since 2.4.8 - 2008-10-21
4156  * @return string
4157  */
4158   function createTzname() {
4159     if( empty( $this->tzname )) return FALSE;
4160     $output = null;
4161     foreach( $this->tzname as $theName ) {
4162       if( !empty( $theName['value'] )) {
4163         $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
4164         $output    .= $this->_createElement( 'TZNAME', $attributes, $this->_strrep( $theName['value'] ));
4165       }
4166       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
4167     }
4168     return $output;
4169   }
4170 /**
4171  * set calendar component property tzname
4172  *
4173  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4174  * @since 2.5.1 - 2008-11-05
4175  * @param string $value
4176  * @param string $params, optional
4177  * @param integer $index, optional
4178  * @return bool
4179  */
4180   function setTzname( $value, $params=FALSE, $index=FALSE ) {
4181     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4182     iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
4183     return TRUE;
4184   }
4185 /*********************************************************************************/
4186 /**
4187  * Property Name: TZOFFSETFROM
4188  */
4189 /**
4190  * creates formatted output for calendar component property tzoffsetfrom
4191  *
4192  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4193  * @since 2.4.8 - 2008-10-21
4194  * @return string
4195  */
4196   function createTzoffsetfrom() {
4197     if( empty( $this->tzoffsetfrom )) return FALSE;
4198     if( empty( $this->tzoffsetfrom['value'] ))
4199       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
4200     $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
4201     return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
4202   }
4203 /**
4204  * set calendar component property tzoffsetfrom
4205  *
4206  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4207  * @since 2.4.8 - 2008-11-04
4208  * @param string $value
4209  * @param string $params optional
4210  * @return bool
4211  */
4212   function setTzoffsetfrom( $value, $params=FALSE ) {
4213     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4214     $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4215     return TRUE;
4216   }
4217 /*********************************************************************************/
4218 /**
4219  * Property Name: TZOFFSETTO
4220  */
4221 /**
4222  * creates formatted output for calendar component property tzoffsetto
4223  *
4224  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4225  * @since 2.4.8 - 2008-10-21
4226  * @return string
4227  */
4228   function createTzoffsetto() {
4229     if( empty( $this->tzoffsetto )) return FALSE;
4230     if( empty( $this->tzoffsetto['value'] ))
4231       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
4232     $attributes = $this->_createParams( $this->tzoffsetto['params'] );
4233     return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
4234   }
4235 /**
4236  * set calendar component property tzoffsetto
4237  *
4238  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4239  * @since 2.4.8 - 2008-11-04
4240  * @param string $value
4241  * @param string $params optional
4242  * @return bool
4243  */
4244   function setTzoffsetto( $value, $params=FALSE ) {
4245     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4246     $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4247     return TRUE;
4248   }
4249 /*********************************************************************************/
4250 /**
4251  * Property Name: TZURL
4252  */
4253 /**
4254  * creates formatted output for calendar component property tzurl
4255  *
4256  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4257  * @since 2.4.8 - 2008-10-21
4258  * @return string
4259  */
4260   function createTzurl() {
4261     if( empty( $this->tzurl )) return FALSE;
4262     if( empty( $this->tzurl['value'] ))
4263       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
4264     $attributes = $this->_createParams( $this->tzurl['params'] );
4265     return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
4266   }
4267 /**
4268  * set calendar component property tzurl
4269  *
4270  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4271  * @since 2.4.8 - 2008-11-04
4272  * @param string $value
4273  * @param string $params optional
4274  * @return boll
4275  */
4276   function setTzurl( $value, $params=FALSE ) {
4277     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4278     $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4279     return TRUE;
4280   }
4281 /*********************************************************************************/
4282 /**
4283  * Property Name: UID
4284  */
4285 /**
4286  * creates formatted output for calendar component property uid
4287  *
4288  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4289  * @since 0.9.7 - 2006-11-20
4290  * @return string
4291  */
4292   function createUid() {
4293     if( 0 >= count( $this->uid ))
4294       $this->_makeuid();
4295     $attributes = $this->_createParams( $this->uid['params'] );
4296     return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
4297   }
4298 /**
4299  * create an unique id for this calendar component object instance
4300  *
4301  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4302  * @since 2.2.7 - 2007-09-04
4303  * @return void
4304  */
4305   function _makeUid() {
4306     $date   = date('Ymd\THisT');
4307     $unique = substr(microtime(), 2, 4);
4308     $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
4309     $start  = 0;
4310     $end    = strlen( $base ) - 1;
4311     $length = 6;
4312     $str    = null;
4313     for( $p = 0; $p < $length; $p++ )
4314       $unique .= $base{mt_rand( $start, $end )};
4315     $this->uid = array( 'params' => null );
4316     $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
4317   }
4318 /**
4319  * set calendar component property uid
4320  *
4321  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4322  * @since 2.4.8 - 2008-11-04
4323  * @param string $value
4324  * @param string $params optional
4325  * @return bool
4326  */
4327   function setUid( $value, $params=FALSE ) {
4328     if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
4329     $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4330     return TRUE;
4331   }
4332 /*********************************************************************************/
4333 /**
4334  * Property Name: URL
4335  */
4336 /**
4337  * creates formatted output for calendar component property url
4338  *
4339  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4340  * @since 2.4.8 - 2008-10-21
4341  * @return string
4342  */
4343   function createUrl() {
4344     if( empty( $this->url )) return FALSE;
4345     if( empty( $this->url['value'] ))
4346       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
4347     $attributes = $this->_createParams( $this->url['params'] );
4348     return $this->_createElement( 'URL', $attributes, $this->url['value'] );
4349   }
4350 /**
4351  * set calendar component property url
4352  *
4353  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4354  * @since 2.4.8 - 2008-11-04
4355  * @param string $value
4356  * @param string $params optional
4357  * @return bool
4358  */
4359   function setUrl( $value, $params=FALSE ) {
4360     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4361     $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4362     return TRUE;
4363   }
4364 /*********************************************************************************/
4365 /**
4366  * Property Name: x-prop
4367  */
4368 /**
4369  * creates formatted output for calendar component property x-prop
4370  *
4371  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4372  * @since 2.9.3 - 2011-05-14
4373  * @return string
4374  */
4375   function createXprop() {
4376     if( empty( $this->xprop )) return FALSE;
4377     $output = null;
4378     foreach( $this->xprop as $label => $xpropPart ) {
4379       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
4380         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
4381         continue;
4382       }
4383       $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
4384       if( is_array( $xpropPart['value'] )) {
4385         foreach( $xpropPart['value'] as $pix => $theXpart )
4386           $xpropPart['value'][$pix] = $this->_strrep( $theXpart );
4387         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
4388       }
4389       else
4390         $xpropPart['value'] = $this->_strrep( $xpropPart['value'] );
4391       $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
4392     }
4393     return $output;
4394   }
4395 /**
4396  * set calendar component property x-prop
4397  *
4398  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4399  * @since 2.11.9 - 2012-01-16
4400  * @param string $label
4401  * @param mixed $value
4402  * @param array $params optional
4403  * @return bool
4404  */
4405   function setXprop( $label, $value, $params=FALSE ) {
4406     if( empty( $label ))
4407       return FALSE;
4408     if( 'X-' != strtoupper( substr( $label, 0, 2 )))
4409       return FALSE;
4410     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
4411     $xprop           = array( 'value' => $value );
4412     $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
4413     if( !is_array( $this->xprop )) $this->xprop = array();
4414     $this->xprop[strtoupper( $label )] = $xprop;
4415     return TRUE;
4416   }
4417 /*********************************************************************************/
4418 /*********************************************************************************/
4419 /**
4420  * create element format parts
4421  *
4422  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4423  * @since 2.0.6 - 2006-06-20
4424  * @return string
4425  */
4426   function _createFormat() {
4427     $objectname                   = null;
4428     switch( $this->format ) {
4429       case 'xcal':
4430         $objectname               = ( isset( $this->timezonetype )) ?
4431                                  strtolower( $this->timezonetype )  :  strtolower( $this->objName );
4432         $this->componentStart1    = $this->elementStart1 = '<';
4433         $this->componentStart2    = $this->elementStart2 = '>';
4434         $this->componentEnd1      = $this->elementEnd1   = '</';
4435         $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
4436         $this->intAttrDelimiter   = '<!-- -->';
4437         $this->attributeDelimiter = $this->nl;
4438         $this->valueInit          = null;
4439         break;
4440       default:
4441         $objectname               = ( isset( $this->timezonetype )) ?
4442                                  strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
4443         $this->componentStart1    = 'BEGIN:';
4444         $this->componentStart2    = null;
4445         $this->componentEnd1      = 'END:';
4446         $this->componentEnd2      = $this->nl;
4447         $this->elementStart1      = null;
4448         $this->elementStart2      = null;
4449         $this->elementEnd1        = null;
4450         $this->elementEnd2        = $this->nl;
4451         $this->intAttrDelimiter   = '<!-- -->';
4452         $this->attributeDelimiter = ';';
4453         $this->valueInit          = ':';
4454         break;
4455     }
4456     return $objectname;
4457   }
4458 /**
4459  * creates formatted output for calendar component property
4460  *
4461  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4462  * @since 2.10.16 - 2011-10-28
4463  * @param string $label property name
4464  * @param string $attributes property attributes
4465  * @param string $content property content (optional)
4466  * @return string
4467  */
4468   function _createElement( $label, $attributes=null, $content=FALSE ) {
4469     switch( $this->format ) {
4470       case 'xcal':
4471         $label = strtolower( $label );
4472         break;
4473       default:
4474         $label = strtoupper( $label );
4475         break;
4476     }
4477     $output = $this->elementStart1.$label;
4478     $categoriesAttrLang = null;
4479     $attachInlineBinary = FALSE;
4480     $attachfmttype      = null;
4481     if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
4482       $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
4483                                , 'ref'      => $label
4484                                , 'type2'    => '(#PCDATA)' );
4485     }
4486     if( !empty( $attributes ))  {
4487       $attributes  = trim( $attributes );
4488       if ( 'xcal' == $this->format ) {
4489         $attributes2 = explode( $this->intAttrDelimiter, $attributes );
4490         $attributes  = null;
4491         foreach( $attributes2 as $aix => $attribute ) {
4492           $attrKVarr = explode( '=', $attribute );
4493           if( empty( $attrKVarr[0] ))
4494             continue;
4495           if( !isset( $attrKVarr[1] )) {
4496             $attrValue = $attrKVarr[0];
4497             $attrKey   = $aix;
4498           }
4499           elseif( 2 == count( $attrKVarr)) {
4500             $attrKey   = strtolower( $attrKVarr[0] );
4501             $attrValue = $attrKVarr[1];
4502           }
4503           else {
4504             $attrKey   = strtolower( $attrKVarr[0] );
4505             unset( $attrKVarr[0] );
4506             $attrValue = implode( '=', $attrKVarr );
4507           }
4508           if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
4509             $attachInlineBinary = TRUE;
4510             if( 'fmttype' == $attrKey )
4511               $attachfmttype = $attrKey.'='.$attrValue;
4512             continue;
4513           }
4514           elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
4515             $categoriesAttrLang = $attrKey.'='.$attrValue;
4516           else {
4517             $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
4518             $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
4519             if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
4520               $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
4521               $attrValue = str_replace( '"', '', $attrValue );
4522             }
4523             $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
4524           }
4525         }
4526       }
4527       else {
4528         $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
4529       }
4530     }
4531     if(( 'xcal' == $this->format) &&
4532        ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
4533       $pos = strrpos($content, "/");
4534       $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
4535       $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
4536                                , 'uri'      => $docname
4537                                , 'ref'      => 'SYSTEM'
4538                                , 'external' => $content
4539                                , 'type'     => 'NDATA'
4540                                , 'type2'    => 'BINERY' );
4541       $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
4542       $attributes .= 'uri="'.$docname.'"';
4543       $content = null;
4544       if( 'attach' == $label ) {
4545         $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
4546         $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
4547         $attributes = null;
4548       }
4549     }
4550     elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
4551       $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
4552     }
4553     $output .= $attributes;
4554     if( !$content && ( '0' != $content )) {
4555       switch( $this->format ) {
4556         case 'xcal':
4557           $output .= ' /';
4558           $output .= $this->elementStart2.$this->nl;
4559           return $output;
4560           break;
4561         default:
4562           $output .= $this->elementStart2.$this->valueInit;
4563           return $this->_size75( $output );
4564           break;
4565       }
4566     }
4567     $output .= $this->elementStart2;
4568     $output .= $this->valueInit.$content;
4569     switch( $this->format ) {
4570       case 'xcal':
4571         return $output.$this->elementEnd1.$label.$this->elementEnd2;
4572         break;
4573       default:
4574         return $this->_size75( $output );
4575         break;
4576     }
4577   }
4578 /**
4579  * creates formatted output for calendar component property parameters
4580  *
4581  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4582  * @since 2.10.27 - 2012-01-16
4583  * @param array $params  optional
4584  * @param array $ctrKeys optional
4585  * @return string
4586  */
4587   function _createParams( $params=array(), $ctrKeys=array() ) {
4588     if( !is_array( $params ) || empty( $params ))
4589       $params = array();
4590     $attrLANG = $attr1 = $attr2 = $lang = null;
4591     $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
4592     $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
4593     $CNattrExist = $LANGattrExist = FALSE;
4594     $xparams = array();
4595     foreach( $params as $paramKey => $paramValue ) {
4596       if(( FALSE !== strpos( $paramValue, ':' )) ||
4597          ( FALSE !== strpos( $paramValue, ';' )) ||
4598          ( FALSE !== strpos( $paramValue, ',' )))
4599         $paramValue = '"'.$paramValue.'"';
4600       if( ctype_digit( (string) $paramKey )) {
4601         $xparams[]          = $paramValue;
4602         continue;
4603       }
4604       $paramKey             = strtoupper( $paramKey );
4605       if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
4606         $xparams[$paramKey] = $paramValue;
4607       else
4608         $params[$paramKey]  = $paramValue;
4609     }
4610     ksort( $xparams, SORT_STRING );
4611     foreach( $xparams as $paramKey => $paramValue ) {
4612       if( ctype_digit( (string) $paramKey ))
4613         $attr2             .= $this->intAttrDelimiter.$paramValue;
4614       else
4615         $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
4616     }
4617     if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
4618       $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
4619       $attr2                = null;
4620     }
4621     if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
4622       if( !empty( $attr2 )) {
4623         $attr1             .= $attr2;
4624         $attr2              = null;
4625       }
4626       $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
4627     }
4628     if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
4629       $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
4630     if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
4631       $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
4632     }
4633     if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
4634       $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
4635     if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
4636       $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
4637     if( isset( $params['CN'] )       && $CNattrKey ) {
4638       $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
4639       $CNattrExist          = TRUE;
4640     }
4641     if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
4642       $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
4643       $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
4644     }
4645     if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
4646       $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
4647     if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
4648       $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
4649       $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
4650     }
4651     if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
4652       $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
4653       $LANGattrExist        = TRUE;
4654     }
4655     if( !$LANGattrExist ) {
4656       $lang = $this->getConfig( 'language' );
4657       if(( $CNattrExist || $LANGattrKey ) && $lang )
4658         $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
4659     }
4660     return $attr1.$attrLANG.$attr2;
4661   }
4662 /**
4663  * creates formatted output for calendar component property data value type recur
4664  *
4665  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4666  * @since 2.4.8 - 2008-10-22
4667  * @param array $recurlabel
4668  * @param array $recurdata
4669  * @return string
4670  */
4671   function _format_recur( $recurlabel, $recurdata ) {
4672     $output = null;
4673     foreach( $recurdata as $therule ) {
4674       if( empty( $therule['value'] )) {
4675         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
4676         continue;
4677       }
4678       $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
4679       $content1  = $content2  = null;
4680       foreach( $therule['value'] as $rulelabel => $rulevalue ) {
4681         switch( $rulelabel ) {
4682           case 'FREQ': {
4683             $content1 .= "FREQ=$rulevalue";
4684             break;
4685           }
4686           case 'UNTIL': {
4687             $content2 .= ";UNTIL=";
4688             $content2 .= iCalUtilityFunctions::_format_date_time( $rulevalue );
4689             break;
4690           }
4691           case 'COUNT':
4692           case 'INTERVAL':
4693           case 'WKST': {
4694             $content2 .= ";$rulelabel=$rulevalue";
4695             break;
4696           }
4697           case 'BYSECOND':
4698           case 'BYMINUTE':
4699           case 'BYHOUR':
4700           case 'BYMONTHDAY':
4701           case 'BYYEARDAY':
4702           case 'BYWEEKNO':
4703           case 'BYMONTH':
4704           case 'BYSETPOS': {
4705             $content2 .= ";$rulelabel=";
4706             if( is_array( $rulevalue )) {
4707               foreach( $rulevalue as $vix => $valuePart ) {
4708                 $content2 .= ( $vix ) ? ',' : null;
4709                 $content2 .= $valuePart;
4710               }
4711             }
4712             else
4713              $content2 .= $rulevalue;
4714             break;
4715           }
4716           case 'BYDAY': {
4717             $content2 .= ";$rulelabel=";
4718             $bydaycnt = 0;
4719             foreach( $rulevalue as $vix => $valuePart ) {
4720               $content21 = $content22 = null;
4721               if( is_array( $valuePart )) {
4722                 $content2 .= ( $bydaycnt ) ? ',' : null;
4723                 foreach( $valuePart as $vix2 => $valuePart2 ) {
4724                   if( 'DAY' != strtoupper( $vix2 ))
4725                       $content21 .= $valuePart2;
4726                   else
4727                     $content22 .= $valuePart2;
4728                 }
4729                 $content2 .= $content21.$content22;
4730                 $bydaycnt++;
4731               }
4732               else {
4733                 $content2 .= ( $bydaycnt ) ? ',' : null;
4734                 if( 'DAY' != strtoupper( $vix ))
4735                     $content21 .= $valuePart;
4736                 else {
4737                   $content22 .= $valuePart;
4738                   $bydaycnt++;
4739                 }
4740                 $content2 .= $content21.$content22;
4741               }
4742             }
4743             break;
4744           }
4745           default: {
4746             $content2 .= ";$rulelabel=$rulevalue";
4747             break;
4748           }
4749         }
4750       }
4751       $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
4752     }
4753     return $output;
4754   }
4755 /**
4756  * check if property not exists within component
4757  *
4758  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4759  * @since 2.5.1 - 2008-10-15
4760  * @param string $propName
4761  * @return bool
4762  */
4763   function _notExistProp( $propName ) {
4764     if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
4765     $propName = strtolower( $propName );
4766     if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
4767     elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
4768     elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
4769     elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
4770     elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
4771     elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
4772     return FALSE;
4773   }
4774 /*********************************************************************************/
4775 /*********************************************************************************/
4776 /**
4777  * get general component config variables or info about subcomponents
4778  *
4779  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4780  * @since 2.9.6 - 2011-05-14
4781  * @param mixed $config
4782  * @return value
4783  */
4784   function getConfig( $config = FALSE) {
4785     if( !$config ) {
4786       $return = array();
4787       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
4788       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
4789       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
4790         $return['LANGUAGE']  = $lang;
4791       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
4792       $return['TZTD']        = $this->getConfig( 'TZID' );
4793       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
4794       return $return;
4795     }
4796     switch( strtoupper( $config )) {
4797       case 'ALLOWEMPTY':
4798         return $this->allowEmpty;
4799         break;
4800       case 'COMPSINFO':
4801         unset( $this->compix );
4802         $info = array();
4803         if( isset( $this->components )) {
4804           foreach( $this->components as $cix => $component ) {
4805             if( empty( $component )) continue;
4806             $info[$cix]['ordno'] = $cix + 1;
4807             $info[$cix]['type']  = $component->objName;
4808             $info[$cix]['uid']   = $component->getProperty( 'uid' );
4809             $info[$cix]['props'] = $component->getConfig( 'propinfo' );
4810             $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
4811           }
4812         }
4813         return $info;
4814         break;
4815       case 'FORMAT':
4816         return $this->format;
4817         break;
4818       case 'LANGUAGE':
4819          // get language for calendar component as defined in [RFC 1766]
4820         return $this->language;
4821         break;
4822       case 'NL':
4823       case 'NEWLINECHAR':
4824         return $this->nl;
4825         break;
4826       case 'PROPINFO':
4827         $output = array();
4828         if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
4829           if( empty( $this->uid['value'] )) $this->_makeuid();
4830                                               $output['UID']              = 1;
4831         }
4832         if( !empty( $this->dtstamp ))         $output['DTSTAMP']          = 1;
4833         if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
4834         if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
4835         if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
4836         if( !empty( $this->dtend ))           $output['DTEND']            = 1;
4837         if( !empty( $this->due ))             $output['DUE']              = 1;
4838         if( !empty( $this->duration ))        $output['DURATION']         = 1;
4839         if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
4840         if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
4841         if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
4842         if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
4843         if( !empty( $this->action ))          $output['ACTION']           = 1;
4844         if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
4845         if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
4846         if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
4847         if( !empty( $this->class ))           $output['CLASS']            = 1;
4848         if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
4849         if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
4850         if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
4851         if( !empty( $this->created ))         $output['CREATED']          = 1;
4852         if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
4853         if( !empty( $this->geo ))             $output['GEO']              = 1;
4854         if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
4855         if( !empty( $this->location ))        $output['LOCATION']         = 1;
4856         if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
4857         if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
4858         if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
4859         if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
4860         if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
4861         if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
4862         if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
4863         if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
4864         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
4865         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
4866         if( !empty( $this->status ))          $output['STATUS']           = 1;
4867         if( !empty( $this->transp ))          $output['TRANSP']           = 1;
4868         if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
4869         if( !empty( $this->tzid ))            $output['TZID']             = 1;
4870         if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
4871         if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
4872         if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
4873         if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
4874         if( !empty( $this->url ))             $output['URL']              = 1;
4875         if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
4876         return $output;
4877         break;
4878       case 'TZID':
4879         return $this->dtzid;
4880         break;
4881       case 'UNIQUE_ID':
4882         if( empty( $this->unique_id ))
4883           $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
4884         return $this->unique_id;
4885         break;
4886     }
4887   }
4888 /**
4889  * general component config setting
4890  *
4891  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4892  * @since 2.10.18 - 2011-10-28
4893  * @param mixed  $config
4894  * @param string $value
4895  * @param bool   $softUpdate
4896  * @return void
4897  */
4898   function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
4899     if( is_array( $config )) {
4900       $ak = array_keys( $config );
4901       foreach( $ak as $k ) {
4902         if( 'NEWLINECHAR' == strtoupper( $k )) {
4903           if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
4904             return FALSE;
4905           unset( $config[$k] );
4906           break;
4907         }
4908       }
4909       foreach( $config as $cKey => $cValue ) {
4910         if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
4911           return FALSE;
4912       }
4913       return TRUE;
4914     }
4915     $res = FALSE;
4916     switch( strtoupper( $config )) {
4917       case 'ALLOWEMPTY':
4918         $this->allowEmpty = $value;
4919         $subcfg = array( 'ALLOWEMPTY' => $value );
4920         $res    = TRUE;
4921         break;
4922       case 'FORMAT':
4923         $value  = trim( strtolower( $value ));
4924         $this->format = $value;
4925         $this->_createFormat();
4926         $subcfg = array( 'FORMAT' => $value );
4927         $res    = TRUE;
4928         break;
4929       case 'LANGUAGE':
4930          // set language for calendar component as defined in [RFC 1766]
4931         $value  = trim( $value );
4932         if( empty( $this->language ) || !$softUpdate )
4933           $this->language = $value;
4934         $subcfg = array( 'LANGUAGE' => $value );
4935         $res    = TRUE;
4936         break;
4937       case 'NL':
4938       case 'NEWLINECHAR':
4939         $this->nl = $value;
4940         $this->_createFormat();
4941         $subcfg = array( 'NL' => $value );
4942         $res    = TRUE;
4943         break;
4944       case 'TZID':
4945         $this->dtzid = $value;
4946         $subcfg = array( 'TZID' => $value );
4947         $res    = TRUE;
4948         break;
4949       case 'UNIQUE_ID':
4950         $value  = trim( $value );
4951         $this->unique_id = $value;
4952         $subcfg = array( 'UNIQUE_ID' => $value );
4953         $res    = TRUE;
4954         break;
4955       default:  // any unvalid config key.. .
4956         return TRUE;
4957     }
4958     if( !$res ) return FALSE;
4959     if( isset( $subcfg ) && !empty( $this->components )) {
4960       foreach( $subcfg as $cfgkey => $cfgvalue ) {
4961         foreach( $this->components as $cix => $component ) {
4962           $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
4963           if( !$res )
4964             break 2;
4965           $this->components[$cix] = $component->copy(); // PHP4 compliant
4966         }
4967       }
4968     }
4969     return $res;
4970   }
4971 /*********************************************************************************/
4972 /**
4973  * delete component property value
4974  *
4975  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4976  * @since 2.8.8 - 2011-03-15
4977  * @param mixed $propName, bool FALSE => X-property
4978  * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
4979  * @return bool, if successfull delete TRUE
4980  */
4981   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
4982     if( $this->_notExistProp( $propName )) return FALSE;
4983     $propName = strtoupper( $propName );
4984     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
4985                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
4986       if( !$propix )
4987         $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
4988       $this->propdelix[$propName] = --$propix;
4989     }
4990     $return = FALSE;
4991     switch( $propName ) {
4992       case 'ACTION':
4993         if( !empty( $this->action )) {
4994           $this->action = '';
4995           $return = TRUE;
4996         }
4997         break;
4998       case 'ATTACH':
4999         return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
5000         break;
5001       case 'ATTENDEE':
5002         return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
5003         break;
5004       case 'CATEGORIES':
5005         return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
5006         break;
5007       case 'CLASS':
5008         if( !empty( $this->class )) {
5009           $this->class = '';
5010           $return = TRUE;
5011         }
5012         break;
5013       case 'COMMENT':
5014         return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
5015         break;
5016       case 'COMPLETED':
5017         if( !empty( $this->completed )) {
5018           $this->completed = '';
5019           $return = TRUE;
5020         }
5021         break;
5022       case 'CONTACT':
5023         return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
5024         break;
5025       case 'CREATED':
5026         if( !empty( $this->created )) {
5027           $this->created = '';
5028           $return = TRUE;
5029         }
5030         break;
5031       case 'DESCRIPTION':
5032         return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
5033         break;
5034       case 'DTEND':
5035         if( !empty( $this->dtend )) {
5036           $this->dtend = '';
5037           $return = TRUE;
5038         }
5039         break;
5040       case 'DTSTAMP':
5041         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5042           return FALSE;
5043         if( !empty( $this->dtstamp )) {
5044           $this->dtstamp = '';
5045           $return = TRUE;
5046         }
5047         break;
5048       case 'DTSTART':
5049         if( !empty( $this->dtstart )) {
5050           $this->dtstart = '';
5051           $return = TRUE;
5052         }
5053         break;
5054       case 'DUE':
5055         if( !empty( $this->due )) {
5056           $this->due = '';
5057           $return = TRUE;
5058         }
5059         break;
5060       case 'DURATION':
5061         if( !empty( $this->duration )) {
5062           $this->duration = '';
5063           $return = TRUE;
5064         }
5065         break;
5066       case 'EXDATE':
5067         return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
5068         break;
5069       case 'EXRULE':
5070         return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
5071         break;
5072       case 'FREEBUSY':
5073         return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
5074         break;
5075       case 'GEO':
5076         if( !empty( $this->geo )) {
5077           $this->geo = '';
5078           $return = TRUE;
5079         }
5080         break;
5081       case 'LAST-MODIFIED':
5082         if( !empty( $this->lastmodified )) {
5083           $this->lastmodified = '';
5084           $return = TRUE;
5085         }
5086         break;
5087       case 'LOCATION':
5088         if( !empty( $this->location )) {
5089           $this->location = '';
5090           $return = TRUE;
5091         }
5092         break;
5093       case 'ORGANIZER':
5094         if( !empty( $this->organizer )) {
5095           $this->organizer = '';
5096           $return = TRUE;
5097         }
5098         break;
5099       case 'PERCENT-COMPLETE':
5100         if( !empty( $this->percentcomplete )) {
5101           $this->percentcomplete = '';
5102           $return = TRUE;
5103         }
5104         break;
5105       case 'PRIORITY':
5106         if( !empty( $this->priority )) {
5107           $this->priority = '';
5108           $return = TRUE;
5109         }
5110         break;
5111       case 'RDATE':
5112         return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
5113         break;
5114       case 'RECURRENCE-ID':
5115         if( !empty( $this->recurrenceid )) {
5116           $this->recurrenceid = '';
5117           $return = TRUE;
5118         }
5119         break;
5120       case 'RELATED-TO':
5121         return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
5122         break;
5123       case 'REPEAT':
5124         if( !empty( $this->repeat )) {
5125           $this->repeat = '';
5126           $return = TRUE;
5127         }
5128         break;
5129       case 'REQUEST-STATUS':
5130         return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
5131         break;
5132       case 'RESOURCES':
5133         return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
5134         break;
5135       case 'RRULE':
5136         return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
5137         break;
5138       case 'SEQUENCE':
5139         if( !empty( $this->sequence )) {
5140           $this->sequence = '';
5141           $return = TRUE;
5142         }
5143         break;
5144       case 'STATUS':
5145         if( !empty( $this->status )) {
5146           $this->status = '';
5147           $return = TRUE;
5148         }
5149         break;
5150       case 'SUMMARY':
5151         if( !empty( $this->summary )) {
5152           $this->summary = '';
5153           $return = TRUE;
5154         }
5155         break;
5156       case 'TRANSP':
5157         if( !empty( $this->transp )) {
5158           $this->transp = '';
5159           $return = TRUE;
5160         }
5161         break;
5162       case 'TRIGGER':
5163         if( !empty( $this->trigger )) {
5164           $this->trigger = '';
5165           $return = TRUE;
5166         }
5167         break;
5168       case 'TZID':
5169         if( !empty( $this->tzid )) {
5170           $this->tzid = '';
5171           $return = TRUE;
5172         }
5173         break;
5174       case 'TZNAME':
5175         return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
5176         break;
5177       case 'TZOFFSETFROM':
5178         if( !empty( $this->tzoffsetfrom )) {
5179           $this->tzoffsetfrom = '';
5180           $return = TRUE;
5181         }
5182         break;
5183       case 'TZOFFSETTO':
5184         if( !empty( $this->tzoffsetto )) {
5185           $this->tzoffsetto = '';
5186           $return = TRUE;
5187         }
5188         break;
5189       case 'TZURL':
5190         if( !empty( $this->tzurl )) {
5191           $this->tzurl = '';
5192           $return = TRUE;
5193         }
5194         break;
5195       case 'UID':
5196         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5197           return FALSE;
5198         if( !empty( $this->uid )) {
5199           $this->uid = '';
5200           $return = TRUE;
5201         }
5202         break;
5203       case 'URL':
5204         if( !empty( $this->url )) {
5205           $this->url = '';
5206           $return = TRUE;
5207         }
5208         break;
5209       default:
5210         $reduced = '';
5211         if( $propName != 'X-PROP' ) {
5212           if( !isset( $this->xprop[$propName] )) return FALSE;
5213           foreach( $this->xprop as $k => $a ) {
5214             if(( $k != $propName ) && !empty( $a ))
5215               $reduced[$k] = $a;
5216           }
5217         }
5218         else {
5219           if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
5220           $xpropno = 0;
5221           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
5222             if( $propix != $xpropno )
5223               $reduced[$xpropkey] = $xpropvalue;
5224             $xpropno++;
5225           }
5226         }
5227         $this->xprop = $reduced;
5228         if( empty( $this->xprop )) {
5229           unset( $this->propdelix[$propName] );
5230           return FALSE;
5231         }
5232         return TRUE;
5233     }
5234     return $return;
5235   }
5236 /*********************************************************************************/
5237 /**
5238  * delete component property value, fixing components with multiple occurencies
5239  *
5240  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5241  * @since 2.8.8 - 2011-03-15
5242  * @param array $multiprop, reference to a component property
5243  * @param int   $propix, reference to removal counter
5244  * @return bool TRUE
5245  */
5246   function deletePropertyM( & $multiprop, & $propix ) {
5247     if( isset( $multiprop[$propix] ))
5248       unset( $multiprop[$propix] );
5249     if( empty( $multiprop )) {
5250       $multiprop = '';
5251       unset( $propix );
5252       return FALSE;
5253     }
5254     else
5255       return TRUE;
5256   }
5257 /**
5258  * get component property value/params
5259  *
5260  * if property has multiply values, consequtive function calls are needed
5261  *
5262  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5263  * @since 2.11.3 - 2012-01-10
5264  * @param string $propName, optional
5265  * @param int @propix, optional, if specific property is wanted in case of multiply occurences
5266  * @param bool $inclParam=FALSE
5267  * @param bool $specform=FALSE
5268  * @return mixed
5269  */
5270   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
5271     if( $this->_notExistProp( $propName )) return FALSE;
5272     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
5273     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
5274                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
5275       if( !$propix )
5276         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
5277       $this->propix[$propName] = --$propix;
5278     }
5279     switch( $propName ) {
5280       case 'ACTION':
5281         if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
5282         break;
5283       case 'ATTACH':
5284         $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
5285         while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
5286           $propix++;
5287         $this->propix[$propName] = $propix;
5288         if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5289         return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
5290         break;
5291       case 'ATTENDEE':
5292         $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
5293         while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
5294           $propix++;
5295         $this->propix[$propName] = $propix;
5296         if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5297         return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
5298         break;
5299       case 'CATEGORIES':
5300         $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
5301         while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
5302           $propix++;
5303         $this->propix[$propName] = $propix;
5304         if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5305         return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
5306         break;
5307       case 'CLASS':
5308         if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
5309         break;
5310       case 'COMMENT':
5311         $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
5312         while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
5313           $propix++;
5314         $this->propix[$propName] = $propix;
5315         if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5316         return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
5317         break;
5318       case 'COMPLETED':
5319         if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
5320         break;
5321       case 'CONTACT':
5322         $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
5323         while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
5324           $propix++;
5325         $this->propix[$propName] = $propix;
5326         if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5327         return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
5328         break;
5329       case 'CREATED':
5330         if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
5331         break;
5332       case 'DESCRIPTION':
5333         $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
5334         while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
5335           $propix++;
5336         $this->propix[$propName] = $propix;
5337         if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5338         return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
5339         break;
5340       case 'DTEND':
5341         if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
5342         break;
5343       case 'DTSTAMP':
5344         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5345           return;
5346         if( !isset( $this->dtstamp['value'] ))
5347           $this->_makeDtstamp();
5348         return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
5349         break;
5350       case 'DTSTART':
5351         if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
5352         break;
5353       case 'DUE':
5354         if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
5355         break;
5356       case 'DURATION':
5357         if( !isset( $this->duration['value'] )) return FALSE;
5358         $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
5359         return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
5360         break;
5361       case 'EXDATE':
5362         $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
5363         while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
5364           $propix++;
5365         $this->propix[$propName] = $propix;
5366         if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5367         return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
5368         break;
5369       case 'EXRULE':
5370         $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
5371         while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
5372           $propix++;
5373         $this->propix[$propName] = $propix;
5374         if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5375         return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
5376         break;
5377       case 'FREEBUSY':
5378         $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
5379         while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
5380           $propix++;
5381         $this->propix[$propName] = $propix;
5382         if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5383         return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
5384         break;
5385       case 'GEO':
5386         if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
5387         break;
5388       case 'LAST-MODIFIED':
5389         if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
5390         break;
5391       case 'LOCATION':
5392         if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
5393         break;
5394       case 'ORGANIZER':
5395         if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
5396         break;
5397       case 'PERCENT-COMPLETE':
5398         if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
5399         break;
5400       case 'PRIORITY':
5401         if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value'];
5402         break;
5403       case 'RDATE':
5404         $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
5405         while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
5406           $propix++;
5407         $this->propix[$propName] = $propix;
5408         if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5409         return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
5410         break;
5411       case 'RECURRENCE-ID':
5412         if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
5413         break;
5414       case 'RELATED-TO':
5415         $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
5416         while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
5417           $propix++;
5418         $this->propix[$propName] = $propix;
5419         if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5420         return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
5421         break;
5422       case 'REPEAT':
5423         if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
5424         break;
5425       case 'REQUEST-STATUS':
5426         $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
5427         while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
5428           $propix++;
5429         $this->propix[$propName] = $propix;
5430         if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5431         return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
5432         break;
5433       case 'RESOURCES':
5434         $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
5435         while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
5436           $propix++;
5437         $this->propix[$propName] = $propix;
5438         if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5439         return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
5440         break;
5441       case 'RRULE':
5442         $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
5443         while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
5444           $propix++;
5445         $this->propix[$propName] = $propix;
5446         if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5447         return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
5448         break;
5449       case 'SEQUENCE':
5450         if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
5451         break;
5452       case 'STATUS':
5453         if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
5454         break;
5455       case 'SUMMARY':
5456         if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
5457         break;
5458       case 'TRANSP':
5459         if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
5460         break;
5461       case 'TRIGGER':
5462         if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
5463         break;
5464       case 'TZID':
5465         if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
5466         break;
5467       case 'TZNAME':
5468         $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
5469         while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
5470           $propix++;
5471         $this->propix[$propName] = $propix;
5472         if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5473         return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
5474         break;
5475       case 'TZOFFSETFROM':
5476         if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
5477         break;
5478       case 'TZOFFSETTO':
5479         if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
5480         break;
5481       case 'TZURL':
5482         if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
5483         break;
5484       case 'UID':
5485         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5486           return FALSE;
5487         if( empty( $this->uid['value'] ))
5488           $this->_makeuid();
5489         return ( $inclParam ) ? $this->uid : $this->uid['value'];
5490         break;
5491       case 'URL':
5492         if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
5493         break;
5494       default:
5495         if( $propName != 'X-PROP' ) {
5496           if( !isset( $this->xprop[$propName] )) return FALSE;
5497           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
5498                                 : array( $propName, $this->xprop[$propName]['value'] );
5499         }
5500         else {
5501           if( empty( $this->xprop )) return FALSE;
5502           $xpropno = 0;
5503           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
5504             if( $propix == $xpropno )
5505               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
5506                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
5507             else
5508               $xpropno++;
5509           }
5510           return FALSE; // not found ??
5511         }
5512     }
5513     return FALSE;
5514   }
5515 /**
5516  * returns calendar property unique values for 'CATEGORIES', 'RESOURCES' or 'ATTENDEE' and each number of ocurrence
5517  *
5518  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5519  * @since 2.8.8 - 2011-04-13
5520  * @param string $propName
5521  * @param array  $output, incremented result array
5522  */
5523   function _getProperties( $propName, & $output ) {
5524     if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'RESOURCES' )))
5525       return output;
5526     while( FALSE !== ( $content = $this->getProperty( $propName ))) {
5527       if( is_array( $content )) {
5528         foreach( $content as $part ) {
5529           if( FALSE !== strpos( $part, ',' )) {
5530             $part = explode( ',', $part );
5531             foreach( $part as $thePart ) {
5532               $thePart = trim( $thePart );
5533               if( !empty( $thePart )) {
5534                 if( !isset( $output[$thePart] ))
5535                   $output[$thePart] = 1;
5536                 else
5537                   $output[$thePart] += 1;
5538               }
5539             }
5540           }
5541           else {
5542             $part = trim( $part );
5543             if( !isset( $output[$part] ))
5544               $output[$part] = 1;
5545             else
5546               $output[$part] += 1;
5547           }
5548         }
5549       }
5550       elseif( FALSE !== strpos( $content, ',' )) {
5551         $content = explode( ',', $content );
5552         foreach( $content as $thePart ) {
5553           $thePart = trim( $thePart );
5554           if( !empty( $thePart )) {
5555             if( !isset( $output[$thePart] ))
5556               $output[$thePart] = 1;
5557             else
5558               $output[$thePart] += 1;
5559           }
5560         }
5561       }
5562       else {
5563         $content = trim( $content );
5564         if( !empty( $content )) {
5565           if( !isset( $output[$content] ))
5566             $output[$content] = 1;
5567           else
5568             $output[$content] += 1;
5569         }
5570       }
5571     }
5572     ksort( $output );
5573     return $output;
5574   }
5575 /**
5576  * general component property setting
5577  *
5578  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5579  * @since 2.5.1 - 2008-11-05
5580  * @param mixed $args variable number of function arguments,
5581  *                    first argument is ALWAYS component name,
5582  *                    second ALWAYS component value!
5583  * @return void
5584  */
5585   function setProperty() {
5586     $numargs    = func_num_args();
5587     if( 1 > $numargs ) return FALSE;
5588     $arglist    = func_get_args();
5589     if( $this->_notExistProp( $arglist[0] )) return FALSE;
5590     if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
5591       return FALSE;
5592     $arglist[0] = strtoupper( $arglist[0] );
5593     for( $argix=$numargs; $argix < 12; $argix++ ) {
5594       if( !isset( $arglist[$argix] ))
5595         $arglist[$argix] = null;
5596     }
5597     switch( $arglist[0] ) {
5598       case 'ACTION':
5599         return $this->setAction( $arglist[1], $arglist[2] );
5600       case 'ATTACH':
5601         return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
5602       case 'ATTENDEE':
5603         return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
5604       case 'CATEGORIES':
5605         return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
5606       case 'CLASS':
5607         return $this->setClass( $arglist[1], $arglist[2] );
5608       case 'COMMENT':
5609         return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
5610       case 'COMPLETED':
5611         return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5612       case 'CONTACT':
5613         return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
5614       case 'CREATED':
5615         return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5616       case 'DESCRIPTION':
5617         return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
5618       case 'DTEND':
5619         return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5620       case 'DTSTAMP':
5621         return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5622       case 'DTSTART':
5623         return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5624       case 'DUE':
5625         return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5626       case 'DURATION':
5627         return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
5628       case 'EXDATE':
5629         return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
5630       case 'EXRULE':
5631         return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
5632       case 'FREEBUSY':
5633         return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
5634       case 'GEO':
5635         return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
5636       case 'LAST-MODIFIED':
5637         return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5638       case 'LOCATION':
5639         return $this->setLocation( $arglist[1], $arglist[2] );
5640       case 'ORGANIZER':
5641         return $this->setOrganizer( $arglist[1], $arglist[2] );
5642       case 'PERCENT-COMPLETE':
5643         return $this->setPercentComplete( $arglist[1], $arglist[2] );
5644       case 'PRIORITY':
5645         return $this->setPriority( $arglist[1], $arglist[2] );
5646       case 'RDATE':
5647         return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
5648       case 'RECURRENCE-ID':
5649        return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5650       case 'RELATED-TO':
5651         return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
5652       case 'REPEAT':
5653         return $this->setRepeat( $arglist[1], $arglist[2] );
5654       case 'REQUEST-STATUS':
5655         return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
5656       case 'RESOURCES':
5657         return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
5658       case 'RRULE':
5659         return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
5660       case 'SEQUENCE':
5661         return $this->setSequence( $arglist[1], $arglist[2] );
5662       case 'STATUS':
5663         return $this->setStatus( $arglist[1], $arglist[2] );
5664       case 'SUMMARY':
5665         return $this->setSummary( $arglist[1], $arglist[2] );
5666       case 'TRANSP':
5667         return $this->setTransp( $arglist[1], $arglist[2] );
5668       case 'TRIGGER':
5669         return $this->setTrigger( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8], $arglist[9], $arglist[10], $arglist[11] );
5670       case 'TZID':
5671         return $this->setTzid( $arglist[1], $arglist[2] );
5672       case 'TZNAME':
5673         return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
5674       case 'TZOFFSETFROM':
5675         return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
5676       case 'TZOFFSETTO':
5677         return $this->setTzoffsetto( $arglist[1], $arglist[2] );
5678       case 'TZURL':
5679         return $this->setTzurl( $arglist[1], $arglist[2] );
5680       case 'UID':
5681         return $this->setUid( $arglist[1], $arglist[2] );
5682       case 'URL':
5683         return $this->setUrl( $arglist[1], $arglist[2] );
5684       default:
5685         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
5686     }
5687     return FALSE;
5688   }
5689 /*********************************************************************************/
5690 /**
5691  * parse component unparsed data into properties
5692  *
5693  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5694  * @since 2.11.17 - 2012-02-03
5695  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
5696  * @return bool FALSE if error occurs during parsing
5697  *
5698  */
5699   function parse( $unparsedtext=null ) {
5700     if( !empty( $unparsedtext )) {
5701       $nl = $this->getConfig( 'nl' );
5702       if( is_array( $unparsedtext ))
5703         $unparsedtext = implode( '\n'.$nl, $unparsedtext );
5704             /* fix line folding */
5705       $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings
5706       $EOLmark = FALSE;
5707       foreach( $eolchars as $eolchar ) {
5708         if( !$EOLmark  && ( FALSE !== strpos( $unparsedtext, $eolchar ))) {
5709           $unparsedtext = str_replace( $eolchar." ",  '',  $unparsedtext );
5710           $unparsedtext = str_replace( $eolchar."\t", '',  $unparsedtext );
5711           if( $eolchar != $nl )
5712             $unparsedtext = str_replace( $eolchar,    $nl, $unparsedtext );
5713           $EOLmark = TRUE;
5714         }
5715       }
5716       $tmp = explode( $nl, $unparsedtext );
5717       $unparsedtext = array();
5718       foreach( $tmp as $tmpr )
5719         if( !empty( $tmpr ))
5720           $unparsedtext[] = $tmpr;
5721     }
5722     elseif( !isset( $this->unparsed ))
5723       $unparsedtext = array();
5724     else
5725       $unparsedtext = $this->unparsed;
5726     $this->unparsed = array();
5727     $comp = & $this;
5728     $config = $this->getConfig();
5729     foreach ( $unparsedtext as $line ) {
5730       if( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VA', 'END:DA' )))
5731         $this->components[] = $comp->copy();
5732       elseif( 'END:ST' == strtoupper( substr( $line, 0, 6 )))
5733         array_unshift( $this->components, $comp->copy());
5734       elseif( 'END:' == strtoupper( substr( $line, 0, 4 )))
5735         break;
5736       elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 )))
5737         $comp = new valarm( $config);
5738       elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 )))
5739         $comp = new vtimezone( 'standard', $config );
5740       elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 )))
5741         $comp = new vtimezone( 'daylight', $config );
5742       elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))
5743         continue;
5744       else
5745         $comp->unparsed[] = $line;
5746     }
5747     unset( $config );
5748             /* concatenate property values spread over several lines */
5749     $lastix    = -1;
5750     $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
5751                       , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
5752                       , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
5753                       , 'last-modified', 'location', 'organizer', 'percent-complete'
5754                       , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
5755                       , 'request-status', 'resources', 'rrule', 'sequence', 'status'
5756                       , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
5757                       , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
5758     $proprows  = array();
5759     foreach( $this->unparsed as $line ) {
5760       $newProp = FALSE;
5761       foreach ( $propnames as $propname ) {
5762         if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
5763           $newProp = TRUE;
5764           break;
5765         }
5766       }
5767       if( $newProp ) {
5768         $newProp = FALSE;
5769         $lastix++;
5770         $proprows[$lastix]  = $line;
5771       }
5772       else
5773         $proprows[$lastix] .= '!"#¤%&/()=?'.$line;
5774     }
5775             /* parse each property 'line' */
5776     $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
5777     $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
5778     $paramProto4 = array( 'crid:', 'news:', 'pres:' );
5779     foreach( $proprows as $line ) {
5780       $line = str_replace( '!"#¤%&/()=? ', '', $line );
5781       $line = str_replace( '!"#¤%&/()=?', '', $line );
5782       if( '\n' == substr( $line, -2 ))
5783         $line = substr( $line, 0, strlen( $line ) - 2 );
5784             /* get propname, (problem with x-properties, otherwise in previous loop) */
5785       $cix = $propname = null;
5786       for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) {
5787         if( in_array( $line[$cix], array( ':', ';' )))
5788           break;
5789         else
5790           $propname .= $line[$cix];
5791       }
5792       if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
5793         $propname2 = $propname;
5794         $propname  = 'X-';
5795       }
5796             /* rest of the line is opt.params and value */
5797       $line = substr( $line, $cix );
5798             /* separate attributes from value */
5799       $attr         = array();
5800       $attrix       = -1;
5801       $clen         = strlen( $line );
5802       $WithinQuotes = FALSE;
5803       for( $cix=0; $cix < $clen; $cix++ ) {
5804         if(                       (  ':' == $line[$cix] )                         &&
5805                                   ( substr( $line,$cix,     3 )  != '://' )       &&
5806            ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
5807            ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
5808            ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
5809                       ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
5810              !$WithinQuotes ) {
5811           $attrEnd = TRUE;
5812           if(( $cix < ( $clen - 4 )) &&
5813                ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
5814             for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
5815               if( '://' == substr( $line, $c2ix - 2, 3 )) {
5816                 $attrEnd = FALSE;
5817                 break; // an URI with a portnr!!
5818               }
5819             }
5820           }
5821           if( $attrEnd) {
5822             $line = substr( $line, ( $cix + 1 ));
5823             break;
5824           }
5825         }
5826         if( '"' == $line[$cix] )
5827           $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
5828         if( ';' == $line[$cix] )
5829           $attr[++$attrix] = null;
5830         else
5831           $attr[$attrix] .= $line[$cix];
5832       }
5833             /* make attributes in array format */
5834       $propattr = array();
5835       foreach( $attr as $attribute ) {
5836         $attrsplit = explode( '=', $attribute, 2 );
5837         if( 1 < count( $attrsplit ))
5838           $propattr[$attrsplit[0]] = $attrsplit[1];
5839         else
5840           $propattr[] = $attribute;
5841       }
5842             /* call setProperty( $propname.. . */
5843       switch( strtoupper( $propname )) {
5844         case 'ATTENDEE':
5845           foreach( $propattr as $pix => $attr ) {
5846             if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
5847               continue;
5848             $attr2 = explode( ',', $attr );
5849               if( 1 < count( $attr2 ))
5850                 $propattr[$pix] = $attr2;
5851           }
5852           $this->setProperty( $propname, $line, $propattr );
5853           break;
5854         case 'X-':
5855           $propname = ( isset( $propname2 )) ? $propname2 : $propname;
5856           unset( $propname2 );
5857         case 'CATEGORIES':
5858         case 'RESOURCES':
5859           if( FALSE !== strpos( $line, ',' )) {
5860             $llen     = strlen( $line );
5861             $content  = array( 0 => '' );
5862             $cix      = 0;
5863             for( $lix = 0; $lix < $llen; $lix++ ) {
5864               if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
5865                 $cix++;
5866                 $content[$cix] = '';
5867               }
5868               else
5869                 $content[$cix] .= $line[$lix];
5870             }
5871             if( 1 < count( $content )) {
5872               $content = array_values( $content );
5873               foreach( $content as $cix => $contentPart )
5874                 $content[$cix] = calendarComponent::_strunrep( $contentPart );
5875               $this->setProperty( $propname, $content, $propattr );
5876               break;
5877             }
5878             else
5879               $line = reset( $content );
5880           }
5881         case 'COMMENT':
5882         case 'CONTACT':
5883         case 'DESCRIPTION':
5884         case 'LOCATION':
5885         case 'SUMMARY':
5886           if( empty( $line ))
5887             $propattr = null;
5888           $this->setProperty( $propname, calendarComponent::_strunrep( $line ), $propattr );
5889           break;
5890         case 'REQUEST-STATUS':
5891           $values    = explode( ';', $line, 3 );
5892           $values[1] = ( !isset( $values[1] )) ? null : calendarComponent::_strunrep( $values[1] );
5893           $values[2] = ( !isset( $values[2] )) ? null : calendarComponent::_strunrep( $values[2] );
5894           $this->setProperty( $propname
5895                             , $values[0]  // statcode
5896                             , $values[1]  // statdesc
5897                             , $values[2]  // extdata
5898                             , $propattr );
5899           break;
5900         case 'FREEBUSY':
5901           $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
5902           unset( $propattr['FBTYPE'] );
5903           $values = explode( ',', $line );
5904           foreach( $values as $vix => $value ) {
5905             $value2 = explode( '/', $value );
5906             if( 1 < count( $value2 ))
5907               $values[$vix] = $value2;
5908           }
5909           $this->setProperty( $propname, $fbtype, $values, $propattr );
5910           break;
5911         case 'GEO':
5912           $value = explode( ';', $line, 2 );
5913           if( 2 > count( $value ))
5914             $value[1] = null;
5915           $this->setProperty( $propname, $value[0], $value[1], $propattr );
5916           break;
5917         case 'EXDATE':
5918           $values = ( !empty( $line )) ? explode( ',', $line ) : null;
5919           $this->setProperty( $propname, $values, $propattr );
5920           break;
5921         case 'RDATE':
5922           if( empty( $line )) {
5923             $this->setProperty( $propname, $line, $propattr );
5924             break;
5925           }
5926           $values = explode( ',', $line );
5927           foreach( $values as $vix => $value ) {
5928             $value2 = explode( '/', $value );
5929             if( 1 < count( $value2 ))
5930               $values[$vix] = $value2;
5931           }
5932           $this->setProperty( $propname, $values, $propattr );
5933           break;
5934         case 'EXRULE':
5935         case 'RRULE':
5936           $values = explode( ';', $line );
5937           $recur = array();
5938           foreach( $values as $value2 ) {
5939             if( empty( $value2 ))
5940               continue; // ;-char in ending position ???
5941             $value3 = explode( '=', $value2, 2 );
5942             $rulelabel = strtoupper( $value3[0] );
5943             switch( $rulelabel ) {
5944               case 'BYDAY': {
5945                 $value4 = explode( ',', $value3[1] );
5946                 if( 1 < count( $value4 )) {
5947                   foreach( $value4 as $v5ix => $value5 ) {
5948                     $value6 = array();
5949                     $dayno = $dayname = null;
5950                     $value5 = trim( (string) $value5 );
5951                     if(( ctype_alpha( substr( $value5, -1 ))) &&
5952                        ( ctype_alpha( substr( $value5, -2, 1 )))) {
5953                       $dayname = substr( $value5, -2, 2 );
5954                       if( 2 < strlen( $value5 ))
5955                         $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
5956                     }
5957                     if( $dayno )
5958                       $value6[] = $dayno;
5959                     if( $dayname )
5960                       $value6['DAY'] = $dayname;
5961                     $value4[$v5ix] = $value6;
5962                   }
5963                 }
5964                 else {
5965                   $value4 = array();
5966                   $dayno  = $dayname = null;
5967                   $value5 = trim( (string) $value3[1] );
5968                   if(( ctype_alpha( substr( $value5, -1 ))) &&
5969                      ( ctype_alpha( substr( $value5, -2, 1 )))) {
5970                       $dayname = substr( $value5, -2, 2 );
5971                     if( 2 < strlen( $value5 ))
5972                       $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
5973                   }
5974                   if( $dayno )
5975                     $value4[] = $dayno;
5976                   if( $dayname )
5977                     $value4['DAY'] = $dayname;
5978                 }
5979                 $recur[$rulelabel] = $value4;
5980                 break;
5981               }
5982               default: {
5983                 $value4 = explode( ',', $value3[1] );
5984                 if( 1 < count( $value4 ))
5985                   $value3[1] = $value4;
5986                 $recur[$rulelabel] = $value3[1];
5987                 break;
5988               }
5989             } // end - switch $rulelabel
5990           } // end - foreach( $values.. .
5991           $this->setProperty( $propname, $recur, $propattr );
5992           break;
5993         case 'ACTION':
5994         case 'CLASSIFICATION':
5995         case 'STATUS':
5996         case 'TRANSP':
5997         case 'UID':
5998         case 'TZID':
5999         case 'RELATED-TO':
6000         case 'TZNAME':
6001           $line = calendarComponent::_strunrep( $line );
6002         default:
6003           $this->setProperty( $propname, $line, $propattr );
6004           break;
6005       } // end  switch( $propname.. .
6006     } // end - foreach( $proprows.. .
6007     unset( $unparsedtext, $this->unparsed, $proprows );
6008     if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
6009       $ckeys = array_keys( $this->components );
6010       foreach( $ckeys as $ckey ) {
6011         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
6012           $this->components[$ckey]->parse();
6013         }
6014       }
6015     }
6016     return TRUE;
6017   }
6018 /*********************************************************************************/
6019 /*********************************************************************************/
6020 /**
6021  * return a copy of this component
6022  *
6023  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6024  * @since 2.8.8 - 2011-03-15
6025  * @return object
6026  */
6027   function copy() {
6028     $serialized_contents = serialize( $this );
6029     $copy = unserialize( $serialized_contents );
6030     return $copy;
6031  }
6032 /*********************************************************************************/
6033 /*********************************************************************************/
6034 /**
6035  * delete calendar subcomponent from component container
6036  *
6037  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6038  * @since 2.8.8 - 2011-03-15
6039  * @param mixed $arg1 ordno / component type / component uid
6040  * @param mixed $arg2 optional, ordno if arg1 = component type
6041  * @return void
6042  */
6043   function deleteComponent( $arg1, $arg2=FALSE  ) {
6044     if( !isset( $this->components )) return FALSE;
6045     $argType = $index = null;
6046     if ( ctype_digit( (string) $arg1 )) {
6047       $argType = 'INDEX';
6048       $index   = (int) $arg1 - 1;
6049     }
6050     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
6051       $argType = strtolower( $arg1 );
6052       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
6053     }
6054     $cix2dC = 0;
6055     foreach ( $this->components as $cix => $component) {
6056       if( empty( $component )) continue;
6057       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
6058         unset( $this->components[$cix] );
6059         return TRUE;
6060       }
6061       elseif( $argType == $component->objName ) {
6062         if( $index == $cix2dC ) {
6063           unset( $this->components[$cix] );
6064           return TRUE;
6065         }
6066         $cix2dC++;
6067       }
6068       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
6069         unset( $this->components[$cix] );
6070         return TRUE;
6071       }
6072     }
6073     return FALSE;
6074   }
6075 /**
6076  * get calendar component subcomponent from component container
6077  *
6078  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6079  * @since 2.8.8 - 2011-03-15
6080  * @param mixed $arg1 optional, ordno/component type/ component uid
6081  * @param mixed $arg2 optional, ordno if arg1 = component type
6082  * @return object
6083  */
6084   function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
6085     if( !isset( $this->components )) return FALSE;
6086     $index = $argType = null;
6087     if ( !$arg1 ) {
6088       $argType = 'INDEX';
6089       $index   = $this->compix['INDEX'] =
6090         ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
6091     }
6092     elseif ( ctype_digit( (string) $arg1 )) {
6093       $argType = 'INDEX';
6094       $index   = (int) $arg1;
6095       unset( $this->compix );
6096     }
6097     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
6098       unset( $this->compix['INDEX'] );
6099       $argType = strtolower( $arg1 );
6100       if( !$arg2 )
6101         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
6102       else
6103         $index = (int) $arg2;
6104     }
6105     $index  -= 1;
6106     $ckeys = array_keys( $this->components );
6107     if( !empty( $index) && ( $index > end( $ckeys )))
6108       return FALSE;
6109     $cix2gC = 0;
6110     foreach( $this->components as $cix => $component ) {
6111       if( empty( $component )) continue;
6112       if(( 'INDEX' == $argType ) && ( $index == $cix ))
6113         return $component->copy();
6114       elseif( $argType == $component->objName ) {
6115          if( $index == $cix2gC )
6116            return $component->copy();
6117          $cix2gC++;
6118       }
6119       elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
6120         return $component->copy();
6121     }
6122             /* not found.. . */
6123     unset( $this->compix );
6124     return false;
6125   }
6126 /**
6127  * add calendar component as subcomponent to container for subcomponents
6128  *
6129  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6130  * @since 1.x.x - 2007-04-24
6131  * @param object $component calendar component
6132  * @return void
6133  */
6134   function addSubComponent ( $component ) {
6135     $this->setComponent( $component );
6136   }
6137 /**
6138  * create new calendar component subcomponent, already included within component
6139  *
6140  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6141  * @since 2.6.33 - 2011-01-03
6142  * @param string $compType subcomponent type
6143  * @return object (reference)
6144  */
6145   function & newComponent( $compType ) {
6146     $config = $this->getConfig();
6147     $keys   = array_keys( $this->components );
6148     $ix     = end( $keys) + 1;
6149     switch( strtoupper( $compType )) {
6150       case 'ALARM':
6151       case 'VALARM':
6152         $this->components[$ix] = new valarm( $config );
6153         break;
6154       case 'STANDARD':
6155         array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
6156         $ix = 0;
6157         break;
6158       case 'DAYLIGHT':
6159         $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
6160         break;
6161       default:
6162         return FALSE;
6163     }
6164     return $this->components[$ix];
6165   }
6166 /**
6167  * add calendar component as subcomponent to container for subcomponents
6168  *
6169  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6170  * @since 2.8.8 - 2011-03-15
6171  * @param object $component calendar component
6172  * @param mixed $arg1 optional, ordno/component type/ component uid
6173  * @param mixed $arg2 optional, ordno if arg1 = component type
6174  * @return bool
6175  */
6176   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
6177     if( !isset( $this->components )) return FALSE;
6178     $component->setConfig( $this->getConfig(), FALSE, TRUE );
6179     if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
6180             /* make sure dtstamp and uid is set */
6181       $dummy = $component->getProperty( 'dtstamp' );
6182       $dummy = $component->getProperty( 'uid' );
6183     }
6184     if( !$arg1 ) { // plain insert, last in chain
6185       $this->components[] = $component->copy();
6186       return TRUE;
6187     }
6188     $argType = $index = null;
6189     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
6190       $argType = 'INDEX';
6191       $index   = (int) $arg1 - 1;
6192     }
6193     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
6194       $argType = strtolower( $arg1 );
6195       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
6196     }
6197     // else if arg1 is set, arg1 must be an UID
6198     $cix2sC = 0;
6199     foreach ( $this->components as $cix => $component2 ) {
6200       if( empty( $component2 )) continue;
6201       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
6202         $this->components[$cix] = $component->copy();
6203         return TRUE;
6204       }
6205       elseif( $argType == $component2->objName ) { // component Type index insert/replace
6206         if( $index == $cix2sC ) {
6207           $this->components[$cix] = $component->copy();
6208           return TRUE;
6209         }
6210         $cix2sC++;
6211       }
6212       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
6213         $this->components[$cix] = $component->copy();
6214         return TRUE;
6215       }
6216     }
6217             /* arg1=index and not found.. . insert at index .. .*/
6218     if( 'INDEX' == $argType ) {
6219       $this->components[$index] = $component->copy();
6220       ksort( $this->components, SORT_NUMERIC );
6221     }
6222     else    /* not found.. . insert last in chain anyway .. .*/
6223     $this->components[] = $component->copy();
6224     return TRUE;
6225   }
6226 /**
6227  * creates formatted output for subcomponents
6228  *
6229  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6230  * @since 2.11.20 - 2012-02-06
6231  * @param array $xcaldecl
6232  * @return string
6233  */
6234   function createSubComponent() {
6235     $output = null;
6236     if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
6237       $stdarr = $dlarr = array();
6238       foreach( $this->components as $component ) {
6239         if( empty( $component ))
6240           continue;
6241         $dt  = $component->getProperty( 'dtstart' );
6242         $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
6243         if( 'standard' == $component->objName ) {
6244           while( isset( $stdarr[$key] ))
6245             $key += 1;
6246           $stdarr[$key] = $component->copy();
6247         }
6248         elseif( 'daylight' == $component->objName ) {
6249           while( isset( $dlarr[$key] ))
6250             $key += 1;
6251           $dlarr[$key] = $component->copy();
6252         }
6253       } // end foreach( $this->components as $component )
6254       $this->components = array();
6255       ksort( $stdarr, SORT_NUMERIC );
6256       foreach( $stdarr as $std )
6257         $this->components[] = $std->copy();
6258       unset( $stdarr );
6259       ksort( $dlarr,  SORT_NUMERIC );
6260       foreach( $dlarr as $dl )
6261         $this->components[] = $dl->copy();
6262       unset( $dlarr );
6263     } // end if( 'vtimezone' == $this->objName )
6264     foreach( $this->components as $component ) {
6265       $component->setConfig( $this->getConfig(), FALSE, TRUE );
6266       $output .= $component->createComponent( $this->xcaldecl );
6267     }
6268     return $output;
6269   }
6270 /********************************************************************************/
6271 /**
6272  * break lines at pos 75
6273  *
6274  * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
6275  * break. Long content lines SHOULD be split into a multiple line
6276  * representations using a line "folding" technique. That is, a long
6277  * line can be split between any two characters by inserting a CRLF
6278  * immediately followed by a single linear white space character (i.e.,
6279  * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
6280  * of CRLF followed immediately by a single linear white space character
6281  * is ignored (i.e., removed) when processing the content type.
6282  *
6283  * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
6284  * the reserved expression "\n" in the arg $string could be broken up by the
6285  * folding of lines, causing ambiguity in the return string.
6286  * Fix uses var $breakAtChar=75 and breaks the line at $breakAtChar-1 if need be.
6287  *
6288  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6289  * @since 2.11.13 - 2012-02-14
6290  * @param string $value
6291  * @return string
6292  */
6293   function _size75( $string ) {
6294     $tmp        = $string;
6295     $string     = '';
6296     $eolcharlen = strlen( '\n' );
6297             /* if PHP is config with  mb_string and conf overload.. . */
6298     if( defined( 'MB_OVERLOAD_STRING' ) && ( 1 < ini_get( 'mbstring.func_overload' ))) {
6299       $strlen  = mb_strlen( $tmp );
6300       while( $strlen > 75 ) {
6301         if( '\n' == mb_substr( $tmp, 75, $eolcharlen ))
6302           $breakAtChar = 74;
6303         else
6304           $breakAtChar = 75;
6305         $string .= mb_substr( $tmp, 0, $breakAtChar );
6306         if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl ))))
6307           $string .= $this->nl;
6308         $tmp     = mb_substr( $tmp, $breakAtChar );
6309         if( !empty( $tmp ))
6310           $tmp   = ' '.$tmp;
6311         $strlen  = mb_strlen( $tmp );
6312       } // end while
6313       if( 0 < $strlen ) {
6314         $string .= $tmp; // the rest
6315         if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl ))))
6316           $string .= $this->nl;
6317       }
6318       return $string;
6319     }
6320             /* if PHP is not config with  mb_string.. . */
6321     while( TRUE ) {
6322       $bytecnt = strlen( $tmp );
6323       $charCnt = $ix = 0;
6324       for( $ix = 0; $ix < $bytecnt; $ix++ ) {
6325         if(( 73 < $charCnt ) && ( '\n' == substr( $tmp, $ix, $eolcharlen )))
6326           break;                                    // break before '\n'
6327         elseif( 74 < $charCnt ) {
6328           if( '\n' == substr( $tmp, $ix, $eolcharlen ))
6329             $ix -= 1;                               // don't break inside '\n'
6330           break;                                    // always break while-loop here
6331         }
6332         else {
6333           $byte = ord( $tmp[$ix] );
6334           if ($byte <= 127) {                       // add a one byte character
6335             $string .= substr( $tmp, $ix, 1 );
6336             $charCnt += 1;
6337           }
6338           else if ($byte >= 194 && $byte <= 223) {  // start byte in two byte character
6339             $string .= substr( $tmp, $ix, 2 );      // add a two bytes character
6340             $charCnt += 1;
6341           }
6342           else if ($byte >= 224 && $byte <= 239) {  // start byte in three bytes character
6343             $string .= substr( $tmp, $ix, 3 );      // add a three bytes character
6344             $charCnt += 1;
6345           }
6346           else if ($byte >= 240 && $byte <= 244) {  // start byte in four bytes character
6347             $string .= substr( $tmp, $ix, 4 );      // add a four bytes character
6348             $charCnt += 1;
6349           }
6350         }
6351       } // end for
6352       if( $this->nl != substr( $string, ( 0 - strlen( $this->nl ))))
6353         $string .= $this->nl;
6354       if( FALSE === ( $tmp = substr( $tmp, $ix )))
6355         break; // while-loop breakes here
6356       else
6357         $tmp  = ' '.$tmp;
6358     } // end while
6359     if( '\n'.$this->nl == substr( $string, ( 0 - strlen( '\n'.$this->nl ))))
6360       $string = substr( $string, 0, ( strlen( $string ) - strlen( '\n'.$this->nl ))).$this->nl;
6361     return $string;
6362   }
6363 /**
6364  * special characters management output
6365  *
6366  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6367  * @since 2.6.15 - 2010-09-24
6368  * @param string $string
6369  * @return string
6370  */
6371   function _strrep( $string ) {
6372     switch( $this->format ) {
6373       case 'xcal':
6374         $string = str_replace( '\n',  $this->nl, $string);
6375         $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
6376         break;
6377       default:
6378         $pos = 0;
6379         $specChars = array( 'n', 'N', 'r', ',', ';' );
6380         while( $pos <= strlen( $string )) {
6381           $pos = strpos( $string, "\\", $pos );
6382           if( FALSE === $pos )
6383             break;
6384           if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
6385             $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
6386             $pos += 1;
6387           }
6388           $pos += 1;
6389         }
6390         if( FALSE !== strpos( $string, '"' ))
6391           $string = str_replace('"',   "'",       $string);
6392         if( FALSE !== strpos( $string, ',' ))
6393           $string = str_replace(',',   '\,',      $string);
6394         if( FALSE !== strpos( $string, ';' ))
6395           $string = str_replace(';',   '\;',      $string);
6396
6397         if( FALSE !== strpos( $string, "\r\n" ))
6398           $string = str_replace( "\r\n", '\n',    $string);
6399         elseif( FALSE !== strpos( $string, "\r" ))
6400           $string = str_replace( "\r", '\n',      $string);
6401
6402         elseif( FALSE !== strpos( $string, "\n" ))
6403           $string = str_replace( "\n", '\n',      $string);
6404
6405         if( FALSE !== strpos( $string, '\N' ))
6406           $string = str_replace( '\N', '\n',      $string);
6407 //        if( FALSE !== strpos( $string, $this->nl ))
6408           $string = str_replace( $this->nl, '\n', $string);
6409         break;
6410     }
6411     return $string;
6412   }
6413 /**
6414  * special characters management input (from iCal file)
6415  *
6416  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6417  * @since 2.6.22 - 2010-10-17
6418  * @param string $string
6419  * @return string
6420  */
6421   static function _strunrep( $string ) {
6422     $string = str_replace( '\\\\', '\\',     $string);
6423     $string = str_replace( '\,',   ',',      $string);
6424     $string = str_replace( '\;',   ';',      $string);
6425 //    $string = str_replace( '\n',  $this->nl, $string); // ??
6426     return $string;
6427   }
6428 }
6429 /*********************************************************************************/
6430 /*********************************************************************************/
6431 /**
6432  * class for calendar component VEVENT
6433  *
6434  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6435  * @since 2.5.1 - 2008-10-12
6436  */
6437 class vevent extends calendarComponent {
6438   var $attach;
6439   var $attendee;
6440   var $categories;
6441   var $comment;
6442   var $contact;
6443   var $class;
6444   var $created;
6445   var $description;
6446   var $dtend;
6447   var $dtstart;
6448   var $duration;
6449   var $exdate;
6450   var $exrule;
6451   var $geo;
6452   var $lastmodified;
6453   var $location;
6454   var $organizer;
6455   var $priority;
6456   var $rdate;
6457   var $recurrenceid;
6458   var $relatedto;
6459   var $requeststatus;
6460   var $resources;
6461   var $rrule;
6462   var $sequence;
6463   var $status;
6464   var $summary;
6465   var $transp;
6466   var $url;
6467   var $xprop;
6468             //  component subcomponents container
6469   var $components;
6470 /**
6471  * constructor for calendar component VEVENT object
6472  *
6473  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6474  * @since 2.8.2 - 2011-05-01
6475  * @param  array $config
6476  * @return void
6477  */
6478   function vevent( $config = array()) {
6479     $this->calendarComponent();
6480
6481     $this->attach          = '';
6482     $this->attendee        = '';
6483     $this->categories      = '';
6484     $this->class           = '';
6485     $this->comment         = '';
6486     $this->contact         = '';
6487     $this->created         = '';
6488     $this->description     = '';
6489     $this->dtstart         = '';
6490     $this->dtend           = '';
6491     $this->duration        = '';
6492     $this->exdate          = '';
6493     $this->exrule          = '';
6494     $this->geo             = '';
6495     $this->lastmodified    = '';
6496     $this->location        = '';
6497     $this->organizer       = '';
6498     $this->priority        = '';
6499     $this->rdate           = '';
6500     $this->recurrenceid    = '';
6501     $this->relatedto       = '';
6502     $this->requeststatus   = '';
6503     $this->resources       = '';
6504     $this->rrule           = '';
6505     $this->sequence        = '';
6506     $this->status          = '';
6507     $this->summary         = '';
6508     $this->transp          = '';
6509     $this->url             = '';
6510     $this->xprop           = '';
6511
6512     $this->components      = array();
6513
6514     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6515                                           $config['language']   = ICAL_LANG;
6516     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6517     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6518     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6519     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6520     $this->setConfig( $config );
6521
6522   }
6523 /**
6524  * create formatted output for calendar component VEVENT object instance
6525  *
6526  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6527  * @since 2.10.16 - 2011-10-28
6528  * @param array $xcaldecl
6529  * @return string
6530  */
6531   function createComponent( &$xcaldecl ) {
6532     $objectname    = $this->_createFormat();
6533     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6534     $component    .= $this->createUid();
6535     $component    .= $this->createDtstamp();
6536     $component    .= $this->createAttach();
6537     $component    .= $this->createAttendee();
6538     $component    .= $this->createCategories();
6539     $component    .= $this->createComment();
6540     $component    .= $this->createContact();
6541     $component    .= $this->createClass();
6542     $component    .= $this->createCreated();
6543     $component    .= $this->createDescription();
6544     $component    .= $this->createDtstart();
6545     $component    .= $this->createDtend();
6546     $component    .= $this->createDuration();
6547     $component    .= $this->createExdate();
6548     $component    .= $this->createExrule();
6549     $component    .= $this->createGeo();
6550     $component    .= $this->createLastModified();
6551     $component    .= $this->createLocation();
6552     $component    .= $this->createOrganizer();
6553     $component    .= $this->createPriority();
6554     $component    .= $this->createRdate();
6555     $component    .= $this->createRrule();
6556     $component    .= $this->createRelatedTo();
6557     $component    .= $this->createRequestStatus();
6558     $component    .= $this->createRecurrenceid();
6559     $component    .= $this->createResources();
6560     $component    .= $this->createSequence();
6561     $component    .= $this->createStatus();
6562     $component    .= $this->createSummary();
6563     $component    .= $this->createTransp();
6564     $component    .= $this->createUrl();
6565     $component    .= $this->createXprop();
6566     $component    .= $this->createSubComponent();
6567     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6568     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6569       foreach( $this->xcaldecl as $localxcaldecl )
6570         $xcaldecl[] = $localxcaldecl;
6571     }
6572     return $component;
6573   }
6574 }
6575 /*********************************************************************************/
6576 /*********************************************************************************/
6577 /**
6578  * class for calendar component VTODO
6579  *
6580  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6581  * @since 2.5.1 - 2008-10-12
6582  */
6583 class vtodo extends calendarComponent {
6584   var $attach;
6585   var $attendee;
6586   var $categories;
6587   var $comment;
6588   var $completed;
6589   var $contact;
6590   var $class;
6591   var $created;
6592   var $description;
6593   var $dtstart;
6594   var $due;
6595   var $duration;
6596   var $exdate;
6597   var $exrule;
6598   var $geo;
6599   var $lastmodified;
6600   var $location;
6601   var $organizer;
6602   var $percentcomplete;
6603   var $priority;
6604   var $rdate;
6605   var $recurrenceid;
6606   var $relatedto;
6607   var $requeststatus;
6608   var $resources;
6609   var $rrule;
6610   var $sequence;
6611   var $status;
6612   var $summary;
6613   var $url;
6614   var $xprop;
6615             //  component subcomponents container
6616   var $components;
6617 /**
6618  * constructor for calendar component VTODO object
6619  *
6620  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6621  * @since 2.8.2 - 2011-05-01
6622  * @param array $config
6623  * @return void
6624  */
6625   function vtodo( $config = array()) {
6626     $this->calendarComponent();
6627
6628     $this->attach          = '';
6629     $this->attendee        = '';
6630     $this->categories      = '';
6631     $this->class           = '';
6632     $this->comment         = '';
6633     $this->completed       = '';
6634     $this->contact         = '';
6635     $this->created         = '';
6636     $this->description     = '';
6637     $this->dtstart         = '';
6638     $this->due             = '';
6639     $this->duration        = '';
6640     $this->exdate          = '';
6641     $this->exrule          = '';
6642     $this->geo             = '';
6643     $this->lastmodified    = '';
6644     $this->location        = '';
6645     $this->organizer       = '';
6646     $this->percentcomplete = '';
6647     $this->priority        = '';
6648     $this->rdate           = '';
6649     $this->recurrenceid    = '';
6650     $this->relatedto       = '';
6651     $this->requeststatus   = '';
6652     $this->resources       = '';
6653     $this->rrule           = '';
6654     $this->sequence        = '';
6655     $this->status          = '';
6656     $this->summary         = '';
6657     $this->url             = '';
6658     $this->xprop           = '';
6659
6660     $this->components      = array();
6661
6662     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6663                                           $config['language']   = ICAL_LANG;
6664     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6665     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6666     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6667     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6668     $this->setConfig( $config );
6669
6670   }
6671 /**
6672  * create formatted output for calendar component VTODO object instance
6673  *
6674  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6675  * @since 2.5.1 - 2008-11-07
6676  * @param array $xcaldecl
6677  * @return string
6678  */
6679   function createComponent( &$xcaldecl ) {
6680     $objectname    = $this->_createFormat();
6681     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6682     $component    .= $this->createUid();
6683     $component    .= $this->createDtstamp();
6684     $component    .= $this->createAttach();
6685     $component    .= $this->createAttendee();
6686     $component    .= $this->createCategories();
6687     $component    .= $this->createClass();
6688     $component    .= $this->createComment();
6689     $component    .= $this->createCompleted();
6690     $component    .= $this->createContact();
6691     $component    .= $this->createCreated();
6692     $component    .= $this->createDescription();
6693     $component    .= $this->createDtstart();
6694     $component    .= $this->createDue();
6695     $component    .= $this->createDuration();
6696     $component    .= $this->createExdate();
6697     $component    .= $this->createExrule();
6698     $component    .= $this->createGeo();
6699     $component    .= $this->createLastModified();
6700     $component    .= $this->createLocation();
6701     $component    .= $this->createOrganizer();
6702     $component    .= $this->createPercentComplete();
6703     $component    .= $this->createPriority();
6704     $component    .= $this->createRdate();
6705     $component    .= $this->createRelatedTo();
6706     $component    .= $this->createRequestStatus();
6707     $component    .= $this->createRecurrenceid();
6708     $component    .= $this->createResources();
6709     $component    .= $this->createRrule();
6710     $component    .= $this->createSequence();
6711     $component    .= $this->createStatus();
6712     $component    .= $this->createSummary();
6713     $component    .= $this->createUrl();
6714     $component    .= $this->createXprop();
6715     $component    .= $this->createSubComponent();
6716     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6717     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6718       foreach( $this->xcaldecl as $localxcaldecl )
6719         $xcaldecl[] = $localxcaldecl;
6720     }
6721     return $component;
6722   }
6723 }
6724 /*********************************************************************************/
6725 /*********************************************************************************/
6726 /**
6727  * class for calendar component VJOURNAL
6728  *
6729  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6730  * @since 2.5.1 - 2008-10-12
6731  */
6732 class vjournal extends calendarComponent {
6733   var $attach;
6734   var $attendee;
6735   var $categories;
6736   var $comment;
6737   var $contact;
6738   var $class;
6739   var $created;
6740   var $description;
6741   var $dtstart;
6742   var $exdate;
6743   var $exrule;
6744   var $lastmodified;
6745   var $organizer;
6746   var $rdate;
6747   var $recurrenceid;
6748   var $relatedto;
6749   var $requeststatus;
6750   var $rrule;
6751   var $sequence;
6752   var $status;
6753   var $summary;
6754   var $url;
6755   var $xprop;
6756 /**
6757  * constructor for calendar component VJOURNAL object
6758  *
6759  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6760  * @since 2.8.2 - 2011-05-01
6761  * @param array $config
6762  * @return void
6763  */
6764   function vjournal( $config = array()) {
6765     $this->calendarComponent();
6766
6767     $this->attach          = '';
6768     $this->attendee        = '';
6769     $this->categories      = '';
6770     $this->class           = '';
6771     $this->comment         = '';
6772     $this->contact         = '';
6773     $this->created         = '';
6774     $this->description     = '';
6775     $this->dtstart         = '';
6776     $this->exdate          = '';
6777     $this->exrule          = '';
6778     $this->lastmodified    = '';
6779     $this->organizer       = '';
6780     $this->rdate           = '';
6781     $this->recurrenceid    = '';
6782     $this->relatedto       = '';
6783     $this->requeststatus   = '';
6784     $this->rrule           = '';
6785     $this->sequence        = '';
6786     $this->status          = '';
6787     $this->summary         = '';
6788     $this->url             = '';
6789     $this->xprop           = '';
6790
6791     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6792                                           $config['language']   = ICAL_LANG;
6793     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6794     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6795     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6796     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6797     $this->setConfig( $config );
6798
6799   }
6800 /**
6801  * create formatted output for calendar component VJOURNAL object instance
6802  *
6803  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6804  * @since 2.5.1 - 2008-10-12
6805  * @param array $xcaldecl
6806  * @return string
6807  */
6808   function createComponent( &$xcaldecl ) {
6809     $objectname = $this->_createFormat();
6810     $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6811     $component .= $this->createUid();
6812     $component .= $this->createDtstamp();
6813     $component .= $this->createAttach();
6814     $component .= $this->createAttendee();
6815     $component .= $this->createCategories();
6816     $component .= $this->createClass();
6817     $component .= $this->createComment();
6818     $component .= $this->createContact();
6819     $component .= $this->createCreated();
6820     $component .= $this->createDescription();
6821     $component .= $this->createDtstart();
6822     $component .= $this->createExdate();
6823     $component .= $this->createExrule();
6824     $component .= $this->createLastModified();
6825     $component .= $this->createOrganizer();
6826     $component .= $this->createRdate();
6827     $component .= $this->createRequestStatus();
6828     $component .= $this->createRecurrenceid();
6829     $component .= $this->createRelatedTo();
6830     $component .= $this->createRrule();
6831     $component .= $this->createSequence();
6832     $component .= $this->createStatus();
6833     $component .= $this->createSummary();
6834     $component .= $this->createUrl();
6835     $component .= $this->createXprop();
6836     $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
6837     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6838       foreach( $this->xcaldecl as $localxcaldecl )
6839         $xcaldecl[] = $localxcaldecl;
6840     }
6841     return $component;
6842   }
6843 }
6844 /*********************************************************************************/
6845 /*********************************************************************************/
6846 /**
6847  * class for calendar component VFREEBUSY
6848  *
6849  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6850  * @since 2.5.1 - 2008-10-12
6851  */
6852 class vfreebusy extends calendarComponent {
6853   var $attendee;
6854   var $comment;
6855   var $contact;
6856   var $dtend;
6857   var $dtstart;
6858   var $duration;
6859   var $freebusy;
6860   var $organizer;
6861   var $requeststatus;
6862   var $url;
6863   var $xprop;
6864             //  component subcomponents container
6865   var $components;
6866 /**
6867  * constructor for calendar component VFREEBUSY object
6868  *
6869  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6870  * @since 2.8.2 - 2011-05-01
6871  * @param array $config
6872  * @return void
6873  */
6874   function vfreebusy( $config = array()) {
6875     $this->calendarComponent();
6876
6877     $this->attendee        = '';
6878     $this->comment         = '';
6879     $this->contact         = '';
6880     $this->dtend           = '';
6881     $this->dtstart         = '';
6882     $this->duration        = '';
6883     $this->freebusy        = '';
6884     $this->organizer       = '';
6885     $this->requeststatus   = '';
6886     $this->url             = '';
6887     $this->xprop           = '';
6888
6889     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6890                                           $config['language']   = ICAL_LANG;
6891     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6892     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6893     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6894     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6895     $this->setConfig( $config );
6896
6897   }
6898 /**
6899  * create formatted output for calendar component VFREEBUSY object instance
6900  *
6901  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6902  * @since 2.3.1 - 2007-11-19
6903  * @param array $xcaldecl
6904  * @return string
6905  */
6906   function createComponent( &$xcaldecl ) {
6907     $objectname = $this->_createFormat();
6908     $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6909     $component .= $this->createUid();
6910     $component .= $this->createDtstamp();
6911     $component .= $this->createAttendee();
6912     $component .= $this->createComment();
6913     $component .= $this->createContact();
6914     $component .= $this->createDtstart();
6915     $component .= $this->createDtend();
6916     $component .= $this->createDuration();
6917     $component .= $this->createFreebusy();
6918     $component .= $this->createOrganizer();
6919     $component .= $this->createRequestStatus();
6920     $component .= $this->createUrl();
6921     $component .= $this->createXprop();
6922     $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
6923     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6924       foreach( $this->xcaldecl as $localxcaldecl )
6925         $xcaldecl[] = $localxcaldecl;
6926     }
6927     return $component;
6928   }
6929 }
6930 /*********************************************************************************/
6931 /*********************************************************************************/
6932 /**
6933  * class for calendar component VALARM
6934  *
6935  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6936  * @since 2.5.1 - 2008-10-12
6937  */
6938 class valarm extends calendarComponent {
6939   var $action;
6940   var $attach;
6941   var $attendee;
6942   var $description;
6943   var $duration;
6944   var $repeat;
6945   var $summary;
6946   var $trigger;
6947   var $xprop;
6948 /**
6949  * constructor for calendar component VALARM object
6950  *
6951  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6952  * @since 2.8.2 - 2011-05-01
6953  * @param array $config
6954  * @return void
6955  */
6956   function valarm( $config = array()) {
6957     $this->calendarComponent();
6958
6959     $this->action          = '';
6960     $this->attach          = '';
6961     $this->attendee        = '';
6962     $this->description     = '';
6963     $this->duration        = '';
6964     $this->repeat          = '';
6965     $this->summary         = '';
6966     $this->trigger         = '';
6967     $this->xprop           = '';
6968
6969     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6970                                           $config['language']   = ICAL_LANG;
6971     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6972     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6973     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6974     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6975     $this->setConfig( $config );
6976
6977   }
6978 /**
6979  * create formatted output for calendar component VALARM object instance
6980  *
6981  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6982  * @since 2.5.1 - 2008-10-22
6983  * @param array $xcaldecl
6984  * @return string
6985  */
6986   function createComponent( &$xcaldecl ) {
6987     $objectname    = $this->_createFormat();
6988     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6989     $component    .= $this->createAction();
6990     $component    .= $this->createAttach();
6991     $component    .= $this->createAttendee();
6992     $component    .= $this->createDescription();
6993     $component    .= $this->createDuration();
6994     $component    .= $this->createRepeat();
6995     $component    .= $this->createSummary();
6996     $component    .= $this->createTrigger();
6997     $component    .= $this->createXprop();
6998     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6999     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
7000       foreach( $this->xcaldecl as $localxcaldecl )
7001         $xcaldecl[] = $localxcaldecl;
7002     }
7003     return $component;
7004   }
7005 }
7006 /**********************************************************************************
7007 /*********************************************************************************/
7008 /**
7009  * class for calendar component VTIMEZONE
7010  *
7011  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7012  * @since 2.5.1 - 2008-10-12
7013  */
7014 class vtimezone extends calendarComponent {
7015   var $timezonetype;
7016
7017   var $comment;
7018   var $dtstart;
7019   var $lastmodified;
7020   var $rdate;
7021   var $rrule;
7022   var $tzid;
7023   var $tzname;
7024   var $tzoffsetfrom;
7025   var $tzoffsetto;
7026   var $tzurl;
7027   var $xprop;
7028             //  component subcomponents container
7029   var $components;
7030 /**
7031  * constructor for calendar component VTIMEZONE object
7032  *
7033  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7034  * @since 2.8.2 - 2011-05-01
7035  * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
7036  * @param array $config
7037  * @return void
7038  */
7039   function vtimezone( $timezonetype=FALSE, $config = array()) {
7040     if( is_array( $timezonetype )) {
7041       $config       = $timezonetype;
7042       $timezonetype = FALSE;
7043     }
7044     if( !$timezonetype )
7045       $this->timezonetype = 'VTIMEZONE';
7046     else
7047       $this->timezonetype = strtoupper( $timezonetype );
7048     $this->calendarComponent();
7049
7050     $this->comment         = '';
7051     $this->dtstart         = '';
7052     $this->lastmodified    = '';
7053     $this->rdate           = '';
7054     $this->rrule           = '';
7055     $this->tzid            = '';
7056     $this->tzname          = '';
7057     $this->tzoffsetfrom    = '';
7058     $this->tzoffsetto      = '';
7059     $this->tzurl           = '';
7060     $this->xprop           = '';
7061
7062     $this->components      = array();
7063
7064     if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
7065                                           $config['language']   = ICAL_LANG;
7066     if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
7067     if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
7068     if( !isset( $config['format'] ))      $config['format']     = 'iCal';
7069     if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
7070     $this->setConfig( $config );
7071
7072   }
7073 /**
7074  * create formatted output for calendar component VTIMEZONE object instance
7075  *
7076  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7077  * @since 2.5.1 - 2008-10-25
7078  * @param array $xcaldecl
7079  * @return string
7080  */
7081   function createComponent( &$xcaldecl ) {
7082     $objectname    = $this->_createFormat();
7083     $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
7084     $component    .= $this->createTzid();
7085     $component    .= $this->createLastModified();
7086     $component    .= $this->createTzurl();
7087     $component    .= $this->createDtstart();
7088     $component    .= $this->createTzoffsetfrom();
7089     $component    .= $this->createTzoffsetto();
7090     $component    .= $this->createComment();
7091     $component    .= $this->createRdate();
7092     $component    .= $this->createRrule();
7093     $component    .= $this->createTzname();
7094     $component    .= $this->createXprop();
7095     $component    .= $this->createSubComponent();
7096     $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
7097     if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
7098       foreach( $this->xcaldecl as $localxcaldecl )
7099         $xcaldecl[] = $localxcaldecl;
7100     }
7101     return $component;
7102   }
7103 }
7104 /*********************************************************************************/
7105 /*********************************************************************************/
7106 /**
7107  * moving all utility (static) functions to a utility class
7108  * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
7109  *
7110  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7111  * @since 2.10.1 - 2011-07-16
7112  *
7113  */
7114 class iCalUtilityFunctions {
7115   // Store the single instance of iCalUtilityFunctions
7116   private static $m_pInstance;
7117
7118   // Private constructor to limit object instantiation to within the class
7119   private function __construct() {
7120     $m_pInstance = FALSE;
7121   }
7122
7123   // Getter method for creating/returning the single instance of this class
7124   public static function getInstance() {
7125     if (!self::$m_pInstance)
7126       self::$m_pInstance = new iCalUtilityFunctions();
7127
7128     return self::$m_pInstance;
7129   }
7130 /**
7131  * check a date(-time) for an opt. timezone and if it is a DATE-TIME or DATE
7132  *
7133  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7134  * @since 2.10.30 - 2012-01-16
7135  * @param array $date, date to check
7136  * @param int $parno, no of date parts (i.e. year, month.. .)
7137  * @return array $params, property parameters
7138  */
7139   public static function _chkdatecfg( $theDate, & $parno, & $params ) {
7140     if( isset( $params['TZID'] ))
7141       $parno = 6;
7142     elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
7143       $parno = 3;
7144     else {
7145       if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
7146         $parno = 7;
7147       if( is_array( $theDate )) {
7148         if( isset( $theDate['timestamp'] ))
7149           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
7150         else
7151           $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
7152         if( !empty( $tzid )) {
7153           $parno = 7;
7154           if( !iCalUtilityFunctions::_isOffset( $tzid ))
7155             $params['TZID'] = $tzid; // save only timezone
7156         }
7157         elseif( !$parno && ( 3 == count( $theDate )) &&
7158           ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
7159           $parno = 3;
7160         else
7161           $parno = 6;
7162       }
7163       else { // string
7164         $date = trim( $theDate );
7165         if( 'Z' == substr( $date, -1 ))
7166           $parno = 7; // UTC DATE-TIME
7167         elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
7168           ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
7169           $parno = 3; // DATE
7170         $date = iCalUtilityFunctions::_date_time_string( $date, $parno );
7171         unset( $date['unparsedtext'] );
7172         if( !empty( $date['tz'] )) {
7173           $parno = 7;
7174           if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
7175             $params['TZID'] = $date['tz']; // save only timezone
7176         }
7177         elseif( empty( $parno ))
7178           $parno = 6;
7179       }
7180       if( isset( $params['TZID'] ))
7181         $parno = 6;
7182     }
7183   }
7184 /**
7185  * create timezone and standard/daylight components
7186  *
7187  * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
7188  *
7189  * BEGIN:VTIMEZONE
7190  * TZID:Europe/Stockholm
7191  * BEGIN:STANDARD
7192  * DTSTART:20101031T020000
7193  * TZOFFSETFROM:+0200
7194  * TZOFFSETTO:+0100
7195  * TZNAME:CET
7196  * END:STANDARD
7197  * BEGIN:DAYLIGHT
7198  * DTSTART:20100328T030000
7199  * TZOFFSETFROM:+0100
7200  * TZOFFSETTO:+0200
7201  * TZNAME:CEST
7202  * END:DAYLIGHT
7203  * END:VTIMEZONE
7204  *
7205  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7206  * @since 2.11.8 - 2012-02-06
7207  * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
7208  * @param object $calendar, reference to an iCalcreator calendar instance
7209  * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
7210  * @param array  $xProp,    *[x-propName => x-propValue], optional
7211  * @param int    $from      an unix timestamp
7212  * @param int    $to        an unix timestamp
7213  * @return bool
7214  */
7215   public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
7216     if( !class_exists( 'DateTimeZone' ))
7217       return FALSE;
7218     if( empty( $timezone ))
7219       return FALSE;
7220     try {
7221       $dtz               = new DateTimeZone( $timezone );
7222       $transitions       = $dtz->getTransitions();
7223       unset( $dtz );
7224       $utcTz             = new DateTimeZone( 'UTC' );
7225     }
7226     catch( Exception $e ) {
7227       return FALSE;
7228     }
7229     if( empty( $to ))
7230       $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
7231     $transCnt            = 2; // number of transitions in output if empty input $from/$to and an empty dates-array
7232     $dateFrom            = new DateTime( 'now' );
7233     $dateTo              = new DateTime( 'now' );
7234     if( !empty( $from ))
7235       $dateFrom->setTimestamp( $from );
7236     else {
7237       if( !empty( $dates ))
7238         $dateFrom = new DateTime( reset( $dates ));              // set lowest date to the lowest dtstart date
7239       $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date
7240     }
7241     $dateFrom->setTimezone( $utcTz );                            // convert local date to UTC
7242     if( !empty( $to ))
7243       $dateTo->setTimestamp( $to );
7244     else {
7245       if( !empty( $dates )) {
7246         $dateTo          = new DateTime( end( $dates ));         // set highest date to the highest dtstart date
7247         $to              = $dateTo->getTimestamp();              // set mark that a highest date is found
7248       }
7249       $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date
7250     }
7251     $dateTo->setTimezone( $utcTz );                              // convert local date to UTC
7252     $transTemp           = array();
7253     $prevOffsetfrom      = $stdCnt = $dlghtCnt = 0;
7254     $stdIx  = $dlghtIx   = null;
7255     $date = new DateTime( 'now', $utcTz );
7256     foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
7257       $date->setTimestamp( $trans['ts'] );                       // set transition date (UTC)
7258       if ( $date < $dateFrom ) {
7259         $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
7260         continue;
7261       }
7262       if( $date > $dateTo )
7263         break;                                                   // loop always (?) breaks here
7264       if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
7265         $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
7266         $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
7267         $trans['time'] = array( 'year'  => $date->format( 'Y' )  // set dtstart to array to ease up dtstart and (opt) rdate setting
7268                               , 'month' => $date->format( 'n' )
7269                               , 'day'   => $date->format( 'j' )
7270                               , 'hour'  => $date->format( 'G' )
7271                               , 'min'   => $date->format( 'i' )
7272                               , 'sec'   => $date->format( 's' )); 
7273       }
7274       $prevOffsetfrom    = $trans['offset'];
7275       $trans['prevYear'] = $trans['time']['year'];
7276       if( TRUE !== $trans['isdst'] ) {                           // standard timezone
7277         if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any rdate's (in strict year order)
7278            ( $transTemp[$stdIx]['abbr']          == $trans['abbr'] )       &&
7279            ( $transTemp[$stdIx]['offsetfrom']    == $trans['offsetfrom'] ) &&
7280            ( $transTemp[$stdIx]['offset']        == $trans['offset'] )     &&
7281            (($transTemp[$stdIx]['prevYear'] + 1) == $trans['time']['year'] )) {
7282           $transTemp[$stdIx]['prevYear'] = $trans['time']['year'];
7283           $transTemp[$stdIx]['rdate'][]  = $trans['time'];
7284           continue;
7285         }
7286         $stdIx           = $tix;
7287         $stdCnt         += 1;
7288       } // end standard timezone
7289       else {                                                     // daylight timezone
7290         if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any rdate's (in strict year order)
7291            ( $transTemp[$dlghtIx]['abbr']          == $trans['abbr'] )           &&
7292            ( $transTemp[$dlghtIx]['offsetfrom']    == $trans['offsetfrom'] )     &&
7293            ( $transTemp[$dlghtIx]['offset']        == $trans['offset'] )         &&
7294            (($transTemp[$dlghtIx]['prevYear'] + 1) == $trans['time']['year'] )) {
7295           $transTemp[$dlghtIx]['prevYear'] = $trans['time']['year'];
7296           $transTemp[$dlghtIx]['rdate'][]  = $trans['time'];
7297           continue;
7298         }
7299         $dlghtIx         = $tix;
7300         $dlghtCnt       += 1;
7301       } // end daylight timezone
7302       if( empty( $to ) && ( $transCnt == count( $transTemp ))) { // store only $transCnt transitions
7303         if( TRUE !== $transTemp[0]['isdst'] )
7304           $stdCnt       -= 1;
7305         else
7306          $dlghtCnt      -= 1;
7307         array_shift( $transTemp );
7308       } // end if( empty( $to ) && ( $transCnt == count( $transTemp )))
7309       $transTemp[$tix]   = $trans;
7310     } // end foreach( $transitions as $tix => $trans )
7311     unset( $transitions );
7312     if( empty( $transTemp ))
7313       return FALSE;
7314     $tz  = & $calendar->newComponent( 'vtimezone' );
7315     $tz->setproperty( 'tzid', $timezone );
7316     if( !empty( $xProp )) {
7317       foreach( $xProp as $xPropName => $xPropValue )
7318         if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
7319           $tz->setproperty( $xPropName, $xPropValue );
7320     }
7321     foreach( $transTemp as $trans ) {
7322       $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
7323       $scomp = & $tz->newComponent( $type );
7324       $scomp->setProperty( 'dtstart',         $trans['time'] );
7325 //      $scomp->setProperty( 'x-utc-timestamp', $trans['ts'] );   // test ###
7326       if( !empty( $trans['abbr'] ))
7327         $scomp->setProperty( 'tzname',        $trans['abbr'] );
7328       $scomp->setProperty( 'tzoffsetfrom',    iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
7329       $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
7330       if( isset( $trans['rdate'] ))
7331         $scomp->setProperty( 'RDATE',         $trans['rdate'] );
7332     }
7333     return TRUE;
7334   }
7335 /**
7336  * convert a date/datetime (array) to timestamp
7337  *
7338  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7339  * @since 2.4.8 - 2008-10-30
7340  * @param array  $datetime  datetime/(date)
7341  * @param string $tz        timezone
7342  * @return timestamp
7343  */
7344   public static function _date2timestamp( $datetime, $tz=null ) {
7345     $output = null;
7346     if( !isset( $datetime['hour'] )) $datetime['hour'] = '0';
7347     if( !isset( $datetime['min'] ))  $datetime['min']  = '0';
7348     if( !isset( $datetime['sec'] ))  $datetime['sec']  = '0';
7349     foreach( $datetime as $dkey => $dvalue ) {
7350       if( 'tz' != $dkey )
7351         $datetime[$dkey] = (integer) $dvalue;
7352     }
7353     if( $tz )
7354       $datetime['tz'] = $tz;
7355     $offset = ( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) ? iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) : 0;
7356     $output = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year'] );
7357     return $output;
7358   }
7359 /**
7360  * ensures internal date-time/date format for input date-time/date in array format
7361  *
7362  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7363  * @since 2.11.4 - 2012-03-18
7364  * @param array $datetime
7365  * @param int $parno optional, default FALSE
7366  * @return array
7367  */
7368   public static function _date_time_array( $datetime, $parno=FALSE ) {
7369     $output = array();
7370     foreach( $datetime as $dateKey => $datePart ) {
7371       switch ( $dateKey ) {
7372         case '0': case 'year':   $output['year']  = $datePart; break;
7373         case '1': case 'month':  $output['month'] = $datePart; break;
7374         case '2': case 'day':    $output['day']   = $datePart; break;
7375       }
7376       if( 3 != $parno ) {
7377         switch ( $dateKey ) {
7378           case '0':
7379           case '1':
7380           case '2': break;
7381           case '3': case 'hour': $output['hour']  = $datePart; break;
7382           case '4': case 'min' : $output['min']   = $datePart; break;
7383           case '5': case 'sec' : $output['sec']   = $datePart; break;
7384           case '6': case 'tz'  : $output['tz']    = $datePart; break;
7385         }
7386       }
7387     }
7388     if( 3 != $parno ) {
7389       if( !isset( $output['hour'] ))
7390         $output['hour'] = 0;
7391       if( !isset( $output['min']  ))
7392         $output['min'] = 0;
7393       if( !isset( $output['sec']  ))
7394         $output['sec'] = 0;
7395       if( isset( $output['tz'] ) && ( 'Z' != $output['tz'] ) &&
7396         (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
7397           $output['tz'] = 'Z';
7398     }
7399     return $output;
7400   }
7401 /**
7402  * ensures internal date-time/date format for input date-time/date in string fromat
7403  *
7404  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7405  * @since 2.10.30 - 2012-01-06
7406  * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
7407  * @param array $datetime
7408  * @param int $parno optional, default FALSE
7409  * @return array
7410  */
7411   public static function _date_time_string( $datetime, $parno=FALSE ) {
7412     // save original input string to return it later
7413     $unparseddatetime = $datetime;
7414     $datetime = (string) trim( $datetime );
7415     $tz  = null;
7416     $len = strlen( $datetime ) - 1;
7417     if( 'Z' == substr( $datetime, -1 )) {
7418       $tz = 'Z';
7419       $datetime = trim( substr( $datetime, 0, $len ));
7420     }
7421     elseif( ( ctype_digit( substr( $datetime, -2, 2 ))) && // time or date
7422                   ( '-' == substr( $datetime, -3, 1 )) ||
7423                   ( ':' == substr( $datetime, -3, 1 )) ||
7424                   ( '.' == substr( $datetime, -3, 1 ))) {
7425       $continue = TRUE;
7426     }
7427     elseif( ( ctype_digit( substr( $datetime, -4, 4 ))) && // 4 pos offset
7428             ( ' +' == substr( $datetime, -6, 2 )) ||
7429             ( ' -' == substr( $datetime, -6, 2 ))) {
7430       $tz = substr( $datetime, -5, 5 );
7431       $datetime = substr( $datetime, 0, ($len - 5));
7432     }
7433     elseif( ( ctype_digit( substr( $datetime, -6, 6 ))) && // 6 pos offset
7434             ( ' +' == substr( $datetime, -8, 2 )) ||
7435             ( ' -' == substr( $datetime, -8, 2 ))) {
7436       $tz = substr( $datetime, -7, 7 );
7437       $datetime = substr( $datetime, 0, ($len - 7));
7438     }
7439     elseif( ( 6 < $len ) && ( ctype_digit( substr( $datetime, -6, 6 )))) {
7440       $continue = TRUE;
7441     }
7442     elseif( 'T' ==  substr( $datetime, -7, 1 )) {
7443       $continue = TRUE;
7444     }
7445     else {
7446       $cx  = $tx = 0;    //  19970415T133000 US-Eastern
7447       for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
7448         $char = substr( $datetime, $cx, 1 );
7449         if(( ' ' == $char) || ctype_digit( $char))
7450           break; // if exists, tz ends here.. . ?
7451         else
7452            $tx--; // tz length counter
7453       }
7454       if( 0 > $tx ) {
7455         $tz = substr( $datetime, $tx );
7456         $datetime = trim( substr( $datetime, 0, $len + $tx + 1 ));
7457       }
7458     }
7459     if( 0 < substr_count( $datetime, '-' )) {
7460       $datetime = str_replace( '-', '/', $datetime );
7461     }
7462     elseif( ctype_digit( substr( $datetime, 0, 8 )) &&
7463            ( 'T' ==      substr( $datetime, 8, 1 )) &&
7464             ctype_digit( substr( $datetime, 9, 6 ))) {
7465      }
7466     $datestring = date( 'Y-m-d H:i:s', strtotime( $datetime ));
7467     $tz                = trim( $tz );
7468     $output            = array();
7469     $output['year']    = substr( $datestring, 0, 4 );
7470     $output['month']   = substr( $datestring, 5, 2 );
7471     $output['day']     = substr( $datestring, 8, 2 );
7472     if(( 6 == $parno ) || ( 7 == $parno ) || ( !$parno && ( 'Z' == $tz ))) {
7473       $output['hour']  = substr( $datestring, 11, 2 );
7474       $output['min']   = substr( $datestring, 14, 2 );
7475       $output['sec']   = substr( $datestring, 17, 2 );
7476       if( !empty( $tz ))
7477         $output['tz']  = $tz;
7478     }
7479     elseif( 3 != $parno ) {
7480       if(( '00' < substr( $datestring, 11, 2 )) ||
7481          ( '00' < substr( $datestring, 14, 2 )) ||
7482          ( '00' < substr( $datestring, 17, 2 ))) {
7483         $output['hour']  = substr( $datestring, 11, 2 );
7484         $output['min']   = substr( $datestring, 14, 2 );
7485         $output['sec']   = substr( $datestring, 17, 2 );
7486       }
7487       if( !empty( $tz ))
7488         $output['tz']  = $tz;
7489     }
7490     // return original string in the array in case strtotime failed to make sense of it
7491     $output['unparsedtext']    = $unparseddatetime;
7492     return $output;
7493   }
7494 /**
7495  * convert local startdate/enddate (Ymd[His]) to duration array
7496  *
7497  * uses this component dates if missing input dates
7498  *
7499  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7500  * @since 2.6.11 - 2010-10-21
7501  * @param array $startdate
7502  * @param array $duration
7503  * @return array duration
7504  */
7505   public static function _date2duration( $startdate, $enddate ) {
7506     $startWdate  = mktime( 0, 0, 0, $startdate['month'], $startdate['day'], $startdate['year'] );
7507     $endWdate    = mktime( 0, 0, 0, $enddate['month'],   $enddate['day'],   $enddate['year'] );
7508     $wduration   = $endWdate - $startWdate;
7509     $dur         = array();
7510     $dur['week'] = (int) floor( $wduration / ( 7 * 24 * 60 * 60 ));
7511     $wduration   =              $wduration % ( 7 * 24 * 60 * 60 );
7512     $dur['day']  = (int) floor( $wduration / ( 24 * 60 * 60 ));
7513     $wduration   =              $wduration % ( 24 * 60 * 60 );
7514     $dur['hour'] = (int) floor( $wduration / ( 60 * 60 ));
7515     $wduration   =              $wduration % ( 60 * 60 );
7516     $dur['min']  = (int) floor( $wduration / ( 60 ));
7517     $dur['sec']  = (int)        $wduration % ( 60 );
7518     return $dur;
7519   }
7520 /**
7521  * ensures internal duration format for input in array format
7522  *
7523  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7524  * @since 2.1.1 - 2007-06-24
7525  * @param array $duration
7526  * @return array
7527  */
7528   public static function _duration_array( $duration ) {
7529     $output = array();
7530     if(    is_array( $duration )        &&
7531        ( 1 == count( $duration ))       &&
7532               isset( $duration['sec'] ) &&
7533               ( 60 < $duration['sec'] )) {
7534       $durseconds  = $duration['sec'];
7535       $output['week'] = floor( $durseconds / ( 60 * 60 * 24 * 7 ));
7536       $durseconds  =           $durseconds % ( 60 * 60 * 24 * 7 );
7537       $output['day']  = floor( $durseconds / ( 60 * 60 * 24 ));
7538       $durseconds  =           $durseconds % ( 60 * 60 * 24 );
7539       $output['hour'] = floor( $durseconds / ( 60 * 60 ));
7540       $durseconds  =           $durseconds % ( 60 * 60 );
7541       $output['min']  = floor( $durseconds / ( 60 ));
7542       $output['sec']  =      ( $durseconds % ( 60 ));
7543     }
7544     else {
7545       foreach( $duration as $durKey => $durValue ) {
7546         if( empty( $durValue )) continue;
7547         switch ( $durKey ) {
7548           case '0': case 'week': $output['week']  = $durValue; break;
7549           case '1': case 'day':  $output['day']   = $durValue; break;
7550           case '2': case 'hour': $output['hour']  = $durValue; break;
7551           case '3': case 'min':  $output['min']   = $durValue; break;
7552           case '4': case 'sec':  $output['sec']   = $durValue; break;
7553         }
7554       }
7555     }
7556     if( isset( $output['week'] ) && ( 0 < $output['week'] )) {
7557       unset( $output['day'], $output['hour'], $output['min'], $output['sec'] );
7558       return $output;
7559     }
7560     unset( $output['week'] );
7561     if( empty( $output['day'] ))
7562       unset( $output['day'] );
7563     if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) {
7564       if( !isset( $output['hour'] )) $output['hour'] = 0;
7565       if( !isset( $output['min']  )) $output['min']  = 0;
7566       if( !isset( $output['sec']  )) $output['sec']  = 0;
7567       if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
7568         unset( $output['hour'], $output['min'], $output['sec'] );
7569     }
7570     return $output;
7571   }
7572 /**
7573  * ensures internal duration format for input in string format
7574  *
7575  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7576  * @since 2.0.5 - 2007-03-14
7577  * @param string $duration
7578  * @return array
7579  */
7580   public static function _duration_string( $duration ) {
7581     $duration = (string) trim( $duration );
7582     while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
7583       if( 0 < strlen( $duration ))
7584         $duration = substr( $duration, 1 );
7585       else
7586         return false; // no leading P !?!?
7587     }
7588     $duration = substr( $duration, 1 ); // skip P
7589     $duration = str_replace ( 't', 'T', $duration );
7590     $duration = str_replace ( 'T', '', $duration );
7591     $output = array();
7592     $val    = null;
7593     for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
7594       switch( strtoupper( substr( $duration, $ix, 1 ))) {
7595        case 'W':
7596          $output['week'] = $val;
7597          $val            = null;
7598          break;
7599        case 'D':
7600          $output['day']  = $val;
7601          $val            = null;
7602          break;
7603        case 'H':
7604          $output['hour'] = $val;
7605          $val            = null;
7606          break;
7607        case 'M':
7608          $output['min']  = $val;
7609          $val            = null;
7610          break;
7611        case 'S':
7612          $output['sec']  = $val;
7613          $val            = null;
7614          break;
7615        default:
7616          if( !ctype_digit( substr( $duration, $ix, 1 )))
7617            return false; // unknown duration control character  !?!?
7618          else
7619            $val .= substr( $duration, $ix, 1 );
7620       }
7621     }
7622     return iCalUtilityFunctions::_duration_array( $output );
7623   }
7624 /**
7625  * convert duration to date in array format
7626  *
7627  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7628  * @since 2.8.7 - 2011-03-03
7629  * @param array $startdate
7630  * @param array $duration
7631  * @return array, date format
7632  */
7633   public static function _duration2date( $startdate=null, $duration=null ) {
7634     if( empty( $startdate )) return FALSE;
7635     if( empty( $duration ))  return FALSE;
7636     $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
7637     $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
7638     $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
7639     $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
7640     $dtend = 0;
7641     if(    isset( $duration['week'] ))
7642       $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
7643     if(    isset( $duration['day'] ))
7644       $dtend += ( $duration['day'] * 24 * 60 * 60 );
7645     if(    isset( $duration['hour'] ))
7646       $dtend += ( $duration['hour'] * 60 *60 );
7647     if(    isset( $duration['min'] ))
7648       $dtend += ( $duration['min'] * 60 );
7649     if(    isset( $duration['sec'] ))
7650       $dtend +=   $duration['sec'];
7651     $dtend  = mktime( $startdate['hour'], $startdate['min'], ( $startdate['sec'] + $dtend ), $startdate['month'], $startdate['day'], $startdate['year'] );
7652     $dtend2 = array();
7653     $dtend2['year']   = date('Y', $dtend );
7654     $dtend2['month']  = date('m', $dtend );
7655     $dtend2['day']    = date('d', $dtend );
7656     $dtend2['hour']   = date('H', $dtend );
7657     $dtend2['min']    = date('i', $dtend );
7658     $dtend2['sec']    = date('s', $dtend );
7659     if( isset( $startdate['tz'] ))
7660       $dtend2['tz']   = $startdate['tz'];
7661     if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
7662       unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
7663     return $dtend2;
7664   }
7665 /**
7666  * if not preSet, if exist, remove key with expected value from array and return hit value else return elseValue
7667  *
7668  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7669  * @since 2.4.16 - 2008-11-08
7670  * @param array $array
7671  * @param string $expkey, expected key
7672  * @param string $expval, expected value
7673  * @param int $hitVal optional, return value if found
7674  * @param int $elseVal optional, return value if not found
7675  * @param int $preSet optional, return value if already preset
7676  * @return int
7677  */
7678   public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
7679     if( $preSet )
7680       return $preSet;
7681     if( !is_array( $array ) || ( 0 == count( $array )))
7682       return $elseVal;
7683     foreach( $array as $key => $value ) {
7684       if( strtoupper( $expkey ) == strtoupper( $key )) {
7685         if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
7686           unset( $array[$key] );
7687           return $hitVal;
7688         }
7689       }
7690     }
7691     return $elseVal;
7692   }
7693 /**
7694  * creates formatted output for calendar component property data value type date/date-time
7695  *
7696  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7697  * @since 2.11.8 - 2012-03-17
7698  * @param array   $datetime
7699  * @param int     $parno, optional, default 6
7700  * @return string
7701  */
7702   public static function _format_date_time( $datetime, $parno=6 ) {
7703     if( !isset( $datetime['year'] )  &&
7704         !isset( $datetime['month'] ) &&
7705         !isset( $datetime['day'] )   &&
7706         !isset( $datetime['hour'] )  &&
7707         !isset( $datetime['min'] )   &&
7708         !isset( $datetime['sec'] ))
7709       return ;
7710     $output = null;
7711     foreach( $datetime as $dkey => & $dvalue )
7712       if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
7713     $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
7714     if( isset( $datetime['hour'] )  ||
7715         isset( $datetime['min'] )   ||
7716         isset( $datetime['sec'] )   ||
7717         isset( $datetime['tz'] )) {
7718       if( isset( $datetime['tz'] )  &&
7719          !isset( $datetime['hour'] ))
7720         $datetime['hour'] = 0;
7721       if( isset( $datetime['hour'] )  &&
7722          !isset( $datetime['min'] ))
7723         $datetime['min'] = 0;
7724       if( isset( $datetime['hour'] )  &&
7725           isset( $datetime['min'] )   &&
7726          !isset( $datetime['sec'] ))
7727         $datetime['sec'] = 0;
7728       $output .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
7729       if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
7730         $datetime['tz'] = trim( $datetime['tz'] );
7731         if( 'Z' == $datetime['tz'] )
7732           $output .= 'Z';
7733         $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
7734         if( 0 != $offset ) {
7735           $date   = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year']);
7736           $output = date( 'Ymd\THis\Z', $date );
7737         }
7738       }
7739       elseif( 7 == $parno )
7740         $output .= 'Z';
7741     }
7742     return $output;
7743   }
7744 /**
7745  * creates formatted output for calendar component property data value type duration
7746  *
7747  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7748  * @since 2.9.9 - 2011-06-17
7749  * @param array $duration ( week, day, hour, min, sec )
7750  * @return string
7751  */
7752   public static function _format_duration( $duration ) {
7753     if( isset( $duration['week'] ) ||
7754         isset( $duration['day'] )  ||
7755         isset( $duration['hour'] ) ||
7756         isset( $duration['min'] )  ||
7757         isset( $duration['sec'] ))
7758        $ok = TRUE;
7759     else
7760       return;
7761     if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
7762       return 'P'.$duration['week'].'W';
7763     $output = 'P';
7764     if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
7765       $output .= $duration['day'].'D';
7766     if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
7767        ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
7768        ( isset( $duration['sec'])  && ( 0 < $duration['sec'] )))
7769       $output .= 'T';
7770     $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '';
7771     $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '';
7772     $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '';
7773     if( 'P' == $output )
7774       $output = 'PT0S';
7775     return $output;
7776   }
7777 /**
7778  * checks if input array contains a date
7779  *
7780  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7781  * @since 2.11.8 - 2012-01-20
7782  * @param array $input
7783  * @return bool
7784  */
7785   public static function _isArrayDate( $input ) {
7786     if( !is_array( $input ))
7787       return FALSE;
7788     if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 ))))
7789       return FALSE;
7790     if( 7 == count( $input ))
7791       return TRUE;
7792     if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
7793       return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
7794     if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
7795       return FALSE;
7796     if( in_array( 0, $input ))
7797       return FALSE;
7798     if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
7799       return FALSE;
7800     if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
7801          checkdate( (int) $input[1], (int) $input[2], (int) $input[0] ))
7802       return TRUE;
7803     $input = iCalUtilityFunctions::_date_time_string( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
7804     if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
7805       return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
7806     return FALSE;
7807   }
7808 /**
7809  * checks if input array contains a timestamp date
7810  *
7811  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7812  * @since 2.4.16 - 2008-10-18
7813  * @param array $input
7814  * @return bool
7815  */
7816   public static function _isArrayTimestampDate( $input ) {
7817     return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
7818   }
7819 /**
7820  * controll if input string contains trailing UTC offset
7821  *
7822  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7823  * @since 2.4.16 - 2008-10-19
7824  * @param string $input
7825  * @return bool
7826  */
7827   public static function _isOffset( $input ) {
7828     $input         = trim( (string) $input );
7829     if( 'Z' == substr( $input, -1 ))
7830       return TRUE;
7831     elseif((   5 <= strlen( $input )) &&
7832        ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
7833        (   '0000'  < substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
7834       return TRUE;
7835     elseif((    7 <= strlen( $input )) &&
7836        ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
7837        ( '000000'  < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
7838       return TRUE;
7839     return FALSE;
7840   }
7841 /**
7842  * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
7843  * matching (MS) UCT offset and time zone descriptors
7844  *
7845  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7846  * @since 2.10.29 - 2012-01-11
7847  * @param string $timezone, input/output variable reference
7848  * @return bool
7849  */
7850   public static function ms2phpTZ( & $timezone ) {
7851     if( !class_exists( 'DateTimeZone' ))
7852       return FALSE;
7853     if( empty( $timezone ))
7854       return FALSE;
7855     $search = str_replace( '"', '', $timezone );
7856     $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
7857     if( '(UTC' != substr( $search, 0, 4 ))
7858       return FALSE;
7859     if( FALSE === ( $pos = strpos( $search, ')' )))
7860       return FALSE;
7861     $pos    = strpos( $search, ')' );
7862     $searchOffset = substr( $search, 4, ( $pos - 4 ));
7863     $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
7864     while( ' ' ==substr( $search, ( $pos + 1 )))
7865       $pos += 1;
7866     $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
7867     $searchWords  = explode( ' ', $searchText );
7868     $timezone_abbreviations = DateTimeZone::listAbbreviations();
7869     $hits = array();
7870     foreach( $timezone_abbreviations as $name => $transitions ) {
7871       foreach( $transitions as $cnt => $transition ) {
7872         if( empty( $transition['offset'] )      ||
7873             empty( $transition['timezone_id'] ) ||
7874           ( $transition['offset'] != $searchOffset ))
7875         continue;
7876         $cWords = explode( '/', $transition['timezone_id'] );
7877         $cPrio   = $hitCnt = $rank = 0;
7878         foreach( $cWords as $cWord ) {
7879           if( empty( $cWord ))
7880             continue;
7881           $cPrio += 1;
7882           $sPrio  = 0;
7883           foreach( $searchWords as $sWord ) {
7884             if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
7885               continue;
7886             $sPrio += 1;
7887             if( strtolower( $cWord ) == strtolower( $sWord )) {
7888               $hitCnt += 1;
7889               $rank   += ( $cPrio + $sPrio );
7890             }
7891             else
7892               $rank += 10;
7893           }
7894         }
7895         if( 0 < $hitCnt ) {
7896           $hits[$rank][] = $transition['timezone_id'];
7897         }
7898       }
7899     }
7900     unset( $timezone_abbreviations );
7901     if( empty( $hits ))
7902       return FALSE;
7903     ksort( $hits );
7904     foreach( $hits as $rank => $tzs ) {
7905       if( !empty( $tzs )) {
7906         $timezone = reset( $tzs );
7907         return TRUE;
7908       }
7909     }
7910     return FALSE;
7911   }
7912 /**
7913  * transform offset in seconds to [-/+]hhmm[ss]
7914  *
7915  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7916  * @since 2011-05-02
7917  * @param string $seconds
7918  * @return string
7919  */
7920   public static function offsetSec2His( $seconds ) {
7921     if( '-' == substr( $seconds, 0, 1 )) {
7922       $prefix  = '-';
7923       $seconds = substr( $seconds, 1 );
7924     }
7925     elseif( '+' == substr( $seconds, 0, 1 )) {
7926       $prefix  = '+';
7927       $seconds = substr( $seconds, 1 );
7928     }
7929     else
7930       $prefix  = '+';
7931     $output  = '';
7932     $hour    = (int) floor( $seconds / 3600 );
7933     if( 10 > $hour )
7934       $hour  = '0'.$hour;
7935     $seconds = $seconds % 3600;
7936     $min     = (int) floor( $seconds / 60 );
7937     if( 10 > $min )
7938       $min   = '0'.$min;
7939     $output  = $hour.$min;
7940     $seconds = $seconds % 60;
7941     if( 0 < $seconds) {
7942       if( 9 < $seconds)
7943         $output .= $seconds;
7944       else
7945         $output .= '0'.$seconds;
7946     }
7947     return $prefix.$output;
7948   }
7949 /**
7950  * remakes a recur pattern to an array of dates
7951  *
7952  * if missing, UNTIL is set 1 year from startdate (emergency break)
7953  *
7954  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7955  * @since 2.10.19 - 2011-10-31
7956  * @param array $result, array to update, array([timestamp] => timestamp)
7957  * @param array $recur, pattern for recurrency (only value part, params ignored)
7958  * @param array $wdate, component start date
7959  * @param array $startdate, start date
7960  * @param array $enddate, optional
7961  * @return array of recurrence (start-)dates as index
7962  * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
7963  */
7964   public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
7965     foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
7966     $wdateStart  = $wdate;
7967     $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate );
7968     $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
7969     if( !$enddate ) {
7970       $enddate = $startdate;
7971       $enddate['year'] += 1;
7972     }
7973 // echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br />\n";print_r($recur);echo "<br />\n";//test###
7974     $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
7975     if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
7976       $recur['UNTIL'] = $enddate; // create break
7977     if( isset( $recur['UNTIL'] )) {
7978       $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
7979       if( $endDatets > $tdatets ) {
7980         $endDatets = $tdatets; // emergency break
7981         $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
7982       }
7983       else
7984         $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
7985     }
7986     if( $wdatets > $endDatets ) {
7987 // echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
7988       return array(); // nothing to do.. .
7989     }
7990     if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
7991       $recur['FREQ'] = 'DAILY'; // ??
7992     $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
7993     $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
7994     if( !isset( $recur['INTERVAL'] ))
7995       $recur['INTERVAL'] = 1;
7996     $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
7997             /* find out how to step up dates and set index for interval count */
7998     $step = array();
7999     if( 'YEARLY' == $recur['FREQ'] )
8000       $step['year']  = 1;
8001     elseif( 'MONTHLY' == $recur['FREQ'] )
8002       $step['month'] = 1;
8003     elseif( 'WEEKLY' == $recur['FREQ'] )
8004       $step['day']   = 7;
8005     else
8006       $step['day']   = 1;
8007     if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
8008       $step = array( 'month' => 1 );
8009     if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
8010       $step = array( 'day' => 7 );
8011     if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
8012       $step = array( 'day' => 1 );
8013     $intervalarr = array();
8014     if( 1 < $recur['INTERVAL'] ) {
8015       $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
8016       $intervalarr = array( $intervalix => 0 );
8017     }
8018     if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
8019       $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
8020 // echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br />\n"; // test ###
8021       if( is_array( $recur['BYSETPOS'] )) {
8022         foreach( $recur['BYSETPOS'] as $bix => $bval )
8023           $recur['BYSETPOS'][$bix] = (int) $bval;
8024       }
8025       else
8026         $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
8027       if( 'YEARLY' == $recur['FREQ'] ) {
8028         $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
8029         $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
8030         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
8031       }
8032       elseif( 'MONTHLY' == $recur['FREQ'] ) {
8033         $wdate['day']   = 1; // start from beginning of month
8034         $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
8035         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
8036       }
8037       else
8038         iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
8039 // echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br />\n";//test###
8040       $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
8041       $bysetposYold = $wdate['year'];
8042       $bysetposMold = $wdate['month'];
8043       $bysetposDold = $wdate['day'];
8044     }
8045     else
8046       iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8047     $year_old     = null;
8048     $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
8049              /* MAIN LOOP */
8050 // echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br />\n";//test
8051     while( TRUE ) {
8052       if( isset( $endDatets ) && ( $wdatets > $endDatets ))
8053         break;
8054       if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
8055         break;
8056       if( $year_old != $wdate['year'] ) {
8057         $year_old   = $wdate['year'];
8058         $daycnts    = array();
8059         $yeardays   = $weekno = 0;
8060         $yeardaycnt = array();
8061         foreach( $daynames as $dn )
8062           $yeardaycnt[$dn] = 0;
8063         for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
8064           $daycnts[$m] = array();
8065           $weekdaycnt = array();
8066           foreach( $daynames as $dn )
8067             $weekdaycnt[$dn] = 0;
8068           $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
8069           for( $d   = 1; $d <= $mcnt; $d++ ) {
8070             $daycnts[$m][$d] = array();
8071             if( isset( $recur['BYYEARDAY'] )) {
8072               $yeardays++;
8073               $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
8074             }
8075             if( isset( $recur['BYDAY'] )) {
8076               $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
8077               $day    = $daynames[$day];
8078               $daycnts[$m][$d]['DAY'] = $day;
8079               $weekdaycnt[$day]++;
8080               $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
8081               $yeardaycnt[$day]++;
8082               $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
8083             }
8084             if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
8085               $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
8086           }
8087         }
8088         $daycnt = 0;
8089         $yeardaycnt = array();
8090         if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
8091           $weekno = null;
8092           for( $d=31; $d > 25; $d-- ) { // get last weekno for year
8093             if( !$weekno )
8094               $weekno = $daycnts[12][$d]['weekno_up'];
8095             elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
8096               $weekno = $daycnts[12][$d]['weekno_up'];
8097               break;
8098             }
8099           }
8100         }
8101         for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
8102           $weekdaycnt = array();
8103           foreach( $daynames as $dn )
8104             $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
8105           $monthcnt = 0;
8106           $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
8107           for( $d   = $mcnt; $d > 0; $d-- ) {
8108             if( isset( $recur['BYYEARDAY'] )) {
8109               $daycnt -= 1;
8110               $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
8111             }
8112             if( isset( $recur['BYMONTHDAY'] )) {
8113               $monthcnt -= 1;
8114               $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
8115             }
8116             if( isset( $recur['BYDAY'] )) {
8117               $day  = $daycnts[$m][$d]['DAY'];
8118               $weekdaycnt[$day] -= 1;
8119               $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
8120               $yeardaycnt[$day] -= 1;
8121               $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
8122             }
8123             if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
8124               $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
8125           }
8126         }
8127       }
8128             /* check interval */
8129       if( 1 < $recur['INTERVAL'] ) {
8130             /* create interval index */
8131         $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
8132             /* check interval */
8133         $currentKey = array_keys( $intervalarr );
8134         $currentKey = end( $currentKey ); // get last index
8135         if( $currentKey != $intervalix )
8136           $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
8137         if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
8138            ( 0 != $intervalarr[$intervalix] )) {
8139             /* step up date */
8140 // echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
8141           iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8142           continue;
8143         }
8144         else // continue within the selected interval
8145           $intervalarr[$intervalix] = 0;
8146 // echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br />\n";//test
8147       }
8148       $updateOK = TRUE;
8149       if( $updateOK && isset( $recur['BYMONTH'] ))
8150         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
8151                                            , $wdate['month']
8152                                            ,($wdate['month'] - 13));
8153       if( $updateOK && isset( $recur['BYWEEKNO'] ))
8154         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
8155                                            , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
8156                                            , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
8157       if( $updateOK && isset( $recur['BYYEARDAY'] ))
8158         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
8159                                            , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
8160                                            , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
8161       if( $updateOK && isset( $recur['BYMONTHDAY'] ))
8162         $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
8163                                            , $wdate['day']
8164                                            , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
8165 // echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n";//test###
8166       if( $updateOK && isset( $recur['BYDAY'] )) {
8167         $updateOK = FALSE;
8168         $m = $wdate['month'];
8169         $d = $wdate['day'];
8170         if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
8171           $daynoexists = $daynosw = $daynamesw =  FALSE;
8172           if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
8173             $daynamesw = TRUE;
8174           if( isset( $recur['BYDAY'][0] )) {
8175             $daynoexists = TRUE;
8176             if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
8177               $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
8178                                                 , $daycnts[$m][$d]['monthdayno_up']
8179                                                 , $daycnts[$m][$d]['monthdayno_down'] );
8180             elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
8181               $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
8182                                                 , $daycnts[$m][$d]['yeardayno_up']
8183                                                 , $daycnts[$m][$d]['yeardayno_down'] );
8184           }
8185           if((  $daynoexists &&  $daynosw && $daynamesw ) ||
8186              ( !$daynoexists && !$daynosw && $daynamesw )) {
8187             $updateOK = TRUE;
8188 // echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
8189           }
8190 //echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br />\n"; // test ###
8191         }
8192         else {
8193           foreach( $recur['BYDAY'] as $bydayvalue ) {
8194             $daynoexists = $daynosw = $daynamesw = FALSE;
8195             if( isset( $bydayvalue['DAY'] ) &&
8196                      ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
8197               $daynamesw = TRUE;
8198             if( isset( $bydayvalue[0] )) {
8199               $daynoexists = TRUE;
8200               if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
8201                    isset( $recur['BYMONTH'] ))
8202                 $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
8203                                                   , $daycnts[$m][$d]['monthdayno_up']
8204                                                   , $daycnts[$m][$d]['monthdayno_down'] );
8205               elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
8206                 $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
8207                                                   , $daycnts[$m][$d]['yeardayno_up']
8208                                                   , $daycnts[$m][$d]['yeardayno_down'] );
8209             }
8210 // echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br />\n"; // test ###
8211             if((  $daynoexists &&  $daynosw && $daynamesw ) ||
8212                ( !$daynoexists && !$daynosw && $daynamesw )) {
8213               $updateOK = TRUE;
8214               break;
8215             }
8216           }
8217         }
8218       }
8219 // echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br />\n"; // test ###
8220             /* check BYSETPOS */
8221       if( $updateOK ) {
8222         if( isset( $recur['BYSETPOS'] ) &&
8223           ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
8224           if( isset( $recur['WEEKLY'] )) {
8225             if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
8226               $bysetposw1[] = $wdatets;
8227             else
8228               $bysetposw2[] = $wdatets;
8229           }
8230           else {
8231             if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
8232                                             ( $bysetposYold == $wdate['year'] ))   ||
8233                ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
8234                                            (( $bysetposYold == $wdate['year'] )  &&
8235                                             ( $bysetposMold == $wdate['month'] ))) ||
8236                ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
8237                                            (( $bysetposYold == $wdate['year'] )  &&
8238                                             ( $bysetposMold == $wdate['month'])  &&
8239                                             ( $bysetposDold == $wdate['day'] )))) {
8240 // echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
8241               $bysetposymd1[] = $wdatets;
8242             }
8243             else {
8244 // echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
8245               $bysetposymd2[] = $wdatets;
8246             }
8247           }
8248         }
8249         else {
8250             /* update result array if BYSETPOS is set */
8251           $countcnt++;
8252           if( $startdatets <= $wdatets ) { // only output within period
8253             $result[$wdatets] = TRUE;
8254 // echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br />\n";//test
8255           }
8256 // echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br />\n";//test
8257           $updateOK = FALSE;
8258         }
8259       }
8260             /* step up date */
8261       iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8262             /* check if BYSETPOS is set for updating result array */
8263       if( $updateOK && isset( $recur['BYSETPOS'] )) {
8264         $bysetpos       = FALSE;
8265         if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
8266           ( $bysetposYold != $wdate['year'] )) {
8267           $bysetpos     = TRUE;
8268           $bysetposYold = $wdate['year'];
8269         }
8270         elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
8271          (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
8272           $bysetpos     = TRUE;
8273           $bysetposYold = $wdate['year'];
8274           $bysetposMold = $wdate['month'];
8275         }
8276         elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
8277           $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
8278           if( $bysetposWold != $weekno ) {
8279             $bysetposWold = $weekno;
8280             $bysetpos     = TRUE;
8281           }
8282         }
8283         elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
8284          (( $bysetposYold != $wdate['year'] )  ||
8285           ( $bysetposMold != $wdate['month'] ) ||
8286           ( $bysetposDold != $wdate['day'] ))) {
8287           $bysetpos     = TRUE;
8288           $bysetposYold = $wdate['year'];
8289           $bysetposMold = $wdate['month'];
8290           $bysetposDold = $wdate['day'];
8291         }
8292         if( $bysetpos ) {
8293           if( isset( $recur['BYWEEKNO'] )) {
8294             $bysetposarr1 = & $bysetposw1;
8295             $bysetposarr2 = & $bysetposw2;
8296           }
8297           else {
8298             $bysetposarr1 = & $bysetposymd1;
8299             $bysetposarr2 = & $bysetposymd2;
8300           }
8301 // echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
8302           foreach( $recur['BYSETPOS'] as $ix ) {
8303             if( 0 > $ix ) // both positive and negative BYSETPOS allowed
8304               $ix = ( count( $bysetposarr1 ) + $ix + 1);
8305             $ix--;
8306             if( isset( $bysetposarr1[$ix] )) {
8307               if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
8308 //                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ###
8309 //                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
8310 // echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ###
8311                 $result[$bysetposarr1[$ix]] = TRUE;
8312 // echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
8313               }
8314               $countcnt++;
8315             }
8316             if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
8317               break;
8318           }
8319 // echo "<br />\n"; // test ###
8320           $bysetposarr1 = $bysetposarr2;
8321           $bysetposarr2 = array();
8322         }
8323       }
8324     }
8325   }
8326   public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
8327     if( is_array( $BYvalue ) &&
8328       ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
8329       return TRUE;
8330     elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
8331       return TRUE;
8332     else
8333       return FALSE;
8334   }
8335   public static function _recurIntervalIx( $freq, $date, $wkst ) {
8336             /* create interval index */
8337     switch( $freq ) {
8338       case 'YEARLY':
8339         $intervalix = $date['year'];
8340         break;
8341       case 'MONTHLY':
8342         $intervalix = $date['year'].'-'.$date['month'];
8343         break;
8344       case 'WEEKLY':
8345         $wdatets    = iCalUtilityFunctions::_date2timestamp( $date );
8346         $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
8347        break;
8348       case 'DAILY':
8349            default:
8350         $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
8351         break;
8352     }
8353     return $intervalix;
8354   }
8355 /**
8356  * convert input format for exrule and rrule to internal format
8357  *
8358  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8359  * @since 2.11.15 - 2012-01-31
8360  * @param array $rexrule
8361  * @return array
8362  */
8363   public static function _setRexrule( $rexrule ) {
8364     $input          = array();
8365     if( empty( $rexrule ))
8366       return $input;
8367     foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
8368       $rexrulelabel = strtoupper( $rexrulelabel );
8369       if( 'UNTIL'  != $rexrulelabel )
8370         $input[$rexrulelabel]   = $rexrulevalue;
8371       else {
8372         iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
8373         if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time
8374           $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 6 );
8375         elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or date-time
8376           $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 6 : 3;
8377           $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_array( $rexrulevalue, $parno );
8378         }
8379         elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual datetime/date 2006-08-03 10:12:18
8380           $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_string( $rexrulevalue );
8381           unset( $input['$rexrulelabel']['unparsedtext'] );
8382         }
8383         if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
8384           $input[$rexrulelabel]['tz'] = 'Z';
8385       }
8386     }
8387             /* set recurrence rule specification in rfc2445 order */
8388     $input2 = array();
8389     if( isset( $input['FREQ'] ))
8390       $input2['FREQ']       = $input['FREQ'];
8391     if( isset( $input['UNTIL'] ))
8392       $input2['UNTIL']      = $input['UNTIL'];
8393     elseif( isset( $input['COUNT'] ))
8394       $input2['COUNT']      = $input['COUNT'];
8395     if( isset( $input['INTERVAL'] ))
8396       $input2['INTERVAL']   = $input['INTERVAL'];
8397     if( isset( $input['BYSECOND'] ))
8398       $input2['BYSECOND']   = $input['BYSECOND'];
8399     if( isset( $input['BYMINUTE'] ))
8400       $input2['BYMINUTE']   = $input['BYMINUTE'];
8401     if( isset( $input['BYHOUR'] ))
8402       $input2['BYHOUR']     = $input['BYHOUR'];
8403     if( isset( $input['BYDAY'] )) {
8404       if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
8405         $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
8406       else {
8407         foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
8408           if( 'DAY'        == strtoupper( $BYDAYx ))
8409              $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
8410           elseif( !is_array( $BYDAYv )) {
8411              $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
8412           }
8413           else {
8414             foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
8415               if( 'DAY'    == strtoupper( $BYDAYx2 ))
8416                  $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
8417               else
8418                  $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
8419             }
8420           }
8421         }
8422       }
8423     }
8424     if( isset( $input['BYMONTHDAY'] ))
8425       $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
8426     if( isset( $input['BYYEARDAY'] ))
8427       $input2['BYYEARDAY']  = $input['BYYEARDAY'];
8428     if( isset( $input['BYWEEKNO'] ))
8429       $input2['BYWEEKNO']   = $input['BYWEEKNO'];
8430     if( isset( $input['BYMONTH'] ))
8431       $input2['BYMONTH']    = $input['BYMONTH'];
8432     if( isset( $input['BYSETPOS'] ))
8433       $input2['BYSETPOS']   = $input['BYSETPOS'];
8434     if( isset( $input['WKST'] ))
8435       $input2['WKST']       = $input['WKST'];
8436     return $input2;
8437   }
8438 /**
8439  * convert format for input date to internal date with parameters
8440  *
8441  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8442  * @since 2.11.8 - 2012-03-18
8443  * @param mixed $year
8444  * @param mixed $month optional
8445  * @param int $day optional
8446  * @param int $hour optional
8447  * @param int $min optional
8448  * @param int $sec optional
8449  * @param string $tz optional
8450  * @param array $params optional
8451  * @param string $caller optional
8452  * @param string $objName optional
8453  * @param string $tzid optional
8454  * @return array
8455  */
8456   public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
8457     $input = $parno = null;
8458     $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
8459     iCalUtilityFunctions::_strDate2arr( $year );
8460     if( iCalUtilityFunctions::_isArrayDate( $year )) {
8461       if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
8462       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8463       if( isset( $input['params']['TZID'] )) {
8464         $input['params']['VALUE'] = 'DATE-TIME';
8465         unset( $year['tz'] );
8466       }
8467       $hitval          = ( isset( $year['tz'] ) || isset( $year[6] )) ? 7 : 6;
8468       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
8469       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $year ), $parno );
8470       $input['value']  = iCalUtilityFunctions::_date_time_array( $year, $parno );
8471     }
8472     elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
8473       if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
8474       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8475       if( isset( $input['params']['TZID'] )) {
8476         $input['params']['VALUE'] = 'DATE-TIME';
8477         unset( $year['tz'] );
8478       }
8479       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
8480       $hitval          = ( isset( $year['tz'] )) ? 7 : 6;
8481       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
8482       $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
8483     }
8484     elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
8485       if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
8486       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8487       if( isset( $input['params']['TZID'] )) {
8488         $input['params']['VALUE'] = 'DATE-TIME';
8489         $parno = 6;
8490       }
8491       elseif( $tzid && iCalUtilityFunctions::_isOffset( substr( $year, -7 ))) {
8492         if(( in_array( substr( $year, -5, 1 ), array( '+', '-' ))) &&
8493            (   '0000'  < substr( $year, -4 )) && (   '9999' >= substr( $year, -4 )))
8494           $year = substr( $year, 0, ( strlen( $year ) - 5 ));
8495         elseif(( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
8496                ( '000000'  < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
8497           $year = substr( $year, 0, ( strlen( $year ) - 7 ));
8498         $parno = 6;
8499       }
8500       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
8501       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
8502       $input['value']  = iCalUtilityFunctions::_date_time_string( $year, $parno );
8503       unset( $input['value']['unparsedtext'] );
8504     }
8505     else {
8506       if( is_array( $params )) {
8507         if( $localtime ) unset ( $params['VALUE'], $params['TZID'] );
8508         $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
8509       }
8510       elseif( is_array( $tz )) {
8511         $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
8512         $tz = FALSE;
8513       }
8514       elseif( is_array( $hour )) {
8515         $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
8516         $hour = $min = $sec = $tz = FALSE;
8517       }
8518       if( isset( $input['params']['TZID'] )) {
8519         $tz            = null;
8520         $input['params']['VALUE'] = 'DATE-TIME';
8521       }
8522       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
8523       $hitval          = ( !empty( $tz )) ? 7 : 6;
8524       $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
8525       $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
8526       if( 3 != $parno ) {
8527         $input['value']['hour'] = ( $hour ) ? $hour : '0';
8528         $input['value']['min']  = ( $min )  ? $min  : '0';
8529         $input['value']['sec']  = ( $sec )  ? $sec  : '0';
8530         if( !empty( $tz ))
8531           $input['value']['tz'] = $tz;
8532       }
8533     }
8534     if( 3 == $parno ) {
8535       $input['params']['VALUE'] = 'DATE';
8536       unset( $input['value']['tz'] );
8537       unset( $input['params']['TZID'] );
8538     }
8539     elseif( isset( $input['params']['TZID'] ))
8540       unset( $input['value']['tz'] );
8541     if( $localtime )
8542       unset( $input['value']['tz'], $input['params']['TZID'] );
8543     elseif(( !isset( $input['params']['VALUE'] ) || ( $input['params']['VALUE'] != 'DATE' )) && !isset( $input['params']['TZID'] ) && $tzid )
8544       $input['params']['TZID'] = $tzid;
8545     if( isset( $input['value']['tz'] ))
8546       $input['value']['tz'] = (string) $input['value']['tz'];
8547     if( !empty( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && // real time zone in tz to TZID
8548       ( !iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))) {
8549       $input['params']['TZID'] = $input['value']['tz'];
8550       unset( $input['value']['tz'] );
8551     }
8552     if(  isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
8553       if(( 'Z' != $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {  // utc offset in TZID to tz
8554         $input['value']['tz'] = $input['params']['TZID'];
8555         unset( $input['params']['TZID'] );
8556       }
8557       elseif( in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) { // time zone Z
8558         $input['value']['tz'] = 'Z';
8559         unset( $input['params']['TZID'] );
8560       }
8561     }
8562     return $input;
8563   }
8564 /**
8565  * convert format for input date (UTC) to internal date with parameters
8566  *
8567  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8568  * @since 2.11.8 - 2012-01-19
8569  * @param mixed $year
8570  * @param mixed $month optional
8571  * @param int $day optional
8572  * @param int $hour optional
8573  * @param int $min optional
8574  * @param int $sec optional
8575  * @param array $params optional
8576  * @return array
8577  */
8578   public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
8579     $input = null;
8580     iCalUtilityFunctions::_strDate2arr( $year );
8581     if( iCalUtilityFunctions::_isArrayDate( $year )) {
8582       $input['value']  = iCalUtilityFunctions::_date_time_array( $year, 7 );
8583       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) );
8584     }
8585     elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
8586       $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
8587       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) );
8588     }
8589     elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
8590       $input['value']  = iCalUtilityFunctions::_date_time_string( $year, 7 );
8591       unset( $input['value']['unparsedtext'] );
8592       $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) );
8593     }
8594     else {
8595       $input['value']  = array( 'year'  => $year
8596                               , 'month' => $month
8597                               , 'day'   => $day
8598                               , 'hour'  => $hour
8599                               , 'min'   => $min
8600                               , 'sec'   => $sec );
8601       $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
8602     }
8603     $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
8604     if( !isset( $input['value']['hour'] ))
8605       $input['value']['hour'] = 0;
8606     if( !isset( $input['value']['min'] ))
8607       $input['value']['min'] = 0;
8608     if( !isset( $input['value']['sec'] ))
8609       $input['value']['sec'] = 0;
8610     if(  isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
8611       if(( 'Z' != $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {  // utc offset in TZID to tz
8612         $input['value']['tz'] = $input['params']['TZID'];
8613         unset( $input['params']['TZID'] );
8614       }
8615       elseif( in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) { // time zone Z
8616         $input['value']['tz'] = 'Z';
8617         unset( $input['params']['TZID'] );
8618       }
8619     }
8620     if( !isset( $input['value']['tz'] ) || !iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
8621       $input['value']['tz'] = 'Z';
8622     return $input;
8623   }
8624 /**
8625  * check index and set (an indexed) content in multiple value array
8626  *
8627  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8628  * @since 2.6.12 - 2011-01-03
8629  * @param array $valArr
8630  * @param mixed $value
8631  * @param array $params
8632  * @param array $defaults
8633  * @param int $index
8634  * @return void
8635  */
8636   public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
8637     if( !is_array( $valArr )) $valArr = array();
8638     if( $index )
8639       $index = $index - 1;
8640     elseif( 0 < count( $valArr )) {
8641       $keys  = array_keys( $valArr );
8642       $index = end( $keys ) + 1;
8643     }
8644     else
8645       $index = 0;
8646     $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
8647     ksort( $valArr );
8648   }
8649 /**
8650  * set input (formatted) parameters- component property attributes
8651  *
8652  * default parameters can be set, if missing
8653  *
8654  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8655  * @since 1.x.x - 2007-05-01
8656  * @param array $params
8657  * @param array $defaults
8658  * @return array
8659  */
8660   public static function _setParams( $params, $defaults=FALSE ) {
8661     if( !is_array( $params))
8662       $params = array();
8663     $input = array();
8664     foreach( $params as $paramKey => $paramValue ) {
8665       if( is_array( $paramValue )) {
8666         foreach( $paramValue as $pkey => $pValue ) {
8667           if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
8668             $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
8669         }
8670       }
8671       elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
8672         $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
8673       if( 'VALUE' == strtoupper( $paramKey ))
8674         $input['VALUE']                 = strtoupper( $paramValue );
8675       else
8676         $input[strtoupper( $paramKey )] = $paramValue;
8677     }
8678     if( is_array( $defaults )) {
8679       foreach( $defaults as $paramKey => $paramValue ) {
8680         if( !isset( $input[$paramKey] ))
8681           $input[$paramKey] = $paramValue;
8682       }
8683     }
8684     return (0 < count( $input )) ? $input : null;
8685   }
8686 /**
8687  * step date, return updated date, array and timpstamp
8688  *
8689  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8690  * @since 2.4.16 - 2008-10-18
8691  * @param array $date, date to step
8692  * @param int $timestamp
8693  * @param array $step, default array( 'day' => 1 )
8694  * @return void
8695  */
8696   public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
8697     foreach( $step as $stepix => $stepvalue )
8698       $date[$stepix] += $stepvalue;
8699     $timestamp  = iCalUtilityFunctions::_date2timestamp( $date );
8700     $date       = iCalUtilityFunctions::_timestamp2date( $timestamp, 6 );
8701     foreach( $date as $k => $v ) {
8702       if( ctype_digit( $v ))
8703         $date[$k] = (int) $v;
8704     }
8705   }
8706 /**
8707  * convert a date from specific string to array format
8708  *
8709  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8710  * @since 2.11.8 - 2012-01-27
8711  * @param mixed $input
8712  * @return bool, TRUE on success
8713  */
8714   public static function _strDate2arr( & $input ) {
8715     if( is_array( $input ))
8716       return FALSE;
8717     if( 5 > strlen( (string) $input ))
8718       return FALSE;
8719     $work = $input;
8720     if( 2 == substr_count( $work, '-' ))
8721       $work = str_replace( '-', '', $work );
8722     if( 2 == substr_count( $work, '/' ))
8723       $work = str_replace( '/', '', $work );
8724     if( !ctype_digit( substr( $work, 0, 8 )))
8725       return FALSE;
8726     if( !checkdate( (int) substr( $work,  4, 2 ), (int) substr( $work,  6, 2 ), (int) substr( $work,  0, 4 )))
8727       return FALSE;
8728     $temp = array( 'year'  => substr( $work,  0, 4 )
8729                  , 'month' => substr( $work,  4, 2 )
8730                  , 'day'   => substr( $work,  6, 2 ));
8731     if( 8 == strlen( $work )) {
8732       $input = $temp;
8733       return TRUE;
8734     }
8735     if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
8736       $work =  substr( $work, 9 );
8737     elseif( ctype_digit( substr( $work, 8, 1 )))
8738       $work = substr( $work, 8 );
8739     else
8740      return FALSE;
8741     if( 2 == substr_count( $work, ':' ))
8742       $work = str_replace( ':', '', $work );
8743     if( !ctype_digit( substr( $work, 0, 4 )))
8744       return FALSE;
8745     $temp['hour']  = substr( $work, 0, 2 );
8746     $temp['min']   = substr( $work, 2, 2 );
8747     if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
8748        (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
8749       return FALSE;
8750     if( ctype_digit( substr( $work, 4, 2 ))) {
8751       $temp['sec'] = substr( $work, 4, 2 );
8752       if((  0 > $temp['sec'] )  || ( $temp['sec']  > 59 ))
8753         return FALSE;
8754       $len = 6;
8755     }
8756     else {
8757       $temp['sec'] = 0;
8758       $len = 4;
8759     }
8760     if( $len < strlen( $work))
8761       $temp['tz'] = trim( substr( $work, 6 ));
8762     $input = $temp;
8763     return TRUE;
8764   }
8765 /**
8766  * convert timestamp to date array
8767  *
8768  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8769  * @since 2.4.16 - 2008-11-01
8770  * @param mixed $timestamp
8771  * @param int $parno
8772  * @return array
8773  */
8774   public static function _timestamp2date( $timestamp, $parno=6 ) {
8775     if( is_array( $timestamp )) {
8776       if(( 7 == $parno ) && !empty( $timestamp['tz'] ))
8777         $tz = $timestamp['tz'];
8778       $timestamp = $timestamp['timestamp'];
8779     }
8780     $output = array( 'year'  => date( 'Y', $timestamp )
8781                    , 'month' => date( 'm', $timestamp )
8782                    , 'day'   => date( 'd', $timestamp ));
8783     if( 3 != $parno ) {
8784              $output['hour'] =  date( 'H', $timestamp );
8785              $output['min']  =  date( 'i', $timestamp );
8786              $output['sec']  =  date( 's', $timestamp );
8787       if( isset( $tz ))
8788         $output['tz'] = $tz;
8789     }
8790     return $output;
8791   }
8792 /**
8793  * convert timestamp to duration in array format
8794  *
8795  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8796  * @since 2.6.23 - 2010-10-23
8797  * @param int $timestamp
8798  * @return array, duration format
8799  */
8800   public static function _timestamp2duration( $timestamp ) {
8801     $dur         = array();
8802     $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
8803     $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 );
8804     $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 ));
8805     $timestamp   =              $timestamp % ( 24 * 60 * 60 );
8806     $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
8807     $timestamp   =              $timestamp % ( 60 * 60 );
8808     $dur['min']  = (int) floor( $timestamp / ( 60 ));
8809     $dur['sec']  = (int)        $timestamp % ( 60 );
8810     return $dur;
8811   }
8812 /**
8813  * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
8814  *
8815  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8816  * @since 2.11.14 - 2012-01-24
8817  * @param mixed  $date,   date to alter
8818  * @param string $tzFrom, PHP valid old timezone
8819  * @param string $tzTo,   PHP valid new timezone, default 'UTC'
8820  * @param string $format, date output format, default 'Ymd\THis'
8821  * @return bool
8822  */
8823   public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
8824     if( !class_exists( 'DateTime' ) || !class_exists( 'DateTimeZone' ))
8825       return FALSE;
8826     if( is_array( $date ) && isset( $date['timestamp'] ))
8827        $timestamp = $date['timestamp'];
8828     elseif( iCalUtilityFunctions::_isArrayDate( $date )) {
8829       if(isset( $date['tz'] ))
8830         unset( $date['tz'] );
8831       $date  = iCalUtilityFunctions::_format_date_time( iCalUtilityFunctions::_date_time_array( $date ));
8832       if( 'Z' == substr( $date, -1 ))
8833         $date = substr( $date, 0, ( strlen( $date ) - 2 ));
8834       if( FALSE === ( $timestamp = strtotime( $date )))
8835         return FALSE;
8836     }
8837     elseif( FALSE === ( $timestamp = @strtotime( $date )))
8838       return FALSE;
8839     try {
8840       $d = new DateTime( date( 'Y-m-d H:i:s', $timestamp ), new DateTimeZone( $tzFrom ));
8841       $d->setTimezone( new DateTimeZone( $tzTo ));
8842     }
8843     catch (Exception $e) {
8844       return FALSE;
8845     }
8846     $date = $d->format( $format );
8847     return TRUE;
8848   }
8849 /**
8850  * convert (numeric) local time offset, ("+" / "-")HHmm[ss], to seconds correcting localtime to GMT
8851  *
8852  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8853  * @since 2.11.4 - 2012-01-11
8854  * @param string $offset
8855  * @return integer
8856  */
8857   public static function _tz2offset( $tz ) {
8858     $tz           = trim( (string) $tz );
8859     $offset       = 0;
8860     if(((     5  != strlen( $tz )) && ( 7  != strlen( $tz ))) ||
8861        ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
8862        (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
8863            (( 7  == strlen( $tz )) && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
8864       return $offset;
8865     $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
8866     $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
8867     $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
8868     $offset       = $hours2sec + $min2sec + $sec;
8869     $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
8870     return $offset;
8871   }
8872 }
8873 /*********************************************************************************/
8874 /*          iCalcreator XML (rfc6321) helper functions                           */
8875 /*********************************************************************************/
8876 /**
8877  * format iCal XML output, rfc6321, using PHP SimpleXMLElement
8878  *
8879  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8880  * @since 2.11.1 - 2012-02-22
8881  * @param object $calendar, iCalcreator vcalendar instance reference
8882  * @return string
8883  */
8884 function iCal2XML( & $calendar ) {
8885             /** fix an SimpleXMLElement instance and create root element */
8886   $xmlstr     = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
8887   $xmlstr    .= '<!-- created utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
8888   $xmlstr    .= '</icalendar>';
8889   $xml        = new SimpleXMLElement( $xmlstr );
8890   $vcalendar  = $xml->addChild( 'vcalendar' );
8891             /** fix calendar properties */
8892   $properties = $vcalendar->addChild( 'properties' );
8893   $calProps = array( 'prodid', 'version', 'calscale', 'method' );
8894   foreach( $calProps as $calProp ) {
8895     if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
8896       _addXMLchild( $properties, $calProp, 'text', $content );
8897   }
8898   while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
8899     _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
8900   $langCal = $calendar->getConfig( 'language' );
8901             /** prepare to fix components with properties */
8902   $components    = $vcalendar->addChild( 'components' );
8903   $comps         = array( 'vtimezone', 'vevent', 'vtodo', 'vjournal', 'vfreebusy' );
8904   $eventProps    = array( 'dtstamp', 'dtstart', 'uid',
8905                           'class', 'created', 'description', 'geo', 'last-modified', 'location', 'organizer', 'priority',
8906                           'sequence', 'status', 'summary', 'transp', 'url', 'recurrence-id', 'rrule', 'dtend', 'duration',
8907                           'attach', 'attendee', 'categories', 'comment', 'contact', 'exdate', 'request-status', 'related-to', 'resources', 'rdate',
8908                           'x-prop' );
8909   $todoProps     = array( 'dtstamp', 'uid',
8910                           'class', 'completed', 'created', 'description', 'geo', 'last-modified', 'location', 'organizer', 'percent-complete', 'priority',
8911                           'recurrence-id', 'sequence', 'status', 'summary', 'url', 'rrule', 'dtstart', 'due', 'duration',
8912                           'attach', 'attendee', 'categories', 'comment', 'contact', 'exdate', 'request-status', 'related-to', 'resources', 'rdate',
8913                           'x-prop' );
8914   $journalProps  = array( 'dtstamp', 'uid',
8915                           'class', 'created', 'dtstart', 'last-modified', 'organizer', 'recurrence-id', 'sequence', 'status', 'summary', 'url', 'rrule',
8916                           'attach', 'attendee', 'categories', 'comment', 'contact',
8917                           'description',
8918                           'exdate', 'related-to', 'rdate', 'request-status',
8919                           'x-prop' );
8920   $freebusyProps = array( 'dtstamp', 'uid',
8921                           'contact', 'dtstart', 'dtend', 'duration', 'organizer', 'url',
8922                           'attendee', 'comment', 'freebusy', 'request-status',
8923                           'x-prop' );
8924   $timezoneProps = array( 'tzid',
8925                           'last-modified', 'tzurl',
8926                           'x-prop' );
8927   $alarmProps    = array( 'action', 'description', 'trigger', 'summary',
8928                           'attendee',
8929                           'duration', 'repeat', 'attach',
8930                           'x-prop' );
8931   $stddghtProps  = array( 'dtstart', 'tzoffsetto', 'tzoffsetfrom',
8932                           'rrule',
8933                           'comment', 'rdate', 'tzname',
8934                           'x-prop' );
8935   foreach( $comps as $compName ) {
8936     switch( $compName ) {
8937       case 'vevent':
8938         $props        = & $eventProps;
8939         $subComps     = array( 'valarm' );
8940         $subCompProps = & $alarmProps;
8941         break;
8942       case 'vtodo':
8943         $props        = & $todoProps;
8944         $subComps     = array( 'valarm' );
8945         $subCompProps = & $alarmProps;
8946         break;
8947       case 'vjournal':
8948         $props        = & $journalProps;
8949         $subComps     = array();
8950         $subCompProps = array();
8951         break;
8952       case 'vfreebusy':
8953         $props        = & $freebusyProps;
8954         $subComps     = array();
8955         $subCompProps = array();
8956         break;
8957       case 'vtimezone':
8958         $props        = & $timezoneProps;
8959         $subComps     = array( 'standard', 'daylight' );
8960         $subCompProps = & $stddghtProps;
8961         break;
8962     } // end switch( $compName )
8963             /** fix component properties */
8964     while( FALSE !== ( $component = $calendar->getComponent( $compName ))) {
8965       $child      = $components->addChild( $compName );
8966       $properties = $child->addChild( 'properties' );
8967       $langComp = $component->getConfig( 'language' );
8968       foreach( $props as $prop ) {
8969         switch( $prop ) {
8970           case 'attach':          // may occur multiple times, below
8971             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
8972               $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
8973               unset( $content['params']['VALUE'] );
8974               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
8975             }
8976             break;
8977           case 'attendee':
8978             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
8979               if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
8980                 if( $langComp )
8981                   $content['params']['LANGUAGE'] = $langComp;
8982                 elseif( $langCal )
8983                   $content['params']['LANGUAGE'] = $langCal;
8984               }
8985               _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
8986             }
8987             break;
8988           case 'exdate':
8989             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
8990               $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
8991               unset( $content['params']['VALUE'] );
8992               foreach( $content['value'] as & $exDate ) {
8993                 if( (  isset( $exDate['tz'] ) &&  // fix UTC-date if offset set
8994                        iCalUtilityFunctions::_isOffset( $exDate['tz'] ) &&
8995                      ( 'Z' != $exDate['tz'] ))
8996                  || (  isset( $content['params']['TZID'] ) &&
8997                        iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
8998                      ( 'Z' != $content['params']['TZID'] ))) {
8999                   $offset = isset( $exDate['tz'] ) ? $exDate['tz'] : $content['params']['TZID'];
9000                   $date = mktime( (int)  $exDate['hour'],
9001                                   (int)  $exDate['min'],
9002                                   (int) ($exDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9003                                   (int)  $exDate['month'],
9004                                   (int)  $exDate['day'],
9005                                   (int)  $exDate['year'] );
9006                   unset( $exDate['tz'] );
9007                   $exDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9008                   unset( $exDate['unparsedtext'] );
9009                 }
9010               }
9011               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9012             }
9013             break;
9014           case 'freebusy':
9015             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9016               _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
9017             break;
9018           case 'request-status':
9019             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9020               if( !isset( $content['params']['LANGUAGE'] )) {
9021                 if( $langComp )
9022                   $content['params']['LANGUAGE'] = $langComp;
9023                 elseif( $langCal )
9024                   $content['params']['LANGUAGE'] = $langCal;
9025               }
9026               _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
9027             }
9028             break;
9029           case 'rdate':
9030             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9031               $type = 'date-time';
9032               if( isset( $content['params']['VALUE'] )) {
9033                 if( 'DATE' == $content['params']['VALUE'] )
9034                   $type = 'date';
9035                 elseif( 'PERIOD' == $content['params']['VALUE'] )
9036                   $type = 'period';
9037               }
9038               if( 'period' == $type ) {
9039                 foreach( $content['value'] as & $rDates ) {
9040                   if( (  isset( $rDates[0]['tz'] ) &&  // fix UTC-date if offset set
9041                          iCalUtilityFunctions::_isOffset( $rDates[0]['tz'] ) &&
9042                        ( 'Z' != $rDates[0]['tz'] ))
9043                    || (  isset( $content['params']['TZID'] ) &&
9044                          iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9045                        ( 'Z' != $content['params']['TZID'] ))) {
9046                     $offset = isset( $rDates[0]['tz'] ) ? $rDates[0]['tz'] : $content['params']['TZID'];
9047                     $date = mktime( (int)  $rDates[0]['hour'],
9048                                     (int)  $rDates[0]['min'],
9049                                     (int) ($rDates[0]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9050                                     (int)  $rDates[0]['month'],
9051                                     (int)  $rDates[0]['day'],
9052                                     (int)  $rDates[0]['year'] );
9053                     unset( $rDates[0]['tz'] );
9054                     $rDates[0] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9055                     unset( $rDates[0]['unparsedtext'] );
9056                   }
9057                   if( isset( $rDates[1]['year'] )) {
9058                     if( (  isset( $rDates[1]['tz'] ) &&  // fix UTC-date if offset set
9059                            iCalUtilityFunctions::_isOffset( $rDates[1]['tz'] ) &&
9060                          ( 'Z' != $rDates[1]['tz'] ))
9061                      || (  isset( $content['params']['TZID'] ) &&
9062                            iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9063                          ( 'Z' != $content['params']['TZID'] ))) {
9064                       $offset = isset( $rDates[1]['tz'] ) ? $rDates[1]['tz'] : $content['params']['TZID'];
9065                       $date = mktime( (int)  $rDates[1]['hour'],
9066                                       (int)  $rDates[1]['min'],
9067                                       (int) ($rDates[1]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9068                                       (int)  $rDates[1]['month'],
9069                                       (int)  $rDates[1]['day'],
9070                                       (int)  $rDates[1]['year'] );
9071                       unset( $rDates[1]['tz'] );
9072                       $rDates[1] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9073                       unset( $rDates[1]['unparsedtext'] );
9074                     }
9075                   }
9076                 }
9077               }
9078               elseif( 'date-time' == $type ) {
9079                 foreach( $content['value'] as & $rDate ) {
9080                   if( (  isset( $rDate['tz'] ) &&  // fix UTC-date if offset set
9081                          iCalUtilityFunctions::_isOffset( $rDate['tz'] ) &&
9082                        ( 'Z' != $rDate['tz'] ))
9083                    || (  isset( $content['params']['TZID'] ) &&
9084                          iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9085                        ( 'Z' != $content['params']['TZID'] ))) {
9086                     $offset = isset( $rDate['tz'] ) ? $rDate['tz'] : $content['params']['TZID'];
9087                     $date = mktime( (int)  $rDate['hour'],
9088                                     (int)  $rDate['min'],
9089                                     (int) ($rDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9090                                     (int)  $rDate['month'],
9091                                     (int)  $rDate['day'],
9092                                     (int)  $rDate['year'] );
9093                     unset( $rDate['tz'] );
9094                     $rDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9095                     unset( $rDate['unparsedtext'] );
9096                   }
9097                 }
9098               }
9099               unset( $content['params']['VALUE'] );
9100               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9101             }
9102             break;
9103           case 'categories':
9104           case 'comment':
9105           case 'contact':
9106           case 'description':
9107           case 'related-to':
9108           case 'resources':
9109             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9110               if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
9111                 if( $langComp )
9112                   $content['params']['LANGUAGE'] = $langComp;
9113                 elseif( $langCal )
9114                   $content['params']['LANGUAGE'] = $langCal;
9115               }
9116               _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9117             }
9118             break;
9119           case 'x-prop':
9120             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9121               _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9122             break;
9123           case 'created':         // single occurence below, if set
9124           case 'completed':
9125           case 'dtstamp':
9126           case 'last-modified':
9127             $utcDate = TRUE;
9128           case 'dtstart':
9129           case 'dtend':
9130           case 'due':
9131           case 'recurrence-id':
9132             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9133               if( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) {
9134                 $type = 'date';
9135                 unset( $content['value']['hour'], $content['value']['min'], $content['value']['sec'] );
9136               }
9137               else {
9138                 $type = 'date-time';
9139                 if( isset( $utcDate ) && !isset( $content['value']['tz'] ))
9140                   $content['value']['tz'] = 'Z';
9141                 if( (  isset( $content['value']['tz'] ) &&  // fix UTC-date if offset set
9142                        iCalUtilityFunctions::_isOffset( $content['value']['tz'] ) &&
9143                      ( 'Z' != $content['value']['tz'] ))
9144                  || (  isset( $content['params']['TZID'] ) &&
9145                        iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9146                      ( 'Z' != $content['params']['TZID'] ))) {
9147                   $offset = isset( $content['value']['tz'] ) ? $content['value']['tz'] : $content['params']['TZID'];
9148                   $date = mktime( (int)  $content['value']['hour'],
9149                                   (int)  $content['value']['min'],
9150                                   (int) ($content['value']['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9151                                   (int)  $content['value']['month'],
9152                                   (int)  $content['value']['day'],
9153                                   (int)  $content['value']['year'] );
9154                   unset( $content['value']['tz'], $content['params']['TZID'] );
9155                   $content['value'] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9156                   unset( $content['value']['unparsedtext'] );
9157                 }
9158                 elseif( isset( $content['value']['tz'] ) && !empty( $content['value']['tz'] ) &&
9159                       ( 'Z' != $content['value']['tz'] ) && !isset( $content['params']['TZID'] )) {
9160                   $content['params']['TZID'] = $content['value']['tz'];
9161                   unset( $content['value']['tz'] );
9162                 }
9163               }
9164               unset( $content['params']['VALUE'] );
9165               if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
9166                 unset( $content['params']['TZID'] );
9167               _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9168             }
9169             unset( $utcDate );
9170             break;
9171           case 'duration':
9172             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9173               _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
9174             break;
9175           case 'rrule':
9176             while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9177               _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
9178             break;
9179           case 'class':
9180           case 'location':
9181           case 'status':
9182           case 'summary':
9183           case 'transp':
9184           case 'tzid':
9185           case 'uid':
9186             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9187               if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
9188                 if( $langComp )
9189                   $content['params']['LANGUAGE'] = $langComp;
9190                 elseif( $langCal )
9191                   $content['params']['LANGUAGE'] = $langCal;
9192               }
9193               _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9194             }
9195             break;
9196           case 'geo':
9197             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9198               _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
9199             break;
9200           case 'organizer':
9201             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9202               if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9203                 if( $langComp )
9204                   $content['params']['LANGUAGE'] = $langComp;
9205                 elseif( $langCal )
9206                   $content['params']['LANGUAGE'] = $langCal;
9207               }
9208               _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9209             }
9210             break;
9211           case 'percent-complete':
9212           case 'priority':
9213           case 'sequence':
9214             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9215               _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
9216             break;
9217           case 'tzurl':
9218           case 'url':
9219             if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9220               _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
9221             break;
9222         } // end switch( $prop )
9223       } // end foreach( $props as $prop )
9224             /** fix subComponent properties, if any */
9225       foreach( $subComps as $subCompName ) {
9226         while( FALSE !== ( $subcomp = $component->getComponent( $subCompName ))) {
9227           $child2     = $child->addChild( $subCompName );
9228           $properties = $child2->addChild( 'properties' );
9229           $langComp   = $subcomp->getConfig( 'language' );
9230           foreach( $subCompProps as $prop ) {
9231             switch( $prop ) {
9232               case 'attach':          // may occur multiple times, below
9233                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9234                   $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
9235                   unset( $content['params']['VALUE'] );
9236                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9237                 }
9238                 break;
9239               case 'attendee':
9240                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9241                   if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9242                     if( $langComp )
9243                       $content['params']['LANGUAGE'] = $langComp;
9244                     elseif( $langCal )
9245                       $content['params']['LANGUAGE'] = $langCal;
9246                   }
9247                   _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9248                 }
9249                 break;
9250               case 'comment':
9251               case 'tzname':
9252                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9253                   if( !isset( $content['params']['LANGUAGE'] )) {
9254                     if( $langComp )
9255                       $content['params']['LANGUAGE'] = $langComp;
9256                     elseif( $langCal )
9257                       $content['params']['LANGUAGE'] = $langCal;
9258                   }
9259                   _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9260                 }
9261                 break;
9262               case 'rdate':
9263                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9264                   $type = 'date-time';
9265                   if( isset( $content['params']['VALUE'] )) {
9266                     if( 'DATE' == $content['params']['VALUE'] )
9267                       $type = 'date';
9268                     elseif( 'PERIOD' == $content['params']['VALUE'] )
9269                       $type = 'period';
9270                   }
9271                   if( 'period' == $type ) {
9272                     foreach( $content['value'] as & $rDates ) {
9273                       if( (  isset( $rDates[0]['tz'] ) &&  // fix UTC-date if offset set
9274                              iCalUtilityFunctions::_isOffset( $rDates[0]['tz'] ) &&
9275                           ( 'Z' != $rDates[0]['tz'] ))
9276                        || (  isset( $content['params']['TZID'] ) &&
9277                              iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9278                            ( 'Z' != $content['params']['TZID'] ))) {
9279                         $offset = isset( $rDates[0]['tz'] ) ? $rDates[0]['tz'] : $content['params']['TZID'];
9280                         $date = mktime( (int)  $rDates[0]['hour'],
9281                                         (int)  $rDates[0]['min'],
9282                                         (int) ($rDates[0]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9283                                         (int)  $rDates[0]['month'],
9284                                         (int)  $rDates[0]['day'],
9285                                         (int)  $rDates[0]['year'] );
9286                         unset( $rDates[0]['tz'] );
9287                         $rDates[0] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9288                         unset( $rDates[0]['unparsedtext'] );
9289                       }
9290                       if( isset( $rDates[1]['year'] )) {
9291                         if( (  isset( $rDates[1]['tz'] ) &&  // fix UTC-date if offset set
9292                                iCalUtilityFunctions::_isOffset( $rDates[1]['tz'] ) &&
9293                              ( 'Z' != $rDates[1]['tz'] ))
9294                          || (  isset( $content['params']['TZID'] ) &&
9295                                iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9296                              ( 'Z' != $content['params']['TZID'] ))) {
9297                           $offset = isset( $rDates[1]['tz'] ) ? $rDates[1]['tz'] : $content['params']['TZID'];
9298                           $date = mktime( (int)  $rDates[1]['hour'],
9299                                           (int)  $rDates[1]['min'],
9300                                           (int) ($rDates[1]['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9301                                           (int)  $rDates[1]['month'],
9302                                           (int)  $rDates[1]['day'],
9303                                           (int)  $rDates[1]['year'] );
9304                           unset( $rDates[1]['tz'] );
9305                           $rDates[1] = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9306                           unset( $rDates[1]['unparsedtext'] );
9307                         }
9308                       }
9309                     }
9310                   }
9311                   elseif( 'date-time' == $type ) {
9312                     foreach( $content['value'] as & $rDate ) {
9313                       if( (  isset( $rDate['tz'] ) &&  // fix UTC-date if offset set
9314                              iCalUtilityFunctions::_isOffset( $rDate['tz'] ) &&
9315                            ( 'Z' != $rDate['tz'] ))
9316                        || (  isset( $content['params']['TZID'] ) &&
9317                              iCalUtilityFunctions::_isOffset( $content['params']['TZID'] ) &&
9318                            ( 'Z' != $content['params']['TZID'] ))) {
9319                         $offset = isset( $rDate['tz'] ) ? $rDate['tz'] : $content['params']['TZID'];
9320                         $date = mktime( (int)  $rDate['hour'],
9321                                         (int)  $rDate['min'],
9322                                         (int) ($rDate['sec'] + iCalUtilityFunctions::_tz2offset( $offset )),
9323                                         (int)  $rDate['month'],
9324                                         (int)  $rDate['day'],
9325                                         (int)  $rDate['year'] );
9326                         unset( $rDate['tz'] );
9327                         $rDate = iCalUtilityFunctions::_date_time_string( date( 'YmdTHis\Z', $date ), 6 );
9328                         unset( $rDate['unparsedtext'] );
9329                       }
9330                     }
9331                   }
9332                   unset( $content['params']['VALUE'] );
9333                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9334                 }
9335                 break;
9336               case 'x-prop':
9337                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9338                   _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9339                 break;
9340               case 'action':      // single occurence below, if set
9341               case 'description':
9342               case 'summary':
9343                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9344                   if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
9345                     if( $langComp )
9346                       $content['params']['LANGUAGE'] = $langComp;
9347                     elseif( $langCal )
9348                       $content['params']['LANGUAGE'] = $langCal;
9349                   }
9350                   _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9351                 }
9352                 break;
9353               case 'dtstart':
9354                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9355                   unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
9356                   _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
9357                 }
9358                 break;
9359               case 'duration':
9360                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9361                   _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
9362                 break;
9363               case 'repeat':
9364                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9365                   _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
9366                 break;
9367               case 'trigger':
9368                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9369                   if( isset( $content['value']['year'] )   &&
9370                       isset( $content['value']['month'] )  &&
9371                       isset( $content['value']['day'] ))
9372                     $type = 'date-time';
9373                   else
9374                     $type = 'duration';
9375                   _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9376                 }
9377                 break;
9378               case 'tzoffsetto':
9379               case 'tzoffsetfrom':
9380                 if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9381                   _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
9382                 break;
9383               case 'rrule':
9384                 while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9385                   _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
9386                 break;
9387             } // switch( $prop )
9388           } // end foreach( $subCompProps as $prop )
9389         } // end while( FALSE !== ( $subcomp = $component->getComponent( subCompName )))
9390       } // end foreach( $subCombs as $subCompName )
9391     } // end while( FALSE !== ( $component = $calendar->getComponent( $compName )))
9392   } // end foreach( $comps as $compName)
9393   return $xml->asXML();
9394 }
9395 /**
9396  * Add children to a SimpleXMLelement
9397  *
9398  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9399  * @since 2.11.1 - 2012-01-16
9400  * @param object $parent,  reference to a SimpleXMLelement node
9401  * @param string $name,    new element node name
9402  * @param string $type,    content type, subelement(-s) name
9403  * @param string $content, new subelement content
9404  * @param array  $params,  new element 'attributes'
9405  * @return void
9406  */
9407 function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
9408             /** create new child node */
9409   $child = $parent->addChild( strtolower( $name ));
9410             /** fix attributes */
9411   if( is_array( $content ) && isset( $content['fbtype'] )) {
9412     $params['FBTYPE'] = $content['fbtype'];
9413     unset( $content['fbtype'] );
9414   }
9415   if( isset( $params['VALUE'] ))
9416     unset( $params['VALUE'] );
9417   if(( 'trigger' == $name ) && ( 'duration' == $type ) && ( TRUE !== $content['relatedStart'] ))
9418     $params['RELATED'] = 'END';
9419   if( !empty( $params )) {
9420     $parameters = $child->addChild( 'parameters' );
9421     foreach( $params as $param => $parVal ) {
9422       $param = strtolower( $param );
9423       if( 'x-' == substr( $param, 0, 2  )) {
9424         $p1 = $parameters->addChild( $param );
9425         $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
9426       }
9427       else {
9428         $p1 = $parameters->addChild( $param );
9429         switch( $param ) {
9430           case 'altrep':
9431           case 'dir':            $ptype = 'uri';            break;
9432           case 'delegated-from':
9433           case 'delegated-to':
9434           case 'member':
9435           case 'sent-by':        $ptype = 'cal-address';    break;
9436           case 'rsvp':           $ptype = 'boolean';        break ;
9437           default:               $ptype = 'text';           break;
9438         }
9439         if( is_array( $parVal )) {
9440           foreach( $parVal as $pV )
9441             $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
9442         }
9443         else
9444           $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
9445       }
9446     }
9447   }
9448   if( empty( $content ) && ( '0' != $content ))
9449     return;
9450             /** store content */
9451   switch( $type ) {
9452     case 'binary':
9453       $v = $child->addChild( $type, $content );
9454       break;
9455     case 'boolean':
9456       break;
9457     case 'cal-address':
9458       $v = $child->addChild( $type, $content );
9459       break;
9460     case 'date':
9461       if( array_key_exists( 'year', $content ))
9462         $content = array( $content );
9463       foreach( $content as $date ) {
9464         $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
9465         $v = $child->addChild( $type, $str );
9466       }
9467       break;
9468     case 'date-time':
9469       if( array_key_exists( 'year', $content ))
9470         $content = array( $content );
9471       foreach( $content as $dt ) {
9472         if( !isset( $dt['hour'] )) $dt['hour'] = 0;
9473         if( !isset( $dt['min'] ))  $dt['min']  = 0;
9474         if( !isset( $dt['sec'] ))  $dt['sec']  = 0;
9475         $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
9476         if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
9477           $str .= 'Z';
9478         $v = $child->addChild( $type, $str );
9479       }
9480       break;
9481     case 'duration':
9482       $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
9483       $v = $child->addChild( $type, $output.iCalUtilityFunctions::_format_duration( $content ) );
9484       break;
9485     case 'geo':
9486       $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' ));
9487       $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' ));
9488       break;
9489     case 'integer':
9490       $v = $child->addChild( $type, $content );
9491       break;
9492     case 'period':
9493       if( !is_array( $content ))
9494         break;
9495       foreach( $content as $period ) {
9496         $v1 = $child->addChild( $type );
9497         $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
9498         if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
9499           $str .= 'Z';
9500         $v2 = $v1->addChild( 'start', $str );
9501         if( array_key_exists( 'year', $period[1] )) {
9502           $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
9503           if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
9504             $str .= 'Z';
9505           $v2 = $v1->addChild( 'end', $str );
9506         }
9507         else
9508           $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_format_duration( $period[1] ));
9509       }
9510       break;
9511     case 'recur':
9512       foreach( $content as $rulelabel => $rulevalue ) {
9513         $rulelabel = strtolower( $rulelabel );
9514         switch( $rulelabel ) {
9515           case 'until':
9516             if( isset( $rulevalue['hour'] ))
9517               $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
9518             else
9519               $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
9520             $v = $child->addChild( $rulelabel, $str );
9521             break;
9522           case 'bysecond':
9523           case 'byminute':
9524           case 'byhour':
9525           case 'bymonthday':
9526           case 'byyearday':
9527           case 'byweekno':
9528           case 'bymonth':
9529           case 'bysetpos': {
9530             if( is_array( $rulevalue )) {
9531               foreach( $rulevalue as $vix => $valuePart )
9532                 $v = $child->addChild( $rulelabel, $valuePart );
9533             }
9534             else
9535               $v = $child->addChild( $rulelabel, $rulevalue );
9536             break;
9537           }
9538           case 'byday': {
9539             if( isset( $rulevalue['DAY'] )) {
9540               $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
9541               $str .= $rulevalue['DAY'];
9542               $p    = $child->addChild( $rulelabel, $str );
9543             }
9544             else {
9545               foreach( $rulevalue as $valuePart ) {
9546                 if( isset( $valuePart['DAY'] )) {
9547                   $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
9548                   $str .= $valuePart['DAY'];
9549                   $p    = $child->addChild( $rulelabel, $str );
9550                 }
9551                 else
9552                   $p    = $child->addChild( $rulelabel, $valuePart );
9553               }
9554             }
9555             break;
9556           }
9557           case 'freq':
9558           case 'count':
9559           case 'interval':
9560           case 'wkst':
9561           default:
9562             $p = $child->addChild( $rulelabel, $rulevalue );
9563             break;
9564         } // end switch( $rulelabel )
9565       } // end foreach( $content as $rulelabel => $rulevalue )
9566       break;
9567     case 'rstatus':
9568       $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
9569       $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
9570       if( isset( $content['extdata'] ))
9571         $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
9572       break;
9573     case 'text':
9574       if( !is_array( $content ))
9575         $content = array( $content );
9576       foreach( $content as $part )
9577         $v = $child->addChild( $type, htmlspecialchars( $part ));
9578       break;
9579     case 'time':
9580       break;
9581     case 'uri':
9582       $v = $child->addChild( $type, $content );
9583       break;
9584     case 'utc-offset':
9585       if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
9586         $str     = substr( $content, 0, 1 );
9587         $content = substr( $content, 1 );
9588       }
9589       else
9590         $str     = '+';
9591       $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
9592       if( 4 < strlen( $content ))
9593         $str .= ':'.substr( $content, 4 );
9594       $v = $child->addChild( $type, $str );
9595       break;
9596     case 'unknown':
9597     default:
9598       if( is_array( $content ))
9599         $content = implode( '', $content );
9600       $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
9601       break;
9602   }
9603 }
9604 /**
9605  * parse xml string into iCalcreator instance
9606  *
9607  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9608  * @since 2.11.2 - 2012-01-31
9609  * @param  string $xmlstr
9610  * @param  array  $iCalcfg iCalcreator config array (opt)
9611  * @return mixed  iCalcreator instance or FALSE on error
9612  */
9613 function & XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
9614   libxml_use_internal_errors( TRUE );
9615   $xml = simplexml_load_string( $xmlstr );
9616   if( !$xml ) {
9617     $str    = '';
9618     $return = FALSE;
9619     foreach( libxml_get_errors() as $error ) {
9620       switch ( $error->level ) {
9621         case LIBXML_ERR_FATAL:   $str .= ' FATAL ';   break;
9622         case LIBXML_ERR_ERROR:   $str .= ' ERROR ';   break;
9623         case LIBXML_ERR_WARNING:
9624         default:                 $str .= ' WARNING '; break;
9625       }
9626       $str .= PHP_EOL.'Error when loading XML';
9627       if( !empty( $error->file ))
9628         $str .= ',  file:'.$error->file.', ';
9629       $str .= ', line:'.$error->line;
9630       $str .= ', ('.$error->code.') '.$error->message;
9631     }
9632     error_log( $str );
9633     if( LIBXML_ERR_WARNING != $error->level )
9634       return $return;
9635     libxml_clear_errors();
9636   }
9637   return xml2iCal( $xml, $iCalcfg );
9638 }
9639 /**
9640  * parse xml file into iCalcreator instance
9641  *
9642  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9643  * @since  2.11.2 - 2012-01-20
9644  * @param  string $xmlfile
9645  * @param  array$iCalcfg iCalcreator config array (opt)
9646  * @return mixediCalcreator instance or FALSE on error
9647  */
9648 function & XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
9649   libxml_use_internal_errors( TRUE );
9650   $xml = simplexml_load_file( $xmlfile );
9651   if( !$xml ) {
9652     $str = '';
9653     foreach( libxml_get_errors() as $error ) {
9654       switch ( $error->level ) {
9655         case LIBXML_ERR_FATAL:   $str .= 'FATAL ';   break;
9656         case LIBXML_ERR_ERROR:   $str .= 'ERROR ';   break;
9657         case LIBXML_ERR_WARNING:
9658         default:                 $str .= 'WARNING '; break;
9659       }
9660       $str .= 'Failed loading XML'.PHP_EOL;
9661       if( !empty( $error->file ))
9662         $str .= ' file:'.$error->file.', ';
9663       $str .= 'line:'.$error->line.PHP_EOL;
9664       $str .= '('.$error->code.') '.$error->message.PHP_EOL;
9665     }
9666     error_log( $str );
9667     if( LIBXML_ERR_WARNING != $error->level )
9668       return FALSE;
9669     libxml_clear_errors();
9670   }
9671   return xml2iCal( $xml, $iCalcfg );
9672 }
9673 /**
9674  * parse SimpleXMLElement xCal into iCalcreator instance
9675  *
9676  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9677  * @since  2.11.2 - 2012-01-27
9678  * @param  object $xmlobj  SimpleXMLElement
9679  * @param  array  $iCalcfg iCalcreator config array (opt)
9680  * @return mixed  iCalcreator instance or FALSE on error
9681  */
9682 function & XML2iCal( $xmlobj, $iCalcfg=array()) {
9683   $iCal = new vcalendar( $iCalcfg );
9684   foreach( $xmlobj->children() as $icalendar ) { // vcalendar
9685     foreach( $icalendar->children() as $calPart ) { // calendar properties and components
9686       if( 'components' == $calPart->getName()) {
9687         foreach( $calPart->children() as $component ) { // single components
9688           if( 0 < $component->count())
9689             _getXMLComponents( $iCal, $component );
9690         }
9691       }
9692       elseif(( 'properties' == $calPart->getName()) && ( 0 < $calPart->count())) {
9693         foreach( $calPart->children() as $calProp ) { // calendar properties
9694          $propName = $calProp->getName();
9695           if(( 'calscale' != $propName ) && ( 'method' != $propName ) && ( 'x-' != substr( $propName,0,2 )))
9696             continue;
9697           $params = array();
9698           foreach( $calProp->children() as $calPropElem ) { // single calendar property
9699             if( 'parameters' == $calPropElem->getName())
9700               $params = _getXMLParams( $calPropElem );
9701             else
9702               $iCal->setProperty( $propName, reset( $calPropElem ), $params );
9703           } // end foreach( $calProp->children() as $calPropElem )
9704         } // end foreach( $calPart->properties->children() as $calProp )
9705       } // end if( 0 < $calPart->properties->count())
9706     } // end foreach( $icalendar->children() as $calPart )
9707   } // end foreach( $xmlobj->children() as $icalendar )
9708   return $iCal;
9709 }
9710 /**
9711  * parse SimpleXMLElement xCal property parameters and return iCalcreator property parameter array
9712  *
9713  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9714  * @since  2.11.2 - 2012-01-15
9715  * @param  object $parameters SimpleXMLElement
9716  * @return array  iCalcreator property parameter array
9717  */
9718 function _getXMLParams( & $parameters ) {
9719   if( 1 > $parameters->count())
9720     return array();
9721   $params = array();
9722   foreach( $parameters->children() as $parameter ) { // single parameter key
9723     $key   = strtoupper( $parameter->getName());
9724     $value = array();
9725     foreach( $parameter->children() as $paramValue ) // skip parameter value type
9726       $value[] = reset( $paramValue );
9727     if( 2 > count( $value ))
9728       $params[$key] = html_entity_decode( reset( $value ));
9729     else
9730       $params[$key] = $value;
9731   }
9732   return $params;
9733 }
9734 /**
9735  * parse SimpleXMLElement xCal components, create iCalcreator component and update
9736  *
9737  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9738  * @since  2.11.2 - 2012-01-15
9739  * @param  array  $iCal iCalcreator calendar instance
9740  * @param  object $component SimpleXMLElement
9741  * @return void
9742  */
9743 function _getXMLComponents( & $iCal, & $component ) {
9744   $compName = $component->getName();
9745   $comp     = & $iCal->newComponent( $compName );
9746   $subComponents = array( 'valarm', 'standard', 'daylight' );
9747   foreach( $component->children() as $compPart ) { // properties and (opt) subComponents
9748     if( 1 > $compPart->count())
9749       continue;
9750     if( in_array( $compPart->getName(), $subComponents ))
9751       _getXMLComponents( $comp, $compPart );
9752     elseif( 'properties' == $compPart->getName()) {
9753       foreach( $compPart->children() as $property ) // properties as single property
9754         _getXMLProperties( $comp, $property );
9755     }
9756   } // end foreach( $component->children() as $compPart )
9757 }
9758 /**
9759  * parse SimpleXMLElement xCal property, create iCalcreator component property
9760  *
9761  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9762  * @since  2.11.2 - 2012-01-27
9763  * @param  array  $iCal iCalcreator calendar instance
9764  * @param  object $component SimpleXMLElement
9765  * @return void
9766  */
9767 function _getXMLProperties( & $iCal, & $property ) {
9768   $propName  = $property->getName();
9769   $value     = $params = array();
9770   $valueType = '';
9771   foreach( $property->children() as $propPart ) { // calendar property parameters (opt) and value(-s)
9772     $valueType = $propPart->getName();
9773     if( 'parameters' == $valueType) {
9774       $params = _getXMLParams( $propPart );
9775       continue;
9776     }
9777     switch( $valueType ) {
9778       case 'binary':
9779         $value = reset( $propPart );
9780         break;
9781       case 'boolean':
9782         break;
9783       case 'cal-address':
9784         $value = reset( $propPart );
9785         break;
9786       case 'date':
9787         $params['VALUE'] = 'DATE';
9788       case 'date-time':
9789         if(( 'exdate' == $propName ) || ( 'rdate' == $propName ))
9790           $value[] = reset( $propPart );
9791         else
9792           $value = reset( $propPart );
9793         break;
9794       case 'duration':
9795         $value = reset( $propPart );
9796         break;
9797 //        case 'geo':
9798       case 'latitude':
9799       case 'longitude':
9800         $value[$valueType] = reset( $propPart );
9801         break;
9802       case 'integer':
9803         $value = reset( $propPart );
9804         break;
9805       case 'period':
9806         if( 'rdate' == $propName )
9807           $params['VALUE'] = 'PERIOD';
9808         $pData = array();
9809         foreach( $propPart->children() as $periodPart )
9810           $pData[] = reset( $periodPart );
9811         if( !empty( $pData ))
9812           $value[] = $pData;
9813         break;
9814 //        case 'rrule':
9815       case 'freq':
9816       case 'count':
9817       case 'until':
9818       case 'interval':
9819       case 'wkst':
9820         $value[$valueType] = reset( $propPart );
9821         break;
9822       case 'bysecond':
9823       case 'byminute':
9824       case 'byhour':
9825       case 'bymonthday':
9826       case 'byyearday':
9827       case 'byweekno':
9828       case 'bymonth':
9829       case 'bysetpos':
9830         $value[$valueType][] = reset( $propPart );
9831         break;
9832       case 'byday':
9833         $byday = reset( $propPart );
9834         if( 2 == strlen( $byday ))
9835           $value[$valueType][] = array( 'DAY' => $byday );
9836         else {
9837           $day = substr( $byday, -2 );
9838           $key = substr( $byday, 0, ( strlen( $byday ) - 2 ));
9839           $value[$valueType][] = array( $key, 'DAY' => $day );
9840         }
9841         break;
9842 //      case 'rstatus':
9843       case 'code':
9844         $value[0] = reset( $propPart );
9845         break;
9846       case 'description':
9847         $value[1] = reset( $propPart );
9848         break;
9849       case 'data':
9850         $value[2] = reset( $propPart );
9851         break;
9852       case 'text':
9853         $text = str_replace( array( "\r\n", "\n\r", "\r", "\n"), '\n', reset( $propPart ));
9854         $value['text'][] = html_entity_decode( $text );
9855         break;
9856       case 'time':
9857         break;
9858       case 'uri':
9859         $value = reset( $propPart );
9860         break;
9861       case 'utc-offset':
9862         $value = str_replace( ':', '', reset( $propPart ));
9863         break;
9864       case 'unknown':
9865       default:
9866         $value = html_entity_decode( reset( $propPart ));
9867         break;
9868     } // end switch( $valueType )
9869   } // end  foreach( $property->children() as $propPart )
9870   if( 'freebusy' == $propName ) {
9871     $fbtype = $params['FBTYPE'];
9872     unset( $params['FBTYPE'] );
9873     $iCal->setProperty( $propName, $fbtype, $value, $params );
9874   }
9875   elseif( 'geo' == $propName )
9876     $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
9877   elseif( 'request-status' == $propName ) {
9878     if( !isset( $value[2] ))
9879       $value[2] = FALSE;
9880     $iCal->setProperty( $propName, $value[0], $value[1], $value[2], $params );
9881   }
9882   else {
9883     if( isset( $value['text'] ) && is_array( $value['text'] )) {
9884       if(( 'categories' == $propName ) || ( 'resources' == $propName ))
9885         $value = $value['text'];
9886       else
9887         $value = reset( $value['text'] );
9888     }
9889     $iCal->setProperty( $propName, $value, $params );
9890   }
9891 }
9892 /**
9893  * Additional functions to use with vtimezone components
9894  * For use with
9895  * iCalcreator (kigkonsult.se/iCalcreator/index.php)
9896  * copyright (c) 2011 Yitzchok Lavi
9897  * icalcreator@onebigsystem.com
9898  *
9899  * This library is free software; you can redistribute it and/or
9900  * modify it under the terms of the GNU Lesser General Public
9901  * License as published by the Free Software Foundation; either
9902  * version 2.1 of the License, or (at your option) any later version.
9903  *
9904  * This library is distributed in the hope that it will be useful,
9905  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9906  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
9907  * Lesser General Public License for more details.
9908  *
9909  * You should have received a copy of the GNU Lesser General Public
9910  * License along with this library; if not, write to the Free Software
9911  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
9912  */
9913 /**
9914  * Additional functions to use with vtimezone components
9915  *
9916  * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
9917  *
9918  * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
9919  *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9920  * @version 1.0.2 - 2011-02-24
9921  *
9922  */
9923 /**
9924  * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
9925  * timezone, according to the VTIMEZONE information in the input array.
9926  *
9927  * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below)
9928  * $param string $tzid,           time zone identifier
9929  * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format)
9930  * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
9931  *
9932  */
9933 function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
9934     if( is_array( $timestamp )) {
9935 //$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] );
9936       $timestamp = gmmktime(
9937             $timestamp['hour'],
9938             $timestamp['min'],
9939             $timestamp['sec'],
9940             $timestamp['month'],
9941             $timestamp['day'],
9942             $timestamp['year']
9943             ) ;
9944 //echo '<td colspan="4">&nbsp;'."\n".'<tr><td>&nbsp;<td class="r">'.$timestamp.'<td class="r">'.$disp.'<td colspan="4">&nbsp;'."\n".'<tr><td colspan="3">&nbsp;'; // test ###
9945     }
9946     $tzoffset = array();
9947     // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
9948     $tzoffset['offsetHis'] = '+0000';
9949     $tzoffset['offsetSec'] = 0;
9950     $tzoffset['tzname']    = '?';
9951     if( !isset( $timezonesarray[$tzid] ))
9952       return $tzoffset;
9953     $tzdatearray = $timezonesarray[$tzid];
9954     if ( is_array($tzdatearray) ) {
9955         sort($tzdatearray); // just in case
9956         if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
9957             // our date is before the first change
9958             $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
9959             $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
9960             $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
9961         } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
9962             // our date is after the last change (we do this so our scan can stop at the last record but one)
9963             $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
9964             $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
9965             $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
9966         } else {
9967             // our date somewhere in between
9968             // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
9969             // we don't include the last date in our loop as there isn't one after it to check
9970             for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
9971                 if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
9972                     $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
9973                     $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
9974                     $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ;
9975                     break;
9976                 }
9977             }
9978         }
9979     }
9980     return $tzoffset;
9981 }
9982 /**
9983  * Returns an array containing all the timezone data in the vcalendar object
9984  *
9985  * @param object $vcalendar, iCalcreator calendar instance
9986  * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
9987  *                based on the timezone data in the vcalendar object
9988  *
9989  */
9990 function getTimezonesAsDateArrays($vcalendar) {
9991     $timezonedata = array();
9992     while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
9993         $tzid       = $vtz->getProperty('tzid');
9994         $alltzdates = array();
9995         while ( $vtzc = $vtz->getComponent( 'standard' )) {
9996             $newtzdates = expandTimezoneDates($vtzc);
9997             $alltzdates = array_merge($alltzdates, $newtzdates);
9998         }
9999         while ( $vtzc = $vtz->getComponent( 'daylight' )) {
10000             $newtzdates = expandTimezoneDates($vtzc);
10001             $alltzdates = array_merge($alltzdates, $newtzdates);
10002         }
10003         sort($alltzdates);
10004         $timezonedata[$tzid] = $alltzdates;
10005     }
10006     return $timezonedata;
10007 }
10008 /**
10009  * Returns an array containing time zone data from vtimezone standard/daylight instances
10010  *
10011  * @param object $vtzc, an iCalcreator calendar standard/daylight instance
10012  * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
10013  *
10014  */
10015 function expandTimezoneDates($vtzc) {
10016     $tzdates = array();
10017     // prepare time zone "description" to attach to each change
10018     $tzbefore = array();
10019     $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ;
10020     $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
10021     if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
10022       $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
10023     $tzafter = array();
10024     $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ;
10025     $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
10026     if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
10027       $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
10028     if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
10029       $tzafter['tzname'] = $tzafter['offsetHis'];
10030     // find out where to start from
10031     $dtstart = $vtzc->getProperty('dtstart');
10032     $dtstarttimestamp = mktime(
10033             $dtstart['hour'],
10034             $dtstart['min'],
10035             $dtstart['sec'],
10036             $dtstart['month'],
10037             $dtstart['day'],
10038             $dtstart['year']
10039             ) ;
10040     if( !isset( $dtstart['unparsedtext'] )) // ??
10041       $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
10042     if ( $dtstarttimestamp == 0 ) {
10043         // it seems that the dtstart string may not have parsed correctly
10044         // let's set a timestamp starting from 1902, using the time part of the original string
10045         // so that the time will change at the right time of day
10046         // at worst we'll get midnight again
10047         $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
10048         $dtstarttimestamp = strtotime("19020101",0);
10049         $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
10050     }
10051     // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
10052     $diff  = -1 * $tzbefore['offsetSec'];
10053     $dtstarttimestamp += $diff;
10054                 // add this (start) change to the array of changes
10055     $tzdates[] = array(
10056         'timestamp' => $dtstarttimestamp,
10057         'tzbefore'  => $tzbefore,
10058         'tzafter'   => $tzafter
10059         );
10060     $datearray = getdate($dtstarttimestamp);
10061     // save original array to use time parts, because strtotime (used below) apparently loses the time
10062     $changetime = $datearray ;
10063     // generate dates according to an RRULE line
10064     $rrule = $vtzc->getProperty('rrule') ;
10065     if ( is_array($rrule) ) {
10066         if ( $rrule['FREQ'] == 'YEARLY' ) {
10067             // calculate transition dates starting from DTSTART
10068             $offsetchangetimestamp = $dtstarttimestamp;
10069             // calculate transition dates until 10 years in the future
10070             $stoptimestamp = strtotime("+10 year",time());
10071             // if UNTIL is set, calculate until then (however far ahead)
10072             if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
10073                 $stoptimestamp = mktime(
10074                     $rrule['UNTIL']['hour'],
10075                     $rrule['UNTIL']['min'],
10076                     $rrule['UNTIL']['sec'],
10077                     $rrule['UNTIL']['month'],
10078                     $rrule['UNTIL']['day'],
10079                     $rrule['UNTIL']['year']
10080                     ) ;
10081             }
10082             $count = 0 ;
10083             $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
10084             $daynames = array(
10085                         'SU' => 'Sunday',
10086                         'MO' => 'Monday',
10087                         'TU' => 'Tuesday',
10088                         'WE' => 'Wednesday',
10089                         'TH' => 'Thursday',
10090                         'FR' => 'Friday',
10091                         'SA' => 'Saturday'
10092                         );
10093             // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
10094             while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
10095                 // break up the timestamp into its parts
10096                 $datearray = getdate($offsetchangetimestamp);
10097                 if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
10098                     // set the month
10099                     $datearray['mon'] = $rrule['BYMONTH'] ;
10100                 }
10101                 if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
10102                     // set specific day of month
10103                     $datearray['mday']  = $rrule['BYMONTHDAY'];
10104                 } elseif ( is_array($rrule['BYDAY']) ) {
10105                     // find the Xth WKDAY in the month
10106                     // the starting point for this process is the first of the month set above
10107                     $datearray['mday'] = 1 ;
10108                     // turn $datearray as it is now back into a timestamp
10109                     $offsetchangetimestamp = mktime(
10110                         $datearray['hours'],
10111                         $datearray['minutes'],
10112                         $datearray['seconds'],
10113                         $datearray['mon'],
10114                         $datearray['mday'],
10115                         $datearray['year']
10116                             );
10117                     if ($rrule['BYDAY'][0] > 0) {
10118                         // to find Xth WKDAY in month, we find last WKDAY in month before
10119                         // we do that by finding first WKDAY in this month and going back one week
10120                         // then we add X weeks (below)
10121                         $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
10122                         $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
10123                     } else {
10124                         // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
10125                         // we do that by going forward one month and going to WKDAY there
10126                         // then we subtract X weeks (below)
10127                         $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
10128                         $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
10129                     }
10130                     // now move forward or back the appropriate number of weeks, into the month we want
10131                     $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
10132                     $datearray = getdate($offsetchangetimestamp);
10133                 }
10134                 // convert the date parts back into a timestamp, setting the time parts according to the
10135                 // original time data which we stored
10136                 $offsetchangetimestamp = mktime(
10137                     $changetime['hours'],
10138                     $changetime['minutes'],
10139                     $changetime['seconds'] + $diff,
10140                     $datearray['mon'],
10141                     $datearray['mday'],
10142                     $datearray['year']
10143                         );
10144                 // add this change to the array of changes
10145                 $tzdates[] = array(
10146                     'timestamp' => $offsetchangetimestamp,
10147                     'tzbefore'  => $tzbefore,
10148                     'tzafter'   => $tzafter
10149                     );
10150                 // update counters (timestamp and count)
10151                 $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
10152                 $count += 1 ;
10153             }
10154         }
10155     }
10156     // generate dates according to RDATE lines
10157     while ($rdates = $vtzc->getProperty('rdate')) {
10158         if ( is_array($rdates) ) {
10159
10160             foreach ( $rdates as $rdate ) {
10161                 // convert the explicit change date to a timestamp
10162                 $offsetchangetimestamp = mktime(
10163                         $rdate['hour'],
10164                         $rdate['min'],
10165                         $rdate['sec'] + $diff,
10166                         $rdate['month'],
10167                         $rdate['day'],
10168                         $rdate['year']
10169                         ) ;
10170                 // add this change to the array of changes
10171                 $tzdates[] = array(
10172                     'timestamp' => $offsetchangetimestamp,
10173                     'tzbefore'  => $tzbefore,
10174                     'tzafter'   => $tzafter
10175                     );
10176             }
10177         }
10178     }
10179     return $tzdates;
10180 }
10181 ?>