fix image text
[pear] / iCal / calendarComponent.php
1 <?php
2
3 /*********************************************************************************/
4 /*********************************************************************************/
5 /**
6  *  abstract class for calendar components
7  *
8  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9  * @since 2.9.6 - 2011-05-14
10  */
11 class calendarComponent {
12             //  component property variables
13   var $uid;
14   var $dtstamp;
15
16             //  component config variables
17   var $allowEmpty;
18   var $language;
19   var $nl;
20   var $unique_id;
21   var $format;
22   var $objName; // created automatically at instance creation
23   var $dtzid;   // default (local) timezone
24             //  component internal variables
25   var $componentStart1;
26   var $componentStart2;
27   var $componentEnd1;
28   var $componentEnd2;
29   var $elementStart1;
30   var $elementStart2;
31   var $elementEnd1;
32   var $elementEnd2;
33   var $intAttrDelimiter;
34   var $attributeDelimiter;
35   var $valueInit;
36             //  component xCal declaration container
37   var $xcaldecl;
38 /**
39  * constructor for calendar component object
40  *
41  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
42  * @since 2.9.6 - 2011-05-17
43  */
44   function calendarComponent() {
45     $this->objName         = ( isset( $this->timezonetype )) ?
46                           strtolower( $this->timezonetype )  :  get_class ( $this );
47     $this->uid             = array();
48     $this->dtstamp         = array();
49
50     $this->language        = null;
51     $this->nl              = null;
52     $this->unique_id       = null;
53     $this->format          = null;
54     $this->dtzid           = null;
55     $this->allowEmpty      = TRUE;
56     $this->xcaldecl        = array();
57
58     $this->_createFormat();
59     $this->_makeDtstamp();
60   }
61 /*********************************************************************************/
62 /**
63  * Property Name: ACTION
64  */
65 /**
66  * creates formatted output for calendar component property action
67  *
68  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
69  * @since 2.4.8 - 2008-10-22
70  * @return string
71  */
72   function createAction() {
73     if( empty( $this->action )) return FALSE;
74     if( empty( $this->action['value'] ))
75       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
76     $attributes = $this->_createParams( $this->action['params'] );
77     return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
78   }
79 /**
80  * set calendar component property action
81  *
82  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
83  * @since 2.4.8 - 2008-11-04
84  * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
85  * @param mixed $params
86  * @return bool
87  */
88   function setAction( $value, $params=FALSE ) {
89     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
90     $this->action = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
91     return TRUE;
92   }
93 /*********************************************************************************/
94 /**
95  * Property Name: ATTACH
96  */
97 /**
98  * creates formatted output for calendar component property attach
99  *
100  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
101  * @since 0.9.7 - 2006-11-23
102  * @return string
103  */
104   function createAttach() {
105     if( empty( $this->attach )) return FALSE;
106     $output       = null;
107     foreach( $this->attach as $attachPart ) {
108       if(! empty( $attachPart['value'] )) {
109         $attributes = $this->_createParams( $attachPart['params'] );
110         $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
111       }
112       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
113     }
114     return $output;
115   }
116 /**
117  * set calendar component property attach
118  *
119  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
120  * @since 2.5.1 - 2008-11-06
121  * @param string $value
122  * @param array $params, optional
123  * @param integer $index, optional
124  * @return bool
125  */
126   function setAttach( $value, $params=FALSE, $index=FALSE ) {
127     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
128     iCal_UtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
129     return TRUE;
130   }
131 /*********************************************************************************/
132 /**
133  * Property Name: ATTENDEE
134  */
135 /**
136  * creates formatted output for calendar component property attendee
137  *
138  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
139  * @since 2.9.8 - 2011-05-30
140  * @return string
141  */
142   function createAttendee() {
143     if( empty( $this->attendee )) return FALSE;
144     $output = null;
145     foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
146       if( empty( $attendeePart['value'] )) {
147         if( $this->getConfig( 'allowEmpty' ))
148           $output .= $this->_createElement( 'ATTENDEE' );
149         continue;
150       }
151       $attendee1 = $attendee2 = null;
152       foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
153         if( 'value' == $paramlabel )
154           $attendee2     .= $paramvalue;
155         elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
156         // set attenddee parameters in rfc2445 order
157           if( isset( $paramvalue['CUTYPE'] ))
158             $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
159           if( isset( $paramvalue['MEMBER'] )) {
160             $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
161             foreach( $paramvalue['MEMBER'] as $cix => $opv )
162               $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ;
163           }
164           if( isset( $paramvalue['ROLE'] ))
165             $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
166           if( isset( $paramvalue['PARTSTAT'] ))
167             $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
168           if( isset( $paramvalue['RSVP'] ))
169             $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
170           if( isset( $paramvalue['DELEGATED-TO'] )) {
171             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
172             foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
173               $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ;
174           }
175           if( isset( $paramvalue['DELEGATED-FROM'] )) {
176             $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
177             foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
178               $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ;
179           }
180           if( isset( $paramvalue['SENT-BY'] ))
181             $attendee1   .= $this->intAttrDelimiter.'SENT-BY="'.$paramvalue['SENT-BY'].'"';
182           if( isset( $paramvalue['CN'] ))
183             $attendee1   .= $this->intAttrDelimiter.'CN="'.$paramvalue['CN'].'"';
184           if( isset( $paramvalue['DIR'] ))
185             $attendee1   .= $this->intAttrDelimiter.'DIR="'.$paramvalue['DIR'].'"';
186           if( isset( $paramvalue['LANGUAGE'] ))
187             $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
188           $xparams = array();
189           foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
190             if( ctype_digit( (string) $optparamlabel )) {
191               $xparams[]  = $optparamvalue;
192               continue;
193             }
194             if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
195               $xparams[$optparamlabel] = $optparamvalue;
196           } // end foreach 3
197           ksort( $xparams, SORT_STRING );
198           foreach( $xparams as $paramKey => $paramValue ) {
199             if( ctype_digit( (string) $paramKey ))
200               $attendee1 .= $this->intAttrDelimiter.$paramValue;
201             else
202               $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
203           }      // end foreach 3
204         }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
205       }          // end foreach 2
206       $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
207     }              // end foreach 1
208     return $output;
209   }
210 /**
211  * set calendar component property attach
212  *
213  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
214  * @since 2.6.34 - 2010-12-18
215  * @param string $value
216  * @param array $params, optional
217  * @param integer $index, optional
218  * @return bool
219  */
220   function setAttendee( $value, $params=FALSE, $index=FALSE ) {
221     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
222           // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
223     if( FALSE !== ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
224       $value = strtoupper( substr( $value, 0, $pos )).substr( $value, $pos );
225     elseif( !empty( $value ))
226       $value = 'MAILTO:'.$value;
227     $params2 = array();
228     if( is_array($params )) {
229       $optarrays = array();
230       foreach( $params as $optparamlabel => $optparamvalue ) {
231         $optparamlabel = strtoupper( $optparamlabel );
232         switch( $optparamlabel ) {
233           case 'MEMBER':
234           case 'DELEGATED-TO':
235           case 'DELEGATED-FROM':
236             if( !is_array( $optparamvalue ))
237               $optparamvalue = array( $optparamvalue );
238             foreach( $optparamvalue as $part ) {
239               $part = trim( $part );
240               if(( '"' == substr( $part, 0, 1 )) &&
241                  ( '"' == substr( $part, -1 )))
242                 $part = substr( $part, 1, ( strlen( $part ) - 2 ));
243               if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
244                 $part = "MAILTO:$part";
245               else
246                 $part = 'MAILTO:'.substr( $part, 7 );
247               $optarrays[$optparamlabel][] = $part;
248             }
249             break;
250           default:
251             if(( '"' == substr( $optparamvalue, 0, 1 )) &&
252                ( '"' == substr( $optparamvalue, -1 )))
253               $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
254             if( 'SENT-BY' ==  $optparamlabel ) {
255               if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
256                 $optparamvalue = "MAILTO:$optparamvalue";
257               else
258                 $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
259             }
260             $params2[$optparamlabel] = $optparamvalue;
261             break;
262         } // end switch( $optparamlabel.. .
263       } // end foreach( $optparam.. .
264       foreach( $optarrays as $optparamlabel => $optparams )
265         $params2[$optparamlabel] = $optparams;
266     }
267         // remove defaults
268     iCal_UtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
269     iCal_UtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
270     iCal_UtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
271     iCal_UtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
272         // check language setting
273     if( isset( $params2['CN' ] )) {
274       $lang = $this->getConfig( 'language' );
275       if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
276         $params2['LANGUAGE' ] = $lang;
277     }
278     iCal_UtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
279     return TRUE;
280   }
281 /*********************************************************************************/
282 /**
283  * Property Name: CATEGORIES
284  */
285 /**
286  * creates formatted output for calendar component property categories
287  *
288  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
289  * @since 2.4.8 - 2008-10-22
290  * @return string
291  */
292   function createCategories() {
293     if( empty( $this->categories )) return FALSE;
294     $output = null;
295     foreach( $this->categories as $category ) {
296       if( empty( $category['value'] )) {
297         if ( $this->getConfig( 'allowEmpty' ))
298           $output .= $this->_createElement( 'CATEGORIES' );
299         continue;
300       }
301       $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
302       if( is_array( $category['value'] )) {
303         foreach( $category['value'] as $cix => $categoryPart )
304           $category['value'][$cix] = $this->_strrep( $categoryPart );
305         $content  = implode( ',', $category['value'] );
306       }
307       else
308         $content  = $this->_strrep( $category['value'] );
309       $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
310     }
311     return $output;
312   }
313 /**
314  * set calendar component property categories
315  *
316  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
317  * @since 2.5.1 - 2008-11-06
318  * @param mixed $value
319  * @param array $params, optional
320  * @param integer $index, optional
321  * @return bool
322  */
323   function setCategories( $value, $params=FALSE, $index=FALSE ) {
324     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
325     iCal_UtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
326     return TRUE;
327  }
328 /*********************************************************************************/
329 /**
330  * Property Name: CLASS
331  */
332 /**
333  * creates formatted output for calendar component property class
334  *
335  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
336  * @since 0.9.7 - 2006-11-20
337  * @return string
338  */
339   function createClass() {
340     if( empty( $this->class )) return FALSE;
341     if( empty( $this->class['value'] ))
342       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
343     $attributes = $this->_createParams( $this->class['params'] );
344     return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
345   }
346 /**
347  * set calendar component property class
348  *
349  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
350  * @since 2.4.8 - 2008-11-04
351  * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
352  * @param array $params optional
353  * @return bool
354  */
355   function setClass( $value, $params=FALSE ) {
356     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
357     $this->class = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
358     return TRUE;
359   }
360 /*********************************************************************************/
361 /**
362  * Property Name: COMMENT
363  */
364 /**
365  * creates formatted output for calendar component property comment
366  *
367  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
368  * @since 2.4.8 - 2008-10-22
369  * @return string
370  */
371   function createComment() {
372     if( empty( $this->comment )) return FALSE;
373     $output = null;
374     foreach( $this->comment as $commentPart ) {
375       if( empty( $commentPart['value'] )) {
376         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
377         continue;
378       }
379       $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
380       $content    = $this->_strrep( $commentPart['value'] );
381       $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
382     }
383     return $output;
384   }
385 /**
386  * set calendar component property comment
387  *
388  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
389  * @since 2.5.1 - 2008-11-06
390  * @param string $value
391  * @param array $params, optional
392  * @param integer $index, optional
393  * @return bool
394  */
395   function setComment( $value, $params=FALSE, $index=FALSE ) {
396     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
397     iCal_UtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
398     return TRUE;
399   }
400 /*********************************************************************************/
401 /**
402  * Property Name: COMPLETED
403  */
404 /**
405  * creates formatted output for calendar component property completed
406  *
407  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
408  * @since 2.4.8 - 2008-10-22
409  * @return string
410  */
411   function createCompleted( ) {
412     if( empty( $this->completed )) return FALSE;
413     if( !isset( $this->completed['value']['year'] )  &&
414         !isset( $this->completed['value']['month'] ) &&
415         !isset( $this->completed['value']['day'] )   &&
416         !isset( $this->completed['value']['hour'] )  &&
417         !isset( $this->completed['value']['min'] )   &&
418         !isset( $this->completed['value']['sec'] ))
419       if( $this->getConfig( 'allowEmpty' ))
420         return $this->_createElement( 'COMPLETED' );
421       else return FALSE;
422     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->completed['value'], 7 );
423     $attributes = $this->_createParams( $this->completed['params'] );
424     return $this->_createElement( 'COMPLETED', $attributes, $formatted );
425   }
426 /**
427  * set calendar component property completed
428  *
429  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
430  * @since 2.4.8 - 2008-10-23
431  * @param mixed $year
432  * @param mixed $month optional
433  * @param int $day optional
434  * @param int $hour optional
435  * @param int $min optional
436  * @param int $sec optional
437  * @param array $params optional
438  * @return bool
439  */
440   function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
441     if( empty( $year )) {
442       if( $this->getConfig( 'allowEmpty' )) {
443         $this->completed = array( 'value' => null, 'params' => iCal_UtilityFunctions::_setParams( $params ));
444         return TRUE;
445       }
446       else
447         return FALSE;
448     }
449     $this->completed = iCal_UtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
450     return TRUE;
451   }
452 /*********************************************************************************/
453 /**
454  * Property Name: CONTACT
455  */
456 /**
457  * creates formatted output for calendar component property contact
458  *
459  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
460  * @since 2.4.8 - 2008-10-23
461  * @return string
462  */
463   function createContact() {
464     if( empty( $this->contact )) return FALSE;
465     $output = null;
466     foreach( $this->contact as $contact ) {
467       if( !empty( $contact['value'] )) {
468         $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
469         $content    = $this->_strrep( $contact['value'] );
470         $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
471       }
472       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
473     }
474     return $output;
475   }
476 /**
477  * set calendar component property contact
478  *
479  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
480  * @since 2.5.1 - 2008-11-05
481  * @param string $value
482  * @param array $params, optional
483  * @param integer $index, optional
484  * @return bool
485  */
486   function setContact( $value, $params=FALSE, $index=FALSE ) {
487     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
488     iCal_UtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
489     return TRUE;
490   }
491 /*********************************************************************************/
492 /**
493  * Property Name: CREATED
494  */
495 /**
496  * creates formatted output for calendar component property created
497  *
498  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
499  * @since 2.4.8 - 2008-10-21
500  * @return string
501  */
502   function createCreated() {
503     if( empty( $this->created )) return FALSE;
504     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->created['value'], 7 );
505     $attributes = $this->_createParams( $this->created['params'] );
506     return $this->_createElement( 'CREATED', $attributes, $formatted );
507   }
508 /**
509  * set calendar component property created
510  *
511  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
512  * @since 2.4.8 - 2008-10-23
513  * @param mixed $year optional
514  * @param mixed $month optional
515  * @param int $day optional
516  * @param int $hour optional
517  * @param int $min optional
518  * @param int $sec optional
519  * @param mixed $params optional
520  * @return bool
521  */
522   function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
523     if( !isset( $year )) {
524       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
525     }
526     $this->created = iCal_UtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
527     return TRUE;
528   }
529 /*********************************************************************************/
530 /**
531  * Property Name: DESCRIPTION
532  */
533 /**
534  * creates formatted output for calendar component property description
535  *
536  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
537  * @since 2.4.8 - 2008-10-22
538  * @return string
539  */
540   function createDescription() {
541     if( empty( $this->description )) return FALSE;
542     $output       = null;
543     foreach( $this->description as $description ) {
544       if( !empty( $description['value'] )) {
545         $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
546         $content    = $this->_strrep( $description['value'] );
547         $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
548       }
549       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
550     }
551     return $output;
552   }
553 /**
554  * set calendar component property description
555  *
556  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
557  * @since 2.6.24 - 2010-11-06
558  * @param string $value
559  * @param array $params, optional
560  * @param integer $index, optional
561  * @return bool
562  */
563   function setDescription( $value, $params=FALSE, $index=FALSE ) {
564     if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; }
565     if( 'vjournal' != $this->objName )
566       $index = 1;
567     iCal_UtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
568     return TRUE;
569   }
570 /*********************************************************************************/
571 /**
572  * Property Name: DTEND
573  */
574 /**
575  * creates formatted output for calendar component property dtend
576  *
577  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
578  * @since 2.9.6 - 2011-05-14
579  * @return string
580  */
581   function createDtend() {
582     if( empty( $this->dtend )) return FALSE;
583     if( !isset( $this->dtend['value']['year'] )  &&
584         !isset( $this->dtend['value']['month'] ) &&
585         !isset( $this->dtend['value']['day'] )   &&
586         !isset( $this->dtend['value']['hour'] )  &&
587         !isset( $this->dtend['value']['min'] )   &&
588         !isset( $this->dtend['value']['sec'] ))
589       if( $this->getConfig( 'allowEmpty' ))
590         return $this->_createElement( 'DTEND' );
591       else return FALSE;
592     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->dtend['value'] );
593     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
594        ( !isset( $this->dtend['params']['VALUE'] )        || ( $this->dtend['params']['VALUE'] != 'DATE' )) &&
595          !isset( $this->dtend['params']['TZID'] ))
596       $this->dtend['params']['TZID'] = $tzid;
597     $attributes = $this->_createParams( $this->dtend['params'] );
598     return $this->_createElement( 'DTEND', $attributes, $formatted );
599   }
600 /**
601  * set calendar component property dtend
602  *
603  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
604  * @since 2.9.6 - 2011-05-14
605  * @param mixed $year
606  * @param mixed $month optional
607  * @param int $day optional
608  * @param int $hour optional
609  * @param int $min optional
610  * @param int $sec optional
611  * @param string $tz optional
612  * @param array params optional
613  * @return bool
614  */
615   function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
616     if( empty( $year )) {
617       if( $this->getConfig( 'allowEmpty' )) {
618         $this->dtend = array( 'value' => null, 'params' => iCal_UtilityFunctions::_setParams( $params ));
619         return TRUE;
620       }
621       else
622         return FALSE;
623     }
624     $this->dtend = iCal_UtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
625     return TRUE;
626   }
627 /*********************************************************************************/
628 /**
629  * Property Name: DTSTAMP
630  */
631 /**
632  * creates formatted output for calendar component property dtstamp
633  *
634  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
635  * @since 2.4.4 - 2008-03-07
636  * @return string
637  */
638   function createDtstamp() {
639     if( !isset( $this->dtstamp['value']['year'] )  &&
640         !isset( $this->dtstamp['value']['month'] ) &&
641         !isset( $this->dtstamp['value']['day'] )   &&
642         !isset( $this->dtstamp['value']['hour'] )  &&
643         !isset( $this->dtstamp['value']['min'] )   &&
644         !isset( $this->dtstamp['value']['sec'] ))
645       $this->_makeDtstamp();
646     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->dtstamp['value'], 7 );
647     $attributes = $this->_createParams( $this->dtstamp['params'] );
648     return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
649   }
650 /**
651  * computes datestamp for calendar component object instance dtstamp
652  *
653  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
654  * @since 2.10.9 - 2011-08-10
655  * @return void
656  */
657   function _makeDtstamp() {
658     $d = mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y'));
659     $this->dtstamp['value'] = array( 'year'  => date( 'Y', $d )
660                                    , 'month' => date( 'm', $d )
661                                    , 'day'   => date( 'd', $d )
662                                    , 'hour'  => date( 'H', $d )
663                                    , 'min'   => date( 'i', $d )
664                                    , 'sec'   => date( 's', $d ));
665     $this->dtstamp['params'] = null;
666   }
667 /**
668  * set calendar component property dtstamp
669  *
670  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
671  * @since 2.4.8 - 2008-10-23
672  * @param mixed $year
673  * @param mixed $month optional
674  * @param int $day optional
675  * @param int $hour optional
676  * @param int $min optional
677  * @param int $sec optional
678  * @param array $params optional
679  * @return TRUE
680  */
681   function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
682     if( empty( $year ))
683       $this->_makeDtstamp();
684     else
685       $this->dtstamp = iCal_UtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
686     return TRUE;
687   }
688 /*********************************************************************************/
689 /**
690  * Property Name: DTSTART
691  */
692 /**
693  * creates formatted output for calendar component property dtstart
694  *
695  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
696  * @since 2.9.6 - 2011-05-15
697  * @return string
698  */
699   function createDtstart() {
700     if( empty( $this->dtstart )) return FALSE;
701     if( !isset( $this->dtstart['value']['year'] )  &&
702         !isset( $this->dtstart['value']['month'] ) &&
703         !isset( $this->dtstart['value']['day'] )   &&
704         !isset( $this->dtstart['value']['hour'] )  &&
705         !isset( $this->dtstart['value']['min'] )   &&
706         !isset( $this->dtstart['value']['sec'] )) {
707       if( $this->getConfig( 'allowEmpty' ))
708         return $this->_createElement( 'DTSTART' );
709       else return FALSE;
710     }
711     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
712       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
713     elseif(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
714        ( !isset( $this->dtstart['params']['VALUE'] ) || ( $this->dtstart['params']['VALUE'] != 'DATE' ))  &&
715          !isset( $this->dtstart['params']['TZID'] ))
716       $this->dtstart['params']['TZID'] = $tzid;
717     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->dtstart['value'] );
718     $attributes = $this->_createParams( $this->dtstart['params'] );
719     return $this->_createElement( 'DTSTART', $attributes, $formatted );
720   }
721 /**
722  * set calendar component property dtstart
723  *
724  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
725  * @since 2.6.22 - 2010-09-22
726  * @param mixed $year
727  * @param mixed $month optional
728  * @param int $day optional
729  * @param int $hour optional
730  * @param int $min optional
731  * @param int $sec optional
732  * @param string $tz optional
733  * @param array $params optional
734  * @return bool
735  */
736   function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
737     if( empty( $year )) {
738       if( $this->getConfig( 'allowEmpty' )) {
739         $this->dtstart = array( 'value' => null, 'params' => iCal_UtilityFunctions::_setParams( $params ));
740         return TRUE;
741       }
742       else
743         return FALSE;
744     }
745     $this->dtstart = iCal_UtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
746     return TRUE;
747   }
748 /*********************************************************************************/
749 /**
750  * Property Name: DUE
751  */
752 /**
753  * creates formatted output for calendar component property due
754  *
755  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
756  * @since 2.4.8 - 2008-10-22
757  * @return string
758  */
759   function createDue() {
760     if( empty( $this->due )) return FALSE;
761     if( !isset( $this->due['value']['year'] )  &&
762         !isset( $this->due['value']['month'] ) &&
763         !isset( $this->due['value']['day'] )   &&
764         !isset( $this->due['value']['hour'] )  &&
765         !isset( $this->due['value']['min'] )   &&
766         !isset( $this->due['value']['sec'] )) {
767       if( $this->getConfig( 'allowEmpty' ))
768         return $this->_createElement( 'DUE' );
769       else
770        return FALSE;
771     }
772     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->due['value'] );
773     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
774        ( !isset( $this->due['params']['VALUE'] ) || ( $this->due['params']['VALUE'] != 'DATE' ))  &&
775          !isset( $this->due['params']['TZID'] ))
776       $this->due['params']['TZID'] = $tzid;
777     $attributes = $this->_createParams( $this->due['params'] );
778     return $this->_createElement( 'DUE', $attributes, $formatted );
779   }
780 /**
781  * set calendar component property due
782  *
783  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
784  * @since 2.4.8 - 2008-11-04
785  * @param mixed $year
786  * @param mixed $month optional
787  * @param int $day optional
788  * @param int $hour optional
789  * @param int $min optional
790  * @param int $sec optional
791  * @param array $params optional
792  * @return bool
793  */
794   function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
795     if( empty( $year )) {
796       if( $this->getConfig( 'allowEmpty' )) {
797         $this->due = array( 'value' => null, 'params' => iCal_UtilityFunctions::_setParams( $params ));
798         return TRUE;
799       }
800       else
801         return FALSE;
802     }
803     $this->due = iCal_UtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
804     return TRUE;
805   }
806 /*********************************************************************************/
807 /**
808  * Property Name: DURATION
809  */
810 /**
811  * creates formatted output for calendar component property duration
812  *
813  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
814  * @since 2.4.8 - 2008-10-21
815  * @return string
816  */
817   function createDuration() {
818     if( empty( $this->duration )) return FALSE;
819     if( !isset( $this->duration['value']['week'] ) &&
820         !isset( $this->duration['value']['day'] )  &&
821         !isset( $this->duration['value']['hour'] ) &&
822         !isset( $this->duration['value']['min'] )  &&
823         !isset( $this->duration['value']['sec'] ))
824       if( $this->getConfig( 'allowEmpty' ))
825         return $this->_createElement( 'DURATION', array(), null );
826       else return FALSE;
827     $attributes = $this->_createParams( $this->duration['params'] );
828     return $this->_createElement( 'DURATION', $attributes, iCal_UtilityFunctions::_format_duration( $this->duration['value'] ));
829   }
830 /**
831  * set calendar component property duration
832  *
833  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
834  * @since 2.4.8 - 2008-11-04
835  * @param mixed $week
836  * @param mixed $day optional
837  * @param int $hour optional
838  * @param int $min optional
839  * @param int $sec optional
840  * @param array $params optional
841  * @return bool
842  */
843   function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
844     if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE;
845     if( is_array( $week ) && ( 1 <= count( $week )))
846       $this->duration = array( 'value' => iCal_UtilityFunctions::_duration_array( $week ), 'params' => iCal_UtilityFunctions::_setParams( $day ));
847     elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
848       $week = trim( $week );
849       if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
850         $week = substr( $week, 1 );
851       $this->duration = array( 'value' => iCal_UtilityFunctions::_duration_string( $week ), 'params' => iCal_UtilityFunctions::_setParams( $day ));
852     }
853     elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec ))
854       return FALSE;
855     else
856       $this->duration = array( 'value' => iCal_UtilityFunctions::_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => iCal_UtilityFunctions::_setParams( $params ));
857     return TRUE;
858   }
859 /*********************************************************************************/
860 /**
861  * Property Name: EXDATE
862  */
863 /**
864  * creates formatted output for calendar component property exdate
865  *
866  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
867  * @since 2.4.8 - 2008-10-22
868  * @return string
869  */
870   function createExdate() {
871     if( empty( $this->exdate )) return FALSE;
872     $output = null;
873     foreach( $this->exdate as $ex => $theExdate ) {
874       if( empty( $theExdate['value'] )) {
875         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'EXDATE' );
876         continue;
877       }
878       $content = $attributes = null;
879       foreach( $theExdate['value'] as $eix => $exdatePart ) {
880         $parno = count( $exdatePart );
881         $formatted = iCal_UtilityFunctions::_format_date_time( $exdatePart, $parno );
882         if( isset( $theExdate['params']['TZID'] ))
883           $formatted = str_replace( 'Z', '', $formatted);
884         if( 0 < $eix ) {
885           if( isset( $theExdate['value'][0]['tz'] )) {
886             if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
887                ( 'Z' == $theExdate['value'][0]['tz'] )) {
888               if( 'Z' != substr( $formatted, -1 ))
889                 $formatted .= 'Z';
890             }
891             else
892               $formatted = str_replace( 'Z', '', $formatted );
893           }
894           else
895             $formatted = str_replace( 'Z', '', $formatted );
896         }
897         $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
898       }
899       $attributes .= $this->_createParams( $theExdate['params'] );
900       $output .= $this->_createElement( 'EXDATE', $attributes, $content );
901     }
902     return $output;
903   }
904 /**
905  * set calendar component property exdate
906  *
907  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
908  * @since 2.5.1 - 2008-11-05
909  * @param array exdates
910  * @param array $params, optional
911  * @param integer $index, optional
912  * @return bool
913  */
914   function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
915     if( empty( $exdates )) {
916       if( $this->getConfig( 'allowEmpty' )) {
917         iCal_UtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index );
918         return TRUE;
919       }
920       else
921         return FALSE;
922     }
923     $input  = array( 'params' => iCal_UtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
924             /* ev. check 1:st date and save ev. timezone **/
925     iCal_UtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
926     iCal_UtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
927     foreach( $exdates as $eix => $theExdate ) {
928       if( iCal_UtilityFunctions::_isArrayTimestampDate( $theExdate ))
929         $exdatea = iCal_UtilityFunctions::_timestamp2date( $theExdate, $parno );
930       elseif(  is_array( $theExdate ))
931         $exdatea = iCal_UtilityFunctions::_date_time_array( $theExdate, $parno );
932       elseif( 8 <= strlen( trim( $theExdate ))) // ex. 2006-08-03 10:12:18
933         $exdatea = iCal_UtilityFunctions::_date_time_string( $theExdate, $parno );
934       if( 3 == $parno )
935         unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
936       elseif( isset( $exdatea['tz'] ))
937         $exdatea['tz'] = (string) $exdatea['tz'];
938       if(  isset( $input['params']['TZID'] ) ||
939          ( isset( $exdatea['tz'] ) && !iCal_UtilityFunctions::_isOffset( $exdatea['tz'] )) ||
940          ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
941          ( isset( $input['value'][0]['tz'] ) && !iCal_UtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
942         unset( $exdatea['tz'] );
943       $input['value'][] = $exdatea;
944     }
945     if( 0 >= count( $input['value'] ))
946       return FALSE;
947     if( 3 == $parno ) {
948       $input['params']['VALUE'] = 'DATE';
949       unset( $input['params']['TZID'] );
950     }
951     iCal_UtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
952     return TRUE;
953   }
954 /*********************************************************************************/
955 /**
956  * Property Name: EXRULE
957  */
958 /**
959  * creates formatted output for calendar component property exrule
960  *
961  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
962  * @since 2.4.8 - 2008-10-22
963  * @return string
964  */
965   function createExrule() {
966     if( empty( $this->exrule )) return FALSE;
967     return $this->_format_recur( 'EXRULE', $this->exrule );
968   }
969 /**
970  * set calendar component property exdate
971  *
972  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
973  * @since 2.5.1 - 2008-11-05
974  * @param array $exruleset
975  * @param array $params, optional
976  * @param integer $index, optional
977  * @return bool
978  */
979   function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
980     if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE;
981     iCal_UtilityFunctions::_setMval( $this->exrule, iCal_UtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
982     return TRUE;
983   }
984 /*********************************************************************************/
985 /**
986  * Property Name: FREEBUSY
987  */
988 /**
989  * creates formatted output for calendar component property freebusy
990  *
991  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
992  * @since 2.4.8 - 2008-10-22
993  * @return string
994  */
995   function createFreebusy() {
996     if( empty( $this->freebusy )) return FALSE;
997     $output = null;
998     foreach( $this->freebusy as $freebusyPart ) {
999       if( empty( $freebusyPart['value'] )) {
1000         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
1001         continue;
1002       }
1003       $attributes = $content = null;
1004       if( isset( $freebusyPart['value']['fbtype'] )) {
1005         $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
1006         unset( $freebusyPart['value']['fbtype'] );
1007         $freebusyPart['value'] = array_values( $freebusyPart['value'] );
1008       }
1009       else
1010         $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
1011       $attributes .= $this->_createParams( $freebusyPart['params'] );
1012       $fno = 1;
1013       $cnt = count( $freebusyPart['value']);
1014       foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
1015         $formatted   = iCal_UtilityFunctions::_format_date_time( $freebusyPeriod[0] );
1016         $content .= $formatted;
1017         $content .= '/';
1018         $cnt2 = count( $freebusyPeriod[1]);
1019         if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
1020           $cnt2 = 7;
1021         elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
1022           $cnt2 = 5;
1023         if(( 7 == $cnt2 )   &&    // period=  -> date-time
1024             isset( $freebusyPeriod[1]['year'] )  &&
1025             isset( $freebusyPeriod[1]['month'] ) &&
1026             isset( $freebusyPeriod[1]['day'] )) {
1027           $content .= iCal_UtilityFunctions::_format_date_time( $freebusyPeriod[1] );
1028         }
1029         else {                                  // period=  -> dur-time
1030           $content .= iCal_UtilityFunctions::_format_duration( $freebusyPeriod[1] );
1031         }
1032         if( $fno < $cnt )
1033           $content .= ',';
1034         $fno++;
1035       }
1036       $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
1037     }
1038     return $output;
1039   }
1040 /**
1041  * set calendar component property freebusy
1042  *
1043  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1044  * @since 2.8.10 - 2011-03-24
1045  * @param string $fbType
1046  * @param array $fbValues
1047  * @param array $params, optional
1048  * @param integer $index, optional
1049  * @return bool
1050  */
1051   function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
1052     if( empty( $fbValues )) {
1053       if( $this->getConfig( 'allowEmpty' )) {
1054         iCal_UtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index );
1055         return TRUE;
1056       }
1057       else
1058         return FALSE;
1059     }
1060     $fbType = strtoupper( $fbType );
1061     if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
1062        ( 'X-' != substr( $fbType, 0, 2 )))
1063       $fbType = 'BUSY';
1064     $input = array( 'fbtype' => $fbType );
1065     foreach( $fbValues as $fbPeriod ) {   // periods => period
1066       if( empty( $fbPeriod ))
1067         continue;
1068       $freebusyPeriod = array();
1069       foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
1070         $freebusyPairMember = array();
1071         if( is_array( $fbMember )) {
1072           if( iCal_UtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
1073             $freebusyPairMember       = iCal_UtilityFunctions::_date_time_array( $fbMember, 7 );
1074             $freebusyPairMember['tz'] = 'Z';
1075           }
1076           elseif( iCal_UtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
1077             $freebusyPairMember       = iCal_UtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
1078             $freebusyPairMember['tz'] = 'Z';
1079           }
1080           else {                                         // array format duration
1081             $freebusyPairMember = iCal_UtilityFunctions::_duration_array( $fbMember );
1082           }
1083         }
1084         elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
1085                ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
1086           if( 'P' != $fbMember{0} )
1087             $fbmember = substr( $fbMember, 1 );
1088           $freebusyPairMember = iCal_UtilityFunctions::_duration_string( $fbMember );
1089         }
1090         elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
1091           $freebusyPairMember       = iCal_UtilityFunctions::_date_time_string( $fbMember, 7 );
1092           $freebusyPairMember['tz'] = 'Z';
1093         }
1094         $freebusyPeriod[]   = $freebusyPairMember;
1095       }
1096       $input[]              = $freebusyPeriod;
1097     }
1098     iCal_UtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
1099     return TRUE;
1100   }
1101 /*********************************************************************************/
1102 /**
1103  * Property Name: GEO
1104  */
1105 /**
1106  * creates formatted output for calendar component property geo
1107  *
1108  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1109  * @since 2.4.8 - 2008-10-21
1110  * @return string
1111  */
1112   function createGeo() {
1113     if( empty( $this->geo )) return FALSE;
1114     if( empty( $this->geo['value'] ))
1115       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
1116     $attributes = $this->_createParams( $this->geo['params'] );
1117     $content    = null;
1118     $content   .= number_format( (float) $this->geo['value']['latitude'], 6, '.', '');
1119     $content   .= ';';
1120     $content   .= number_format( (float) $this->geo['value']['longitude'], 6, '.', '');
1121     return $this->_createElement( 'GEO', $attributes, $content );
1122   }
1123 /**
1124  * set calendar component property geo
1125  *
1126  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1127  * @since 2.4.8 - 2008-11-04
1128  * @param float $latitude
1129  * @param float $longitude
1130  * @param array $params optional
1131  * @return bool
1132  */
1133   function setGeo( $latitude, $longitude, $params=FALSE ) {
1134     if( !empty( $latitude ) && !empty( $longitude )) {
1135       if( !is_array( $this->geo )) $this->geo = array();
1136       $this->geo['value']['latitude']  = $latitude;
1137       $this->geo['value']['longitude'] = $longitude;
1138       $this->geo['params'] = iCal_UtilityFunctions::_setParams( $params );
1139     }
1140     elseif( $this->getConfig( 'allowEmpty' ))
1141       $this->geo = array( 'value' => null, 'params' => iCal_UtilityFunctions::_setParams( $params ) );
1142     else
1143       return FALSE;
1144     return TRUE;
1145   }
1146 /*********************************************************************************/
1147 /**
1148  * Property Name: LAST-MODIFIED
1149  */
1150 /**
1151  * creates formatted output for calendar component property last-modified
1152  *
1153  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1154  * @since 2.4.8 - 2008-10-21
1155  * @return string
1156  */
1157   function createLastModified() {
1158     if( empty( $this->lastmodified )) return FALSE;
1159     $attributes = $this->_createParams( $this->lastmodified['params'] );
1160     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->lastmodified['value'], 7 );
1161     return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
1162   }
1163 /**
1164  * set calendar component property completed
1165  *
1166  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1167  * @since 2.4.8 - 2008-10-23
1168  * @param mixed $year optional
1169  * @param mixed $month optional
1170  * @param int $day optional
1171  * @param int $hour optional
1172  * @param int $min optional
1173  * @param int $sec optional
1174  * @param array $params optional
1175  * @return boll
1176  */
1177   function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
1178     if( empty( $year ))
1179       $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
1180     $this->lastmodified = iCal_UtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
1181     return TRUE;
1182   }
1183 /*********************************************************************************/
1184 /**
1185  * Property Name: LOCATION
1186  */
1187 /**
1188  * creates formatted output for calendar component property location
1189  *
1190  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1191  * @since 2.4.8 - 2008-10-22
1192  * @return string
1193  */
1194   function createLocation() {
1195     if( empty( $this->location )) return FALSE;
1196     if( empty( $this->location['value'] ))
1197       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
1198     $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
1199     $content    = $this->_strrep( $this->location['value'] );
1200     return $this->_createElement( 'LOCATION', $attributes, $content );
1201   }
1202 /**
1203  * set calendar component property location
1204  '
1205  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1206  * @since 2.4.8 - 2008-11-04
1207  * @param string $value
1208  * @param array params optional
1209  * @return bool
1210  */
1211   function setLocation( $value, $params=FALSE ) {
1212     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1213     $this->location = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1214     return TRUE;
1215   }
1216 /*********************************************************************************/
1217 /**
1218  * Property Name: ORGANIZER
1219  */
1220 /**
1221  * creates formatted output for calendar component property organizer
1222  *
1223  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1224  * @since 2.6.33 - 2010-12-17
1225  * @return string
1226  */
1227   function createOrganizer() {
1228     if( empty( $this->organizer )) return FALSE;
1229     if( empty( $this->organizer['value'] ))
1230       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
1231     $attributes = $this->_createParams( $this->organizer['params']
1232                                       , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
1233     return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
1234   }
1235 /**
1236  * set calendar component property organizer
1237  *
1238  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1239  * @since 2.6.27 - 2010-11-29
1240  * @param string $value
1241  * @param array params optional
1242  * @return bool
1243  */
1244   function setOrganizer( $value, $params=FALSE ) {
1245     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1246     if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
1247       $value = 'MAILTO:'.$value;
1248     else
1249       $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
1250     $value = str_replace( 'mailto:', 'MAILTO:', $value );
1251     $this->organizer = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1252     if( isset( $this->organizer['params']['SENT-BY'] )){
1253       if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
1254         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
1255       else
1256         $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
1257     }
1258     return TRUE;
1259   }
1260 /*********************************************************************************/
1261 /**
1262  * Property Name: PERCENT-COMPLETE
1263  */
1264 /**
1265  * creates formatted output for calendar component property percent-complete
1266  *
1267  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1268  * @since 2.9.3 - 2011-05-14
1269  * @return string
1270  */
1271   function createPercentComplete() {
1272     if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
1273     if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
1274       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
1275     $attributes = $this->_createParams( $this->percentcomplete['params'] );
1276     return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
1277   }
1278 /**
1279  * set calendar component property percent-complete
1280  *
1281  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1282  * @since 2.9.3 - 2011-05-14
1283  * @param int $value
1284  * @param array $params optional
1285  * @return bool
1286  */
1287   function setPercentComplete( $value, $params=FALSE ) {
1288     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1289     $this->percentcomplete = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1290     return TRUE;
1291   }
1292 /*********************************************************************************/
1293 /**
1294  * Property Name: PRIORITY
1295  */
1296 /**
1297  * creates formatted output for calendar component property priority
1298  *
1299  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1300  * @since 2.9.3 - 2011-05-14
1301  * @return string
1302  */
1303   function createPriority() {
1304     if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
1305     if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
1306       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
1307     $attributes = $this->_createParams( $this->priority['params'] );
1308     return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
1309   }
1310 /**
1311  * set calendar component property priority
1312  *
1313  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1314  * @since 2.9.3 - 2011-05-14
1315  * @param int $value
1316  * @param array $params optional
1317  * @return bool
1318  */
1319   function setPriority( $value, $params=FALSE  ) {
1320     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1321     $this->priority = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1322     return TRUE;
1323   }
1324 /*********************************************************************************/
1325 /**
1326  * Property Name: RDATE
1327  */
1328 /**
1329  * creates formatted output for calendar component property rdate
1330  *
1331  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1332  * @since 2.4.16 - 2008-10-26
1333  * @return string
1334  */
1335   function createRdate() {
1336     if( empty( $this->rdate )) return FALSE;
1337     $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
1338     $output = null;
1339     if( $utctime  )
1340       unset( $this->rdate['params']['TZID'] );
1341     foreach( $this->rdate as $theRdate ) {
1342       if( empty( $theRdate['value'] )) {
1343         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
1344         continue;
1345       }
1346       if( $utctime  )
1347         unset( $theRdate['params']['TZID'] );
1348       $attributes = $this->_createParams( $theRdate['params'] );
1349       $cnt = count( $theRdate['value'] );
1350       $content = null;
1351       $rno = 1;
1352       foreach( $theRdate['value'] as $rpix => $rdatePart ) {
1353         $contentPart = null;
1354         if( is_array( $rdatePart ) &&
1355             isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
1356           if( $utctime )
1357             unset( $rdatePart[0]['tz'] );
1358           $formatted = iCal_UtilityFunctions::_format_date_time( $rdatePart[0]); // PERIOD part 1
1359           if( $utctime || !empty( $theRdate['params']['TZID'] ))
1360             $formatted = str_replace( 'Z', '', $formatted);
1361           if( 0 < $rpix ) {
1362             if( !empty( $rdatePart[0]['tz'] ) && iCal_UtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) {
1363               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
1364             }
1365             else
1366               $formatted = str_replace( 'Z', '', $formatted );
1367           }
1368           $contentPart .= $formatted;
1369           $contentPart .= '/';
1370           $cnt2 = count( $rdatePart[1]);
1371           if( array_key_exists( 'year', $rdatePart[1] )) {
1372             if( array_key_exists( 'hour', $rdatePart[1] ))
1373               $cnt2 = 7;                                      // date-time
1374             else
1375               $cnt2 = 3;                                      // date
1376           }
1377           elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
1378             $cnt2 = 5;
1379           if(( 7 == $cnt2 )   &&    // period=  -> date-time
1380               isset( $rdatePart[1]['year'] )  &&
1381               isset( $rdatePart[1]['month'] ) &&
1382               isset( $rdatePart[1]['day'] )) {
1383             if( $utctime )
1384               unset( $rdatePart[1]['tz'] );
1385             $formatted = iCal_UtilityFunctions::_format_date_time( $rdatePart[1] ); // PERIOD part 2
1386             if( $utctime || !empty( $theRdate['params']['TZID'] ))
1387               $formatted = str_replace( 'Z', '', $formatted);
1388             if( !empty( $rdatePart[0]['tz'] ) && iCal_UtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) {
1389               if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z';
1390             }
1391             else
1392               $formatted = str_replace( 'Z', '', $formatted );
1393            $contentPart .= $formatted;
1394           }
1395           else {                                  // period=  -> dur-time
1396             $contentPart .= iCal_UtilityFunctions::_format_duration( $rdatePart[1] );
1397           }
1398         } // PERIOD end
1399         else { // SINGLE date start
1400           if( $utctime )
1401             unset( $rdatePart['tz'] );
1402           $formatted = iCal_UtilityFunctions::_format_date_time( $rdatePart);
1403           if( $utctime || !empty( $theRdate['params']['TZID'] ))
1404             $formatted = str_replace( 'Z', '', $formatted);
1405           if( !$utctime && ( 0 < $rpix )) {
1406             if( !empty( $theRdate['value'][0]['tz'] ) && iCal_UtilityFunctions::_isOffset( $theRdate['value'][0]['tz'] )) {
1407               if( 'Z' != substr( $formatted, -1 ))
1408                 $formatted .= 'Z';
1409             }
1410             else
1411               $formatted = str_replace( 'Z', '', $formatted );
1412           }
1413           $contentPart .= $formatted;
1414         }
1415         $content .= $contentPart;
1416         if( $rno < $cnt )
1417           $content .= ',';
1418         $rno++;
1419       }
1420       $output    .= $this->_createElement( 'RDATE', $attributes, $content );
1421     }
1422     return $output;
1423   }
1424 /**
1425  * set calendar component property rdate
1426  *
1427  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1428  * @since 2.5.1 - 2008-11-07
1429  * @param array $rdates
1430  * @param array $params, optional
1431  * @param integer $index, optional
1432  * @return bool
1433  */
1434   function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
1435     if( empty( $rdates )) {
1436       if( $this->getConfig( 'allowEmpty' )) {
1437         iCal_UtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index );
1438         return TRUE;
1439       }
1440       else
1441         return FALSE;
1442     }
1443     $input = array( 'params' => iCal_UtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
1444     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
1445       unset( $input['params']['TZID'] );
1446       $input['params']['VALUE'] = 'DATE-TIME';
1447     }
1448             /*  check if PERIOD, if not set */
1449     if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
1450           isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
1451           isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
1452     (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
1453                                       iCal_UtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
1454                                     ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
1455      ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
1456       $input['params']['VALUE'] = 'PERIOD';
1457             /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
1458     $date  = reset( $rdates );
1459     if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
1460       $date  = reset( $date );
1461     iCal_UtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
1462     if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
1463       unset( $input['params']['TZID'] );
1464     iCal_UtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
1465     foreach( $rdates as $rpix => $theRdate ) {
1466       $inputa = null;
1467       if( is_array( $theRdate )) {
1468         if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
1469           foreach( $theRdate as $rix => $rPeriod ) {
1470             if( is_array( $rPeriod )) {
1471               if( iCal_UtilityFunctions::_isArrayTimestampDate( $rPeriod ))      // timestamp
1472                 $inputab  = ( isset( $rPeriod['tz'] )) ? iCal_UtilityFunctions::_timestamp2date( $rPeriod, $parno ) : iCal_UtilityFunctions::_timestamp2date( $rPeriod, 6 );
1473               elseif( iCal_UtilityFunctions::_isArrayDate( $rPeriod ))
1474                 $inputab  = ( 3 < count ( $rPeriod )) ? iCal_UtilityFunctions::_date_time_array( $rPeriod, $parno ) : iCal_UtilityFunctions::_date_time_array( $rPeriod, 6 );
1475               elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod ))))  // text-date
1476                 $inputab  = iCal_UtilityFunctions::_date_time_string( reset( $rPeriod ), $parno );
1477               else                                               // array format duration
1478                 $inputab  = iCal_UtilityFunctions::_duration_array( $rPeriod );
1479             }
1480             elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
1481                    ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
1482               if( 'P' != $rPeriod[0] )
1483                 $rPeriod  = substr( $rPeriod, 1 );
1484               $inputab    = iCal_UtilityFunctions::_duration_string( $rPeriod );
1485             }
1486             elseif( 8 <= strlen( trim( $rPeriod )))              // text date ex. 2006-08-03 10:12:18
1487               $inputab    = iCal_UtilityFunctions::_date_time_string( $rPeriod, $parno );
1488             if(  isset( $input['params']['TZID'] ) ||
1489                ( isset( $inputab['tz'] )   && !iCal_UtilityFunctions::_isOffset( $inputab['tz'] )) ||
1490                ( isset( $inputa[0] )       && ( !isset( $inputa[0]['tz'] )))       ||
1491                ( isset( $inputa[0]['tz'] ) && !iCal_UtilityFunctions::_isOffset( $inputa[0]['tz'] )))
1492               unset( $inputab['tz'] );
1493             $inputa[]     = $inputab;
1494           }
1495         } // PERIOD end
1496         elseif ( iCal_UtilityFunctions::_isArrayTimestampDate( $theRdate ))      // timestamp
1497           $inputa = iCal_UtilityFunctions::_timestamp2date( $theRdate, $parno );
1498         else                                                                    // date[-time]
1499           $inputa = iCal_UtilityFunctions::_date_time_array( $theRdate, $parno );
1500       }
1501       elseif( 8 <= strlen( trim( $theRdate )))                   // text date ex. 2006-08-03 10:12:18
1502         $inputa       = iCal_UtilityFunctions::_date_time_string( $theRdate, $parno );
1503       if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
1504         if( 3 == $parno )
1505           unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
1506         elseif( isset( $inputa['tz'] ))
1507           $inputa['tz'] = (string) $inputa['tz'];
1508         if(  isset( $input['params']['TZID'] ) ||
1509            ( isset( $inputa['tz'] )            && !iCal_UtilityFunctions::_isOffset( $inputa['tz'] ))     ||
1510            ( isset( $input['value'][0] )       && ( !isset( $input['value'][0]['tz'] )))  ||
1511            ( isset( $input['value'][0]['tz'] ) && !iCal_UtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
1512           unset( $inputa['tz'] );
1513       }
1514       $input['value'][] = $inputa;
1515     }
1516     if( 3 == $parno ) {
1517       $input['params']['VALUE'] = 'DATE';
1518       unset( $input['params']['TZID'] );
1519     }
1520     iCal_UtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
1521     return TRUE;
1522   }
1523 /*********************************************************************************/
1524 /**
1525  * Property Name: RECURRENCE-ID
1526  */
1527 /**
1528  * creates formatted output for calendar component property recurrence-id
1529  *
1530  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1531  * @since 2.9.6 - 2011-05-15
1532  * @return string
1533  */
1534   function createRecurrenceid() {
1535     if( empty( $this->recurrenceid )) return FALSE;
1536     if( empty( $this->recurrenceid['value'] ))
1537       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
1538     $formatted  = iCal_UtilityFunctions::_format_date_time( $this->recurrenceid['value'] );
1539     if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) &&
1540        ( !isset( $this->recurrenceid['params']['VALUE'] ) || ( $this->recurrenceid['params']['VALUE'] != 'DATE' ))  &&
1541          !isset( $this->recurrenceid['params']['TZID'] ))
1542       $this->recurrenceid['params']['TZID'] = $tzid;
1543     $attributes = $this->_createParams( $this->recurrenceid['params'] );
1544     return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
1545   }
1546 /**
1547  * set calendar component property recurrence-id
1548  *
1549  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1550  * @since 2.9.6 - 2011-05-15
1551  * @param mixed $year
1552  * @param mixed $month optional
1553  * @param int $day optional
1554  * @param int $hour optional
1555  * @param int $min optional
1556  * @param int $sec optional
1557  * @param array $params optional
1558  * @return bool
1559  */
1560   function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
1561     if( empty( $year )) {
1562       if( $this->getConfig( 'allowEmpty' )) {
1563         $this->recurrenceid = array( 'value' => null, 'params' => null );
1564         return TRUE;
1565       }
1566       else
1567         return FALSE;
1568     }
1569     $this->recurrenceid = iCal_UtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
1570     return TRUE;
1571   }
1572 /*********************************************************************************/
1573 /**
1574  * Property Name: RELATED-TO
1575  */
1576 /**
1577  * creates formatted output for calendar component property related-to
1578  *
1579  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1580  * @since 2.4.8 - 2008-10-23
1581  * @return string
1582  */
1583   function createRelatedTo() {
1584     if( empty( $this->relatedto )) return FALSE;
1585     $output = null;
1586     foreach( $this->relatedto as $relation ) {
1587       if( empty( $relation['value'] )) {
1588         if( $this->getConfig( 'allowEmpty' )) $output.= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ));
1589         continue;
1590       }
1591       $attributes = $this->_createParams( $relation['params'] );
1592       $content    = ( 'xcal' != $this->format ) ? '<' : '';
1593       $content   .= $this->_strrep( $relation['value'] );
1594       $content   .= ( 'xcal' != $this->format ) ? '>' : '';
1595       $output    .= $this->_createElement( 'RELATED-TO', $attributes, $content );
1596     }
1597     return $output;
1598   }
1599 /**
1600  * set calendar component property related-to
1601  *
1602  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1603  * @since 2.5.1 - 2008-11-07
1604  * @param float $relid
1605  * @param array $params, optional
1606  * @param index $index, optional
1607  * @return bool
1608  */
1609   function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
1610     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1611     if(( '<' == substr( $value, 0, 1 )) && ( '>' == substr( $value, -1 )))
1612       $value = substr( $value, 1, ( strlen( $value ) - 2 ));
1613     iCal_UtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
1614     iCal_UtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
1615     return TRUE;
1616   }
1617 /*********************************************************************************/
1618 /**
1619  * Property Name: REPEAT
1620  */
1621 /**
1622  * creates formatted output for calendar component property repeat
1623  *
1624  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1625  * @since 2.9.3 - 2011-05-14
1626  * @return string
1627  */
1628   function createRepeat() {
1629     if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
1630     if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
1631       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
1632     $attributes = $this->_createParams( $this->repeat['params'] );
1633     return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
1634   }
1635 /**
1636  * set calendar component property transp
1637  *
1638  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1639  * @since 2.9.3 - 2011-05-14
1640  * @param string $value
1641  * @param array $params optional
1642  * @return void
1643  */
1644   function setRepeat( $value, $params=FALSE ) {
1645     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1646     $this->repeat = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1647     return TRUE;
1648   }
1649 /*********************************************************************************/
1650 /**
1651  * Property Name: REQUEST-STATUS
1652  */
1653 /**
1654  * creates formatted output for calendar component property request-status
1655  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1656  * @since 2.4.8 - 2008-10-23
1657  * @return string
1658  */
1659   function createRequestStatus() {
1660     if( empty( $this->requeststatus )) return FALSE;
1661     $output = null;
1662     foreach( $this->requeststatus as $rstat ) {
1663       if( empty( $rstat['value']['statcode'] )) {
1664         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
1665         continue;
1666       }
1667       $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
1668       $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
1669       $content    .= ';'.$this->_strrep( $rstat['value']['text'] );
1670       if( isset( $rstat['value']['extdata'] ))
1671         $content  .= ';'.$this->_strrep( $rstat['value']['extdata'] );
1672       $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
1673     }
1674     return $output;
1675   }
1676 /**
1677  * set calendar component property request-status
1678  *
1679  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1680  * @since 2.5.1 - 2008-11-05
1681  * @param float $statcode
1682  * @param string $text
1683  * @param string $extdata, optional
1684  * @param array $params, optional
1685  * @param integer $index, optional
1686  * @return bool
1687  */
1688   function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
1689     if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = null; else return FALSE;
1690     $input              = array( 'statcode' => $statcode, 'text' => $text );
1691     if( $extdata )
1692       $input['extdata'] = $extdata;
1693     iCal_UtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
1694     return TRUE;
1695   }
1696 /*********************************************************************************/
1697 /**
1698  * Property Name: RESOURCES
1699  */
1700 /**
1701  * creates formatted output for calendar component property resources
1702  *
1703  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1704  * @since 2.4.8 - 2008-10-23
1705  * @return string
1706  */
1707   function createResources() {
1708     if( empty( $this->resources )) return FALSE;
1709     $output = null;
1710     foreach( $this->resources as $resource ) {
1711       if( empty( $resource['value'] )) {
1712         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
1713         continue;
1714       }
1715       $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
1716       if( is_array( $resource['value'] )) {
1717         foreach( $resource['value'] as $rix => $resourcePart )
1718           $resource['value'][$rix] = $this->_strrep( $resourcePart );
1719         $content   = implode( ',', $resource['value'] );
1720       }
1721       else
1722         $content   = $this->_strrep( $resource['value'] );
1723       $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
1724     }
1725     return $output;
1726   }
1727 /**
1728  * set calendar component property recources
1729  *
1730  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1731  * @since 2.5.1 - 2008-11-05
1732  * @param mixed $value
1733  * @param array $params, optional
1734  * @param integer $index, optional
1735  * @return bool
1736  */
1737   function setResources( $value, $params=FALSE, $index=FALSE ) {
1738     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1739     iCal_UtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
1740     return TRUE;
1741   }
1742 /*********************************************************************************/
1743 /**
1744  * Property Name: RRULE
1745  */
1746 /**
1747  * creates formatted output for calendar component property rrule
1748  *
1749  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1750  * @since 2.4.8 - 2008-10-21
1751  * @return string
1752  */
1753   function createRrule() {
1754     if( empty( $this->rrule )) return FALSE;
1755     return $this->_format_recur( 'RRULE', $this->rrule );
1756   }
1757 /**
1758  * set calendar component property rrule
1759  *
1760  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1761  * @since 2.5.1 - 2008-11-05
1762  * @param array $rruleset
1763  * @param array $params, optional
1764  * @param integer $index, optional
1765  * @return void
1766  */
1767   function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
1768     if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE;
1769     iCal_UtilityFunctions::_setMval( $this->rrule, iCal_UtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
1770     return TRUE;
1771   }
1772 /*********************************************************************************/
1773 /**
1774  * Property Name: SEQUENCE
1775  */
1776 /**
1777  * creates formatted output for calendar component property sequence
1778  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1779  * @since 2.9.3 - 2011-05-14
1780  * @return string
1781  */
1782   function createSequence() {
1783     if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
1784     if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
1785        ( '0' != $this->sequence['value'] ))
1786       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
1787     $attributes = $this->_createParams( $this->sequence['params'] );
1788     return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
1789   }
1790 /**
1791  * set calendar component property sequence
1792  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1793  * @since 2.10.8 - 2011-09-19
1794  * @param int $value optional
1795  * @param array $params optional
1796  * @return bool
1797  */
1798   function setSequence( $value=FALSE, $params=FALSE ) {
1799     if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
1800       $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
1801     $this->sequence = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1802     return TRUE;
1803   }
1804 /*********************************************************************************/
1805 /**
1806  * Property Name: STATUS
1807  */
1808 /**
1809  * creates formatted output for calendar component property status
1810  *
1811  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1812  * @since 2.4.8 - 2008-10-21
1813  * @return string
1814  */
1815   function createStatus() {
1816     if( empty( $this->status )) return FALSE;
1817     if( empty( $this->status['value'] ))
1818       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
1819     $attributes = $this->_createParams( $this->status['params'] );
1820     return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
1821   }
1822 /**
1823  * set calendar component property status
1824  *
1825  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1826  * @since 2.4.8 - 2008-11-04
1827  * @param string $value
1828  * @param array $params optional
1829  * @return bool
1830  */
1831   function setStatus( $value, $params=FALSE ) {
1832     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1833     $this->status = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1834     return TRUE;
1835   }
1836 /*********************************************************************************/
1837 /**
1838  * Property Name: SUMMARY
1839  */
1840 /**
1841  * creates formatted output for calendar component property summary
1842  *
1843  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1844  * @since 2.4.8 - 2008-10-21
1845  * @return string
1846  */
1847   function createSummary() {
1848     if( empty( $this->summary )) return FALSE;
1849     if( empty( $this->summary['value'] ))
1850       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
1851     $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
1852     $content    = $this->_strrep( $this->summary['value'] );
1853     return $this->_createElement( 'SUMMARY', $attributes, $content );
1854   }
1855 /**
1856  * set calendar component property summary
1857  *
1858  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1859  * @since 2.4.8 - 2008-11-04
1860  * @param string $value
1861  * @param string $params optional
1862  * @return bool
1863  */
1864   function setSummary( $value, $params=FALSE ) {
1865     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1866     $this->summary = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1867     return TRUE;
1868   }
1869 /*********************************************************************************/
1870 /**
1871  * Property Name: TRANSP
1872  */
1873 /**
1874  * creates formatted output for calendar component property transp
1875  *
1876  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1877  * @since 2.4.8 - 2008-10-21
1878  * @return string
1879  */
1880   function createTransp() {
1881     if( empty( $this->transp )) return FALSE;
1882     if( empty( $this->transp['value'] ))
1883       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
1884     $attributes = $this->_createParams( $this->transp['params'] );
1885     return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
1886   }
1887 /**
1888  * set calendar component property transp
1889  *
1890  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1891  * @since 2.4.8 - 2008-11-04
1892  * @param string $value
1893  * @param string $params optional
1894  * @return bool
1895  */
1896   function setTransp( $value, $params=FALSE ) {
1897     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
1898     $this->transp = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
1899     return TRUE;
1900   }
1901 /*********************************************************************************/
1902 /**
1903  * Property Name: TRIGGER
1904  */
1905 /**
1906  * creates formatted output for calendar component property trigger
1907  *
1908  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1909  * @since 2.4.16 - 2008-10-21
1910  * @return string
1911  */
1912   function createTrigger() {
1913     if( empty( $this->trigger )) return FALSE;
1914     if( empty( $this->trigger['value'] ))
1915       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
1916     $content = $attributes = null;
1917     if( isset( $this->trigger['value']['year'] )   &&
1918         isset( $this->trigger['value']['month'] )  &&
1919         isset( $this->trigger['value']['day'] ))
1920       $content      .= iCal_UtilityFunctions::_format_date_time( $this->trigger['value'] );
1921     else {
1922       if( TRUE !== $this->trigger['value']['relatedStart'] )
1923         $attributes .= $this->intAttrDelimiter.'RELATED=END';
1924       if( $this->trigger['value']['before'] )
1925         $content    .= '-';
1926       $content      .= iCal_UtilityFunctions::_format_duration( $this->trigger['value'] );
1927     }
1928     $attributes     .= $this->_createParams( $this->trigger['params'] );
1929     return $this->_createElement( 'TRIGGER', $attributes, $content );
1930   }
1931 /**
1932  * set calendar component property trigger
1933  *
1934  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1935  * @since 2.9.9 - 2011-06-17
1936  * @param mixed $year
1937  * @param mixed $month optional
1938  * @param int $day optional
1939  * @param int $week optional
1940  * @param int $hour optional
1941  * @param int $min optional
1942  * @param int $sec optional
1943  * @param bool $relatedStart optional
1944  * @param bool $before optional
1945  * @param array $params optional
1946  * @return bool
1947  */
1948   function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
1949     if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
1950       if( $this->getConfig( 'allowEmpty' )) {
1951         $this->trigger = array( 'value' => null, 'params' => iCal_UtilityFunctions::_setParams( $params ) );
1952         return TRUE;
1953       }
1954       else
1955         return FALSE;
1956     if( iCal_UtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp
1957       $params = iCal_UtilityFunctions::_setParams( $month );
1958       $date   = iCal_UtilityFunctions::_timestamp2date( $year, 7 );
1959       foreach( $date as $k => $v )
1960         $$k = $v;
1961     }
1962     elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
1963       $params = iCal_UtilityFunctions::_setParams( $month );
1964       if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
1965            array_key_exists( 'month', $year ) &&
1966            array_key_exists( 'day',   $year ))) {  // when this must be a duration
1967         if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
1968           $relatedStart = FALSE;
1969         else
1970           $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
1971         $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
1972       }
1973       $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
1974       $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
1975       $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
1976       $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
1977       $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
1978       $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
1979       $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
1980       $year  = $SSYY;
1981     }
1982     elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
1983       $params = iCal_UtilityFunctions::_setParams( $month );
1984       if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration
1985         $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
1986         $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
1987         if(     'P'  != $year[0] )
1988           $year       = substr( $year, 1 );
1989         $date         = iCal_UtilityFunctions::_duration_string( $year);
1990       }
1991       else   // date
1992         $date    = iCal_UtilityFunctions::_date_time_string( $year, 7 );
1993       unset( $year, $month, $day );
1994       if( empty( $date ))
1995         $sec = 0;
1996       else
1997         foreach( $date as $k => $v )
1998           $$k = $v;
1999     }
2000     else // single values in function input parameters
2001       $params = iCal_UtilityFunctions::_setParams( $params );
2002     if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
2003       $params['VALUE'] = 'DATE-TIME';
2004       $hour = ( $hour ) ? $hour : 0;
2005       $min  = ( $min  ) ? $min  : 0;
2006       $sec  = ( $sec  ) ? $sec  : 0;
2007       $this->trigger = array( 'params' => $params );
2008       $this->trigger['value'] = array( 'year'  => $year
2009                                      , 'month' => $month
2010                                      , 'day'   => $day
2011                                      , 'hour'  => $hour
2012                                      , 'min'   => $min
2013                                      , 'sec'   => $sec
2014                                      , 'tz'    => 'Z' );
2015       return TRUE;
2016     }
2017     elseif(( empty( $year ) && empty( $month )) &&    // duration
2018            (( !empty( $week ) || ( 0 == $week )) ||
2019             ( !empty( $day )  || ( 0 == $day  )) ||
2020             ( !empty( $hour ) || ( 0 == $hour )) ||
2021             ( !empty( $min )  || ( 0 == $min  )) ||
2022             ( !empty( $sec )  || ( 0 == $sec  )))) {
2023       unset( $params['RELATED'] ); // set at output creation (END only)
2024       unset( $params['VALUE'] );   // 'DURATION' default
2025       $this->trigger = array( 'params' => $params );
2026       $this->trigger['value']  = array();
2027       if( !empty( $week )) $this->trigger['value']['week'] = $week;
2028       if( !empty( $day  )) $this->trigger['value']['day']  = $day;
2029       if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
2030       if( !empty( $min  )) $this->trigger['value']['min']  = $min;
2031       if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
2032       if( empty( $this->trigger['value'] )) {
2033         $this->trigger['value']['sec'] = 0;
2034         $before                        = FALSE;
2035       }
2036       $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
2037       $before       = ( FALSE !== $before )       ? TRUE : FALSE;
2038       $this->trigger['value']['relatedStart'] = $relatedStart;
2039       $this->trigger['value']['before']       = $before;
2040       return TRUE;
2041     }
2042     return FALSE;
2043   }
2044 /*********************************************************************************/
2045 /**
2046  * Property Name: TZID
2047  */
2048 /**
2049  * creates formatted output for calendar component property tzid
2050  *
2051  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2052  * @since 2.4.8 - 2008-10-21
2053  * @return string
2054  */
2055   function createTzid() {
2056     if( empty( $this->tzid )) return FALSE;
2057     if( empty( $this->tzid['value'] ))
2058       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
2059     $attributes = $this->_createParams( $this->tzid['params'] );
2060     return $this->_createElement( 'TZID', $attributes, $this->_strrep( $this->tzid['value'] ));
2061   }
2062 /**
2063  * set calendar component property tzid
2064  *
2065  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2066  * @since 2.4.8 - 2008-11-04
2067  * @param string $value
2068  * @param array $params optional
2069  * @return bool
2070  */
2071   function setTzid( $value, $params=FALSE ) {
2072     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2073     $this->tzid = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
2074     return TRUE;
2075   }
2076 /*********************************************************************************/
2077 /**
2078  * .. .
2079  * Property Name: TZNAME
2080  */
2081 /**
2082  * creates formatted output for calendar component property tzname
2083  *
2084  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2085  * @since 2.4.8 - 2008-10-21
2086  * @return string
2087  */
2088   function createTzname() {
2089     if( empty( $this->tzname )) return FALSE;
2090     $output = null;
2091     foreach( $this->tzname as $theName ) {
2092       if( !empty( $theName['value'] )) {
2093         $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
2094         $output    .= $this->_createElement( 'TZNAME', $attributes, $this->_strrep( $theName['value'] ));
2095       }
2096       elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
2097     }
2098     return $output;
2099   }
2100 /**
2101  * set calendar component property tzname
2102  *
2103  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2104  * @since 2.5.1 - 2008-11-05
2105  * @param string $value
2106  * @param string $params, optional
2107  * @param integer $index, optional
2108  * @return bool
2109  */
2110   function setTzname( $value, $params=FALSE, $index=FALSE ) {
2111     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2112     iCal_UtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
2113     return TRUE;
2114   }
2115 /*********************************************************************************/
2116 /**
2117  * Property Name: TZOFFSETFROM
2118  */
2119 /**
2120  * creates formatted output for calendar component property tzoffsetfrom
2121  *
2122  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2123  * @since 2.4.8 - 2008-10-21
2124  * @return string
2125  */
2126   function createTzoffsetfrom() {
2127     if( empty( $this->tzoffsetfrom )) return FALSE;
2128     if( empty( $this->tzoffsetfrom['value'] ))
2129       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
2130     $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
2131     return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
2132   }
2133 /**
2134  * set calendar component property tzoffsetfrom
2135  *
2136  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2137  * @since 2.4.8 - 2008-11-04
2138  * @param string $value
2139  * @param string $params optional
2140  * @return bool
2141  */
2142   function setTzoffsetfrom( $value, $params=FALSE ) {
2143     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2144     $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
2145     return TRUE;
2146   }
2147 /*********************************************************************************/
2148 /**
2149  * Property Name: TZOFFSETTO
2150  */
2151 /**
2152  * creates formatted output for calendar component property tzoffsetto
2153  *
2154  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2155  * @since 2.4.8 - 2008-10-21
2156  * @return string
2157  */
2158   function createTzoffsetto() {
2159     if( empty( $this->tzoffsetto )) return FALSE;
2160     if( empty( $this->tzoffsetto['value'] ))
2161       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
2162     $attributes = $this->_createParams( $this->tzoffsetto['params'] );
2163     return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
2164   }
2165 /**
2166  * set calendar component property tzoffsetto
2167  *
2168  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2169  * @since 2.4.8 - 2008-11-04
2170  * @param string $value
2171  * @param string $params optional
2172  * @return bool
2173  */
2174   function setTzoffsetto( $value, $params=FALSE ) {
2175     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2176     $this->tzoffsetto = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
2177     return TRUE;
2178   }
2179 /*********************************************************************************/
2180 /**
2181  * Property Name: TZURL
2182  */
2183 /**
2184  * creates formatted output for calendar component property tzurl
2185  *
2186  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2187  * @since 2.4.8 - 2008-10-21
2188  * @return string
2189  */
2190   function createTzurl() {
2191     if( empty( $this->tzurl )) return FALSE;
2192     if( empty( $this->tzurl['value'] ))
2193       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
2194     $attributes = $this->_createParams( $this->tzurl['params'] );
2195     return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
2196   }
2197 /**
2198  * set calendar component property tzurl
2199  *
2200  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2201  * @since 2.4.8 - 2008-11-04
2202  * @param string $value
2203  * @param string $params optional
2204  * @return boll
2205  */
2206   function setTzurl( $value, $params=FALSE ) {
2207     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2208     $this->tzurl = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
2209     return TRUE;
2210   }
2211 /*********************************************************************************/
2212 /**
2213  * Property Name: UID
2214  */
2215 /**
2216  * creates formatted output for calendar component property uid
2217  *
2218  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2219  * @since 0.9.7 - 2006-11-20
2220  * @return string
2221  */
2222   function createUid() {
2223     if( 0 >= count( $this->uid ))
2224       $this->_makeuid();
2225     $attributes = $this->_createParams( $this->uid['params'] );
2226     return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
2227   }
2228 /**
2229  * create an unique id for this calendar component object instance
2230  *
2231  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2232  * @since 2.2.7 - 2007-09-04
2233  * @return void
2234  */
2235   function _makeUid() {
2236     $date   = date('Ymd\THisT');
2237     $unique = substr(microtime(), 2, 4);
2238     $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
2239     $start  = 0;
2240     $end    = strlen( $base ) - 1;
2241     $length = 6;
2242     $str    = null;
2243     for( $p = 0; $p < $length; $p++ )
2244       $unique .= $base{mt_rand( $start, $end )};
2245     $this->uid = array( 'params' => null );
2246     $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
2247   }
2248 /**
2249  * set calendar component property uid
2250  *
2251  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2252  * @since 2.4.8 - 2008-11-04
2253  * @param string $value
2254  * @param string $params optional
2255  * @return bool
2256  */
2257   function setUid( $value, $params=FALSE ) {
2258     if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
2259     $this->uid = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
2260     return TRUE;
2261   }
2262 /*********************************************************************************/
2263 /**
2264  * Property Name: URL
2265  */
2266 /**
2267  * creates formatted output for calendar component property url
2268  *
2269  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2270  * @since 2.4.8 - 2008-10-21
2271  * @return string
2272  */
2273   function createUrl() {
2274     if( empty( $this->url )) return FALSE;
2275     if( empty( $this->url['value'] ))
2276       return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
2277     $attributes = $this->_createParams( $this->url['params'] );
2278     return $this->_createElement( 'URL', $attributes, $this->url['value'] );
2279   }
2280 /**
2281  * set calendar component property url
2282  *
2283  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2284  * @since 2.4.8 - 2008-11-04
2285  * @param string $value
2286  * @param string $params optional
2287  * @return bool
2288  */
2289   function setUrl( $value, $params=FALSE ) {
2290     if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2291     $this->url = array( 'value' => $value, 'params' => iCal_UtilityFunctions::_setParams( $params ));
2292     return TRUE;
2293   }
2294 /*********************************************************************************/
2295 /**
2296  * Property Name: x-prop
2297  */
2298 /**
2299  * creates formatted output for calendar component property x-prop
2300  *
2301  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2302  * @since 2.9.3 - 2011-05-14
2303  * @return string
2304  */
2305   function createXprop() {
2306     if( empty( $this->xprop )) return FALSE;
2307     $output = null;
2308     foreach( $this->xprop as $label => $xpropPart ) {
2309       if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
2310         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
2311         continue;
2312       }
2313       $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
2314       if( is_array( $xpropPart['value'] )) {
2315         foreach( $xpropPart['value'] as $pix => $theXpart )
2316           $xpropPart['value'][$pix] = $this->_strrep( $theXpart );
2317         $xpropPart['value']  = implode( ',', $xpropPart['value'] );
2318       }
2319       else
2320         $xpropPart['value'] = $this->_strrep( $xpropPart['value'] );
2321       $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
2322     }
2323     return $output;
2324   }
2325 /**
2326  * set calendar component property x-prop
2327  *
2328  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2329  * @since 2.9.3 - 2011-05-14
2330  * @param string $label
2331  * @param mixed $value
2332  * @param array $params optional
2333  * @return bool
2334  */
2335   function setXprop( $label, $value, $params=FALSE ) {
2336     if( empty( $label )) return;
2337     if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE;
2338     $xprop           = array( 'value' => $value );
2339     $xprop['params'] = iCal_UtilityFunctions::_setParams( $params );
2340     if( !is_array( $this->xprop )) $this->xprop = array();
2341     $this->xprop[strtoupper( $label )] = $xprop;
2342     return TRUE;
2343   }
2344 /*********************************************************************************/
2345 /*********************************************************************************/
2346 /**
2347  * create element format parts
2348  *
2349  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2350  * @since 2.0.6 - 2006-06-20
2351  * @return string
2352  */
2353   function _createFormat() {
2354     $objectname                   = null;
2355     switch( $this->format ) {
2356       case 'xcal':
2357         $objectname               = ( isset( $this->timezonetype )) ?
2358                                  strtolower( $this->timezonetype )  :  strtolower( $this->objName );
2359         $this->componentStart1    = $this->elementStart1 = '<';
2360         $this->componentStart2    = $this->elementStart2 = '>';
2361         $this->componentEnd1      = $this->elementEnd1   = '</';
2362         $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
2363         $this->intAttrDelimiter   = '<!-- -->';
2364         $this->attributeDelimiter = $this->nl;
2365         $this->valueInit          = null;
2366         break;
2367       default:
2368         $objectname               = ( isset( $this->timezonetype )) ?
2369                                  strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
2370         $this->componentStart1    = 'BEGIN:';
2371         $this->componentStart2    = null;
2372         $this->componentEnd1      = 'END:';
2373         $this->componentEnd2      = $this->nl;
2374         $this->elementStart1      = null;
2375         $this->elementStart2      = null;
2376         $this->elementEnd1        = null;
2377         $this->elementEnd2        = $this->nl;
2378         $this->intAttrDelimiter   = '<!-- -->';
2379         $this->attributeDelimiter = ';';
2380         $this->valueInit          = ':';
2381         break;
2382     }
2383     return $objectname;
2384   }
2385 /**
2386  * creates formatted output for calendar component property
2387  *
2388  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2389  * @since 2.6.22 - 2010-12-06
2390  * @param string $label property name
2391  * @param string $attributes property attributes
2392  * @param string $content property content (optional)
2393  * @return string
2394  */
2395   function _createElement( $label, $attributes=null, $content=FALSE ) {
2396     switch( $this->format ) {
2397       case 'xcal':
2398         $label = strtolower( $label );
2399         break;
2400       default:
2401         $label = strtoupper( $label );
2402         break;
2403     }
2404     $output = $this->elementStart1.$label;
2405     $categoriesAttrLang = null;
2406     $attachInlineBinary = FALSE;
2407     $attachfmttype      = null;
2408     if( !empty( $attributes ))  {
2409       $attributes  = trim( $attributes );
2410       if ( 'xcal' == $this->format) {
2411         $attributes2 = explode( $this->intAttrDelimiter, $attributes );
2412         $attributes  = null;
2413         foreach( $attributes2 as $attribute ) {
2414           $attrKVarr = explode( '=', $attribute );
2415           if( empty( $attrKVarr[0] ))
2416             continue;
2417           if( !isset( $attrKVarr[1] )) {
2418             $attrValue = $attrKVarr[0];
2419             $attrKey   = null;
2420           }
2421           elseif( 2 == count( $attrKVarr)) {
2422             $attrKey   = strtolower( $attrKVarr[0] );
2423             $attrValue = $attrKVarr[1];
2424           }
2425           else {
2426             $attrKey   = strtolower( $attrKVarr[0] );
2427             unset( $attrKVarr[0] );
2428             $attrValue = implode( '=', $attrKVarr );
2429           }
2430           if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
2431             $attachInlineBinary = TRUE;
2432             if( 'fmttype' == $attrKey )
2433               $attachfmttype = $attrKey.'='.$attrValue;
2434             continue;
2435           }
2436           elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
2437             $categoriesAttrLang = $attrKey.'='.$attrValue;
2438           else {
2439             $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
2440             $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
2441             if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
2442               $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
2443               $attrValue = str_replace( '"', '', $attrValue );
2444             }
2445             $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
2446           }
2447         }
2448       }
2449       else {
2450         $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
2451       }
2452     }
2453     if(((( 'attach' == $label ) && !$attachInlineBinary ) ||
2454          ( in_array( $label, array( 'tzurl', 'url' ))))      && ( 'xcal' == $this->format)) {
2455       $pos = strrpos($content, "/");
2456       $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
2457       $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
2458                                , 'uri'      => $docname
2459                                , 'ref'      => 'SYSTEM'
2460                                , 'external' => $content
2461                                , 'type'     => 'NDATA'
2462                                , 'type2'    => 'BINERY' );
2463       $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
2464       $attributes .= 'uri="'.$docname.'"';
2465       $content = null;
2466       if( 'attach' == $label ) {
2467         $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
2468         $content = $this->_createElement( 'extref', $attributes, null );
2469         $attributes = null;
2470       }
2471     }
2472     elseif(( 'attach' == $label ) && $attachInlineBinary && ( 'xcal' == $this->format)) {
2473       $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
2474     }
2475     $output .= $attributes;
2476     if( !$content && ( '0' != $content )) {
2477       switch( $this->format ) {
2478         case 'xcal':
2479           $output .= ' /';
2480           $output .= $this->elementStart2;
2481           return $output;
2482           break;
2483         default:
2484           $output .= $this->elementStart2.$this->valueInit;
2485           return $this->_size75( $output );
2486           break;
2487       }
2488     }
2489     $output .= $this->elementStart2;
2490     $output .= $this->valueInit.$content;
2491     switch( $this->format ) {
2492       case 'xcal':
2493         return $output.$this->elementEnd1.$label.$this->elementEnd2;
2494         break;
2495       default:
2496         return $this->_size75( $output );
2497         break;
2498     }
2499   }
2500 /**
2501  * creates formatted output for calendar component property parameters
2502  *
2503  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2504  * @since 2.6.33 - 2010-12-18
2505  * @param array $params  optional
2506  * @param array $ctrKeys optional
2507  * @return string
2508  */
2509   function _createParams( $params=array(), $ctrKeys=array() ) {
2510     if( !is_array( $params ) || empty( $params ))
2511       $params = array();
2512     $attrLANG = $attr1 = $attr2 = $lang = null;
2513     $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
2514     $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
2515     $CNattrExist = $LANGattrExist = FALSE;
2516     $xparams = array();
2517     foreach( $params as $paramKey => $paramValue ) {
2518       if( ctype_digit( (string) $paramKey )) {
2519         $xparams[]          = $paramValue;
2520         continue;
2521       }
2522       $paramKey             = strtoupper( $paramKey );
2523       if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
2524         $xparams[$paramKey] = $paramValue;
2525       else
2526         $params[$paramKey]  = $paramValue;
2527     }
2528     ksort( $xparams, SORT_STRING );
2529     foreach( $xparams as $paramKey => $paramValue ) {
2530       if( ctype_digit( (string) $paramKey ))
2531         $attr2             .= $this->intAttrDelimiter.$paramValue;
2532       else
2533         $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
2534     }
2535     if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
2536       $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
2537       $attr2                = null;
2538     }
2539     if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
2540       if( !empty( $attr2 )) {
2541         $attr1             .= $attr2;
2542         $attr2              = null;
2543       }
2544       $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
2545     }
2546     if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
2547       $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
2548     if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys ))
2549       $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
2550     if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
2551       $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
2552     if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
2553       $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
2554     if( isset( $params['CN'] )       && $CNattrKey ) {
2555       $attr1                = $this->intAttrDelimiter.'CN="'.$params['CN'].'"';
2556       $CNattrExist          = TRUE;
2557     }
2558     if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys ))
2559       $attr1               .= $this->intAttrDelimiter.'DIR="'.$params['DIR'].'"';
2560     if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
2561       $attr1               .= $this->intAttrDelimiter.'SENT-BY="'.$params['SENT-BY'].'"';
2562     if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys ))
2563       $attr1               .= $this->intAttrDelimiter.'ALTREP="'.$params['ALTREP'].'"';
2564     if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
2565       $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
2566       $LANGattrExist        = TRUE;
2567     }
2568     if( !$LANGattrExist ) {
2569       $lang = $this->getConfig( 'language' );
2570       if(( $CNattrExist || $LANGattrKey ) && $lang )
2571         $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
2572     }
2573     return $attr1.$attrLANG.$attr2;
2574   }
2575 /**
2576  * creates formatted output for calendar component property data value type recur
2577  *
2578  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2579  * @since 2.4.8 - 2008-10-22
2580  * @param array $recurlabel
2581  * @param array $recurdata
2582  * @return string
2583  */
2584   function _format_recur( $recurlabel, $recurdata ) {
2585     $output = null;
2586     foreach( $recurdata as $therule ) {
2587       if( empty( $therule['value'] )) {
2588         if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
2589         continue;
2590       }
2591       $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
2592       $content1  = $content2  = null;
2593       foreach( $therule['value'] as $rulelabel => $rulevalue ) {
2594         switch( $rulelabel ) {
2595           case 'FREQ': {
2596             $content1 .= "FREQ=$rulevalue";
2597             break;
2598           }
2599           case 'UNTIL': {
2600             $content2 .= ";UNTIL=";
2601             $content2 .= iCal_UtilityFunctions::_format_date_time( $rulevalue );
2602             break;
2603           }
2604           case 'COUNT':
2605           case 'INTERVAL':
2606           case 'WKST': {
2607             $content2 .= ";$rulelabel=$rulevalue";
2608             break;
2609           }
2610           case 'BYSECOND':
2611           case 'BYMINUTE':
2612           case 'BYHOUR':
2613           case 'BYMONTHDAY':
2614           case 'BYYEARDAY':
2615           case 'BYWEEKNO':
2616           case 'BYMONTH':
2617           case 'BYSETPOS': {
2618             $content2 .= ";$rulelabel=";
2619             if( is_array( $rulevalue )) {
2620               foreach( $rulevalue as $vix => $valuePart ) {
2621                 $content2 .= ( $vix ) ? ',' : null;
2622                 $content2 .= $valuePart;
2623               }
2624             }
2625             else
2626              $content2 .= $rulevalue;
2627             break;
2628           }
2629           case 'BYDAY': {
2630             $content2 .= ";$rulelabel=";
2631             $bydaycnt = 0;
2632             foreach( $rulevalue as $vix => $valuePart ) {
2633               $content21 = $content22 = null;
2634               if( is_array( $valuePart )) {
2635                 $content2 .= ( $bydaycnt ) ? ',' : null;
2636                 foreach( $valuePart as $vix2 => $valuePart2 ) {
2637                   if( 'DAY' != strtoupper( $vix2 ))
2638                       $content21 .= $valuePart2;
2639                   else
2640                     $content22 .= $valuePart2;
2641                 }
2642                 $content2 .= $content21.$content22;
2643                 $bydaycnt++;
2644               }
2645               else {
2646                 $content2 .= ( $bydaycnt ) ? ',' : null;
2647                 if( 'DAY' != strtoupper( $vix ))
2648                     $content21 .= $valuePart;
2649                 else {
2650                   $content22 .= $valuePart;
2651                   $bydaycnt++;
2652                 }
2653                 $content2 .= $content21.$content22;
2654               }
2655             }
2656             break;
2657           }
2658           default: {
2659             $content2 .= ";$rulelabel=$rulevalue";
2660             break;
2661           }
2662         }
2663       }
2664       $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
2665     }
2666     return $output;
2667   }
2668 /**
2669  * check if property not exists within component
2670  *
2671  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2672  * @since 2.5.1 - 2008-10-15
2673  * @param string $propName
2674  * @return bool
2675  */
2676   function _notExistProp( $propName ) {
2677     if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
2678     $propName = strtolower( $propName );
2679     if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
2680     elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
2681     elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
2682     elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
2683     elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
2684     elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
2685     return FALSE;
2686   }
2687 /*********************************************************************************/
2688 /*********************************************************************************/
2689 /**
2690  * get general component config variables or info about subcomponents
2691  *
2692  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2693  * @since 2.9.6 - 2011-05-14
2694  * @param mixed $config
2695  * @return value
2696  */
2697   function getConfig( $config = FALSE) {
2698     if( !$config ) {
2699       $return = array();
2700       $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
2701       $return['FORMAT']      = $this->getConfig( 'FORMAT' );
2702       if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
2703         $return['LANGUAGE']  = $lang;
2704       $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
2705       $return['TZTD']        = $this->getConfig( 'TZID' );
2706       $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
2707       return $return;
2708     }
2709     switch( strtoupper( $config )) {
2710       case 'ALLOWEMPTY':
2711         return $this->allowEmpty;
2712         break;
2713       case 'COMPSINFO':
2714         unset( $this->compix );
2715         $info = array();
2716         if( isset( $this->components )) {
2717           foreach( $this->components as $cix => $component ) {
2718             if( empty( $component )) continue;
2719             $info[$cix]['ordno'] = $cix + 1;
2720             $info[$cix]['type']  = $component->objName;
2721             $info[$cix]['uid']   = $component->getProperty( 'uid' );
2722             $info[$cix]['props'] = $component->getConfig( 'propinfo' );
2723             $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
2724           }
2725         }
2726         return $info;
2727         break;
2728       case 'FORMAT':
2729         return $this->format;
2730         break;
2731       case 'LANGUAGE':
2732          // get language for calendar component as defined in [RFC 1766]
2733         return $this->language;
2734         break;
2735       case 'NL':
2736       case 'NEWLINECHAR':
2737         return $this->nl;
2738         break;
2739       case 'PROPINFO':
2740         $output = array();
2741         if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
2742           if( empty( $this->uid['value'] )) $this->_makeuid();
2743                                               $output['UID']              = 1;
2744         }
2745         if( !empty( $this->dtstamp ))         $output['DTSTAMP']          = 1;
2746         if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
2747         if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
2748         if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
2749         if( !empty( $this->dtend ))           $output['DTEND']            = 1;
2750         if( !empty( $this->due ))             $output['DUE']              = 1;
2751         if( !empty( $this->duration ))        $output['DURATION']         = 1;
2752         if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
2753         if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
2754         if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
2755         if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
2756         if( !empty( $this->action ))          $output['ACTION']           = 1;
2757         if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
2758         if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
2759         if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
2760         if( !empty( $this->class ))           $output['CLASS']            = 1;
2761         if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
2762         if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
2763         if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
2764         if( !empty( $this->created ))         $output['CREATED']          = 1;
2765         if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
2766         if( !empty( $this->geo ))             $output['GEO']              = 1;
2767         if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
2768         if( !empty( $this->location ))        $output['LOCATION']         = 1;
2769         if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
2770         if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
2771         if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
2772         if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
2773         if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
2774         if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
2775         if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
2776         if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
2777         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
2778         if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
2779         if( !empty( $this->status ))          $output['STATUS']           = 1;
2780         if( !empty( $this->transp ))          $output['TRANSP']           = 1;
2781         if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
2782         if( !empty( $this->tzid ))            $output['TZID']             = 1;
2783         if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
2784         if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
2785         if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
2786         if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
2787         if( !empty( $this->url ))             $output['URL']              = 1;
2788         if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
2789         return $output;
2790         break;
2791       case 'TZID':
2792         return $this->dtzid;
2793         break;
2794       case 'UNIQUE_ID':
2795         if( empty( $this->unique_id ))
2796           $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
2797         return $this->unique_id;
2798         break;
2799     }
2800   }
2801 /**
2802  * general component config setting
2803  *
2804  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2805  * @since 2.9.6 - 2011-05-14
2806  * @param mixed  $config
2807  * @param string $value
2808  * @param bool   $softUpdate
2809  * @return void
2810  */
2811   function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
2812     if( is_array( $config )) {
2813       foreach( $config as $cKey => $cValue ) {
2814         if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
2815           return FALSE;
2816       }
2817       return TRUE;
2818     }
2819     $res = FALSE;
2820     switch( strtoupper( $config )) {
2821       case 'ALLOWEMPTY':
2822         $this->allowEmpty = $value;
2823         $subcfg = array( 'ALLOWEMPTY' => $value );
2824         $res    = TRUE;
2825         break;
2826       case 'FORMAT':
2827         $value  = trim( strtolower( $value ));
2828         $this->format = $value;
2829         $this->_createFormat();
2830         $subcfg = array( 'FORMAT' => $value );
2831         $res    = TRUE;
2832         break;
2833       case 'LANGUAGE':
2834          // set language for calendar component as defined in [RFC 1766]
2835         $value  = trim( $value );
2836         if( empty( $this->language ) || !$softUpdate )
2837           $this->language = $value;
2838         $subcfg = array( 'LANGUAGE' => $value );
2839         $res    = TRUE;
2840         break;
2841       case 'NL':
2842       case 'NEWLINECHAR':
2843         $this->nl = $value;
2844         $subcfg = array( 'NL' => $value );
2845         $res    = TRUE;
2846         break;
2847       case 'TZID':
2848         $this->dtzid = $value;
2849         $subcfg = array( 'TZID' => $value );
2850         $res    = TRUE;
2851         break;
2852       case 'UNIQUE_ID':
2853         $value  = trim( $value );
2854         $this->unique_id = $value;
2855         $subcfg = array( 'UNIQUE_ID' => $value );
2856         $res    = TRUE;
2857         break;
2858       default:  // any unvalid config key.. .
2859         return TRUE;
2860     }
2861     if( !$res ) return FALSE;
2862     if( isset( $subcfg ) && !empty( $this->components )) {
2863       foreach( $subcfg as $cfgkey => $cfgvalue ) {
2864         foreach( $this->components as $cix => $component ) {
2865           $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
2866           if( !$res )
2867             break 2;
2868           $this->components[$cix] = $component->copy(); // PHP4 compliant
2869         }
2870       }
2871     }
2872     return $res;
2873   }
2874 /*********************************************************************************/
2875 /**
2876  * delete component property value
2877  *
2878  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2879  * @since 2.8.8 - 2011-03-15
2880  * @param mixed $propName, bool FALSE => X-property
2881  * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
2882  * @return bool, if successfull delete TRUE
2883  */
2884   function deleteProperty( $propName=FALSE, $propix=FALSE ) {
2885     if( $this->_notExistProp( $propName )) return FALSE;
2886     $propName = strtoupper( $propName );
2887     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
2888                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
2889       if( !$propix )
2890         $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
2891       $this->propdelix[$propName] = --$propix;
2892     }
2893     $return = FALSE;
2894     switch( $propName ) {
2895       case 'ACTION':
2896         if( !empty( $this->action )) {
2897           $this->action = '';
2898           $return = TRUE;
2899         }
2900         break;
2901       case 'ATTACH':
2902         return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
2903         break;
2904       case 'ATTENDEE':
2905         return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
2906         break;
2907       case 'CATEGORIES':
2908         return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
2909         break;
2910       case 'CLASS':
2911         if( !empty( $this->class )) {
2912           $this->class = '';
2913           $return = TRUE;
2914         }
2915         break;
2916       case 'COMMENT':
2917         return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
2918         break;
2919       case 'COMPLETED':
2920         if( !empty( $this->completed )) {
2921           $this->completed = '';
2922           $return = TRUE;
2923         }
2924         break;
2925       case 'CONTACT':
2926         return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
2927         break;
2928       case 'CREATED':
2929         if( !empty( $this->created )) {
2930           $this->created = '';
2931           $return = TRUE;
2932         }
2933         break;
2934       case 'DESCRIPTION':
2935         return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
2936         break;
2937       case 'DTEND':
2938         if( !empty( $this->dtend )) {
2939           $this->dtend = '';
2940           $return = TRUE;
2941         }
2942         break;
2943       case 'DTSTAMP':
2944         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
2945           return FALSE;
2946         if( !empty( $this->dtstamp )) {
2947           $this->dtstamp = '';
2948           $return = TRUE;
2949         }
2950         break;
2951       case 'DTSTART':
2952         if( !empty( $this->dtstart )) {
2953           $this->dtstart = '';
2954           $return = TRUE;
2955         }
2956         break;
2957       case 'DUE':
2958         if( !empty( $this->due )) {
2959           $this->due = '';
2960           $return = TRUE;
2961         }
2962         break;
2963       case 'DURATION':
2964         if( !empty( $this->duration )) {
2965           $this->duration = '';
2966           $return = TRUE;
2967         }
2968         break;
2969       case 'EXDATE':
2970         return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
2971         break;
2972       case 'EXRULE':
2973         return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
2974         break;
2975       case 'FREEBUSY':
2976         return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
2977         break;
2978       case 'GEO':
2979         if( !empty( $this->geo )) {
2980           $this->geo = '';
2981           $return = TRUE;
2982         }
2983         break;
2984       case 'LAST-MODIFIED':
2985         if( !empty( $this->lastmodified )) {
2986           $this->lastmodified = '';
2987           $return = TRUE;
2988         }
2989         break;
2990       case 'LOCATION':
2991         if( !empty( $this->location )) {
2992           $this->location = '';
2993           $return = TRUE;
2994         }
2995         break;
2996       case 'ORGANIZER':
2997         if( !empty( $this->organizer )) {
2998           $this->organizer = '';
2999           $return = TRUE;
3000         }
3001         break;
3002       case 'PERCENT-COMPLETE':
3003         if( !empty( $this->percentcomplete )) {
3004           $this->percentcomplete = '';
3005           $return = TRUE;
3006         }
3007         break;
3008       case 'PRIORITY':
3009         if( !empty( $this->priority )) {
3010           $this->priority = '';
3011           $return = TRUE;
3012         }
3013         break;
3014       case 'RDATE':
3015         return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
3016         break;
3017       case 'RECURRENCE-ID':
3018         if( !empty( $this->recurrenceid )) {
3019           $this->recurrenceid = '';
3020           $return = TRUE;
3021         }
3022         break;
3023       case 'RELATED-TO':
3024         return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
3025         break;
3026       case 'REPEAT':
3027         if( !empty( $this->repeat )) {
3028           $this->repeat = '';
3029           $return = TRUE;
3030         }
3031         break;
3032       case 'REQUEST-STATUS':
3033         return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
3034         break;
3035       case 'RESOURCES':
3036         return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
3037         break;
3038       case 'RRULE':
3039         return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
3040         break;
3041       case 'SEQUENCE':
3042         if( !empty( $this->sequence )) {
3043           $this->sequence = '';
3044           $return = TRUE;
3045         }
3046         break;
3047       case 'STATUS':
3048         if( !empty( $this->status )) {
3049           $this->status = '';
3050           $return = TRUE;
3051         }
3052         break;
3053       case 'SUMMARY':
3054         if( !empty( $this->summary )) {
3055           $this->summary = '';
3056           $return = TRUE;
3057         }
3058         break;
3059       case 'TRANSP':
3060         if( !empty( $this->transp )) {
3061           $this->transp = '';
3062           $return = TRUE;
3063         }
3064         break;
3065       case 'TRIGGER':
3066         if( !empty( $this->trigger )) {
3067           $this->trigger = '';
3068           $return = TRUE;
3069         }
3070         break;
3071       case 'TZID':
3072         if( !empty( $this->tzid )) {
3073           $this->tzid = '';
3074           $return = TRUE;
3075         }
3076         break;
3077       case 'TZNAME':
3078         return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
3079         break;
3080       case 'TZOFFSETFROM':
3081         if( !empty( $this->tzoffsetfrom )) {
3082           $this->tzoffsetfrom = '';
3083           $return = TRUE;
3084         }
3085         break;
3086       case 'TZOFFSETTO':
3087         if( !empty( $this->tzoffsetto )) {
3088           $this->tzoffsetto = '';
3089           $return = TRUE;
3090         }
3091         break;
3092       case 'TZURL':
3093         if( !empty( $this->tzurl )) {
3094           $this->tzurl = '';
3095           $return = TRUE;
3096         }
3097         break;
3098       case 'UID':
3099         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
3100           return FALSE;
3101         if( !empty( $this->uid )) {
3102           $this->uid = '';
3103           $return = TRUE;
3104         }
3105         break;
3106       case 'URL':
3107         if( !empty( $this->url )) {
3108           $this->url = '';
3109           $return = TRUE;
3110         }
3111         break;
3112       default:
3113         $reduced = '';
3114         if( $propName != 'X-PROP' ) {
3115           if( !isset( $this->xprop[$propName] )) return FALSE;
3116           foreach( $this->xprop as $k => $a ) {
3117             if(( $k != $propName ) && !empty( $a ))
3118               $reduced[$k] = $a;
3119           }
3120         }
3121         else {
3122           if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
3123           $xpropno = 0;
3124           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
3125             if( $propix != $xpropno )
3126               $reduced[$xpropkey] = $xpropvalue;
3127             $xpropno++;
3128           }
3129         }
3130         $this->xprop = $reduced;
3131         if( empty( $this->xprop )) {
3132           unset( $this->propdelix[$propName] );
3133           return FALSE;
3134         }
3135         return TRUE;
3136     }
3137     return $return;
3138   }
3139 /*********************************************************************************/
3140 /**
3141  * delete component property value, fixing components with multiple occurencies
3142  *
3143  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3144  * @since 2.8.8 - 2011-03-15
3145  * @param array $multiprop, reference to a component property
3146  * @param int   $propix, reference to removal counter
3147  * @return bool TRUE
3148  */
3149   function deletePropertyM( & $multiprop, & $propix ) {
3150     if( isset( $multiprop[$propix] ))
3151       unset( $multiprop[$propix] );
3152     if( empty( $multiprop )) {
3153       $multiprop = '';
3154       unset( $propix );
3155       return FALSE;
3156     }
3157     else
3158       return TRUE;
3159   }
3160 /**
3161  * get component property value/params
3162  *
3163  * if property has multiply values, consequtive function calls are needed
3164  *
3165  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3166  * @since 2.10.1 - 2011-07-16
3167  * @param string $propName, optional
3168  * @param int @propix, optional, if specific property is wanted in case of multiply occurences
3169  * @param bool $inclParam=FALSE
3170  * @param bool $specform=FALSE
3171  * @return mixed
3172  */
3173   function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
3174     if( $this->_notExistProp( $propName )) return FALSE;
3175     $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
3176     if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
3177                                     'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
3178       if( !$propix )
3179         $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
3180       $this->propix[$propName] = --$propix;
3181     }
3182     switch( $propName ) {
3183       case 'ACTION':
3184         if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
3185         break;
3186       case 'ATTACH':
3187         $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
3188         while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
3189           $propix++;
3190         if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3191         return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
3192         break;
3193       case 'ATTENDEE':
3194         $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
3195         while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
3196           $propix++;
3197         if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3198         return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
3199         break;
3200       case 'CATEGORIES':
3201         $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
3202         while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
3203           $propix++;
3204         if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3205         return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
3206         break;
3207       case 'CLASS':
3208         if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
3209         break;
3210       case 'COMMENT':
3211         $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
3212         while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
3213           $propix++;
3214         if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3215         return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
3216         break;
3217       case 'COMPLETED':
3218         if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
3219         break;
3220       case 'CONTACT':
3221         $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
3222         while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
3223           $propix++;
3224         if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3225         return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
3226         break;
3227       case 'CREATED':
3228         if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
3229         break;
3230       case 'DESCRIPTION':
3231         $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
3232         while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
3233           $propix++;
3234         if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3235         return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
3236         break;
3237       case 'DTEND':
3238         if( !empty( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
3239         break;
3240       case 'DTSTAMP':
3241         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
3242           return;
3243         if( !isset( $this->dtstamp['value'] ))
3244           $this->_makeDtstamp();
3245         return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
3246         break;
3247       case 'DTSTART':
3248         if( !empty( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
3249         break;
3250       case 'DUE':
3251         if( !empty( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
3252         break;
3253       case 'DURATION':
3254         if( !isset( $this->duration['value'] )) return FALSE;
3255         $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCal_UtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
3256         return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
3257         break;
3258       case 'EXDATE':
3259         $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
3260         while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
3261           $propix++;
3262         if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3263         return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
3264         break;
3265       case 'EXRULE':
3266         $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
3267         while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
3268           $propix++;
3269         if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3270         return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
3271         break;
3272       case 'FREEBUSY':
3273         $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
3274         while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
3275           $propix++;
3276         if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3277         return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
3278         break;
3279       case 'GEO':
3280         if( !empty( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
3281         break;
3282       case 'LAST-MODIFIED':
3283         if( !empty( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
3284         break;
3285       case 'LOCATION':
3286         if( !empty( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
3287         break;
3288       case 'ORGANIZER':
3289         if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
3290         break;
3291       case 'PERCENT-COMPLETE':
3292         if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
3293         break;
3294       case 'PRIORITY':
3295         if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value'];
3296         break;
3297       case 'RDATE':
3298         $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
3299         while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
3300           $propix++;
3301         if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3302         return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
3303         break;
3304       case 'RECURRENCE-ID':
3305         if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
3306         break;
3307       case 'RELATED-TO':
3308         $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
3309         while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
3310           $propix++;
3311         if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3312         return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
3313         break;
3314       case 'REPEAT':
3315         if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
3316         break;
3317       case 'REQUEST-STATUS':
3318         $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
3319         while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
3320           $propix++;
3321         if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3322         return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
3323         break;
3324       case 'RESOURCES':
3325         $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
3326         while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
3327           $propix++;
3328         if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3329         return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
3330         break;
3331       case 'RRULE':
3332         $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
3333         while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
3334           $propix++;
3335         if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3336         return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
3337         break;
3338       case 'SEQUENCE':
3339         if(
3340                     isset( $this->sequence['value'] ) &&
3341                     ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))
3342         )
3343         return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
3344         break;
3345       case 'STATUS':
3346         if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
3347         break;
3348       case 'SUMMARY':
3349         if( !empty( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
3350         break;
3351       case 'TRANSP':
3352         if( !empty( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
3353         break;
3354       case 'TRIGGER':
3355         if( !empty( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
3356         break;
3357       case 'TZID':
3358         if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
3359         break;
3360       case 'TZNAME':
3361         $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
3362         while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
3363           $propix++;
3364         if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
3365         return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
3366         break;
3367       case 'TZOFFSETFROM':
3368         if( !empty( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
3369         break;
3370       case 'TZOFFSETTO':
3371         if( !empty( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
3372         break;
3373       case 'TZURL':
3374         if( !empty( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
3375         break;
3376       case 'UID':
3377         if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
3378           return FALSE;
3379         if( empty( $this->uid['value'] ))
3380           $this->_makeuid();
3381         return ( $inclParam ) ? $this->uid : $this->uid['value'];
3382         break;
3383       case 'URL':
3384         if( !empty( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
3385         break;
3386       default:
3387         if( $propName != 'X-PROP' ) {
3388           if( !isset( $this->xprop[$propName] )) return FALSE;
3389           return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
3390                                 : array( $propName, $this->xprop[$propName]['value'] );
3391         }
3392         else {
3393           if( empty( $this->xprop )) return FALSE;
3394           $xpropno = 0;
3395           foreach( $this->xprop as $xpropkey => $xpropvalue ) {
3396             if( $propix == $xpropno )
3397               return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
3398                                     : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
3399             else
3400               $xpropno++;
3401           }
3402           return FALSE; // not found ??
3403         }
3404     }
3405     return FALSE;
3406   }
3407 /**
3408  * returns calendar property unique values for 'CATEGORIES', 'RESOURCES' or 'ATTENDEE' and each number of ocurrence
3409  *
3410  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3411  * @since 2.8.8 - 2011-04-13
3412  * @param string $propName
3413  * @param array  $output, incremented result array
3414  */
3415   function _getProperties( $propName, & $output ) {
3416     if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'RESOURCES' )))
3417       return output;
3418     while( FALSE !== ( $content = $this->getProperty( $propName ))) {
3419       if( is_array( $content )) {
3420         foreach( $content as $part ) {
3421           if( FALSE !== strpos( $part, ',' )) {
3422             $part = explode( ',', $part );
3423             foreach( $part as $thePart ) {
3424               $thePart = trim( $thePart );
3425               if( !empty( $thePart )) {
3426                 if( !isset( $output[$thePart] ))
3427                   $output[$thePart] = 1;
3428                 else
3429                   $output[$thePart] += 1;
3430               }
3431             }
3432           }
3433           else {
3434             $part = trim( $part );
3435             if( !isset( $output[$part] ))
3436               $output[$part] = 1;
3437             else
3438               $output[$part] += 1;
3439           }
3440         }
3441       }
3442       elseif( FALSE !== strpos( $content, ',' )) {
3443         $content = explode( ',', $content );
3444         foreach( $content as $thePart ) {
3445           $thePart = trim( $thePart );
3446           if( !empty( $thePart )) {
3447             if( !isset( $output[$thePart] ))
3448               $output[$thePart] = 1;
3449             else
3450               $output[$thePart] += 1;
3451           }
3452         }
3453       }
3454       else {
3455         $content = trim( $content );
3456         if( !empty( $content )) {
3457           if( !isset( $output[$content] ))
3458             $output[$content] = 1;
3459           else
3460             $output[$content] += 1;
3461         }
3462       }
3463     }
3464     ksort( $output );
3465     return $output;
3466   }
3467 /**
3468  * general component property setting
3469  *
3470  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3471  * @since 2.5.1 - 2008-11-05
3472  * @param mixed $args variable number of function arguments,
3473  *                    first argument is ALWAYS component name,
3474  *                    second ALWAYS component value!
3475  * @return void
3476  */
3477   function setProperty() {
3478     $numargs    = func_num_args();
3479     if( 1 > $numargs ) return FALSE;
3480     $arglist    = func_get_args();
3481     if( $this->_notExistProp( $arglist[0] )) return FALSE;
3482     if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
3483       return FALSE;
3484     $arglist[0] = strtoupper( $arglist[0] );
3485     for( $argix=$numargs; $argix < 12; $argix++ ) {
3486       if( !isset( $arglist[$argix] ))
3487         $arglist[$argix] = null;
3488     }
3489     switch( $arglist[0] ) {
3490       case 'ACTION':
3491         return $this->setAction( $arglist[1], $arglist[2] );
3492       case 'ATTACH':
3493         return $this->setAttach( $arglist[1], $arglist[2], $arglist[3] );
3494       case 'ATTENDEE':
3495         return $this->setAttendee( $arglist[1], $arglist[2], $arglist[3] );
3496       case 'CATEGORIES':
3497         return $this->setCategories( $arglist[1], $arglist[2], $arglist[3] );
3498       case 'CLASS':
3499         return $this->setClass( $arglist[1], $arglist[2] );
3500       case 'COMMENT':
3501         return $this->setComment( $arglist[1], $arglist[2], $arglist[3] );
3502       case 'COMPLETED':
3503         return $this->setCompleted( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
3504       case 'CONTACT':
3505         return $this->setContact( $arglist[1], $arglist[2], $arglist[3] );
3506       case 'CREATED':
3507         return $this->setCreated( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
3508       case 'DESCRIPTION':
3509         return $this->setDescription( $arglist[1], $arglist[2], $arglist[3] );
3510       case 'DTEND':
3511         return $this->setDtend( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
3512       case 'DTSTAMP':
3513         return $this->setDtstamp( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
3514       case 'DTSTART':
3515         return $this->setDtstart( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
3516       case 'DUE':
3517         return $this->setDue( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
3518       case 'DURATION':
3519         return $this->setDuration( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
3520       case 'EXDATE':
3521         return $this->setExdate( $arglist[1], $arglist[2], $arglist[3] );
3522       case 'EXRULE':
3523         return $this->setExrule( $arglist[1], $arglist[2], $arglist[3] );
3524       case 'FREEBUSY':
3525         return $this->setFreebusy( $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
3526       case 'GEO':
3527         return $this->setGeo( $arglist[1], $arglist[2], $arglist[3] );
3528       case 'LAST-MODIFIED':
3529         return $this->setLastModified( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
3530       case 'LOCATION':
3531         return $this->setLocation( $arglist[1], $arglist[2] );
3532       case 'ORGANIZER':
3533         return $this->setOrganizer( $arglist[1], $arglist[2] );
3534       case 'PERCENT-COMPLETE':
3535         return $this->setPercentComplete( $arglist[1], $arglist[2] );
3536       case 'PRIORITY':
3537         return $this->setPriority( $arglist[1], $arglist[2] );
3538       case 'RDATE':
3539         return $this->setRdate( $arglist[1], $arglist[2], $arglist[3] );
3540       case 'RECURRENCE-ID':
3541        return $this->setRecurrenceid( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
3542       case 'RELATED-TO':
3543         return $this->setRelatedTo( $arglist[1], $arglist[2], $arglist[3] );
3544       case 'REPEAT':
3545         return $this->setRepeat( $arglist[1], $arglist[2] );
3546       case 'REQUEST-STATUS':
3547         return $this->setRequestStatus( $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
3548       case 'RESOURCES':
3549         return $this->setResources( $arglist[1], $arglist[2], $arglist[3] );
3550       case 'RRULE':
3551         return $this->setRrule( $arglist[1], $arglist[2], $arglist[3] );
3552       case 'SEQUENCE':
3553         return $this->setSequence( $arglist[1], $arglist[2] );
3554       case 'STATUS':
3555         return $this->setStatus( $arglist[1], $arglist[2] );
3556       case 'SUMMARY':
3557         return $this->setSummary( $arglist[1], $arglist[2] );
3558       case 'TRANSP':
3559         return $this->setTransp( $arglist[1], $arglist[2] );
3560       case 'TRIGGER':
3561         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] );
3562       case 'TZID':
3563         return $this->setTzid( $arglist[1], $arglist[2] );
3564       case 'TZNAME':
3565         return $this->setTzname( $arglist[1], $arglist[2], $arglist[3] );
3566       case 'TZOFFSETFROM':
3567         return $this->setTzoffsetfrom( $arglist[1], $arglist[2] );
3568       case 'TZOFFSETTO':
3569         return $this->setTzoffsetto( $arglist[1], $arglist[2] );
3570       case 'TZURL':
3571         return $this->setTzurl( $arglist[1], $arglist[2] );
3572       case 'UID':
3573         return $this->setUid( $arglist[1], $arglist[2] );
3574       case 'URL':
3575         return $this->setUrl( $arglist[1], $arglist[2] );
3576       default:
3577         return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
3578     }
3579     return FALSE;
3580   }
3581 /*********************************************************************************/
3582 /**
3583  * parse component unparsed data into properties
3584  *
3585  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3586  * @since 2.10.2 - 2011-07-17
3587  * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
3588  * @return bool FALSE if error occurs during parsing
3589  *
3590  */
3591   function parse( $unparsedtext=null ) {
3592     if( !empty( $unparsedtext )) {
3593       $nl = $this->getConfig( 'nl' );
3594       if( is_array( $unparsedtext ))
3595         $unparsedtext = implode( '\n'.$nl, $unparsedtext );
3596             /* fix line folding */
3597       $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings
3598       $EOLmark = FALSE;
3599       foreach( $eolchars as $eolchar ) {
3600         if( !$EOLmark  && ( FALSE !== strpos( $unparsedtext, $eolchar ))) {
3601           $unparsedtext = str_replace( $eolchar." ",  '',  $unparsedtext );
3602           $unparsedtext = str_replace( $eolchar."\t", '',  $unparsedtext );
3603           if( $eolchar != $nl )
3604             $unparsedtext = str_replace( $eolchar,    $nl, $unparsedtext );
3605           $EOLmark = TRUE;
3606         }
3607       }
3608       $tmp = explode( $nl, $unparsedtext );
3609       $unparsedtext = array();
3610       foreach( $tmp as $tmpr )
3611         if( !empty( $tmpr ))
3612           $unparsedtext[] = $tmpr;
3613     }
3614     elseif( !isset( $this->unparsed ))
3615       $unparsedtext = array();
3616     else
3617       $unparsedtext = $this->unparsed;
3618     $this->unparsed = array();
3619     $comp = & $this;
3620     $config = $this->getConfig();
3621     foreach ( $unparsedtext as $line ) {
3622  // echo $comp->objName.": $line<br />"; // test ###
3623       if( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VA', 'END:DA' )))
3624         $this->components[] = $comp->copy();
3625       elseif( 'END:ST' == strtoupper( substr( $line, 0, 6 )))
3626         array_unshift( $this->components, $comp->copy());
3627       elseif( 'END:' == strtoupper( substr( $line, 0, 4 )))
3628         break;
3629       elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 )))
3630         $comp = new valarm( $config);
3631       elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 )))
3632         $comp = new vtimezone( 'standard', $config );
3633       elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 )))
3634         $comp = new vtimezone( 'daylight', $config );
3635       elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))
3636         continue;
3637       else {
3638         $comp->unparsed[] = $line;
3639 // echo $comp->objName.": $line<br />\n"; // test ###
3640       }
3641     }
3642     unset( $config );
3643 // echo $this->objName.'<br />'.var_export( $this->unparsed, TRUE )."<br />\n"; // test ###
3644             /* concatenate property values spread over several lines */
3645     $lastix    = -1;
3646     $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
3647                       , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
3648                       , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
3649                       , 'last-modified', 'location', 'organizer', 'percent-complete'
3650                       , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
3651                       , 'request-status', 'resources', 'rrule', 'sequence', 'status'
3652                       , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
3653                       , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
3654     $proprows  = array();
3655     foreach( $this->unparsed as $line ) {
3656       $newProp = FALSE;
3657       foreach ( $propnames as $propname ) {
3658         if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) {
3659           $newProp = TRUE;
3660           break;
3661         }
3662       }
3663       if( $newProp ) {
3664         if( -1 < $lastix )
3665           $proprows[$lastix] = $proprows[$lastix];
3666         $newProp = FALSE;
3667         $lastix++;
3668         $proprows[$lastix]  = $line;
3669       }
3670       else
3671         $proprows[$lastix] .= '!"#¤%&/()=?'.$line;
3672     }
3673             /* parse each property 'line' */
3674     foreach( $proprows as $line ) {
3675       $line = str_replace( '!"#¤%&/()=? ', '', $line );
3676       $line = str_replace( '!"#¤%&/()=?', '', $line );
3677       if( '\n' == substr( $line, -2 ))
3678         $line = substr( $line, 0, strlen( $line ) - 2 );
3679             /* get propname, (problem with x-properties, otherwise in previous loop) */
3680       $cix = $propname = null;
3681       for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) {
3682         if( in_array( $line[$cix], array( ':', ';' )))
3683           break;
3684         else {
3685           $propname .= $line[$cix];
3686         }
3687       }
3688       if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
3689         $propname2 = $propname;
3690         $propname  = 'X-';
3691       }
3692             /* rest of the line is opt.params and value */
3693       $line = substr( $line, $cix );
3694             /* separate attributes from value */
3695       $attr   = array();
3696       $attrix = -1;
3697       $clen = strlen( $line );
3698       for( $cix=0; $cix < $clen; $cix++ ) {
3699         if((       ':'   == $line[$cix] )             &&
3700                  ( '://' != substr( $line, $cix, 3 )) &&
3701              ( !in_array( strtolower( substr( $line, $cix - 3, 4 )), array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ))) &&
3702              ( !in_array( strtolower( substr( $line, $cix - 4, 5 )), array( 'crid:', 'news:', 'pres:' ))) &&
3703            ( 'mailto:'   != strtolower( substr( $line, $cix - 6, 7 )))) {
3704           $attrEnd = TRUE;
3705           if(( $cix < ( $clen - 4 )) &&
3706                ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
3707             for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
3708               if( '://' == substr( $line, $c2ix - 2, 3 )) {
3709                 $attrEnd = FALSE;
3710                 break; // an URI with a portnr!!
3711               }
3712             }
3713           }
3714           if( $attrEnd) {
3715             $line = substr( $line, $cix + 1 );
3716             break;
3717           }
3718         }
3719         if( ';' == $line[$cix] )
3720           $attr[++$attrix] = null;
3721         else
3722           $attr[$attrix] .= $line[$cix];
3723       }
3724             /* make attributes in array format */
3725       $propattr = array();
3726       foreach( $attr as $attribute ) {
3727         $attrsplit = explode( '=', $attribute, 2 );
3728         if( 1 < count( $attrsplit ))
3729           $propattr[$attrsplit[0]] = $attrsplit[1];
3730         else
3731           $propattr[] = $attribute;
3732       }
3733             /* call setProperty( $propname.. . */
3734       switch( strtoupper( $propname )) {
3735         case 'ATTENDEE':
3736           foreach( $propattr as $pix => $attr ) {
3737             $attr2 = explode( ',', $attr );
3738               if( 1 < count( $attr2 ))
3739                 $propattr[$pix] = $attr2;
3740           }
3741           $this->setProperty( $propname, $line, $propattr );
3742           break;
3743         case 'CATEGORIES':
3744         case 'RESOURCES':
3745           if( FALSE !== strpos( $line, ',' )) {
3746             $content  = explode( ',', $line );
3747             $clen     = count( $content );
3748             for( $cix = 0; $cix < $clen; $cix++ ) {
3749               if( "\\" == substr($content[$cix], -1)) {
3750                 $content[$cix] .= ','.$content[$cix + 1];
3751                 unset($content[$cix + 1]);
3752                 $cix++;
3753               }
3754             }
3755             if( 1 < count( $content )) {
3756               $content = array_values( $content );
3757               foreach( $content as $cix => $contentPart )
3758                 $content[$cix] = calendarComponent::_strunrep( $contentPart );
3759               $this->setProperty( $propname, $content, $propattr );
3760               break;
3761             }
3762             else
3763               $line = reset( $content );
3764           }
3765         case 'X-':
3766           $propname = ( isset( $propname2 )) ? $propname2 : $propname;
3767         case 'COMMENT':
3768         case 'CONTACT':
3769         case 'DESCRIPTION':
3770         case 'LOCATION':
3771         case 'SUMMARY':
3772           if( empty( $line ))
3773             $propattr = null;
3774           $this->setProperty( $propname, calendarComponent::_strunrep( $line ), $propattr );
3775           unset( $propname2 );
3776           break;
3777         case 'REQUEST-STATUS':
3778           $values    = explode( ';', $line, 3 );
3779           $values[1] = ( !isset( $values[1] )) ? null : calendarComponent::_strunrep( $values[1] );
3780           $values[2] = ( !isset( $values[2] )) ? null : calendarComponent::_strunrep( $values[2] );
3781           $this->setProperty( $propname
3782                             , $values[0]  // statcode
3783                             , $values[1]  // statdesc
3784                             , $values[2]  // extdata
3785                             , $propattr );
3786           break;
3787         case 'FREEBUSY':
3788           $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
3789           unset( $propattr['FBTYPE'] );
3790           $values = explode( ',', $line );
3791           foreach( $values as $vix => $value ) {
3792             $value2 = explode( '/', $value );
3793             if( 1 < count( $value2 ))
3794               $values[$vix] = $value2;
3795           }
3796           $this->setProperty( $propname, $fbtype, $values, $propattr );
3797           break;
3798         case 'GEO':
3799           $value = explode( ';', $line, 2 );
3800           if( 2 > count( $value ))
3801             $value[1] = null;
3802           $this->setProperty( $propname, $value[0], $value[1], $propattr );
3803           break;
3804         case 'EXDATE':
3805           $values = ( !empty( $line )) ? explode( ',', $line ) : null;
3806           $this->setProperty( $propname, $values, $propattr );
3807           break;
3808         case 'RDATE':
3809           if( empty( $line )) {
3810             $this->setProperty( $propname, $line, $propattr );
3811             break;
3812           }
3813           $values = explode( ',', $line );
3814           foreach( $values as $vix => $value ) {
3815             $value2 = explode( '/', $value );
3816             if( 1 < count( $value2 ))
3817               $values[$vix] = $value2;
3818           }
3819           $this->setProperty( $propname, $values, $propattr );
3820           break;
3821         case 'EXRULE':
3822         case 'RRULE':
3823           $values = explode( ';', $line );
3824           $recur = array();
3825           foreach( $values as $value2 ) {
3826             if( empty( $value2 ))
3827               continue; // ;-char in ending position ???
3828             $value3 = explode( '=', $value2, 2 );
3829             $rulelabel = strtoupper( $value3[0] );
3830             switch( $rulelabel ) {
3831               case 'BYDAY': {
3832                 $value4 = explode( ',', $value3[1] );
3833                 if( 1 < count( $value4 )) {
3834                   foreach( $value4 as $v5ix => $value5 ) {
3835                     $value6 = array();
3836                     $dayno = $dayname = null;
3837                     $value5 = trim( (string) $value5 );
3838                     if(( ctype_alpha( substr( $value5, -1 ))) &&
3839                        ( ctype_alpha( substr( $value5, -2, 1 )))) {
3840                       $dayname = substr( $value5, -2, 2 );
3841                       if( 2 < strlen( $value5 ))
3842                         $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
3843                     }
3844                     if( $dayno )
3845                       $value6[] = $dayno;
3846                     if( $dayname )
3847                       $value6['DAY'] = $dayname;
3848                     $value4[$v5ix] = $value6;
3849                   }
3850                 }
3851                 else {
3852                   $value4 = array();
3853                   $dayno  = $dayname = null;
3854                   $value5 = trim( (string) $value3[1] );
3855                   if(( ctype_alpha( substr( $value5, -1 ))) &&
3856                      ( ctype_alpha( substr( $value5, -2, 1 )))) {
3857                       $dayname = substr( $value5, -2, 2 );
3858                     if( 2 < strlen( $value5 ))
3859                       $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
3860                   }
3861                   if( $dayno )
3862                     $value4[] = $dayno;
3863                   if( $dayname )
3864                     $value4['DAY'] = $dayname;
3865                 }
3866                 $recur[$rulelabel] = $value4;
3867                 break;
3868               }
3869               default: {
3870                 $value4 = explode( ',', $value3[1] );
3871                 if( 1 < count( $value4 ))
3872                   $value3[1] = $value4;
3873                 $recur[$rulelabel] = $value3[1];
3874                 break;
3875               }
3876             } // end - switch $rulelabel
3877           } // end - foreach( $values.. .
3878           $this->setProperty( $propname, $recur, $propattr );
3879           break;
3880         case 'ACTION':
3881         case 'CLASSIFICATION':
3882         case 'STATUS':
3883         case 'TRANSP':
3884         case 'UID':
3885         case 'TZID':
3886         case 'RELATED-TO':
3887         case 'TZNAME':
3888           $line = calendarComponent::_strunrep( $line );
3889         default:
3890           $this->setProperty( $propname, $line, $propattr );
3891           break;
3892       } // end  switch( $propname.. .
3893     } // end - foreach( $proprows.. .
3894     unset( $unparsedtext, $this->unparsed, $proprows );
3895     if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
3896       $ckeys = array_keys( $this->components );
3897       foreach( $ckeys as $ckey ) {
3898         if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
3899           $this->components[$ckey]->parse();
3900         }
3901       }
3902     }
3903     return TRUE;
3904   }
3905 /*********************************************************************************/
3906 /*********************************************************************************/
3907 /**
3908  * return a copy of this component
3909  *
3910  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3911  * @since 2.8.8 - 2011-03-15
3912  * @return object
3913  */
3914   function copy() {
3915     $serialized_contents = serialize( $this );
3916     $copy = unserialize( $serialized_contents );
3917     return $copy;
3918  }
3919 /*********************************************************************************/
3920 /*********************************************************************************/
3921 /**
3922  * delete calendar subcomponent from component container
3923  *
3924  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3925  * @since 2.8.8 - 2011-03-15
3926  * @param mixed $arg1 ordno / component type / component uid
3927  * @param mixed $arg2 optional, ordno if arg1 = component type
3928  * @return void
3929  */
3930   function deleteComponent( $arg1, $arg2=FALSE  ) {
3931     if( !isset( $this->components )) return FALSE;
3932     $argType = $index = null;
3933     if ( ctype_digit( (string) $arg1 )) {
3934       $argType = 'INDEX';
3935       $index   = (int) $arg1 - 1;
3936     }
3937     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
3938       $argType = strtolower( $arg1 );
3939       $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
3940     }
3941     $cix2dC = 0;
3942     foreach ( $this->components as $cix => $component) {
3943       if( empty( $component )) continue;
3944       if(( 'INDEX' == $argType ) && ( $index == $cix )) {
3945         unset( $this->components[$cix] );
3946         return TRUE;
3947       }
3948       elseif( $argType == $component->objName ) {
3949         if( $index == $cix2dC ) {
3950           unset( $this->components[$cix] );
3951           return TRUE;
3952         }
3953         $cix2dC++;
3954       }
3955       elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
3956         unset( $this->components[$cix] );
3957         return TRUE;
3958       }
3959     }
3960     return FALSE;
3961   }
3962 /**
3963  * get calendar component subcomponent from component container
3964  *
3965  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3966  * @since 2.8.8 - 2011-03-15
3967  * @param mixed $arg1 optional, ordno/component type/ component uid
3968  * @param mixed $arg2 optional, ordno if arg1 = component type
3969  * @return object
3970  */
3971   function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
3972     if( !isset( $this->components )) return FALSE;
3973     $index = $argType = null;
3974     if ( !$arg1 ) {
3975       $argType = 'INDEX';
3976       $index   = $this->compix['INDEX'] =
3977         ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
3978     }
3979     elseif ( ctype_digit( (string) $arg1 )) {
3980       $argType = 'INDEX';
3981       $index   = (int) $arg1;
3982       unset( $this->compix );
3983     }
3984     elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
3985       unset( $this->compix['INDEX'] );
3986       $argType = strtolower( $arg1 );
3987       if( !$arg2 )
3988         $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
3989       else
3990         $index = (int) $arg2;
3991     }
3992     $index  -= 1;
3993     $ckeys = array_keys( $this->components );
3994     if( !empty( $index) && ( $index > end( $ckeys )))
3995       return FALSE;
3996     $cix2gC = 0;
3997     foreach( $this->components as $cix => $component ) {
3998       if( empty( $component )) continue;
3999       if(( 'INDEX' == $argType ) && ( $index == $cix ))
4000         return $component->copy();
4001       elseif( $argType == $component->objName ) {
4002          if( $index == $cix2gC )
4003            return $component->copy();
4004          $cix2gC++;
4005       }
4006       elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
4007         return $component->copy();
4008     }
4009             /* not found.. . */
4010     unset( $this->compix );
4011     return false;
4012   }
4013 /**
4014  * add calendar component as subcomponent to container for subcomponents
4015  *
4016  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4017  * @since 1.x.x - 2007-04-24
4018  * @param object $component calendar component
4019  * @return void
4020  */
4021   function addSubComponent ( $component ) {
4022     $this->setComponent( $component );
4023   }
4024 /**
4025  * create new calendar component subcomponent, already included within component
4026  *
4027  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4028  * @since 2.6.33 - 2011-01-03
4029  * @param string $compType subcomponent type
4030  * @return object (reference)
4031  */
4032   function & newComponent( $compType ) {
4033     $config = $this->getConfig();
4034     $keys   = array_keys( $this->components );
4035     $ix     = end( $keys) + 1;
4036     switch( strtoupper( $compType )) {
4037       case 'ALARM':
4038       case 'VALARM':
4039         $this->components[$ix] = new valarm( $config );
4040         break;
4041       case 'STANDARD':
4042         array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
4043         $ix = 0;
4044         break;
4045       case 'DAYLIGHT':
4046         $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
4047         break;
4048       default:
4049         return FALSE;
4050     }
4051     return $this->components[$ix];
4052   }
4053 /**
4054  * add calendar component as subcomponent to container for subcomponents
4055  *
4056  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4057  * @since 2.8.8 - 2011-03-15
4058  * @param object $component calendar component
4059  * @param mixed $arg1 optional, ordno/component type/ component uid
4060  * @param mixed $arg2 optional, ordno if arg1 = component type
4061  * @return bool
4062  */
4063   function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
4064     if( !isset( $this->components )) return FALSE;
4065     $component->setConfig( $this->getConfig(), FALSE, TRUE );
4066     if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
4067             /* make sure dtstamp and uid is set */
4068       $dummy = $component->getProperty( 'dtstamp' );
4069       $dummy = $component->getProperty( 'uid' );
4070     }
4071     if( !$arg1 ) { // plain insert, last in chain
4072       $this->components[] = $component->copy();
4073       return TRUE;
4074     }
4075     $argType = $index = null;
4076     if ( ctype_digit( (string) $arg1 )) { // index insert/replace
4077       $argType = 'INDEX';
4078       $index   = (int) $arg1 - 1;
4079     }
4080     elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
4081       $argType = strtolower( $arg1 );
4082       $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
4083     }
4084     // else if arg1 is set, arg1 must be an UID
4085     $cix2sC = 0;
4086     foreach ( $this->components as $cix => $component2 ) {
4087       if( empty( $component2 )) continue;
4088       if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
4089         $this->components[$cix] = $component->copy();
4090         return TRUE;
4091       }
4092       elseif( $argType == $component2->objName ) { // component Type index insert/replace
4093         if( $index == $cix2sC ) {
4094           $this->components[$cix] = $component->copy();
4095           return TRUE;
4096         }
4097         $cix2sC++;
4098       }
4099       elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
4100         $this->components[$cix] = $component->copy();
4101         return TRUE;
4102       }
4103     }
4104             /* arg1=index and not found.. . insert at index .. .*/
4105     if( 'INDEX' == $argType ) {
4106       $this->components[$index] = $component->copy();
4107       ksort( $this->components, SORT_NUMERIC );
4108     }
4109     else    /* not found.. . insert last in chain anyway .. .*/
4110     $this->components[] = $component->copy();
4111     return TRUE;
4112   }
4113 /**
4114  * creates formatted output for subcomponents
4115  *
4116  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4117  * @since 2.6.27 - 2010-12-12
4118  * @return string
4119  */
4120   function createSubComponent() {
4121     $output = null;
4122     foreach( $this->components as $component ) {
4123       if( empty( $component )) continue;
4124       $component->setConfig( $this->getConfig(), FALSE, TRUE );
4125       $output .= $component->createComponent( $this->xcaldecl );
4126     }
4127     return $output;
4128   }
4129 /********************************************************************************/
4130 /**
4131  * break lines at pos 75
4132  *
4133  * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
4134  * break. Long content lines SHOULD be split into a multiple line
4135  * representations using a line "folding" technique. That is, a long
4136  * line can be split between any two characters by inserting a CRLF
4137  * immediately followed by a single linear white space character (i.e.,
4138  * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
4139  * of CRLF followed immediately by a single linear white space character
4140  * is ignored (i.e., removed) when processing the content type.
4141  *
4142  * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
4143  * the reserved expression "\n" in the arg $string could be broken up by the
4144  * folding of lines, causing ambiguity in the return string.
4145  * Fix uses var $breakAtChar=75 and breaks the line at $breakAtChar-1 if need be.
4146  *
4147  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4148  * @since 2.10.11 - 2011-09-01
4149  * @param string $value
4150  * @return string
4151  */
4152   function _size75( $string ) {
4153     $tmp        = $string;
4154     $string     = '';
4155     $eolcharlen = strlen( '\n' );
4156             /* if PHP is config with  mb_string and conf overload.. . */
4157     if( defined( 'MB_OVERLOAD_STRING' ) && ( 1 < ini_get( 'mbstring.func_overload' ))) {
4158       $strlen  = mb_strlen( $tmp );
4159       while( $strlen > 75 ) {
4160         if( '\n' == mb_substr( $tmp, 75, $eolcharlen ))
4161           $breakAtChar = 74;
4162         else
4163           $breakAtChar = 75;
4164         $string .= mb_substr( $tmp, 0, $breakAtChar );
4165         if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl ))))
4166           $string .= $this->nl;
4167         $tmp     = mb_substr( $tmp, $breakAtChar );
4168         if( !empty( $tmp ))
4169           $tmp   = ' '.$tmp;
4170         $strlen  = mb_strlen( $tmp );
4171       } // end while
4172       if( 0 < $strlen ) {
4173         $string .= $tmp; // the rest
4174         if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl ))))
4175           $string .= $this->nl;
4176       }
4177       return $string;
4178     }
4179             /* if PHP is not config with  mb_string.. . */
4180     while( TRUE ) {
4181       $bytecnt = strlen( $tmp );
4182       $charCnt = $ix = 0;
4183       for( $ix = 0; $ix < $bytecnt; $ix++ ) {
4184         if(( 73 < $charCnt ) && ( '\n' == substr( $tmp, $ix, $eolcharlen )))
4185           break;                                    // break before '\n'
4186         elseif( 74 < $charCnt ) {
4187           if( '\n' == substr( $tmp, $ix, $eolcharlen ))
4188             $ix -= 1;                               // don't break inside '\n'
4189           break;                                    // always break while-loop here
4190         }
4191         else {
4192           $byte = ord( $tmp[$ix] );
4193           if ($byte <= 127) {                       // add a one byte character
4194             $string .= substr( $tmp, $ix, 1 );
4195             $charCnt += 1;
4196           }
4197           else if ($byte >= 194 && $byte <= 223) {  // start byte in two byte character
4198             $string .= substr( $tmp, $ix, 2 );      // add a two bytes character
4199             $charCnt += 1;
4200           }
4201           else if ($byte >= 224 && $byte <= 239) {  // start byte in three bytes character
4202             $string .= substr( $tmp, $ix, 3 );      // add a three bytes character
4203             $charCnt += 1;
4204           }
4205           else if ($byte >= 240 && $byte <= 244) {  // start byte in four bytes character
4206             $string .= substr( $tmp, $ix, 4 );      // add a four bytes character
4207             $charCnt += 1;
4208           }
4209         }
4210       } // end for
4211       if( $this->nl != substr( $string, ( 0 - strlen( $this->nl ))))
4212         $string .= $this->nl;
4213       $tmp     = substr( $tmp, $ix );
4214       if( empty( $tmp ))
4215         break; // while-loop breakes here
4216       else
4217         $tmp  = ' '.$tmp;
4218     } // end while
4219     return $string;
4220   }
4221 /**
4222  * special characters management output
4223  *
4224  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4225  * @since 2.6.15 - 2010-09-24
4226  * @param string $string
4227  * @return string
4228  */
4229   function _strrep( $string ) {
4230     switch( $this->format ) {
4231       case 'xcal':
4232         $string = str_replace( '\n',  $this->nl, $string);
4233         $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
4234         break;
4235       default:
4236         $pos = 0;
4237         $specChars = array( 'n', 'N', 'r', ',', ';' );
4238         while( $pos <= strlen( $string )) {
4239           $pos = strpos( $string, "\\", $pos );
4240           if( FALSE === $pos )
4241             break;
4242           if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
4243             $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
4244             $pos += 1;
4245           }
4246           $pos += 1;
4247         }
4248         if( FALSE !== strpos( $string, '"' ))
4249           $string = str_replace('"',   "'",       $string);
4250         if( FALSE !== strpos( $string, ',' ))
4251           $string = str_replace(',',   '\,',      $string);
4252         if( FALSE !== strpos( $string, ';' ))
4253           $string = str_replace(';',   '\;',      $string);
4254
4255         if( FALSE !== strpos( $string, "\r\n" ))
4256           $string = str_replace( "\r\n", '\n',    $string);
4257         elseif( FALSE !== strpos( $string, "\r" ))
4258           $string = str_replace( "\r", '\n',      $string);
4259
4260         elseif( FALSE !== strpos( $string, "\n" ))
4261           $string = str_replace( "\n", '\n',      $string);
4262
4263         if( FALSE !== strpos( $string, '\N' ))
4264           $string = str_replace( '\N', '\n',      $string);
4265 //        if( FALSE !== strpos( $string, $this->nl ))
4266           $string = str_replace( $this->nl, '\n', $string);
4267         break;
4268     }
4269     return $string;
4270   }
4271 /**
4272  * special characters management input (from iCal file)
4273  *
4274  * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4275  * @since 2.6.22 - 2010-10-17
4276  * @param string $string
4277  * @return string
4278  */
4279   static function _strunrep( $string ) {
4280     $string = str_replace( '\\\\', '\\',     $string);
4281     $string = str_replace( '\,',   ',',      $string);
4282     $string = str_replace( '\;',   ';',      $string);
4283 //    $string = str_replace( '\n',  $this->nl, $string); // ??
4284     return $string;
4285   }
4286 }