Projet

Général

Profil

Paste
Télécharger (432 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / libraries / iCalcreator-version / iCalcreator.class.php @ 13755f8d

1
<?php
2
/*********************************************************************************/
3
/**
4
 * iCalcreator v2.18
5
 * copyright (c) 2007-2013 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
6
 * kigkonsult.se/iCalcreator/index.php
7
 * ical@kigkonsult.se
8
 *
9
 * Description:
10
 * This file is a PHP implementation of rfc2445/rfc5545.
11
 *
12
 * This library is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU Lesser General Public
14
 * License as published by the Free Software Foundation; either
15
 * version 2.1 of the License, or (at your option) any later version.
16
 *
17
 * This library is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
 * Lesser General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Lesser General Public
23
 * License along with this library; if not, write to the Free Software
24
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
 */
26
/*********************************************************************************/
27
/*********************************************************************************/
28
/*         A little setup                                                        */
29
/*********************************************************************************/
30
            /* your local language code */
31
// define( 'ICAL_LANG', 'sv' );
32
            // alt. autosetting
33
/*
34
$langstr     = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
35
$pos         = strpos( $langstr, ';' );
36
if ($pos   !== false) {
37
  $langstr   = substr( $langstr, 0, $pos );
38
  $pos       = strpos( $langstr, ',' );
39
  if ($pos !== false) {
40
    $pos     = strpos( $langstr, ',' );
41
    $langstr = substr( $langstr, 0, $pos );
42
  }
43
  define( 'ICAL_LANG', $langstr );
44
}
45
*/
46
/*********************************************************************************/
47
/*         version, do NOT remove!!                                              */
48
define( 'ICALCREATOR_VERSION', 'iCalcreator 2.18' );
49
/*********************************************************************************/
50
/*********************************************************************************/
51
/**
52
 * vcalendar class
53
 *
54
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
55
 * @since 2.9.6 - 2011-05-14
56
 */
57
class vcalendar {
58
            //  calendar property variables
59
  var $calscale;
60
  var $method;
61
  var $prodid;
62
  var $version;
63
  var $xprop;
64
            //  container for calendar components
65
  var $components;
66
            //  component config variables
67
  var $allowEmpty;
68
  var $unique_id;
69
  var $language;
70
  var $directory;
71
  var $filename;
72
  var $url;
73
  var $delimiter;
74
  var $nl;
75
  var $format;
76
  var $dtzid;
77
            //  component internal variables
78
  var $attributeDelimiter;
79
  var $valueInit;
80
            //  component xCal declaration container
81
  var $xcaldecl;
82
/**
83
 * constructor for calendar object
84
 *
85
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
86
 * @since 2.9.6 - 2011-05-14
87
 * @param array $config
88
 * @return void
89
 */
90
  function vcalendar ( $config = array()) {
91
    $this->_makeVersion();
92
    $this->calscale   = null;
93
    $this->method     = null;
94
    $this->_makeUnique_id();
95
    $this->prodid     = null;
96
    $this->xprop      = array();
97
    $this->language   = null;
98
    $this->directory  = null;
99
    $this->filename   = null;
100
    $this->url        = null;
101
    $this->dtzid      = null;
102
/**
103
 *   language = <Text identifying a language, as defined in [RFC 1766]>
104
 */
105
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
106
                                          $config['language']   = ICAL_LANG;
107
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
108
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
109
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
110
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
111
    $this->setConfig( $config );
112

    
113
    $this->xcaldecl   = array();
114
    $this->components = array();
115
  }
116
/*********************************************************************************/
117
/**
118
 * Property Name: CALSCALE
119
 */
120
/**
121
 * creates formatted output for calendar property calscale
122
 *
123
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
124
 * @since 2.10.16 - 2011-10-28
125
 * @return string
126
 */
127
  function createCalscale() {
128
    if( empty( $this->calscale )) return FALSE;
129
    switch( $this->format ) {
130
      case 'xcal':
131
        return $this->nl.' calscale="'.$this->calscale.'"';
132
        break;
133
      default:
134
        return 'CALSCALE:'.$this->calscale.$this->nl;
135
        break;
136
    }
137
  }
138
/**
139
 * set calendar property calscale
140
 *
141
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
142
 * @since 2.4.8 - 2008-10-21
143
 * @param string $value
144
 * @return void
145
 */
146
  function setCalscale( $value ) {
147
    if( empty( $value )) return FALSE;
148
    $this->calscale = $value;
149
  }
150
/*********************************************************************************/
151
/**
152
 * Property Name: METHOD
153
 */
154
/**
155
 * creates formatted output for calendar property method
156
 *
157
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
158
 * @since 2.10.16 - 2011-10-28
159
 * @return string
160
 */
161
  function createMethod() {
162
    if( empty( $this->method )) return FALSE;
163
    switch( $this->format ) {
164
      case 'xcal':
165
        return $this->nl.' method="'.$this->method.'"';
166
        break;
167
      default:
168
        return 'METHOD:'.$this->method.$this->nl;
169
        break;
170
    }
171
  }
172
/**
173
 * set calendar property method
174
 *
175
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
176
 * @since 2.4.8 - 2008-20-23
177
 * @param string $value
178
 * @return bool
179
 */
180
  function setMethod( $value ) {
181
    if( empty( $value )) return FALSE;
182
    $this->method = $value;
183
    return TRUE;
184
  }
185
/*********************************************************************************/
186
/**
187
 * Property Name: PRODID
188
 *
189
 *  The identifier is RECOMMENDED to be the identical syntax to the
190
 * [RFC 822] addr-spec. A good method to assure uniqueness is to put the
191
 * domain name or a domain literal IP address of the host on which.. .
192
 */
193
/**
194
 * creates formatted output for calendar property prodid
195
 *
196
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
197
 * @since 2.12.11 - 2012-05-13
198
 * @return string
199
 */
200
  function createProdid() {
201
    if( !isset( $this->prodid ))
202
      $this->_makeProdid();
203
    switch( $this->format ) {
204
      case 'xcal':
205
        return $this->nl.' prodid="'.$this->prodid.'"';
206
        break;
207
      default:
208
        $toolbox = new calendarComponent();
209
        $toolbox->setConfig( $this->getConfig());
210
        return $toolbox->_createElement( 'PRODID', '', $this->prodid );
211
        break;
212
    }
213
  }
214
/**
215
 * make default value for calendar prodid
216
 *
217
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
218
 * @since 2.6.8 - 2009-12-30
219
 * @return void
220
 */
221
  function _makeProdid() {
222
    $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
223
  }
224
/**
225
 * Conformance: The property MUST be specified once in an iCalendar object.
226
 * Description: The vendor of the implementation SHOULD assure that this
227
 * is a globally unique identifier; using some technique such as an FPI
228
 * value, as defined in [ISO 9070].
229
 */
230
/**
231
 * make default unique_id for calendar prodid
232
 *
233
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
234
 * @since 0.3.0 - 2006-08-10
235
 * @return void
236
 */
237
  function _makeUnique_id() {
238
    $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
239
  }
240
/*********************************************************************************/
241
/**
242
 * Property Name: VERSION
243
 *
244
 * Description: A value of "2.0" corresponds to this memo.
245
 */
246
/**
247
 * creates formatted output for calendar property version
248

249
 *
250
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
251
 * @since 2.10.16 - 2011-10-28
252
 * @return string
253
 */
254
  function createVersion() {
255
    if( empty( $this->version ))
256
      $this->_makeVersion();
257
    switch( $this->format ) {
258
      case 'xcal':
259
        return $this->nl.' version="'.$this->version.'"';
260
        break;
261
      default:
262
        return 'VERSION:'.$this->version.$this->nl;
263
        break;
264
    }
265
  }
266
/**
267
 * set default calendar version
268
 *
269
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
270
 * @since 0.3.0 - 2006-08-10
271
 * @return void
272
 */
273
  function _makeVersion() {
274
    $this->version = '2.0';
275
  }
276
/**
277
 * set calendar version
278
 *
279
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
280
 * @since 2.4.8 - 2008-10-23
281
 * @param string $value
282
 * @return void
283
 */
284
  function setVersion( $value ) {
285
    if( empty( $value )) return FALSE;
286
    $this->version = $value;
287
    return TRUE;
288
  }
289
/*********************************************************************************/
290
/**
291
 * Property Name: x-prop
292
 */
293
/**
294
 * creates formatted output for calendar property x-prop, iCal format only
295
 *
296
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
297
 * @since 2.16.21 - 2013-05-25
298
 * @return string
299
 */
300
  function createXprop() {
301
    if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE;
302
    $output        = null;
303
    $toolbox       = new calendarComponent();
304
    $toolbox->setConfig( $this->getConfig());
305
    foreach( $this->xprop as $label => $xpropPart ) {
306
      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
307
        if( $this->getConfig( 'allowEmpty' ))
308
          $output .= $toolbox->_createElement( $label );
309
        continue;
310
      }
311
      $attributes  = $toolbox->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
312
      if( is_array( $xpropPart['value'] )) {
313
        foreach( $xpropPart['value'] as $pix => $theXpart )
314
          $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->nl );
315
        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
316
      }
317
      else
318
        $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
319
      $output     .= $toolbox->_createElement( $label, $attributes, $xpropPart['value'] );
320
      if( is_array( $toolbox->xcaldecl ) && ( 0 < count( $toolbox->xcaldecl ))) {
321
        foreach( $toolbox->xcaldecl as $localxcaldecl )
322
          $this->xcaldecl[] = $localxcaldecl;
323
      }
324
    }
325
    return $output;
326
  }
327
/**
328
 * set calendar property x-prop
329
 *
330
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
331
 * @since 2.16.21 - 2013-06-23
332
 * @param string $label
333
 * @param string $value
334
 * @param array $params optional
335
 * @return bool
336
 */
337
  function setXprop( $label, $value, $params=FALSE ) {
338
    if( empty( $label ))
339
      return FALSE;
340
    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
341
      return FALSE;
342
    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
343
    $xprop           = array( 'value' => $value );
344
    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
345
    if( !is_array( $this->xprop )) $this->xprop = array();
346
    $this->xprop[strtoupper( $label )] = $xprop;
347
    return TRUE;
348
  }
349
/*********************************************************************************/
350
/**
351
 * delete calendar property value
352
 *
353
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
354
 * @since 2.8.8 - 2011-03-15
355
 * @param mixed $propName, bool FALSE => X-property
356
 * @param int $propix, optional, if specific property is wanted in case of multiply occurences
357
 * @return bool, if successfull delete
358
 */
359
  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
360
    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
361
    if( !$propix )
362
      $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
363
    $this->propdelix[$propName] = --$propix;
364
    $return = FALSE;
365
    switch( $propName ) {
366
      case 'CALSCALE':
367
        if( isset( $this->calscale )) {
368
          $this->calscale = null;
369
          $return = TRUE;
370
        }
371
        break;
372
      case 'METHOD':
373
        if( isset( $this->method )) {
374
          $this->method   = null;
375
          $return = TRUE;
376
        }
377
        break;
378
      default:
379
        $reduced = array();
380
        if( $propName != 'X-PROP' ) {
381
          if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
382
          foreach( $this->xprop as $k => $a ) {
383
            if(( $k != $propName ) && !empty( $a ))
384
              $reduced[$k] = $a;
385
          }
386
        }
387
        else {
388
          if( count( $this->xprop ) <= $propix )  return FALSE;
389
          $xpropno = 0;
390
          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
391
            if( $propix != $xpropno )
392
              $reduced[$xpropkey] = $xpropvalue;
393
            $xpropno++;
394
          }
395
        }
396
        $this->xprop = $reduced;
397
        if( empty( $this->xprop )) {
398
          unset( $this->propdelix[$propName] );
399
          return FALSE;
400
        }
401
        return TRUE;
402
    }
403
    return $return;
404
  }
405
/**
406
 * get calendar property value/params
407
 *
408
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
409
 * @since 2.13.4 - 2012-08-08
410
 * @param string $propName, optional
411
 * @param int $propix, optional, if specific property is wanted in case of multiply occurences
412
 * @param bool $inclParam=FALSE
413
 * @return mixed
414
 */
415
  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
416
    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
417
    if( 'X-PROP' == $propName ) {
418
      if( !$propix )
419
        $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
420
      $this->propix[$propName] = --$propix;
421
    }
422
    else
423
      $mProps    = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
424
    switch( $propName ) {
425
      case 'ATTENDEE':
426
      case 'CATEGORIES':
427
      case 'CONTACT':
428
      case 'DTSTART':
429
      case 'GEOLOCATION':
430
      case 'LOCATION':
431
      case 'ORGANIZER':
432
      case 'PRIORITY':
433
      case 'RESOURCES':
434
      case 'STATUS':
435
      case 'SUMMARY':
436
      case 'RECURRENCE-ID-UID':
437
      case 'RELATED-TO':
438
      case 'R-UID':
439
      case 'UID':
440
      case 'URL':
441
        $output  = array();
442
        foreach ( $this->components as $cix => $component) {
443
          if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' )))
444
            continue;
445
          if( in_array( strtoupper( $propName ), $mProps )) {
446
            $component->_getProperties( $propName, $output );
447
            continue;
448
          }
449
          elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
450
            if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
451
              $content = $component->getProperty( 'UID' );
452
          }
453
          elseif( 'GEOLOCATION' == $propName ) {
454
            $content = $component->getProperty( 'LOCATION' );
455
            $content = ( !empty( $content )) ? $content.' ' : '';
456
            if(( FALSE === ( $geo     = $component->getProperty( 'GEO' ))) || empty( $geo ))
457
              continue;
458
            if( 0.0 < $geo['latitude'] )
459
              $sign   = '+';
460
            else
461
              $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
462
            $content .= ' '.$sign.sprintf( "%09.6f", abs( $geo['latitude'] ));
463
            $content  = rtrim( rtrim( $content, '0' ), '.' );
464
            if( 0.0 < $geo['longitude'] )
465
              $sign   = '+';
466
            else
467
              $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
468
            $content .= $sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';
469
          }
470
          elseif( FALSE === ( $content = $component->getProperty( $propName )))
471
            continue;
472
          if(( FALSE === $content ) || empty( $content ))
473
            continue;
474
          elseif( is_array( $content )) {
475
            if( isset( $content['year'] )) {
476
              $key  = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] );
477
              if( !isset( $output[$key] ))
478
                $output[$key] = 1;
479
              else
480
                $output[$key] += 1;
481
            }
482
            else {
483
              foreach( $content as $partValue => $partCount ) {
484
                if( !isset( $output[$partValue] ))
485
                  $output[$partValue] = $partCount;
486
                else
487
                  $output[$partValue] += $partCount;
488
              }
489
            }
490
          } // end elseif( is_array( $content )) {
491
          elseif( !isset( $output[$content] ))
492
            $output[$content] = 1;
493
          else
494
            $output[$content] += 1;
495
        } // end foreach ( $this->components as $cix => $component)
496
        if( !empty( $output ))
497
          ksort( $output );
498
        return $output;
499
        break;
500
      case 'CALSCALE':
501
        return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
502
        break;
503
      case 'METHOD':
504
        return ( !empty( $this->method )) ? $this->method : FALSE;
505
        break;
506
      case 'PRODID':
507
        if( empty( $this->prodid ))
508
          $this->_makeProdid();
509
        return $this->prodid;
510
        break;
511
      case 'VERSION':
512
        return ( !empty( $this->version )) ? $this->version : FALSE;
513
        break;
514
      default:
515
        if( $propName != 'X-PROP' ) {
516
          if( !isset( $this->xprop[$propName] )) return FALSE;
517
          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
518
                                : array( $propName, $this->xprop[$propName]['value'] );
519
        }
520
        else {
521
          if( empty( $this->xprop )) return FALSE;
522
          $xpropno = 0;
523
          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
524
            if( $propix == $xpropno )
525
              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
526
                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
527
            else
528
              $xpropno++;
529
          }
530
          unset( $this->propix[$propName] );
531
          return FALSE; // not found ??
532
        }
533
    }
534
    return FALSE;
535
  }
536
/**
537
 * general vcalendar property setting
538
 *
539
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
540
 * @since 2.2.13 - 2007-11-04
541
 * @param mixed $args variable number of function arguments,
542
 *                    first argument is ALWAYS component name,
543
 *                    second ALWAYS component value!
544
 * @return bool
545
 */
546
  function setProperty () {
547
    $numargs    = func_num_args();
548
    if( 1 > $numargs )
549
      return FALSE;
550
    $arglist    = func_get_args();
551
    $arglist[0] = strtoupper( $arglist[0] );
552
    switch( $arglist[0] ) {
553
      case 'CALSCALE':
554
        return $this->setCalscale( $arglist[1] );
555
      case 'METHOD':
556
        return $this->setMethod( $arglist[1] );
557
      case 'VERSION':
558
        return $this->setVersion( $arglist[1] );
559
      default:
560
        if( !isset( $arglist[1] )) $arglist[1] = null;
561
        if( !isset( $arglist[2] )) $arglist[2] = null;
562
        return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
563
    }
564
    return FALSE;
565
  }
566
/*********************************************************************************/
567
/**
568
 * get vcalendar config values or * calendar components
569
 *
570
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
571
 * @since 2.11.7 - 2012-01-12
572
 * @param mixed $config
573
 * @return value
574
 */
575
  function getConfig( $config = FALSE ) {
576
    if( !$config ) {
577
      $return = array();
578
      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
579
      $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
580
      $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
581
      $return['FILENAME']    = $this->getConfig( 'FILENAME' );
582
      $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
583
      $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
584
      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
585
      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
586
        $return['LANGUAGE']  = $lang;
587
      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
588
      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
589
      if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
590
        $return['URL']       = $url;
591
      $return['TZID']        = $this->getConfig( 'TZID' );
592
      return $return;
593
    }
594
    switch( strtoupper( $config )) {
595
      case 'ALLOWEMPTY':
596
        return $this->allowEmpty;
597
        break;
598
      case 'COMPSINFO':
599
        unset( $this->compix );
600
        $info = array();
601
        foreach( $this->components as $cix => $component ) {
602
          if( empty( $component )) continue;
603
          $info[$cix]['ordno'] = $cix + 1;
604
          $info[$cix]['type']  = $component->objName;
605
          $info[$cix]['uid']   = $component->getProperty( 'uid' );
606
          $info[$cix]['props'] = $component->getConfig( 'propinfo' );
607
          $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
608
        }
609
        return $info;
610
        break;
611
      case 'DELIMITER':
612
        return $this->delimiter;
613
        break;
614
      case 'DIRECTORY':
615
        if( empty( $this->directory ) && ( '0' != $this->directory ))
616
          $this->directory = '.';
617
        return $this->directory;
618
        break;
619
      case 'DIRFILE':
620
        return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
621
        break;
622
      case 'FILEINFO':
623
        return array( $this->getConfig( 'directory' )
624
                    , $this->getConfig( 'filename' )
625
                    , $this->getConfig( 'filesize' ));
626
        break;
627
      case 'FILENAME':
628
        if( empty( $this->filename ) && ( '0' != $this->filename )) {
629
          if( 'xcal' == $this->format )
630
            $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
631
          else
632
            $this->filename = date( 'YmdHis' ).'.ics';
633
        }
634
        return $this->filename;
635
        break;
636
      case 'FILESIZE':
637
        $size    = 0;
638
        if( empty( $this->url )) {
639
          $dirfile = $this->getConfig( 'dirfile' );
640
          if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
641
            $size = 0;
642
          clearstatcache();
643
        }
644
        return $size;
645
        break;
646
      case 'FORMAT':
647
        return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
648
        break;
649
      case 'LANGUAGE':
650
         /* get language for calendar component as defined in [RFC 1766] */
651
        return $this->language;
652
        break;
653
      case 'NL':
654
      case 'NEWLINECHAR':
655
        return $this->nl;
656
        break;
657
      case 'TZID':
658
        return $this->dtzid;
659
        break;
660
      case 'UNIQUE_ID':
661
        return $this->unique_id;
662
        break;
663
      case 'URL':
664
        if( !empty( $this->url ))
665
          return $this->url;
666
        else
667
          return FALSE;
668
        break;
669
    }
670
  }
671
/**
672
 * general vcalendar config setting
673
 *
674
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
675
 * @since 2.16.7 - 2013-01-11
676
 * @param mixed  $config
677
 * @param string $value
678
 * @return void
679
 */
680
  function setConfig( $config, $value = FALSE) {
681
    if( is_array( $config )) {
682
      $ak = array_keys( $config );
683
      foreach( $ak as $k ) {
684
        if( 'DIRECTORY' == strtoupper( $k )) {
685
          if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] ))
686
            return FALSE;
687
          unset( $config[$k] );
688
        }
689
        elseif( 'NEWLINECHAR' == strtoupper( $k )) {
690
          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
691
            return FALSE;
692
          unset( $config[$k] );
693
        }
694
      }
695
      foreach( $config as $cKey => $cValue ) {
696
        if( FALSE === $this->setConfig( $cKey, $cValue ))
697
          return FALSE;
698
      }
699
      return TRUE;
700
    }
701
    $res = FALSE;
702
    switch( strtoupper( $config )) {
703
      case 'ALLOWEMPTY':
704
        $this->allowEmpty = $value;
705
        $subcfg  = array( 'ALLOWEMPTY' => $value );
706
        $res = TRUE;
707
        break;
708
      case 'DELIMITER':
709
        $this->delimiter = $value;
710
        return TRUE;
711
        break;
712
      case 'DIRECTORY':
713
        $value   = trim( $value );
714
        $del     = $this->getConfig('delimiter');
715
        if( $del == substr( $value, ( 0 - strlen( $del ))))
716
          $value = substr( $value, 0, ( strlen( $value ) - strlen( $del )));
717
        if( is_dir( $value )) {
718
            /* local directory */
719
          clearstatcache();
720
          $this->directory = $value;
721
          $this->url       = null;
722
          return TRUE;
723
        }
724
        else
725
          return FALSE;
726
        break;
727
      case 'FILENAME':
728
        $value   = trim( $value );
729
        if( !empty( $this->url )) {
730
            /* remote directory+file -> URL */
731
          $this->filename = $value;
732
          return TRUE;
733
        }
734
        $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value;
735
        if( file_exists( $dirfile )) {
736
            /* local file exists */
737
          if( is_readable( $dirfile ) || is_writable( $dirfile )) {
738
            clearstatcache();
739
            $this->filename = $value;
740
            return TRUE;
741
          }
742
          else
743
            return FALSE;
744
        }
745
        elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) {
746
            /* read- or writable directory */
747
          $this->filename = $value;
748
          return TRUE;
749
        }
750
        else
751
          return FALSE;
752
        break;
753
      case 'FORMAT':
754
        $value   = trim( strtolower( $value ));
755
        if( 'xcal' == $value ) {
756
          $this->format             = 'xcal';
757
          $this->attributeDelimiter = $this->nl;
758
          $this->valueInit          = null;
759
        }
760
        else {
761
          $this->format             = null;
762
          $this->attributeDelimiter = ';';
763
          $this->valueInit          = ':';
764
        }
765
        $subcfg  = array( 'FORMAT' => $value );
766
        $res = TRUE;
767
        break;
768
      case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
769
        $value   = trim( $value );
770
        $this->language = $value;
771
        $this->_makeProdid();
772
        $subcfg  = array( 'LANGUAGE' => $value );
773
        $res = TRUE;
774
        break;
775
      case 'NL':
776
      case 'NEWLINECHAR':
777
        $this->nl = $value;
778
        if( 'xcal' == $value ) {
779
          $this->attributeDelimiter = $this->nl;
780
          $this->valueInit          = null;
781
        }
782
        else {
783
          $this->attributeDelimiter = ';';
784
          $this->valueInit          = ':';
785
        }
786
        $subcfg  = array( 'NL' => $value );
787
        $res = TRUE;
788
        break;
789
      case 'TZID':
790
        $this->dtzid = $value;
791
        $subcfg  = array( 'TZID' => $value );
792
        $res = TRUE;
793
        break;
794
      case 'UNIQUE_ID':
795
        $value   = trim( $value );
796
        $this->unique_id = $value;
797
        $this->_makeProdid();
798
        $subcfg  = array( 'UNIQUE_ID' => $value );
799
        $res = TRUE;
800
        break;
801
      case 'URL':
802
            /* remote file - URL */
803
        $value     = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
804
        if( 'http://' != substr( $value, 0, 7 ))
805
          return FALSE;
806
        $s1        = $this->url;
807
        $this->url = $value;
808
        $s2        = $this->directory;
809
        $this->directory = null;
810
        $parts     = pathinfo( $value );
811
        if( FALSE === $this->setConfig( 'filename',  $parts['basename'] )) {
812
          $this->url       = $s1;
813
          $this->directory = $s2;
814
          return FALSE;
815
        }
816
        else
817
          return TRUE;
818
        break;
819
      default:  // any unvalid config key.. .
820
        return TRUE;
821
    }
822
    if( !$res ) return FALSE;
823
    if( isset( $subcfg ) && !empty( $this->components )) {
824
      foreach( $subcfg as $cfgkey => $cfgvalue ) {
825
        foreach( $this->components as $cix => $component ) {
826
          $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
827
          if( !$res )
828
            break 2;
829
          $this->components[$cix] = $component->copy(); // PHP4 compliant
830
        }
831
      }
832
    }
833
    return $res;
834
  }
835
/*********************************************************************************/
836
/**
837
 * add calendar component to container
838
 *
839
 * alias to setComponent
840
 *
841
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
842
 * @since 1.x.x - 2007-04-24
843
 * @param object $component calendar component
844
 * @return void
845
 */
846
  function addComponent( $component ) {
847
    $this->setComponent( $component );
848
  }
849
/**
850
 * delete calendar component from container
851
 *
852
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
853
 * @since 2.8.8 - 2011-03-15
854
 * @param mixed $arg1 ordno / component type / component uid
855
 * @param mixed $arg2 optional, ordno if arg1 = component type
856
 * @return void
857
 */
858
  function deleteComponent( $arg1, $arg2=FALSE  ) {
859
    $argType = $index = null;
860
    if ( ctype_digit( (string) $arg1 )) {
861
      $argType = 'INDEX';
862
      $index   = (int) $arg1 - 1;
863
    }
864
    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
865
      $argType = strtolower( $arg1 );
866
      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
867
    }
868
    $cix1dC = 0;
869
    foreach ( $this->components as $cix => $component) {
870
      if( empty( $component )) continue;
871
      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
872
        unset( $this->components[$cix] );
873
        return TRUE;
874
      }
875
      elseif( $argType == $component->objName ) {
876
        if( $index == $cix1dC ) {
877
          unset( $this->components[$cix] );
878
          return TRUE;
879
        }
880
        $cix1dC++;
881
      }
882
      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
883
        unset( $this->components[$cix] );
884
        return TRUE;
885
      }
886
    }
887
    return FALSE;
888
  }
889
/**
890
 * get calendar component from container
891
 *
892
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
893
 * @since 2.16.15 - 2013-04-25
894
 * @param mixed $arg1 optional, ordno/component type/ component uid
895
 * @param mixed $arg2 optional, ordno if arg1 = component type
896
 * @return object
897
 */
898
  function getComponent( $arg1=FALSE, $arg2=FALSE ) {
899
    $index = $argType = null;
900
    if ( !$arg1 ) { // first or next in component chain
901
      $argType = 'INDEX';
902
      $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
903
    }
904
    elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
905
      $arg2  = implode( '-', array_keys( $arg1 ));
906
      $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
907
      $dateProps  = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' );
908
      $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
909
      $mProps     = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' );
910
    }
911
    elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
912
      $argType = 'INDEX';
913
      $index   = (int) $arg1;
914
      unset( $this->compix );
915
    }
916
    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
917
      unset( $this->compix['INDEX'] );
918
      $argType = strtolower( $arg1 );
919
      if( !$arg2 )
920
        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
921
      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
922
        $index = (int) $arg2;
923
    }
924
    elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
925
      if( !$arg2 )
926
        $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
927
      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
928
        $index = (int) $arg2;
929
    }
930
    if( isset( $index ))
931
      $index  -= 1;
932
    $ckeys = array_keys( $this->components );
933
    if( !empty( $index) && ( $index > end(  $ckeys )))
934
      return FALSE;
935
    $cix1gC = 0;
936
    foreach ( $this->components as $cix => $component) {
937
      if( empty( $component )) continue;
938
      if(( 'INDEX' == $argType ) && ( $index == $cix ))
939
        return $component->copy();
940
      elseif( $argType == $component->objName ) {
941
        if( $index == $cix1gC )
942
          return $component->copy();
943
        $cix1gC++;
944
      }
945
      elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
946
        $hit = array();
947
        foreach( $arg1 as $pName => $pValue ) {
948
          $pName = strtoupper( $pName );
949
          if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps ))
950
            continue;
951
          if( in_array( $pName, $mProps )) { // multiple occurrence
952
            $propValues = array();
953
            $component->_getProperties( $pName, $propValues );
954
            $propValues = array_keys( $propValues );
955
            $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
956
            continue;
957
          } // end   if(.. .// multiple occurrence
958
          if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
959
            $hit[] = FALSE; // missing property
960
            continue;
961
          }
962
          if( 'SUMMARY' == $pName ) { // exists within (any case)
963
            $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
964
            continue;
965
          }
966
          if( in_array( strtoupper( $pName ), $dateProps )) {
967
            $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] );
968
            if( 8 < strlen( $pValue )) {
969
              if( isset( $value['hour'] )) {
970
                if( 'T' == substr( $pValue, 8, 1 ))
971
                  $pValue = str_replace( 'T', '', $pValue );
972
                $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] );
973
              }
974
              else
975
                $pValue = substr( $pValue, 0, 8 );
976
            }
977
            $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
978
            continue;
979
          }
980
          elseif( !is_array( $value ))
981
            $value = array( $value );
982
          foreach( $value as $part ) {
983
            $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
984
            foreach( $part as $subPart ) {
985
              if( $pValue == $subPart ) {
986
                $hit[] = TRUE;
987
                continue 3;
988
              }
989
            }
990
          } // end foreach( $value as $part )
991
          $hit[] = FALSE; // no hit in property
992
        } // end  foreach( $arg1 as $pName => $pValue )
993
        if( in_array( TRUE, $hit )) {
994
          if( $index == $cix1gC )
995
            return $component->copy();
996
          $cix1gC++;
997
        }
998
      } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
999
      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
1000
        if( $index == $cix1gC )
1001
          return $component->copy();
1002
        $cix1gC++;
1003
      }
1004
    } // end foreach ( $this->components.. .
1005
            /* not found.. . */
1006
    unset( $this->compix );
1007
    return FALSE;
1008
  }
1009
/**
1010
 * create new calendar component, already included within calendar
1011
 *
1012
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1013
 * @since 2.6.33 - 2011-01-03
1014
 * @param string $compType component type
1015
 * @return object (reference)
1016
 */
1017
  function & newComponent( $compType ) {
1018
    $config = $this->getConfig();
1019
    $keys   = array_keys( $this->components );
1020
    $ix     = end( $keys) + 1;
1021
    switch( strtoupper( $compType )) {
1022
      case 'EVENT':
1023
      case 'VEVENT':
1024
        $this->components[$ix] = new vevent( $config );
1025
        break;
1026
      case 'TODO':
1027
      case 'VTODO':
1028
        $this->components[$ix] = new vtodo( $config );
1029
        break;
1030
      case 'JOURNAL':
1031
      case 'VJOURNAL':
1032
        $this->components[$ix] = new vjournal( $config );
1033
        break;
1034
      case 'FREEBUSY':
1035
      case 'VFREEBUSY':
1036
        $this->components[$ix] = new vfreebusy( $config );
1037
        break;
1038
      case 'TIMEZONE':
1039
      case 'VTIMEZONE':
1040
        array_unshift( $this->components, new vtimezone( $config ));
1041
        $ix = 0;
1042
        break;
1043
      default:
1044
        return FALSE;
1045
    }
1046
    return $this->components[$ix];
1047
  }
1048
/**
1049
 * select components from calendar on date or selectOption basis
1050
 *
1051
 * Ensure DTSTART is set for every component.
1052
 * No date controls occurs.
1053
 *
1054
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1055
 * @since 2.16.13 - 2013-03-16
1056
 * @param mixed $startY optional, start Year,  default current Year ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
1057
 * @param int   $startM optional, start Month, default current Month
1058
 * @param int   $startD optional, start Day,   default current Day
1059
 * @param int   $endY   optional, end   Year,  default $startY
1060
 * @param int   $endY   optional, end   Month, default $startM
1061
 * @param int   $endY   optional, end   Day,   default $startD
1062
 * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
1063
 * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
1064
 *                                TRUE            => output : array[] (ignores split)
1065
 * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
1066
 *                                FALSE          - only component(-s) that starts within period
1067
 * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
1068
 *                                                 period (implies flat=FALSE)
1069
 *                                FALSE          - one occurance of component only in output array
1070
 * @return array or FALSE
1071
 */
1072
  function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
1073
            /* check  if empty calendar */
1074
    if( 0 >= count( $this->components )) return FALSE;
1075
    if( is_array( $startY ))
1076
      return $this->selectComponents2( $startY );
1077
            /* check default dates */
1078
    if( !$startY ) $startY = date( 'Y' );
1079
    if( !$startM ) $startM = date( 'm' );
1080
    if( !$startD ) $startD = date( 'd' );
1081
    $startDate = mktime( 0, 0, 0, $startM, $startD, $startY );
1082
    if( !$endY )   $endY   = $startY;
1083
    if( !$endM )   $endM   = $startM;
1084
    if( !$endD )   $endD   = $startD;
1085
    $endDate   = mktime( 23, 59, 59, $endM, $endD, $endY );
1086
// echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."<br>\n"; $tcnt = 0;// test ###
1087
            /* check component types */
1088
    $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
1089
    if( is_array( $cType )) {
1090
      foreach( $cType as $cix => $theType ) {
1091
        $cType[$cix] = $theType = strtolower( $theType );
1092
        if( !in_array( $theType, $validTypes ))
1093
          $cType[$cix] = 'vevent';
1094
      }
1095
      $cType = array_unique( $cType );
1096
    }
1097
    elseif( !empty( $cType )) {
1098
      $cType = strtolower( $cType );
1099
      if( !in_array( $cType, $validTypes ))
1100
        $cType = array( 'vevent' );
1101
      else
1102
        $cType = array( $cType );
1103
    }
1104
    else
1105
      $cType = $validTypes;
1106
    if( 0 >= count( $cType ))
1107
      $cType = $validTypes;
1108
    if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
1109
      $split = FALSE;
1110
    if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
1111
      $split = FALSE;
1112
            /* iterate components */
1113
    $result       = array();
1114
    $this->sort( 'UID' );
1115
    $compUIDcmp   = null;
1116
    $recurridList = array();
1117
    foreach ( $this->components as $cix => $component ) {
1118
      if( empty( $component )) continue;
1119
      unset( $start );
1120
            /* deselect unvalid type components */
1121
      if( !in_array( $component->objName, $cType ))
1122
        continue;
1123
      $start = $component->getProperty( 'dtstart' );
1124
            /* select due when dtstart is missing */
1125
      if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' ))))
1126
        continue;
1127
      if( empty( $start ))
1128
        continue;
1129
      $compUID      = $component->getProperty( 'UID' );
1130
      if( $compUIDcmp != $compUID ) {
1131
        $compUIDcmp = $compUID;
1132
        unset( $exdatelist, $recurridList );
1133
      }
1134
      $dtendExist = $dueExist = $durationExist = $endAllDayEvent = $recurrid = FALSE;
1135
      unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $workstart, $workend, $endDateFormat ); // clean up
1136
      $startWdate = iCalUtilityFunctions::_date2timestamp( $start );
1137
      $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1138
            /* get end date from dtend/due/duration properties */
1139
      $end = $component->getProperty( 'dtend' );
1140
      if( !empty( $end )) {
1141
        $dtendExist = TRUE;
1142
        $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1143
      }
1144
      if( empty( $end ) && ( $component->objName == 'vtodo' )) {
1145
        $end = $component->getProperty( 'due' );
1146
        if( !empty( $end )) {
1147
          $dueExist = TRUE;
1148
          $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1149
        }
1150
      }
1151
      if( !empty( $end ) && !isset( $end['hour'] )) {
1152
          /* a DTEND without time part regards an event that ends the day before,
1153
             for an all-day event DTSTART=20071201 DTEND=20071202 (taking place 20071201!!! */
1154
        $endAllDayEvent = TRUE;
1155
        $endWdate = mktime( 23, 59, 59, $end['month'], ($end['day'] - 1), $end['year'] );
1156
        $end['year']  = date( 'Y', $endWdate );
1157
        $end['month'] = date( 'm', $endWdate );
1158
        $end['day']   = date( 'd', $endWdate );
1159
        $end['hour']  = 23;
1160
        $end['min']   = $end['sec'] = 59;
1161
      }
1162
      if( empty( $end )) {
1163
        $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format
1164
        if( !empty( $end ))
1165
          $durationExist = TRUE;
1166
          $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d';
1167
// if( !empty($end))  echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."<br>\n"; // test ###
1168
      }
1169
      if( empty( $end )) { // assume one day duration if missing end date
1170
        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
1171
      }
1172
// if( isset($end))  echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."<br>\n"; // test ###
1173
      $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
1174
      if( $endWdate < $startWdate ) { // MUST be after start date!!
1175
        $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 );
1176
        $endWdate = iCalUtilityFunctions::_date2timestamp( $end );
1177
      }
1178
      $rdurWsecs  = $endWdate - $startWdate; // compute event (component) duration in seconds
1179
            /* make a list of optional exclude dates for component occurence from exrule and exdate */
1180
      $exdatelist = array();
1181
      $workstart  = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6);
1182
      $workend    = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6);
1183
      while( FALSE !== ( $exrule = $component->getProperty( 'exrule' )))    // check exrule
1184
        iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend );
1185
      while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) {  // check exdate
1186
        foreach( $exdate as $theExdate ) {
1187
          $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate );
1188
          $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate )); // on a day-basis !!!
1189
          if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate ))
1190
            $exdatelist[$exWdate] = TRUE;
1191
        } // end - foreach( $exdate as $theExdate )
1192
      }  // end - check exdate
1193
            /* check recurrence-id (note, a missing sequence is the same as sequence=0 so don't test for sequence), remove hit with reccurr-id date */
1194
      if( FALSE !== ( $t = $recurrid = $component->getProperty( 'recurrence-id' ))) {
1195
        $recurrid = iCalUtilityFunctions::_date2timestamp( $recurrid );
1196
        $recurrid = mktime( 0, 0, 0, date( 'm', $recurrid ), date( 'd', $recurrid ), date( 'Y', $recurrid )); // on a day-basis !!!
1197
        $recurridList[$recurrid] = TRUE;                                             // no recurring to start this day
1198
// echo "adding comp no:$cix with date=".implode($start)." and recurrid=".implode($t)." to recurridList id=$recurrid<br>\n"; // test ###
1199
      } // end recurrence-id/sequence test
1200
            /* select only components with.. . */
1201
      if(( !$any && ( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) || // (dt)start within the period
1202
         (  $any && ( $startWdate < $endDate ) && ( $endWdate >= $startDate ))) {    // occurs within the period
1203
            /* add the selected component (WITHIN valid dates) to output array */
1204
        if( $flat ) { // any=true/false, ignores split
1205
          if( !$recurrid )
1206
            $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
1207
        }
1208
        elseif( $split ) { // split the original component
1209
          if( $endWdate > $endDate )
1210
            $endWdate = $endDate;     // use period end date
1211
          $rstart   = $startWdate;
1212
          if( $rstart < $startDate )
1213
            $rstart = $startDate; // use period start date
1214
          $startYMD = $rstartYMD = date( 'Ymd', $rstart );
1215
          $endYMD   = date( 'Ymd', $endWdate );
1216
          $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1217
// echo "going to test comp no:$cix with rstartYMD=$rstartYMD, endYMD=$endYMD and checkDate($checkDate) with recurridList=".implode(',',array_keys($recurridList))."<br>\n"; // test ###
1218
          if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist
1219
            while( $rstartYMD <= $endYMD ) { // iterate
1220
              if( isset( $exdatelist[$checkDate] ) ||                   // exclude any recurrence date, found in the exdatelist
1221
                ( isset( $recurridList[$checkDate] ) && !$recurrid )) { // or in the recurridList, but not itself
1222
// echo "skipping comp no:$cix with datestart=$rstartYMD and checkdate=$checkDate<br>\n"; // test ###
1223
                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1224
                $rstartYMD = date( 'Ymd', $rstart );
1225
                continue;
1226
              }
1227
              if( $rstartYMD > $startYMD ) // date after dtstart
1228
                $datestring = date( $startDateFormat, $checkDate ); // mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )));
1229
              else
1230
                $datestring = date( $startDateFormat, $rstart );
1231
              if( isset( $start['tz'] ))
1232
                $datestring .= ' '.$start['tz'];
1233
// echo "split org comp no:$cix rstartYMD=$rstartYMD (datestring=$datestring)<br>\n"; // test ###
1234
              $component->setProperty( 'X-CURRENT-DTSTART', $datestring );
1235
              if( $dtendExist || $dueExist || $durationExist ) {
1236
                if( $rstartYMD < $endYMD ) // not the last day
1237
                  $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1238
                else
1239
                  $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1240
                if( $endAllDayEvent && $dtendExist )
1241
                  $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1242
                $datestring = date( $endDateFormat, $tend );
1243
                if( isset( $end['tz'] ))
1244
                  $datestring .= ' '.$end['tz'];
1245
                $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1246
                $component->setProperty( $propName, $datestring );
1247
              } // end if( $dtendExist || $dueExist || $durationExist )
1248
              $wd        = getdate( $rstart );
1249
              $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
1250
              $rstart    = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1251
              $rstartYMD = date( 'Ymd', $rstart );
1252
              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1253
            } // end while( $rstart <= $endWdate )
1254
          } // end if( !isset( $exdatelist[$checkDate] ))
1255
        } // end elseif( $split )   -  else use component date
1256
        elseif( $recurrid && !$flat && !$any && !$split )
1257
          $continue = TRUE;
1258
        else { // !$flat && !$split, i.e. no flat array and DTSTART within period
1259
          $checkDate = mktime( 0, 0, 0, date( 'm', $startWdate ), date( 'd', $startWdate ), date( 'Y', $startWdate ) ); // on a day-basis !!!
1260
// echo "going to test comp no:$cix with checkDate=$checkDate with recurridList=".implode(',',array_keys($recurridList)); // test ###
1261
          if(( !$any || !isset( $exdatelist[$checkDate] )) &&   // exclude any recurrence date, found in exdatelist
1262
              ( !isset( $recurridList[$checkDate] ) || $recurrid )) { // or in the recurridList, but not itself
1263
// echo " and copied to output<br>\n"; // test ###
1264
            $wd = getdate( $startWdate );
1265
            $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component->copy(); // copy to output
1266
          }
1267
        }
1268
      } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate ))
1269
            /* if 'any' components, check components with reccurrence rules, removing all excluding dates */
1270
      if( TRUE === $any ) {
1271
            /* make a list of optional repeating dates for component occurence, rrule, rdate */
1272
        $recurlist = array();
1273
        while( FALSE !== ( $rrule = $component->getProperty( 'rrule' )))    // check rrule
1274
          iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend );
1275
        foreach( $recurlist as $recurkey => $recurvalue )                   // key=match date as timestamp
1276
          $recurlist[$recurkey] = $rdurWsecs;                               // add duration in seconds
1277
        while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) {  // check rdate
1278
          foreach( $rdate as $theRdate ) {
1279
            if( is_array( $theRdate ) && ( 2 == count( $theRdate )) &&      // all days within PERIOD
1280
                   array_key_exists( '0', $theRdate ) &&  array_key_exists( '1', $theRdate )) {
1281
              $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] );
1282
              if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate ))
1283
                continue;
1284
              if( isset( $theRdate[1]['year'] )) // date-date period
1285
                $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] );
1286
              else {                             // date-duration period
1287
                $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] );
1288
                $rend = iCalUtilityFunctions::_date2timestamp( $rend );
1289
              }
1290
              while( $rstart < $rend ) {
1291
                $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds
1292
                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1293
              }
1294
            } // PERIOD end
1295
            else { // single date
1296
              $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate );
1297
              if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate ))
1298
                $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds
1299
            }
1300
          }
1301
        }  // end - check rdate
1302
        foreach( $recurlist as $recurkey => $durvalue ) { // remove all recurrence START dates found in the exdatelist
1303
          $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
1304
          if( isset( $exdatelist[$checkDate] )) // no recurring to start this day
1305
            unset( $recurlist[$recurkey] );
1306
        }
1307
        if( 0 < count( $recurlist )) {
1308
          ksort( $recurlist );
1309
          $xRecurrence = 1;
1310
          $component2  = $component->copy();
1311
          $compUID     = $component2->getProperty( 'UID' );
1312
          foreach( $recurlist as $recurkey => $durvalue ) {
1313
// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."<br>\n"; // test ###;
1314
            if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period
1315
              continue;
1316
            $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!!
1317
            if( isset( $recurridList[$checkDate] )) // no recurring to start this day
1318
              continue;
1319
            if( isset( $exdatelist[$checkDate] ))   // check excluded dates
1320
              continue;
1321
            if( $startWdate >= $recurkey )          // exclude component start date
1322
              continue;
1323
            $rstart = $recurkey;
1324
            $rend   = $recurkey + $durvalue;
1325
           /* add repeating components within valid dates to output array, only start date set */
1326
            if( $flat ) {
1327
              if( !isset( $result[$compUID] )) // only one comp
1328
                $result[$compUID] = $component2->copy(); // copy to output
1329
            }
1330
           /* add repeating components within valid dates to output array, one each day */
1331
            elseif( $split ) {
1332
              $xRecurrence += 1;
1333
              if( $rend > $endDate )
1334
                $rend = $endDate;
1335
              $startYMD = $rstartYMD = date( 'Ymd', $rstart );
1336
              $endYMD   = date( 'Ymd', $rend );
1337
// echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."<br>\n"; // test ###;
1338
              while( $rstart <= $rend ) { // iterate.. .
1339
                $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1340
                if( isset( $recurridList[$checkDate] )) // no recurring to start this day
1341
                  break;
1342
                if( isset( $exdatelist[$checkDate] ))   // exclude any recurrence START date, found in exdatelist
1343
                  break;
1344
// echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."<br>"; // test ###;
1345
                if( $rstart >= $startDate ) {           // date after dtstart
1346
                  if( $rstartYMD > $startYMD )          // date after dtstart
1347
                    $datestring = date( $startDateFormat, $checkDate );
1348
                  else
1349
                    $datestring = date( $startDateFormat, $rstart );
1350
                  if( isset( $start['tz'] ))
1351
                    $datestring .= ' '.$start['tz'];
1352
// echo "spliting = $datestring<BR>\n"; // test ###
1353
                  $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1354
                  if( $dtendExist || $dueExist || $durationExist ) {
1355
                    if( $rstartYMD < $endYMD ) // not the last day
1356
                      $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ));
1357
                    else
1358
                      $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1359
                    if( $endAllDayEvent && $dtendExist )
1360
                      $tend += ( 24 * 3600 );           // alldaysevents has an end date 'day after' meaning this day
1361
                    $datestring = date( $endDateFormat, $tend );
1362
                    if( isset( $end['tz'] ))
1363
                      $datestring .= ' '.$end['tz'];
1364
                    $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1365
                    $component2->setProperty( $propName, $datestring );
1366
                  } // end if( $dtendExist || $dueExist || $durationExist )
1367
                  $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1368
                  $wd = getdate( $rstart );
1369
                  $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
1370
                } // end if( $checkDate > $startYMD ) { // date after dtstart
1371
                $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day
1372
                $rstartYMD = date( 'Ymd', $rstart );
1373
              } // end while( $rstart <= $rend )
1374
            } // end elseif( $split )
1375
            elseif( $rstart >= $startDate ) {           // date within period   //* flat=FALSE && split=FALSE => one comp every recur startdate *//
1376
              $xRecurrence += 1;
1377
              $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!!
1378
              if( !isset( $exdatelist[$checkDate] )) {  // exclude any recurrence START date, found in exdatelist
1379
                $datestring = date( $startDateFormat, $rstart );
1380
                if( isset( $start['tz'] ))
1381
                  $datestring .= ' '.$start['tz'];
1382
//echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."<br>";$component2->setProperty( 'X-CNT', $tcnt ); // test ###
1383
                $component2->setProperty( 'X-CURRENT-DTSTART', $datestring );
1384
                if( $dtendExist || $dueExist || $durationExist ) {
1385
                  $tend = $rstart + $rdurWsecs;
1386
                  if( date( 'Ymd', $tend ) < date( 'Ymd', $endWdate ))
1387
                    $tend = mktime( 23, 59, 59, date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ));
1388
                  else
1389
                    $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $tend ), date( 'd', $tend ), date( 'Y', $tend ) ); // on a day-basis !!!
1390
                  if( $endAllDayEvent && $dtendExist )
1391
                    $tend += ( 24 * 3600 ); // alldaysevents has an end date 'day after' meaning this day
1392
                  $datestring = date( $endDateFormat, $tend );
1393
                  if( isset( $end['tz'] ))
1394
                    $datestring .= ' '.$end['tz'];
1395
                  $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE';
1396
                  $component2->setProperty( $propName, $datestring );
1397
                } // end if( $dtendExist || $dueExist || $durationExist )
1398
                $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1399
                $wd = getdate( $rstart );
1400
                $result[$wd['year']][$wd['mon']][$wd['mday']][$compUID] = $component2->copy(); // copy to output
1401
              } // end if( !isset( $exdatelist[$checkDate] ))
1402
            } // end elseif( $rstart >= $startDate )
1403
          } // end foreach( $recurlist as $recurkey => $durvalue )
1404
          unset( $component2 );
1405
        } // end if( 0 < count( $recurlist ))
1406
            /* deselect components with startdate/enddate not within period */
1407
        if(( $endWdate < $startDate ) || ( $startWdate > $endDate ))
1408
          continue;
1409
      } // end if( TRUE === $any )
1410
    } // end foreach ( $this->components as $cix => $component )
1411
    unset( $dtendExist, $dueExist, $durationExist, $endAllDayEvent, $recurrid, $recurridList,
1412
           $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $recurlist, $workstart, $workend, $endDateFormat ); // clean up
1413
    if( 0 >= count( $result )) return FALSE;
1414
    elseif( !$flat ) {
1415
      foreach( $result as $y => $yeararr ) {
1416
        foreach( $yeararr as $m => $montharr ) {
1417
          foreach( $montharr as $d => $dayarr ) {
1418
            if( empty( $result[$y][$m][$d] ))
1419
                unset( $result[$y][$m][$d] );
1420
            else
1421
              $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. .
1422
          }
1423
          if( empty( $result[$y][$m] ))
1424
              unset( $result[$y][$m] );
1425
          else
1426
            ksort( $result[$y][$m] );
1427
        }
1428
        if( empty( $result[$y] ))
1429
            unset( $result[$y] );
1430
        else
1431
          ksort( $result[$y] );
1432
      }
1433
      if( empty( $result ))
1434
          unset( $result );
1435
      else
1436
        ksort( $result );
1437
    } // end elseif( !$flat )
1438
    if( 0 >= count( $result ))
1439
      return FALSE;
1440
    return $result;
1441
  }
1442
/**
1443
 * select components from calendar on based on specific property value(-s)
1444
 *
1445
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1446
 * @since 2.16.6 - 2012-12-26
1447
 * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName)
1448
 * @return array
1449
 */
1450
  function selectComponents2( $selectOptions ) {
1451
    $output = array();
1452
    $allowedComps      = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' );
1453
    $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' );
1454
    foreach( $this->components as $cix => $component3 ) {
1455
      if( !in_array( $component3->objName, $allowedComps ))
1456
        continue;
1457
      $uid = $component3->getProperty( 'UID' );
1458
      foreach( $selectOptions as $propName => $pvalue ) {
1459
        $propName = strtoupper( $propName );
1460
        if( !in_array( $propName, $allowedProperties ))
1461
          continue;
1462
        if( !is_array( $pvalue ))
1463
          $pvalue = array( $pvalue );
1464
        if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
1465
          $output[$uid][] = $component3->copy();
1466
          continue;
1467
        }
1468
        elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'CONTACT' == $propName ) || ( 'RELATED-TO' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple occurrence?
1469
          $propValues = array();
1470
          $component3->_getProperties( $propName, $propValues );
1471
          $propValues = array_keys( $propValues );
1472
          foreach( $pvalue as $theValue ) {
1473
            if( in_array( $theValue, $propValues )) { //  && !isset( $output[$uid] )) {
1474
              $output[$uid][] = $component3->copy();
1475
              break;
1476
            }
1477
          }
1478
          continue;
1479
        } // end   elseif( // multiple occurrence?
1480
        elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
1481
          continue;
1482
        if( is_array( $d )) {
1483
          foreach( $d as $part ) {
1484
            if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
1485
              $output[$uid][] = $component3->copy();
1486
          }
1487
        }
1488
        elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
1489
          foreach( $pvalue as $pval ) {
1490
            if( FALSE !== stripos( $d, $pval )) {
1491
              $output[$uid][] = $component3->copy();
1492
              break;
1493
            }
1494
          }
1495
        }
1496
        elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
1497
          $output[$uid][] = $component3->copy();
1498
      } // end foreach( $selectOptions as $propName => $pvalue ) {
1499
    } // end foreach( $this->components as $cix => $component3 ) {
1500
    if( !empty( $output )) {
1501
      ksort( $output ); // uid order
1502
      $output2 = array();
1503
      foreach( $output as $uid => $components ) {
1504
        foreach( $components as $component )
1505
          $output2[] = $component;
1506
      }
1507
      $output = $output2;
1508
    }
1509
    return $output;
1510
  }
1511
/**
1512
 * add calendar component to container
1513
 *
1514
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1515
 * @since 2.8.8 - 2011-03-15
1516
 * @param object $component calendar component
1517
 * @param mixed $arg1 optional, ordno/component type/ component uid
1518
 * @param mixed $arg2 optional, ordno if arg1 = component type
1519
 * @return void
1520
 */
1521
  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
1522
    $component->setConfig( $this->getConfig(), FALSE, TRUE );
1523
    if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
1524
            /* make sure dtstamp and uid is set */
1525
      $dummy1 = $component->getProperty( 'dtstamp' );
1526
      $dummy2 = $component->getProperty( 'uid' );
1527
    }
1528
    if( !$arg1 ) { // plain insert, last in chain
1529
      $this->components[] = $component->copy();
1530
      return TRUE;
1531
    }
1532
    $argType = $index = null;
1533
    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
1534
      $argType = 'INDEX';
1535
      $index   = (int) $arg1 - 1;
1536
    }
1537
    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
1538
      $argType = strtolower( $arg1 );
1539
      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
1540
    }
1541
    // else if arg1 is set, arg1 must be an UID
1542
    $cix1sC = 0;
1543
    foreach ( $this->components as $cix => $component2) {
1544
      if( empty( $component2 )) continue;
1545
      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
1546
        $this->components[$cix] = $component->copy();
1547
        return TRUE;
1548
      }
1549
      elseif( $argType == $component2->objName ) { // component Type index insert/replace
1550
        if( $index == $cix1sC ) {
1551
          $this->components[$cix] = $component->copy();
1552
          return TRUE;
1553
        }
1554
        $cix1sC++;
1555
      }
1556
      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
1557
        $this->components[$cix] = $component->copy();
1558
        return TRUE;
1559
      }
1560
    }
1561
            /* arg1=index and not found.. . insert at index .. .*/
1562
    if( 'INDEX' == $argType ) {
1563
      $this->components[$index] = $component->copy();
1564
      ksort( $this->components, SORT_NUMERIC );
1565
    }
1566
    else    /* not found.. . insert last in chain anyway .. .*/
1567
      $this->components[] = $component->copy();
1568
    return TRUE;
1569
  }
1570
/**
1571
 * sort iCal compoments
1572
 *
1573
 * ascending sort on properties (if exist) x-current-dtstart, dtstart,
1574
 * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
1575
 * otherwise sorting on specific (argument) property values
1576
 *
1577
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1578
 * @since 2.16.4 - 2012-12-17
1579
 * @param string $sortArg, optional
1580
 * @return void
1581
 *
1582
 */
1583
  function sort( $sortArg=FALSE ) {
1584
    if( ! is_array( $this->components ) || ( 2 > count( $this->components )))
1585
      return;
1586
    if( $sortArg ) {
1587
      $sortArg = strtoupper( $sortArg );
1588
      if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RELATED-TO', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID', 'URL' )))
1589
        $sortArg = FALSE;
1590
    }
1591
            /* set sort parameters for each component */
1592
    foreach( $this->components as $cix => & $c ) {
1593
      $c->srtk = array( '0', '0', '0', '0' );
1594
      if( 'vtimezone' == $c->objName ) {
1595
        if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' )))
1596
          $c->srtk[0] = 0;
1597
        continue;
1598
      }
1599
      elseif( $sortArg ) {
1600
        if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'CONTACT' == $sortArg ) || ( 'RELATED-TO' == $sortArg ) || ( 'RESOURCES' == $sortArg )) {
1601
          $propValues = array();
1602
          $c->_getProperties( $sortArg, $propValues );
1603
          if( !empty( $propValues )) {
1604
            $sk         = array_keys( $propValues );
1605
            $c->srtk[0] = $sk[0];
1606
            if( 'RELATED-TO'  == $sortArg )
1607
              $c->srtk[0] .= $c->getProperty( 'uid' );
1608
          }
1609
          elseif( 'RELATED-TO'  == $sortArg )
1610
            $c->srtk[0] = $c->getProperty( 'uid' );
1611
        }
1612
        elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) {
1613
          $c->srtk[0] = $d;
1614
          if( 'UID' == $sortArg ) {
1615
            if( FALSE !== ( $d = $c->getProperty( 'recurrence-id' ))) {
1616
              $c->srtk[1] = iCalUtilityFunctions::_date2strdate( $d );
1617
              if( FALSE === ( $c->srtk[2] = $c->getProperty( 'sequence' )))
1618
                $c->srtk[2] = PHP_INT_MAX;
1619
            }
1620
            else
1621
              $c->srtk[1] = $c->srtk[2] = PHP_INT_MAX;
1622
          }
1623
        }
1624
        continue;
1625
      } // end elseif( $sortArg )
1626
      if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) {
1627
        $c->srtk[0] = iCalUtilityFunctions::_strdate2date( $d[1] );
1628
        unset( $c->srtk[0]['unparsedtext'] );
1629
      }
1630
      elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' )))
1631
        $c->srtk[0] = 0;                                                  // sortkey 0 : dtstart
1632

    
1633
      if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) {
1634
        $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );   // sortkey 1 : dtend/due(/duration)
1635
        unset( $c->srtk[1]['unparsedtext'] );
1636
      }
1637
      elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) {
1638
        if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) {
1639
          $c->srtk[1] = iCalUtilityFunctions::_strdate2date( $d[1] );
1640
          unset( $c->srtk[1]['unparsedtext'] );
1641
        }
1642
        elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' )))
1643
          if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE )))
1644
            $c->srtk[1] = 0;
1645
      }
1646

    
1647
      if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' )))      // sortkey 2 : created/dtstamp
1648
        if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' )))
1649
          $c->srtk[2] = 0;
1650

    
1651
      if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' )))          // sortkey 3 : uid
1652
        $c->srtk[3] = 0;
1653
    } // end foreach( $this->components as & $c
1654
            /* sort */
1655
    usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
1656
  }
1657
/**
1658
 * parse iCal text/file into vcalendar, components, properties and parameters
1659
 *
1660
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1661
 * @since 2.16.2 - 2012-12-18
1662
 * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings
1663
 * @return bool FALSE if error occurs during parsing
1664
 *
1665
 */
1666
  function parse( $unparsedtext=FALSE ) {
1667
    $nl = $this->getConfig( 'nl' );
1668
    if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
1669
            /* directory+filename is set previously via setConfig directory+filename or url */
1670
      if( FALSE === ( $filename = $this->getConfig( 'url' )))
1671
        $filename = $this->getConfig( 'dirfile' );
1672
            /* READ FILE */
1673
      if( FALSE === ( $rows = file_get_contents( $filename )))
1674
        return FALSE;                 /* err 1 */
1675
    }
1676
    elseif( is_array( $unparsedtext ))
1677
      $rows =  implode( '\n'.$nl, $unparsedtext );
1678
    else
1679
      $rows = & $unparsedtext;
1680
            /* fix line folding */
1681
    $rows = explode( $nl, iCalUtilityFunctions::convEolChar( $rows, $nl ));
1682
            /* skip leading (empty/invalid) lines */
1683
    foreach( $rows as $lix => $line ) {
1684
      if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' ))
1685
        break;
1686
      unset( $rows[$lix] );
1687
    }
1688
    $rcnt = count( $rows );
1689
    if( 3 > $rcnt )                  /* err 10 */
1690
      return FALSE;
1691
            /* skip trailing empty lines and ensure an end row */
1692
    $lix  = array_keys( $rows );
1693
    $lix  = end( $lix );
1694
    while( 3 < $lix ) {
1695
      $tst = trim( $rows[$lix] );
1696
      if(( '\n' == $tst ) || empty( $tst )) {
1697
        unset( $rows[$lix] );
1698
        $lix--;
1699
        continue;
1700
      }
1701
      if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
1702
        $rows[] = 'END:VCALENDAR';
1703
      break;
1704
    }
1705
    $comp    = & $this;
1706
    $calsync = $compsync = 0;
1707
            /* identify components and update unparsed data within component */
1708
    $config = $this->getConfig();
1709
    $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
1710
    foreach( $rows as $lix => $line ) {
1711
      if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
1712
        $calsync++;
1713
        continue;
1714
      }
1715
      elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
1716
        if( 0 < $compsync )
1717
          $this->components[] = $comp->copy();
1718
        $compsync--;
1719
        $calsync--;
1720
        break;
1721
      }
1722
      elseif( 1 != $calsync )
1723
        return FALSE;                 /* err 20 */
1724
      elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
1725
        $this->components[] = $comp->copy();
1726
        $compsync--;
1727
        continue;
1728
      }
1729
      if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) {
1730
        $comp = new vevent( $config );
1731
        $compsync++;
1732
      }
1733
      elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
1734
        $comp = new vfreebusy( $config );
1735
        $compsync++;
1736
      }
1737
      elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) {
1738
        $comp = new vjournal( $config );
1739
        $compsync++;
1740
      }
1741
      elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) {
1742
        $comp = new vtodo( $config );
1743
        $compsync++;
1744
      }
1745
      elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
1746
        $comp = new vtimezone( $config );
1747
        $compsync++;
1748
      }
1749
      else { /* update component with unparsed data */
1750
        $comp->unparsed[] = $line;
1751
      }
1752
    } // end foreach( $rows as $line )
1753
    unset( $config, $endtxt );
1754
            /* parse data for calendar (this) object */
1755
    if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
1756
            /* concatenate property values spread over several lines */
1757
      $propnames = array( 'calscale','method','prodid','version','x-' );
1758
      $proprows  = array();
1759
      for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
1760
        $line = rtrim( $this->unparsed[$i], $nl );
1761
        while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
1762
          $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
1763
        $proprows[] = $line;
1764
      }
1765
      $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
1766
      $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
1767
      $paramProto4 = array( 'crid:', 'news:', 'pres:' );
1768
      foreach( $proprows as $line ) {
1769
        if( '\n' == substr( $line, -2 ))
1770
          $line = substr( $line, 0, -2 );
1771
            /* get property name */
1772
        $propname  = '';
1773
        $cix       = 0;
1774
        while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
1775
          if( in_array( $char, array( ':', ';' )))
1776
            break;
1777
          else
1778
            $propname .= $char;
1779
          $cix++;
1780
        }
1781
            /* skip non standard property names */
1782
        if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
1783
          continue;
1784
            /* ignore version/prodid properties */
1785
        if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
1786
          continue;
1787
            /* rest of the line is opt.params and value */
1788
        $line = substr( $line, $cix);
1789
            /* separate attributes from value */
1790
        $attr         = array();
1791
        $attrix       = -1;
1792
        $strlen       = strlen( $line );
1793
        $WithinQuotes = FALSE;
1794
        $cix          = 0;
1795
        while( FALSE !== substr( $line, $cix, 1 )) {
1796
          if(                       ( ':'  == $line[$cix] )                         &&
1797
                                    ( substr( $line,$cix,     3 )  != '://' )       &&
1798
             ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
1799
             ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
1800
             ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
1801
                        ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
1802
               !$WithinQuotes ) {
1803
            $attrEnd = TRUE;
1804
            if(( $cix < ( $strlen - 4 )) &&
1805
                 ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
1806
              for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
1807
                if( '://' == substr( $line, $c2ix - 2, 3 )) {
1808
                  $attrEnd = FALSE;
1809
                  break; // an URI with a portnr!!
1810
                }
1811
              }
1812
            }
1813
            if( $attrEnd) {
1814
              $line = substr( $line, ( $cix + 1 ));
1815
              break;
1816
            }
1817
          }
1818
          if( '"' == $line[$cix] )
1819
            $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
1820
          if( ';' == $line[$cix] )
1821
            $attr[++$attrix] = null;
1822
          else
1823
            $attr[$attrix] .= $line[$cix];
1824
          $cix++;
1825
        }
1826
            /* make attributes in array format */
1827
        $propattr = array();
1828
        foreach( $attr as $attribute ) {
1829
          $attrsplit = explode( '=', $attribute, 2 );
1830
          if( 1 < count( $attrsplit ))
1831
            $propattr[$attrsplit[0]] = $attrsplit[1];
1832
          else
1833
            $propattr[] = $attribute;
1834
        }
1835
            /* update Property */
1836
        if( FALSE !== strpos( $line, ',' )) {
1837
          $content  = array( 0 => '' );
1838
          $cix = $lix = 0;
1839
          while( FALSE !== substr( $line, $lix, 1 )) {
1840
            if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
1841
              $cix++;
1842
              $content[$cix] = '';
1843
            }
1844
            else
1845
              $content[$cix] .= $line[$lix];
1846
            $lix++;
1847
          }
1848
          if( 1 < count( $content )) {
1849
            foreach( $content as $cix => $contentPart )
1850
              $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
1851
            $this->setProperty( $propname, $content, $propattr );
1852
            continue;
1853
          }
1854
          else
1855
            $line = reset( $content );
1856
          $line = iCalUtilityFunctions::_strunrep( $line );
1857
        }
1858
        $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propattr );
1859
      } // end - foreach( $this->unparsed.. .
1860
    } // end - if( is_array( $this->unparsed.. .
1861
    unset( $unparsedtext, $rows, $this->unparsed, $proprows );
1862
            /* parse Components */
1863
    if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
1864
      $ckeys = array_keys( $this->components );
1865
      foreach( $ckeys as $ckey ) {
1866
        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
1867
          $this->components[$ckey]->parse();
1868
        }
1869
      }
1870
    }
1871
    else
1872
      return FALSE;                   /* err 91 or something.. . */
1873
    return TRUE;
1874
  }
1875
/*********************************************************************************/
1876
/**
1877
 * creates formatted output for calendar object instance
1878
 *
1879
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1880
 * @since 2.10.16 - 2011-10-28
1881
 * @return string
1882
 */
1883
  function createCalendar() {
1884
    $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
1885
    switch( $this->format ) {
1886
      case 'xcal':
1887
        $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
1888
                         '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
1889
                         '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
1890
        $calendarStart = '>'.$this->nl.'<vcalendar';
1891
        break;
1892
      default:
1893
        $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
1894
        break;
1895
    }
1896
    $calendarStart .= $this->createVersion();
1897
    $calendarStart .= $this->createProdid();
1898
    $calendarStart .= $this->createCalscale();
1899
    $calendarStart .= $this->createMethod();
1900
    if( 'xcal' == $this->format )
1901
      $calendarStart .= '>'.$this->nl;
1902
    $calendar .= $this->createXprop();
1903

    
1904
    foreach( $this->components as $component ) {
1905
      if( empty( $component )) continue;
1906
      $component->setConfig( $this->getConfig(), FALSE, TRUE );
1907
      $calendar .= $component->createComponent( $this->xcaldecl );
1908
    }
1909
    if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
1910
      $calendarInit .= ' [';
1911
      $old_xcaldecl  = array();
1912
      foreach( $this->xcaldecl as $declix => $declPart ) {
1913
        if(( 0 < count( $old_xcaldecl))    &&
1914
             isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
1915
             isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
1916
           ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
1917
           ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
1918
          continue; // no duplicate uri and ext. references
1919
        if(( 0 < count( $old_xcaldecl))    &&
1920
            !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
1921
             isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
1922
           ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
1923
          continue; // no duplicate element declarations
1924
        $calendarxCaldecl .= $this->nl.'<!';
1925
        foreach( $declPart as $declKey => $declValue ) {
1926
          switch( $declKey ) {                    // index
1927
            case 'xmldecl':                       // no 1
1928
              $calendarxCaldecl .= $declValue.' ';
1929
              break;
1930
            case 'uri':                           // no 2
1931
              $calendarxCaldecl .= $declValue.' ';
1932
              $old_xcaldecl['uri'][] = $declValue;
1933
              break;
1934
            case 'ref':                           // no 3
1935
              $calendarxCaldecl .= $declValue.' ';
1936
              $old_xcaldecl['ref'][] = $declValue;
1937
              break;
1938
            case 'external':                      // no 4
1939
              $calendarxCaldecl .= '"'.$declValue.'" ';
1940
              $old_xcaldecl['external'][] = $declValue;
1941
              break;
1942
            case 'type':                          // no 5
1943
              $calendarxCaldecl .= $declValue.' ';
1944
              break;
1945
            case 'type2':                         // no 6
1946
              $calendarxCaldecl .= $declValue;
1947
              break;
1948
          }
1949
        }
1950
        $calendarxCaldecl .= '>';
1951
      }
1952
      $calendarxCaldecl .= $this->nl.']';
1953
    }
1954
    switch( $this->format ) {
1955
      case 'xcal':
1956
        $calendar .= '</vcalendar>'.$this->nl;
1957
        break;
1958
      default:
1959
        $calendar .= 'END:VCALENDAR'.$this->nl;
1960
        break;
1961
    }
1962
    return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
1963
  }
1964
/**
1965
 * a HTTP redirect header is sent with created, updated and/or parsed calendar
1966
 *
1967
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1968
 * @since 2.10.24 - 2011-12-23
1969
 * @param bool $utf8Encode
1970
 * @param bool $gzip
1971
 * @return redirect
1972
 */
1973
  function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) {
1974
    $filename = $this->getConfig( 'filename' );
1975
    $output   = $this->createCalendar();
1976
    if( $utf8Encode )
1977
      $output = utf8_encode( $output );
1978
    if( $gzip ) {
1979
      $output = gzencode( $output, 9 );
1980
      header( 'Content-Encoding: gzip' );
1981
      header( 'Vary: *' );
1982
      header( 'Content-Length: '.strlen( $output ));
1983
    }
1984
    if( 'xcal' == $this->format )
1985
      header( 'Content-Type: application/calendar+xml; charset=utf-8' );
1986
    else
1987
      header( 'Content-Type: text/calendar; charset=utf-8' );
1988
    header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
1989
    header( 'Cache-Control: max-age=10' );
1990
    die( $output );
1991
  }
1992
/**
1993
 * save content in a file
1994
 *
1995
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1996
 * @since 2.2.12 - 2007-12-30
1997
 * @param string $directory optional
1998
 * @param string $filename optional
1999
 * @param string $delimiter optional
2000
 * @return bool
2001
 */
2002
  function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) {
2003
    if( $directory )
2004
      $this->setConfig( 'directory', $directory );
2005
    if( $filename )
2006
      $this->setConfig( 'filename',  $filename );
2007
    if( $delimiter && ($delimiter != DIRECTORY_SEPARATOR ))
2008
      $this->setConfig( 'delimiter', $delimiter );
2009
    if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
2010
      $dirfile = $this->getConfig( 'dirfile' );
2011
    $iCalFile = @fopen( $dirfile, 'w' );
2012
    if( $iCalFile ) {
2013
      if( FALSE === fwrite( $iCalFile, $this->createCalendar() ))
2014
        return FALSE;
2015
      fclose( $iCalFile );
2016
      return TRUE;
2017
    }
2018
    else
2019
      return FALSE;
2020
  }
2021
/**
2022
 * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
2023
 * else FALSE is returned
2024
 *
2025
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2026
 * @since 2.2.12 - 2007-10-28
2027
 * @param string $directory optional alt. int timeout
2028
 * @param string $filename optional
2029
 * @param string $delimiter optional
2030
 * @param int timeout optional, default 3600 sec
2031
 * @return redirect/FALSE
2032
 */
2033
  function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, $timeout=3600) {
2034
    if ( $directory && ctype_digit( (string) $directory ) && !$filename ) {
2035
      $timeout   = (int) $directory;
2036
      $directory = FALSE;
2037
    }
2038
    if( $directory )
2039
      $this->setConfig( 'directory', $directory );
2040
    if( $filename )
2041
      $this->setConfig( 'filename',  $filename );
2042
    if( $delimiter && ( $delimiter != DIRECTORY_SEPARATOR ))
2043
      $this->setConfig( 'delimiter', $delimiter );
2044
    $filesize    = $this->getConfig( 'filesize' );
2045
    if( 0 >= $filesize )
2046
      return FALSE;
2047
    $dirfile     = $this->getConfig( 'dirfile' );
2048
    if( time() - filemtime( $dirfile ) < $timeout) {
2049
      clearstatcache();
2050
      $dirfile   = $this->getConfig( 'dirfile' );
2051
      $filename  = $this->getConfig( 'filename' );
2052
//    if( headers_sent( $filename, $linenum ))
2053
//      die( "Headers already sent in $filename on line $linenum\n" );
2054
      if( 'xcal' == $this->format )
2055
        header( 'Content-Type: application/calendar+xml; charset=utf-8' );
2056
      else
2057
        header( 'Content-Type: text/calendar; charset=utf-8' );
2058
      header( 'Content-Length: '.$filesize );
2059
      header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
2060
      header( 'Cache-Control: max-age=10' );
2061
      $fp = @fopen( $dirfile, 'r' );
2062
      if( $fp ) {
2063
        fpassthru( $fp );
2064
        fclose( $fp );
2065
      }
2066
      die();
2067
    }
2068
    else
2069
      return FALSE;
2070
  }
2071
}
2072
/*********************************************************************************/
2073
/*********************************************************************************/
2074
/**
2075
 *  abstract class for calendar components
2076
 *
2077
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2078
 * @since 2.9.6 - 2011-05-14
2079
 */
2080
class calendarComponent {
2081
            //  component property variables
2082
  var $uid;
2083
  var $dtstamp;
2084

    
2085
            //  component config variables
2086
  var $allowEmpty;
2087
  var $language;
2088
  var $nl;
2089
  var $unique_id;
2090
  var $format;
2091
  var $objName; // created automatically at instance creation
2092
  var $dtzid;   // default (local) timezone
2093
            //  component internal variables
2094
  var $componentStart1;
2095
  var $componentStart2;
2096
  var $componentEnd1;
2097
  var $componentEnd2;
2098
  var $elementStart1;
2099
  var $elementStart2;
2100
  var $elementEnd1;
2101
  var $elementEnd2;
2102
  var $intAttrDelimiter;
2103
  var $attributeDelimiter;
2104
  var $valueInit;
2105
            //  component xCal declaration container
2106
  var $xcaldecl;
2107
/**
2108
 * constructor for calendar component object
2109
 *
2110
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2111
 * @since 2.9.6 - 2011-05-17
2112
 */
2113
  function calendarComponent() {
2114
    $this->objName         = ( isset( $this->timezonetype )) ?
2115
                          strtolower( $this->timezonetype )  :  get_class ( $this );
2116
    $this->uid             = array();
2117
    $this->dtstamp         = array();
2118

    
2119
    $this->language        = null;
2120
    $this->nl              = null;
2121
    $this->unique_id       = null;
2122
    $this->format          = null;
2123
    $this->dtzid           = null;
2124
    $this->allowEmpty      = TRUE;
2125
    $this->xcaldecl        = array();
2126

    
2127
    $this->_createFormat();
2128
    $this->_makeDtstamp();
2129
  }
2130
/*********************************************************************************/
2131
/**
2132
 * Property Name: ACTION
2133
 */
2134
/**
2135
 * creates formatted output for calendar component property action
2136
 *
2137
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2138
 * @since 2.4.8 - 2008-10-22
2139
 * @return string
2140
 */
2141
  function createAction() {
2142
    if( empty( $this->action )) return FALSE;
2143
    if( empty( $this->action['value'] ))
2144
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ACTION' ) : FALSE;
2145
    $attributes = $this->_createParams( $this->action['params'] );
2146
    return $this->_createElement( 'ACTION', $attributes, $this->action['value'] );
2147
  }
2148
/**
2149
 * set calendar component property action
2150
 *
2151
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2152
 * @since 2.16.21 - 2013-06-23
2153
 * @param string $value  "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE"
2154
 * @param mixed $params
2155
 * @return bool
2156
 */
2157
  function setAction( $value, $params=FALSE ) {
2158
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2159
    $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
2160
    return TRUE;
2161
  }
2162
/*********************************************************************************/
2163
/**
2164
 * Property Name: ATTACH
2165
 */
2166
/**
2167
 * creates formatted output for calendar component property attach
2168
 *
2169
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2170
 * @since 2.11.16 - 2012-02-04
2171
 * @return string
2172
 */
2173
  function createAttach() {
2174
    if( empty( $this->attach )) return FALSE;
2175
    $output       = null;
2176
    foreach( $this->attach as $attachPart ) {
2177
      if( !empty( $attachPart['value'] )) {
2178
        $attributes = $this->_createParams( $attachPart['params'] );
2179
        if(( 'xcal' != $this->format ) && isset( $attachPart['params']['VALUE'] ) && ( 'BINARY' == $attachPart['params']['VALUE'] )) {
2180
          $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
2181
          $str        = 'ATTACH'.$attributes.$this->valueInit.$attachPart['value'];
2182
          $output     = substr( $str, 0, 75 ).$this->nl;
2183
          $str        = substr( $str, 75 );
2184
          $output    .= ' '.chunk_split( $str, 74, $this->nl.' ' );
2185
          if( ' ' == substr( $output, -1 ))
2186
            $output   = rtrim( $output );
2187
          if( $this->nl != substr( $output, ( 0 - strlen( $this->nl ))))
2188
            $output  .= $this->nl;
2189
          return $output;
2190
        }
2191
        $output    .= $this->_createElement( 'ATTACH', $attributes, $attachPart['value'] );
2192
      }
2193
      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'ATTACH' );
2194
    }
2195
    return $output;
2196
  }
2197
/**
2198
 * set calendar component property attach
2199
 *
2200
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2201
 * @since 2.16.21 - 2013-06-23
2202
 * @param string $value
2203
 * @param array $params, optional
2204
 * @param integer $index, optional
2205
 * @return bool
2206
 */
2207
  function setAttach( $value, $params=FALSE, $index=FALSE ) {
2208
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2209
    iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index );
2210
    return TRUE;
2211
  }
2212
/*********************************************************************************/
2213
/**
2214
 * Property Name: ATTENDEE
2215
 */
2216
/**
2217
 * creates formatted output for calendar component property attendee
2218
 *
2219
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2220
 * @since 2.11.12 - 2012-01-31
2221
 * @return string
2222
 */
2223
  function createAttendee() {
2224
    if( empty( $this->attendee )) return FALSE;
2225
    $output = null;
2226
    foreach( $this->attendee as $attendeePart ) {                      // start foreach 1
2227
      if( empty( $attendeePart['value'] )) {
2228
        if( $this->getConfig( 'allowEmpty' ))
2229
          $output .= $this->_createElement( 'ATTENDEE' );
2230
        continue;
2231
      }
2232
      $attendee1 = $attendee2 = null;
2233
      foreach( $attendeePart as $paramlabel => $paramvalue ) {         // start foreach 2
2234
        if( 'value' == $paramlabel )
2235
          $attendee2     .= $paramvalue;
2236
        elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif
2237
          $mParams = array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' );
2238
          foreach( $paramvalue as $pKey => $pValue ) {                 // fix (opt) quotes
2239
            if( is_array( $pValue ) || in_array( $pKey, $mParams ))
2240
              continue;
2241
            if(( FALSE !== strpos( $pValue, ':' )) ||
2242
               ( FALSE !== strpos( $pValue, ';' )) ||
2243
               ( FALSE !== strpos( $pValue, ',' )))
2244
              $paramvalue[$pKey] = '"'.$pValue.'"';
2245
          }
2246
        // set attenddee parameters in rfc2445 order
2247
          if( isset( $paramvalue['CUTYPE'] ))
2248
            $attendee1   .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE'];
2249
          if( isset( $paramvalue['MEMBER'] )) {
2250
            $attendee1   .= $this->intAttrDelimiter.'MEMBER=';
2251
            foreach( $paramvalue['MEMBER'] as $cix => $opv )
2252
              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2253
          }
2254
          if( isset( $paramvalue['ROLE'] ))
2255
            $attendee1   .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE'];
2256
          if( isset( $paramvalue['PARTSTAT'] ))
2257
            $attendee1   .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT'];
2258
          if( isset( $paramvalue['RSVP'] ))
2259
            $attendee1   .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP'];
2260
          if( isset( $paramvalue['DELEGATED-TO'] )) {
2261
            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-TO=';
2262
            foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv )
2263
              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2264
          }
2265
          if( isset( $paramvalue['DELEGATED-FROM'] )) {
2266
            $attendee1   .= $this->intAttrDelimiter.'DELEGATED-FROM=';
2267
            foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv )
2268
              $attendee1 .= ( $cix ) ? ',"'.$opv.'"' : '"'.$opv.'"' ;
2269
          }
2270
          if( isset( $paramvalue['SENT-BY'] ))
2271
            $attendee1   .= $this->intAttrDelimiter.'SENT-BY='.$paramvalue['SENT-BY'];
2272
          if( isset( $paramvalue['CN'] ))
2273
            $attendee1   .= $this->intAttrDelimiter.'CN='.$paramvalue['CN'];
2274
          if( isset( $paramvalue['DIR'] )) {
2275
            $delim = ( FALSE === strpos( $paramvalue['DIR'], '"' )) ? '"' : '';
2276
            $attendee1   .= $this->intAttrDelimiter.'DIR='.$delim.$paramvalue['DIR'].$delim;
2277
          }
2278
          if( isset( $paramvalue['LANGUAGE'] ))
2279
            $attendee1   .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE'];
2280
          $xparams = array();
2281
          foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3
2282
            if( ctype_digit( (string) $optparamlabel )) {
2283
              $xparams[]  = $optparamvalue;
2284
              continue;
2285
            }
2286
            if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' )))
2287
              $xparams[$optparamlabel] = $optparamvalue;
2288
          } // end foreach 3
2289
          ksort( $xparams, SORT_STRING );
2290
          foreach( $xparams as $paramKey => $paramValue ) {
2291
            if( ctype_digit( (string) $paramKey ))
2292
              $attendee1 .= $this->intAttrDelimiter.$paramValue;
2293
            else
2294
              $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue";
2295
          }      // end foreach 3
2296
        }        // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue )))
2297
      }          // end foreach 2
2298
      $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 );
2299
    }              // end foreach 1
2300
    return $output;
2301
  }
2302
/**
2303
 * set calendar component property attach
2304
 *
2305
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2306
 * @since 2.16.21 - 2013-06-23
2307
 * @param string $value
2308
 * @param array $params, optional
2309
 * @param integer $index, optional
2310
 * @return bool
2311
 */
2312
  function setAttendee( $value, $params=FALSE, $index=FALSE ) {
2313
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2314
          // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero://  may exist.. . also in params
2315
    if( !empty( $value )) {
2316
      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
2317
        $value = 'MAILTO:'.$value;
2318
      elseif( !empty( $value ))
2319
        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
2320
      $value = str_replace( 'mailto:', 'MAILTO:', $value );
2321
    }
2322
    $params2 = array();
2323
    if( is_array($params )) {
2324
      $optarrays = array();
2325
      foreach( $params as $optparamlabel => $optparamvalue ) {
2326
        $optparamlabel = strtoupper( $optparamlabel );
2327
        switch( $optparamlabel ) {
2328
          case 'MEMBER':
2329
          case 'DELEGATED-TO':
2330
          case 'DELEGATED-FROM':
2331
            if( !is_array( $optparamvalue ))
2332
              $optparamvalue = array( $optparamvalue );
2333
            foreach( $optparamvalue as $part ) {
2334
              $part = trim( $part );
2335
              if(( '"' == substr( $part, 0, 1 )) &&
2336
                 ( '"' == substr( $part, -1 )))
2337
                $part = substr( $part, 1, ( strlen( $part ) - 2 ));
2338
              if( 'mailto:' != strtolower( substr( $part, 0, 7 )))
2339
                $part = "MAILTO:$part";
2340
              else
2341
                $part = 'MAILTO:'.substr( $part, 7 );
2342
              $optarrays[$optparamlabel][] = $part;
2343
            }
2344
            break;
2345
          default:
2346
            if(( '"' == substr( $optparamvalue, 0, 1 )) &&
2347
               ( '"' == substr( $optparamvalue, -1 )))
2348
              $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 ));
2349
            if( 'SENT-BY' ==  $optparamlabel ) {
2350
              if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 )))
2351
                $optparamvalue = "MAILTO:$optparamvalue";
2352
              else
2353
                $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 );
2354
            }
2355
            $params2[$optparamlabel] = $optparamvalue;
2356
            break;
2357
        } // end switch( $optparamlabel.. .
2358
      } // end foreach( $optparam.. .
2359
      foreach( $optarrays as $optparamlabel => $optparams )
2360
        $params2[$optparamlabel] = $optparams;
2361
    }
2362
        // remove defaults
2363
    iCalUtilityFunctions::_existRem( $params2, 'CUTYPE',   'INDIVIDUAL' );
2364
    iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' );
2365
    iCalUtilityFunctions::_existRem( $params2, 'ROLE',     'REQ-PARTICIPANT' );
2366
    iCalUtilityFunctions::_existRem( $params2, 'RSVP',     'FALSE' );
2367
        // check language setting
2368
    if( isset( $params2['CN' ] )) {
2369
      $lang = $this->getConfig( 'language' );
2370
      if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang ))
2371
        $params2['LANGUAGE' ] = $lang;
2372
    }
2373
    iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index );
2374
    return TRUE;
2375
  }
2376
/*********************************************************************************/
2377
/**
2378
 * Property Name: CATEGORIES
2379
 */
2380
/**
2381
 * creates formatted output for calendar component property categories
2382
 *
2383
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2384
 * @since 2.16.2 - 2012-12-18
2385
 * @return string
2386
 */
2387
  function createCategories() {
2388
    if( empty( $this->categories )) return FALSE;
2389
    $output = null;
2390
    foreach( $this->categories as $category ) {
2391
      if( empty( $category['value'] )) {
2392
        if ( $this->getConfig( 'allowEmpty' ))
2393
          $output .= $this->_createElement( 'CATEGORIES' );
2394
        continue;
2395
      }
2396
      $attributes = $this->_createParams( $category['params'], array( 'LANGUAGE' ));
2397
      if( is_array( $category['value'] )) {
2398
        foreach( $category['value'] as $cix => $categoryPart )
2399
          $category['value'][$cix] = iCalUtilityFunctions::_strrep( $categoryPart, $this->format, $this->nl );
2400
        $content  = implode( ',', $category['value'] );
2401
      }
2402
      else
2403
        $content  = iCalUtilityFunctions::_strrep( $category['value'], $this->format, $this->nl );
2404
      $output    .= $this->_createElement( 'CATEGORIES', $attributes, $content );
2405
    }
2406
    return $output;
2407
  }
2408
/**
2409
 * set calendar component property categories
2410
 *
2411
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2412
 * @since 2.16.21 - 2013-06-23
2413
 * @param mixed $value
2414
 * @param array $params, optional
2415
 * @param integer $index, optional
2416
 * @return bool
2417
 */
2418
  function setCategories( $value, $params=FALSE, $index=FALSE ) {
2419
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2420
    iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index );
2421
    return TRUE;
2422
 }
2423
/*********************************************************************************/
2424
/**
2425
 * Property Name: CLASS
2426
 */
2427
/**
2428
 * creates formatted output for calendar component property class
2429
 *
2430
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2431
 * @since 0.9.7 - 2006-11-20
2432
 * @return string
2433
 */
2434
  function createClass() {
2435
    if( empty( $this->class )) return FALSE;
2436
    if( empty( $this->class['value'] ))
2437
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'CLASS' ) : FALSE;
2438
    $attributes = $this->_createParams( $this->class['params'] );
2439
    return $this->_createElement( 'CLASS', $attributes, $this->class['value'] );
2440
  }
2441
/**
2442
 * set calendar component property class
2443
 *
2444
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2445
 * @since 2.16.21 - 2013-06-23
2446
 * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name
2447
 * @param array $params optional
2448
 * @return bool
2449
 */
2450
  function setClass( $value, $params=FALSE ) {
2451
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2452
    $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
2453
    return TRUE;
2454
  }
2455
/*********************************************************************************/
2456
/**
2457
 * Property Name: COMMENT
2458
 */
2459
/**
2460
 * creates formatted output for calendar component property comment
2461
 *
2462
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2463
 * @since 2.16.2 - 2012-12-18
2464
 * @return string
2465
 */
2466
  function createComment() {
2467
    if( empty( $this->comment )) return FALSE;
2468
    $output = null;
2469
    foreach( $this->comment as $commentPart ) {
2470
      if( empty( $commentPart['value'] )) {
2471
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'COMMENT' );
2472
        continue;
2473
      }
2474
      $attributes = $this->_createParams( $commentPart['params'], array( 'ALTREP', 'LANGUAGE' ));
2475
      $content    = iCalUtilityFunctions::_strrep( $commentPart['value'], $this->format, $this->nl );
2476
      $output    .= $this->_createElement( 'COMMENT', $attributes, $content );
2477
    }
2478
    return $output;
2479
  }
2480
/**
2481
 * set calendar component property comment
2482
 *
2483
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2484
 * @since 2.16.21 - 2013-06-23
2485
 * @param string $value
2486
 * @param array $params, optional
2487
 * @param integer $index, optional
2488
 * @return bool
2489
 */
2490
  function setComment( $value, $params=FALSE, $index=FALSE ) {
2491
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2492
    iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index );
2493
    return TRUE;
2494
  }
2495
/*********************************************************************************/
2496
/**
2497
 * Property Name: COMPLETED
2498
 */
2499
/**
2500
 * creates formatted output for calendar component property completed
2501
 *
2502
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2503
 * @since 2.4.8 - 2008-10-22
2504
 * @return string
2505
 */
2506
  function createCompleted( ) {
2507
    if( empty( $this->completed )) return FALSE;
2508
    if( !isset( $this->completed['value']['year'] )  &&
2509
        !isset( $this->completed['value']['month'] ) &&
2510
        !isset( $this->completed['value']['day'] )   &&
2511
        !isset( $this->completed['value']['hour'] )  &&
2512
        !isset( $this->completed['value']['min'] )   &&
2513
        !isset( $this->completed['value']['sec'] ))
2514
      if( $this->getConfig( 'allowEmpty' ))
2515
        return $this->_createElement( 'COMPLETED' );
2516
      else return FALSE;
2517
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->completed['value'], 7 );
2518
    $attributes = $this->_createParams( $this->completed['params'] );
2519
    return $this->_createElement( 'COMPLETED', $attributes, $formatted );
2520
  }
2521
/**
2522
 * set calendar component property completed
2523
 *
2524
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2525
 * @since 2.16.21 - 2013-06-23
2526
 * @param mixed $year
2527
 * @param mixed $month optional
2528
 * @param int $day optional
2529
 * @param int $hour optional
2530
 * @param int $min optional
2531
 * @param int $sec optional
2532
 * @param array $params optional
2533
 * @return bool
2534
 */
2535
  function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2536
    if( empty( $year )) {
2537
      if( $this->getConfig( 'allowEmpty' )) {
2538
        $this->completed = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
2539
        return TRUE;
2540
      }
2541
      else
2542
        return FALSE;
2543
    }
2544
    $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2545
    return TRUE;
2546
  }
2547
/*********************************************************************************/
2548
/**
2549
 * Property Name: CONTACT
2550
 */
2551
/**
2552
 * creates formatted output for calendar component property contact
2553
 *
2554
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2555
 * @since 2.16.2 - 2012-12-18
2556
 * @return string
2557
 */
2558
  function createContact() {
2559
    if( empty( $this->contact )) return FALSE;
2560
    $output = null;
2561
    foreach( $this->contact as $contact ) {
2562
      if( !empty( $contact['value'] )) {
2563
        $attributes = $this->_createParams( $contact['params'], array( 'ALTREP', 'LANGUAGE' ));
2564
        $content    = iCalUtilityFunctions::_strrep( $contact['value'], $this->format, $this->nl );
2565
        $output    .= $this->_createElement( 'CONTACT', $attributes, $content );
2566
      }
2567
      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'CONTACT' );
2568
    }
2569
    return $output;
2570
  }
2571
/**
2572
 * set calendar component property contact
2573
 *
2574
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2575
 * @since 2.16.21 - 2013-06-23
2576
 * @param string $value
2577
 * @param array $params, optional
2578
 * @param integer $index, optional
2579
 * @return bool
2580
 */
2581
  function setContact( $value, $params=FALSE, $index=FALSE ) {
2582
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
2583
    iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index );
2584
    return TRUE;
2585
  }
2586
/*********************************************************************************/
2587
/**
2588
 * Property Name: CREATED
2589
 */
2590
/**
2591
 * creates formatted output for calendar component property created
2592
 *
2593
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2594
 * @since 2.4.8 - 2008-10-21
2595
 * @return string
2596
 */
2597
  function createCreated() {
2598
    if( empty( $this->created )) return FALSE;
2599
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->created['value'], 7 );
2600
    $attributes = $this->_createParams( $this->created['params'] );
2601
    return $this->_createElement( 'CREATED', $attributes, $formatted );
2602
  }
2603
/**
2604
 * set calendar component property created
2605
 *
2606
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2607
 * @since 2.4.8 - 2008-10-23
2608
 * @param mixed $year optional
2609
 * @param mixed $month optional
2610
 * @param int $day optional
2611
 * @param int $hour optional
2612
 * @param int $min optional
2613
 * @param int $sec optional
2614
 * @param mixed $params optional
2615
 * @return bool
2616
 */
2617
  function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2618
    if( !isset( $year )) {
2619
      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
2620
    }
2621
    $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2622
    return TRUE;
2623
  }
2624
/*********************************************************************************/
2625
/**
2626
 * Property Name: DESCRIPTION
2627
 */
2628
/**
2629
 * creates formatted output for calendar component property description
2630
 *
2631
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2632
 * @since 2.16.2 - 2012-12-18
2633
 * @return string
2634
 */
2635
  function createDescription() {
2636
    if( empty( $this->description )) return FALSE;
2637
    $output       = null;
2638
    foreach( $this->description as $description ) {
2639
      if( !empty( $description['value'] )) {
2640
        $attributes = $this->_createParams( $description['params'], array( 'ALTREP', 'LANGUAGE' ));
2641
        $content    = iCalUtilityFunctions::_strrep( $description['value'], $this->format, $this->nl );
2642
        $output    .= $this->_createElement( 'DESCRIPTION', $attributes, $content );
2643
      }
2644
      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'DESCRIPTION' );
2645
    }
2646
    return $output;
2647
  }
2648
/**
2649
 * set calendar component property description
2650
 *
2651
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2652
 * @since 2.16.21 - 2013-06-23
2653
 * @param string $value
2654
 * @param array $params, optional
2655
 * @param integer $index, optional
2656
 * @return bool
2657
 */
2658
  function setDescription( $value, $params=FALSE, $index=FALSE ) {
2659
    if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE; }
2660
    if( 'vjournal' != $this->objName )
2661
      $index = 1;
2662
    iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index );
2663
    return TRUE;
2664
  }
2665
/*********************************************************************************/
2666
/**
2667
 * Property Name: DTEND
2668
 */
2669
/**
2670
 * creates formatted output for calendar component property dtend
2671
 *
2672
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2673
 * @since 2.14.4 - 2012-09-26
2674
 * @return string
2675
 */
2676
  function createDtend() {
2677
    if( empty( $this->dtend )) return FALSE;
2678
    if( !isset( $this->dtend['value']['year'] )  &&
2679
        !isset( $this->dtend['value']['month'] ) &&
2680
        !isset( $this->dtend['value']['day'] )   &&
2681
        !isset( $this->dtend['value']['hour'] )  &&
2682
        !isset( $this->dtend['value']['min'] )   &&
2683
        !isset( $this->dtend['value']['sec'] ))
2684
      if( $this->getConfig( 'allowEmpty' ))
2685
        return $this->_createElement( 'DTEND' );
2686
      else return FALSE;
2687
    $parno      = ( isset( $this->dtend['params']['VALUE'] ) && ( 'DATE' == $this->dtend['params']['VALUE'] )) ? 3 : null;
2688
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtend['value'], $parno );
2689
    $attributes = $this->_createParams( $this->dtend['params'] );
2690
    return $this->_createElement( 'DTEND', $attributes, $formatted );
2691
  }
2692
/**
2693
 * set calendar component property dtend
2694
 *
2695
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2696
 * @since 2.16.21 - 2013-06-23
2697
 * @param mixed $year
2698
 * @param mixed $month optional
2699
 * @param int $day optional
2700
 * @param int $hour optional
2701
 * @param int $min optional
2702
 * @param int $sec optional
2703
 * @param string $tz optional
2704
 * @param array params optional
2705
 * @return bool
2706
 */
2707
  function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2708
    if( empty( $year )) {
2709
      if( $this->getConfig( 'allowEmpty' )) {
2710
        $this->dtend = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
2711
        return TRUE;
2712
      }
2713
      else
2714
        return FALSE;
2715
    }
2716
    $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
2717
    return TRUE;
2718
  }
2719
/*********************************************************************************/
2720
/**
2721
 * Property Name: DTSTAMP
2722
 */
2723
/**
2724
 * creates formatted output for calendar component property dtstamp
2725
 *
2726
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2727
 * @since 2.4.4 - 2008-03-07
2728
 * @return string
2729
 */
2730
  function createDtstamp() {
2731
    if( !isset( $this->dtstamp['value']['year'] )  &&
2732
        !isset( $this->dtstamp['value']['month'] ) &&
2733
        !isset( $this->dtstamp['value']['day'] )   &&
2734
        !isset( $this->dtstamp['value']['hour'] )  &&
2735
        !isset( $this->dtstamp['value']['min'] )   &&
2736
        !isset( $this->dtstamp['value']['sec'] ))
2737
      $this->_makeDtstamp();
2738
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstamp['value'], 7 );
2739
    $attributes = $this->_createParams( $this->dtstamp['params'] );
2740
    return $this->_createElement( 'DTSTAMP', $attributes, $formatted );
2741
  }
2742
/**
2743
 * computes datestamp for calendar component object instance dtstamp
2744
 *
2745
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2746
 * @since 2.14.1 - 2012-09-29
2747
 * @return void
2748
 */
2749
  function _makeDtstamp() {
2750
    $d    = date( 'Y-m-d-H-i-s', mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')));
2751
    $date = explode( '-', $d );
2752
    $this->dtstamp['value'] = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2], 'hour' => $date[3], 'min' => $date[4], 'sec' => $date[5], 'tz' => 'Z' );
2753
    $this->dtstamp['params'] = null;
2754
  }
2755
/**
2756
 * set calendar component property dtstamp
2757
 *
2758
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2759
 * @since 2.4.8 - 2008-10-23
2760
 * @param mixed $year
2761
 * @param mixed $month optional
2762
 * @param int $day optional
2763
 * @param int $hour optional
2764
 * @param int $min optional
2765
 * @param int $sec optional
2766
 * @param array $params optional
2767
 * @return TRUE
2768
 */
2769
  function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2770
    if( empty( $year ))
2771
      $this->_makeDtstamp();
2772
    else
2773
      $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
2774
    return TRUE;
2775
  }
2776
/*********************************************************************************/
2777
/**
2778
 * Property Name: DTSTART
2779
 */
2780
/**
2781
 * creates formatted output for calendar component property dtstart
2782
 *
2783
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2784
 * @since 2.14.4 - 2012-09-26
2785
 * @return string
2786
 */
2787
  function createDtstart() {
2788
    if( empty( $this->dtstart )) return FALSE;
2789
    if( !isset( $this->dtstart['value']['year'] )  &&
2790
        !isset( $this->dtstart['value']['month'] ) &&
2791
        !isset( $this->dtstart['value']['day'] )   &&
2792
        !isset( $this->dtstart['value']['hour'] )  &&
2793
        !isset( $this->dtstart['value']['min'] )   &&
2794
        !isset( $this->dtstart['value']['sec'] )) {
2795
      if( $this->getConfig( 'allowEmpty' ))
2796
        return $this->_createElement( 'DTSTART' );
2797
      else return FALSE;
2798
    }
2799
    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' )))
2800
       unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] );
2801
    $parno      = ( isset( $this->dtstart['params']['VALUE'] ) && ( 'DATE' == $this->dtstart['params']['VALUE'] )) ? 3 : null;
2802
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->dtstart['value'], $parno );
2803
    $attributes = $this->_createParams( $this->dtstart['params'] );
2804
    return $this->_createElement( 'DTSTART', $attributes, $formatted );
2805
  }
2806
/**
2807
 * set calendar component property dtstart
2808
 *
2809
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2810
 * @since 2.16.21 - 2013-06-23
2811
 * @param mixed $year
2812
 * @param mixed $month optional
2813
 * @param int $day optional
2814
 * @param int $hour optional
2815
 * @param int $min optional
2816
 * @param int $sec optional
2817
 * @param string $tz optional
2818
 * @param array $params optional
2819
 * @return bool
2820
 */
2821
  function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2822
    if( empty( $year )) {
2823
      if( $this->getConfig( 'allowEmpty' )) {
2824
        $this->dtstart = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
2825
        return TRUE;
2826
      }
2827
      else
2828
        return FALSE;
2829
    }
2830
    $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' ));
2831
    return TRUE;
2832
  }
2833
/*********************************************************************************/
2834
/**
2835
 * Property Name: DUE
2836
 */
2837
/**
2838
 * creates formatted output for calendar component property due
2839
 *
2840
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2841
 * @since 2.14.4 - 2012-09-26
2842
 * @return string
2843
 */
2844
  function createDue() {
2845
    if( empty( $this->due )) return FALSE;
2846
    if( !isset( $this->due['value']['year'] )  &&
2847
        !isset( $this->due['value']['month'] ) &&
2848
        !isset( $this->due['value']['day'] )   &&
2849
        !isset( $this->due['value']['hour'] )  &&
2850
        !isset( $this->due['value']['min'] )   &&
2851
        !isset( $this->due['value']['sec'] )) {
2852
      if( $this->getConfig( 'allowEmpty' ))
2853
        return $this->_createElement( 'DUE' );
2854
      else
2855
       return FALSE;
2856
    }
2857
    $parno      = ( isset( $this->due['params']['VALUE'] ) && ( 'DATE' == $this->due['params']['VALUE'] )) ? 3 : null;
2858
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->due['value'], $parno );
2859
    $attributes = $this->_createParams( $this->due['params'] );
2860
    return $this->_createElement( 'DUE', $attributes, $formatted );
2861
  }
2862
/**
2863
 * set calendar component property due
2864
 *
2865
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2866
 * @since 2.16.21 - 2013-06-23
2867
 * @param mixed $year
2868
 * @param mixed $month optional
2869
 * @param int $day optional
2870
 * @param int $hour optional
2871
 * @param int $min optional
2872
 * @param int $sec optional
2873
 * @param array $params optional
2874
 * @return bool
2875
 */
2876
  function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
2877
    if( empty( $year )) {
2878
      if( $this->getConfig( 'allowEmpty' )) {
2879
        $this->due = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ));
2880
        return TRUE;
2881
      }
2882
      else
2883
        return FALSE;
2884
    }
2885
    $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
2886
    return TRUE;
2887
  }
2888
/*********************************************************************************/
2889
/**
2890
 * Property Name: DURATION
2891
 */
2892
/**
2893
 * creates formatted output for calendar component property duration
2894
 *
2895
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2896
 * @since 2.16.21 - 2013-05-25
2897
 * @return string
2898
 */
2899
  function createDuration() {
2900
    if( empty( $this->duration )) return FALSE;
2901
    if( !isset( $this->duration['value']['week'] ) &&
2902
        !isset( $this->duration['value']['day'] )  &&
2903
        !isset( $this->duration['value']['hour'] ) &&
2904
        !isset( $this->duration['value']['min'] )  &&
2905
        !isset( $this->duration['value']['sec'] ))
2906
      if( $this->getConfig( 'allowEmpty' ))
2907
        return $this->_createElement( 'DURATION' );
2908
      else return FALSE;
2909
    $attributes = $this->_createParams( $this->duration['params'] );
2910
    return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_duration2str( $this->duration['value'] ));
2911
  }
2912
/**
2913
 * set calendar component property duration
2914
 *
2915
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2916
 * @since 2.16.21 - 2013-05-25
2917
 * @param mixed $week
2918
 * @param mixed $day optional
2919
 * @param int $hour optional
2920
 * @param int $min optional
2921
 * @param int $sec optional
2922
 * @param array $params optional
2923
 * @return bool
2924
 */
2925
  function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
2926
    if( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec )) {
2927
      if( $this->getConfig( 'allowEmpty' ))
2928
        $week = $day = null;
2929
      else
2930
        return FALSE;
2931
    }
2932
    if( is_array( $week ) && ( 1 <= count( $week )))
2933
      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
2934
    elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) {
2935
      $week = trim( $week );
2936
      if( in_array( substr( $week, 0, 1 ), array( '+', '-' )))
2937
        $week = substr( $week, 1 );
2938
      $this->duration = array( 'value' => iCalUtilityFunctions::_durationStr2arr( $week ), 'params' => iCalUtilityFunctions::_setParams( $day ));
2939
    }
2940
    else
2941
      $this->duration = array( 'value' => iCalUtilityFunctions::_duration2arr( array( 'week' => $week, 'day' => $day, 'hour' => $hour, 'min' => $min, 'sec' => $sec ))
2942
                             , 'params' => iCalUtilityFunctions::_setParams( $params ));
2943
    return TRUE;
2944
  }
2945
/*********************************************************************************/
2946
/**
2947
 * Property Name: EXDATE
2948
 */
2949
/**
2950
 * creates formatted output for calendar component property exdate
2951
 *
2952
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2953
 * @since 2.16.5 - 2012-12-28
2954
 * @return string
2955
 */
2956
  function createExdate() {
2957
    if( empty( $this->exdate )) return FALSE;
2958
    $output  = null;
2959
    $exdates = array();
2960
    foreach( $this->exdate as $theExdate ) {
2961
      if( empty( $theExdate['value'] )) {
2962
        if( $this->getConfig( 'allowEmpty' ))
2963
          $output .= $this->_createElement( 'EXDATE' );
2964
        continue;
2965
      }
2966
      if( 1 < count( $theExdate['value'] ))
2967
        usort( $theExdate['value'], array( 'iCalUtilityFunctions', '_sortExdate1' ));
2968
      $exdates[] = $theExdate;
2969
    }
2970
    if( 1 < count( $exdates ))
2971
      usort( $exdates, array( 'iCalUtilityFunctions', '_sortExdate2' ));
2972
    foreach( $exdates as $theExdate ) {
2973
      $content = $attributes = null;
2974
      foreach( $theExdate['value'] as $eix => $exdatePart ) {
2975
        $parno = count( $exdatePart );
2976
        $formatted = iCalUtilityFunctions::_date2strdate( $exdatePart, $parno );
2977
        if( isset( $theExdate['params']['TZID'] ))
2978
          $formatted = str_replace( 'Z', '', $formatted);
2979
        if( 0 < $eix ) {
2980
          if( isset( $theExdate['value'][0]['tz'] )) {
2981
            if( ctype_digit( substr( $theExdate['value'][0]['tz'], -4 )) ||
2982
               ( 'Z' == $theExdate['value'][0]['tz'] )) {
2983
              if( 'Z' != substr( $formatted, -1 ))
2984
                $formatted .= 'Z';
2985
            }
2986
            else
2987
              $formatted = str_replace( 'Z', '', $formatted );
2988
          }
2989
          else
2990
            $formatted = str_replace( 'Z', '', $formatted );
2991
        } // end if( 0 < $eix )
2992
        $content .= ( 0 < $eix ) ? ','.$formatted : $formatted;
2993
      } // end foreach( $theExdate['value'] as $eix => $exdatePart )
2994
      $attributes .= $this->_createParams( $theExdate['params'] );
2995
      $output .= $this->_createElement( 'EXDATE', $attributes, $content );
2996
    } // end foreach( $exdates as $theExdate )
2997
    return $output;
2998
  }
2999
/**
3000
 * set calendar component property exdate
3001
 *
3002
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3003
 * @since 2.16.21 - 2013-06-23
3004
 * @param array exdates
3005
 * @param array $params, optional
3006
 * @param integer $index, optional
3007
 * @return bool
3008
 */
3009
  function setExdate( $exdates, $params=FALSE, $index=FALSE ) {
3010
    if( empty( $exdates )) {
3011
      if( $this->getConfig( 'allowEmpty' )) {
3012
        iCalUtilityFunctions::_setMval( $this->exdate, '', $params, FALSE, $index );
3013
        return TRUE;
3014
      }
3015
      else
3016
        return FALSE;
3017
    }
3018
    $input  = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
3019
    $toZ = ( isset( $input['params']['TZID'] ) && in_array( strtoupper( $input['params']['TZID'] ), array( 'GMT', 'UTC', 'Z' ))) ? TRUE : FALSE;
3020
            /* ev. check 1:st date and save ev. timezone **/
3021
    iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] );
3022
    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter
3023
    foreach( $exdates as $eix => $theExdate ) {
3024
      iCalUtilityFunctions::_strDate2arr( $theExdate );
3025
      if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) {
3026
        if( isset( $theExdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theExdate['tz'] )) {
3027
          if( isset( $input['params']['TZID'] ))
3028
            $theExdate['tz'] = $input['params']['TZID'];
3029
          else
3030
            $input['params']['TZID'] = $theExdate['tz'];
3031
        }
3032
        $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno );
3033
      }
3034
      elseif(  is_array( $theExdate )) {
3035
        $d = iCalUtilityFunctions::_chkDateArr( $theExdate, $parno );
3036
        if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
3037
          $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
3038
          $exdatea = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
3039
          unset( $exdatea['unparsedtext'] );
3040
        }
3041
        else
3042
          $exdatea = $d;
3043
      }
3044
      elseif( 8 <= strlen( trim( $theExdate ))) { // ex. 2006-08-03 10:12:18
3045
        $exdatea = iCalUtilityFunctions::_strdate2date( $theExdate, $parno );
3046
        unset( $exdatea['unparsedtext'] );
3047
      }
3048
      if( 3 == $parno )
3049
        unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] );
3050
      elseif( isset( $exdatea['tz'] ))
3051
        $exdatea['tz'] = (string) $exdatea['tz'];
3052
      if(  isset( $input['params']['TZID'] ) ||
3053
         ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) ||
3054
         ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) ||
3055
         ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] )))
3056
        unset( $exdatea['tz'] );
3057
      if( $toZ ) // time zone Z
3058
        $exdatea['tz'] = 'Z';
3059
      $input['value'][] = $exdatea;
3060
    }
3061
    if( 0 >= count( $input['value'] ))
3062
      return FALSE;
3063
    if( 3 == $parno ) {
3064
      $input['params']['VALUE'] = 'DATE';
3065
      unset( $input['params']['TZID'] );
3066
    }
3067
    if( $toZ ) // time zone Z
3068
      unset( $input['params']['TZID'] );
3069
    iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index );
3070
    return TRUE;
3071
  }
3072
/*********************************************************************************/
3073
/**
3074
 * Property Name: EXRULE
3075
 */
3076
/**
3077
 * creates formatted output for calendar component property exrule
3078
 *
3079
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3080
 * @since 2.4.8 - 2008-10-22
3081
 * @return string
3082
 */
3083
  function createExrule() {
3084
    if( empty( $this->exrule )) return FALSE;
3085
    return $this->_format_recur( 'EXRULE', $this->exrule );
3086
  }
3087
/**
3088
 * set calendar component property exdate
3089
 *
3090
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3091
 * @since 2.16.21 - 2013-06-23
3092
 * @param array $exruleset
3093
 * @param array $params, optional
3094
 * @param integer $index, optional
3095
 * @return bool
3096
 */
3097
  function setExrule( $exruleset, $params=FALSE, $index=FALSE ) {
3098
    if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = ''; else return FALSE;
3099
    iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index );
3100
    return TRUE;
3101
  }
3102
/*********************************************************************************/
3103
/**
3104
 * Property Name: FREEBUSY
3105
 */
3106
/**
3107
 * creates formatted output for calendar component property freebusy
3108
 *
3109
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3110
 * @since 2.16.27 - 2013-07-05
3111
 * @return string
3112
 */
3113
  function createFreebusy() {
3114
    if( empty( $this->freebusy )) return FALSE;
3115
    $output = null;
3116
    foreach( $this->freebusy as $freebusyPart ) {
3117
      if( empty( $freebusyPart['value'] ) || (( 1 == count( $freebusyPart['value'] )) && isset( $freebusyPart['value']['fbtype'] ))) {
3118
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'FREEBUSY' );
3119
        continue;
3120
      }
3121
      $attributes = $content = null;
3122
      if( isset( $freebusyPart['value']['fbtype'] )) {
3123
          $attributes .= $this->intAttrDelimiter.'FBTYPE='.$freebusyPart['value']['fbtype'];
3124
        unset( $freebusyPart['value']['fbtype'] );
3125
        $freebusyPart['value'] = array_values( $freebusyPart['value'] );
3126
      }
3127
      else
3128
        $attributes .= $this->intAttrDelimiter.'FBTYPE=BUSY';
3129
      $attributes .= $this->_createParams( $freebusyPart['params'] );
3130
      $fno = 1;
3131
      $cnt = count( $freebusyPart['value']);
3132
      if( 1 < $cnt )
3133
        usort( $freebusyPart['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
3134
      foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) {
3135
        $formatted   = iCalUtilityFunctions::_date2strdate( $freebusyPeriod[0] );
3136
        $content .= $formatted;
3137
        $content .= '/';
3138
        $cnt2 = count( $freebusyPeriod[1]);
3139
        if( array_key_exists( 'year', $freebusyPeriod[1] ))      // date-time
3140
          $cnt2 = 7;
3141
        elseif( array_key_exists( 'week', $freebusyPeriod[1] ))  // duration
3142
          $cnt2 = 5;
3143
        if(( 7 == $cnt2 )   &&    // period=  -> date-time
3144
            isset( $freebusyPeriod[1]['year'] )  &&
3145
            isset( $freebusyPeriod[1]['month'] ) &&
3146
            isset( $freebusyPeriod[1]['day'] )) {
3147
          $content .= iCalUtilityFunctions::_date2strdate( $freebusyPeriod[1] );
3148
        }
3149
        else {                                  // period=  -> dur-time
3150
          $content .= iCalUtilityFunctions::_duration2str( $freebusyPeriod[1] );
3151
        }
3152
        if( $fno < $cnt )
3153
          $content .= ',';
3154
        $fno++;
3155
      }
3156
      $output .= $this->_createElement( 'FREEBUSY', $attributes, $content );
3157
    }
3158
    return $output;
3159
  }
3160
/**
3161
 * set calendar component property freebusy
3162
 *
3163
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3164
 * @since 2.16.21 - 2013-06-23
3165
 * @param string $fbType
3166
 * @param array $fbValues
3167
 * @param array $params, optional
3168
 * @param integer $index, optional
3169
 * @return bool
3170
 */
3171
  function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) {
3172
    if( empty( $fbValues )) {
3173
      if( $this->getConfig( 'allowEmpty' )) {
3174
        iCalUtilityFunctions::_setMval( $this->freebusy, '', $params, FALSE, $index );
3175
        return TRUE;
3176
      }
3177
      else
3178
        return FALSE;
3179
    }
3180
    $fbType = strtoupper( $fbType );
3181
    if(( !in_array( $fbType, array( 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE' ))) &&
3182
       ( 'X-' != substr( $fbType, 0, 2 )))
3183
      $fbType = 'BUSY';
3184
    $input = array( 'fbtype' => $fbType );
3185
    foreach( $fbValues as $fbPeriod ) {   // periods => period
3186
      if( empty( $fbPeriod ))
3187
        continue;
3188
      $freebusyPeriod = array();
3189
      foreach( $fbPeriod as $fbMember ) { // pairs => singlepart
3190
        $freebusyPairMember = array();
3191
        if( is_array( $fbMember )) {
3192
          if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value
3193
            $freebusyPairMember       = iCalUtilityFunctions::_chkDateArr( $fbMember, 7 );
3194
            $freebusyPairMember['tz'] = 'Z';
3195
          }
3196
          elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value
3197
            $freebusyPairMember       = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 );
3198
            $freebusyPairMember['tz'] = 'Z';
3199
          }
3200
          else {                                         // array format duration
3201
            $freebusyPairMember = iCalUtilityFunctions::_duration2arr( $fbMember );
3202
          }
3203
        }
3204
        elseif(( 3 <= strlen( trim( $fbMember ))) &&    // string format duration
3205
               ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) {
3206
          if( 'P' != $fbMember{0} )
3207
            $fbmember = substr( $fbMember, 1 );
3208
          $freebusyPairMember = iCalUtilityFunctions::_durationStr2arr( $fbMember );
3209
        }
3210
        elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18
3211
          $freebusyPairMember       = iCalUtilityFunctions::_strdate2date( $fbMember, 7 );
3212
          unset( $freebusyPairMember['unparsedtext'] );
3213
          $freebusyPairMember['tz'] = 'Z';
3214
        }
3215
        $freebusyPeriod[]   = $freebusyPairMember;
3216
      }
3217
      $input[]              = $freebusyPeriod;
3218
    }
3219
    iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index );
3220
    return TRUE;
3221
  }
3222
/*********************************************************************************/
3223
/**
3224
 * Property Name: GEO
3225
 */
3226
/**
3227
 * creates formatted output for calendar component property geo
3228
 *
3229
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3230
 * @since 2.12.6 - 2012-04-21
3231
 * @return string
3232
 */
3233
  function createGeo() {
3234
    if( empty( $this->geo )) return FALSE;
3235
    if( empty( $this->geo['value'] ))
3236
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'GEO' ) : FALSE;
3237
    $attributes = $this->_createParams( $this->geo['params'] );
3238
    if( 0.0 < $this->geo['value']['latitude'] )
3239
      $sign   = '+';
3240
    else
3241
      $sign   = ( 0.0 > $this->geo['value']['latitude'] ) ? '-' : '';
3242
    $content  = $sign.sprintf( "%09.6f", abs( $this->geo['value']['latitude'] ));       // sprintf && lpad && float && sign !"#¤%&/(
3243
    $content  = rtrim( rtrim( $content, '0' ), '.' );
3244
    if( 0.0 < $this->geo['value']['longitude'] )
3245
      $sign   = '+';
3246
    else
3247
      $sign   = ( 0.0 > $this->geo['value']['longitude'] ) ? '-' : '';
3248
    $content .= ';'.$sign.sprintf( '%8.6f', abs( $this->geo['value']['longitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
3249
    $content  = rtrim( rtrim( $content, '0' ), '.' );
3250
    return $this->_createElement( 'GEO', $attributes, $content );
3251
  }
3252
/**
3253
 * set calendar component property geo
3254
 *
3255
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3256
 * @since 2.16.21 - 2013-06-23
3257
 * @param float $latitude
3258
 * @param float $longitude
3259
 * @param array $params optional
3260
 * @return bool
3261
 */
3262
  function setGeo( $latitude, $longitude, $params=FALSE ) {
3263
    if( isset( $latitude ) && isset( $longitude )) {
3264
      if( !is_array( $this->geo )) $this->geo = array();
3265
      $this->geo['value']['latitude']  = (float) $latitude;
3266
      $this->geo['value']['longitude'] = (float) $longitude;
3267
      $this->geo['params'] = iCalUtilityFunctions::_setParams( $params );
3268
    }
3269
    elseif( $this->getConfig( 'allowEmpty' ))
3270
      $this->geo = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $params ) );
3271
    else
3272
      return FALSE;
3273
    return TRUE;
3274
  }
3275
/*********************************************************************************/
3276
/**
3277
 * Property Name: LAST-MODIFIED
3278
 */
3279
/**
3280
 * creates formatted output for calendar component property last-modified
3281
 *
3282
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3283
 * @since 2.4.8 - 2008-10-21
3284
 * @return string
3285
 */
3286
  function createLastModified() {
3287
    if( empty( $this->lastmodified )) return FALSE;
3288
    $attributes = $this->_createParams( $this->lastmodified['params'] );
3289
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->lastmodified['value'], 7 );
3290
    return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted );
3291
  }
3292
/**
3293
 * set calendar component property completed
3294
 *
3295
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3296
 * @since 2.4.8 - 2008-10-23
3297
 * @param mixed $year optional
3298
 * @param mixed $month optional
3299
 * @param int $day optional
3300
 * @param int $hour optional
3301
 * @param int $min optional
3302
 * @param int $sec optional
3303
 * @param array $params optional
3304
 * @return boll
3305
 */
3306
  function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
3307
    if( empty( $year ))
3308
      $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' )));
3309
    $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params );
3310
    return TRUE;
3311
  }
3312
/*********************************************************************************/
3313
/**
3314
 * Property Name: LOCATION
3315
 */
3316
/**
3317
 * creates formatted output for calendar component property location
3318
 *
3319
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3320
 * @since 2.16.2 - 2012-12-18
3321
 * @return string
3322
 */
3323
  function createLocation() {
3324
    if( empty( $this->location )) return FALSE;
3325
    if( empty( $this->location['value'] ))
3326
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'LOCATION' ) : FALSE;
3327
    $attributes = $this->_createParams( $this->location['params'], array( 'ALTREP', 'LANGUAGE' ));
3328
    $content    = iCalUtilityFunctions::_strrep( $this->location['value'], $this->format, $this->nl );
3329
    return $this->_createElement( 'LOCATION', $attributes, $content );
3330
  }
3331
/**
3332
 * set calendar component property location
3333
 '
3334
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3335
 * @since 2.16.21 - 2013-06-23
3336
 * @param string $value
3337
 * @param array params optional
3338
 * @return bool
3339
 */
3340
  function setLocation( $value, $params=FALSE ) {
3341
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3342
    $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3343
    return TRUE;
3344
  }
3345
/*********************************************************************************/
3346
/**
3347
 * Property Name: ORGANIZER
3348
 */
3349
/**
3350
 * creates formatted output for calendar component property organizer
3351
 *
3352
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3353
 * @since 2.6.33 - 2010-12-17
3354
 * @return string
3355
 */
3356
  function createOrganizer() {
3357
    if( empty( $this->organizer )) return FALSE;
3358
    if( empty( $this->organizer['value'] ))
3359
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE;
3360
    $attributes = $this->_createParams( $this->organizer['params']
3361
                                      , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' ));
3362
    return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] );
3363
  }
3364
/**
3365
 * set calendar component property organizer
3366
 *
3367
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3368
 * @since 2.16.21 - 2013-06-23
3369
 * @param string $value
3370
 * @param array params optional
3371
 * @return bool
3372
 */
3373
  function setOrganizer( $value, $params=FALSE ) {
3374
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3375
    if( !empty( $value )) {
3376
      if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' )))
3377
        $value = 'MAILTO:'.$value;
3378
      elseif( !empty( $value ))
3379
        $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos );
3380
      $value = str_replace( 'mailto:', 'MAILTO:', $value );
3381
    }
3382
    $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3383
    if( isset( $this->organizer['params']['SENT-BY'] )){
3384
      if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 )))
3385
        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY'];
3386
      else
3387
        $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 );
3388
    }
3389
    return TRUE;
3390
  }
3391
/*********************************************************************************/
3392
/**
3393
 * Property Name: PERCENT-COMPLETE
3394
 */
3395
/**
3396
 * creates formatted output for calendar component property percent-complete
3397
 *
3398
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3399
 * @since 2.9.3 - 2011-05-14
3400
 * @return string
3401
 */
3402
  function createPercentComplete() {
3403
    if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE;
3404
    if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] )))
3405
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE;
3406
    $attributes = $this->_createParams( $this->percentcomplete['params'] );
3407
    return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] );
3408
  }
3409
/**
3410
 * set calendar component property percent-complete
3411
 *
3412
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3413
 * @since 2.16.21 - 2013-06-23
3414
 * @param int $value
3415
 * @param array $params optional
3416
 * @return bool
3417
 */
3418
  function setPercentComplete( $value, $params=FALSE ) {
3419
    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3420
    $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3421
    return TRUE;
3422
  }
3423
/*********************************************************************************/
3424
/**
3425
 * Property Name: PRIORITY
3426
 */
3427
/**
3428
 * creates formatted output for calendar component property priority
3429
 *
3430
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3431
 * @since 2.9.3 - 2011-05-14
3432
 * @return string
3433
 */
3434
  function createPriority() {
3435
    if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE;
3436
    if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] )))
3437
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE;
3438
    $attributes = $this->_createParams( $this->priority['params'] );
3439
    return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] );
3440
  }
3441
/**
3442
 * set calendar component property priority
3443
 *
3444
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3445
 * @since 2.16.21 - 2013-06-23
3446
 * @param int $value
3447
 * @param array $params optional
3448
 * @return bool
3449
 */
3450
  function setPriority( $value, $params=FALSE  ) {
3451
    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3452
    $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3453
    return TRUE;
3454
  }
3455
/*********************************************************************************/
3456
/**
3457
 * Property Name: RDATE
3458
 */
3459
/**
3460
 * creates formatted output for calendar component property rdate
3461
 *
3462
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3463
 * @since 2.16.9 - 2013-01-09
3464
 * @return string
3465
 */
3466
  function createRdate() {
3467
    if( empty( $this->rdate )) return FALSE;
3468
    $utctime = ( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
3469
    $output = null;
3470
    $rdates = array();
3471
    foreach( $this->rdate as $rpix => $theRdate ) {
3472
      if( empty( $theRdate['value'] )) {
3473
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RDATE' );
3474
        continue;
3475
      }
3476
      if( $utctime  )
3477
        unset( $theRdate['params']['TZID'] );
3478
      if( 1 < count( $theRdate['value'] ))
3479
        usort( $theRdate['value'], array( 'iCalUtilityFunctions', '_sortRdate1' ));
3480
      $rdates[] = $theRdate;
3481
    }
3482
    if( 1 < count( $rdates ))
3483
      usort( $rdates, array( 'iCalUtilityFunctions', '_sortRdate2' ));
3484
    foreach( $rdates as $rpix => $theRdate ) {
3485
      $attributes = $this->_createParams( $theRdate['params'] );
3486
      $cnt = count( $theRdate['value'] );
3487
      $content = null;
3488
      $rno = 1;
3489
      foreach( $theRdate['value'] as $rix => $rdatePart ) {
3490
        $contentPart = null;
3491
        if( is_array( $rdatePart ) &&
3492
            isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD
3493
          if( $utctime )
3494
            unset( $rdatePart[0]['tz'] );
3495
          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[0] ); // PERIOD part 1
3496
          if( $utctime || !empty( $theRdate['params']['TZID'] ))
3497
            $formatted = str_replace( 'Z', '', $formatted);
3498
          $contentPart .= $formatted;
3499
          $contentPart .= '/';
3500
          $cnt2 = count( $rdatePart[1]);
3501
          if( array_key_exists( 'year', $rdatePart[1] )) {
3502
            if( array_key_exists( 'hour', $rdatePart[1] ))
3503
              $cnt2 = 7;                                      // date-time
3504
            else
3505
              $cnt2 = 3;                                      // date
3506
          }
3507
          elseif( array_key_exists( 'week', $rdatePart[1] ))  // duration
3508
            $cnt2 = 5;
3509
          if(( 7 == $cnt2 )   &&    // period=  -> date-time
3510
              isset( $rdatePart[1]['year'] )  &&
3511
              isset( $rdatePart[1]['month'] ) &&
3512
              isset( $rdatePart[1]['day'] )) {
3513
            if( $utctime )
3514
              unset( $rdatePart[1]['tz'] );
3515
            $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart[1] ); // PERIOD part 2
3516
            if( $utctime || !empty( $theRdate['params']['TZID'] ))
3517
              $formatted = str_replace( 'Z', '', $formatted );
3518
           $contentPart .= $formatted;
3519
          }
3520
          else {                                  // period=  -> dur-time
3521
            $contentPart .= iCalUtilityFunctions::_duration2str( $rdatePart[1] );
3522
          }
3523
        } // PERIOD end
3524
        else { // SINGLE date start
3525
          if( $utctime )
3526
            unset( $rdatePart['tz'] );
3527
          $parno = ( isset( $theRdate['params']['VALUE'] ) && ( 'DATE' == isset( $theRdate['params']['VALUE'] ))) ? 3 : null;
3528
          $formatted = iCalUtilityFunctions::_date2strdate( $rdatePart, $parno );
3529
          if( $utctime || !empty( $theRdate['params']['TZID'] ))
3530
            $formatted = str_replace( 'Z', '', $formatted);
3531
          $contentPart .= $formatted;
3532
        }
3533
        $content .= $contentPart;
3534
        if( $rno < $cnt )
3535
          $content .= ',';
3536
        $rno++;
3537
      }
3538
      $output    .= $this->_createElement( 'RDATE', $attributes, $content );
3539
    }
3540
    return $output;
3541
  }
3542
/**
3543
 * set calendar component property rdate
3544
 *
3545
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3546
 * @since 2.16.21 - 2013-06-23
3547
 * @param array $rdates
3548
 * @param array $params, optional
3549
 * @param integer $index, optional
3550
 * @return bool
3551
 */
3552
  function setRdate( $rdates, $params=FALSE, $index=FALSE ) {
3553
    if( empty( $rdates )) {
3554
      if( $this->getConfig( 'allowEmpty' )) {
3555
        iCalUtilityFunctions::_setMval( $this->rdate, '', $params, FALSE, $index );
3556
        return TRUE;
3557
      }
3558
      else
3559
        return FALSE;
3560
    }
3561
    $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )));
3562
    if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) {
3563
      unset( $input['params']['TZID'] );
3564
      $input['params']['VALUE'] = 'DATE-TIME';
3565
    }
3566
    $zArr = array( 'GMT', 'UTC', 'Z' );
3567
    $toZ = ( isset( $params['TZID'] ) && in_array( strtoupper( $params['TZID'] ), $zArr )) ? TRUE : FALSE;
3568
            /*  check if PERIOD, if not set */
3569
    if((!isset( $input['params']['VALUE'] ) || !in_array( $input['params']['VALUE'], array( 'DATE', 'PERIOD' ))) &&
3570
          isset( $rdates[0] )    && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) &&
3571
          isset( $rdates[0][0] ) &&    isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) &&
3572
    (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) ||
3573
                                      iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) ||
3574
                                    ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] )))))  &&
3575
     ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] ))))))
3576
      $input['params']['VALUE'] = 'PERIOD';
3577
            /* check 1:st date, upd. $parno (opt) and save ev. timezone **/
3578
    $date  = reset( $rdates );
3579
    if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD
3580
      $date  = reset( $date );
3581
    iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] );
3582
    iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default
3583
    foreach( $rdates as $rpix => $theRdate ) {
3584
      $inputa = null;
3585
      iCalUtilityFunctions::_strDate2arr( $theRdate );
3586
      if( is_array( $theRdate )) {
3587
        if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD
3588
          foreach( $theRdate as $rix => $rPeriod ) {
3589
            iCalUtilityFunctions::_strDate2arr( $theRdate );
3590
            if( is_array( $rPeriod )) {
3591
              if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) {    // timestamp
3592
                if( isset( $rPeriod['tz'] ) && !iCalUtilityFunctions::_isOffset( $rPeriod['tz'] )) {
3593
                  if( isset( $input['params']['TZID'] ))
3594
                    $rPeriod['tz'] = $input['params']['TZID'];
3595
                  else
3596
                    $input['params']['TZID'] = $rPeriod['tz'];
3597
                }
3598
                $inputab = iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno );
3599
              }
3600
              elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) {
3601
                $d = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_chkDateArr( $rPeriod, $parno ) : iCalUtilityFunctions::_chkDateArr( $rPeriod, 6 );
3602
                if( isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
3603
                  $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
3604
                  $inputab = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
3605
                  unset( $inputab['unparsedtext'] );
3606
                }
3607
                else
3608
                  $inputab = $d;
3609
              }
3610
              elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) { // text-date
3611
                $inputab   = iCalUtilityFunctions::_strdate2date( reset( $rPeriod ), $parno );
3612
                unset( $inputab['unparsedtext'] );
3613
              }
3614
              else                                               // array format duration
3615
                $inputab   = iCalUtilityFunctions::_duration2arr( $rPeriod );
3616
            }
3617
            elseif(( 3 <= strlen( trim( $rPeriod ))) &&          // string format duration
3618
                   ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) {
3619
              if( 'P' != $rPeriod[0] )
3620
                $rPeriod   = substr( $rPeriod, 1 );
3621
              $inputab     = iCalUtilityFunctions::_durationStr2arr( $rPeriod );
3622
            }
3623
            elseif( 8 <= strlen( trim( $rPeriod ))) {            // text date ex. 2006-08-03 10:12:18
3624
              $inputab     = iCalUtilityFunctions::_strdate2date( $rPeriod, $parno );
3625
              unset( $inputab['unparsedtext'] );
3626
            }
3627
            if(( 0 == $rpix ) && ( 0 == $rix )) {
3628
              if( isset( $inputab['tz'] ) && in_array( strtoupper( $inputab['tz'] ), $zArr )) {
3629
                $inputab['tz'] = 'Z';
3630
                $toZ = TRUE;
3631
              }
3632
            }
3633
            else {
3634
              if( isset( $inputa[0]['tz'] ) && ( 'Z' == $inputa[0]['tz'] ) && isset( $inputab['year'] ))
3635
                $inputab['tz'] = 'Z';
3636
              else
3637
                unset( $inputab['tz'] );
3638
            }
3639
            if( $toZ && isset( $inputab['year'] ) )
3640
              $inputab['tz'] = 'Z';
3641
            $inputa[]      = $inputab;
3642
          }
3643
        } // PERIOD end
3644
        elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) {    // timestamp
3645
          if( isset( $theRdate['tz'] ) && !iCalUtilityFunctions::_isOffset( $theRdate['tz'] )) {
3646
            if( isset( $input['params']['TZID'] ))
3647
              $theRdate['tz'] = $input['params']['TZID'];
3648
            else
3649
              $input['params']['TZID'] = $theRdate['tz'];
3650
          }
3651
          $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno );
3652
        }
3653
        else {                                                                  // date[-time]
3654
          $inputa = iCalUtilityFunctions::_chkDateArr( $theRdate, $parno );
3655
          if( isset( $inputa['tz'] ) && ( 'Z' != $inputa['tz'] ) && iCalUtilityFunctions::_isOffset( $inputa['tz'] )) {
3656
            $strdate = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $inputa['year'], $inputa['month'], $inputa['day'], $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
3657
            $inputa  = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
3658
            unset( $inputa['unparsedtext'] );
3659
          }
3660
        }
3661
      }
3662
      elseif( 8 <= strlen( trim( $theRdate ))) {                 // text date ex. 2006-08-03 10:12:18
3663
        $inputa       = iCalUtilityFunctions::_strdate2date( $theRdate, $parno );
3664
        unset( $inputa['unparsedtext'] );
3665
        if( $toZ )
3666
          $inputa['tz'] = 'Z';
3667
      }
3668
      if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD
3669
        if(( 0 == $rpix ) && !$toZ )
3670
          $toZ = ( isset( $inputa['tz'] ) && in_array( strtoupper( $inputa['tz'] ), $zArr )) ? TRUE : FALSE;
3671
        if( $toZ )
3672
          $inputa['tz']    = 'Z';
3673
        if( 3 == $parno )
3674
          unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] );
3675
        elseif( isset( $inputa['tz'] ))
3676
          $inputa['tz']    = (string) $inputa['tz'];
3677
        if( isset( $input['params']['TZID'] ) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))))
3678
          if( !$toZ )
3679
            unset( $inputa['tz'] );
3680
      }
3681
      $input['value'][]    = $inputa;
3682
    }
3683
    if( 3 == $parno ) {
3684
      $input['params']['VALUE'] = 'DATE';
3685
      unset( $input['params']['TZID'] );
3686
    }
3687
    if( $toZ )
3688
      unset( $input['params']['TZID'] );
3689
    iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index );
3690
    return TRUE;
3691
  }
3692
/*********************************************************************************/
3693
/**
3694
 * Property Name: RECURRENCE-ID
3695
 */
3696
/**
3697
 * creates formatted output for calendar component property recurrence-id
3698
 *
3699
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3700
 * @since 2.14.4 - 2012-09-26
3701
 * @return string
3702
 */
3703
  function createRecurrenceid() {
3704
    if( empty( $this->recurrenceid )) return FALSE;
3705
    if( empty( $this->recurrenceid['value'] ))
3706
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE;
3707
    $parno      = ( isset( $this->recurrenceid['params']['VALUE'] ) && ( 'DATE' == $this->recurrenceid['params']['VALUE'] )) ? 3 : null;
3708
    $formatted  = iCalUtilityFunctions::_date2strdate( $this->recurrenceid['value'], $parno );
3709
    $attributes = $this->_createParams( $this->recurrenceid['params'] );
3710
    return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted );
3711
  }
3712
/**
3713
 * set calendar component property recurrence-id
3714
 *
3715
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3716
 * @since 2.16.21 - 2013-06-23
3717
 * @param mixed $year
3718
 * @param mixed $month optional
3719
 * @param int $day optional
3720
 * @param int $hour optional
3721
 * @param int $min optional
3722
 * @param int $sec optional
3723
 * @param array $params optional
3724
 * @return bool
3725
 */
3726
  function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) {
3727
    if( empty( $year )) {
3728
      if( $this->getConfig( 'allowEmpty' )) {
3729
        $this->recurrenceid = array( 'value' => '', 'params' => null );
3730
        return TRUE;
3731
      }
3732
      else
3733
        return FALSE;
3734
    }
3735
    $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' ));
3736
    return TRUE;
3737
  }
3738
/*********************************************************************************/
3739
/**
3740
 * Property Name: RELATED-TO
3741
 */
3742
/**
3743
 * creates formatted output for calendar component property related-to
3744
 *
3745
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3746
 * @since 2.16.21 - 2013-05-25
3747
 * @return string
3748
 */
3749
  function createRelatedTo() {
3750
    if( empty( $this->relatedto )) return FALSE;
3751
    $output = null;
3752
    foreach( $this->relatedto as $relation ) {
3753
      if( !empty( $relation['value'] ))
3754
        $output .= $this->_createElement( 'RELATED-TO', $this->_createParams( $relation['params'] ), iCalUtilityFunctions::_strrep( $relation['value'], $this->format, $this->nl ));
3755
      elseif( $this->getConfig( 'allowEmpty' ))
3756
        $output .= $this->_createElement( 'RELATED-TO' );
3757
    }
3758
    return $output;
3759
  }
3760
/**
3761
 * set calendar component property related-to
3762
 *
3763
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3764
 * @since 2.16.21 - 2013-06-23
3765
 * @param float $relid
3766
 * @param array $params, optional
3767
 * @param index $index, optional
3768
 * @return bool
3769
 */
3770
  function setRelatedTo( $value, $params=FALSE, $index=FALSE ) {
3771
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3772
    iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default
3773
    iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index );
3774
    return TRUE;
3775
  }
3776
/*********************************************************************************/
3777
/**
3778
 * Property Name: REPEAT
3779
 */
3780
/**
3781
 * creates formatted output for calendar component property repeat
3782
 *
3783
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3784
 * @since 2.9.3 - 2011-05-14
3785
 * @return string
3786
 */
3787
  function createRepeat() {
3788
    if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE;
3789
    if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] )))
3790
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE;
3791
    $attributes = $this->_createParams( $this->repeat['params'] );
3792
    return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] );
3793
  }
3794
/**
3795
 * set calendar component property repeat
3796
 *
3797
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3798
 * @since 2.16.21 - 2013-06-23
3799
 * @param string $value
3800
 * @param array $params optional
3801
 * @return void
3802
 */
3803
  function setRepeat( $value, $params=FALSE ) {
3804
    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3805
    $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3806
    return TRUE;
3807
  }
3808
/*********************************************************************************/
3809
/**
3810
 * Property Name: REQUEST-STATUS
3811
 */
3812
/**
3813
 * creates formatted output for calendar component property request-status
3814
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3815
 * @since 2.16.2 - 2012-12-18
3816
 * @return string
3817
 */
3818
  function createRequestStatus() {
3819
    if( empty( $this->requeststatus )) return FALSE;
3820
    $output = null;
3821
    foreach( $this->requeststatus as $rstat ) {
3822
      if( empty( $rstat['value']['statcode'] )) {
3823
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'REQUEST-STATUS' );
3824
        continue;
3825
      }
3826
      $attributes  = $this->_createParams( $rstat['params'], array( 'LANGUAGE' ));
3827
      $content     = number_format( (float) $rstat['value']['statcode'], 2, '.', '');
3828
      $content    .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['text'], $this->format, $this->nl );
3829
      if( isset( $rstat['value']['extdata'] ))
3830
        $content  .= ';'.iCalUtilityFunctions::_strrep( $rstat['value']['extdata'], $this->format, $this->nl );
3831
      $output     .= $this->_createElement( 'REQUEST-STATUS', $attributes, $content );
3832
    }
3833
    return $output;
3834
  }
3835
/**
3836
 * set calendar component property request-status
3837
 *
3838
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3839
 * @since 2.16.21 - 2013-06-23
3840
 * @param float $statcode
3841
 * @param string $text
3842
 * @param string $extdata, optional
3843
 * @param array $params, optional
3844
 * @param integer $index, optional
3845
 * @return bool
3846
 */
3847
  function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $index=FALSE ) {
3848
    if( empty( $statcode ) || empty( $text )) if( $this->getConfig( 'allowEmpty' )) $statcode = $text = ''; else return FALSE;
3849
    $input              = array( 'statcode' => $statcode, 'text' => $text );
3850
    if( $extdata )
3851
      $input['extdata'] = $extdata;
3852
    iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index );
3853
    return TRUE;
3854
  }
3855
/*********************************************************************************/
3856
/**
3857
 * Property Name: RESOURCES
3858
 */
3859
/**
3860
 * creates formatted output for calendar component property resources
3861
 *
3862
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3863
 * @since 2.16.2 - 2012-12-18
3864
 * @return string
3865
 */
3866
  function createResources() {
3867
    if( empty( $this->resources )) return FALSE;
3868
    $output = null;
3869
    foreach( $this->resources as $resource ) {
3870
      if( empty( $resource['value'] )) {
3871
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'RESOURCES' );
3872
        continue;
3873
      }
3874
      $attributes  = $this->_createParams( $resource['params'], array( 'ALTREP', 'LANGUAGE' ));
3875
      if( is_array( $resource['value'] )) {
3876
        foreach( $resource['value'] as $rix => $resourcePart )
3877
          $resource['value'][$rix] = iCalUtilityFunctions::_strrep( $resourcePart, $this->format, $this->nl );
3878
        $content   = implode( ',', $resource['value'] );
3879
      }
3880
      else
3881
        $content   = iCalUtilityFunctions::_strrep( $resource['value'], $this->format, $this->nl );
3882
      $output     .= $this->_createElement( 'RESOURCES', $attributes, $content );
3883
    }
3884
    return $output;
3885
  }
3886
/**
3887
 * set calendar component property recources
3888
 *
3889
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3890
 * @since 2.16.21 - 2013-06-23
3891
 * @param mixed $value
3892
 * @param array $params, optional
3893
 * @param integer $index, optional
3894
 * @return bool
3895
 */
3896
  function setResources( $value, $params=FALSE, $index=FALSE ) {
3897
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3898
    iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index );
3899
    return TRUE;
3900
  }
3901
/*********************************************************************************/
3902
/**
3903
 * Property Name: RRULE
3904
 */
3905
/**
3906
 * creates formatted output for calendar component property rrule
3907
 *
3908
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3909
 * @since 2.4.8 - 2008-10-21
3910
 * @return string
3911
 */
3912
  function createRrule() {
3913
    if( empty( $this->rrule )) return FALSE;
3914
    return $this->_format_recur( 'RRULE', $this->rrule );
3915
  }
3916
/**
3917
 * set calendar component property rrule
3918
 *
3919
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3920
 * @since 2.16.21 - 2013-06-23
3921
 * @param array $rruleset
3922
 * @param array $params, optional
3923
 * @param integer $index, optional
3924
 * @return void
3925
 */
3926
  function setRrule( $rruleset, $params=FALSE, $index=FALSE ) {
3927
    if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = ''; else return FALSE;
3928
    iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index );
3929
    return TRUE;
3930
  }
3931
/*********************************************************************************/
3932
/**
3933
 * Property Name: SEQUENCE
3934
 */
3935
/**
3936
 * creates formatted output for calendar component property sequence
3937
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3938
 * @since 2.9.3 - 2011-05-14
3939
 * @return string
3940
 */
3941
  function createSequence() {
3942
    if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE;
3943
    if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) &&
3944
       ( '0' != $this->sequence['value'] ))
3945
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE;
3946
    $attributes = $this->_createParams( $this->sequence['params'] );
3947
    return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] );
3948
  }
3949
/**
3950
 * set calendar component property sequence
3951
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3952
 * @since 2.10.8 - 2011-09-19
3953
 * @param int $value optional
3954
 * @param array $params optional
3955
 * @return bool
3956
 */
3957
  function setSequence( $value=FALSE, $params=FALSE ) {
3958
    if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value ))
3959
      $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0';
3960
    $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3961
    return TRUE;
3962
  }
3963
/*********************************************************************************/
3964
/**
3965
 * Property Name: STATUS
3966
 */
3967
/**
3968
 * creates formatted output for calendar component property status
3969
 *
3970
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3971
 * @since 2.4.8 - 2008-10-21
3972
 * @return string
3973
 */
3974
  function createStatus() {
3975
    if( empty( $this->status )) return FALSE;
3976
    if( empty( $this->status['value'] ))
3977
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'STATUS' ) : FALSE;
3978
    $attributes = $this->_createParams( $this->status['params'] );
3979
    return $this->_createElement( 'STATUS', $attributes, $this->status['value'] );
3980
  }
3981
/**
3982
 * set calendar component property status
3983
 *
3984
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
3985
 * @since 2.16.21 - 2013-06-23
3986
 * @param string $value
3987
 * @param array $params optional
3988
 * @return bool
3989
 */
3990
  function setStatus( $value, $params=FALSE ) {
3991
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
3992
    $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
3993
    return TRUE;
3994
  }
3995
/*********************************************************************************/
3996
/**
3997
 * Property Name: SUMMARY
3998
 */
3999
/**
4000
 * creates formatted output for calendar component property summary
4001
 *
4002
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4003
 * @since 2.16.2 - 2012-12-18
4004
 * @return string
4005
 */
4006
  function createSummary() {
4007
    if( empty( $this->summary )) return FALSE;
4008
    if( empty( $this->summary['value'] ))
4009
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SUMMARY' ) : FALSE;
4010
    $attributes = $this->_createParams( $this->summary['params'], array( 'ALTREP', 'LANGUAGE' ));
4011
    $content    = iCalUtilityFunctions::_strrep( $this->summary['value'], $this->format, $this->nl );
4012
    return $this->_createElement( 'SUMMARY', $attributes, $content );
4013
  }
4014
/**
4015
 * set calendar component property summary
4016
 *
4017
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4018
 * @since 2.16.21 - 2013-06-23
4019
 * @param string $value
4020
 * @param string $params optional
4021
 * @return bool
4022
 */
4023
  function setSummary( $value, $params=FALSE ) {
4024
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4025
    $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4026
    return TRUE;
4027
  }
4028
/*********************************************************************************/
4029
/**
4030
 * Property Name: TRANSP
4031
 */
4032
/**
4033
 * creates formatted output for calendar component property transp
4034
 *
4035
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4036
 * @since 2.4.8 - 2008-10-21
4037
 * @return string
4038
 */
4039
  function createTransp() {
4040
    if( empty( $this->transp )) return FALSE;
4041
    if( empty( $this->transp['value'] ))
4042
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRANSP' ) : FALSE;
4043
    $attributes = $this->_createParams( $this->transp['params'] );
4044
    return $this->_createElement( 'TRANSP', $attributes, $this->transp['value'] );
4045
  }
4046
/**
4047
 * set calendar component property transp
4048
 *
4049
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4050
 * @since 2.16.21 - 2013-06-23
4051
 * @param string $value
4052
 * @param string $params optional
4053
 * @return bool
4054
 */
4055
  function setTransp( $value, $params=FALSE ) {
4056
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4057
    $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4058
    return TRUE;
4059
  }
4060
/*********************************************************************************/
4061
/**
4062
 * Property Name: TRIGGER
4063
 */
4064
/**
4065
 * creates formatted output for calendar component property trigger
4066
 *
4067
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4068
 * @since 2.4.16 - 2008-10-21
4069
 * @return string
4070
 */
4071
  function createTrigger() {
4072
    if( empty( $this->trigger )) return FALSE;
4073
    if( empty( $this->trigger['value'] ))
4074
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TRIGGER' ) : FALSE;
4075
    $content = $attributes = null;
4076
    if( isset( $this->trigger['value']['year'] )   &&
4077
        isset( $this->trigger['value']['month'] )  &&
4078
        isset( $this->trigger['value']['day'] ))
4079
      $content      .= iCalUtilityFunctions::_date2strdate( $this->trigger['value'] );
4080
    else {
4081
      if( TRUE !== $this->trigger['value']['relatedStart'] )
4082
        $attributes .= $this->intAttrDelimiter.'RELATED=END';
4083
      if( $this->trigger['value']['before'] )
4084
        $content    .= '-';
4085
      $content      .= iCalUtilityFunctions::_duration2str( $this->trigger['value'] );
4086
    }
4087
    $attributes     .= $this->_createParams( $this->trigger['params'] );
4088
    return $this->_createElement( 'TRIGGER', $attributes, $content );
4089
  }
4090
/**
4091
 * set calendar component property trigger
4092
 *
4093
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4094
 * @since 2.16.21 - 2013-06-23
4095
 * @param mixed $year
4096
 * @param mixed $month optional
4097
 * @param int $day optional
4098
 * @param int $week optional
4099
 * @param int $hour optional
4100
 * @param int $min optional
4101
 * @param int $sec optional
4102
 * @param bool $relatedStart optional
4103
 * @param bool $before optional
4104
 * @param array $params optional
4105
 * @return bool
4106
 */
4107
  function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) {
4108
    if( empty( $year ) && ( empty( $month ) || is_array( $month )) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec ))
4109
      if( $this->getConfig( 'allowEmpty' )) {
4110
        $this->trigger = array( 'value' => '', 'params' => iCalUtilityFunctions::_setParams( $month ) );
4111
        return TRUE;
4112
      }
4113
      else
4114
        return FALSE;
4115
    if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp UTC
4116
      $params = iCalUtilityFunctions::_setParams( $month );
4117
      $date   = iCalUtilityFunctions::_timestamp2date( $year, 7 );
4118
      foreach( $date as $k => $v )
4119
        $$k = $v;
4120
    }
4121
    elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) {
4122
      $params = iCalUtilityFunctions::_setParams( $month );
4123
      if(!(array_key_exists( 'year',  $year ) &&   // exclude date-time
4124
           array_key_exists( 'month', $year ) &&
4125
           array_key_exists( 'day',   $year ))) {  // when this must be a duration
4126
        if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] )))
4127
          $relatedStart = FALSE;
4128
        else
4129
          $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE;
4130
        $before         = ( array_key_exists( 'before', $year )       && ( TRUE !== $year['before'] ))       ? FALSE : TRUE;
4131
      }
4132
      $SSYY  = ( array_key_exists( 'year',  $year )) ? $year['year']  : null;
4133
      $month = ( array_key_exists( 'month', $year )) ? $year['month'] : null;
4134
      $day   = ( array_key_exists( 'day',   $year )) ? $year['day']   : null;
4135
      $week  = ( array_key_exists( 'week',  $year )) ? $year['week']  : null;
4136
      $hour  = ( array_key_exists( 'hour',  $year )) ? $year['hour']  : 0; //null;
4137
      $min   = ( array_key_exists( 'min',   $year )) ? $year['min']   : 0; //null;
4138
      $sec   = ( array_key_exists( 'sec',   $year )) ? $year['sec']   : 0; //null;
4139
      $year  = $SSYY;
4140
    }
4141
    elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) {  // duration or date in a string
4142
      $params = iCalUtilityFunctions::_setParams( $month );
4143
      if( in_array( $year{0}, array( 'P', '+', '-' ))) { // duration
4144
        $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE;
4145
        $before       = ( '-'  == $year[0] ) ? TRUE : FALSE;
4146
        if(     'P'  != $year[0] )
4147
          $year       = substr( $year, 1 );
4148
        $date         = iCalUtilityFunctions::_durationStr2arr( $year);
4149
      }
4150
      else   // date
4151
        $date    = iCalUtilityFunctions::_strdate2date( $year, 7 );
4152
      unset( $year, $month, $day, $date['unparsedtext'] );
4153
      if( empty( $date ))
4154
        $sec = 0;
4155
      else
4156
        foreach( $date as $k => $v )
4157
          $$k = $v;
4158
    }
4159
    else // single values in function input parameters
4160
      $params = iCalUtilityFunctions::_setParams( $params );
4161
    if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date
4162
      $params['VALUE'] = 'DATE-TIME';
4163
      $hour = ( $hour ) ? $hour : 0;
4164
      $min  = ( $min  ) ? $min  : 0;
4165
      $sec  = ( $sec  ) ? $sec  : 0;
4166
      $this->trigger = array( 'params' => $params );
4167
      $this->trigger['value'] = array( 'year'  => $year
4168
                                     , 'month' => $month
4169
                                     , 'day'   => $day
4170
                                     , 'hour'  => $hour
4171
                                     , 'min'   => $min
4172
                                     , 'sec'   => $sec
4173
                                     , 'tz'    => 'Z' );
4174
      return TRUE;
4175
    }
4176
    elseif(( empty( $year ) && empty( $month )) &&    // duration
4177
           (( !empty( $week ) || ( 0 == $week )) ||
4178
            ( !empty( $day )  || ( 0 == $day  )) ||
4179
            ( !empty( $hour ) || ( 0 == $hour )) ||
4180
            ( !empty( $min )  || ( 0 == $min  )) ||
4181
            ( !empty( $sec )  || ( 0 == $sec  )))) {
4182
      unset( $params['RELATED'] ); // set at output creation (END only)
4183
      unset( $params['VALUE'] );   // 'DURATION' default
4184
      $this->trigger = array( 'params' => $params );
4185
      $this->trigger['value']  = array();
4186
      if( !empty( $week )) $this->trigger['value']['week'] = $week;
4187
      if( !empty( $day  )) $this->trigger['value']['day']  = $day;
4188
      if( !empty( $hour )) $this->trigger['value']['hour'] = $hour;
4189
      if( !empty( $min  )) $this->trigger['value']['min']  = $min;
4190
      if( !empty( $sec  )) $this->trigger['value']['sec']  = $sec;
4191
      if( empty( $this->trigger['value'] )) {
4192
        $this->trigger['value']['sec'] = 0;
4193
        $before                        = FALSE;
4194
      }
4195
      else
4196
        $this->trigger['value'] = iCalUtilityFunctions::_duration2arr( $this->trigger['value'] );
4197
      $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE;
4198
      $before       = ( FALSE !== $before )       ? TRUE : FALSE;
4199
      $this->trigger['value']['relatedStart'] = $relatedStart;
4200
      $this->trigger['value']['before']       = $before;
4201
      return TRUE;
4202
    }
4203
    return FALSE;
4204
  }
4205
/*********************************************************************************/
4206
/**
4207
 * Property Name: TZID
4208
 */
4209
/**
4210
 * creates formatted output for calendar component property tzid
4211
 *
4212
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4213
 * @since 2.16.2 - 2012-12-18
4214
 * @return string
4215
 */
4216
  function createTzid() {
4217
    if( empty( $this->tzid )) return FALSE;
4218
    if( empty( $this->tzid['value'] ))
4219
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZID' ) : FALSE;
4220
    $attributes = $this->_createParams( $this->tzid['params'] );
4221
    return $this->_createElement( 'TZID', $attributes, iCalUtilityFunctions::_strrep( $this->tzid['value'], $this->format, $this->nl ));
4222
  }
4223
/**
4224
 * set calendar component property tzid
4225
 *
4226
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4227
 * @since 2.16.21 - 2013-06-23
4228
 * @param string $value
4229
 * @param array $params optional
4230
 * @return bool
4231
 */
4232
  function setTzid( $value, $params=FALSE ) {
4233
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4234
    $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4235
    return TRUE;
4236
  }
4237
/*********************************************************************************/
4238
/**
4239
 * .. .
4240
 * Property Name: TZNAME
4241
 */
4242
/**
4243
 * creates formatted output for calendar component property tzname
4244
 *
4245
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4246
 * @since 2.16.2 - 2012-12-18
4247
 * @return string
4248
 */
4249
  function createTzname() {
4250
    if( empty( $this->tzname )) return FALSE;
4251
    $output = null;
4252
    foreach( $this->tzname as $theName ) {
4253
      if( !empty( $theName['value'] )) {
4254
        $attributes = $this->_createParams( $theName['params'], array( 'LANGUAGE' ));
4255
        $output    .= $this->_createElement( 'TZNAME', $attributes, iCalUtilityFunctions::_strrep( $theName['value'], $this->format, $this->nl ));
4256
      }
4257
      elseif( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( 'TZNAME' );
4258
    }
4259
    return $output;
4260
  }
4261
/**
4262
 * set calendar component property tzname
4263
 *
4264
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4265
 * @since 2.16.21 - 2013-06-23
4266
 * @param string $value
4267
 * @param string $params, optional
4268
 * @param integer $index, optional
4269
 * @return bool
4270
 */
4271
  function setTzname( $value, $params=FALSE, $index=FALSE ) {
4272
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4273
    iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index );
4274
    return TRUE;
4275
  }
4276
/*********************************************************************************/
4277
/**
4278
 * Property Name: TZOFFSETFROM
4279
 */
4280
/**
4281
 * creates formatted output for calendar component property tzoffsetfrom
4282
 *
4283
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4284
 * @since 2.4.8 - 2008-10-21
4285
 * @return string
4286
 */
4287
  function createTzoffsetfrom() {
4288
    if( empty( $this->tzoffsetfrom )) return FALSE;
4289
    if( empty( $this->tzoffsetfrom['value'] ))
4290
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETFROM' ) : FALSE;
4291
    $attributes = $this->_createParams( $this->tzoffsetfrom['params'] );
4292
    return $this->_createElement( 'TZOFFSETFROM', $attributes, $this->tzoffsetfrom['value'] );
4293
  }
4294
/**
4295
 * set calendar component property tzoffsetfrom
4296
 *
4297
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4298
 * @since 2.16.21 - 2013-06-23
4299
 * @param string $value
4300
 * @param string $params optional
4301
 * @return bool
4302
 */
4303
  function setTzoffsetfrom( $value, $params=FALSE ) {
4304
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4305
    $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4306
    return TRUE;
4307
  }
4308
/*********************************************************************************/
4309
/**
4310
 * Property Name: TZOFFSETTO
4311
 */
4312
/**
4313
 * creates formatted output for calendar component property tzoffsetto
4314
 *
4315
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4316
 * @since 2.4.8 - 2008-10-21
4317
 * @return string
4318
 */
4319
  function createTzoffsetto() {
4320
    if( empty( $this->tzoffsetto )) return FALSE;
4321
    if( empty( $this->tzoffsetto['value'] ))
4322
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZOFFSETTO' ) : FALSE;
4323
    $attributes = $this->_createParams( $this->tzoffsetto['params'] );
4324
    return $this->_createElement( 'TZOFFSETTO', $attributes, $this->tzoffsetto['value'] );
4325
  }
4326
/**
4327
 * set calendar component property tzoffsetto
4328
 *
4329
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4330
 * @since 2.16.21 - 2013-06-23
4331
 * @param string $value
4332
 * @param string $params optional
4333
 * @return bool
4334
 */
4335
  function setTzoffsetto( $value, $params=FALSE ) {
4336
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4337
    $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4338
    return TRUE;
4339
  }
4340
/*********************************************************************************/
4341
/**
4342
 * Property Name: TZURL
4343
 */
4344
/**
4345
 * creates formatted output for calendar component property tzurl
4346
 *
4347
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4348
 * @since 2.4.8 - 2008-10-21
4349
 * @return string
4350
 */
4351
  function createTzurl() {
4352
    if( empty( $this->tzurl )) return FALSE;
4353
    if( empty( $this->tzurl['value'] ))
4354
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'TZURL' ) : FALSE;
4355
    $attributes = $this->_createParams( $this->tzurl['params'] );
4356
    return $this->_createElement( 'TZURL', $attributes, $this->tzurl['value'] );
4357
  }
4358
/**
4359
 * set calendar component property tzurl
4360
 *
4361
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4362
 * @since 2.16.21 - 2013-06-23
4363
 * @param string $value
4364
 * @param string $params optional
4365
 * @return boll
4366
 */
4367
  function setTzurl( $value, $params=FALSE ) {
4368
    if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4369
    $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4370
    return TRUE;
4371
  }
4372
/*********************************************************************************/
4373
/**
4374
 * Property Name: UID
4375
 */
4376
/**
4377
 * creates formatted output for calendar component property uid
4378
 *
4379
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4380
 * @since 0.9.7 - 2006-11-20
4381
 * @return string
4382
 */
4383
  function createUid() {
4384
    if( 0 >= count( $this->uid ))
4385
      $this->_makeuid();
4386
    $attributes = $this->_createParams( $this->uid['params'] );
4387
    return $this->_createElement( 'UID', $attributes, $this->uid['value'] );
4388
  }
4389
/**
4390
 * create an unique id for this calendar component object instance
4391
 *
4392
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4393
 * @since 2.2.7 - 2007-09-04
4394
 * @return void
4395
 */
4396
  function _makeUid() {
4397
    $date   = date('Ymd\THisT');
4398
    $unique = substr(microtime(), 2, 4);
4399
    $base   = 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPrRsStTuUvVxXuUvVwWzZ1234567890';
4400
    $start  = 0;
4401
    $end    = strlen( $base ) - 1;
4402
    $length = 6;
4403
    $str    = null;
4404
    for( $p = 0; $p < $length; $p++ )
4405
      $unique .= $base{mt_rand( $start, $end )};
4406
    $this->uid = array( 'params' => null );
4407
    $this->uid['value']  = $date.'-'.$unique.'@'.$this->getConfig( 'unique_id' );
4408
  }
4409
/**
4410
 * set calendar component property uid
4411
 *
4412
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4413
 * @since 2.4.8 - 2008-11-04
4414
 * @param string $value
4415
 * @param string $params optional
4416
 * @return bool
4417
 */
4418
  function setUid( $value, $params=FALSE ) {
4419
    if( empty( $value )) return FALSE; // no allowEmpty check here !!!!
4420
    $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4421
    return TRUE;
4422
  }
4423
/*********************************************************************************/
4424
/**
4425
 * Property Name: URL
4426
 */
4427
/**
4428
 * creates formatted output for calendar component property url
4429
 *
4430
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4431
 * @since 2.4.8 - 2008-10-21
4432
 * @return string
4433
 */
4434
  function createUrl() {
4435
    if( empty( $this->url )) return FALSE;
4436
    if( empty( $this->url['value'] ))
4437
      return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'URL' ) : FALSE;
4438
    $attributes = $this->_createParams( $this->url['params'] );
4439
    return $this->_createElement( 'URL', $attributes, $this->url['value'] );
4440
  }
4441
/**
4442
 * set calendar component property url
4443
 *
4444
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4445
 * @since 2.16.21 - 2013-06-23
4446
 * @param string $value
4447
 * @param string $params optional
4448
 * @return bool
4449
 */
4450
  function setUrl( $value, $params=FALSE ) {
4451
    if( !empty( $value )) {
4452
      if( !filter_var( $value, FILTER_VALIDATE_URL ) && ( 'urn' != strtolower( substr( $value, 0, 3 ))))
4453
        return FALSE;
4454
    }
4455
    elseif( $this->getConfig( 'allowEmpty' ))
4456
      $value = '';
4457
    else
4458
      return FALSE;
4459
    $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params ));
4460
    return TRUE;
4461
  }
4462
/*********************************************************************************/
4463
/**
4464
 * Property Name: x-prop
4465
 */
4466
/**
4467
 * creates formatted output for calendar component property x-prop
4468
 *
4469
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4470
 * @since 2.16.2 - 2012-12-18
4471
 * @return string
4472
 */
4473
  function createXprop() {
4474
    if( empty( $this->xprop )) return FALSE;
4475
    $output = null;
4476
    foreach( $this->xprop as $label => $xpropPart ) {
4477
      if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) {
4478
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label );
4479
        continue;
4480
      }
4481
      $attributes = $this->_createParams( $xpropPart['params'], array( 'LANGUAGE' ));
4482
      if( is_array( $xpropPart['value'] )) {
4483
        foreach( $xpropPart['value'] as $pix => $theXpart )
4484
          $xpropPart['value'][$pix] = iCalUtilityFunctions::_strrep( $theXpart, $this->format, $this->format );
4485
        $xpropPart['value']  = implode( ',', $xpropPart['value'] );
4486
      }
4487
      else
4488
        $xpropPart['value'] = iCalUtilityFunctions::_strrep( $xpropPart['value'], $this->format, $this->nl );
4489
      $output    .= $this->_createElement( $label, $attributes, $xpropPart['value'] );
4490
    }
4491
    return $output;
4492
  }
4493
/**
4494
 * set calendar component property x-prop
4495
 *
4496
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4497
 * @since 2.16.21 - 2013-06-23
4498
 * @param string $label
4499
 * @param mixed $value
4500
 * @param array $params optional
4501
 * @return bool
4502
 */
4503
  function setXprop( $label, $value, $params=FALSE ) {
4504
    if( empty( $label ))
4505
      return FALSE;
4506
    if( 'X-' != strtoupper( substr( $label, 0, 2 )))
4507
      return FALSE;
4508
    if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = ''; else return FALSE;
4509
    $xprop           = array( 'value' => $value );
4510
    $xprop['params'] = iCalUtilityFunctions::_setParams( $params );
4511
    if( !is_array( $this->xprop )) $this->xprop = array();
4512
    $this->xprop[strtoupper( $label )] = $xprop;
4513
    return TRUE;
4514
  }
4515
/*********************************************************************************/
4516
/*********************************************************************************/
4517
/**
4518
 * create element format parts
4519
 *
4520
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4521
 * @since 2.0.6 - 2006-06-20
4522
 * @return string
4523
 */
4524
  function _createFormat() {
4525
    $objectname                   = null;
4526
    switch( $this->format ) {
4527
      case 'xcal':
4528
        $objectname               = ( isset( $this->timezonetype )) ?
4529
                                 strtolower( $this->timezonetype )  :  strtolower( $this->objName );
4530
        $this->componentStart1    = $this->elementStart1 = '<';
4531
        $this->componentStart2    = $this->elementStart2 = '>';
4532
        $this->componentEnd1      = $this->elementEnd1   = '</';
4533
        $this->componentEnd2      = $this->elementEnd2   = '>'.$this->nl;
4534
        $this->intAttrDelimiter   = '<!-- -->';
4535
        $this->attributeDelimiter = $this->nl;
4536
        $this->valueInit          = null;
4537
        break;
4538
      default:
4539
        $objectname               = ( isset( $this->timezonetype )) ?
4540
                                 strtoupper( $this->timezonetype )  :  strtoupper( $this->objName );
4541
        $this->componentStart1    = 'BEGIN:';
4542
        $this->componentStart2    = null;
4543
        $this->componentEnd1      = 'END:';
4544
        $this->componentEnd2      = $this->nl;
4545
        $this->elementStart1      = null;
4546
        $this->elementStart2      = null;
4547
        $this->elementEnd1        = null;
4548
        $this->elementEnd2        = $this->nl;
4549
        $this->intAttrDelimiter   = '<!-- -->';
4550
        $this->attributeDelimiter = ';';
4551
        $this->valueInit          = ':';
4552
        break;
4553
    }
4554
    return $objectname;
4555
  }
4556
/**
4557
 * creates formatted output for calendar component property
4558
 *
4559
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4560
 * @since 2.16.2 - 2012-12-18
4561
 * @param string $label property name
4562
 * @param string $attributes property attributes
4563
 * @param string $content property content (optional)
4564
 * @return string
4565
 */
4566
  function _createElement( $label, $attributes=null, $content=FALSE ) {
4567
    switch( $this->format ) {
4568
      case 'xcal':
4569
        $label = strtolower( $label );
4570
        break;
4571
      default:
4572
        $label = strtoupper( $label );
4573
        break;
4574
    }
4575
    $output = $this->elementStart1.$label;
4576
    $categoriesAttrLang = null;
4577
    $attachInlineBinary = FALSE;
4578
    $attachfmttype      = null;
4579
    if (( 'xcal' == $this->format) && ( 'x-' == substr( $label, 0, 2 ))) {
4580
      $this->xcaldecl[] = array( 'xmldecl'  => 'ELEMENT'
4581
                               , 'ref'      => $label
4582
                               , 'type2'    => '(#PCDATA)' );
4583
    }
4584
    if( !empty( $attributes ))  {
4585
      $attributes  = trim( $attributes );
4586
      if ( 'xcal' == $this->format ) {
4587
        $attributes2 = explode( $this->intAttrDelimiter, $attributes );
4588
        $attributes  = null;
4589
        foreach( $attributes2 as $aix => $attribute ) {
4590
          $attrKVarr = explode( '=', $attribute );
4591
          if( empty( $attrKVarr[0] ))
4592
            continue;
4593
          if( !isset( $attrKVarr[1] )) {
4594
            $attrValue = $attrKVarr[0];
4595
            $attrKey   = $aix;
4596
          }
4597
          elseif( 2 == count( $attrKVarr)) {
4598
            $attrKey   = strtolower( $attrKVarr[0] );
4599
            $attrValue = $attrKVarr[1];
4600
          }
4601
          else {
4602
            $attrKey   = strtolower( $attrKVarr[0] );
4603
            unset( $attrKVarr[0] );
4604
            $attrValue = implode( '=', $attrKVarr );
4605
          }
4606
          if(( 'attach' == $label ) && ( in_array( $attrKey, array( 'fmttype', 'encoding', 'value' )))) {
4607
            $attachInlineBinary = TRUE;
4608
            if( 'fmttype' == $attrKey )
4609
              $attachfmttype = $attrKey.'='.$attrValue;
4610
            continue;
4611
          }
4612
          elseif(( 'categories' == $label ) && ( 'language' == $attrKey ))
4613
            $categoriesAttrLang = $attrKey.'='.$attrValue;
4614
          else {
4615
            $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
4616
            $attributes .= ( !empty( $attrKey )) ? $attrKey.'=' : null;
4617
            if(( '"' == substr( $attrValue, 0, 1 )) && ( '"' == substr( $attrValue, -1 ))) {
4618
              $attrValue = substr( $attrValue, 1, ( strlen( $attrValue ) - 2 ));
4619
              $attrValue = str_replace( '"', '', $attrValue );
4620
            }
4621
            $attributes .= '"'.htmlspecialchars( $attrValue ).'"';
4622
          }
4623
        }
4624
      }
4625
      else {
4626
        $attributes = str_replace( $this->intAttrDelimiter, $this->attributeDelimiter, $attributes );
4627
      }
4628
    }
4629
    if(( 'xcal' == $this->format) &&
4630
       ((( 'attach' == $label ) && !$attachInlineBinary ) || ( in_array( $label, array( 'tzurl', 'url' ))))) {
4631
      $pos = strrpos($content, "/");
4632
      $docname = ( $pos !== false) ? substr( $content, (1 - strlen( $content ) + $pos )) : $content;
4633
      $this->xcaldecl[] = array( 'xmldecl'  => 'ENTITY'
4634
                               , 'uri'      => $docname
4635
                               , 'ref'      => 'SYSTEM'
4636
                               , 'external' => $content
4637
                               , 'type'     => 'NDATA'
4638
                               , 'type2'    => 'BINERY' );
4639
      $attributes .= ( empty( $attributes )) ? ' ' : $this->attributeDelimiter.' ';
4640
      $attributes .= 'uri="'.$docname.'"';
4641
      $content = null;
4642
      if( 'attach' == $label ) {
4643
        $attributes = str_replace( $this->attributeDelimiter, $this->intAttrDelimiter, $attributes );
4644
        $content = $this->nl.$this->_createElement( 'extref', $attributes, null );
4645
        $attributes = null;
4646
      }
4647
    }
4648
    elseif(( 'xcal' == $this->format) && ( 'attach' == $label ) && $attachInlineBinary ) {
4649
      $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute
4650
    }
4651
    $output .= $attributes;
4652
    if( !$content && ( '0' != $content )) {
4653
      switch( $this->format ) {
4654
        case 'xcal':
4655
          $output .= ' /';
4656
          $output .= $this->elementStart2.$this->nl;
4657
          return $output;
4658
          break;
4659
        default:
4660
          $output .= $this->elementStart2.$this->valueInit;
4661
          return iCalUtilityFunctions::_size75( $output, $this->nl );
4662
          break;
4663
      }
4664
    }
4665
    $output .= $this->elementStart2;
4666
    $output .= $this->valueInit.$content;
4667
    switch( $this->format ) {
4668
      case 'xcal':
4669
        return $output.$this->elementEnd1.$label.$this->elementEnd2;
4670
        break;
4671
      default:
4672
        return iCalUtilityFunctions::_size75( $output, $this->nl );
4673
        break;
4674
    }
4675
  }
4676
/**
4677
 * creates formatted output for calendar component property parameters
4678
 *
4679
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4680
 * @since 2.10.27 - 2012-01-16
4681
 * @param array $params  optional
4682
 * @param array $ctrKeys optional
4683
 * @return string
4684
 */
4685
  function _createParams( $params=array(), $ctrKeys=array() ) {
4686
    if( !is_array( $params ) || empty( $params ))
4687
      $params = array();
4688
    $attrLANG = $attr1 = $attr2 = $lang = null;
4689
    $CNattrKey   = ( in_array( 'CN',       $ctrKeys )) ? TRUE : FALSE ;
4690
    $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ;
4691
    $CNattrExist = $LANGattrExist = FALSE;
4692
    $xparams = array();
4693
    foreach( $params as $paramKey => $paramValue ) {
4694
      if(( FALSE !== strpos( $paramValue, ':' )) ||
4695
         ( FALSE !== strpos( $paramValue, ';' )) ||
4696
         ( FALSE !== strpos( $paramValue, ',' )))
4697
        $paramValue = '"'.$paramValue.'"';
4698
      if( ctype_digit( (string) $paramKey )) {
4699
        $xparams[]          = $paramValue;
4700
        continue;
4701
      }
4702
      $paramKey             = strtoupper( $paramKey );
4703
      if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' )))
4704
        $xparams[$paramKey] = $paramValue;
4705
      else
4706
        $params[$paramKey]  = $paramValue;
4707
    }
4708
    ksort( $xparams, SORT_STRING );
4709
    foreach( $xparams as $paramKey => $paramValue ) {
4710
      if( ctype_digit( (string) $paramKey ))
4711
        $attr2             .= $this->intAttrDelimiter.$paramValue;
4712
      else
4713
        $attr2             .= $this->intAttrDelimiter."$paramKey=$paramValue";
4714
    }
4715
    if( isset( $params['FMTTYPE'] )  && !in_array( 'FMTTYPE', $ctrKeys )) {
4716
      $attr1               .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2;
4717
      $attr2                = null;
4718
    }
4719
    if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING',   $ctrKeys )) {
4720
      if( !empty( $attr2 )) {
4721
        $attr1             .= $attr2;
4722
        $attr2              = null;
4723
      }
4724
      $attr1               .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING'];
4725
    }
4726
    if( isset( $params['VALUE'] )    && !in_array( 'VALUE',   $ctrKeys ))
4727
      $attr1               .= $this->intAttrDelimiter.'VALUE='.$params['VALUE'];
4728
    if( isset( $params['TZID'] )     && !in_array( 'TZID',    $ctrKeys )) {
4729
      $attr1               .= $this->intAttrDelimiter.'TZID='.$params['TZID'];
4730
    }
4731
    if( isset( $params['RANGE'] )    && !in_array( 'RANGE',   $ctrKeys ))
4732
      $attr1               .= $this->intAttrDelimiter.'RANGE='.$params['RANGE'];
4733
    if( isset( $params['RELTYPE'] )  && !in_array( 'RELTYPE', $ctrKeys ))
4734
      $attr1               .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE'];
4735
    if( isset( $params['CN'] )       && $CNattrKey ) {
4736
      $attr1                = $this->intAttrDelimiter.'CN='.$params['CN'];
4737
      $CNattrExist          = TRUE;
4738
    }
4739
    if( isset( $params['DIR'] )      && in_array( 'DIR',      $ctrKeys )) {
4740
      $delim = ( FALSE !== strpos( $params['DIR'], '"' )) ? '' : '"';
4741
      $attr1               .= $this->intAttrDelimiter.'DIR='.$delim.$params['DIR'].$delim;
4742
    }
4743
    if( isset( $params['SENT-BY'] )  && in_array( 'SENT-BY',  $ctrKeys ))
4744
      $attr1               .= $this->intAttrDelimiter.'SENT-BY='.$params['SENT-BY'];
4745
    if( isset( $params['ALTREP'] )   && in_array( 'ALTREP',   $ctrKeys )) {
4746
      $delim = ( FALSE !== strpos( $params['ALTREP'], '"' )) ? '' : '"';
4747
      $attr1               .= $this->intAttrDelimiter.'ALTREP='.$delim.$params['ALTREP'].$delim;
4748
    }
4749
    if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) {
4750
      $attrLANG            .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE'];
4751
      $LANGattrExist        = TRUE;
4752
    }
4753
    if( !$LANGattrExist ) {
4754
      $lang = $this->getConfig( 'language' );
4755
      if(( $CNattrExist || $LANGattrKey ) && $lang )
4756
        $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang;
4757
    }
4758
    return $attr1.$attrLANG.$attr2;
4759
  }
4760
/**
4761
 * creates formatted output for calendar component property data value type recur
4762
 *
4763
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4764
 * @since 2.16.25 - 2013-06-30
4765
 * @param array $recurlabel
4766
 * @param array $recurdata
4767
 * @return string
4768
 */
4769
  function _format_recur( $recurlabel, $recurdata ) {
4770
    $output = null;
4771
    foreach( $recurdata as $therule ) {
4772
      if( empty( $therule['value'] )) {
4773
        if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel );
4774
        continue;
4775
      }
4776
      $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null;
4777
      $content1  = $content2  = null;
4778
      foreach( $therule['value'] as $rulelabel => $rulevalue ) {
4779
        switch( strtoupper( $rulelabel )) {
4780
          case 'FREQ': {
4781
            $content1 .= "FREQ=$rulevalue";
4782
            break;
4783
          }
4784
          case 'UNTIL': {
4785
            $parno     = ( isset( $rulevalue['hour'] )) ? 7 : 3;
4786
            $content2 .= ';UNTIL='.iCalUtilityFunctions::_date2strdate( $rulevalue, $parno );
4787
            break;
4788
          }
4789
          case 'COUNT':
4790
          case 'INTERVAL':
4791
          case 'WKST': {
4792
            $content2 .= ";$rulelabel=$rulevalue";
4793
            break;
4794
          }
4795
          case 'BYSECOND':
4796
          case 'BYMINUTE':
4797
          case 'BYHOUR':
4798
          case 'BYMONTHDAY':
4799
          case 'BYYEARDAY':
4800
          case 'BYWEEKNO':
4801
          case 'BYMONTH':
4802
          case 'BYSETPOS': {
4803
            $content2 .= ";$rulelabel=";
4804
            if( is_array( $rulevalue )) {
4805
              foreach( $rulevalue as $vix => $valuePart ) {
4806
                $content2 .= ( $vix ) ? ',' : null;
4807
                $content2 .= $valuePart;
4808
              }
4809
            }
4810
            else
4811
             $content2 .= $rulevalue;
4812
            break;
4813
          }
4814
          case 'BYDAY': {
4815
            $byday          = array( '' );
4816
            $bx             = 0;
4817
            foreach( $rulevalue as $bix => $bydayPart ) {
4818
              if( ! empty( $byday[$bx] ) && ! ctype_digit( substr( $byday[$bx], -1 ))) // new day
4819
                $byday[++$bx] = '';
4820
              if( ! is_array( $bydayPart ))   // day without order number
4821
                $byday[$bx] .= (string) $bydayPart;
4822
              else {                          // day with order number
4823
                foreach( $bydayPart as $bix2 => $bydayPart2 )
4824
                  $byday[$bx] .= (string) $bydayPart2;
4825
              }
4826
            } // end foreach( $rulevalue as $bix => $bydayPart )
4827
            if( 1 < count( $byday ))
4828
              usort( $byday, array( 'iCalUtilityFunctions', '_recurBydaySort' ));
4829
            $content2      .= ';BYDAY='.implode( ',', $byday );
4830
            break;
4831
          }
4832
          default: {
4833
            $content2 .= ";$rulelabel=$rulevalue";
4834
            break;
4835
          }
4836
        }
4837
      }
4838
      $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 );
4839
    }
4840
    return $output;
4841
  }
4842
/**
4843
 * check if property not exists within component
4844
 *
4845
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4846
 * @since 2.5.1 - 2008-10-15
4847
 * @param string $propName
4848
 * @return bool
4849
 */
4850
  function _notExistProp( $propName ) {
4851
    if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed
4852
    $propName = strtolower( $propName );
4853
    if(     'last-modified'    == $propName )  { if( !isset( $this->lastmodified ))    return TRUE; }
4854
    elseif( 'percent-complete' == $propName )  { if( !isset( $this->percentcomplete )) return TRUE; }
4855
    elseif( 'recurrence-id'    == $propName )  { if( !isset( $this->recurrenceid ))    return TRUE; }
4856
    elseif( 'related-to'       == $propName )  { if( !isset( $this->relatedto ))       return TRUE; }
4857
    elseif( 'request-status'   == $propName )  { if( !isset( $this->requeststatus ))   return TRUE; }
4858
    elseif((       'x-' != substr($propName,0,2)) && !isset( $this->$propName ))       return TRUE;
4859
    return FALSE;
4860
  }
4861
/*********************************************************************************/
4862
/*********************************************************************************/
4863
/**
4864
 * get general component config variables or info about subcomponents
4865
 *
4866
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4867
 * @since 2.9.6 - 2011-05-14
4868
 * @param mixed $config
4869
 * @return value
4870
 */
4871
  function getConfig( $config = FALSE) {
4872
    if( !$config ) {
4873
      $return = array();
4874
      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
4875
      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
4876
      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
4877
        $return['LANGUAGE']  = $lang;
4878
      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
4879
      $return['TZTD']        = $this->getConfig( 'TZID' );
4880
      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
4881
      return $return;
4882
    }
4883
    switch( strtoupper( $config )) {
4884
      case 'ALLOWEMPTY':
4885
        return $this->allowEmpty;
4886
        break;
4887
      case 'COMPSINFO':
4888
        unset( $this->compix );
4889
        $info = array();
4890
        if( isset( $this->components )) {
4891
          foreach( $this->components as $cix => $component ) {
4892
            if( empty( $component )) continue;
4893
            $info[$cix]['ordno'] = $cix + 1;
4894
            $info[$cix]['type']  = $component->objName;
4895
            $info[$cix]['uid']   = $component->getProperty( 'uid' );
4896
            $info[$cix]['props'] = $component->getConfig( 'propinfo' );
4897
            $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
4898
          }
4899
        }
4900
        return $info;
4901
        break;
4902
      case 'FORMAT':
4903
        return $this->format;
4904
        break;
4905
      case 'LANGUAGE':
4906
         // get language for calendar component as defined in [RFC 1766]
4907
        return $this->language;
4908
        break;
4909
      case 'NL':
4910
      case 'NEWLINECHAR':
4911
        return $this->nl;
4912
        break;
4913
      case 'PROPINFO':
4914
        $output = array();
4915
        if( !in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
4916
          if( empty( $this->uid['value'] ))   $this->_makeuid();
4917
                                              $output['UID']              = 1;
4918
          if( empty( $this->dtstamp ))        $this->_makeDtstamp();
4919
                                              $output['DTSTAMP']          = 1;
4920
        }
4921
        if( !empty( $this->summary ))         $output['SUMMARY']          = 1;
4922
        if( !empty( $this->description ))     $output['DESCRIPTION']      = count( $this->description );
4923
        if( !empty( $this->dtstart ))         $output['DTSTART']          = 1;
4924
        if( !empty( $this->dtend ))           $output['DTEND']            = 1;
4925
        if( !empty( $this->due ))             $output['DUE']              = 1;
4926
        if( !empty( $this->duration ))        $output['DURATION']         = 1;
4927
        if( !empty( $this->rrule ))           $output['RRULE']            = count( $this->rrule );
4928
        if( !empty( $this->rdate ))           $output['RDATE']            = count( $this->rdate );
4929
        if( !empty( $this->exdate ))          $output['EXDATE']           = count( $this->exdate );
4930
        if( !empty( $this->exrule ))          $output['EXRULE']           = count( $this->exrule );
4931
        if( !empty( $this->action ))          $output['ACTION']           = 1;
4932
        if( !empty( $this->attach ))          $output['ATTACH']           = count( $this->attach );
4933
        if( !empty( $this->attendee ))        $output['ATTENDEE']         = count( $this->attendee );
4934
        if( !empty( $this->categories ))      $output['CATEGORIES']       = count( $this->categories );
4935
        if( !empty( $this->class ))           $output['CLASS']            = 1;
4936
        if( !empty( $this->comment ))         $output['COMMENT']          = count( $this->comment );
4937
        if( !empty( $this->completed ))       $output['COMPLETED']        = 1;
4938
        if( !empty( $this->contact ))         $output['CONTACT']          = count( $this->contact );
4939
        if( !empty( $this->created ))         $output['CREATED']          = 1;
4940
        if( !empty( $this->freebusy ))        $output['FREEBUSY']         = count( $this->freebusy );
4941
        if( !empty( $this->geo ))             $output['GEO']              = 1;
4942
        if( !empty( $this->lastmodified ))    $output['LAST-MODIFIED']    = 1;
4943
        if( !empty( $this->location ))        $output['LOCATION']         = 1;
4944
        if( !empty( $this->organizer ))       $output['ORGANIZER']        = 1;
4945
        if( !empty( $this->percentcomplete )) $output['PERCENT-COMPLETE'] = 1;
4946
        if( !empty( $this->priority ))        $output['PRIORITY']         = 1;
4947
        if( !empty( $this->recurrenceid ))    $output['RECURRENCE-ID']    = 1;
4948
        if( !empty( $this->relatedto ))       $output['RELATED-TO']       = count( $this->relatedto );
4949
        if( !empty( $this->repeat ))          $output['REPEAT']           = 1;
4950
        if( !empty( $this->requeststatus ))   $output['REQUEST-STATUS']   = count( $this->requeststatus );
4951
        if( !empty( $this->resources ))       $output['RESOURCES']        = count( $this->resources );
4952
        if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
4953
        if( !empty( $this->sequence ))        $output['SEQUENCE']         = 1;
4954
        if( !empty( $this->status ))          $output['STATUS']           = 1;
4955
        if( !empty( $this->transp ))          $output['TRANSP']           = 1;
4956
        if( !empty( $this->trigger ))         $output['TRIGGER']          = 1;
4957
        if( !empty( $this->tzid ))            $output['TZID']             = 1;
4958
        if( !empty( $this->tzname ))          $output['TZNAME']           = count( $this->tzname );
4959
        if( !empty( $this->tzoffsetfrom ))    $output['TZOFFSETFROM']     = 1;
4960
        if( !empty( $this->tzoffsetto ))      $output['TZOFFSETTO']       = 1;
4961
        if( !empty( $this->tzurl ))           $output['TZURL']            = 1;
4962
        if( !empty( $this->url ))             $output['URL']              = 1;
4963
        if( !empty( $this->xprop ))           $output['X-PROP']           = count( $this->xprop );
4964
        return $output;
4965
        break;
4966
      case 'SETPROPERTYNAMES':
4967
        return array_keys( $this->getConfig( 'propinfo' ));
4968
        break;
4969
      case 'TZID':
4970
        return $this->dtzid;
4971
        break;
4972
      case 'UNIQUE_ID':
4973
        if( empty( $this->unique_id ))
4974
          $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
4975
        return $this->unique_id;
4976
        break;
4977
    }
4978
  }
4979
/**
4980
 * general component config setting
4981
 *
4982
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
4983
 * @since 2.10.18 - 2011-10-28
4984
 * @param mixed  $config
4985
 * @param string $value
4986
 * @param bool   $softUpdate
4987
 * @return void
4988
 */
4989
  function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) {
4990
    if( is_array( $config )) {
4991
      $ak = array_keys( $config );
4992
      foreach( $ak as $k ) {
4993
        if( 'NEWLINECHAR' == strtoupper( $k )) {
4994
          if( FALSE === $this->setConfig( 'NEWLINECHAR', $config[$k] ))
4995
            return FALSE;
4996
          unset( $config[$k] );
4997
          break;
4998
        }
4999
      }
5000
      foreach( $config as $cKey => $cValue ) {
5001
        if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate ))
5002
          return FALSE;
5003
      }
5004
      return TRUE;
5005
    }
5006
    $res = FALSE;
5007
    switch( strtoupper( $config )) {
5008
      case 'ALLOWEMPTY':
5009
        $this->allowEmpty = $value;
5010
        $subcfg = array( 'ALLOWEMPTY' => $value );
5011
        $res    = TRUE;
5012
        break;
5013
      case 'FORMAT':
5014
        $value  = trim( strtolower( $value ));
5015
        $this->format = $value;
5016
        $this->_createFormat();
5017
        $subcfg = array( 'FORMAT' => $value );
5018
        $res    = TRUE;
5019
        break;
5020
      case 'LANGUAGE':
5021
         // set language for calendar component as defined in [RFC 1766]
5022
        $value  = trim( $value );
5023
        if( empty( $this->language ) || !$softUpdate )
5024
          $this->language = $value;
5025
        $subcfg = array( 'LANGUAGE' => $value );
5026
        $res    = TRUE;
5027
        break;
5028
      case 'NL':
5029
      case 'NEWLINECHAR':
5030
        $this->nl = $value;
5031
        $this->_createFormat();
5032
        $subcfg = array( 'NL' => $value );
5033
        $res    = TRUE;
5034
        break;
5035
      case 'TZID':
5036
        $this->dtzid = $value;
5037
        $subcfg = array( 'TZID' => $value );
5038
        $res    = TRUE;
5039
        break;
5040
      case 'UNIQUE_ID':
5041
        $value  = trim( $value );
5042
        $this->unique_id = $value;
5043
        $subcfg = array( 'UNIQUE_ID' => $value );
5044
        $res    = TRUE;
5045
        break;
5046
      default:  // any unvalid config key.. .
5047
        return TRUE;
5048
    }
5049
    if( !$res ) return FALSE;
5050
    if( isset( $subcfg ) && !empty( $this->components )) {
5051
      foreach( $subcfg as $cfgkey => $cfgvalue ) {
5052
        foreach( $this->components as $cix => $component ) {
5053
          $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate );
5054
          if( !$res )
5055
            break 2;
5056
          $this->components[$cix] = $component->copy(); // PHP4 compliant
5057
        }
5058
      }
5059
    }
5060
    return $res;
5061
  }
5062
/*********************************************************************************/
5063
/**
5064
 * delete component property value
5065
 *
5066
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5067
 * @since 2.8.8 - 2011-03-15
5068
 * @param mixed $propName, bool FALSE => X-property
5069
 * @param int   $propix, optional, if specific property is wanted in case of multiply occurences
5070
 * @return bool, if successfull delete TRUE
5071
 */
5072
  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
5073
    if( $this->_notExistProp( $propName )) return FALSE;
5074
    $propName = strtoupper( $propName );
5075
    if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
5076
                                    'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
5077
      if( !$propix )
5078
        $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
5079
      $this->propdelix[$propName] = --$propix;
5080
    }
5081
    $return = FALSE;
5082
    switch( $propName ) {
5083
      case 'ACTION':
5084
        if( !empty( $this->action )) {
5085
          $this->action = '';
5086
          $return = TRUE;
5087
        }
5088
        break;
5089
      case 'ATTACH':
5090
        return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] );
5091
        break;
5092
      case 'ATTENDEE':
5093
        return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] );
5094
        break;
5095
      case 'CATEGORIES':
5096
        return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] );
5097
        break;
5098
      case 'CLASS':
5099
        if( !empty( $this->class )) {
5100
          $this->class = '';
5101
          $return = TRUE;
5102
        }
5103
        break;
5104
      case 'COMMENT':
5105
        return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] );
5106
        break;
5107
      case 'COMPLETED':
5108
        if( !empty( $this->completed )) {
5109
          $this->completed = '';
5110
          $return = TRUE;
5111
        }
5112
        break;
5113
      case 'CONTACT':
5114
        return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] );
5115
        break;
5116
      case 'CREATED':
5117
        if( !empty( $this->created )) {
5118
          $this->created = '';
5119
          $return = TRUE;
5120
        }
5121
        break;
5122
      case 'DESCRIPTION':
5123
        return $this->deletePropertyM( $this->description, $this->propdelix[$propName] );
5124
        break;
5125
      case 'DTEND':
5126
        if( !empty( $this->dtend )) {
5127
          $this->dtend = '';
5128
          $return = TRUE;
5129
        }
5130
        break;
5131
      case 'DTSTAMP':
5132
        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5133
          return FALSE;
5134
        if( !empty( $this->dtstamp )) {
5135
          $this->dtstamp = '';
5136
          $return = TRUE;
5137
        }
5138
        break;
5139
      case 'DTSTART':
5140
        if( !empty( $this->dtstart )) {
5141
          $this->dtstart = '';
5142
          $return = TRUE;
5143
        }
5144
        break;
5145
      case 'DUE':
5146
        if( !empty( $this->due )) {
5147
          $this->due = '';
5148
          $return = TRUE;
5149
        }
5150
        break;
5151
      case 'DURATION':
5152
        if( !empty( $this->duration )) {
5153
          $this->duration = '';
5154
          $return = TRUE;
5155
        }
5156
        break;
5157
      case 'EXDATE':
5158
        return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] );
5159
        break;
5160
      case 'EXRULE':
5161
        return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] );
5162
        break;
5163
      case 'FREEBUSY':
5164
        return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] );
5165
        break;
5166
      case 'GEO':
5167
        if( !empty( $this->geo )) {
5168
          $this->geo = '';
5169
          $return = TRUE;
5170
        }
5171
        break;
5172
      case 'LAST-MODIFIED':
5173
        if( !empty( $this->lastmodified )) {
5174
          $this->lastmodified = '';
5175
          $return = TRUE;
5176
        }
5177
        break;
5178
      case 'LOCATION':
5179
        if( !empty( $this->location )) {
5180
          $this->location = '';
5181
          $return = TRUE;
5182
        }
5183
        break;
5184
      case 'ORGANIZER':
5185
        if( !empty( $this->organizer )) {
5186
          $this->organizer = '';
5187
          $return = TRUE;
5188
        }
5189
        break;
5190
      case 'PERCENT-COMPLETE':
5191
        if( !empty( $this->percentcomplete )) {
5192
          $this->percentcomplete = '';
5193
          $return = TRUE;
5194
        }
5195
        break;
5196
      case 'PRIORITY':
5197
        if( !empty( $this->priority )) {
5198
          $this->priority = '';
5199
          $return = TRUE;
5200
        }
5201
        break;
5202
      case 'RDATE':
5203
        return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] );
5204
        break;
5205
      case 'RECURRENCE-ID':
5206
        if( !empty( $this->recurrenceid )) {
5207
          $this->recurrenceid = '';
5208
          $return = TRUE;
5209
        }
5210
        break;
5211
      case 'RELATED-TO':
5212
        return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] );
5213
        break;
5214
      case 'REPEAT':
5215
        if( !empty( $this->repeat )) {
5216
          $this->repeat = '';
5217
          $return = TRUE;
5218
        }
5219
        break;
5220
      case 'REQUEST-STATUS':
5221
        return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] );
5222
        break;
5223
      case 'RESOURCES':
5224
        return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] );
5225
        break;
5226
      case 'RRULE':
5227
        return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] );
5228
        break;
5229
      case 'SEQUENCE':
5230
        if( !empty( $this->sequence )) {
5231
          $this->sequence = '';
5232
          $return = TRUE;
5233
        }
5234
        break;
5235
      case 'STATUS':
5236
        if( !empty( $this->status )) {
5237
          $this->status = '';
5238
          $return = TRUE;
5239
        }
5240
        break;
5241
      case 'SUMMARY':
5242
        if( !empty( $this->summary )) {
5243
          $this->summary = '';
5244
          $return = TRUE;
5245
        }
5246
        break;
5247
      case 'TRANSP':
5248
        if( !empty( $this->transp )) {
5249
          $this->transp = '';
5250
          $return = TRUE;
5251
        }
5252
        break;
5253
      case 'TRIGGER':
5254
        if( !empty( $this->trigger )) {
5255
          $this->trigger = '';
5256
          $return = TRUE;
5257
        }
5258
        break;
5259
      case 'TZID':
5260
        if( !empty( $this->tzid )) {
5261
          $this->tzid = '';
5262
          $return = TRUE;
5263
        }
5264
        break;
5265
      case 'TZNAME':
5266
        return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] );
5267
        break;
5268
      case 'TZOFFSETFROM':
5269
        if( !empty( $this->tzoffsetfrom )) {
5270
          $this->tzoffsetfrom = '';
5271
          $return = TRUE;
5272
        }
5273
        break;
5274
      case 'TZOFFSETTO':
5275
        if( !empty( $this->tzoffsetto )) {
5276
          $this->tzoffsetto = '';
5277
          $return = TRUE;
5278
        }
5279
        break;
5280
      case 'TZURL':
5281
        if( !empty( $this->tzurl )) {
5282
          $this->tzurl = '';
5283
          $return = TRUE;
5284
        }
5285
        break;
5286
      case 'UID':
5287
        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5288
          return FALSE;
5289
        if( !empty( $this->uid )) {
5290
          $this->uid = '';
5291
          $return = TRUE;
5292
        }
5293
        break;
5294
      case 'URL':
5295
        if( !empty( $this->url )) {
5296
          $this->url = '';
5297
          $return = TRUE;
5298
        }
5299
        break;
5300
      default:
5301
        $reduced = '';
5302
        if( $propName != 'X-PROP' ) {
5303
          if( !isset( $this->xprop[$propName] )) return FALSE;
5304
          foreach( $this->xprop as $k => $a ) {
5305
            if(( $k != $propName ) && !empty( $a ))
5306
              $reduced[$k] = $a;
5307
          }
5308
        }
5309
        else {
5310
          if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; }
5311
          $xpropno = 0;
5312
          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
5313
            if( $propix != $xpropno )
5314
              $reduced[$xpropkey] = $xpropvalue;
5315
            $xpropno++;
5316
          }
5317
        }
5318
        $this->xprop = $reduced;
5319
        if( empty( $this->xprop )) {
5320
          unset( $this->propdelix[$propName] );
5321
          return FALSE;
5322
        }
5323
        return TRUE;
5324
    }
5325
    return $return;
5326
  }
5327
/*********************************************************************************/
5328
/**
5329
 * delete component property value, fixing components with multiple occurencies
5330
 *
5331
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5332
 * @since 2.8.8 - 2011-03-15
5333
 * @param array $multiprop, reference to a component property
5334
 * @param int   $propix, reference to removal counter
5335
 * @return bool TRUE
5336
 */
5337
  function deletePropertyM( & $multiprop, & $propix ) {
5338
    if( isset( $multiprop[$propix] ))
5339
      unset( $multiprop[$propix] );
5340
    if( empty( $multiprop )) {
5341
      $multiprop = '';
5342
      unset( $propix );
5343
      return FALSE;
5344
    }
5345
    else
5346
      return TRUE;
5347
  }
5348
/**
5349
 * get component property value/params
5350
 *
5351
 * if property has multiply values, consequtive function calls are needed
5352
 *
5353
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5354
 * @since 2.16.21 - 2013-06-23
5355
 * @param string $propName, optional
5356
 * @param int @propix, optional, if specific property is wanted in case of multiply occurences
5357
 * @param bool $inclParam=FALSE
5358
 * @param bool $specform=FALSE
5359
 * @return mixed
5360
 */
5361
  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specform=FALSE ) {
5362
    if( 'GEOLOCATION' == strtoupper( $propName )) {
5363
      $content = $this->getProperty( 'LOCATION' );
5364
      $content = ( !empty( $content )) ? $content.' ' : '';
5365
      if(( FALSE === ( $geo     = $this->getProperty( 'GEO' ))) || empty( $geo ))
5366
        return FALSE;
5367
      if( 0.0 < $geo['latitude'] )
5368
        $sign   = '+';
5369
      else
5370
        $sign   = ( 0.0 > $geo['latitude'] ) ? '-' : '';
5371
      $content .= $sign.sprintf( "%09.6f", abs( $geo['latitude'] ));   // sprintf && lpad && float && sign !"#¤%&/(
5372
      $content  = rtrim( rtrim( $content, '0' ), '.' );
5373
      if( 0.0 < $geo['longitude'] )
5374
        $sign   = '+';
5375
      else
5376
       $sign   = ( 0.0 > $geo['longitude'] ) ? '-' : '';
5377
      return $content.$sign.sprintf( '%8.6f', abs( $geo['longitude'] )).'/';   // sprintf && lpad && float && sign !"#¤%&/(
5378
    }
5379
    if( $this->_notExistProp( $propName )) return FALSE;
5380
    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
5381
    if( in_array( $propName, array( 'ATTACH',   'ATTENDEE', 'CATEGORIES', 'COMMENT',   'CONTACT', 'DESCRIPTION',    'EXDATE', 'EXRULE',
5382
                                    'FREEBUSY', 'RDATE',    'RELATED-TO', 'RESOURCES', 'RRULE',   'REQUEST-STATUS', 'TZNAME', 'X-PROP'  ))) {
5383
      if( !$propix )
5384
        $propix = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
5385
      $this->propix[$propName] = --$propix;
5386
    }
5387
    switch( $propName ) {
5388
      case 'ACTION':
5389
        if( isset( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value'];
5390
        break;
5391
      case 'ATTACH':
5392
        $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array();
5393
        while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak )))
5394
          $propix++;
5395
        $this->propix[$propName] = $propix;
5396
        if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5397
        return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value'];
5398
        break;
5399
      case 'ATTENDEE':
5400
        $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array();
5401
        while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak )))
5402
          $propix++;
5403
        $this->propix[$propName] = $propix;
5404
        if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5405
        return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value'];
5406
        break;
5407
      case 'CATEGORIES':
5408
        $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array();
5409
        while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak )))
5410
          $propix++;
5411
        $this->propix[$propName] = $propix;
5412
        if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5413
        return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value'];
5414
        break;
5415
      case 'CLASS':
5416
        if( isset( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value'];
5417
        break;
5418
      case 'COMMENT':
5419
        $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array();
5420
        while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak )))
5421
          $propix++;
5422
        $this->propix[$propName] = $propix;
5423
        if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5424
        return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value'];
5425
        break;
5426
      case 'COMPLETED':
5427
        if( isset( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value'];
5428
        break;
5429
      case 'CONTACT':
5430
        $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array();
5431
        while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak )))
5432
          $propix++;
5433
        $this->propix[$propName] = $propix;
5434
        if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5435
        return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value'];
5436
        break;
5437
      case 'CREATED':
5438
        if( isset( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value'];
5439
        break;
5440
      case 'DESCRIPTION':
5441
        $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array();
5442
        while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak )))
5443
          $propix++;
5444
        $this->propix[$propName] = $propix;
5445
        if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5446
        return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value'];
5447
        break;
5448
      case 'DTEND':
5449
        if( isset( $this->dtend['value'] )) return ( $inclParam ) ? $this->dtend : $this->dtend['value'];
5450
        break;
5451
      case 'DTSTAMP':
5452
        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5453
          return;
5454
        if( !isset( $this->dtstamp['value'] ))
5455
          $this->_makeDtstamp();
5456
        return ( $inclParam ) ? $this->dtstamp : $this->dtstamp['value'];
5457
        break;
5458
      case 'DTSTART':
5459
        if( isset( $this->dtstart['value'] )) return ( $inclParam ) ? $this->dtstart : $this->dtstart['value'];
5460
        break;
5461
      case 'DUE':
5462
        if( isset( $this->due['value'] )) return ( $inclParam ) ? $this->due : $this->due['value'];
5463
        break;
5464
      case 'DURATION':
5465
        if( ! isset( $this->duration['value'] )) return FALSE;
5466
        $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value'];
5467
        return ( $inclParam ) ? array( 'value' => $value, 'params' =>  $this->duration['params'] ) : $value;
5468
        break;
5469
      case 'EXDATE':
5470
        $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array();
5471
        while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak )))
5472
          $propix++;
5473
        $this->propix[$propName] = $propix;
5474
        if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5475
        return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value'];
5476
        break;
5477
      case 'EXRULE':
5478
        $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array();
5479
        while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak )))
5480
          $propix++;
5481
        $this->propix[$propName] = $propix;
5482
        if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5483
        return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value'];
5484
        break;
5485
      case 'FREEBUSY':
5486
        $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array();
5487
        while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak )))
5488
          $propix++;
5489
        $this->propix[$propName] = $propix;
5490
        if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5491
        return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value'];
5492
        break;
5493
      case 'GEO':
5494
        if( isset( $this->geo['value'] )) return ( $inclParam ) ? $this->geo : $this->geo['value'];
5495
        break;
5496
      case 'LAST-MODIFIED':
5497
        if( isset( $this->lastmodified['value'] )) return ( $inclParam ) ? $this->lastmodified : $this->lastmodified['value'];
5498
        break;
5499
      case 'LOCATION':
5500
        if( isset( $this->location['value'] )) return ( $inclParam ) ? $this->location : $this->location['value'];
5501
        break;
5502
      case 'ORGANIZER':
5503
        if( isset( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value'];
5504
        break;
5505
      case 'PERCENT-COMPLETE':
5506
        if( isset( $this->percentcomplete['value'] )) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value'];
5507
        break;
5508
      case 'PRIORITY':
5509
        if( isset( $this->priority['value'] )) return ( $inclParam ) ? $this->priority : $this->priority['value'];
5510
        break;
5511
      case 'RDATE':
5512
        $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array();
5513
        while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak )))
5514
          $propix++;
5515
        $this->propix[$propName] = $propix;
5516
        if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5517
        return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value'];
5518
        break;
5519
      case 'RECURRENCE-ID':
5520
        if( isset( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value'];
5521
        break;
5522
      case 'RELATED-TO':
5523
        $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array();
5524
        while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak )))
5525
          $propix++;
5526
        $this->propix[$propName] = $propix;
5527
        if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5528
        return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value'];
5529
        break;
5530
      case 'REPEAT':
5531
        if( isset( $this->repeat['value'] )) return ( $inclParam ) ? $this->repeat : $this->repeat['value'];
5532
        break;
5533
      case 'REQUEST-STATUS':
5534
        $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array();
5535
        while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak )))
5536
          $propix++;
5537
        $this->propix[$propName] = $propix;
5538
        if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5539
        return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value'];
5540
        break;
5541
      case 'RESOURCES':
5542
        $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array();
5543
        while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak )))
5544
          $propix++;
5545
        $this->propix[$propName] = $propix;
5546
        if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5547
        return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value'];
5548
        break;
5549
      case 'RRULE':
5550
        $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array();
5551
        while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak )))
5552
          $propix++;
5553
        $this->propix[$propName] = $propix;
5554
        if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5555
        return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value'];
5556
        break;
5557
      case 'SEQUENCE':
5558
        if( isset( $this->sequence['value'] )) return ( $inclParam ) ? $this->sequence : $this->sequence['value'];
5559
        break;
5560
      case 'STATUS':
5561
        if( isset( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value'];
5562
        break;
5563
      case 'SUMMARY':
5564
        if( isset( $this->summary['value'] )) return ( $inclParam ) ? $this->summary : $this->summary['value'];
5565
        break;
5566
      case 'TRANSP':
5567
        if( isset( $this->transp['value'] )) return ( $inclParam ) ? $this->transp : $this->transp['value'];
5568
        break;
5569
      case 'TRIGGER':
5570
        if( isset( $this->trigger['value'] )) return ( $inclParam ) ? $this->trigger : $this->trigger['value'];
5571
        break;
5572
      case 'TZID':
5573
        if( isset( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value'];
5574
        break;
5575
      case 'TZNAME':
5576
        $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array();
5577
        while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak )))
5578
          $propix++;
5579
        $this->propix[$propName] = $propix;
5580
        if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; }
5581
        return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value'];
5582
        break;
5583
      case 'TZOFFSETFROM':
5584
        if( isset( $this->tzoffsetfrom['value'] )) return ( $inclParam ) ? $this->tzoffsetfrom : $this->tzoffsetfrom['value'];
5585
        break;
5586
      case 'TZOFFSETTO':
5587
        if( isset( $this->tzoffsetto['value'] )) return ( $inclParam ) ? $this->tzoffsetto : $this->tzoffsetto['value'];
5588
        break;
5589
      case 'TZURL':
5590
        if( isset( $this->tzurl['value'] )) return ( $inclParam ) ? $this->tzurl : $this->tzurl['value'];
5591
        break;
5592
      case 'UID':
5593
        if( in_array( $this->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' )))
5594
          return FALSE;
5595
        if( empty( $this->uid['value'] ))
5596
          $this->_makeuid();
5597
        return ( $inclParam ) ? $this->uid : $this->uid['value'];
5598
        break;
5599
      case 'URL':
5600
        if( isset( $this->url['value'] )) return ( $inclParam ) ? $this->url : $this->url['value'];
5601
        break;
5602
      default:
5603
        if( $propName != 'X-PROP' ) {
5604
          if( !isset( $this->xprop[$propName] )) return FALSE;
5605
          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
5606
                                : array( $propName, $this->xprop[$propName]['value'] );
5607
        }
5608
        else {
5609
          if( empty( $this->xprop )) return FALSE;
5610
          $xpropno = 0;
5611
          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
5612
            if( $propix == $xpropno )
5613
              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
5614
                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
5615
            else
5616
              $xpropno++;
5617
          }
5618
          return FALSE; // not found ??
5619
        }
5620
    }
5621
    return FALSE;
5622
  }
5623
/**
5624
 * returns calendar property unique values for 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO' or 'RESOURCES' and for each, number of occurrence
5625
 *
5626
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5627
 * @since 2.13.4 - 2012-08-07
5628
 * @param string $propName
5629
 * @param array  $output, incremented result array
5630
 */
5631
  function _getProperties( $propName, & $output ) {
5632
    if( empty( $output ))
5633
      $output = array();
5634
    if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'CONTACT', 'RELATED-TO', 'RESOURCES' )))
5635
      return $output;
5636
    while( FALSE !== ( $content = $this->getProperty( $propName ))) {
5637
      if( empty( $content ))
5638
        continue;
5639
      if( is_array( $content )) {
5640
        foreach( $content as $part ) {
5641
          if( FALSE !== strpos( $part, ',' )) {
5642
            $part = explode( ',', $part );
5643
            foreach( $part as $thePart ) {
5644
              $thePart = trim( $thePart );
5645
              if( !empty( $thePart )) {
5646
                if( !isset( $output[$thePart] ))
5647
                  $output[$thePart] = 1;
5648
                else
5649
                  $output[$thePart] += 1;
5650
              }
5651
            }
5652
          }
5653
          else {
5654
            $part = trim( $part );
5655
            if( !isset( $output[$part] ))
5656
              $output[$part] = 1;
5657
            else
5658
              $output[$part] += 1;
5659
          }
5660
        }
5661
      } // end if( is_array( $content ))
5662
      elseif( FALSE !== strpos( $content, ',' )) {
5663
        $content = explode( ',', $content );
5664
        foreach( $content as $thePart ) {
5665
          $thePart = trim( $thePart );
5666
          if( !empty( $thePart )) {
5667
            if( !isset( $output[$thePart] ))
5668
              $output[$thePart] = 1;
5669
            else
5670
              $output[$thePart] += 1;
5671
          }
5672
        }
5673
      } // end elseif( FALSE !== strpos( $content, ',' ))
5674
      else {
5675
        $content = trim( $content );
5676
        if( !empty( $content )) {
5677
          if( !isset( $output[$content] ))
5678
            $output[$content] = 1;
5679
          else
5680
            $output[$content] += 1;
5681
        }
5682
      }
5683
    }
5684
    ksort( $output );
5685
  }
5686
/**
5687
 * general component property setting
5688
 *
5689
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5690
 * @since 2.5.1 - 2008-11-05
5691
 * @param mixed $args variable number of function arguments,
5692
 *                    first argument is ALWAYS component name,
5693
 *                    second ALWAYS component value!
5694
 * @return void
5695
 */
5696
  function setProperty() {
5697
    $numargs    = func_num_args();
5698
    if( 1 > $numargs ) return FALSE;
5699
    $arglist    = func_get_args();
5700
    if( $this->_notExistProp( $arglist[0] )) return FALSE;
5701
    if( !$this->getConfig( 'allowEmpty' ) && ( !isset( $arglist[1] ) || empty( $arglist[1] )))
5702
      return FALSE;
5703
    $arglist[0] = strtoupper( $arglist[0] );
5704
    for( $argix=$numargs; $argix < 12; $argix++ ) {
5705
      if( !isset( $arglist[$argix] ))
5706
        $arglist[$argix] = null;
5707
    }
5708
    switch( $arglist[0] ) {
5709
      case 'ACTION':
5710
        return $this->setAction(          $arglist[1], $arglist[2] );
5711
      case 'ATTACH':
5712
        return $this->setAttach(          $arglist[1], $arglist[2], $arglist[3] );
5713
      case 'ATTENDEE':
5714
        return $this->setAttendee(        $arglist[1], $arglist[2], $arglist[3] );
5715
      case 'CATEGORIES':
5716
        return $this->setCategories(      $arglist[1], $arglist[2], $arglist[3] );
5717
      case 'CLASS':
5718
        return $this->setClass(           $arglist[1], $arglist[2] );
5719
      case 'COMMENT':
5720
        return $this->setComment(         $arglist[1], $arglist[2], $arglist[3] );
5721
      case 'COMPLETED':
5722
        return $this->setCompleted(       $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5723
      case 'CONTACT':
5724
        return $this->setContact(         $arglist[1], $arglist[2], $arglist[3] );
5725
      case 'CREATED':
5726
        return $this->setCreated(         $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5727
      case 'DESCRIPTION':
5728
        return $this->setDescription(     $arglist[1], $arglist[2], $arglist[3] );
5729
      case 'DTEND':
5730
        return $this->setDtend(           $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5731
      case 'DTSTAMP':
5732
        return $this->setDtstamp(         $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5733
      case 'DTSTART':
5734
        return $this->setDtstart(         $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5735
      case 'DUE':
5736
        return $this->setDue(             $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5737
      case 'DURATION':
5738
        return $this->setDuration(        $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6] );
5739
      case 'EXDATE':
5740
        return $this->setExdate(          $arglist[1], $arglist[2], $arglist[3] );
5741
      case 'EXRULE':
5742
        return $this->setExrule(          $arglist[1], $arglist[2], $arglist[3] );
5743
      case 'FREEBUSY':
5744
        return $this->setFreebusy(        $arglist[1], $arglist[2], $arglist[3], $arglist[4] );
5745
      case 'GEO':
5746
        return $this->setGeo(             $arglist[1], $arglist[2], $arglist[3] );
5747
      case 'LAST-MODIFIED':
5748
        return $this->setLastModified(    $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7] );
5749
      case 'LOCATION':
5750
        return $this->setLocation(        $arglist[1], $arglist[2] );
5751
      case 'ORGANIZER':
5752
        return $this->setOrganizer(       $arglist[1], $arglist[2] );
5753
      case 'PERCENT-COMPLETE':
5754
        return $this->setPercentComplete( $arglist[1], $arglist[2] );
5755
      case 'PRIORITY':
5756
        return $this->setPriority(        $arglist[1], $arglist[2] );
5757
      case 'RDATE':
5758
        return $this->setRdate(           $arglist[1], $arglist[2], $arglist[3] );
5759
      case 'RECURRENCE-ID':
5760
       return $this->setRecurrenceid(     $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5], $arglist[6], $arglist[7], $arglist[8] );
5761
      case 'RELATED-TO':
5762
        return $this->setRelatedTo(       $arglist[1], $arglist[2], $arglist[3] );
5763
      case 'REPEAT':
5764
        return $this->setRepeat(          $arglist[1], $arglist[2] );
5765
      case 'REQUEST-STATUS':
5766
        return $this->setRequestStatus(   $arglist[1], $arglist[2], $arglist[3], $arglist[4], $arglist[5] );
5767
      case 'RESOURCES':
5768
        return $this->setResources(       $arglist[1], $arglist[2], $arglist[3] );
5769
      case 'RRULE':
5770
        return $this->setRrule(           $arglist[1], $arglist[2], $arglist[3] );
5771
      case 'SEQUENCE':
5772
        return $this->setSequence(        $arglist[1], $arglist[2] );
5773
      case 'STATUS':
5774
        return $this->setStatus(          $arglist[1], $arglist[2] );
5775
      case 'SUMMARY':
5776
        return $this->setSummary(         $arglist[1], $arglist[2] );
5777
      case 'TRANSP':
5778
        return $this->setTransp(          $arglist[1], $arglist[2] );
5779
      case 'TRIGGER':
5780
        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] );
5781
      case 'TZID':
5782
        return $this->setTzid(            $arglist[1], $arglist[2] );
5783
      case 'TZNAME':
5784
        return $this->setTzname(          $arglist[1], $arglist[2], $arglist[3] );
5785
      case 'TZOFFSETFROM':
5786
        return $this->setTzoffsetfrom(    $arglist[1], $arglist[2] );
5787
      case 'TZOFFSETTO':
5788
        return $this->setTzoffsetto(      $arglist[1], $arglist[2] );
5789
      case 'TZURL':
5790
        return $this->setTzurl(           $arglist[1], $arglist[2] );
5791
      case 'UID':
5792
        return $this->setUid(             $arglist[1], $arglist[2] );
5793
      case 'URL':
5794
        return $this->setUrl(             $arglist[1], $arglist[2] );
5795
      default:
5796
        return $this->setXprop(           $arglist[0], $arglist[1], $arglist[2] );
5797
    }
5798
    return FALSE;
5799
  }
5800
/*********************************************************************************/
5801
/**
5802
 * parse component unparsed data into properties
5803
 *
5804
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
5805
 * @since 2.16.26 - 2013-07-02
5806
 * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings
5807
 * @return bool FALSE if error occurs during parsing
5808
 *
5809
 */
5810
  function parse( $unparsedtext=null ) {
5811
    $nl = $this->getConfig( 'nl' );
5812
    if( !empty( $unparsedtext )) {
5813
      if( is_array( $unparsedtext ))
5814
        $unparsedtext = implode( '\n'.$nl, $unparsedtext );
5815
      $unparsedtext = explode( $nl, iCalUtilityFunctions::convEolChar( $unparsedtext, $nl ));
5816
    }
5817
    elseif( !isset( $this->unparsed ))
5818
      $unparsedtext = array();
5819
    else
5820
      $unparsedtext = $this->unparsed;
5821
            /* skip leading (empty/invalid) lines */
5822
    foreach( $unparsedtext as $lix => $line ) {
5823
      $tst = trim( $line );
5824
      if(( '\n' == $tst ) || empty( $tst ))
5825
        unset( $unparsedtext[$lix] );
5826
      else
5827
        break;
5828
    }
5829
    $this->unparsed = array();
5830
    $comp           = & $this;
5831
    $config         = $this->getConfig();
5832
    $compsync = $subsync = 0;
5833
    foreach ( $unparsedtext as $lix => $line ) {
5834
      if( 'END:VALARM'         == strtoupper( substr( $line, 0, 10 ))) {
5835
        if( 1 != $subsync ) return FALSE;
5836
        $this->components[]     = $comp->copy();
5837
        $subsync--;
5838
      }
5839
      elseif( 'END:DAYLIGHT'   == strtoupper( substr( $line, 0, 12 ))) {
5840
        if( 1 != $subsync ) return FALSE;
5841
        $this->components[]     = $comp->copy();
5842
        $subsync--;
5843
      }
5844
      elseif( 'END:STANDARD'   == strtoupper( substr( $line, 0, 12 ))) {
5845
        if( 1 != $subsync ) return FALSE;
5846
        array_unshift( $this->components, $comp->copy());
5847
        $subsync--;
5848
      }
5849
      elseif( 'END:'           == strtoupper( substr( $line, 0, 4 ))) { // end:<component>
5850
        if( 1 != $compsync ) return FALSE;
5851
        if( 0 < $subsync )
5852
          $this->components[]   = $comp->copy();
5853
        $compsync--;
5854
        break;                       /* skip trailing empty lines */
5855
      }
5856
      elseif( 'BEGIN:VALARM'   == strtoupper( substr( $line, 0, 12 ))) {
5857
        $comp = new valarm( $config);
5858
        $subsync++;
5859
      }
5860
      elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) {
5861
        $comp = new vtimezone( 'standard', $config );
5862
        $subsync++;
5863
      }
5864
      elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) {
5865
        $comp = new vtimezone( 'daylight', $config );
5866
        $subsync++;
5867
      }
5868
      elseif( 'BEGIN:'         == strtoupper( substr( $line, 0, 6 )))  // begin:<component>
5869
        $compsync++;
5870
      else
5871
        $comp->unparsed[]       = $line;
5872
    }
5873
    if( 0 < $subsync )
5874
      $this->components[]   = $comp->copy();
5875
    unset( $config );
5876
            /* concatenate property values spread over several lines */
5877
    $lastix    = -1;
5878
    $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed'
5879
                      , 'contact', 'class', 'created', 'description', 'dtend', 'dtstart'
5880
                      , 'dtstamp', 'due', 'duration', 'exdate', 'exrule', 'freebusy', 'geo'
5881
                      , 'last-modified', 'location', 'organizer', 'percent-complete'
5882
                      , 'priority', 'rdate', 'recurrence-id', 'related-to', 'repeat'
5883
                      , 'request-status', 'resources', 'rrule', 'sequence', 'status'
5884
                      , 'summary', 'transp', 'trigger', 'tzid', 'tzname', 'tzoffsetfrom'
5885
                      , 'tzoffsetto', 'tzurl', 'uid', 'url', 'x-' );
5886
    $proprows  = array();
5887
    for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
5888
      $line = rtrim( $this->unparsed[$i], $nl );
5889
      while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
5890
        $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
5891
      $proprows[] = $line;
5892
    }
5893
            /* parse each property 'line' */
5894
    $paramMStz   = array( 'utc-', 'utc+', 'gmt-', 'gmt+' );
5895
    $paramProto3 = array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' );
5896
    $paramProto4 = array( 'crid:', 'news:', 'pres:' );
5897
    foreach( $proprows as $line ) {
5898
      if( '\n' == substr( $line, -2 ))
5899
        $line = substr( $line, 0, -2 );
5900
            /* get propname */
5901
      $propname = null;
5902
      $cix = 0;
5903
      while( isset( $line[$cix] )) {
5904
        if( in_array( $line[$cix], array( ':', ';' )))
5905
          break;
5906
        else
5907
          $propname .= $line[$cix];
5908
        $cix++;
5909
      }
5910
      if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) {
5911
        $propname2 = $propname;
5912
        $propname  = 'X-';
5913
      }
5914
      if( !in_array( strtolower( $propname ), $propnames )) // skip non standard property names
5915
        continue;
5916
            /* rest of the line is opt.params and value */
5917
      $line = substr( $line, $cix );
5918
            /* separate attributes from value */
5919
      $attr         = array();
5920
      $attrix       = -1;
5921
      $clen         = strlen( $line );
5922
      $WithinQuotes = FALSE;
5923
      $cix          = 0;
5924
      while( FALSE !== substr( $line, $cix, 1 )) {
5925
        if(                       (  ':' == $line[$cix] )                         &&
5926
                                  ( substr( $line,$cix,     3 )  != '://' )       &&
5927
           ( !in_array( strtolower( substr( $line,$cix - 6, 4 )), $paramMStz ))   &&
5928
           ( !in_array( strtolower( substr( $line,$cix - 3, 4 )), $paramProto3 )) &&
5929
           ( !in_array( strtolower( substr( $line,$cix - 4, 5 )), $paramProto4 )) &&
5930
                      ( strtolower( substr( $line,$cix - 6, 7 )) != 'mailto:' )   &&
5931
             !$WithinQuotes ) {
5932
          $attrEnd = TRUE;
5933
          if(( $cix < ( $clen - 4 )) &&
5934
               ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr??
5935
            for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) {
5936
              if( '://' == substr( $line, $c2ix - 2, 3 )) {
5937
                $attrEnd = FALSE;
5938
                break; // an URI with a portnr!!
5939
              }
5940
            }
5941
          }
5942
          if( $attrEnd) {
5943
            $line = substr( $line, ( $cix + 1 ));
5944
            break;
5945
          }
5946
          $cix++;
5947
        }
5948
        if( '"' == $line[$cix] )
5949
          $WithinQuotes = ( FALSE === $WithinQuotes ) ? TRUE : FALSE;
5950
        if( ';' == $line[$cix] )
5951
          $attr[++$attrix] = null;
5952
        else
5953
          $attr[$attrix] .= $line[$cix];
5954
        $cix++;
5955
      }
5956
            /* make attributes in array format */
5957
      $propattr = array();
5958
      foreach( $attr as $attribute ) {
5959
        $attrsplit = explode( '=', $attribute, 2 );
5960
        if( 1 < count( $attrsplit ))
5961
          $propattr[$attrsplit[0]] = $attrsplit[1];
5962
        else
5963
          $propattr[] = $attribute;
5964
      }
5965
            /* call setProperty( $propname.. . */
5966
      switch( strtoupper( $propname )) {
5967
        case 'ATTENDEE':
5968
          foreach( $propattr as $pix => $attr ) {
5969
            if( !in_array( strtoupper( $pix ), array( 'MEMBER', 'DELEGATED-TO', 'DELEGATED-FROM' )))
5970
              continue;
5971
            $attr2 = explode( ',', $attr );
5972
              if( 1 < count( $attr2 ))
5973
                $propattr[$pix] = $attr2;
5974
          }
5975
          $this->setProperty( $propname, $line, $propattr );
5976
          break;
5977
        case 'CATEGORIES':
5978
        case 'RESOURCES':
5979
          if( FALSE !== strpos( $line, ',' )) {
5980
            $content  = array( 0 => '' );
5981
            $cix = $lix = 0;
5982
            while( FALSE !== substr( $line, $lix, 1 )) {
5983
              if(( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
5984
                $cix++;
5985
                $content[$cix] = '';
5986
              }
5987
              else
5988
                $content[$cix] .= $line[$lix];
5989
              $lix++;
5990
            }
5991
            if( 1 < count( $content )) {
5992
              $content = array_values( $content );
5993
              foreach( $content as $cix => $contentPart )
5994
                $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
5995
              $this->setProperty( $propname, $content, $propattr );
5996
              break;
5997
            }
5998
            else
5999
              $line = reset( $content );
6000
          }
6001
        case 'COMMENT':
6002
        case 'CONTACT':
6003
        case 'DESCRIPTION':
6004
        case 'LOCATION':
6005
        case 'SUMMARY':
6006
          if( empty( $line ))
6007
            $propattr = null;
6008
          $this->setProperty( $propname, iCalUtilityFunctions::_strunrep( $line ), $propattr );
6009
          break;
6010
        case 'REQUEST-STATUS':
6011
          $values    = explode( ';', $line, 3 );
6012
          $values[1] = ( !isset( $values[1] )) ? null : iCalUtilityFunctions::_strunrep( $values[1] );
6013
          $values[2] = ( !isset( $values[2] )) ? null : iCalUtilityFunctions::_strunrep( $values[2] );
6014
          $this->setProperty( $propname
6015
                            , $values[0]  // statcode
6016
                            , $values[1]  // statdesc
6017
                            , $values[2]  // extdata
6018
                            , $propattr );
6019
          break;
6020
        case 'FREEBUSY':
6021
          $fbtype = ( isset( $propattr['FBTYPE'] )) ? $propattr['FBTYPE'] : ''; // force setting default, if missing
6022
          unset( $propattr['FBTYPE'] );
6023
          $values = explode( ',', $line );
6024
          foreach( $values as $vix => $value ) {
6025
            $value2 = explode( '/', $value );
6026
            if( 1 < count( $value2 ))
6027
              $values[$vix] = $value2;
6028
          }
6029
          $this->setProperty( $propname, $fbtype, $values, $propattr );
6030
          break;
6031
        case 'GEO':
6032
          $value = explode( ';', $line, 2 );
6033
          if( 2 > count( $value ))
6034
            $value[1] = null;
6035
          $this->setProperty( $propname, $value[0], $value[1], $propattr );
6036
          break;
6037
        case 'EXDATE':
6038
          $values = ( !empty( $line )) ? explode( ',', $line ) : null;
6039
          $this->setProperty( $propname, $values, $propattr );
6040
          break;
6041
        case 'RDATE':
6042
          if( empty( $line )) {
6043
            $this->setProperty( $propname, $line, $propattr );
6044
            break;
6045
          }
6046
          $values = explode( ',', $line );
6047
          foreach( $values as $vix => $value ) {
6048
            $value2 = explode( '/', $value );
6049
            if( 1 < count( $value2 ))
6050
              $values[$vix] = $value2;
6051
          }
6052
          $this->setProperty( $propname, $values, $propattr );
6053
          break;
6054
        case 'EXRULE':
6055
        case 'RRULE':
6056
          $values = explode( ';', $line );
6057
          $recur = array();
6058
          foreach( $values as $value2 ) {
6059
            if( empty( $value2 ))
6060
              continue; // ;-char in ending position ???
6061
            $value3 = explode( '=', $value2, 2 );
6062
            $rulelabel = strtoupper( $value3[0] );
6063
            switch( $rulelabel ) {
6064
              case 'BYDAY': {
6065
                $value4 = explode( ',', $value3[1] );
6066
                if( 1 < count( $value4 )) {
6067
                  foreach( $value4 as $v5ix => $value5 ) {
6068
                    $value6 = array();
6069
                    $dayno = $dayname = null;
6070
                    $value5 = trim( (string) $value5 );
6071
                    if(( ctype_alpha( substr( $value5, -1 ))) &&
6072
                       ( ctype_alpha( substr( $value5, -2, 1 )))) {
6073
                      $dayname = substr( $value5, -2, 2 );
6074
                      if( 2 < strlen( $value5 ))
6075
                        $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
6076
                    }
6077
                    if( $dayno )
6078
                      $value6[] = $dayno;
6079
                    if( $dayname )
6080
                      $value6['DAY'] = $dayname;
6081
                    $value4[$v5ix] = $value6;
6082
                  }
6083
                }
6084
                else {
6085
                  $value4 = array();
6086
                  $dayno  = $dayname = null;
6087
                  $value5 = trim( (string) $value3[1] );
6088
                  if(( ctype_alpha( substr( $value5, -1 ))) &&
6089
                     ( ctype_alpha( substr( $value5, -2, 1 )))) {
6090
                      $dayname = substr( $value5, -2, 2 );
6091
                    if( 2 < strlen( $value5 ))
6092
                      $dayno = substr( $value5, 0, ( strlen( $value5 ) - 2 ));
6093
                  }
6094
                  if( $dayno )
6095
                    $value4[] = $dayno;
6096
                  if( $dayname )
6097
                    $value4['DAY'] = $dayname;
6098
                }
6099
                $recur[$rulelabel] = $value4;
6100
                break;
6101
              }
6102
              default: {
6103
                $value4 = explode( ',', $value3[1] );
6104
                if( 1 < count( $value4 ))
6105
                  $value3[1] = $value4;
6106
                $recur[$rulelabel] = $value3[1];
6107
                break;
6108
              }
6109
            } // end - switch $rulelabel
6110
          } // end - foreach( $values.. .
6111
          $this->setProperty( $propname, $recur, $propattr );
6112
          break;
6113
        case 'X-':
6114
          $propname = ( isset( $propname2 )) ? $propname2 : $propname;
6115
          unset( $propname2 );
6116
        case 'ACTION':
6117
        case 'CLASSIFICATION':
6118
        case 'STATUS':
6119
        case 'TRANSP':
6120
        case 'UID':
6121
        case 'TZID':
6122
        case 'RELATED-TO':
6123
        case 'TZNAME':
6124
          $line = iCalUtilityFunctions::_strunrep( $line );
6125
        default:
6126
          $this->setProperty( $propname, $line, $propattr );
6127
          break;
6128
      } // end  switch( $propname.. .
6129
    } // end - foreach( $proprows.. .
6130
    unset( $unparsedtext, $this->unparsed, $proprows );
6131
    if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) {
6132
      $ckeys = array_keys( $this->components );
6133
      foreach( $ckeys as $ckey ) {
6134
        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
6135
          $this->components[$ckey]->parse();
6136
        }
6137
      }
6138
    }
6139
    return TRUE;
6140
  }
6141
/*********************************************************************************/
6142
/*********************************************************************************/
6143
/**
6144
 * return a copy of this component
6145
 *
6146
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6147
 * @since 2.15.4 - 2012-10-18
6148
 * @return object
6149
 */
6150
  function copy() {
6151
    return unserialize( serialize( $this ));
6152
 }
6153
/*********************************************************************************/
6154
/*********************************************************************************/
6155
/**
6156
 * delete calendar subcomponent from component container
6157
 *
6158
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6159
 * @since 2.8.8 - 2011-03-15
6160
 * @param mixed $arg1 ordno / component type / component uid
6161
 * @param mixed $arg2 optional, ordno if arg1 = component type
6162
 * @return void
6163
 */
6164
  function deleteComponent( $arg1, $arg2=FALSE  ) {
6165
    if( !isset( $this->components )) return FALSE;
6166
    $argType = $index = null;
6167
    if ( ctype_digit( (string) $arg1 )) {
6168
      $argType = 'INDEX';
6169
      $index   = (int) $arg1 - 1;
6170
    }
6171
    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
6172
      $argType = strtolower( $arg1 );
6173
      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
6174
    }
6175
    $cix2dC = 0;
6176
    foreach ( $this->components as $cix => $component) {
6177
      if( empty( $component )) continue;
6178
      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
6179
        unset( $this->components[$cix] );
6180
        return TRUE;
6181
      }
6182
      elseif( $argType == $component->objName ) {
6183
        if( $index == $cix2dC ) {
6184
          unset( $this->components[$cix] );
6185
          return TRUE;
6186
        }
6187
        $cix2dC++;
6188
      }
6189
      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
6190
        unset( $this->components[$cix] );
6191
        return TRUE;
6192
      }
6193
    }
6194
    return FALSE;
6195
  }
6196
/**
6197
 * get calendar component subcomponent from component container
6198
 *
6199
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6200
 * @since 2.8.8 - 2011-03-15
6201
 * @param mixed $arg1 optional, ordno/component type/ component uid
6202
 * @param mixed $arg2 optional, ordno if arg1 = component type
6203
 * @return object
6204
 */
6205
  function getComponent ( $arg1=FALSE, $arg2=FALSE ) {
6206
    if( !isset( $this->components )) return FALSE;
6207
    $index = $argType = null;
6208
    if ( !$arg1 ) {
6209
      $argType = 'INDEX';
6210
      $index   = $this->compix['INDEX'] =
6211
        ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
6212
    }
6213
    elseif ( ctype_digit( (string) $arg1 )) {
6214
      $argType = 'INDEX';
6215
      $index   = (int) $arg1;
6216
      unset( $this->compix );
6217
    }
6218
    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
6219
      unset( $this->compix['INDEX'] );
6220
      $argType = strtolower( $arg1 );
6221
      if( !$arg2 )
6222
        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
6223
      else
6224
        $index = (int) $arg2;
6225
    }
6226
    $index  -= 1;
6227
    $ckeys = array_keys( $this->components );
6228
    if( !empty( $index) && ( $index > end( $ckeys )))
6229
      return FALSE;
6230
    $cix2gC = 0;
6231
    foreach( $this->components as $cix => $component ) {
6232
      if( empty( $component )) continue;
6233
      if(( 'INDEX' == $argType ) && ( $index == $cix ))
6234
        return $component->copy();
6235
      elseif( $argType == $component->objName ) {
6236
         if( $index == $cix2gC )
6237
           return $component->copy();
6238
         $cix2gC++;
6239
      }
6240
      elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' )))
6241
        return $component->copy();
6242
    }
6243
            /* not found.. . */
6244
    unset( $this->compix );
6245
    return false;
6246
  }
6247
/**
6248
 * add calendar component as subcomponent to container for subcomponents
6249
 *
6250
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6251
 * @since 1.x.x - 2007-04-24
6252
 * @param object $component calendar component
6253
 * @return void
6254
 */
6255
  function addSubComponent ( $component ) {
6256
    $this->setComponent( $component );
6257
  }
6258
/**
6259
 * create new calendar component subcomponent, already included within component
6260
 *
6261
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6262
 * @since 2.6.33 - 2011-01-03
6263
 * @param string $compType subcomponent type
6264
 * @return object (reference)
6265
 */
6266
  function & newComponent( $compType ) {
6267
    $config = $this->getConfig();
6268
    $keys   = array_keys( $this->components );
6269
    $ix     = end( $keys) + 1;
6270
    switch( strtoupper( $compType )) {
6271
      case 'ALARM':
6272
      case 'VALARM':
6273
        $this->components[$ix] = new valarm( $config );
6274
        break;
6275
      case 'STANDARD':
6276
        array_unshift( $this->components, new vtimezone( 'STANDARD', $config ));
6277
        $ix = 0;
6278
        break;
6279
      case 'DAYLIGHT':
6280
        $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config );
6281
        break;
6282
      default:
6283
        return FALSE;
6284
    }
6285
    return $this->components[$ix];
6286
  }
6287
/**
6288
 * add calendar component as subcomponent to container for subcomponents
6289
 *
6290
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6291
 * @since 2.8.8 - 2011-03-15
6292
 * @param object $component calendar component
6293
 * @param mixed $arg1 optional, ordno/component type/ component uid
6294
 * @param mixed $arg2 optional, ordno if arg1 = component type
6295
 * @return bool
6296
 */
6297
  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
6298
    if( !isset( $this->components )) return FALSE;
6299
    $component->setConfig( $this->getConfig(), FALSE, TRUE );
6300
    if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) {
6301
            /* make sure dtstamp and uid is set */
6302
      $dummy = $component->getProperty( 'dtstamp' );
6303
      $dummy = $component->getProperty( 'uid' );
6304
    }
6305
    if( !$arg1 ) { // plain insert, last in chain
6306
      $this->components[] = $component->copy();
6307
      return TRUE;
6308
    }
6309
    $argType = $index = null;
6310
    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
6311
      $argType = 'INDEX';
6312
      $index   = (int) $arg1 - 1;
6313
    }
6314
    elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) {
6315
      $argType = strtolower( $arg1 );
6316
      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
6317
    }
6318
    // else if arg1 is set, arg1 must be an UID
6319
    $cix2sC = 0;
6320
    foreach ( $this->components as $cix => $component2 ) {
6321
      if( empty( $component2 )) continue;
6322
      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
6323
        $this->components[$cix] = $component->copy();
6324
        return TRUE;
6325
      }
6326
      elseif( $argType == $component2->objName ) { // component Type index insert/replace
6327
        if( $index == $cix2sC ) {
6328
          $this->components[$cix] = $component->copy();
6329
          return TRUE;
6330
        }
6331
        $cix2sC++;
6332
      }
6333
      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
6334
        $this->components[$cix] = $component->copy();
6335
        return TRUE;
6336
      }
6337
    }
6338
            /* arg1=index and not found.. . insert at index .. .*/
6339
    if( 'INDEX' == $argType ) {
6340
      $this->components[$index] = $component->copy();
6341
      ksort( $this->components, SORT_NUMERIC );
6342
    }
6343
    else    /* not found.. . insert last in chain anyway .. .*/
6344
    $this->components[] = $component->copy();
6345
    return TRUE;
6346
  }
6347
/**
6348
 * creates formatted output for subcomponents
6349
 *
6350
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6351
 * @since 2.11.20 - 2012-02-06
6352
 * @param array $xcaldecl
6353
 * @return string
6354
 */
6355
  function createSubComponent() {
6356
    $output = null;
6357
    if( 'vtimezone' == $this->objName ) { // sort subComponents, first standard, then daylight, in dtstart order
6358
      $stdarr = $dlarr = array();
6359
      foreach( $this->components as $component ) {
6360
        if( empty( $component ))
6361
          continue;
6362
        $dt  = $component->getProperty( 'dtstart' );
6363
        $key = sprintf( '%04d%02d%02d%02d%02d%02d000', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
6364
        if( 'standard' == $component->objName ) {
6365
          while( isset( $stdarr[$key] ))
6366
            $key += 1;
6367
          $stdarr[$key] = $component->copy();
6368
        }
6369
        elseif( 'daylight' == $component->objName ) {
6370
          while( isset( $dlarr[$key] ))
6371
            $key += 1;
6372
          $dlarr[$key] = $component->copy();
6373
        }
6374
      } // end foreach( $this->components as $component )
6375
      $this->components = array();
6376
      ksort( $stdarr, SORT_NUMERIC );
6377
      foreach( $stdarr as $std )
6378
        $this->components[] = $std->copy();
6379
      unset( $stdarr );
6380
      ksort( $dlarr,  SORT_NUMERIC );
6381
      foreach( $dlarr as $dl )
6382
        $this->components[] = $dl->copy();
6383
      unset( $dlarr );
6384
    } // end if( 'vtimezone' == $this->objName )
6385
    foreach( $this->components as $component ) {
6386
      $component->setConfig( $this->getConfig(), FALSE, TRUE );
6387
      $output .= $component->createComponent( $this->xcaldecl );
6388
    }
6389
    return $output;
6390
  }
6391
}
6392
/*********************************************************************************/
6393
/*********************************************************************************/
6394
/**
6395
 * class for calendar component VEVENT
6396
 *
6397
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6398
 * @since 2.5.1 - 2008-10-12
6399
 */
6400
class vevent extends calendarComponent {
6401
  var $attach;
6402
  var $attendee;
6403
  var $categories;
6404
  var $comment;
6405
  var $contact;
6406
  var $class;
6407
  var $created;
6408
  var $description;
6409
  var $dtend;
6410
  var $dtstart;
6411
  var $duration;
6412
  var $exdate;
6413
  var $exrule;
6414
  var $geo;
6415
  var $lastmodified;
6416
  var $location;
6417
  var $organizer;
6418
  var $priority;
6419
  var $rdate;
6420
  var $recurrenceid;
6421
  var $relatedto;
6422
  var $requeststatus;
6423
  var $resources;
6424
  var $rrule;
6425
  var $sequence;
6426
  var $status;
6427
  var $summary;
6428
  var $transp;
6429
  var $url;
6430
  var $xprop;
6431
            //  component subcomponents container
6432
  var $components;
6433
/**
6434
 * constructor for calendar component VEVENT object
6435
 *
6436
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6437
 * @since 2.8.2 - 2011-05-01
6438
 * @param  array $config
6439
 * @return void
6440
 */
6441
  function vevent( $config = array()) {
6442
    $this->calendarComponent();
6443

    
6444
    $this->attach          = '';
6445
    $this->attendee        = '';
6446
    $this->categories      = '';
6447
    $this->class           = '';
6448
    $this->comment         = '';
6449
    $this->contact         = '';
6450
    $this->created         = '';
6451
    $this->description     = '';
6452
    $this->dtstart         = '';
6453
    $this->dtend           = '';
6454
    $this->duration        = '';
6455
    $this->exdate          = '';
6456
    $this->exrule          = '';
6457
    $this->geo             = '';
6458
    $this->lastmodified    = '';
6459
    $this->location        = '';
6460
    $this->organizer       = '';
6461
    $this->priority        = '';
6462
    $this->rdate           = '';
6463
    $this->recurrenceid    = '';
6464
    $this->relatedto       = '';
6465
    $this->requeststatus   = '';
6466
    $this->resources       = '';
6467
    $this->rrule           = '';
6468
    $this->sequence        = '';
6469
    $this->status          = '';
6470
    $this->summary         = '';
6471
    $this->transp          = '';
6472
    $this->url             = '';
6473
    $this->xprop           = '';
6474

    
6475
    $this->components      = array();
6476

    
6477
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6478
                                          $config['language']   = ICAL_LANG;
6479
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6480
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6481
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6482
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6483
    $this->setConfig( $config );
6484

    
6485
  }
6486
/**
6487
 * create formatted output for calendar component VEVENT object instance
6488
 *
6489
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6490
 * @since 2.10.16 - 2011-10-28
6491
 * @param array $xcaldecl
6492
 * @return string
6493
 */
6494
  function createComponent( &$xcaldecl ) {
6495
    $objectname    = $this->_createFormat();
6496
    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6497
    $component    .= $this->createUid();
6498
    $component    .= $this->createDtstamp();
6499
    $component    .= $this->createAttach();
6500
    $component    .= $this->createAttendee();
6501
    $component    .= $this->createCategories();
6502
    $component    .= $this->createComment();
6503
    $component    .= $this->createContact();
6504
    $component    .= $this->createClass();
6505
    $component    .= $this->createCreated();
6506
    $component    .= $this->createDescription();
6507
    $component    .= $this->createDtstart();
6508
    $component    .= $this->createDtend();
6509
    $component    .= $this->createDuration();
6510
    $component    .= $this->createExdate();
6511
    $component    .= $this->createExrule();
6512
    $component    .= $this->createGeo();
6513
    $component    .= $this->createLastModified();
6514
    $component    .= $this->createLocation();
6515
    $component    .= $this->createOrganizer();
6516
    $component    .= $this->createPriority();
6517
    $component    .= $this->createRdate();
6518
    $component    .= $this->createRrule();
6519
    $component    .= $this->createRelatedTo();
6520
    $component    .= $this->createRequestStatus();
6521
    $component    .= $this->createRecurrenceid();
6522
    $component    .= $this->createResources();
6523
    $component    .= $this->createSequence();
6524
    $component    .= $this->createStatus();
6525
    $component    .= $this->createSummary();
6526
    $component    .= $this->createTransp();
6527
    $component    .= $this->createUrl();
6528
    $component    .= $this->createXprop();
6529
    $component    .= $this->createSubComponent();
6530
    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6531
    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6532
      foreach( $this->xcaldecl as $localxcaldecl )
6533
        $xcaldecl[] = $localxcaldecl;
6534
    }
6535
    return $component;
6536
  }
6537
}
6538
/*********************************************************************************/
6539
/*********************************************************************************/
6540
/**
6541
 * class for calendar component VTODO
6542
 *
6543
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6544
 * @since 2.5.1 - 2008-10-12
6545
 */
6546
class vtodo extends calendarComponent {
6547
  var $attach;
6548
  var $attendee;
6549
  var $categories;
6550
  var $comment;
6551
  var $completed;
6552
  var $contact;
6553
  var $class;
6554
  var $created;
6555
  var $description;
6556
  var $dtstart;
6557
  var $due;
6558
  var $duration;
6559
  var $exdate;
6560
  var $exrule;
6561
  var $geo;
6562
  var $lastmodified;
6563
  var $location;
6564
  var $organizer;
6565
  var $percentcomplete;
6566
  var $priority;
6567
  var $rdate;
6568
  var $recurrenceid;
6569
  var $relatedto;
6570
  var $requeststatus;
6571
  var $resources;
6572
  var $rrule;
6573
  var $sequence;
6574
  var $status;
6575
  var $summary;
6576
  var $url;
6577
  var $xprop;
6578
            //  component subcomponents container
6579
  var $components;
6580
/**
6581
 * constructor for calendar component VTODO object
6582
 *
6583
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6584
 * @since 2.8.2 - 2011-05-01
6585
 * @param array $config
6586
 * @return void
6587
 */
6588
  function vtodo( $config = array()) {
6589
    $this->calendarComponent();
6590

    
6591
    $this->attach          = '';
6592
    $this->attendee        = '';
6593
    $this->categories      = '';
6594
    $this->class           = '';
6595
    $this->comment         = '';
6596
    $this->completed       = '';
6597
    $this->contact         = '';
6598
    $this->created         = '';
6599
    $this->description     = '';
6600
    $this->dtstart         = '';
6601
    $this->due             = '';
6602
    $this->duration        = '';
6603
    $this->exdate          = '';
6604
    $this->exrule          = '';
6605
    $this->geo             = '';
6606
    $this->lastmodified    = '';
6607
    $this->location        = '';
6608
    $this->organizer       = '';
6609
    $this->percentcomplete = '';
6610
    $this->priority        = '';
6611
    $this->rdate           = '';
6612
    $this->recurrenceid    = '';
6613
    $this->relatedto       = '';
6614
    $this->requeststatus   = '';
6615
    $this->resources       = '';
6616
    $this->rrule           = '';
6617
    $this->sequence        = '';
6618
    $this->status          = '';
6619
    $this->summary         = '';
6620
    $this->url             = '';
6621
    $this->xprop           = '';
6622

    
6623
    $this->components      = array();
6624

    
6625
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6626
                                          $config['language']   = ICAL_LANG;
6627
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6628
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6629
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6630
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6631
    $this->setConfig( $config );
6632

    
6633
  }
6634
/**
6635
 * create formatted output for calendar component VTODO object instance
6636
 *
6637
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6638
 * @since 2.5.1 - 2008-11-07
6639
 * @param array $xcaldecl
6640
 * @return string
6641
 */
6642
  function createComponent( &$xcaldecl ) {
6643
    $objectname    = $this->_createFormat();
6644
    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6645
    $component    .= $this->createUid();
6646
    $component    .= $this->createDtstamp();
6647
    $component    .= $this->createAttach();
6648
    $component    .= $this->createAttendee();
6649
    $component    .= $this->createCategories();
6650
    $component    .= $this->createClass();
6651
    $component    .= $this->createComment();
6652
    $component    .= $this->createCompleted();
6653
    $component    .= $this->createContact();
6654
    $component    .= $this->createCreated();
6655
    $component    .= $this->createDescription();
6656
    $component    .= $this->createDtstart();
6657
    $component    .= $this->createDue();
6658
    $component    .= $this->createDuration();
6659
    $component    .= $this->createExdate();
6660
    $component    .= $this->createExrule();
6661
    $component    .= $this->createGeo();
6662
    $component    .= $this->createLastModified();
6663
    $component    .= $this->createLocation();
6664
    $component    .= $this->createOrganizer();
6665
    $component    .= $this->createPercentComplete();
6666
    $component    .= $this->createPriority();
6667
    $component    .= $this->createRdate();
6668
    $component    .= $this->createRelatedTo();
6669
    $component    .= $this->createRequestStatus();
6670
    $component    .= $this->createRecurrenceid();
6671
    $component    .= $this->createResources();
6672
    $component    .= $this->createRrule();
6673
    $component    .= $this->createSequence();
6674
    $component    .= $this->createStatus();
6675
    $component    .= $this->createSummary();
6676
    $component    .= $this->createUrl();
6677
    $component    .= $this->createXprop();
6678
    $component    .= $this->createSubComponent();
6679
    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6680
    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6681
      foreach( $this->xcaldecl as $localxcaldecl )
6682
        $xcaldecl[] = $localxcaldecl;
6683
    }
6684
    return $component;
6685
  }
6686
}
6687
/*********************************************************************************/
6688
/*********************************************************************************/
6689
/**
6690
 * class for calendar component VJOURNAL
6691
 *
6692
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6693
 * @since 2.5.1 - 2008-10-12
6694
 */
6695
class vjournal extends calendarComponent {
6696
  var $attach;
6697
  var $attendee;
6698
  var $categories;
6699
  var $comment;
6700
  var $contact;
6701
  var $class;
6702
  var $created;
6703
  var $description;
6704
  var $dtstart;
6705
  var $exdate;
6706
  var $exrule;
6707
  var $lastmodified;
6708
  var $organizer;
6709
  var $rdate;
6710
  var $recurrenceid;
6711
  var $relatedto;
6712
  var $requeststatus;
6713
  var $rrule;
6714
  var $sequence;
6715
  var $status;
6716
  var $summary;
6717
  var $url;
6718
  var $xprop;
6719
/**
6720
 * constructor for calendar component VJOURNAL object
6721
 *
6722
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6723
 * @since 2.8.2 - 2011-05-01
6724
 * @param array $config
6725
 * @return void
6726
 */
6727
  function vjournal( $config = array()) {
6728
    $this->calendarComponent();
6729

    
6730
    $this->attach          = '';
6731
    $this->attendee        = '';
6732
    $this->categories      = '';
6733
    $this->class           = '';
6734
    $this->comment         = '';
6735
    $this->contact         = '';
6736
    $this->created         = '';
6737
    $this->description     = '';
6738
    $this->dtstart         = '';
6739
    $this->exdate          = '';
6740
    $this->exrule          = '';
6741
    $this->lastmodified    = '';
6742
    $this->organizer       = '';
6743
    $this->rdate           = '';
6744
    $this->recurrenceid    = '';
6745
    $this->relatedto       = '';
6746
    $this->requeststatus   = '';
6747
    $this->rrule           = '';
6748
    $this->sequence        = '';
6749
    $this->status          = '';
6750
    $this->summary         = '';
6751
    $this->url             = '';
6752
    $this->xprop           = '';
6753

    
6754
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6755
                                          $config['language']   = ICAL_LANG;
6756
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6757
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6758
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6759
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6760
    $this->setConfig( $config );
6761

    
6762
  }
6763
/**
6764
 * create formatted output for calendar component VJOURNAL object instance
6765
 *
6766
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6767
 * @since 2.5.1 - 2008-10-12
6768
 * @param array $xcaldecl
6769
 * @return string
6770
 */
6771
  function createComponent( &$xcaldecl ) {
6772
    $objectname = $this->_createFormat();
6773
    $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6774
    $component .= $this->createUid();
6775
    $component .= $this->createDtstamp();
6776
    $component .= $this->createAttach();
6777
    $component .= $this->createAttendee();
6778
    $component .= $this->createCategories();
6779
    $component .= $this->createClass();
6780
    $component .= $this->createComment();
6781
    $component .= $this->createContact();
6782
    $component .= $this->createCreated();
6783
    $component .= $this->createDescription();
6784
    $component .= $this->createDtstart();
6785
    $component .= $this->createExdate();
6786
    $component .= $this->createExrule();
6787
    $component .= $this->createLastModified();
6788
    $component .= $this->createOrganizer();
6789
    $component .= $this->createRdate();
6790
    $component .= $this->createRequestStatus();
6791
    $component .= $this->createRecurrenceid();
6792
    $component .= $this->createRelatedTo();
6793
    $component .= $this->createRrule();
6794
    $component .= $this->createSequence();
6795
    $component .= $this->createStatus();
6796
    $component .= $this->createSummary();
6797
    $component .= $this->createUrl();
6798
    $component .= $this->createXprop();
6799
    $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
6800
    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6801
      foreach( $this->xcaldecl as $localxcaldecl )
6802
        $xcaldecl[] = $localxcaldecl;
6803
    }
6804
    return $component;
6805
  }
6806
}
6807
/*********************************************************************************/
6808
/*********************************************************************************/
6809
/**
6810
 * class for calendar component VFREEBUSY
6811
 *
6812
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6813
 * @since 2.5.1 - 2008-10-12
6814
 */
6815
class vfreebusy extends calendarComponent {
6816
  var $attendee;
6817
  var $comment;
6818
  var $contact;
6819
  var $dtend;
6820
  var $dtstart;
6821
  var $duration;
6822
  var $freebusy;
6823
  var $organizer;
6824
  var $requeststatus;
6825
  var $url;
6826
  var $xprop;
6827
            //  component subcomponents container
6828
  var $components;
6829
/**
6830
 * constructor for calendar component VFREEBUSY object
6831
 *
6832
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6833
 * @since 2.8.2 - 2011-05-01
6834
 * @param array $config
6835
 * @return void
6836
 */
6837
  function vfreebusy( $config = array()) {
6838
    $this->calendarComponent();
6839

    
6840
    $this->attendee        = '';
6841
    $this->comment         = '';
6842
    $this->contact         = '';
6843
    $this->dtend           = '';
6844
    $this->dtstart         = '';
6845
    $this->duration        = '';
6846
    $this->freebusy        = '';
6847
    $this->organizer       = '';
6848
    $this->requeststatus   = '';
6849
    $this->url             = '';
6850
    $this->xprop           = '';
6851

    
6852
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6853
                                          $config['language']   = ICAL_LANG;
6854
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6855
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6856
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6857
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6858
    $this->setConfig( $config );
6859

    
6860
  }
6861
/**
6862
 * create formatted output for calendar component VFREEBUSY object instance
6863
 *
6864
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6865
 * @since 2.3.1 - 2007-11-19
6866
 * @param array $xcaldecl
6867
 * @return string
6868
 */
6869
  function createComponent( &$xcaldecl ) {
6870
    $objectname = $this->_createFormat();
6871
    $component  = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6872
    $component .= $this->createUid();
6873
    $component .= $this->createDtstamp();
6874
    $component .= $this->createAttendee();
6875
    $component .= $this->createComment();
6876
    $component .= $this->createContact();
6877
    $component .= $this->createDtstart();
6878
    $component .= $this->createDtend();
6879
    $component .= $this->createDuration();
6880
    $component .= $this->createFreebusy();
6881
    $component .= $this->createOrganizer();
6882
    $component .= $this->createRequestStatus();
6883
    $component .= $this->createUrl();
6884
    $component .= $this->createXprop();
6885
    $component .= $this->componentEnd1.$objectname.$this->componentEnd2;
6886
    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6887
      foreach( $this->xcaldecl as $localxcaldecl )
6888
        $xcaldecl[] = $localxcaldecl;
6889
    }
6890
    return $component;
6891
  }
6892
}
6893
/*********************************************************************************/
6894
/*********************************************************************************/
6895
/**
6896
 * class for calendar component VALARM
6897
 *
6898
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6899
 * @since 2.5.1 - 2008-10-12
6900
 */
6901
class valarm extends calendarComponent {
6902
  var $action;
6903
  var $attach;
6904
  var $attendee;
6905
  var $description;
6906
  var $duration;
6907
  var $repeat;
6908
  var $summary;
6909
  var $trigger;
6910
  var $xprop;
6911
/**
6912
 * constructor for calendar component VALARM object
6913
 *
6914
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6915
 * @since 2.8.2 - 2011-05-01
6916
 * @param array $config
6917
 * @return void
6918
 */
6919
  function valarm( $config = array()) {
6920
    $this->calendarComponent();
6921

    
6922
    $this->action          = '';
6923
    $this->attach          = '';
6924
    $this->attendee        = '';
6925
    $this->description     = '';
6926
    $this->duration        = '';
6927
    $this->repeat          = '';
6928
    $this->summary         = '';
6929
    $this->trigger         = '';
6930
    $this->xprop           = '';
6931

    
6932
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
6933
                                          $config['language']   = ICAL_LANG;
6934
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
6935
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
6936
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
6937
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
6938
    $this->setConfig( $config );
6939

    
6940
  }
6941
/**
6942
 * create formatted output for calendar component VALARM object instance
6943
 *
6944
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6945
 * @since 2.5.1 - 2008-10-22
6946
 * @param array $xcaldecl
6947
 * @return string
6948
 */
6949
  function createComponent( &$xcaldecl ) {
6950
    $objectname    = $this->_createFormat();
6951
    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
6952
    $component    .= $this->createAction();
6953
    $component    .= $this->createAttach();
6954
    $component    .= $this->createAttendee();
6955
    $component    .= $this->createDescription();
6956
    $component    .= $this->createDuration();
6957
    $component    .= $this->createRepeat();
6958
    $component    .= $this->createSummary();
6959
    $component    .= $this->createTrigger();
6960
    $component    .= $this->createXprop();
6961
    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
6962
    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
6963
      foreach( $this->xcaldecl as $localxcaldecl )
6964
        $xcaldecl[] = $localxcaldecl;
6965
    }
6966
    return $component;
6967
  }
6968
}
6969
/**********************************************************************************
6970
/*********************************************************************************/
6971
/**
6972
 * class for calendar component VTIMEZONE
6973
 *
6974
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6975
 * @since 2.5.1 - 2008-10-12
6976
 */
6977
class vtimezone extends calendarComponent {
6978
  var $timezonetype;
6979

    
6980
  var $comment;
6981
  var $dtstart;
6982
  var $lastmodified;
6983
  var $rdate;
6984
  var $rrule;
6985
  var $tzid;
6986
  var $tzname;
6987
  var $tzoffsetfrom;
6988
  var $tzoffsetto;
6989
  var $tzurl;
6990
  var $xprop;
6991
            //  component subcomponents container
6992
  var $components;
6993
/**
6994
 * constructor for calendar component VTIMEZONE object
6995
 *
6996
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
6997
 * @since 2.8.2 - 2011-05-01
6998
 * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT )
6999
 * @param array $config
7000
 * @return void
7001
 */
7002
  function vtimezone( $timezonetype=FALSE, $config = array()) {
7003
    if( is_array( $timezonetype )) {
7004
      $config       = $timezonetype;
7005
      $timezonetype = FALSE;
7006
    }
7007
    if( !$timezonetype )
7008
      $this->timezonetype = 'VTIMEZONE';
7009
    else
7010
      $this->timezonetype = strtoupper( $timezonetype );
7011
    $this->calendarComponent();
7012

    
7013
    $this->comment         = '';
7014
    $this->dtstart         = '';
7015
    $this->lastmodified    = '';
7016
    $this->rdate           = '';
7017
    $this->rrule           = '';
7018
    $this->tzid            = '';
7019
    $this->tzname          = '';
7020
    $this->tzoffsetfrom    = '';
7021
    $this->tzoffsetto      = '';
7022
    $this->tzurl           = '';
7023
    $this->xprop           = '';
7024

    
7025
    $this->components      = array();
7026

    
7027
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
7028
                                          $config['language']   = ICAL_LANG;
7029
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
7030
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
7031
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
7032
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
7033
    $this->setConfig( $config );
7034

    
7035
  }
7036
/**
7037
 * create formatted output for calendar component VTIMEZONE object instance
7038
 *
7039
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7040
 * @since 2.5.1 - 2008-10-25
7041
 * @param array $xcaldecl
7042
 * @return string
7043
 */
7044
  function createComponent( &$xcaldecl ) {
7045
    $objectname    = $this->_createFormat();
7046
    $component     = $this->componentStart1.$objectname.$this->componentStart2.$this->nl;
7047
    $component    .= $this->createTzid();
7048
    $component    .= $this->createLastModified();
7049
    $component    .= $this->createTzurl();
7050
    $component    .= $this->createDtstart();
7051
    $component    .= $this->createTzoffsetfrom();
7052
    $component    .= $this->createTzoffsetto();
7053
    $component    .= $this->createComment();
7054
    $component    .= $this->createRdate();
7055
    $component    .= $this->createRrule();
7056
    $component    .= $this->createTzname();
7057
    $component    .= $this->createXprop();
7058
    $component    .= $this->createSubComponent();
7059
    $component    .= $this->componentEnd1.$objectname.$this->componentEnd2;
7060
    if( is_array( $this->xcaldecl ) && ( 0 < count( $this->xcaldecl ))) {
7061
      foreach( $this->xcaldecl as $localxcaldecl )
7062
        $xcaldecl[] = $localxcaldecl;
7063
    }
7064
    return $component;
7065
  }
7066
}
7067
/*********************************************************************************/
7068
/*********************************************************************************/
7069
/**
7070
 * moving all utility (static) functions to a utility class
7071
 * 20111223 - move iCalUtilityFunctions class to the end of the iCalcreator class file
7072
 *
7073
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7074
 * @since 2.10.1 - 2011-07-16
7075
 *
7076
 */
7077
class iCalUtilityFunctions {
7078
  // Store the single instance of iCalUtilityFunctions
7079
  private static $m_pInstance;
7080

    
7081
  // Private constructor to limit object instantiation to within the class
7082
  private function __construct() {
7083
    $m_pInstance = FALSE;
7084
  }
7085

    
7086
  // Getter method for creating/returning the single instance of this class
7087
  public static function getInstance() {
7088
    if (!self::$m_pInstance)
7089
      self::$m_pInstance = new iCalUtilityFunctions();
7090

    
7091
    return self::$m_pInstance;
7092
  }
7093
/**
7094
 * ensures internal date-time/date format (keyed array) for an input date-time/date array (keyed or unkeyed)
7095
 *
7096
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7097
 * @since 2.16.24 - 2013-06-26
7098
 * @param array $datetime
7099
 * @param int $parno optional, default FALSE
7100
 * @return array
7101
 */
7102
  public static function _date_time_array( $datetime, $parno=FALSE ) {
7103
    return iCalUtilityFunctions::_chkDateArr( $datetime, $parno );
7104
  }
7105
  public static function _chkDateArr( $datetime, $parno=FALSE ) {
7106
    $output = array();
7107
    if(( !$parno || ( 6 <= $parno )) && isset( $datetime[3] ) && !isset( $datetime[4] )) { // Y-m-d with tz
7108
      $temp        = $datetime[3];
7109
      $datetime[3] = $datetime[4] = $datetime[5] = 0;
7110
      $datetime[6] = $temp;
7111
    }
7112
    foreach( $datetime as $dateKey => $datePart ) {
7113
      switch ( $dateKey ) {
7114
        case '0': case 'year':   $output['year']  = $datePart; break;
7115
        case '1': case 'month':  $output['month'] = $datePart; break;
7116
        case '2': case 'day':    $output['day']   = $datePart; break;
7117
      }
7118
      if( 3 != $parno ) {
7119
        switch ( $dateKey ) {
7120
          case '0':
7121
          case '1':
7122
          case '2': break;
7123
          case '3': case 'hour': $output['hour']  = $datePart; break;
7124
          case '4': case 'min' : $output['min']   = $datePart; break;
7125
          case '5': case 'sec' : $output['sec']   = $datePart; break;
7126
          case '6': case 'tz'  : $output['tz']    = $datePart; break;
7127
        }
7128
      }
7129
    }
7130
    if( 3 != $parno ) {
7131
      if( !isset( $output['hour'] ))         $output['hour'] = 0;
7132
      if( !isset( $output['min']  ))         $output['min']  = 0;
7133
      if( !isset( $output['sec']  ))         $output['sec']  = 0;
7134
      if( isset( $output['tz'] ) &&
7135
        (( '+0000' == $output['tz'] ) || ( '-0000' == $output['tz'] ) || ( '+000000' == $output['tz'] ) || ( '-000000' == $output['tz'] )))
7136
                                             $output['tz']   = 'Z';
7137
    }
7138
    return $output;
7139
  }
7140
/**
7141
 * check date(-time) and params arrays for an opt. timezone and if it is a DATE-TIME or DATE (updates $parno and params)
7142
 *
7143
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7144
 * @since 2.10.30 - 2012-01-16
7145
 * @param array $date, date to check
7146
 * @param int $parno, no of date parts (i.e. year, month.. .)
7147
 * @param array $params, property parameters
7148
 * @return void
7149
 */
7150
  public static function _chkdatecfg( $theDate, & $parno, & $params ) {
7151
    if( isset( $params['TZID'] ))
7152
      $parno = 6;
7153
    elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))
7154
      $parno = 3;
7155
    else {
7156
      if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] ))
7157
        $parno = 7;
7158
      if( is_array( $theDate )) {
7159
        if( isset( $theDate['timestamp'] ))
7160
          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null;
7161
        else
7162
          $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null;
7163
        if( !empty( $tzid )) {
7164
          $parno = 7;
7165
          if( !iCalUtilityFunctions::_isOffset( $tzid ))
7166
            $params['TZID'] = $tzid; // save only timezone
7167
        }
7168
        elseif( !$parno && ( 3 == count( $theDate )) &&
7169
          ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )))
7170
          $parno = 3;
7171
        else
7172
          $parno = 6;
7173
      }
7174
      else { // string
7175
        $date = trim( $theDate );
7176
        if( 'Z' == substr( $date, -1 ))
7177
          $parno = 7; // UTC DATE-TIME
7178
        elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) &&
7179
          ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' ))))
7180
          $parno = 3; // DATE
7181
        $date = iCalUtilityFunctions::_strdate2date( $date, $parno );
7182
        unset( $date['unparsedtext'] );
7183
        if( !empty( $date['tz'] )) {
7184
          $parno = 7;
7185
          if( !iCalUtilityFunctions::_isOffset( $date['tz'] ))
7186
            $params['TZID'] = $date['tz']; // save only timezone
7187
        }
7188
        elseif( empty( $parno ))
7189
          $parno = 6;
7190
      }
7191
      if( isset( $params['TZID'] ))
7192
        $parno = 6;
7193
    }
7194
  }
7195
/**
7196
 * vcalendar sort callback function
7197
 *
7198
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7199
 * @since 2.16.2 - 2012-12-17
7200
 * @param array $a
7201
 * @param array $b
7202
 * @return int
7203
 */
7204
  public static function _cmpfcn( $a, $b ) {
7205
    if(        empty( $a ))                       return -1;
7206
    if(        empty( $b ))                       return  1;
7207
    if( 'vtimezone' == $a->objName ) {
7208
      if( 'vtimezone' != $b->objName )            return -1;
7209
      elseif( $a->srtk[0] <= $b->srtk[0] )        return -1;
7210
      else                                        return  1;
7211
    }
7212
    elseif( 'vtimezone' == $b->objName )          return  1;
7213
    $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' );
7214
    for( $k = 0; $k < 4 ; $k++ ) {
7215
      if(        empty( $a->srtk[$k] ))           return -1;
7216
      elseif(    empty( $b->srtk[$k] ))           return  1;
7217
      if( is_array( $a->srtk[$k] )) {
7218
        if( is_array( $b->srtk[$k] )) {
7219
          foreach( $sortkeys as $key ) {
7220
            if    ( !isset( $a->srtk[$k][$key] )) return -1;
7221
            elseif( !isset( $b->srtk[$k][$key] )) return  1;
7222
            if    (  empty( $a->srtk[$k][$key] )) return -1;
7223
            elseif(  empty( $b->srtk[$k][$key] )) return  1;
7224
            if    (         $a->srtk[$k][$key] == $b->srtk[$k][$key])
7225
                                                  continue;
7226
            if    ((  (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] ))
7227
                                                  return -1;
7228
            elseif((  (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] ))
7229
                                                  return  1;
7230
          }
7231
        }
7232
        else                                      return -1;
7233
      }
7234
      elseif( is_array( $b->srtk[$k] ))           return  1;
7235
      elseif( $a->srtk[$k] < $b->srtk[$k] )       return -1;
7236
      elseif( $a->srtk[$k] > $b->srtk[$k] )       return  1;
7237
    }
7238
    return 0;
7239
  }
7240
/**
7241
 * byte oriented line folding fix
7242
 *
7243
 * remove any line-endings that may include spaces or tabs
7244
 * and convert all line endings (iCal default '\r\n'),
7245
 * takes care of '\r\n', '\r' and '\n' and mixed '\r\n'+'\r', '\r\n'+'\n'
7246
 *
7247
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7248
 * @since 2.12.17 - 2012-07-12
7249
 * @param string $text
7250
 * @param string $nl
7251
 * @return string
7252
 */
7253
  public static function convEolChar( & $text, $nl ) {
7254
    $outp = '';
7255
    $cix  = 0;
7256
    while(    isset(   $text[$cix] )) {
7257
      if(     isset(   $text[$cix + 2] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] ) &&
7258
        ((    " " ==   $text[$cix + 2] ) ||  ( "\t" == $text[$cix + 2] )))                    // 2 pos eolchar + ' ' or '\t'
7259
        $cix  += 2;                                                                           // skip 3
7260
      elseif( isset(   $text[$cix + 1] ) &&  ( "\r" == $text[$cix] ) && ( "\n" == $text[$cix + 1] )) {
7261
        $outp .= $nl;                                                                         // 2 pos eolchar
7262
        $cix  += 1;                                                                           // replace with $nl
7263
      }
7264
      elseif( isset(   $text[$cix + 1] ) && (( "\r" == $text[$cix] ) || ( "\n" == $text[$cix] )) &&
7265
           (( " " ==   $text[$cix + 1] ) ||  ( "\t" == $text[$cix + 1] )))                     // 1 pos eolchar + ' ' or '\t'
7266
        $cix  += 1;                                                                            // skip 2
7267
      elseif(( "\r" == $text[$cix] )     ||  ( "\n" == $text[$cix] ))                          // 1 pos eolchar
7268
        $outp .= $nl;                                                                          // replace with $nl
7269
      else
7270
        $outp .= $text[$cix];                                                                  // add any other byte
7271
      $cix    += 1;
7272
    }
7273
    return $outp;
7274
  }
7275
/**
7276
 * create a calendar timezone and standard/daylight components
7277
 *
7278
 * Result when 'Europe/Stockholm' and no from/to arguments is used as timezone:
7279
 *
7280
 * BEGIN:VTIMEZONE
7281
 * TZID:Europe/Stockholm
7282
 * BEGIN:STANDARD
7283
 * DTSTART:20101031T020000
7284
 * TZOFFSETFROM:+0200
7285
 * TZOFFSETTO:+0100
7286
 * TZNAME:CET
7287
 * END:STANDARD
7288
 * BEGIN:DAYLIGHT
7289
 * DTSTART:20100328T030000
7290
 * TZOFFSETFROM:+0100
7291
 * TZOFFSETTO:+0200
7292
 * TZNAME:CEST
7293
 * END:DAYLIGHT
7294
 * END:VTIMEZONE
7295
 *
7296
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7297
 * @since 2.16.1 - 2012-11-26
7298
 * Generates components for all transitions in a date range, based on contribution by Yitzchok Lavi <icalcreator@onebigsystem.com>
7299
 * Additional changes jpirkey
7300
 * @param object $calendar, reference to an iCalcreator calendar instance
7301
 * @param string $timezone, a PHP5 (DateTimeZone) valid timezone
7302
 * @param array  $xProp,    *[x-propName => x-propValue], optional
7303
 * @param int    $from      a unix timestamp
7304
 * @param int    $to        a unix timestamp
7305
 * @return bool
7306
 */
7307
  public static function createTimezone( & $calendar, $timezone, $xProp=array(), $from=null, $to=null ) {
7308
    if( empty( $timezone ))
7309
      return FALSE;
7310
    if( !empty( $from ) && !is_int( $from ))
7311
      return FALSE;
7312
    if( !empty( $to )   && !is_int( $to ))
7313
      return FALSE;
7314
    try {
7315
      $dtz               = new DateTimeZone( $timezone );
7316
      $transitions       = $dtz->getTransitions();
7317
      $utcTz             = new DateTimeZone( 'UTC' );
7318
    }
7319
    catch( Exception $e ) { return FALSE; }
7320
    if( empty( $to )) {
7321
      $dates             = array_keys( $calendar->getProperty( 'dtstart' ));
7322
      if( empty( $dates ))
7323
        $dates           = array( date( 'Ymd' ));
7324
    }
7325
    if( !empty( $from ))
7326
      $dateFrom          = new DateTime( "@$from" );             // set lowest date (UTC)
7327
    else {
7328
      $from              = reset( $dates );                      // set lowest date to the lowest dtstart date
7329
      $dateFrom          = new DateTime( $from.'T000000', $dtz );
7330
      $dateFrom->modify( '-1 month' );                           // set $dateFrom to one month before the lowest date
7331
      $dateFrom->setTimezone( $utcTz );                          // convert local date to UTC
7332
    }
7333
    $dateFromYmd         = $dateFrom->format('Y-m-d' );
7334
    if( !empty( $to ))
7335
      $dateTo            = new DateTime( "@$to" );               // set end date (UTC)
7336
    else {
7337
      $to                = end( $dates );                        // set highest date to the highest dtstart date
7338
      $dateTo            = new DateTime( $to.'T235959', $dtz );
7339
      $dateTo->modify( '+1 year' );                              // set $dateTo to one year after the highest date
7340
      $dateTo->setTimezone( $utcTz );                            // convert local date to UTC
7341
    }
7342
    $dateToYmd           = $dateTo->format('Y-m-d' );
7343
    unset( $dtz );
7344
    $transTemp           = array();
7345
    $prevOffsetfrom      = 0;
7346
    $stdIx  = $dlghtIx   = null;
7347
    $prevTrans           = FALSE;
7348
    foreach( $transitions as $tix => $trans ) {                  // all transitions in date-time order!!
7349
      $date              = new DateTime( "@{$trans['ts']}" );    // set transition date (UTC)
7350
      $transDateYmd      = $date->format('Y-m-d' );
7351
      if ( $transDateYmd < $dateFromYmd ) {
7352
        $prevOffsetfrom  = $trans['offset'];                     // previous trans offset will be 'next' trans offsetFrom
7353
        $prevTrans       = $trans;                               // save it in case we don't find any that match
7354
        $prevTrans['offsetfrom'] = ( 0 < $tix ) ? $transitions[$tix-1]['offset'] : 0;
7355
        continue;
7356
      }
7357
      if( $transDateYmd > $dateToYmd )
7358
        break;                                                   // loop always (?) breaks here
7359
      if( !empty( $prevOffsetfrom ) || ( 0 == $prevOffsetfrom )) {
7360
        $trans['offsetfrom'] = $prevOffsetfrom;                  // i.e. set previous offsetto as offsetFrom
7361
        $date->modify( $trans['offsetfrom'].'seconds' );         // convert utc date to local date
7362
        $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart and (opt) rdate setting
7363
        $d = explode( '-', $d );
7364
        $trans['time']   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
7365
      }
7366
      $prevOffsetfrom    = $trans['offset'];
7367
      if( TRUE !== $trans['isdst'] ) {                           // standard timezone
7368
        if( !empty( $stdIx ) && isset( $transTemp[$stdIx]['offsetfrom'] )  && // check for any repeating rdate's (in order)
7369
           ( $transTemp[$stdIx]['abbr']       ==   $trans['abbr'] )        &&
7370
           ( $transTemp[$stdIx]['offsetfrom'] ==   $trans['offsetfrom'] )  &&
7371
           ( $transTemp[$stdIx]['offset']     ==   $trans['offset'] )) {
7372
          $transTemp[$stdIx]['rdate'][]        =   $trans['time'];
7373
          continue;
7374
        }
7375
        $stdIx           = $tix;
7376
      } // end standard timezone
7377
      else {                                                     // daylight timezone
7378
        if( !empty( $dlghtIx ) && isset( $transTemp[$dlghtIx]['offsetfrom'] ) && // check for any repeating rdate's (in order)
7379
           ( $transTemp[$dlghtIx]['abbr']       ==   $trans['abbr'] )         &&
7380
           ( $transTemp[$dlghtIx]['offsetfrom'] ==   $trans['offsetfrom'] )   &&
7381
           ( $transTemp[$dlghtIx]['offset']     ==   $trans['offset'] )) {
7382
          $transTemp[$dlghtIx]['rdate'][]        =   $trans['time'];
7383
          continue;
7384
        }
7385
        $dlghtIx         = $tix;
7386
      } // end daylight timezone
7387
      $transTemp[$tix]   = $trans;
7388
    } // end foreach( $transitions as $tix => $trans )
7389
    $tz  = & $calendar->newComponent( 'vtimezone' );
7390
    $tz->setproperty( 'tzid', $timezone );
7391
    if( !empty( $xProp )) {
7392
      foreach( $xProp as $xPropName => $xPropValue )
7393
        if( 'x-' == strtolower( substr( $xPropName, 0, 2 )))
7394
          $tz->setproperty( $xPropName, $xPropValue );
7395
    }
7396
    if( empty( $transTemp )) {      // if no match found
7397
      if( $prevTrans ) {            // then we use the last transition (before startdate) for the tz info
7398
        $date = new DateTime( "@{$prevTrans['ts']}" );           // set transition date (UTC)
7399
        $date->modify( $prevTrans['offsetfrom'].'seconds' );     // convert utc date to local date
7400
        $d = $date->format( 'Y-n-j-G-i-s' );                     // set date to array to ease up dtstart setting
7401
        $d = explode( '-', $d );
7402
        $prevTrans['time'] = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
7403
        $transTemp[0] = $prevTrans;
7404
      }
7405
      else {                        // or we use the timezone identifier to BUILD the standard tz info (?)
7406
        $date = new DateTime( 'now', new DateTimeZone( $timezone ));
7407
        $transTemp[0] = array( 'time'       => $date->format( 'Y-m-d\TH:i:s O' )
7408
                             , 'offset'     => $date->format( 'Z' )
7409
                             , 'offsetfrom' => $date->format( 'Z' )
7410
                             , 'isdst'      => FALSE );
7411
      }
7412
    }
7413
    unset( $transitions, $date, $prevTrans );
7414
    foreach( $transTemp as $tix => $trans ) {
7415
      $type  = ( TRUE !== $trans['isdst'] ) ? 'standard' : 'daylight';
7416
      $scomp = & $tz->newComponent( $type );
7417
      $scomp->setProperty( 'dtstart',         $trans['time'] );
7418
//      $scomp->setProperty( 'x-utc-timestamp', $tix.' : '.$trans['ts'] );   // test ###
7419
      if( !empty( $trans['abbr'] ))
7420
        $scomp->setProperty( 'tzname',        $trans['abbr'] );
7421
      if( isset( $trans['offsetfrom'] ))
7422
        $scomp->setProperty( 'tzoffsetfrom',  iCalUtilityFunctions::offsetSec2His( $trans['offsetfrom'] ));
7423
      $scomp->setProperty( 'tzoffsetto',      iCalUtilityFunctions::offsetSec2His( $trans['offset'] ));
7424
      if( isset( $trans['rdate'] ))
7425
        $scomp->setProperty( 'RDATE',         $trans['rdate'] );
7426
    }
7427
    return TRUE;
7428
  }
7429
/**
7430
 * creates formatted output for calendar component property data value type date/date-time
7431
 *
7432
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7433
 * @since 2.14.1 - 2012-09-17
7434
 * @param array   $datetime
7435
 * @param int     $parno, optional, default 6
7436
 * @return string
7437
 */
7438
  public static function _format_date_time( $datetime, $parno=6 ) {
7439
    return iCalUtilityFunctions::_date2strdate( $datetime, $parno );
7440
  }
7441
  public static function _date2strdate( $datetime, $parno=6 ) {
7442
    if( !isset( $datetime['year'] )  &&
7443
        !isset( $datetime['month'] ) &&
7444
        !isset( $datetime['day'] )   &&
7445
        !isset( $datetime['hour'] )  &&
7446
        !isset( $datetime['min'] )   &&
7447
        !isset( $datetime['sec'] ))
7448
      return;
7449
    $output     = null;
7450
    foreach( $datetime as $dkey => & $dvalue )
7451
      if( 'tz' != $dkey ) $dvalue = (integer) $dvalue;
7452
    $output = sprintf( '%04d%02d%02d', $datetime['year'], $datetime['month'], $datetime['day'] );
7453
    if( 3 == $parno )
7454
      return $output;
7455
    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
7456
    if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
7457
    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
7458
    $output    .= sprintf( 'T%02d%02d%02d', $datetime['hour'], $datetime['min'], $datetime['sec'] );
7459
    if( isset( $datetime['tz'] ) && ( '' < trim( $datetime['tz'] ))) {
7460
      $datetime['tz'] = trim( $datetime['tz'] );
7461
      if( 'Z'  == $datetime['tz'] )
7462
        $parno  = 7;
7463
      elseif( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
7464
        $parno  = 7;
7465
        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] );
7466
        try {
7467
          $d    = new DateTime( $output, new DateTimeZone( 'UTC' ));
7468
          if( 0 != $offset ) // adjust för offset
7469
            $d->modify( "$offset seconds" );
7470
          $output = $d->format( 'Ymd\THis' );
7471
        }
7472
        catch( Exception $e ) {
7473
          $output = date( 'Ymd\THis', mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] - $offset), $datetime['month'], $datetime['day'], $datetime['year'] ));
7474
        }
7475
      }
7476
      if( 7 == $parno )
7477
        $output .= 'Z';
7478
    }
7479
    return $output;
7480
  }
7481
/**
7482
 * convert a date/datetime (array) to timestamp
7483
 *
7484
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7485
 * @since 2.14.1 - 2012-09-29
7486
 * @param array  $datetime  datetime(/date)
7487
 * @param string $wtz       timezone
7488
 * @return int
7489
 */
7490
  public static function _date2timestamp( $datetime, $wtz=null ) {
7491
    if( !isset( $datetime['hour'] )) $datetime['hour'] = 0;
7492
    if( !isset( $datetime['min'] ))  $datetime['min']  = 0;
7493
    if( !isset( $datetime['sec'] ))  $datetime['sec']  = 0;
7494
    if( empty( $wtz ) && ( !isset( $datetime['tz'] ) || empty(  $datetime['tz'] )))
7495
      return mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
7496
    $output = $offset = 0;
7497
    if( empty( $wtz )) {
7498
      if( iCalUtilityFunctions::_isOffset( $datetime['tz'] )) {
7499
        $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) * -1;
7500
        $wtz    = 'UTC';
7501
      }
7502
      else
7503
        $wtz    = $datetime['tz'];
7504
    }
7505
    if(( 'Z' == $wtz ) || ( 'GMT' == strtoupper( $wtz )))
7506
      $wtz      = 'UTC';
7507
    try {
7508
      $strdate  = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['min'], $datetime['sec'] );
7509
      $d        = new DateTime( $strdate, new DateTimeZone( $wtz ));
7510
      if( 0    != $offset )  // adjust for offset
7511
        $d->modify( $offset.' seconds' );
7512
      $output   = $d->format( 'U' );
7513
      unset( $d );
7514
    }
7515
    catch( Exception $e ) {
7516
      $output = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year'] );
7517
    }
7518
    return $output;
7519
  }
7520
/**
7521
 * ensures internal duration format for input in array format
7522
 *
7523
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7524
 * @since 2.16.23 - 2013-06-23
7525
 * @param array $duration
7526
 * @return array
7527
 */
7528
  public static function _duration_array( $duration ) {
7529
    return iCalUtilityFunctions::_duration2arr( $duration );
7530
  }
7531
  public static function _duration2arr( $duration ) {
7532
    $seconds        = 0;
7533
    foreach( $duration as $durKey => $durValue ) {
7534
      if( empty( $durValue )) continue;
7535
      switch ( $durKey ) {
7536
        case '0': case 'week':
7537
          $seconds += (((int) $durValue ) * 60 * 60 * 24 * 7 );
7538
          break;
7539
        case '1': case 'day':
7540
          $seconds += (((int) $durValue ) * 60 * 60 * 24 );
7541
          break;
7542
        case '2': case 'hour':
7543
          $seconds += (((int) $durValue ) * 60 * 60 );
7544
          break;
7545
        case '3': case 'min':
7546
          $seconds += (((int) $durValue ) * 60 );
7547
          break;
7548
        case '4': case 'sec':
7549
          $seconds +=   (int) $durValue;
7550
          break;
7551
      }
7552
    }
7553
    $output         = array();
7554
    $output['week'] = (int) floor( $seconds / ( 60 * 60 * 24 * 7 ));
7555
    $seconds        =            ( $seconds % ( 60 * 60 * 24 * 7 ));
7556
    $output['day']  = (int) floor( $seconds / ( 60 * 60 * 24 ));
7557
    $seconds        =            ( $seconds % ( 60 * 60 * 24 ));
7558
    $output['hour'] = (int) floor( $seconds / ( 60 * 60 ));
7559
    $seconds        =            ( $seconds % ( 60 * 60 ));
7560
    $output['min']  = (int) floor( $seconds /   60 );
7561
    $output['sec']  =            ( $seconds %   60 );
7562
    if( !empty( $output['week'] ))
7563
      unset( $output['day'], $output['hour'], $output['min'], $output['sec'] );
7564
    else {
7565
      unset( $output['week'] );
7566
      if( empty( $output['day'] ))
7567
        unset( $output['day'] );
7568
      if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] ))
7569
        unset( $output['hour'], $output['min'], $output['sec'] );
7570
    }
7571
    return $output;
7572
  }
7573
/**
7574
 * convert startdate+duration to a array format datetime
7575
 *
7576
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7577
 * @since 2.15.12 - 2012-10-31
7578
 * @param array   $startdate
7579
 * @param array   $duration
7580
 * @return array, date format
7581
 */
7582
  public static function _duration2date( $startdate, $duration ) {
7583
    $dateOnly          = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE;
7584
    $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0;
7585
    $startdate['min']  = ( isset( $startdate['min'] ))  ? $startdate['min']  : 0;
7586
    $startdate['sec']  = ( isset( $startdate['sec'] ))  ? $startdate['sec']  : 0;
7587
    $dtend = 0;
7588
    if(    isset( $duration['week'] )) $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 );
7589
    if(    isset( $duration['day'] ))  $dtend += ( $duration['day'] * 24 * 60 * 60 );
7590
    if(    isset( $duration['hour'] )) $dtend += ( $duration['hour'] * 60 *60 );
7591
    if(    isset( $duration['min'] ))  $dtend += ( $duration['min'] * 60 );
7592
    if(    isset( $duration['sec'] ))  $dtend +=   $duration['sec'];
7593
    $date     = date( 'Y-m-d-H-i-s', mktime((int) $startdate['hour'], (int) $startdate['min'], (int) ( $startdate['sec'] + $dtend ), (int) $startdate['month'], (int) $startdate['day'], (int) $startdate['year'] ));
7594
    $d        = explode( '-', $date );
7595
    $dtend2   = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
7596
    if( isset( $startdate['tz'] ))
7597
      $dtend2['tz']   = $startdate['tz'];
7598
    if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] )))
7599
      unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] );
7600
    return $dtend2;
7601
  }
7602
/**
7603
 * ensures internal duration format for an input string (iCal) formatted duration
7604
 *
7605
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7606
 * @since 2.14.1 - 2012-09-25
7607
 * @param string $duration
7608
 * @return array
7609
 */
7610
  public static function _duration_string( $duration ) {
7611
    return iCalUtilityFunctions::_durationStr2arr( $duration );
7612
  }
7613
  public static function _durationStr2arr( $duration ) {
7614
    $duration = (string) trim( $duration );
7615
    while( 'P' != strtoupper( substr( $duration, 0, 1 ))) {
7616
      if( 0 < strlen( $duration ))
7617
        $duration = substr( $duration, 1 );
7618
      else
7619
        return false; // no leading P !?!?
7620
    }
7621
    $duration = substr( $duration, 1 ); // skip P
7622
    $duration = str_replace ( 't', 'T', $duration );
7623
    $duration = str_replace ( 'T', '', $duration );
7624
    $output = array();
7625
    $val    = null;
7626
    for( $ix=0; $ix < strlen( $duration ); $ix++ ) {
7627
      switch( strtoupper( substr( $duration, $ix, 1 ))) {
7628
       case 'W':
7629
         $output['week'] = $val;
7630
         $val            = null;
7631
         break;
7632
       case 'D':
7633
         $output['day']  = $val;
7634
         $val            = null;
7635
         break;
7636
       case 'H':
7637
         $output['hour'] = $val;
7638
         $val            = null;
7639
         break;
7640
       case 'M':
7641
         $output['min']  = $val;
7642
         $val            = null;
7643
         break;
7644
       case 'S':
7645
         $output['sec']  = $val;
7646
         $val            = null;
7647
         break;
7648
       default:
7649
         if( !ctype_digit( substr( $duration, $ix, 1 )))
7650
           return false; // unknown duration control character  !?!?
7651
         else
7652
           $val .= substr( $duration, $ix, 1 );
7653
      }
7654
    }
7655
    return iCalUtilityFunctions::_duration2arr( $output );
7656
  }
7657
/**
7658
 * creates formatted output for calendar component property data value type duration
7659
 *
7660
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7661
 * @since 2.15.8 - 2012-10-30
7662
 * @param array $duration, array( week, day, hour, min, sec )
7663
 * @return string
7664
 */
7665
  public static function _format_duration( $duration ) {
7666
    return iCalUtilityFunctions::_duration2str( $duration );
7667
  }
7668
  public static function _duration2str( $duration ) {
7669
    if( isset( $duration['week'] ) ||
7670
        isset( $duration['day'] )  ||
7671
        isset( $duration['hour'] ) ||
7672
        isset( $duration['min'] )  ||
7673
        isset( $duration['sec'] ))
7674
       $ok = TRUE;
7675
    else
7676
      return;
7677
    if( isset( $duration['week'] ) && ( 0 < $duration['week'] ))
7678
      return 'P'.$duration['week'].'W';
7679
    $output = 'P';
7680
    if( isset($duration['day'] ) && ( 0 < $duration['day'] ))
7681
      $output .= $duration['day'].'D';
7682
    if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ||
7683
       ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ||
7684
       ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))) {
7685
      $output .= 'T';
7686
      $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H';
7687
      $output .= ( isset( $duration['min'])  && ( 0 < $duration['min'] ))  ? $duration['min']. 'M' : '0M';
7688
      $output .= ( isset( $duration['sec'])  && ( 0 < $duration['sec'] ))  ? $duration['sec']. 'S' : '0S';
7689
    }
7690
    if( 'P' == $output )
7691
      $output = 'PT0H0M0S';
7692
    return $output;
7693
  }
7694
/**
7695
 * removes expkey+expvalue from array and returns hitval (if found) else returns elseval
7696
 *
7697
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7698
 * @since 2.4.16 - 2008-11-08
7699
 * @param array $array
7700
 * @param string $expkey, expected key
7701
 * @param string $expval, expected value
7702
 * @param int $hitVal optional, return value if found
7703
 * @param int $elseVal optional, return value if not found
7704
 * @param int $preSet optional, return value if already preset
7705
 * @return int
7706
 */
7707
  public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) {
7708
    if( $preSet )
7709
      return $preSet;
7710
    if( !is_array( $array ) || ( 0 == count( $array )))
7711
      return $elseVal;
7712
    foreach( $array as $key => $value ) {
7713
      if( strtoupper( $expkey ) == strtoupper( $key )) {
7714
        if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) {
7715
          unset( $array[$key] );
7716
          return $hitVal;
7717
        }
7718
      }
7719
    }
7720
    return $elseVal;
7721
  }
7722
/**
7723
 * checks if input contains a (array formatted) date/time
7724
 *
7725
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7726
 * @since 2.16.24 - 2013-07-02
7727
 * @param array $input
7728
 * @return bool
7729
 */
7730
  public static function _isArrayDate( $input ) {
7731
    if( !is_array( $input ) || isset( $input['week'] ) || isset( $input['timestamp'] ) || ( 3 > count( $input )))
7732
      return FALSE;
7733
    if( 7 == count( $input ))
7734
      return TRUE;
7735
    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
7736
      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
7737
    if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] ))
7738
      return FALSE;
7739
    if(( 0 == $input[0] ) || ( 0 == $input[1] ) || ( 0 == $input[2] ))
7740
      return FALSE;
7741
    if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] ))
7742
      return FALSE;
7743
    if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) &&
7744
         checkdate((int) $input[1], (int) $input[2], (int) $input[0] ))
7745
      return TRUE;
7746
    $input = iCalUtilityFunctions::_strdate2date( $input[1].'/'.$input[2].'/'.$input[0], 3 ); //  m - d - Y
7747
    if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] ))
7748
      return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] );
7749
    return FALSE;
7750
  }
7751
/**
7752
 * checks if input array contains a timestamp date
7753
 *
7754
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7755
 * @since 2.4.16 - 2008-10-18
7756
 * @param array $input
7757
 * @return bool
7758
 */
7759
  public static function _isArrayTimestampDate( $input ) {
7760
    return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ;
7761
  }
7762
/**
7763
 * controls if input string contains (trailing) UTC/iCal offset
7764
 *
7765
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7766
 * @since 2.14.1 - 2012-09-21
7767
 * @param string $input
7768
 * @return bool
7769
 */
7770
  public static function _isOffset( $input ) {
7771
    $input         = trim( (string) $input );
7772
    if( 'Z' == substr( $input, -1 ))
7773
      return TRUE;
7774
    elseif((   5 <= strlen( $input )) &&
7775
       ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) &&
7776
       (   '0000' <= substr( $input, -4 )) && (   '9999' >= substr( $input, -4 )))
7777
      return TRUE;
7778
    elseif((    7 <= strlen( $input )) &&
7779
       ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) &&
7780
       ( '000000' <= substr( $input, -6 )) && ( '999999' >= substr( $input, -6 )))
7781
      return TRUE;
7782
    return FALSE;
7783
  }
7784
/**
7785
 * (very simple) conversion of a MS timezone to a PHP5 valid (Date-)timezone
7786
 * matching (MS) UCT offset and time zone descriptors
7787
 *
7788
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7789
 * @since 2.14.1 - 2012-09-16
7790
 * @param string $timezone, input/output variable reference
7791
 * @return bool
7792
 */
7793
  public static function ms2phpTZ( & $timezone ) {
7794
    if( empty( $timezone ))
7795
      return FALSE;
7796
    $search = str_replace( '"', '', $timezone );
7797
    $search = str_replace( array('GMT', 'gmt', 'utc' ), 'UTC', $search );
7798
    if( '(UTC' != substr( $search, 0, 4 ))
7799
      return FALSE;
7800
    if( FALSE === ( $pos = strpos( $search, ')' )))
7801
      return FALSE;
7802
    $pos    = strpos( $search, ')' );
7803
    $searchOffset = substr( $search, 4, ( $pos - 4 ));
7804
    $searchOffset = iCalUtilityFunctions::_tz2offset( str_replace( ':', '', $searchOffset ));
7805
    while( ' ' ==substr( $search, ( $pos + 1 )))
7806
      $pos += 1;
7807
    $searchText   = trim( str_replace( array( '(', ')', '&', ',', '  ' ), ' ', substr( $search, ( $pos + 1 )) ));
7808
    $searchWords  = explode( ' ', $searchText );
7809
    $timezone_abbreviations = DateTimeZone::listAbbreviations();
7810
    $hits = array();
7811
    foreach( $timezone_abbreviations as $name => $transitions ) {
7812
      foreach( $transitions as $cnt => $transition ) {
7813
        if( empty( $transition['offset'] )      ||
7814
            empty( $transition['timezone_id'] ) ||
7815
          ( $transition['offset'] != $searchOffset ))
7816
        continue;
7817
        $cWords = explode( '/', $transition['timezone_id'] );
7818
        $cPrio   = $hitCnt = $rank = 0;
7819
        foreach( $cWords as $cWord ) {
7820
          if( empty( $cWord ))
7821
            continue;
7822
          $cPrio += 1;
7823
          $sPrio  = 0;
7824
          foreach( $searchWords as $sWord ) {
7825
            if( empty( $sWord ) || ( 'time' == strtolower( $sWord )))
7826
              continue;
7827
            $sPrio += 1;
7828
            if( strtolower( $cWord ) == strtolower( $sWord )) {
7829
              $hitCnt += 1;
7830
              $rank   += ( $cPrio + $sPrio );
7831
            }
7832
            else
7833
              $rank += 10;
7834
          }
7835
        }
7836
        if( 0 < $hitCnt ) {
7837
          $hits[$rank][] = $transition['timezone_id'];
7838
        }
7839
      }
7840
    }
7841
    unset( $timezone_abbreviations );
7842
    if( empty( $hits ))
7843
      return FALSE;
7844
    ksort( $hits );
7845
    foreach( $hits as $rank => $tzs ) {
7846
      if( !empty( $tzs )) {
7847
        $timezone = reset( $tzs );
7848
        return TRUE;
7849
      }
7850
    }
7851
    return FALSE;
7852
  }
7853
/**
7854
 * transforms offset in seconds to [-/+]hhmm[ss]
7855
 *
7856
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7857
 * @since 2011-05-02
7858
 * @param string $seconds
7859
 * @return string
7860
 */
7861
  public static function offsetSec2His( $seconds ) {
7862
    if( '-' == substr( $seconds, 0, 1 )) {
7863
      $prefix  = '-';
7864
      $seconds = substr( $seconds, 1 );
7865
    }
7866
    elseif( '+' == substr( $seconds, 0, 1 )) {
7867
      $prefix  = '+';
7868
      $seconds = substr( $seconds, 1 );
7869
    }
7870
    else
7871
      $prefix  = '+';
7872
    $output  = '';
7873
    $hour    = (int) floor( $seconds / 3600 );
7874
    if( 10 > $hour )
7875
      $hour  = '0'.$hour;
7876
    $seconds = $seconds % 3600;
7877
    $min     = (int) floor( $seconds / 60 );
7878
    if( 10 > $min )
7879
      $min   = '0'.$min;
7880
    $output  = $hour.$min;
7881
    $seconds = $seconds % 60;
7882
    if( 0 < $seconds) {
7883
      if( 9 < $seconds)
7884
        $output .= $seconds;
7885
      else
7886
        $output .= '0'.$seconds;
7887
    }
7888
    return $prefix.$output;
7889
  }
7890
/**
7891
 * updates an array with dates based on a recur pattern
7892
 *
7893
 * if missing, UNTIL is set 1 year from startdate (emergency break)
7894
 *
7895
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
7896
 * @since 2.10.19 - 2011-10-31
7897
 * @param array $result, array to update, array([timestamp] => timestamp)
7898
 * @param array $recur, pattern for recurrency (only value part, params ignored)
7899
 * @param array $wdate, component start date
7900
 * @param array $startdate, start date
7901
 * @param array $enddate, optional
7902
 * @return void
7903
 * @todo BYHOUR, BYMINUTE, BYSECOND, WEEKLY at year end/start
7904
 */
7905
  public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) {
7906
    foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v;
7907
    $wdateStart  = $wdate;
7908
    $wdatets     = iCalUtilityFunctions::_date2timestamp( $wdate );
7909
    $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate );
7910
    if( !$enddate ) {
7911
      $enddate = $startdate;
7912
      $enddate['year'] += 1;
7913
    }
7914
// echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."<br>\n";print_r($recur);echo "<br>\n";//test###
7915
    $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break
7916
    if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] ))
7917
      $recur['UNTIL'] = $enddate; // create break
7918
    if( isset( $recur['UNTIL'] )) {
7919
      $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] );
7920
      if( $endDatets > $tdatets ) {
7921
        $endDatets = $tdatets; // emergency break
7922
        $enddate   = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
7923
      }
7924
      else
7925
        $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 );
7926
    }
7927
    if( $wdatets > $endDatets ) {
7928
// echo "recur out of date ".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
7929
      return array(); // nothing to do.. .
7930
    }
7931
    if( !isset( $recur['FREQ'] )) // "MUST be specified.. ."
7932
      $recur['FREQ'] = 'DAILY'; // ??
7933
    $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ??
7934
    $weekStart = (int) date( 'W', ( $wdatets + $wkst ));
7935
    if( !isset( $recur['INTERVAL'] ))
7936
      $recur['INTERVAL'] = 1;
7937
    $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence
7938
            /* find out how to step up dates and set index for interval count */
7939
    $step = array();
7940
    if( 'YEARLY' == $recur['FREQ'] )
7941
      $step['year']  = 1;
7942
    elseif( 'MONTHLY' == $recur['FREQ'] )
7943
      $step['month'] = 1;
7944
    elseif( 'WEEKLY' == $recur['FREQ'] )
7945
      $step['day']   = 7;
7946
    else
7947
      $step['day']   = 1;
7948
    if( isset( $step['year'] ) && isset( $recur['BYMONTH'] ))
7949
      $step = array( 'month' => 1 );
7950
    if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ??
7951
      $step = array( 'day' => 7 );
7952
    if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] ))
7953
      $step = array( 'day' => 1 );
7954
    $intervalarr = array();
7955
    if( 1 < $recur['INTERVAL'] ) {
7956
      $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
7957
      $intervalarr = array( $intervalix => 0 );
7958
    }
7959
    if( isset( $recur['BYSETPOS'] )) { // save start date + weekno
7960
      $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array();
7961
// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold<br>\n"; // test ###
7962
      if( is_array( $recur['BYSETPOS'] )) {
7963
        foreach( $recur['BYSETPOS'] as $bix => $bval )
7964
          $recur['BYSETPOS'][$bix] = (int) $bval;
7965
      }
7966
      else
7967
        $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] );
7968
      if( 'YEARLY' == $recur['FREQ'] ) {
7969
        $wdate['month'] = $wdate['day'] = 1; // start from beginning of year
7970
        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
7971
        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year
7972
      }
7973
      elseif( 'MONTHLY' == $recur['FREQ'] ) {
7974
        $wdate['day']   = 1; // start from beginning of month
7975
        $wdatets        = iCalUtilityFunctions::_date2timestamp( $wdate );
7976
        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month
7977
      }
7978
      else
7979
        iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period
7980
// echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."<br>\n";//test###
7981
      $bysetposWold = (int) date( 'W', ( $wdatets + $wkst ));
7982
      $bysetposYold = $wdate['year'];
7983
      $bysetposMold = $wdate['month'];
7984
      $bysetposDold = $wdate['day'];
7985
    }
7986
    else
7987
      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
7988
    $year_old     = null;
7989
    $daynames     = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' );
7990
             /* MAIN LOOP */
7991
// echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."<br>\n";//test
7992
    while( TRUE ) {
7993
      if( isset( $endDatets ) && ( $wdatets > $endDatets ))
7994
        break;
7995
      if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
7996
        break;
7997
      if( $year_old != $wdate['year'] ) {
7998
        $year_old   = $wdate['year'];
7999
        $daycnts    = array();
8000
        $yeardays   = $weekno = 0;
8001
        $yeardaycnt = array();
8002
        foreach( $daynames as $dn )
8003
          $yeardaycnt[$dn] = 0;
8004
        for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters
8005
          $daycnts[$m] = array();
8006
          $weekdaycnt = array();
8007
          foreach( $daynames as $dn )
8008
            $weekdaycnt[$dn] = 0;
8009
          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
8010
          for( $d   = 1; $d <= $mcnt; $d++ ) {
8011
            $daycnts[$m][$d] = array();
8012
            if( isset( $recur['BYYEARDAY'] )) {
8013
              $yeardays++;
8014
              $daycnts[$m][$d]['yearcnt_up'] = $yeardays;
8015
            }
8016
            if( isset( $recur['BYDAY'] )) {
8017
              $day    = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] ));
8018
              $day    = $daynames[$day];
8019
              $daycnts[$m][$d]['DAY'] = $day;
8020
              $weekdaycnt[$day]++;
8021
              $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day];
8022
              $yeardaycnt[$day]++;
8023
              $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day];
8024
            }
8025
            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
8026
              $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year']));
8027
          }
8028
        }
8029
        $daycnt = 0;
8030
        $yeardaycnt = array();
8031
        if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) {
8032
          $weekno = null;
8033
          for( $d=31; $d > 25; $d-- ) { // get last weekno for year
8034
            if( !$weekno )
8035
              $weekno = $daycnts[12][$d]['weekno_up'];
8036
            elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) {
8037
              $weekno = $daycnts[12][$d]['weekno_up'];
8038
              break;
8039
            }
8040
          }
8041
        }
8042
        for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters
8043
          $weekdaycnt = array();
8044
          foreach( $daynames as $dn )
8045
            $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0;
8046
          $monthcnt = 0;
8047
          $mcnt     = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] ));
8048
          for( $d   = $mcnt; $d > 0; $d-- ) {
8049
            if( isset( $recur['BYYEARDAY'] )) {
8050
              $daycnt -= 1;
8051
              $daycnts[$m][$d]['yearcnt_down'] = $daycnt;
8052
            }
8053
            if( isset( $recur['BYMONTHDAY'] )) {
8054
              $monthcnt -= 1;
8055
              $daycnts[$m][$d]['monthcnt_down'] = $monthcnt;
8056
            }
8057
            if( isset( $recur['BYDAY'] )) {
8058
              $day  = $daycnts[$m][$d]['DAY'];
8059
              $weekdaycnt[$day] -= 1;
8060
              $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day];
8061
              $yeardaycnt[$day] -= 1;
8062
              $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day];
8063
            }
8064
            if(  isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' ))
8065
              $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1);
8066
          }
8067
        }
8068
      }
8069
            /* check interval */
8070
      if( 1 < $recur['INTERVAL'] ) {
8071
            /* create interval index */
8072
        $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst );
8073
            /* check interval */
8074
        $currentKey = array_keys( $intervalarr );
8075
        $currentKey = end( $currentKey ); // get last index
8076
        if( $currentKey != $intervalix )
8077
          $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 ));
8078
        if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) &&
8079
           ( 0 != $intervalarr[$intervalix] )) {
8080
            /* step up date */
8081
// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br>\n";//test
8082
          iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8083
          continue;
8084
        }
8085
        else // continue within the selected interval
8086
          $intervalarr[$intervalix] = 0;
8087
// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."<br>\n";//test
8088
      }
8089
      $updateOK = TRUE;
8090
      if( $updateOK && isset( $recur['BYMONTH'] ))
8091
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH']
8092
                                           , $wdate['month']
8093
                                           ,($wdate['month'] - 13));
8094
      if( $updateOK && isset( $recur['BYWEEKNO'] ))
8095
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO']
8096
                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_up']
8097
                                           , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] );
8098
      if( $updateOK && isset( $recur['BYYEARDAY'] ))
8099
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY']
8100
                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up']
8101
                                           , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] );
8102
      if( $updateOK && isset( $recur['BYMONTHDAY'] ))
8103
        $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY']
8104
                                           , $wdate['day']
8105
                                           , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] );
8106
// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br>\n";//test###
8107
      if( $updateOK && isset( $recur['BYDAY'] )) {
8108
        $updateOK = FALSE;
8109
        $m = $wdate['month'];
8110
        $d = $wdate['day'];
8111
        if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no
8112
          $daynoexists = $daynosw = $daynamesw =  FALSE;
8113
          if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] )
8114
            $daynamesw = TRUE;
8115
          if( isset( $recur['BYDAY'][0] )) {
8116
            $daynoexists = TRUE;
8117
            if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] ))
8118
              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
8119
                                                , $daycnts[$m][$d]['monthdayno_up']
8120
                                                , $daycnts[$m][$d]['monthdayno_down'] );
8121
            elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
8122
              $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0]
8123
                                                , $daycnts[$m][$d]['yeardayno_up']
8124
                                                , $daycnts[$m][$d]['yeardayno_down'] );
8125
          }
8126
          if((  $daynoexists &&  $daynosw && $daynamesw ) ||
8127
             ( !$daynoexists && !$daynosw && $daynamesw )) {
8128
            $updateOK = TRUE;
8129
// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br>\n"; // test ###
8130
          }
8131
// echo "m=$m d=$d day=".$daycnts[$m][$d]['DAY']." yeardayno_up=".$daycnts[$m][$d]['yeardayno_up']." daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw updateOK:$updateOK<br>\n"; // test ###
8132
        }
8133
        else {
8134
          foreach( $recur['BYDAY'] as $bydayvalue ) {
8135
            $daynoexists = $daynosw = $daynamesw = FALSE;
8136
            if( isset( $bydayvalue['DAY'] ) &&
8137
                     ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] ))
8138
              $daynamesw = TRUE;
8139
            if( isset( $bydayvalue[0] )) {
8140
              $daynoexists = TRUE;
8141
              if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) ||
8142
                   isset( $recur['BYMONTH'] ))
8143
                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
8144
                                                  , $daycnts[$m][$d]['monthdayno_up']
8145
                                                  , $daycnts[$m][$d]['monthdayno_down'] );
8146
              elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' ))
8147
                $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0']
8148
                                                  , $daycnts[$m][$d]['yeardayno_up']
8149
                                                  , $daycnts[$m][$d]['yeardayno_down'] );
8150
            }
8151
// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw<br>\n"; // test ###
8152
            if((  $daynoexists &&  $daynosw && $daynamesw ) ||
8153
               ( !$daynoexists && !$daynosw && $daynamesw )) {
8154
              $updateOK = TRUE;
8155
              break;
8156
            }
8157
          }
8158
        }
8159
      }
8160
// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "<br>\n"; // test ###
8161
            /* check BYSETPOS */
8162
      if( $updateOK ) {
8163
        if( isset( $recur['BYSETPOS'] ) &&
8164
          ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) {
8165
          if( isset( $recur['WEEKLY'] )) {
8166
            if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] )
8167
              $bysetposw1[] = $wdatets;
8168
            else
8169
              $bysetposw2[] = $wdatets;
8170
          }
8171
          else {
8172
            if(( isset( $recur['FREQ'] ) && ( 'YEARLY'      == $recur['FREQ'] )  &&
8173
                                            ( $bysetposYold == $wdate['year'] ))   ||
8174
               ( isset( $recur['FREQ'] ) && ( 'MONTHLY'     == $recur['FREQ'] )  &&
8175
                                           (( $bysetposYold == $wdate['year'] )  &&
8176
                                            ( $bysetposMold == $wdate['month'] ))) ||
8177
               ( isset( $recur['FREQ'] ) && ( 'DAILY'       == $recur['FREQ'] )  &&
8178
                                           (( $bysetposYold == $wdate['year'] )  &&
8179
                                            ( $bysetposMold == $wdate['month'])  &&
8180
                                            ( $bysetposDold == $wdate['day'] )))) {
8181
// echo "bysetposymd1[]=".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
8182
              $bysetposymd1[] = $wdatets;
8183
            }
8184
            else {
8185
// echo "bysetposymd2[]=".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
8186
              $bysetposymd2[] = $wdatets;
8187
            }
8188
          }
8189
        }
8190
        else {
8191
            /* update result array if BYSETPOS is set */
8192
          $countcnt++;
8193
          if( $startdatets <= $wdatets ) { // only output within period
8194
            $result[$wdatets] = TRUE;
8195
// echo "recur ".date('Y-m-d H:i:s',$wdatets)."<br>\n";//test
8196
          }
8197
// echo "recur undate ".date('Y-m-d H:i:s',$wdatets)." okdatstart ".date('Y-m-d H:i:s',$startdatets)."<br>\n";//test
8198
          $updateOK = FALSE;
8199
        }
8200
      }
8201
            /* step up date */
8202
      iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step);
8203
            /* check if BYSETPOS is set for updating result array */
8204
      if( $updateOK && isset( $recur['BYSETPOS'] )) {
8205
        $bysetpos       = FALSE;
8206
        if( isset( $recur['FREQ'] ) && ( 'YEARLY'  == $recur['FREQ'] ) &&
8207
          ( $bysetposYold != $wdate['year'] )) {
8208
          $bysetpos     = TRUE;
8209
          $bysetposYold = $wdate['year'];
8210
        }
8211
        elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] &&
8212
         (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) {
8213
          $bysetpos     = TRUE;
8214
          $bysetposYold = $wdate['year'];
8215
          $bysetposMold = $wdate['month'];
8216
        }
8217
        elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY'  == $recur['FREQ'] )) {
8218
          $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year']));
8219
          if( $bysetposWold != $weekno ) {
8220
            $bysetposWold = $weekno;
8221
            $bysetpos     = TRUE;
8222
          }
8223
        }
8224
        elseif( isset( $recur['FREQ'] ) && ( 'DAILY'   == $recur['FREQ'] ) &&
8225
         (( $bysetposYold != $wdate['year'] )  ||
8226
          ( $bysetposMold != $wdate['month'] ) ||
8227
          ( $bysetposDold != $wdate['day'] ))) {
8228
          $bysetpos     = TRUE;
8229
          $bysetposYold = $wdate['year'];
8230
          $bysetposMold = $wdate['month'];
8231
          $bysetposDold = $wdate['day'];
8232
        }
8233
        if( $bysetpos ) {
8234
          if( isset( $recur['BYWEEKNO'] )) {
8235
            $bysetposarr1 = & $bysetposw1;
8236
            $bysetposarr2 = & $bysetposw2;
8237
          }
8238
          else {
8239
            $bysetposarr1 = & $bysetposymd1;
8240
            $bysetposarr2 = & $bysetposymd2;
8241
          }
8242
// echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ###
8243
          foreach( $recur['BYSETPOS'] as $ix ) {
8244
            if( 0 > $ix ) // both positive and negative BYSETPOS allowed
8245
              $ix = ( count( $bysetposarr1 ) + $ix + 1);
8246
            $ix--;
8247
            if( isset( $bysetposarr1[$ix] )) {
8248
              if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period
8249
//                $testdate   = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 );                // test ###
8250
//                $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ###
8251
// echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)";   // test ###
8252
                $result[$bysetposarr1[$ix]] = TRUE;
8253
// echo " recur ".date('Y-m-d H:i:s',$bysetposarr1[$ix]); // test ###
8254
              }
8255
              $countcnt++;
8256
            }
8257
            if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] ))
8258
              break;
8259
          }
8260
// echo "<br>\n"; // test ###
8261
          $bysetposarr1 = $bysetposarr2;
8262
          $bysetposarr2 = array();
8263
        }
8264
      }
8265
    }
8266
  }
8267
  public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) {
8268
    if( is_array( $BYvalue ) &&
8269
      ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue )))
8270
      return TRUE;
8271
    elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue ))
8272
      return TRUE;
8273
    else
8274
      return FALSE;
8275
  }
8276
  public static function _recurIntervalIx( $freq, $date, $wkst ) {
8277
            /* create interval index */
8278
    switch( $freq ) {
8279
      case 'YEARLY':
8280
        $intervalix = $date['year'];
8281
        break;
8282
      case 'MONTHLY':
8283
        $intervalix = $date['year'].'-'.$date['month'];
8284
        break;
8285
      case 'WEEKLY':
8286
        $wdatets    = iCalUtilityFunctions::_date2timestamp( $date );
8287
        $intervalix = (int) date( 'W', ( $wdatets + $wkst ));
8288
       break;
8289
      case 'DAILY':
8290
           default:
8291
        $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day'];
8292
        break;
8293
    }
8294
    return $intervalix;
8295
  }
8296
  public static function _recurBydaySort( $bydaya, $bydayb ) {
8297
    static $days = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6 );
8298
    return ( $days[substr( $bydaya, -2 )] < $days[substr( $bydayb, -2 )] ) ? -1 : 1;
8299
  }
8300
/**
8301
 * convert input format for exrule and rrule to internal format
8302
 *
8303
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8304
 * @since 2.14.1 - 2012-09-24
8305
 * @param array $rexrule
8306
 * @return array
8307
 */
8308
  public static function _setRexrule( $rexrule ) {
8309
    $input          = array();
8310
    if( empty( $rexrule ))
8311
      return $input;
8312
    foreach( $rexrule as $rexrulelabel => $rexrulevalue ) {
8313
      $rexrulelabel = strtoupper( $rexrulelabel );
8314
      if( 'UNTIL'  != $rexrulelabel )
8315
        $input[$rexrulelabel]   = $rexrulevalue;
8316
      else {
8317
        iCalUtilityFunctions::_strDate2arr( $rexrulevalue );
8318
        if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp, always date-time UTC
8319
          $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 7, 'UTC' );
8320
        elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) { // date or UTC date-time
8321
          $parno = ( isset( $rexrulevalue['hour'] ) || isset( $rexrulevalue[4] )) ? 7 : 3;
8322
          $d = iCalUtilityFunctions::_chkDateArr( $rexrulevalue, $parno );
8323
          if(( 3 < $parno ) && isset( $d['tz'] ) && ( 'Z' != $d['tz'] ) && iCalUtilityFunctions::_isOffset( $d['tz'] )) {
8324
            $strdate              = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8325
            $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8326
            unset( $input[$rexrulelabel]['unparsedtext'] );
8327
          }
8328
          else
8329
           $input[$rexrulelabel] = $d;
8330
        }
8331
        elseif( 8 <= strlen( trim( $rexrulevalue ))) { // ex. textual date-time 2006-08-03 10:12:18 => UTC
8332
          $input[$rexrulelabel] = iCalUtilityFunctions::_strdate2date( $rexrulevalue );
8333
          unset( $input['$rexrulelabel']['unparsedtext'] );
8334
        }
8335
        if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] ))
8336
          $input[$rexrulelabel]['tz'] = 'Z';
8337
      }
8338
    }
8339
            /* set recurrence rule specification in rfc2445 order */
8340
    $input2 = array();
8341
    if( isset( $input['FREQ'] ))
8342
      $input2['FREQ']       = $input['FREQ'];
8343
    if( isset( $input['UNTIL'] ))
8344
      $input2['UNTIL']      = $input['UNTIL'];
8345
    elseif( isset( $input['COUNT'] ))
8346
      $input2['COUNT']      = $input['COUNT'];
8347
    if( isset( $input['INTERVAL'] ))
8348
      $input2['INTERVAL']   = $input['INTERVAL'];
8349
    if( isset( $input['BYSECOND'] ))
8350
      $input2['BYSECOND']   = $input['BYSECOND'];
8351
    if( isset( $input['BYMINUTE'] ))
8352
      $input2['BYMINUTE']   = $input['BYMINUTE'];
8353
    if( isset( $input['BYHOUR'] ))
8354
      $input2['BYHOUR']     = $input['BYHOUR'];
8355
    if( isset( $input['BYDAY'] )) {
8356
      if( !is_array( $input['BYDAY'] )) // ensure upper case.. .
8357
        $input2['BYDAY']    = strtoupper( $input['BYDAY'] );
8358
      else {
8359
        foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) {
8360
          if( 'DAY'        == strtoupper( $BYDAYx ))
8361
             $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv );
8362
          elseif( !is_array( $BYDAYv )) {
8363
             $input2['BYDAY'][$BYDAYx]  = $BYDAYv;
8364
          }
8365
          else {
8366
            foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) {
8367
              if( 'DAY'    == strtoupper( $BYDAYx2 ))
8368
                 $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 );
8369
              else
8370
                 $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2;
8371
            }
8372
          }
8373
        }
8374
      }
8375
    }
8376
    if( isset( $input['BYMONTHDAY'] ))
8377
      $input2['BYMONTHDAY'] = $input['BYMONTHDAY'];
8378
    if( isset( $input['BYYEARDAY'] ))
8379
      $input2['BYYEARDAY']  = $input['BYYEARDAY'];
8380
    if( isset( $input['BYWEEKNO'] ))
8381
      $input2['BYWEEKNO']   = $input['BYWEEKNO'];
8382
    if( isset( $input['BYMONTH'] ))
8383
      $input2['BYMONTH']    = $input['BYMONTH'];
8384
    if( isset( $input['BYSETPOS'] ))
8385
      $input2['BYSETPOS']   = $input['BYSETPOS'];
8386
    if( isset( $input['WKST'] ))
8387
      $input2['WKST']       = $input['WKST'];
8388
    return $input2;
8389
  }
8390
/**
8391
 * convert format for input date to internal date with parameters
8392
 *
8393
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8394
 * @since 2.16.24 - 2013-06-26
8395
 * @param mixed  $year
8396
 * @param mixed  $month   optional
8397
 * @param int    $day     optional
8398
 * @param int    $hour    optional
8399
 * @param int    $min     optional
8400
 * @param int    $sec     optional
8401
 * @param string $tz      optional
8402
 * @param array  $params  optional
8403
 * @param string $caller  optional
8404
 * @param string $objName optional
8405
 * @param string $tzid    optional
8406
 * @return array
8407
 */
8408
  public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) {
8409
    $input = $parno = null;
8410
    $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE;
8411
    iCalUtilityFunctions::_strDate2arr( $year );
8412
    if( iCalUtilityFunctions::_isArrayDate( $year )) {
8413
      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, FALSE ); //$parno );
8414
      if( 100 > $input['value']['year'] )
8415
        $input['value']['year'] += 2000;
8416
      if( $localtime )
8417
        unset( $month['VALUE'], $month['TZID'] );
8418
      elseif( !isset( $month['TZID'] ) && isset( $tzid ))
8419
        $month['TZID'] = $tzid;
8420
      if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
8421
        unset( $month['TZID'] );
8422
      elseif( !isset( $input['value']['tz'] ) &&  isset( $month['TZID'] ) && iCalUtilityFunctions::_isOffset( $month['TZID'] )) {
8423
        $input['value']['tz'] = $month['TZID'];
8424
        unset( $month['TZID'] );
8425
      }
8426
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8427
      $hitval          = ( isset( $input['value']['tz'] )) ? 7 : 6;
8428
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval );
8429
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $input['value'] ), $parno );
8430
      if( 6 > $parno )
8431
        unset( $input['value']['tz'], $input['params']['TZID'], $tzid );
8432
      if(( 6 <= $parno ) && isset( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8433
        $d             = $input['value'];
8434
        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8435
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
8436
        unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8437
      }
8438
      if( isset( $input['value']['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8439
        $input['params']['TZID'] = $input['value']['tz'];
8440
        unset( $input['value']['tz'] );
8441
      }
8442
    } // end if( iCalUtilityFunctions::_isArrayDate( $year ))
8443
    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
8444
      if( $localtime ) unset ( $month['VALUE'], $month['TZID'] );
8445
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8446
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
8447
      $hitval          = 7;
8448
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno );
8449
      if( isset( $year['tz'] ) && !empty( $year['tz'] )) {
8450
        if( !iCalUtilityFunctions::_isOffset( $year['tz'] )) {
8451
          $input['params']['TZID'] = $year['tz'];
8452
          unset( $year['tz'], $tzid );
8453
        }
8454
        else {
8455
          if( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
8456
            if( !iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
8457
              unset( $tzid );
8458
            else
8459
              unset( $input['params']['TZID']);
8460
          }
8461
          elseif( isset( $tzid ) && !iCalUtilityFunctions::_isOffset( $tzid ))
8462
            $input['params']['TZID'] = $tzid;
8463
        }
8464
      }
8465
      elseif( isset( $input['params']['TZID'] ) && !empty( $input['params']['TZID'] )) {
8466
        if( iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8467
          $year['tz'] = $input['params']['TZID'];
8468
          unset( $input['params']['TZID']);
8469
          if( isset( $tzid ) && !empty( $tzid ) && !iCalUtilityFunctions::_isOffset( $tzid ))
8470
            $input['params']['TZID'] = $tzid;
8471
        }
8472
      }
8473
      elseif( isset( $tzid ) && !empty( $tzid )) {
8474
        if( iCalUtilityFunctions::_isOffset( $tzid )) {
8475
          $year['tz'] = $tzid;
8476
          unset( $input['params']['TZID']);
8477
        }
8478
        else
8479
          $input['params']['TZID'] = $tzid;
8480
      }
8481
      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, $parno );
8482
    } // end elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year ))
8483
    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 [[[+/-]1234[56]] / timezone]
8484
      if( $localtime )
8485
        unset( $month['VALUE'], $month['TZID'] );
8486
      elseif( !isset( $month['TZID'] ) && !empty( $tzid ))
8487
        $month['TZID'] = $tzid;
8488
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8489
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno );
8490
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno );
8491
      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, $parno );
8492
      if( 3 == $parno )
8493
        unset( $input['value']['tz'], $input['params']['TZID'] );
8494
      unset( $input['value']['unparsedtext'] );
8495
      if( isset( $input['value']['tz'] )) {
8496
        if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8497
          $d           = $input['value'];
8498
          $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8499
          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8500
          unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8501
        }
8502
        else {
8503
          $input['params']['TZID'] = $input['value']['tz'];
8504
          unset( $input['value']['tz'] );
8505
        }
8506
      }
8507
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8508
        $d             = $input['value'];
8509
        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
8510
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8511
        unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8512
      }
8513
    } // end elseif( 8 <= strlen( trim( $year )))
8514
    else {
8515
      if( is_array( $params ))
8516
        $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
8517
      elseif( is_array( $tz )) {
8518
        $input['params'] = iCalUtilityFunctions::_setParams( $tz,     array( 'VALUE' => 'DATE-TIME' ));
8519
        $tz = FALSE;
8520
      }
8521
      elseif( is_array( $hour )) {
8522
        $input['params'] = iCalUtilityFunctions::_setParams( $hour,   array( 'VALUE' => 'DATE-TIME' ));
8523
        $hour = $min = $sec = $tz = FALSE;
8524
      }
8525
      if( $localtime )
8526
        unset ( $input['params']['VALUE'], $input['params']['TZID'] );
8527
      elseif( !isset( $tz ) && !isset( $input['params']['TZID'] ) && !empty( $tzid ))
8528
        $input['params']['TZID'] = $tzid;
8529
      elseif( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz ))
8530
        unset( $input['params']['TZID'] );
8531
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8532
        $tz            = $input['params']['TZID'];
8533
        unset( $input['params']['TZID'] );
8534
      }
8535
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 );
8536
      $hitval          = ( iCalUtilityFunctions::_isOffset( $tz )) ? 7 : 6;
8537
      $parno           = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno );
8538
      $input['value']  = array( 'year'  => $year, 'month' => $month, 'day'   => $day );
8539
      if( 3 != $parno ) {
8540
        $input['value']['hour'] = ( $hour ) ? $hour : '0';
8541
        $input['value']['min']  = ( $min )  ? $min  : '0';
8542
        $input['value']['sec']  = ( $sec )  ? $sec  : '0';
8543
        if( !empty( $tz ))
8544
          $input['value']['tz'] = $tz;
8545
        $strdate       = iCalUtilityFunctions::_date2strdate( $input['value'], $parno );
8546
        if( !empty( $tz ) && !iCalUtilityFunctions::_isOffset( $tz ))
8547
          $strdate    .= ( 'Z' == $tz ) ? $tz : ' '.$tz;
8548
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, $parno );
8549
        unset( $input['value']['unparsedtext'] );
8550
        if( isset( $input['value']['tz'] )) {
8551
          if( iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) {
8552
            $d           = $input['value'];
8553
            $strdate     = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $d['tz'] );
8554
            $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8555
            unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8556
          }
8557
          else {
8558
            $input['params']['TZID'] = $input['value']['tz'];
8559
            unset( $input['value']['tz'] );
8560
          }
8561
        }
8562
        elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8563
          $d             = $input['value'];
8564
          $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
8565
          $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8566
          unset( $input['value']['unparsedtext'], $input['params']['TZID'] );
8567
        }
8568
      }
8569
    } // end else (i.e. using all arguments)
8570
    if(( 3 == $parno ) || ( isset( $input['params']['VALUE'] ) && ( 'DATE' == $input['params']['VALUE'] ))) {
8571
      $input['params']['VALUE'] = 'DATE';
8572
      unset( $input['value']['hour'], $input['value']['min'], $input['value']['sec'], $input['value']['tz'], $input['params']['TZID'] );
8573
    }
8574
    elseif( isset( $input['params']['TZID'] )) {
8575
      if(( 'UTC' == strtoupper( $input['params']['TZID'] )) || ( 'GMT' == strtoupper( $input['params']['TZID'] ))) {
8576
        $input['value']['tz'] = 'Z';
8577
        unset( $input['params']['TZID'] );
8578
      }
8579
      else
8580
        unset( $input['value']['tz'] );
8581
    }
8582
    elseif( isset( $input['value']['tz'] )) {
8583
      if(( 'UTC' == strtoupper( $input['value']['tz'] )) || ( 'GMT' == strtoupper( $input['value']['tz'] )))
8584
        $input['value']['tz'] = 'Z';
8585
      if( 'Z' != $input['value']['tz'] ) {
8586
        $input['params']['TZID'] = $input['value']['tz'];
8587
        unset( $input['value']['tz'] );
8588
      }
8589
      else
8590
        unset( $input['params']['TZID'] );
8591
    }
8592
    if( $localtime )
8593
      unset( $input['value']['tz'], $input['params']['TZID'] );
8594
    return $input;
8595
  }
8596
/**
8597
 * convert format for input date (UTC) to internal date with parameters
8598
 *
8599
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8600
 * @since 2.16.24 - 2013-07-01
8601
 * @param mixed $year
8602
 * @param mixed $month  optional
8603
 * @param int   $day    optional
8604
 * @param int   $hour   optional
8605
 * @param int   $min    optional
8606
 * @param int   $sec    optional
8607
 * @param array $params optional
8608
 * @return array
8609
 */
8610
  public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) {
8611
    $input = null;
8612
    iCalUtilityFunctions::_strDate2arr( $year );
8613
    if( iCalUtilityFunctions::_isArrayDate( $year )) {
8614
      $input['value']  = iCalUtilityFunctions::_chkDateArr( $year, 7 );
8615
      if( isset( $input['value']['year'] ) && ( 100 > $input['value']['year'] ))
8616
        $input['value']['year'] += 2000;
8617
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8618
      unset( $input['params']['VALUE']  );
8619
      if( isset( $input['value']['tz'] ) && iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))
8620
        $tzid = $input['value']['tz'];
8621
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
8622
        $tzid = $input['params']['TZID'];
8623
      else
8624
        $tzid = '';
8625
      unset( $input['params']['VALUE'], $input['params']['TZID']  );
8626
      if( !empty( $tzid ) && ( 'Z' != $tzid ) && iCalUtilityFunctions::_isOffset( $tzid )) {
8627
        $d             = $input['value'];
8628
        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $tzid );
8629
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8630
        unset( $input['value']['unparsedtext'] );
8631
      }
8632
    }
8633
    elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) {
8634
      if( isset( $year['tz'] ) && ! iCalUtilityFunctions::_isOffset( $year['tz'] ))
8635
        $year['tz']    = 'UTC';
8636
      elseif( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
8637
        $year['tz']    = $input['params']['TZID'];
8638
      else
8639
        $year['tz']    = 'UTC';
8640
      $input['value']  = iCalUtilityFunctions::_timestamp2date( $year, 7 );
8641
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8642
      unset( $input['params']['VALUE'], $input['params']['TZID']  );
8643
    }
8644
    elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18
8645
      $input['value']  = iCalUtilityFunctions::_strdate2date( $year, 7 );
8646
      unset( $input['value']['unparsedtext'] );
8647
      $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ));
8648
      if(( !isset( $input['value']['tz'] ) || empty( $input['value']['tz'] )) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] )) {
8649
        $d             = $input['value'];
8650
        $strdate       = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['min'], $d['sec'], $input['params']['TZID'] );
8651
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8652
        unset( $input['value']['unparsedtext'] );
8653
      }
8654
      unset( $input['params']['VALUE'], $input['params']['TZID']  );
8655
    }
8656
    else {
8657
      $input['value']  = array( 'year'  => $year
8658
                              , 'month' => $month
8659
                              , 'day'   => $day
8660
                              , 'hour'  => $hour
8661
                              , 'min'   => $min
8662
                              , 'sec'   => $sec );
8663
      if(  isset( $tz )) $input['value']['tz'] = $tz;
8664
      if(( isset( $tz ) && iCalUtilityFunctions::_isOffset( $tz )) ||
8665
         ( isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))) {
8666
          if( !isset( $tz ) && isset( $input['params']['TZID'] ) && iCalUtilityFunctions::_isOffset( $input['params']['TZID'] ))
8667
            $input['value']['tz'] = $input['params']['TZID'];
8668
          unset( $input['params']['TZID'] );
8669
        $strdate        = iCalUtilityFunctions::_date2strdate( $input['value'], 7 );
8670
        $input['value'] = iCalUtilityFunctions::_strdate2date( $strdate, 7 );
8671
        unset( $input['value']['unparsedtext'] );
8672
      }
8673
      $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ));
8674
      unset( $input['params']['VALUE']  );
8675
    }
8676
    $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default
8677
    if( !isset( $input['value']['hour'] )) $input['value']['hour'] = 0;
8678
    if( !isset( $input['value']['min'] ))  $input['value']['min']  = 0;
8679
    if( !isset( $input['value']['sec'] ))  $input['value']['sec']  = 0;
8680
    $input['value']['tz'] = 'Z';
8681
    return $input;
8682
  }
8683
/**
8684
 * check index and set (an indexed) content in multiple value array
8685
 *
8686
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8687
 * @since 2.6.12 - 2011-01-03
8688
 * @param array $valArr
8689
 * @param mixed $value
8690
 * @param array $params
8691
 * @param array $defaults
8692
 * @param int $index
8693
 * @return void
8694
 */
8695
  public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) {
8696
    if( !is_array( $valArr )) $valArr = array();
8697
    if( $index )
8698
      $index = $index - 1;
8699
    elseif( 0 < count( $valArr )) {
8700
      $keys  = array_keys( $valArr );
8701
      $index = end( $keys ) + 1;
8702
    }
8703
    else
8704
      $index = 0;
8705
    $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults ));
8706
    ksort( $valArr );
8707
  }
8708
/**
8709
 * set input (formatted) parameters- component property attributes
8710
 *
8711
 * default parameters can be set, if missing
8712
 *
8713
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8714
 * @since 1.x.x - 2007-05-01
8715
 * @param array $params
8716
 * @param array $defaults
8717
 * @return array
8718
 */
8719
  public static function _setParams( $params, $defaults=FALSE ) {
8720
    if( !is_array( $params))
8721
      $params = array();
8722
    $input = array();
8723
    foreach( $params as $paramKey => $paramValue ) {
8724
      if( is_array( $paramValue )) {
8725
        foreach( $paramValue as $pkey => $pValue ) {
8726
          if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 )))
8727
            $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 ));
8728
        }
8729
      }
8730
      elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 )))
8731
        $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 ));
8732
      if( 'VALUE' == strtoupper( $paramKey ))
8733
        $input['VALUE']                 = strtoupper( $paramValue );
8734
      else
8735
        $input[strtoupper( $paramKey )] = $paramValue;
8736
    }
8737
    if( is_array( $defaults )) {
8738
      foreach( $defaults as $paramKey => $paramValue ) {
8739
        if( !isset( $input[$paramKey] ))
8740
          $input[$paramKey] = $paramValue;
8741
      }
8742
    }
8743
    return (0 < count( $input )) ? $input : null;
8744
  }
8745
/**
8746
 * break lines at pos 75
8747
 *
8748
 * Lines of text SHOULD NOT be longer than 75 octets, excluding the line
8749
 * break. Long content lines SHOULD be split into a multiple line
8750
 * representations using a line "folding" technique. That is, a long
8751
 * line can be split between any two characters by inserting a CRLF
8752
 * immediately followed by a single linear white space character (i.e.,
8753
 * SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence
8754
 * of CRLF followed immediately by a single linear white space character
8755
 * is ignored (i.e., removed) when processing the content type.
8756
 *
8757
 * Edited 2007-08-26 by Anders Litzell, anders@litzell.se to fix bug where
8758
 * the reserved expression "\n" in the arg $string could be broken up by the
8759
 * folding of lines, causing ambiguity in the return string.
8760
 *
8761
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8762
 * @since 2.16.2 - 2012-12-18
8763
 * @param string $value
8764
 * @return string
8765
 */
8766
  public static function _size75( $string, $nl ) {
8767
    $tmp             = $string;
8768
    $string          = '';
8769
    $cCnt = $x       = 0;
8770
    while( TRUE ) {
8771
      if( !isset( $tmp[$x] )) {
8772
        $string     .= $nl;                           // loop breakes here
8773
        break;
8774
      }
8775
      elseif(( 74   <= $cCnt ) && ( '\\'  == $tmp[$x] ) && ( 'n' == $tmp[$x+1] )) {
8776
        $string     .= $nl.' \n';                     // don't break lines inside '\n'
8777
        $x          += 2;
8778
        if( !isset( $tmp[$x] )) {
8779
          $string   .= $nl;
8780
          break;
8781
        }
8782
        $cCnt        = 3;
8783
      }
8784
      elseif( 75    <= $cCnt ) {
8785
        $string     .= $nl.' ';
8786
        $cCnt        = 1;
8787
      }
8788
      $byte          = ord( $tmp[$x] );
8789
      $string       .= $tmp[$x];
8790
      switch( TRUE ) { // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
8791
        case(( $byte >= 0x20 ) && ( $byte <= 0x7F )): // characters U-00000000 - U-0000007F (same as ASCII)
8792
          $cCnt     += 1;
8793
          break;                                      // add a one byte character
8794
        case(( $byte & 0xE0) == 0xC0 ):               // characters U-00000080 - U-000007FF, mask 110XXXXX
8795
          if( isset( $tmp[$x+1] )) {
8796
            $cCnt   += 1;
8797
            $string  .= $tmp[$x+1];
8798
            $x       += 1;                            // add a two bytes character
8799
          }
8800
          break;
8801
        case(( $byte & 0xF0 ) == 0xE0 ):              // characters U-00000800 - U-0000FFFF, mask 1110XXXX
8802
          if( isset( $tmp[$x+2] )) {
8803
            $cCnt   += 1;
8804
            $string .= $tmp[$x+1].$tmp[$x+2];
8805
            $x      += 2;                             // add a three bytes character
8806
          }
8807
          break;
8808
        case(( $byte & 0xF8 ) == 0xF0 ):              // characters U-00010000 - U-001FFFFF, mask 11110XXX
8809
          if( isset( $tmp[$x+3] )) {
8810
            $cCnt   += 1;
8811
            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3];
8812
            $x      += 3;                             // add a four bytes character
8813
          }
8814
          break;
8815
        case(( $byte & 0xFC ) == 0xF8 ):              // characters U-00200000 - U-03FFFFFF, mask 111110XX
8816
          if( isset( $tmp[$x+4] )) {
8817
            $cCnt   += 1;
8818
            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4];
8819
            $x      += 4;                             // add a five bytes character
8820
          }
8821
          break;
8822
        case(( $byte & 0xFE ) == 0xFC ):              // characters U-04000000 - U-7FFFFFFF, mask 1111110X
8823
          if( isset( $tmp[$x+5] )) {
8824
            $cCnt   += 1;
8825
            $string .= $tmp[$x+1].$tmp[$x+2].$tmp[$x+3].$tmp[$x+4].$tmp[$x+5];
8826
            $x      += 5;                             // add a six bytes character
8827
          }
8828
        default:                                      // add any other byte without counting up $cCnt
8829
          break;
8830
      } // end switch( TRUE )
8831
      $x         += 1;                                // next 'byte' to test
8832
    } // end while( TRUE ) {
8833
    return $string;
8834
  }
8835
/**
8836
 * sort callback functions for exdate
8837
 *
8838
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8839
 * @since 2.16.11 - 2013-01-12
8840
 * @param array $a
8841
 * @param array $b
8842
 * @return int
8843
 */
8844
  public static function _sortExdate1( $a, $b ) {
8845
    $as  = sprintf( '%04d%02d%02d', $a['year'], $a['month'], $a['day'] );
8846
    $as .= ( isset( $a['hour'] )) ? sprintf( '%02d%02d%02d', $a['hour'], $a['min'], $a['sec'] ) : '';
8847
    $bs  = sprintf( '%04d%02d%02d', $b['year'], $b['month'], $b['day'] );
8848
    $bs .= ( isset( $b['hour'] )) ? sprintf( '%02d%02d%02d', $b['hour'], $b['min'], $b['sec'] ) : '';
8849
    return strcmp( $as, $bs );
8850
  }
8851
  public static function _sortExdate2( $a, $b ) {
8852
    $val = reset( $a['value'] );
8853
    $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8854
    $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8855
    $val = reset( $b['value'] );
8856
    $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8857
    $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8858
    return strcmp( $as, $bs );
8859
  }
8860
/**
8861
 * sort callback functions for rdate
8862
 *
8863
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8864
 * @since 2.16.27 - 2013-07-05
8865
 * @param array $a
8866
 * @param array $b
8867
 * @return int
8868
 */
8869
  public static function _sortRdate1( $a, $b ) {
8870
    $val = isset( $a['year'] ) ? $a : $a[0];
8871
    $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8872
    $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8873
    $val = isset( $b['year'] ) ? $b : $b[0];
8874
    $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8875
    $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8876
    return strcmp( $as, $bs );
8877
  }
8878
  public static function _sortRdate2( $a, $b ) {
8879
    $val   = isset( $a['value'][0]['year'] ) ? $a['value'][0] : $a['value'][0][0];
8880
    if( empty( $val ))
8881
      $as  = '';
8882
    else {
8883
      $as  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8884
      $as .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8885
    }
8886
    $val   = isset( $b['value'][0]['year'] ) ? $b['value'][0] : $b['value'][0][0];
8887
    if( empty( $val ))
8888
      $bs  = '';
8889
    else {
8890
      $bs  = sprintf( '%04d%02d%02d', $val['year'], $val['month'], $val['day'] );
8891
      $bs .= ( isset( $val['hour'] )) ? sprintf( '%02d%02d%02d', $val['hour'], $val['min'], $val['sec'] ) : '';
8892
    }
8893
    return strcmp( $as, $bs );
8894
  }
8895
/**
8896
 * step date, return updated date, array and timpstamp
8897
 *
8898
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8899
 * @since 2.14.1 - 2012-09-24
8900
 * @param array $date, date to step
8901
 * @param int   $timestamp
8902
 * @param array $step, default array( 'day' => 1 )
8903
 * @return void
8904
 */
8905
  public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) {
8906
    if( !isset( $date['hour'] )) $date['hour'] = 0;
8907
    if( !isset( $date['min'] ))  $date['min']  = 0;
8908
    if( !isset( $date['sec'] ))  $date['sec']  = 0;
8909
    foreach( $step as $stepix => $stepvalue )
8910
      $date[$stepix] += $stepvalue;
8911
    $timestamp  = mktime( $date['hour'], $date['min'], $date['sec'], $date['month'], $date['day'], $date['year'] );
8912
    $d          = date( 'Y-m-d-H-i-s', $timestamp);
8913
    $d          = explode( '-', $d );
8914
    $date       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2], 'hour' => $d[3], 'min' => $d[4], 'sec' => $d[5] );
8915
    foreach( $date as $k => $v )
8916
      $date[$k] = (int) $v;
8917
  }
8918
/**
8919
 * convert a date from specific string to array format
8920
 *
8921
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8922
 * @since 2.11.8 - 2012-01-27
8923
 * @param mixed $input
8924
 * @return bool, TRUE on success
8925
 */
8926
  public static function _strDate2arr( & $input ) {
8927
    if( is_array( $input ))
8928
      return FALSE;
8929
    if( 5 > strlen( (string) $input ))
8930
      return FALSE;
8931
    $work = $input;
8932
    if( 2 == substr_count( $work, '-' ))
8933
      $work = str_replace( '-', '', $work );
8934
    if( 2 == substr_count( $work, '/' ))
8935
      $work = str_replace( '/', '', $work );
8936
    if( !ctype_digit( substr( $work, 0, 8 )))
8937
      return FALSE;
8938
    $temp = array( 'year'  => (int) substr( $work,  0, 4 )
8939
                 , 'month' => (int) substr( $work,  4, 2 )
8940
                 , 'day'   => (int) substr( $work,  6, 2 ));
8941
    if( !checkdate( $temp['month'], $temp['day'], $temp['year'] ))
8942
      return FALSE;
8943
    if( 8 == strlen( $work )) {
8944
      $input = $temp;
8945
      return TRUE;
8946
    }
8947
    if(( ' ' == substr( $work, 8, 1 )) || ( 'T' == substr( $work, 8, 1 )) || ( 't' == substr( $work, 8, 1 )))
8948
      $work =  substr( $work, 9 );
8949
    elseif( ctype_digit( substr( $work, 8, 1 )))
8950
      $work = substr( $work, 8 );
8951
    else
8952
     return FALSE;
8953
    if( 2 == substr_count( $work, ':' ))
8954
      $work = str_replace( ':', '', $work );
8955
    if( !ctype_digit( substr( $work, 0, 4 )))
8956
      return FALSE;
8957
    $temp['hour']  = substr( $work, 0, 2 );
8958
    $temp['min']   = substr( $work, 2, 2 );
8959
    if((( 0 > $temp['hour'] ) || ( $temp['hour'] > 23 )) ||
8960
       (( 0 > $temp['min'] )  || ( $temp['min']  > 59 )))
8961
      return FALSE;
8962
    if( ctype_digit( substr( $work, 4, 2 ))) {
8963
      $temp['sec'] = substr( $work, 4, 2 );
8964
      if((  0 > $temp['sec'] ) || ( $temp['sec']  > 59 ))
8965
        return FALSE;
8966
      $len = 6;
8967
    }
8968
    else {
8969
      $temp['sec'] = 0;
8970
      $len = 4;
8971
    }
8972
    if( $len < strlen( $work))
8973
      $temp['tz'] = trim( substr( $work, 6 ));
8974
    $input = $temp;
8975
    return TRUE;
8976
  }
8977
/**
8978
 * ensures internal date-time/date format for input date-time/date in string fromat
8979
 *
8980
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
8981
 * @since 2.16.24 - 2013-06-26
8982
 * Modified to also return original string value by Yitzchok Lavi <icalcreator@onebigsystem.com>
8983
 * @param array $datetime
8984
 * @param int   $parno optional, default FALSE
8985
 * @param moxed $wtz optional, default null
8986
 * @return array
8987
 */
8988
  public static function _date_time_string( $datetime, $parno=FALSE ) {
8989
    return iCalUtilityFunctions::_strdate2date( $datetime, $parno, null );
8990
  }
8991
  public static function _strdate2date( $datetime, $parno=FALSE, $wtz=null ) {
8992
    // save original input string to return it later
8993
    $unparseddatetime = $datetime;
8994
    $datetime   = (string) trim( $datetime );
8995
    $tz         = null;
8996
    $offset     = 0;
8997
    $tzSts      = FALSE;
8998
    $len        = strlen( $datetime );
8999
    if( 'Z' == substr( $datetime, -1 )) {
9000
      $tz       = 'Z';
9001
      $datetime = trim( substr( $datetime, 0, ( $len - 1 )));
9002
      $tzSts    = TRUE;
9003
    }
9004
    if( iCalUtilityFunctions::_isOffset( substr( $datetime, -5, 5 ))) { // [+/-]NNNN offset
9005
      $tz       = substr( $datetime, -5, 5 );
9006
      $datetime = trim( substr( $datetime, 0, ($len - 5)));
9007
    }
9008
    elseif( iCalUtilityFunctions::_isOffset( substr( $datetime, -7, 7 ))) { // [+/-]NNNNNN offset
9009
      $tz       = substr( $datetime, -7, 7 );
9010
      $datetime = trim( substr( $datetime, 0, ($len - 7)));
9011
    }
9012
    elseif( empty( $wtz ) && ctype_digit( substr( $datetime, 0, 4 )) && ctype_digit( substr( $datetime, -2, 2 )) && iCalUtilityFunctions::_strDate2arr( $datetime )) {
9013
      $output = $datetime;
9014
      if( !empty( $tz ))
9015
        $output['tz'] = 'Z';
9016
      $output['unparsedtext'] = $unparseddatetime;
9017
      return $output;
9018
    }
9019
    else {
9020
      $cx  = $tx = 0;    //  find any trailing timezone or offset
9021
      $len = strlen( $datetime );
9022
      for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) {
9023
        $char = substr( $datetime, $cx, 1 );
9024
        if(( ' ' == $char ) || ctype_digit( $char ))
9025
          break; // if exists, tz ends here.. . ?
9026
        else
9027
           $tx--; // tz length counter
9028
      }
9029
      if( 0 > $tx ) { // if any
9030
        $tz     = substr( $datetime, $tx );
9031
        $datetime = trim( substr( $datetime, 0, $len + $tx ));
9032
      }
9033
      if(( ctype_digit( substr( $datetime, 0, 8 )) && ( 'T' ==  substr( $datetime, 8, 1 )) && ctype_digit( substr( $datetime, -6, 6 ))) ||
9034
         ( ctype_digit( substr( $datetime, 0, 14 ))))
9035
        $tzSts  = TRUE;
9036
    }
9037
    if( empty( $tz ) && !empty( $wtz ))
9038
      $tz       = $wtz;
9039
    if( 3 == $parno )
9040
      $tz       = null;
9041
    if( !empty( $tz )) { // tz set
9042
      if(( 'Z' != $tz ) && ( iCalUtilityFunctions::_isOffset( $tz ))) {
9043
        $offset = (string) iCalUtilityFunctions::_tz2offset( $tz ) * -1;
9044
        $tz     = 'UTC';
9045
        $tzSts  = TRUE;
9046
      }
9047
      elseif( !empty( $wtz ))
9048
        $tzSts  = TRUE;
9049
      $tz       = trim( $tz );
9050
      if(( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
9051
        $tz     = 'UTC';
9052
      if( 0 < substr_count( $datetime, '-' ))
9053
        $datetime = str_replace( '-', '/', $datetime );
9054
      try {
9055
        $d        = new DateTime( $datetime, new DateTimeZone( $tz ));
9056
        if( 0  != $offset )  // adjust for offset
9057
          $d->modify( $offset.' seconds' );
9058
        $datestring = $d->format( 'Y-m-d-H-i-s' );
9059
        unset( $d );
9060
      }
9061
      catch( Exception $e ) {
9062
        $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
9063
      }
9064
    } // end if( !empty( $tz ))
9065
    else
9066
      $datestring = date( 'Y-m-d-H-i-s', strtotime( $datetime ));
9067
    if( 'UTC' == $tz )
9068
      $tz         = 'Z';
9069
    $d            = explode( '-', $datestring );
9070
    $output       = array( 'year' => $d[0], 'month' => $d[1], 'day' => $d[2] );
9071
    if( !$parno || ( 3 != $parno )) { // parno is set to 6 or 7
9072
      $output['hour'] = $d[3];
9073
      $output['min']  = $d[4];
9074
      $output['sec']  = $d[5];
9075
      if(( $tzSts || ( 7 == $parno )) && !empty( $tz ))
9076
        $output['tz'] = $tz;
9077
    }
9078
    // return original string in the array in case strtotime failed to make sense of it
9079
    $output['unparsedtext'] = $unparseddatetime;
9080
    return $output;
9081
  }
9082
/********************************************************************************/
9083
/**
9084
 * special characters management output
9085
 *
9086
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9087
 * @since 2.16.2 - 2012-12-18
9088
 * @param string $string
9089
 * @param string $format
9090
 * @param string $nl
9091
 * @return string
9092
 */
9093
  public static function _strrep( $string, $format, $nl ) {
9094
    switch( $format ) {
9095
      case 'xcal':
9096
        $string = str_replace( '\n',  $nl, $string);
9097
        $string = htmlspecialchars( strip_tags( stripslashes( urldecode ( $string ))));
9098
        break;
9099
      default:
9100
        $pos = 0;
9101
        $specChars = array( 'n', 'N', 'r', ',', ';' );
9102
        while( isset( $string[$pos] )) {
9103
          if( FALSE === ( $pos = strpos( $string, "\\", $pos )))
9104
            break;
9105
          if( !in_array( substr( $string, $pos, 1 ), $specChars )) {
9106
            $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 ));
9107
            $pos += 1;
9108
          }
9109
          $pos += 1;
9110
        }
9111
        if( FALSE !== strpos( $string, '"' ))
9112
          $string = str_replace('"',   "'",       $string);
9113
        if( FALSE !== strpos( $string, ',' ))
9114
          $string = str_replace(',',   '\,',      $string);
9115
        if( FALSE !== strpos( $string, ';' ))
9116
          $string = str_replace(';',   '\;',      $string);
9117
        if( FALSE !== strpos( $string, "\r\n" ))
9118
          $string = str_replace( "\r\n", '\n',    $string);
9119
        elseif( FALSE !== strpos( $string, "\r" ))
9120
          $string = str_replace( "\r", '\n',      $string);
9121
        elseif( FALSE !== strpos( $string, "\n" ))
9122
          $string = str_replace( "\n", '\n',      $string);
9123
        if( FALSE !== strpos( $string, '\N' ))
9124
          $string = str_replace( '\N', '\n',      $string);
9125
//        if( FALSE !== strpos( $string, $nl ))
9126
          $string = str_replace( $nl, '\n', $string);
9127
        break;
9128
    }
9129
    return $string;
9130
  }
9131
/**
9132
 * special characters management input (from iCal file)
9133
 *
9134
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9135
 * @since 2.16.2 - 2012-12-18
9136
 * @param string $string
9137
 * @return string
9138
 */
9139
  public static function _strunrep( $string ) {
9140
    $string = str_replace( '\\\\', '\\',     $string);
9141
    $string = str_replace( '\,',   ',',      $string);
9142
    $string = str_replace( '\;',   ';',      $string);
9143
//    $string = str_replace( '\n',  $nl, $string); // ??
9144
    return $string;
9145
  }
9146
/**
9147
 * convert timestamp to date array, default UTC or adjusted for offset/timezone
9148
 *
9149
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9150
 * @since 2.15.1 - 2012-10-17
9151
 * @param mixed   $timestamp
9152
 * @param int     $parno
9153
 * @param string  $wtz
9154
 * @return array
9155
 */
9156
  public static function _timestamp2date( $timestamp, $parno=6, $wtz=null ) {
9157
    if( is_array( $timestamp )) {
9158
      $tz        = ( isset( $timestamp['tz'] )) ? $timestamp['tz'] : $wtz;
9159
      $timestamp = $timestamp['timestamp'];
9160
    }
9161
    $tz          = ( isset( $tz )) ? $tz : $wtz;
9162
    $offset      = 0;
9163
    if( empty( $tz ) || ( 'Z' == $tz ) || ( 'GMT' == strtoupper( $tz )))
9164
      $tz        = 'UTC';
9165
    elseif( iCalUtilityFunctions::_isOffset( $tz )) {
9166
      $offset    = iCalUtilityFunctions::_tz2offset( $tz );
9167
//      $tz        = 'UTC';
9168
    }
9169
    try {
9170
      $d         = new DateTime( "@$timestamp" );  // set UTC date
9171
      if(  0 != $offset )                          // adjust for offset
9172
        $d->modify( $offset.' seconds' );
9173
      elseif( 'UTC' != $tz )
9174
        $d->setTimezone( new DateTimeZone( $tz )); // convert to local date
9175
      $date      = $d->format( 'Y-m-d-H-i-s' );
9176
      unset( $d );
9177
    }
9178
    catch( Exception $e ) {
9179
      $date      = date( 'Y-m-d-H-i-s', $timestamp );
9180
    }
9181
    $date        = explode( '-', $date );
9182
    $output      = array( 'year' => $date[0], 'month' => $date[1], 'day' => $date[2] );
9183
    if( 3 != $parno ) {
9184
      $output['hour'] = $date[3];
9185
      $output['min']  = $date[4];
9186
      $output['sec']  = $date[5];
9187
      if(( 'UTC' == $tz ) || ( 0 == $offset ))
9188
        $output['tz'] = 'Z';
9189
    }
9190
    return $output;
9191
  }
9192
/**
9193
 * convert timestamp (seconds) to duration in array format
9194
 *
9195
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9196
 * @since 2.6.23 - 2010-10-23
9197
 * @param int $timestamp
9198
 * @return array, duration format
9199
 */
9200
  public static function _timestamp2duration( $timestamp ) {
9201
    $dur         = array();
9202
    $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 ));
9203
    $timestamp   =              $timestamp % ( 7 * 24 * 60 * 60 );
9204
    $dur['day']  = (int) floor( $timestamp / ( 24 * 60 * 60 ));
9205
    $timestamp   =              $timestamp % ( 24 * 60 * 60 );
9206
    $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 ));
9207
    $timestamp   =              $timestamp % ( 60 * 60 );
9208
    $dur['min']  = (int) floor( $timestamp / ( 60 ));
9209
    $dur['sec']  = (int)        $timestamp % ( 60 );
9210
    return $dur;
9211
  }
9212
/**
9213
 * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0)
9214
 *
9215
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9216
 * @since 2.15.1 - 2012-10-17
9217
 * @param mixed  $date,   date to alter
9218
 * @param string $tzFrom, PHP valid 'from' timezone
9219
 * @param string $tzTo,   PHP valid 'to' timezone, default 'UTC'
9220
 * @param string $format, date output format, default 'Ymd\THis'
9221
 * @return bool
9222
 */
9223
  public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) {
9224
    if( is_array( $date ) && isset( $date['timestamp'] )) {
9225
      try {
9226
        $d = new DateTime( "@{$date['timestamp']}" ); // set UTC date
9227
        $d->setTimezone(new DateTimeZone( $tzFrom )); // convert to 'from' date
9228
      }
9229
      catch( Exception $e ) { return FALSE; }
9230
    }
9231
    else {
9232
      if( iCalUtilityFunctions::_isArrayDate( $date )) {
9233
        if( isset( $date['tz'] ))
9234
          unset( $date['tz'] );
9235
        $date  = iCalUtilityFunctions::_date2strdate( iCalUtilityFunctions::_chkDateArr( $date ));
9236
      }
9237
      if( 'Z' == substr( $date, -1 ))
9238
        $date = substr( $date, 0, ( strlen( $date ) - 2 ));
9239
      try { $d = new DateTime( $date, new DateTimeZone( $tzFrom )); }
9240
      catch( Exception $e ) { return FALSE; }
9241
    }
9242
    try { $d->setTimezone( new DateTimeZone( $tzTo )); }
9243
    catch( Exception $e ) { return FALSE; }
9244
    $date = $d->format( $format );
9245
    return TRUE;
9246
  }
9247
/**
9248
 * convert offset, [+/-]HHmm[ss], to seconds, used when correcting UTC to localtime or v.v.
9249
 *
9250
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9251
 * @since 2.11.4 - 2012-01-11
9252
 * @param string $offset
9253
 * @return integer
9254
 */
9255
  public static function _tz2offset( $tz ) {
9256
    $tz           = trim( (string) $tz );
9257
    $offset       = 0;
9258
    if(((     5  != strlen( $tz ))       && ( 7  != strlen( $tz ))) ||
9259
       ((    '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) ||
9260
       (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) ||
9261
           (( 7  == strlen( $tz ))       && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 ))))
9262
      return $offset;
9263
    $hours2sec    = (int) substr( $tz, 1, 2 ) * 3600;
9264
    $min2sec      = (int) substr( $tz, 3, 2 ) *   60;
9265
    $sec          = ( 7  == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00';
9266
    $offset       = $hours2sec + $min2sec + $sec;
9267
    $offset       = ('-' == substr( $tz, 0, 1 )) ? $offset * -1 : $offset;
9268
    return $offset;
9269
  }
9270
}
9271
/*********************************************************************************/
9272
/*          iCalcreator vCard helper functions                                   */
9273
/*********************************************************************************/
9274
/**
9275
 * convert single ATTENDEE, CONTACT or ORGANIZER (in email format) to vCard
9276
 * returns vCard/TRUE or if directory (if set) or file write is unvalid, FALSE
9277
 *
9278
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9279
 * @since 2.12.2 - 2012-07-11
9280
 * @param object $email
9281
 * $param string $version, vCard version (default 2.1)
9282
 * $param string $directory, where to save vCards (default FALSE)
9283
 * $param string $ext, vCard file extension (default 'vcf')
9284
 * @return mixed
9285
 */
9286
function iCal2vCard( $email, $version='2.1', $directory=FALSE, $ext='vcf' ) {
9287
  if( FALSE === ( $pos = strpos( $email, '@' )))
9288
    return FALSE;
9289
  if( $directory ) {
9290
    if( DIRECTORY_SEPARATOR != substr( $directory, ( 0 - strlen( DIRECTORY_SEPARATOR ))))
9291
      $directory .= DIRECTORY_SEPARATOR;
9292
    if( !is_dir( $directory ) || !is_writable( $directory ))
9293
      return FALSE;
9294
  }
9295
            /* prepare vCard */
9296
  $email  = str_replace( 'MAILTO:', '', $email );
9297
  $name   = $person = substr( $email, 0, $pos );
9298
  if( ctype_upper( $name ) || ctype_lower( $name ))
9299
    $name = array( $name );
9300
  else {
9301
    if( FALSE !== ( $pos = strpos( $name, '.' ))) {
9302
      $name = explode( '.', $name );
9303
      foreach( $name as $k => $part )
9304
        $name[$k] = ucfirst( $part );
9305
    }
9306
    else { // split camelCase
9307
      $chars = $name;
9308
      $name  = array( $chars[0] );
9309
      $k     = 0;
9310
      $x     = 1;
9311
      while( FALSE !== ( $char = substr( $chars, $x, 1 ))) {
9312
        if( ctype_upper( $char )) {
9313
          $k += 1;
9314
          $name[$k] = '';
9315
        }
9316
        $name[$k]  .= $char;
9317
        $x++;
9318
      }
9319
    }
9320
  }
9321
  $nl     = "\r\n";
9322
  $FN     = 'FN:'.implode( ' ', $name ).$nl;
9323
  $name   = array_reverse( $name );
9324
  $N      = 'N:'.array_shift( $name );
9325
  $scCnt  = 0;
9326
  while( NULL != ( $part = array_shift( $name ))) {
9327
    if(( '4.0' != $version ) || ( 4 > $scCnt ))
9328
      $scCnt += 1;
9329
    $N   .= ';'.$part;
9330
  }
9331
  while(( '4.0' == $version ) && ( 4 > $scCnt )) {
9332
    $N   .= ';';
9333
    $scCnt += 1;
9334
  }
9335
  $N     .= $nl;
9336
  $EMAIL  = 'EMAIL:'.$email.$nl;
9337
           /* create vCard */
9338
  $vCard  = 'BEGIN:VCARD'.$nl;
9339
  $vCard .= "VERSION:$version$nl";
9340
  $vCard .= 'PRODID:-//kigkonsult.se '.ICALCREATOR_VERSION."//$nl";
9341
  $vCard .= $N;
9342
  $vCard .= $FN;
9343
  $vCard .= $EMAIL;
9344
  $vCard .= 'REV:'.gmdate( 'Ymd\THis\Z' ).$nl;
9345
  $vCard .= 'END:VCARD'.$nl;
9346
            /* save each vCard as (unique) single file */
9347
  if( $directory ) {
9348
    $fname = $directory.preg_replace( '/[^a-z0-9.]/i', '', $email );
9349
    $cnt   = 1;
9350
    $dbl   = '';
9351
    while( is_file ( $fname.$dbl.'.'.$ext )) {
9352
      $cnt += 1;
9353
      $dbl = "_$cnt";
9354
    }
9355
    if( FALSE === file_put_contents( $fname, $fname.$dbl.'.'.$ext ))
9356
      return FALSE;
9357
    return TRUE;
9358
  }
9359
            /* return vCard */
9360
  else
9361
    return $vCard;
9362
}
9363
/**
9364
 * convert ATTENDEEs, CONTACTs and ORGANIZERs (in email format) to vCards
9365
 *
9366
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9367
 * @since 2.12.2 - 2012-05-07
9368
 * @param object $calendar, iCalcreator vcalendar instance reference
9369
 * $param string $version, vCard version (default 2.1)
9370
 * $param string $directory, where to save vCards (default FALSE)
9371
 * $param string $ext, vCard file extension (default 'vcf')
9372
 * @return mixed
9373
 */
9374
function iCal2vCards( & $calendar, $version='2.1', $directory=FALSE, $ext='vcf' ) {
9375
  $hits   = array();
9376
  $vCardP = array( 'ATTENDEE', 'CONTACT', 'ORGANIZER' );
9377
  foreach( $vCardP as $prop ) {
9378
    $hits2 = $calendar->getProperty( $prop );
9379
    foreach( $hits2 as $propValue => $occCnt ) {
9380
      if( FALSE === ( $pos = strpos( $propValue, '@' )))
9381
        continue;
9382
      $propValue = str_replace( 'MAILTO:', '', $propValue );
9383
      if( isset( $hits[$propValue] ))
9384
        $hits[$propValue] += $occCnt;
9385
      else
9386
        $hits[$propValue]  = $occCnt;
9387
    }
9388
  }
9389
  if( empty( $hits ))
9390
    return FALSE;
9391
  ksort( $hits );
9392
  $output   = '';
9393
  foreach( $hits as $email => $skip ) {
9394
    $res = iCal2vCard( $email, $version, $directory, $ext );
9395
    if( $directory && !$res )
9396
      return FALSE;
9397
    elseif( !$res )
9398
      return $res;
9399
    else
9400
      $output .= $res;
9401
  }
9402
  if( $directory )
9403
    return TRUE;
9404
  if( !empty( $output ))
9405
    return $output;
9406
  return FALSE;
9407
}
9408
/*********************************************************************************/
9409
/*          iCalcreator XML (rfc6321) helper functions                           */
9410
/*********************************************************************************/
9411
/**
9412
 * format iCal XML output, rfc6321, using PHP SimpleXMLElement
9413
 *
9414
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9415
 * @since 2.16.22 - 2013-06-27
9416
 * @param object $calendar, iCalcreator vcalendar instance reference
9417
 * @return string
9418
 */
9419
function iCal2XML( & $calendar ) {
9420
            /** fix an SimpleXMLElement instance and create root element */
9421
  $xmlstr       = '<?xml version="1.0" encoding="utf-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">';
9422
  $xmlstr      .= '<!-- created '.date( 'Ymd\THis\Z', time());
9423
  $xmlstr      .= ' utilizing kigkonsult.se '.ICALCREATOR_VERSION.' iCal2XMl (rfc6321) -->';
9424
  $xmlstr      .= '</icalendar>';
9425
  $xml          = new SimpleXMLElement( $xmlstr );
9426
  $vcalendar    = $xml->addChild( 'vcalendar' );
9427
            /** fix calendar properties */
9428
  $properties   = $vcalendar->addChild( 'properties' );
9429
  $calProps     = array( 'version', 'prodid', 'calscale', 'method' );
9430
  foreach( $calProps as $calProp ) {
9431
    if( FALSE !== ( $content = $calendar->getProperty( $calProp )))
9432
      _addXMLchild( $properties, $calProp, 'text', $content );
9433
  }
9434
  while( FALSE !== ( $content = $calendar->getProperty( FALSE, FALSE, TRUE )))
9435
    _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9436
  $langCal = $calendar->getConfig( 'language' );
9437
            /** prepare to fix components with properties */
9438
  $components   = $vcalendar->addChild( 'components' );
9439
            /** fix component properties */
9440
  while( FALSE !== ( $component = $calendar->getComponent())) {
9441
    $compName   = $component->objName;
9442
    $child      = $components->addChild( $compName );
9443
    $properties = $child->addChild( 'properties' );
9444
    $langComp   = $component->getConfig( 'language' );
9445
    $props      = $component->getConfig( 'setPropertyNames' );
9446
    foreach( $props as $prop ) {
9447
      switch( strtolower( $prop )) {
9448
        case 'attach':          // may occur multiple times, below
9449
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9450
            $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
9451
            unset( $content['params']['VALUE'] );
9452
            _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9453
          }
9454
          break;
9455
        case 'attendee':
9456
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9457
            if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9458
              if( $langComp )
9459
                $content['params']['LANGUAGE'] = $langComp;
9460
              elseif( $langCal )
9461
                $content['params']['LANGUAGE'] = $langCal;
9462
            }
9463
            _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9464
          }
9465
          break;
9466
        case 'exdate':
9467
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9468
            $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
9469
            unset( $content['params']['VALUE'] );
9470
            _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9471
          }
9472
          break;
9473
        case 'freebusy':
9474
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9475
            if( is_array( $content ) && isset( $content['value']['fbtype'] )) {
9476
              $content['params']['FBTYPE'] = $content['value']['fbtype'];
9477
              unset( $content['value']['fbtype'] );
9478
            }
9479
            _addXMLchild( $properties, $prop, 'period', $content['value'], $content['params'] );
9480
          }
9481
          break;
9482
        case 'request-status':
9483
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9484
            if( !isset( $content['params']['LANGUAGE'] )) {
9485
              if( $langComp )
9486
                $content['params']['LANGUAGE'] = $langComp;
9487
              elseif( $langCal )
9488
                $content['params']['LANGUAGE'] = $langCal;
9489
            }
9490
            _addXMLchild( $properties, $prop, 'rstatus', $content['value'], $content['params'] );
9491
          }
9492
          break;
9493
        case 'rdate':
9494
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9495
            $type = 'date-time';
9496
            if( isset( $content['params']['VALUE'] )) {
9497
              if( 'DATE' == $content['params']['VALUE'] )
9498
                $type = 'date';
9499
              elseif( 'PERIOD' == $content['params']['VALUE'] )
9500
                $type = 'period';
9501
            }
9502
            unset( $content['params']['VALUE'] );
9503
            _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9504
          }
9505
          break;
9506
        case 'categories':
9507
        case 'comment':
9508
        case 'contact':
9509
        case 'description':
9510
        case 'related-to':
9511
        case 'resources':
9512
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9513
            if(( 'related-to' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
9514
              if( $langComp )
9515
                $content['params']['LANGUAGE'] = $langComp;
9516
              elseif( $langCal )
9517
                $content['params']['LANGUAGE'] = $langCal;
9518
            }
9519
            _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9520
          }
9521
          break;
9522
        case 'x-prop':
9523
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9524
            _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9525
          break;
9526
        case 'created':         // single occurence below, if set
9527
        case 'completed':
9528
        case 'dtstamp':
9529
        case 'last-modified':
9530
          $utcDate = TRUE;
9531
        case 'dtstart':
9532
        case 'dtend':
9533
        case 'due':
9534
        case 'recurrence-id':
9535
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9536
            $type = ( isset( $content['params']['VALUE'] ) && ( 'DATE' == $content['params']['VALUE'] )) ? 'date' : 'date-time';
9537
            unset( $content['params']['VALUE'] );
9538
            if(( isset( $content['params']['TZID'] ) && empty( $content['params']['TZID'] )) || @is_null( $content['params']['TZID'] ))
9539
              unset( $content['params']['TZID'] );
9540
            _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9541
          }
9542
          unset( $utcDate );
9543
          break;
9544
        case 'duration':
9545
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9546
            _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
9547
          break;
9548
        case 'exrule':
9549
        case 'rrule':
9550
          while( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9551
            _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
9552
          break;
9553
        case 'class':
9554
        case 'location':
9555
        case 'status':
9556
        case 'summary':
9557
        case 'transp':
9558
        case 'tzid':
9559
        case 'uid':
9560
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9561
            if((( 'location' == $prop ) || ( 'summary' == $prop )) && !isset( $content['params']['LANGUAGE'] )) {
9562
              if( $langComp )
9563
                $content['params']['LANGUAGE'] = $langComp;
9564
              elseif( $langCal )
9565
                $content['params']['LANGUAGE'] = $langCal;
9566
            }
9567
            _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9568
          }
9569
          break;
9570
        case 'geo':
9571
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9572
            _addXMLchild( $properties, $prop, 'geo', $content['value'], $content['params'] );
9573
          break;
9574
        case 'organizer':
9575
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE ))) {
9576
            if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9577
              if( $langComp )
9578
                $content['params']['LANGUAGE'] = $langComp;
9579
              elseif( $langCal )
9580
                $content['params']['LANGUAGE'] = $langCal;
9581
            }
9582
            _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9583
          }
9584
          break;
9585
        case 'percent-complete':
9586
        case 'priority':
9587
        case 'sequence':
9588
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9589
            _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
9590
          break;
9591
        case 'tzurl':
9592
        case 'url':
9593
          if( FALSE !== ( $content = $component->getProperty( $prop, FALSE, TRUE )))
9594
            _addXMLchild( $properties, $prop, 'uri', $content['value'], $content['params'] );
9595
          break;
9596
      } // end switch( $prop )
9597
    } // end foreach( $props as $prop )
9598
            /** fix subComponent properties, if any */
9599
    while( FALSE !== ( $subcomp = $component->getComponent())) {
9600
      $subCompName  = $subcomp->objName;
9601
      $child2       = $child->addChild( $subCompName );
9602
      $properties   = $child2->addChild( 'properties' );
9603
      $langComp     = $subcomp->getConfig( 'language' );
9604
      $subCompProps = $subcomp->getConfig( 'setPropertyNames' );
9605
      foreach( $subCompProps as $prop ) {
9606
        switch( strtolower( $prop )) {
9607
          case 'attach':          // may occur multiple times, below
9608
            while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9609
              $type = ( isset( $content['params']['VALUE'] ) && ( 'BINARY' == $content['params']['VALUE'] )) ? 'binary' : 'uri';
9610
              unset( $content['params']['VALUE'] );
9611
              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9612
            }
9613
            break;
9614
          case 'attendee':
9615
            while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9616
              if( isset( $content['params']['CN'] ) && !isset( $content['params']['LANGUAGE'] )) {
9617
                if( $langComp )
9618
                  $content['params']['LANGUAGE'] = $langComp;
9619
                elseif( $langCal )
9620
                  $content['params']['LANGUAGE'] = $langCal;
9621
              }
9622
              _addXMLchild( $properties, $prop, 'cal-address', $content['value'], $content['params'] );
9623
            }
9624
            break;
9625
          case 'comment':
9626
          case 'tzname':
9627
            while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9628
              if( !isset( $content['params']['LANGUAGE'] )) {
9629
                if( $langComp )
9630
                  $content['params']['LANGUAGE'] = $langComp;
9631
                elseif( $langCal )
9632
                  $content['params']['LANGUAGE'] = $langCal;
9633
              }
9634
              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9635
            }
9636
            break;
9637
          case 'rdate':
9638
            while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9639
              $type = 'date-time';
9640
              if( isset( $content['params']['VALUE'] )) {
9641
                if( 'DATE' == $content['params']['VALUE'] )
9642
                  $type = 'date';
9643
                elseif( 'PERIOD' == $content['params']['VALUE'] )
9644
                  $type = 'period';
9645
              }
9646
              unset( $content['params']['VALUE'] );
9647
              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9648
            }
9649
            break;
9650
          case 'x-prop':
9651
            while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9652
              _addXMLchild( $properties, $content[0], 'unknown', $content[1]['value'], $content[1]['params'] );
9653
            break;
9654
          case 'action':      // single occurence below, if set
9655
          case 'description':
9656
          case 'summary':
9657
            if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9658
              if(( 'action' != $prop ) && !isset( $content['params']['LANGUAGE'] )) {
9659
                if( $langComp )
9660
                  $content['params']['LANGUAGE'] = $langComp;
9661
                elseif( $langCal )
9662
                  $content['params']['LANGUAGE'] = $langCal;
9663
              }
9664
              _addXMLchild( $properties, $prop, 'text', $content['value'], $content['params'] );
9665
            }
9666
            break;
9667
          case 'dtstart':
9668
            if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9669
              unset( $content['value']['tz'], $content['params']['VALUE'] ); // always local time
9670
              _addXMLchild( $properties, $prop, 'date-time', $content['value'], $content['params'] );
9671
            }
9672
            break;
9673
          case 'duration':
9674
            if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9675
              _addXMLchild( $properties, $prop, 'duration', $content['value'], $content['params'] );
9676
            break;
9677
          case 'repeat':
9678
            if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9679
              _addXMLchild( $properties, $prop, 'integer', $content['value'], $content['params'] );
9680
            break;
9681
          case 'trigger':
9682
            if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE ))) {
9683
              if( isset( $content['value']['year'] )   &&
9684
                  isset( $content['value']['month'] )  &&
9685
                  isset( $content['value']['day'] ))
9686
                $type = 'date-time';
9687
              else {
9688
                $type = 'duration';
9689
                if( !isset( $content['value']['relatedStart'] ) || ( TRUE !== $content['value']['relatedStart'] ))
9690
                  $content['params']['RELATED'] = 'END';
9691
              }
9692
              _addXMLchild( $properties, $prop, $type, $content['value'], $content['params'] );
9693
            }
9694
            break;
9695
          case 'tzoffsetto':
9696
          case 'tzoffsetfrom':
9697
            if( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9698
              _addXMLchild( $properties, $prop, 'utc-offset', $content['value'], $content['params'] );
9699
            break;
9700
          case 'rrule':
9701
            while( FALSE !== ( $content = $subcomp->getProperty( $prop, FALSE, TRUE )))
9702
              _addXMLchild( $properties, $prop, 'recur', $content['value'], $content['params'] );
9703
            break;
9704
        } // switch( $prop )
9705
      } // end foreach( $subCompProps as $prop )
9706
    } // end while( FALSE !== ( $subcomp = $component->getComponent()))
9707
  } // end while( FALSE !== ( $component = $calendar->getComponent()))
9708
  return $xml->asXML();
9709
}
9710
/**
9711
 * Add children to a SimpleXMLelement
9712
 *
9713
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9714
 * @since 2.16.22 - 2013-06-24
9715
 * @param object $parent,  reference to a SimpleXMLelement node
9716
 * @param string $name,    new element node name
9717
 * @param string $type,    content type, subelement(-s) name
9718
 * @param string $content, new subelement content
9719
 * @param array  $params,  new element 'attributes'
9720
 * @return void
9721
 */
9722
function _addXMLchild( & $parent, $name, $type, $content, $params=array()) {
9723
            /** create new child node */
9724
  $name  = strtolower( $name );
9725
  $child = $parent->addChild( $name );
9726
  if( !empty( $params )) {
9727
    $parameters = $child->addChild( 'parameters' );
9728
    foreach( $params as $param => $parVal ) {
9729
      if( 'VALUE' == $param )
9730
        continue;
9731
      $param = strtolower( $param );
9732
      if( 'x-' == substr( $param, 0, 2  )) {
9733
        $p1 = $parameters->addChild( $param );
9734
        $p2 = $p1->addChild( 'unknown', htmlspecialchars( $parVal ));
9735
      }
9736
      else {
9737
        $p1 = $parameters->addChild( $param );
9738
        switch( $param ) {
9739
          case 'altrep':
9740
          case 'dir':            $ptype = 'uri';            break;
9741
          case 'delegated-from':
9742
          case 'delegated-to':
9743
          case 'member':
9744
          case 'sent-by':        $ptype = 'cal-address';    break;
9745
          case 'rsvp':           $ptype = 'boolean';        break ;
9746
          default:               $ptype = 'text';           break;
9747
        }
9748
        if( is_array( $parVal )) {
9749
          foreach( $parVal as $pV )
9750
            $p2 = $p1->addChild( $ptype, htmlspecialchars( $pV ));
9751
        }
9752
        else
9753
          $p2 = $p1->addChild( $ptype, htmlspecialchars( $parVal ));
9754
      }
9755
    }
9756
  } // end if( !empty( $params ))
9757
  if(( empty( $content ) && ( '0' != $content )) || ( !is_array( $content) && ( '-' != substr( $content, 0, 1 ) && ( 0 > $content ))))
9758
    return;
9759
            /** store content */
9760
  switch( $type ) {
9761
    case 'binary':
9762
      $v = $child->addChild( $type, $content );
9763
      break;
9764
    case 'boolean':
9765
      break;
9766
    case 'cal-address':
9767
      $v = $child->addChild( $type, $content );
9768
      break;
9769
    case 'date':
9770
      if( array_key_exists( 'year', $content ))
9771
        $content = array( $content );
9772
      foreach( $content as $date ) {
9773
        $str = sprintf( '%04d-%02d-%02d', $date['year'], $date['month'], $date['day'] );
9774
        $v = $child->addChild( $type, $str );
9775
      }
9776
      break;
9777
    case 'date-time':
9778
      if( array_key_exists( 'year', $content ))
9779
        $content = array( $content );
9780
      foreach( $content as $dt ) {
9781
        if( !isset( $dt['hour'] )) $dt['hour'] = 0;
9782
        if( !isset( $dt['min'] ))  $dt['min']  = 0;
9783
        if( !isset( $dt['sec'] ))  $dt['sec']  = 0;
9784
        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $dt['year'], $dt['month'], $dt['day'], $dt['hour'], $dt['min'], $dt['sec'] );
9785
        if( isset( $dt['tz'] ) && ( 'Z' == $dt['tz'] ))
9786
          $str .= 'Z';
9787
        $v = $child->addChild( $type, $str );
9788
      }
9789
      break;
9790
    case 'duration':
9791
      $output = (( 'trigger' == $name ) && ( FALSE !== $content['before'] )) ? '-' : '';
9792
      $v = $child->addChild( $type, $output.iCalUtilityFunctions::_duration2str( $content ) );
9793
      break;
9794
    case 'geo':
9795
      if( !empty( $content )) {
9796
        $v1 = $child->addChild( 'latitude',  number_format( (float) $content['latitude'],  6, '.', '' ));
9797
        $v1 = $child->addChild( 'longitude', number_format( (float) $content['longitude'], 6, '.', '' ));
9798
      }
9799
      break;
9800
    case 'integer':
9801
      $v = $child->addChild( $type, (string) $content );
9802
      break;
9803
    case 'period':
9804
      if( !is_array( $content ))
9805
        break;
9806
      foreach( $content as $period ) {
9807
        $v1 = $child->addChild( $type );
9808
        $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[0]['year'], $period[0]['month'], $period[0]['day'], $period[0]['hour'], $period[0]['min'], $period[0]['sec'] );
9809
        if( isset( $period[0]['tz'] ) && ( 'Z' == $period[0]['tz'] ))
9810
          $str .= 'Z';
9811
        $v2 = $v1->addChild( 'start', $str );
9812
        if( array_key_exists( 'year', $period[1] )) {
9813
          $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02d', $period[1]['year'], $period[1]['month'], $period[1]['day'], $period[1]['hour'], $period[1]['min'], $period[1]['sec'] );
9814
          if( isset($period[1]['tz'] ) && ( 'Z' == $period[1]['tz'] ))
9815
            $str .= 'Z';
9816
          $v2 = $v1->addChild( 'end', $str );
9817
        }
9818
        else
9819
          $v2 = $v1->addChild( 'duration', iCalUtilityFunctions::_duration2str( $period[1] ));
9820
      }
9821
      break;
9822
    case 'recur':
9823
      foreach( $content as $rulelabel => $rulevalue ) {
9824
        $rulelabel = strtolower( $rulelabel );
9825
        switch( $rulelabel ) {
9826
          case 'until':
9827
            if( isset( $rulevalue['hour'] ))
9828
              $str = sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'], $rulevalue['hour'], $rulevalue['min'], $rulevalue['sec'] );
9829
            else
9830
              $str = sprintf( '%04d-%02d-%02d', $rulevalue['year'], $rulevalue['month'], $rulevalue['day'] );
9831
            $v = $child->addChild( $rulelabel, $str );
9832
            break;
9833
          case 'bysecond':
9834
          case 'byminute':
9835
          case 'byhour':
9836
          case 'bymonthday':
9837
          case 'byyearday':
9838
          case 'byweekno':
9839
          case 'bymonth':
9840
          case 'bysetpos': {
9841
            if( is_array( $rulevalue )) {
9842
              foreach( $rulevalue as $vix => $valuePart )
9843
                $v = $child->addChild( $rulelabel, $valuePart );
9844
            }
9845
            else
9846
              $v = $child->addChild( $rulelabel, $rulevalue );
9847
            break;
9848
          }
9849
          case 'byday': {
9850
            if( isset( $rulevalue['DAY'] )) {
9851
              $str  = ( isset( $rulevalue[0] )) ? $rulevalue[0] : '';
9852
              $str .= $rulevalue['DAY'];
9853
              $p    = $child->addChild( $rulelabel, $str );
9854
            }
9855
            else {
9856
              foreach( $rulevalue as $valuePart ) {
9857
                if( isset( $valuePart['DAY'] )) {
9858
                  $str  = ( isset( $valuePart[0] )) ? $valuePart[0] : '';
9859
                  $str .= $valuePart['DAY'];
9860
                  $p    = $child->addChild( $rulelabel, $str );
9861
                }
9862
                else
9863
                  $p    = $child->addChild( $rulelabel, $valuePart );
9864
              }
9865
            }
9866
            break;
9867
          }
9868
          case 'freq':
9869
          case 'count':
9870
          case 'interval':
9871
          case 'wkst':
9872
          default:
9873
            $p = $child->addChild( $rulelabel, $rulevalue );
9874
            break;
9875
        } // end switch( $rulelabel )
9876
      } // end foreach( $content as $rulelabel => $rulevalue )
9877
      break;
9878
    case 'rstatus':
9879
      $v = $child->addChild( 'code', number_format( (float) $content['statcode'], 2, '.', ''));
9880
      $v = $child->addChild( 'description', htmlspecialchars( $content['text'] ));
9881
      if( isset( $content['extdata'] ))
9882
        $v = $child->addChild( 'data', htmlspecialchars( $content['extdata'] ));
9883
      break;
9884
    case 'text':
9885
      if( !is_array( $content ))
9886
        $content = array( $content );
9887
      foreach( $content as $part )
9888
        $v = $child->addChild( $type, htmlspecialchars( $part ));
9889
      break;
9890
    case 'time':
9891
      break;
9892
    case 'uri':
9893
      $v = $child->addChild( $type, $content );
9894
      break;
9895
    case 'utc-offset':
9896
      if( in_array( substr( $content, 0, 1 ), array( '-', '+' ))) {
9897
        $str     = substr( $content, 0, 1 );
9898
        $content = substr( $content, 1 );
9899
      }
9900
      else
9901
        $str     = '+';
9902
      $str .= substr( $content, 0, 2 ).':'.substr( $content, 2, 2 );
9903
      if( 4 < strlen( $content ))
9904
        $str .= ':'.substr( $content, 4 );
9905
      $v = $child->addChild( $type, $str );
9906
      break;
9907
    case 'unknown':
9908
    default:
9909
      if( is_array( $content ))
9910
        $content = implode( '', $content );
9911
      $v = $child->addChild( 'unknown', htmlspecialchars( $content ));
9912
      break;
9913
  }
9914
}
9915
/**
9916
 * parse xml file into iCalcreator instance
9917
 *
9918
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9919
 * @since 2.16.22 - 2013-06-18
9920
 * @param  string $xmlfile
9921
 * @param  array  $iCalcfg iCalcreator config array (opt)
9922
 * @return mixediCalcreator instance or FALSE on error
9923
 */
9924
function XMLfile2iCal( $xmlfile, $iCalcfg=array()) {
9925
  if( FALSE === ( $xmlstr = file_get_contents( $xmlfile )))
9926
    return FALSE;
9927
  return xml2iCal( $xmlstr, $iCalcfg );
9928
}
9929
/**
9930
 * parse xml string into iCalcreator instance, alias of XML2iCal
9931
 *
9932
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9933
 * @since 2.16.22 - 2013-06-18
9934
 * @param  string $xmlstr
9935
 * @param  array  $iCalcfg iCalcreator config array (opt)
9936
 * @return mixed  iCalcreator instance or FALSE on error
9937
 */
9938
function XMLstr2iCal( $xmlstr, $iCalcfg=array()) {
9939
  return XML2iCal( $xmlstr, $iCalcfg);
9940
}
9941
/**
9942
 * parse xml string into iCalcreator instance
9943
 *
9944
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9945
 * @since 2.16.22 - 2013-06-20
9946
 * @param  string $xmlstr
9947
 * @param  array  $iCalcfg iCalcreator config array (opt)
9948
 * @return mixed  iCalcreator instance or FALSE on error
9949
 */
9950
function XML2iCal( $xmlstr, $iCalcfg=array()) {
9951
  $xmlstr  = str_replace( array( "\r\n", "\n\r", "\n", "\r" ), '', $xmlstr );
9952
  $xml     = XMLgetTagContent1( $xmlstr, 'vcalendar', $endIx );
9953
  $iCal    = new vcalendar( $iCalcfg );
9954
  XMLgetComps( $iCal, $xmlstr );
9955
  unset( $xmlstr );
9956
  return $iCal;
9957
}
9958
/**
9959
 * parse XML string into iCalcreator components
9960
 *
9961
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9962
 * @since 2.16.22 - 2013-06-20
9963
 * @param object $iCal, iCalcreator vcalendar or component object instance
9964
 * @param string $xml
9965
 * @return bool
9966
 */
9967
function XMLgetComps( $iCal, $xml ) {
9968
  static $comps = array( 'vtimezone', 'standard', 'daylight', 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm' );
9969
  $sx      = 0;
9970
  while(( FALSE !== substr( $xml, ( $sx + 11 ), 1 )) &&
9971
        ( '<properties>' != substr( $xml, $sx, 12 )) && ( '<components>' != substr( $xml, $sx, 12 )))
9972
    $sx   += 1;
9973
  if( FALSE === substr( $xml, ( $sx + 11 ), 1 ))
9974
    return FALSE;
9975
  if( '<properties>' == substr( $xml, $sx, 12 )) {
9976
    $xml2  = XMLgetTagContent1( $xml, 'properties', $endIx );
9977
    XMLgetProps( $iCal, $xml2 );
9978
    $xml   = substr( $xml, $endIx );
9979
  }
9980
  if( '<components>' == substr( $xml, 0, 12 ))
9981
    $xml     = XMLgetTagContent1( $xml, 'components', $endIx );
9982
  while( ! empty( $xml )) {
9983
    $xml2  = XMLgetTagContent2( $xml, $tagName, $endIx );
9984
    if( in_array( strtolower( $tagName ), $comps ) && ( FALSE !== ( $subComp = $iCal->newComponent( $tagName ))))
9985
      XMLgetComps( $subComp, $xml2 );
9986
    $xml   = substr( $xml, $endIx);
9987
  }
9988
  unset( $xml );
9989
  return $iCal;
9990
}
9991
/**
9992
 * parse XML into iCalcreator properties
9993
 *
9994
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
9995
 * @since 2.16.21 - 2013-06-23
9996
 * @param  array  $iCal iCalcreator calendar/component instance
9997
 * @param  string $xml
9998
 * @return void
9999
 */
10000
function XMLgetProps( $iCal, $xml) {
10001
  while( ! empty( $xml )) {
10002
    $xml2         = XMLgetTagContent2( $xml, $propName, $endIx );
10003
    $propName     = strtoupper( $propName );
10004
    if( empty( $xml2 ) && ( '0' != $xml2 )) {
10005
      $iCal->setProperty( $propName );
10006
      $xml        = substr( $xml, $endIx);
10007
      continue;
10008
    }
10009
    $params       = array();
10010
    if( '<parameters/>' == substr( $xml2, 0, 13 ))
10011
      $xml2       = substr( $xml2, 13 );
10012
    elseif( '<parameters>' == substr( $xml2, 0, 12 )) {
10013
      $xml3       = XMLgetTagContent1( $xml2, 'parameters', $endIx2 );
10014
      while( ! empty( $xml3 )) {
10015
        $xml4     = XMLgetTagContent2( $xml3, $paramKey, $endIx3 );
10016
        $pType    = FALSE; // skip parameter valueType
10017
        $paramKey = strtoupper( $paramKey );
10018
        if( in_array( $paramKey, array( 'DELEGATED-FROM', 'DELEGATED-TO', 'MEMBER' ))) {
10019
          while( ! empty( $xml4 )) {
10020
            if( ! isset( $params[$paramKey] ))
10021
              $params[$paramKey]   = array( XMLgetTagContent1( $xml4, 'cal-address', $endIx4 ));
10022
            else
10023
              $params[$paramKey][] = XMLgetTagContent1( $xml4, 'cal-address', $endIx4 );
10024
            $xml4     = substr( $xml4, $endIx4 );
10025
          }
10026
        }
10027
        else {
10028
          if( ! isset( $params[$paramKey] ))
10029
            $params[$paramKey]  = html_entity_decode( XMLgetTagContent2( $xml4, $pType, $endIx4 ));
10030
          else
10031
            $params[$paramKey] .= ','.html_entity_decode( XMLgetTagContent2( $xml4, $pType, $endIx4 ));
10032
        }
10033
        $xml3     = substr( $xml3, $endIx3 );
10034
      }
10035
      $xml2       = substr( $xml2, $endIx2 );
10036
    } // if( '<parameters>' == substr( $xml2, 0, 12 ))
10037
    $valueType    = FALSE;
10038
    $value        = ( ! empty( $xml2 ) || ( '0' == $xml2 )) ? XMLgetTagContent2( $xml2, $valueType, $endIx3 ) : '';
10039
    switch( $propName ) {
10040
      case 'CATEGORIES':
10041
      case 'RESOURCES':
10042
        $tValue      = array();
10043
        while( ! empty( $xml2 )) {
10044
          $tValue[]  = html_entity_decode( XMLgetTagContent2( $xml2, $valueType, $endIx4 ));
10045
          $xml2      = substr( $xml2, $endIx4 );
10046
        }
10047
        $value       = $tValue;
10048
        break;
10049
      case 'EXDATE':   // multiple single-date(-times) may exist
10050
      case 'RDATE':
10051
        if( 'period' != $valueType ) {
10052
          if( 'date' == $valueType )
10053
            $params['VALUE'] = 'DATE';
10054
          $t         = array();
10055
          while( ! empty( $xml2 ) && ( '<date' == substr( $xml2, 0, 5 ))) {
10056
            $t[]     = XMLgetTagContent2( $xml2, $pType, $endIx4 );
10057
            $xml2    = substr( $xml2, $endIx4 );
10058
          }
10059
          $value = $t;
10060
          break;
10061
        }
10062
      case 'FREEBUSY':
10063
        if( 'RDATE' == $propName )
10064
          $params['VALUE'] = 'PERIOD';
10065
        $value       = array();
10066
        while( ! empty( $xml2 ) && ( '<period>' == substr( $xml2, 0, 8 ))) {
10067
          $xml3      = XMLgetTagContent1( $xml2, 'period', $endIx4 ); // period
10068
          $t         = array();
10069
          while( ! empty( $xml3 )) {
10070
            $t[]     = XMLgetTagContent2( $xml3, $pType, $endIx5 ); // start - end/duration
10071
            $xml3    = substr( $xml3, $endIx5 );
10072
          }
10073
          $value[]   = $t;
10074
          $xml2      = substr( $xml2, $endIx4 );
10075
        }
10076
        break;
10077
      case 'TZOFFSETTO':
10078
      case 'TZOFFSETFROM':
10079
        $value       = str_replace( ':', '', $value );
10080
        break;
10081
      case 'GEO':
10082
        $tValue      = array( 'latitude' => $value );
10083
        $tValue['longitude'] = XMLgetTagContent1( substr( $xml2, $endIx3 ), 'longitude', $endIx3 );
10084
        $value       = $tValue;
10085
        break;
10086
      case 'EXRULE':
10087
      case 'RRULE':
10088
        $tValue      = array( $valueType => $value );
10089
        $xml2        = substr( $xml2, $endIx3 );
10090
        $valueType   = FALSE;
10091
        while( ! empty( $xml2 )) {
10092
          $t         = XMLgetTagContent2( $xml2, $valueType, $endIx4 );
10093
          switch( $valueType ) {
10094
            case 'freq':
10095
            case 'count':
10096
            case 'until':
10097
            case 'interval':
10098
            case 'wkst':
10099
              $tValue[$valueType] = $t;
10100
              break;
10101
            case 'byday':
10102
              if( 2 == strlen( $t ))
10103
                $tValue[$valueType][] = array( 'DAY' => $t );
10104
              else {
10105
                $day = substr( $t, -2 );
10106
                $key = substr( $t, 0, ( strlen( $t ) - 2 ));
10107
                $tValue[$valueType][] = array( $key, 'DAY' => $day );
10108
              }
10109
              break;
10110
            default:
10111
              $tValue[$valueType][] = $t;
10112
          }
10113
          $xml2      = substr( $xml2, $endIx4 );
10114
        }
10115
        $value       = $tValue;
10116
        break;
10117
      case 'REQUEST-STATUS':
10118
        $tValue      = array();
10119
        while( ! empty( $xml2 )) {
10120
          $t         = html_entity_decode( XMLgetTagContent2( $xml2, $valueType, $endIx4 ));
10121
          $tValue[$valueType] = $t;
10122
          $xml2    = substr( $xml2, $endIx4 );
10123
        }
10124
        if( ! empty( $tValue ))
10125
          $value   = $tValue;
10126
        else
10127
          $value   = array( 'code' => null, 'description' => null );
10128
        break;
10129
      default:
10130
        switch( $valueType ) {
10131
          case 'binary':    $params['VALUE'] = 'BINARY';           break;
10132
          case 'date':      $params['VALUE'] = 'DATE';             break;
10133
          case 'date-time': $params['VALUE'] = 'DATE-TIME';        break;
10134
          case 'text':
10135
          case 'unknown':   $value = html_entity_decode( $value ); break;
10136
        }
10137
        break;
10138
    } // end switch( $propName )
10139
    if( 'FREEBUSY' == $propName ) {
10140
      $fbtype = $params['FBTYPE'];
10141
      unset( $params['FBTYPE'] );
10142
      $iCal->setProperty( $propName, $fbtype, $value, $params );
10143
    }
10144
    elseif( 'GEO' == $propName )
10145
      $iCal->setProperty( $propName, $value['latitude'], $value['longitude'], $params );
10146
    elseif( 'REQUEST-STATUS' == $propName ) {
10147
      if( !isset( $value['data'] ))
10148
        $value['data'] = FALSE;
10149
      $iCal->setProperty( $propName, $value['code'], $value['description'], $value['data'], $params );
10150
    }
10151
    else {
10152
      if( empty( $value ) && ( is_array( $value ) || ( '0' > $value )))
10153
        $value = '';
10154
      $iCal->setProperty( $propName, $value, $params );
10155
    }
10156
    $xml        = substr( $xml, $endIx);
10157
  } // end while( ! empty( $xml ))
10158
}
10159
/**
10160
 * fetch a specific XML tag content
10161
 *
10162
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10163
 * @since 2.16.22 - 2013-06-20
10164
 * @param string $xml
10165
 * @param string $tagName
10166
 * @param int $endIx
10167
 * @return mixed
10168
 */
10169
function XMLgetTagContent1( $xml, $tagName, & $endIx=0 ) {
10170
  $strlen    = strlen( $tagName );
10171
  $sx1       = 0;
10172
  while( FALSE !== substr( $xml, $sx1, 1 )) {
10173
    if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 1 ), 1 )) &&
10174
       ( strtolower( "<$tagName>" )   == strtolower( substr( $xml, $sx1, ( $strlen + 2 )))))
10175
      break;
10176
    if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 3 ), 1 )) &&
10177
       ( strtolower( "<$tagName />" ) == strtolower( substr( $xml, $sx1, ( $strlen + 4 ))))) { // empty tag
10178
      $endIx = $strlen + 5;
10179
      return '';
10180
    }
10181
    if(( FALSE !== substr( $xml, ( $sx1 + $strlen + 2 ), 1 )) &&
10182
       ( strtolower( "<$tagName/>" )  == strtolower( substr( $xml, $sx1, ( $strlen + 3 ))))) { // empty tag
10183
      $endIx = $strlen + 4;
10184
      return '';
10185
    }
10186
    $sx1    += 1;
10187
  }
10188
  if( FALSE === substr( $xml, $sx1, 1 )) {
10189
    $endIx   = ( empty( $sx )) ? 0 : $sx - 1;
10190
    return '';
10191
  }
10192
  if( FALSE === ( $pos = stripos( $xml, "</$tagName>" ))) { // missing end tag??
10193
    $endIx   = strlen( $xml ) + 1;
10194
    return '';
10195
  }
10196
  $endIx     = $pos + $strlen + 3;
10197
  return substr( $xml, ( $sx1 + $strlen + 2 ), ( $pos - $sx1 - 2 - $strlen ));
10198
}
10199
/**
10200
 * fetch next (unknown) XML tagname AND content
10201
 *
10202
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10203
 * @since 2.16.22 - 2013-06-20
10204
 * @param string $xml
10205
 * @param string $tagName
10206
 * @param int $endIx
10207
 * @return mixed
10208
 */
10209
function XMLgetTagContent2( $xml, & $tagName, & $endIx ) {
10210
  $endIx       = strlen( $xml ) + 1; // just in case.. .
10211
  $sx1         = 0;
10212
  while( FALSE !== substr( $xml, $sx1, 1 )) {
10213
    if( '<' == substr( $xml, $sx1, 1 )) {
10214
      if(( FALSE !== substr( $xml, ( $sx1 + 3 ), 1 )) && ( '<!--' == substr( $xml, $sx1, 4 ))) // skip comment
10215
        $sx1  += 1;
10216
      else
10217
        break; // tagname start here
10218
    }
10219
    else
10220
      $sx1    += 1;
10221
  }
10222
  $sx2         = $sx1;
10223
  while( FALSE !== substr( $xml, $sx2 )) {
10224
    if(( FALSE !== substr( $xml, ( $sx2 + 1 ), 1 )) && ( '/>' == substr( $xml, $sx2, 2 ))) { // empty tag
10225
      $tagName = trim( substr( $xml, ( $sx1 + 1 ), ( $sx2 - $sx1 - 1 )));
10226
      $endIx   = $sx2 + 2;
10227
      return '';
10228
    }
10229
    if( '>' == substr( $xml, $sx2, 1 )) // tagname ends here
10230
      break;
10231
    $sx2      += 1;
10232
  }
10233
  $tagName     = substr( $xml, ( $sx1 + 1 ), ( $sx2 - $sx1 - 1 ));
10234
  $endIx       = $sx2 + 1;
10235
  if( FALSE === substr( $xml, $sx2, 1 )) {
10236
    return '';
10237
  }
10238
  $strlen      = strlen( $tagName );
10239
  if(( 'duration' == $tagName ) &&
10240
     ( FALSE !== ( $pos1 = stripos( $xml, "<duration>",  $sx1+1  ))) &&
10241
     ( FALSE !== ( $pos2 = stripos( $xml, "</duration>", $pos1+1 ))) &&
10242
     ( FALSE !== ( $pos3 = stripos( $xml, "</duration>", $pos2+1 ))) &&
10243
     ( $pos1 < $pos2 ) && ( $pos2 < $pos3 ))
10244
    $pos = $pos3;
10245
  elseif( FALSE === ( $pos = stripos( $xml, "</$tagName>", $sx2 )))
10246
    return '';
10247
  $endIx       = $pos + $strlen + 3;
10248
  return substr( $xml, ( $sx1 + $strlen + 2 ), ( $pos - $strlen - 2 ));
10249
}
10250
/*********************************************************************************/
10251
/*          Additional functions to use with vtimezone components                */
10252
/*********************************************************************************/
10253
/**
10254
 * For use with
10255
 * iCalcreator (kigkonsult.se/iCalcreator/index.php)
10256
 * copyright (c) 2011 Yitzchok Lavi
10257
 * icalcreator@onebigsystem.com
10258
 *
10259
 * This library is free software; you can redistribute it and/or
10260
 * modify it under the terms of the GNU Lesser General Public
10261
 * License as published by the Free Software Foundation; either
10262
 * version 2.1 of the License, or (at your option) any later version.
10263
 *
10264
 * This library is distributed in the hope that it will be useful,
10265
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10266
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10267
 * Lesser General Public License for more details.
10268
 *
10269
 * You should have received a copy of the GNU Lesser General Public
10270
 * License along with this library; if not, write to the Free Software
10271
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
10272
 */
10273
/**
10274
 * Additional functions to use with vtimezone components
10275
 *
10276
 * Before calling the functions, set time zone 'GMT' ('date_default_timezone_set')!
10277
 *
10278
 * @author Yitzchok Lavi <icalcreator@onebigsystem.com>
10279
 *         adjusted for iCalcreator Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
10280
 * @version 1.0.2 - 2011-02-24
10281
 *
10282
 */
10283
/**
10284
 * Returns array with the offset information from UTC for a (UTC) datetime/timestamp in the
10285
 * timezone, according to the VTIMEZONE information in the input array.
10286
 *
10287
 * $param array  $timezonesarray, output from function getTimezonesAsDateArrays (below)
10288
 * $param string $tzid,           time zone identifier
10289
 * $param mixed  $timestamp,      timestamp or a UTC datetime (in array format)
10290
 * @return array, time zone data with keys for 'offsetHis', 'offsetSec' and 'tzname'
10291
 *
10292
 */
10293
function getTzOffsetForDate($timezonesarray, $tzid, $timestamp) {
10294
    if( is_array( $timestamp )) {
10295
//$disp = sprintf( '%04d%02d%02d %02d%02d%02d', $timestamp['year'], $timestamp['month'], $timestamp['day'], $timestamp['hour'], $timestamp['min'], $timestamp['sec'] ); // test ###
10296
      $timestamp = gmmktime(
10297
            $timestamp['hour'],
10298
            $timestamp['min'],
10299
            $timestamp['sec'],
10300
            $timestamp['month'],
10301
            $timestamp['day'],
10302
            $timestamp['year']
10303
            ) ;
10304
    }
10305
    $tzoffset = array();
10306
    // something to return if all goes wrong (such as if $tzid doesn't find us an array of dates)
10307
    $tzoffset['offsetHis'] = '+0000';
10308
    $tzoffset['offsetSec'] = 0;
10309
    $tzoffset['tzname']    = '?';
10310
    if( !isset( $timezonesarray[$tzid] ))
10311
      return $tzoffset;
10312
    $tzdatearray = $timezonesarray[$tzid];
10313
    if ( is_array($tzdatearray) ) {
10314
        sort($tzdatearray); // just in case
10315
        if ( $timestamp < $tzdatearray[0]['timestamp'] ) {
10316
            // our date is before the first change
10317
            $tzoffset['offsetHis'] = $tzdatearray[0]['tzbefore']['offsetHis'] ;
10318
            $tzoffset['offsetSec'] = $tzdatearray[0]['tzbefore']['offsetSec'] ;
10319
            $tzoffset['tzname']    = $tzdatearray[0]['tzbefore']['offsetHis'] ; // we don't know the tzname in this case
10320
        } elseif ( $timestamp >= $tzdatearray[count($tzdatearray)-1]['timestamp'] ) {
10321
            // our date is after the last change (we do this so our scan can stop at the last record but one)
10322
            $tzoffset['offsetHis'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetHis'] ;
10323
            $tzoffset['offsetSec'] = $tzdatearray[count($tzdatearray)-1]['tzafter']['offsetSec'] ;
10324
            $tzoffset['tzname']    = $tzdatearray[count($tzdatearray)-1]['tzafter']['tzname'] ;
10325
        } else {
10326
            // our date somewhere in between
10327
            // loop through the list of dates and stop at the one where the timestamp is before our date and the next one is after it
10328
            // we don't include the last date in our loop as there isn't one after it to check
10329
            for ( $i = 0 ; $i <= count($tzdatearray)-2 ; $i++ ) {
10330
                if(( $timestamp >= $tzdatearray[$i]['timestamp'] ) && ( $timestamp < $tzdatearray[$i+1]['timestamp'] )) {
10331
                    $tzoffset['offsetHis'] = $tzdatearray[$i]['tzafter']['offsetHis'] ;
10332
                    $tzoffset['offsetSec'] = $tzdatearray[$i]['tzafter']['offsetSec'] ;
10333
                    $tzoffset['tzname']    = $tzdatearray[$i]['tzafter']['tzname'] ;
10334
                    break;
10335
                }
10336
            }
10337
        }
10338
    }
10339
    return $tzoffset;
10340
}
10341
/**
10342
 * Returns an array containing all the timezone data in the vcalendar object
10343
 *
10344
 * @param object $vcalendar, iCalcreator calendar instance
10345
 * @return array, time zone transition timestamp, array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
10346
 *                based on the timezone data in the vcalendar object
10347
 *
10348
 */
10349
function getTimezonesAsDateArrays($vcalendar) {
10350
    $timezonedata = array();
10351
    while( $vtz = $vcalendar->getComponent( 'vtimezone' )) {
10352
        $tzid       = $vtz->getProperty('tzid');
10353
        $alltzdates = array();
10354
        while ( $vtzc = $vtz->getComponent( 'standard' )) {
10355
            $newtzdates = expandTimezoneDates($vtzc);
10356
            $alltzdates = array_merge($alltzdates, $newtzdates);
10357
        }
10358
        while ( $vtzc = $vtz->getComponent( 'daylight' )) {
10359
            $newtzdates = expandTimezoneDates($vtzc);
10360
            $alltzdates = array_merge($alltzdates, $newtzdates);
10361
        }
10362
        sort($alltzdates);
10363
        $timezonedata[$tzid] = $alltzdates;
10364
    }
10365
    return $timezonedata;
10366
}
10367
/**
10368
 * Returns an array containing time zone data from vtimezone standard/daylight instances
10369
 *
10370
 * @param object $vtzc, an iCalcreator calendar standard/daylight instance
10371
 * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname)
10372
 *
10373
 */
10374
function expandTimezoneDates($vtzc) {
10375
    $tzdates = array();
10376
    // prepare time zone "description" to attach to each change
10377
    $tzbefore = array();
10378
    $tzbefore['offsetHis']  = $vtzc->getProperty('tzoffsetfrom') ;
10379
    $tzbefore['offsetSec'] = iCalUtilityFunctions::_tz2offset($tzbefore['offsetHis']);
10380
    if(( '-' != substr( (string) $tzbefore['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzbefore['offsetSec'], 0, 1 )))
10381
      $tzbefore['offsetSec'] = '+'.$tzbefore['offsetSec'];
10382
    $tzafter = array();
10383
    $tzafter['offsetHis']   = $vtzc->getProperty('tzoffsetto') ;
10384
    $tzafter['offsetSec']  = iCalUtilityFunctions::_tz2offset($tzafter['offsetHis']);
10385
    if(( '-' != substr( (string) $tzafter['offsetSec'], 0, 1 )) && ( '+' != substr( (string) $tzafter['offsetSec'], 0, 1 )))
10386
      $tzafter['offsetSec'] = '+'.$tzafter['offsetSec'];
10387
    if( FALSE === ( $tzafter['tzname'] = $vtzc->getProperty('tzname')))
10388
      $tzafter['tzname'] = $tzafter['offsetHis'];
10389
    // find out where to start from
10390
    $dtstart = $vtzc->getProperty('dtstart');
10391
    $dtstarttimestamp = mktime(
10392
            $dtstart['hour'],
10393
            $dtstart['min'],
10394
            $dtstart['sec'],
10395
            $dtstart['month'],
10396
            $dtstart['day'],
10397
            $dtstart['year']
10398
            ) ;
10399
    if( !isset( $dtstart['unparsedtext'] )) // ??
10400
      $dtstart['unparsedtext'] = sprintf( '%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec'] );
10401
    if ( $dtstarttimestamp == 0 ) {
10402
        // it seems that the dtstart string may not have parsed correctly
10403
        // let's set a timestamp starting from 1902, using the time part of the original string
10404
        // so that the time will change at the right time of day
10405
        // at worst we'll get midnight again
10406
        $origdtstartsplit = explode('T',$dtstart['unparsedtext']) ;
10407
        $dtstarttimestamp = strtotime("19020101",0);
10408
        $dtstarttimestamp = strtotime($origdtstartsplit[1],$dtstarttimestamp);
10409
    }
10410
    // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp
10411
    $diff  = -1 * $tzbefore['offsetSec'];
10412
    $dtstarttimestamp += $diff;
10413
                // add this (start) change to the array of changes
10414
    $tzdates[] = array(
10415
        'timestamp' => $dtstarttimestamp,
10416
        'tzbefore'  => $tzbefore,
10417
        'tzafter'   => $tzafter
10418
        );
10419
    $datearray = getdate($dtstarttimestamp);
10420
    // save original array to use time parts, because strtotime (used below) apparently loses the time
10421
    $changetime = $datearray ;
10422
    // generate dates according to an RRULE line
10423
    $rrule = $vtzc->getProperty('rrule') ;
10424
    if ( is_array($rrule) ) {
10425
        if ( $rrule['FREQ'] == 'YEARLY' ) {
10426
            // calculate transition dates starting from DTSTART
10427
            $offsetchangetimestamp = $dtstarttimestamp;
10428
            // calculate transition dates until 10 years in the future
10429
            $stoptimestamp = strtotime("+10 year",time());
10430
            // if UNTIL is set, calculate until then (however far ahead)
10431
            if ( isset( $rrule['UNTIL'] ) && ( $rrule['UNTIL'] != '' )) {
10432
                $stoptimestamp = mktime(
10433
                    $rrule['UNTIL']['hour'],
10434
                    $rrule['UNTIL']['min'],
10435
                    $rrule['UNTIL']['sec'],
10436
                    $rrule['UNTIL']['month'],
10437
                    $rrule['UNTIL']['day'],
10438
                    $rrule['UNTIL']['year']
10439
                    ) ;
10440
            }
10441
            $count = 0 ;
10442
            $stopcount = isset( $rrule['COUNT'] ) ? $rrule['COUNT'] : 0 ;
10443
            $daynames = array(
10444
                        'SU' => 'Sunday',
10445
                        'MO' => 'Monday',
10446
                        'TU' => 'Tuesday',
10447
                        'WE' => 'Wednesday',
10448
                        'TH' => 'Thursday',
10449
                        'FR' => 'Friday',
10450
                        'SA' => 'Saturday'
10451
                        );
10452
            // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates
10453
            while ( $offsetchangetimestamp < $stoptimestamp && ( $stopcount == 0 || $count < $stopcount ) ) {
10454
                // break up the timestamp into its parts
10455
                $datearray = getdate($offsetchangetimestamp);
10456
                if ( isset( $rrule['BYMONTH'] ) && ( $rrule['BYMONTH'] != 0 )) {
10457
                    // set the month
10458
                    $datearray['mon'] = $rrule['BYMONTH'] ;
10459
                }
10460
                if ( isset( $rrule['BYMONTHDAY'] ) && ( $rrule['BYMONTHDAY'] != 0 )) {
10461
                    // set specific day of month
10462
                    $datearray['mday']  = $rrule['BYMONTHDAY'];
10463
                } elseif ( is_array($rrule['BYDAY']) ) {
10464
                    // find the Xth WKDAY in the month
10465
                    // the starting point for this process is the first of the month set above
10466
                    $datearray['mday'] = 1 ;
10467
                    // turn $datearray as it is now back into a timestamp
10468
                    $offsetchangetimestamp = mktime(
10469
                        $datearray['hours'],
10470
                        $datearray['minutes'],
10471
                        $datearray['seconds'],
10472
                        $datearray['mon'],
10473
                        $datearray['mday'],
10474
                        $datearray['year']
10475
                            );
10476
                    if ($rrule['BYDAY'][0] > 0) {
10477
                        // to find Xth WKDAY in month, we find last WKDAY in month before
10478
                        // we do that by finding first WKDAY in this month and going back one week
10479
                        // then we add X weeks (below)
10480
                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
10481
                        $offsetchangetimestamp = strtotime("-1 week",$offsetchangetimestamp);
10482
                    } else {
10483
                        // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month
10484
                        // we do that by going forward one month and going to WKDAY there
10485
                        // then we subtract X weeks (below)
10486
                        $offsetchangetimestamp = strtotime("+1 month",$offsetchangetimestamp);
10487
                        $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']],$offsetchangetimestamp);
10488
                    }
10489
                    // now move forward or back the appropriate number of weeks, into the month we want
10490
                    $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week",$offsetchangetimestamp);
10491
                    $datearray = getdate($offsetchangetimestamp);
10492
                }
10493
                // convert the date parts back into a timestamp, setting the time parts according to the
10494
                // original time data which we stored
10495
                $offsetchangetimestamp = mktime(
10496
                    $changetime['hours'],
10497
                    $changetime['minutes'],
10498
                    $changetime['seconds'] + $diff,
10499
                    $datearray['mon'],
10500
                    $datearray['mday'],
10501
                    $datearray['year']
10502
                        );
10503
                // add this change to the array of changes
10504
                $tzdates[] = array(
10505
                    'timestamp' => $offsetchangetimestamp,
10506
                    'tzbefore'  => $tzbefore,
10507
                    'tzafter'   => $tzafter
10508
                    );
10509
                // update counters (timestamp and count)
10510
                $offsetchangetimestamp = strtotime("+" . (( isset( $rrule['INTERVAL'] ) && ( $rrule['INTERVAL'] != 0 )) ? $rrule['INTERVAL'] : 1 ) . " year",$offsetchangetimestamp);
10511
                $count += 1 ;
10512
            }
10513
        }
10514
    }
10515
    // generate dates according to RDATE lines
10516
    while ($rdates = $vtzc->getProperty('rdate')) {
10517
        if ( is_array($rdates) ) {
10518

    
10519
            foreach ( $rdates as $rdate ) {
10520
                // convert the explicit change date to a timestamp
10521
                $offsetchangetimestamp = mktime(
10522
                        $rdate['hour'],
10523
                        $rdate['min'],
10524
                        $rdate['sec'] + $diff,
10525
                        $rdate['month'],
10526
                        $rdate['day'],
10527
                        $rdate['year']
10528
                        ) ;
10529
                // add this change to the array of changes
10530
                $tzdates[] = array(
10531
                    'timestamp' => $offsetchangetimestamp,
10532
                    'tzbefore'  => $tzbefore,
10533
                    'tzafter'   => $tzafter
10534
                    );
10535
            }
10536
        }
10537
    }
10538
    return $tzdates;
10539
}
10540
?>