Projet

Général

Profil

Paste
Télécharger (86,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / libraries / iCalcreator-2.22.1 / lib / vcalendar.class.php @ 9525582e

1
<?php
2
/*********************************************************************************/
3
/**
4
 *
5
 * iCalcreator, a PHP rfc2445/rfc5545 solution.
6
 *
7
 * @copyright Copyright (c) 2007-2015 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
8
 * @link      http://kigkonsult.se/iCalcreator/index.php
9
 * @license   http://kigkonsult.se/downloads/dl.php?f=LGPL
10
 * @package   iCalcreator
11
 * @version   2.22
12
 */
13
/**
14
 * This library is free software; you can redistribute it and/or
15
 * modify it under the terms of the GNU Lesser General Public
16
 * License as published by the Free Software Foundation; either
17
 * version 2.1 of the License, or (at your option) any later version.
18
 *
19
 * This library is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22
 * Lesser General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public
25
 * License along with this library; if not, write to the Free Software
26
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27
 */
28
/*********************************************************************************/
29
/**
30
 * vcalendar class
31
 *
32
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
33
 * @since 2.21.11 - 2015-03-31
34
 */
35
class vcalendar extends iCalBase {
36
/**
37
 * @var array $calscale  calendar property variable
38
 * @var array $method    calendar property variable
39
 * @var array $prodid    calendar property variable
40
 * @var array $version   calendar property variable
41
 * @access private
42
 */
43
  private $calscale;
44
  private $method;
45
  private $prodid;
46
  private $version;
47
/**
48
 * @var array $directory  calendar config variable
49
 * @var array $filename   calendar config variable
50
 * @var array $url        calendar config variable
51
 * @access private
52
 */
53
  private $directory;
54
  private $filename;
55
  private $url;
56
/**
57
 * redirect headers
58
 *
59
 * @var array $headers
60
 * @access private
61
 * @static
62
 */
63
  private static $headers = array( 'Content-Encoding: gzip',
64
                                   'Vary: *',
65
                                   'Content-Length: %s',
66
                                   'Content-Type: application/calendar+xml; charset=utf-8',
67
                                   'Content-Type: text/calendar; charset=utf-8',
68
                                   'Content-Disposition: attachment; filename="%s"',
69
                                   'Content-Disposition: inline; filename="%s"',
70
                                   'Cache-Control: max-age=10',
71
                                 );
72
/**
73
 * constructor for calendar object
74
 *
75
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
76
 * @since 2.21.15 - 2015-04-04
77
 * @param array $config
78
 * @uses vcalendar::_makeVersion()
79
 * @uses vcalendar::$calscale
80
 * @uses vcalendar::$method
81
 * @uses vcalendar::_makeUnique_id()
82
 * @uses vcalendar::$prodid
83
 * @uses vcalendar::$xprop
84
 * @uses vcalendar::$language
85
 * @uses vcalendar::$directory
86
 * @uses vcalendar::$filename
87
 * @uses vcalendar::$url
88
 * @uses vcalendar::$dtzid
89
 * @uses vcalendar::setConfig()
90
 * @uses vcalendar::$xcaldecl
91
 * @uses vcalendar::$components
92
 */
93
  function vcalendar ( $config = array()) {
94
    $this->_makeVersion();
95
    $this->calscale   = null;
96
    $this->method     = null;
97
    $this->_makeUnique_id();
98
    $this->prodid     = null;
99
    $this->xprop      = array();
100
    $this->language   = null;
101
    $this->directory  = '.';
102
    $this->filename   = null;
103
    $this->url        = null;
104
    $this->dtzid      = null;
105
/**
106
 *   language = <Text identifying a language, as defined in [RFC 1766]>
107
 */
108
    if( defined( 'ICAL_LANG' ) && !isset( $config['language'] ))
109
                                          $config['language']   = ICAL_LANG;
110
    if( !isset( $config['allowEmpty'] ))  $config['allowEmpty'] = TRUE;
111
    if( !isset( $config['nl'] ))          $config['nl']         = "\r\n";
112
    if( !isset( $config['format'] ))      $config['format']     = 'iCal';
113
    if( !isset( $config['delimiter'] ))   $config['delimiter']  = DIRECTORY_SEPARATOR;
114
    $this->setConfig( $config );
115
    $this->xcaldecl   = array();
116
    $this->components = array();
117
  }
118
/**
119
 * return iCalcreator version number
120
 *
121
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
122
 * @since 2.18.5 - 2013-08-29
123
 * @uses ICALCREATOR_VERSION
124
 * @return string
125
 */
126
  public static function iCalcreatorVersion() {
127
    return trim( substr( ICALCREATOR_VERSION, strpos( ICALCREATOR_VERSION, ' ' )));
128
  }
129
/*********************************************************************************/
130
/**
131
 * Property Name: CALSCALE
132
 */
133
/**
134
 * creates formatted output for calendar property calscale
135
 *
136
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
137
 * @since 2.10.16 - 2011-10-28
138
 * @uses vcalendar::$calscale
139
 * @uses vcalendar::$format
140
 * @uses vcalendar::$nl
141
 * @return string
142
 */
143
  function createCalscale() {
144
    if( empty( $this->calscale )) return FALSE;
145
    switch( $this->format ) {
146
      case 'xcal':
147
        return $this->nl.' calscale="'.$this->calscale.'"';
148
        break;
149
      default:
150
        return 'CALSCALE:'.$this->calscale.$this->nl;
151
        break;
152
    }
153
  }
154
/**
155
 * set calendar property calscale
156
 *
157
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
158
 * @since 2.4.8 - 2008-10-21
159
 * @param string $value
160
 * @uses vcalendar::$calscale
161
 * @return void
162
 */
163
  function setCalscale( $value ) {
164
    if( empty( $value )) return FALSE;
165
    $this->calscale = $value;
166
  }
167
/*********************************************************************************/
168
/**
169
 * Property Name: METHOD
170
 */
171
/**
172
 * creates formatted output for calendar property method
173
 *
174
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
175
 * @since 2.10.16 - 2011-10-28
176
 * @uses vcalendar::$method
177
 * @uses vcalendar::$format
178
 * @uses vcalendar::$nl
179
 * @return string
180
 */
181
  function createMethod() {
182
    if( empty( $this->method )) return FALSE;
183
    switch( $this->format ) {
184
      case 'xcal':
185
        return $this->nl.' method="'.$this->method.'"';
186
        break;
187
      default:
188
        return 'METHOD:'.$this->method.$this->nl;
189
        break;
190
    }
191
  }
192
/**
193
 * set calendar property method
194
 *
195
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
196
 * @since 2.4.8 - 2008-20-23
197
 * @param string $value
198
 * @uses vcalendar::$method
199
 * @return bool
200
 */
201
  function setMethod( $value ) {
202
    if( empty( $value )) return FALSE;
203
    $this->method = $value;
204
    return TRUE;
205
  }
206
/*********************************************************************************/
207
/**
208
 * Property Name: PRODID
209
 *
210
 */
211
/**
212
 * creates formatted output for calendar property prodid
213
 *
214
 * @copyright copyright (c) 2007-2013 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
215
 * @license   http://kigkonsult.se/downloads/dl.php?f=LGPL
216
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
217
 * @since 2.21.11 - 2015-03-31
218
 * @uses vcalendar::$prodid
219
 * @uses vcalendar::_makeProdid()
220
 * @uses vcalendar::$format
221
 * @uses vcalendar::$nl
222
 * @uses vcalendar::_createElement()
223
 * @return string
224
 */
225
  function createProdid() {
226
    if( !isset( $this->prodid ))
227
      $this->_makeProdid();
228
    switch( $this->format ) {
229
      case 'xcal':
230
        return $this->nl.' prodid="'.$this->prodid.'"';
231
        break;
232
      default:
233
        return $this->_createElement( 'PRODID', '', $this->prodid );
234
        break;
235
    }
236
  }
237
/**
238
 * make default value for calendar prodid, do NOT alter or remove this method or invoke of this method
239
 *
240
 * @copyright copyright (c) 2007-2013 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
241
 * @license   http://kigkonsult.se/downloads/dl.php?f=LGPL
242
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
243
 * @since 2.6.8 - 2009-12-30
244
 * @uses vcalendar::$prodid
245
 * @uses vcalendar::$unique_id
246
 * @uses ICALCREATOR_VERSION
247
 * @uses vcalendar::$language
248
 * @return void
249
 */
250
  function _makeProdid() {
251
    $this->prodid  = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language );
252
  }
253
/**
254
 * Conformance: The property MUST be specified once in an iCalendar object.
255
 * Description: The vendor of the implementation SHOULD assure that this
256
 * is a globally unique identifier; using some technique such as an FPI
257
 * value, as defined in [ISO 9070].
258
 */
259
/**
260
 * make default unique_id for calendar prodid
261
 *
262
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
263
 * @since 0.3.0 - 2006-08-10
264
 * @uses vcalendar::$unique_id
265
 * @return void
266
 */
267
  function _makeUnique_id() {
268
    $this->unique_id  = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost';
269
  }
270
/*********************************************************************************/
271
/**
272
 * Property Name: VERSION
273
 *
274
 * Description: A value of "2.0" corresponds to this memo.
275
 */
276
/**
277
 * creates formatted output for calendar property version
278

279
 *
280
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
281
 * @since 2.10.16 - 2011-10-28
282
 * @uses vcalendar::$version
283
 * @uses vcalendar::$format
284
 * @uses vcalendar::$nl
285
 * @return string
286
 */
287
  function createVersion() {
288
    if( empty( $this->version ))
289
      $this->_makeVersion();
290
    switch( $this->format ) {
291
      case 'xcal':
292
        return $this->nl.' version="'.$this->version.'"';
293
        break;
294
      default:
295
        return 'VERSION:'.$this->version.$this->nl;
296
        break;
297
    }
298
  }
299
/**
300
 * set default calendar version
301
 *
302
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
303
 * @since 0.3.0 - 2006-08-10
304
 * @uses vcalendar::$version
305
 * @return void
306
 */
307
  function _makeVersion() {
308
    $this->version = '2.0';
309
  }
310
/**
311
 * set calendar version
312
 *
313
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
314
 * @since 2.4.8 - 2008-10-23
315
 * @param string $value
316
 * @uses vcalendar::$version
317
 * @return void
318
 */
319
  function setVersion( $value ) {
320
    if( empty( $value )) return FALSE;
321
    $this->version = $value;
322
    return TRUE;
323
  }
324
/*********************************************************************************/
325
/*********************************************************************************/
326
/**
327
 * delete calendar property value
328
 *
329
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
330
 * @since 2.8.8 - 2011-03-15
331
 * @param mixed $propName  bool FALSE => X-property
332
 * @param int   $propix    specific property in case of multiply occurences
333
 * @uses vcalendar::$propdelix
334
 * @uses vcalendar::$calscale
335
 * @uses vcalendar::$method
336
 * @uses vcalendar::$xprop
337
 * @return bool, if successfull delete
338
 */
339
  function deleteProperty( $propName=FALSE, $propix=FALSE ) {
340
    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
341
    if( !$propix )
342
      $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1;
343
    $this->propdelix[$propName] = --$propix;
344
    $return = FALSE;
345
    switch( $propName ) {
346
      case 'CALSCALE':
347
        if( isset( $this->calscale )) {
348
          $this->calscale = null;
349
          $return = TRUE;
350
        }
351
        break;
352
      case 'METHOD':
353
        if( isset( $this->method )) {
354
          $this->method   = null;
355
          $return = TRUE;
356
        }
357
        break;
358
      default:
359
        $reduced = array();
360
        if( $propName != 'X-PROP' ) {
361
          if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; }
362
          foreach( $this->xprop as $k => $a ) {
363
            if(( $k != $propName ) && !empty( $a ))
364
              $reduced[$k] = $a;
365
          }
366
        }
367
        else {
368
          if( count( $this->xprop ) <= $propix )  return FALSE;
369
          $xpropno = 0;
370
          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
371
            if( $propix != $xpropno )
372
              $reduced[$xpropkey] = $xpropvalue;
373
            $xpropno++;
374
          }
375
        }
376
        $this->xprop = $reduced;
377
        if( empty( $this->xprop )) {
378
          unset( $this->propdelix[$propName] );
379
          return FALSE;
380
        }
381
        return TRUE;
382
    }
383
    return $return;
384
  }
385
/**
386
 * get calendar property value/params
387
 *
388
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
389
 * @since 2.21.09 - 2015-03-29
390
 * @param string $propName
391
 * @param int    $propix    specific property in case of multiply occurences
392
 * @param bool   $inclParam
393
 * @uses vcalendar::$propix
394
 * @uses vcalendar::$components
395
 * @uses calendarComponent::$objName
396
 * @uses iCalUtilityFunctions::$vComps
397
 * @uses iCalUtilityFunctions::$mProps1
398
 * @uses calendarComponent::_getProperties()
399
 * @uses calendarComponent::getProperty()
400
 * @uses iCalUtilityFunctions::_geo2str2()
401
 * @uses iCalUtilityFunctions::$geoLatFmt
402
 * @uses iCalUtilityFunctions::$geoLongFmt
403
 * @uses iCalUtilityFunctions::$fmt
404
 * @uses vcalendar::$calscale
405
 * @uses vcalendar::$method
406
 * @uses vcalendar::$prodid
407
 * @uses vcalendar::_makeProdid()
408
 * @uses vcalendar::$version
409
 * @uses vcalendar::$xprop
410
 * @return mixed
411
 */
412
  function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) {
413
    $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP';
414
    if( 'X-PROP' == $propName ) {
415
      if( empty( $propix ))
416
        $propix  = ( isset( $this->propix[$propName] )) ? $this->propix[$propName] + 2 : 1;
417
      $this->propix[$propName] = --$propix;
418
    }
419
    switch( $propName ) {
420
      case 'ATTENDEE':
421
      case 'CATEGORIES':
422
      case 'CONTACT':
423
      case 'DTSTART':
424
      case 'GEOLOCATION':
425
      case 'LOCATION':
426
      case 'ORGANIZER':
427
      case 'PRIORITY':
428
      case 'RESOURCES':
429
      case 'STATUS':
430
      case 'SUMMARY':
431
      case 'RECURRENCE-ID-UID':
432
      case 'RELATED-TO':
433
      case 'R-UID':
434
      case 'UID':
435
      case 'URL':
436
        $output  = array();
437
        foreach ( $this->components as $cix => $component) {
438
          if( !in_array( $component->objName, iCalUtilityFunctions::$vComps ))
439
            continue;
440
          if( in_array( $propName, iCalUtilityFunctions::$mProps1 )) {
441
            $component->_getProperties( $propName, $output );
442
            continue;
443
          }
444
          elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) {
445
            if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' )))
446
              $content = $component->getProperty( 'UID' );
447
          }
448
          elseif( 'GEOLOCATION' == $propName ) {
449
            $content = ( FALSE === ( $loc = $component->getProperty( 'LOCATION' ))) ? '' : $loc.' ';
450
            if( FALSE === ( $geo = $component->getProperty( 'GEO' )))
451
              continue;
452
            $content .= iCalUtilityFunctions::_geo2str2( $geo['latitude'],  iCalUtilityFunctions::$geoLatFmt ).
453
                        iCalUtilityFunctions::_geo2str2( $geo['longitude'], iCalUtilityFunctions::$geoLongFmt ).'/';
454
          }
455
          elseif( FALSE === ( $content = $component->getProperty( $propName )))
456
            continue;
457
          if(( FALSE === $content ) || empty( $content ))
458
            continue;
459
          elseif( is_array( $content )) {
460
            if( isset( $content['year'] )) {
461
              $key  = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $content['year'], (int) $content['month'], (int) $content['day'] );
462
              if( !isset( $output[$key] ))
463
                $output[$key] = 1;
464
              else
465
                $output[$key] += 1;
466
            }
467
            else {
468
              foreach( $content as $partValue => $partCount ) {
469
                if( !isset( $output[$partValue] ))
470
                  $output[$partValue] = $partCount;
471
                else
472
                  $output[$partValue] += $partCount;
473
              }
474
            }
475
          } // end elseif( is_array( $content )) {
476
          elseif( !isset( $output[$content] ))
477
            $output[$content] = 1;
478
          else
479
            $output[$content] += 1;
480
        } // end foreach ( $this->components as $cix => $component)
481
        if( !empty( $output ))
482
          ksort( $output );
483
        return $output;
484
        break;
485
      case 'CALSCALE':
486
        return ( !empty( $this->calscale )) ? $this->calscale : FALSE;
487
        break;
488
      case 'METHOD':
489
        return ( !empty( $this->method )) ? $this->method : FALSE;
490
        break;
491
      case 'PRODID':
492
        if( empty( $this->prodid ))
493
          $this->_makeProdid();
494
        return $this->prodid;
495
        break;
496
      case 'VERSION':
497
        return ( !empty( $this->version )) ? $this->version : FALSE;
498
        break;
499
      default:
500
        if( $propName != 'X-PROP' ) {
501
          if( !isset( $this->xprop[$propName] )) return FALSE;
502
          return ( $inclParam ) ? array( $propName, $this->xprop[$propName] )
503
                                : array( $propName, $this->xprop[$propName]['value'] );
504
        }
505
        else {
506
          if( empty( $this->xprop )) return FALSE;
507
          $xpropno = 0;
508
          foreach( $this->xprop as $xpropkey => $xpropvalue ) {
509
            if( $propix == $xpropno )
510
              return ( $inclParam ) ? array( $xpropkey, $this->xprop[$xpropkey] )
511
                                    : array( $xpropkey, $this->xprop[$xpropkey]['value'] );
512
            else
513
              $xpropno++;
514
          }
515
          unset( $this->propix[$propName] );
516
          return FALSE; // not found ??
517
        }
518
    }
519
    return FALSE;
520
  }
521
/**
522
 * general vcalendar property setting
523
 *
524
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
525
 * @since 2.2.13 - 2007-11-04
526
 * @param mixed $args variable number of function arguments,
527
 *                    first argument is ALWAYS component name,
528
 *                    second ALWAYS component value!
529
 * @uses vcalendar::setCalscale()
530
 * @uses vcalendar::setMethod()
531
 * @uses vcalendar::setVersion()
532
 * @uses vcalendar::setXprop()
533
 * @return bool
534
 */
535
  function setProperty () {
536
    $numargs    = func_num_args();
537
    if( 1 > $numargs )
538
      return FALSE;
539
    $arglist    = func_get_args();
540
    $arglist[0] = strtoupper( $arglist[0] );
541
    switch( $arglist[0] ) {
542
      case 'CALSCALE':
543
        return $this->setCalscale( $arglist[1] );
544
      case 'METHOD':
545
        return $this->setMethod( $arglist[1] );
546
      case 'VERSION':
547
        return $this->setVersion( $arglist[1] );
548
      default:
549
        if( !isset( $arglist[1] )) $arglist[1] = null;
550
        if( !isset( $arglist[2] )) $arglist[2] = null;
551
        return $this->setXprop( $arglist[0], $arglist[1], $arglist[2] );
552
    }
553
    return FALSE;
554
  }
555
/*********************************************************************************/
556
/**
557
 * get vcalendar config values or * calendar components
558
 *
559
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
560
 * @since 2.11.7 - 2012-01-12
561
 * @param mixed $config
562
 * @uses vcalendar::getConfig()
563
 * @uses vcalendar::$allowEmpty
564
 * @uses vcalendar::$components
565
 * @uses calendarComponent::_getProperties()
566
 * @uses calendarComponent::$objName
567
 * @uses calendarComponent::getProperty()
568
 * @uses calendarComponent::_getConfig()
569
 * @uses vcalendar::$url
570
 * @uses vcalendar::$delimiter
571
 * @uses vcalendar::$directory
572
 * @uses vcalendar::$filename
573
 * @uses vcalendar::$format
574
 * @uses vcalendar::$language
575
 * @uses vcalendar::$nl
576
 * @uses vcalendar::$dtzid
577
 * @uses vcalendar::$unique_id
578
 * @return value
579
 */
580
  function getConfig( $config = FALSE ) {
581
    if( !$config ) {
582
      $return = array();
583
      $return['ALLOWEMPTY']  = $this->getConfig( 'ALLOWEMPTY' );
584
      $return['DELIMITER']   = $this->getConfig( 'DELIMITER' );
585
      $return['DIRECTORY']   = $this->getConfig( 'DIRECTORY' );
586
      $return['FILENAME']    = $this->getConfig( 'FILENAME' );
587
      $return['DIRFILE']     = $this->getConfig( 'DIRFILE' );
588
      $return['FILESIZE']    = $this->getConfig( 'FILESIZE' );
589
      $return['FORMAT']      = $this->getConfig( 'FORMAT' );
590
      if( FALSE !== ( $lang  = $this->getConfig( 'LANGUAGE' )))
591
        $return['LANGUAGE']  = $lang;
592
      $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' );
593
      $return['UNIQUE_ID']   = $this->getConfig( 'UNIQUE_ID' );
594
      if( FALSE !== ( $url   = $this->getConfig( 'URL' )))
595
        $return['URL']       = $url;
596
      $return['TZID']        = $this->getConfig( 'TZID' );
597
      return $return;
598
    }
599
    switch( strtoupper( $config )) {
600
      case 'ALLOWEMPTY':
601
        return $this->allowEmpty;
602
        break;
603
      case 'COMPSINFO':
604
        unset( $this->compix );
605
        $info = array();
606
        foreach( $this->components as $cix => $component ) {
607
          if( empty( $component )) continue;
608
          $info[$cix]['ordno'] = $cix + 1;
609
          $info[$cix]['type']  = $component->objName;
610
          $info[$cix]['uid']   = $component->getProperty( 'uid' );
611
          $info[$cix]['props'] = $component->getConfig( 'propinfo' );
612
          $info[$cix]['sub']   = $component->getConfig( 'compsinfo' );
613
        }
614
        return $info;
615
        break;
616
      case 'DELIMITER':
617
        return $this->delimiter;
618
        break;
619
      case 'DIRECTORY':
620
        if( empty( $this->directory ) && ( '0' != $this->directory ))
621
          $this->directory = '.';
622
        return $this->directory;
623
        break;
624
      case 'DIRFILE':
625
        return $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$this->getConfig( 'filename' );
626
        break;
627
      case 'FILEINFO':
628
        return array( $this->getConfig( 'directory' )
629
                    , $this->getConfig( 'filename' )
630
                    , $this->getConfig( 'filesize' ));
631
        break;
632
      case 'FILENAME':
633
        if( empty( $this->filename ) && ( '0' != $this->filename )) {
634
          if( 'xcal' == $this->format )
635
            $this->filename = date( 'YmdHis' ).'.xml'; // recommended xcs.. .
636
          else
637
            $this->filename = date( 'YmdHis' ).'.ics';
638
        }
639
        return $this->filename;
640
        break;
641
      case 'FILESIZE':
642
        $size    = 0;
643
        if( empty( $this->url )) {
644
          $dirfile = $this->getConfig( 'dirfile' );
645
          if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile ))))
646
            $size = 0;
647
          clearstatcache();
648
        }
649
        return $size;
650
        break;
651
      case 'FORMAT':
652
        return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal';
653
        break;
654
      case 'LANGUAGE':
655
         /* get language for calendar component as defined in [RFC 1766] */
656
        return $this->language;
657
        break;
658
      case 'NL':
659
      case 'NEWLINECHAR':
660
        return $this->nl;
661
        break;
662
      case 'TZID':
663
        return $this->dtzid;
664
        break;
665
      case 'UNIQUE_ID':
666
        return $this->unique_id;
667
        break;
668
      case 'URL':
669
        if( !empty( $this->url ))
670
          return $this->url;
671
        else
672
          return FALSE;
673
        break;
674
    }
675
  }
676
/**
677
 * general vcalendar config setting
678
 *
679
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
680
 * @since 2.20.2 - 2014-05-09
681
 * @param mixed  $config
682
 * @param string $value
683
 * @uses vcalendar::setConfig()
684
 * @uses vcalendar::$allowEmpty
685
 * @uses vcalendar::$components
686
 * @uses vcalendar::$url
687
 * @uses vcalendar::$delimiter
688
 * @uses vcalendar::$directory
689
 * @uses vcalendar::$filename
690
 * @uses vcalendar::$format
691
 * @uses vcalendar::$language
692
 * @uses vcalendar::$nl
693
 * @uses vcalendar::$dtzid
694
 * @uses vcalendar::$unique_id
695
 * @uses vcalendar::_makeProdid()
696
 * @uses vcalendar::$components
697
 * @uses calendarComponent::setConfig()
698
 * @uses calendarComponent::copy()
699
 * @return void
700
 */
701
  function setConfig( $config, $value = FALSE) {
702
    if( is_array( $config )) {
703
      $config  = array_change_key_case( $config, CASE_UPPER );
704
      if( isset( $config['DELIMITER'] )) {
705
        if( FALSE === $this->setConfig( 'DELIMITER', $config['DELIMITER'] ))
706
          return FALSE;
707
        unset( $config['DELIMITER'] );
708
      }
709
      if( isset( $config['DIRECTORY'] )) {
710
        if( FALSE === $this->setConfig( 'DIRECTORY', $config['DIRECTORY'] ))
711
          return FALSE;
712
        unset( $config['DIRECTORY'] );
713
      }
714
      foreach( $config as $cKey => $cValue ) {
715
        if( FALSE === $this->setConfig( $cKey, $cValue ))
716
          return FALSE;
717
      }
718
      return TRUE;
719
    }
720
    else
721
    $res    = FALSE;
722
    $config = strtoupper( $config );
723
    switch( $config ) {
724
      case 'ALLOWEMPTY':
725
        $this->allowEmpty = $value;
726
        $subcfg  = array( 'ALLOWEMPTY' => $value );
727
        $res = TRUE;
728
        break;
729
      case 'DELIMITER':
730
        $this->delimiter = $value;
731
        return TRUE;
732
        break;
733
      case 'DIRECTORY':
734
        if( FALSE === ( $value = realpath( rtrim( trim( $value ), $this->delimiter ))))
735
          return FALSE;
736
        else {
737
            /* local directory */
738
          $this->directory = $value;
739
          $this->url       = null;
740
          return TRUE;
741
        }
742
        break;
743
      case 'FILENAME':
744
        $value   = trim( $value );
745
        $dirfile = $this->directory.$this->delimiter.$value;
746
        if( file_exists( $dirfile )) {
747
            /* local file exists */
748
          if( is_readable( $dirfile ) || is_writable( $dirfile )) {
749
            clearstatcache();
750
            $this->filename = $value;
751
            return TRUE;
752
          }
753
          else
754
            return FALSE;
755
        }
756
        elseif( is_readable( $this->directory ) || is_writable( $this->directory )) {
757
            /* read- or writable directory */
758
          clearstatcache();
759
          $this->filename = $value;
760
          return TRUE;
761
        }
762
        else
763
          return FALSE;
764
        break;
765
      case 'FORMAT':
766
        $value   = trim( strtolower( $value ));
767
        if( 'xcal' == $value ) {
768
          $this->format             = 'xcal';
769
          $this->attributeDelimiter = $this->nl;
770
          $this->valueInit          = null;
771
        }
772
        else {
773
          $this->format             = null;
774
          $this->attributeDelimiter = ';';
775
          $this->valueInit          = ':';
776
        }
777
        $subcfg  = array( 'FORMAT' => $value );
778
        $res = TRUE;
779
        break;
780
      case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766]
781
        $value   = trim( $value );
782
        $this->language = $value;
783
        $this->_makeProdid();
784
        $subcfg  = array( 'LANGUAGE' => $value );
785
        $res = TRUE;
786
        break;
787
      case 'NL':
788
      case 'NEWLINECHAR':
789
        $this->nl = $value;
790
        if( 'xcal' == $value ) {
791
          $this->attributeDelimiter = $this->nl;
792
          $this->valueInit          = null;
793
        }
794
        else {
795
          $this->attributeDelimiter = ';';
796
          $this->valueInit          = ':';
797
        }
798
        $subcfg  = array( 'NL' => $value );
799
        $res = TRUE;
800
        break;
801
      case 'TZID':
802
        $this->dtzid = $value;
803
        $subcfg  = array( 'TZID' => $value );
804
        $res = TRUE;
805
        break;
806
      case 'UNIQUE_ID':
807
        $value   = trim( $value );
808
        $this->unique_id = $value;
809
        $this->_makeProdid();
810
        $subcfg  = array( 'UNIQUE_ID' => $value );
811
        $res = TRUE;
812
        break;
813
      case 'URL':
814
            /* remote file - URL */
815
        $value     = str_replace( array( 'HTTP://', 'WEBCAL://', 'webcal://' ), 'http://', trim( $value ));
816
        $value     = str_replace( 'HTTPS://', 'https://', trim( $value ));
817
        if(( 'http://' != substr( $value, 0, 7 )) && ( 'https://' != substr( $value, 0, 8 )))
818
          return FALSE;
819
        $this->directory = '.';
820
        $this->url = $value;
821
        if( '.ics' != strtolower( substr( $value, -4 )))
822
          unset( $this->filename );
823
        else
824
          $this->filename = basename( $value );
825
        return TRUE;
826
        break;
827
      default:  // any unvalid config key.. .
828
        return TRUE;
829
    }
830
    if( !$res ) return FALSE;
831
    if( isset( $subcfg ) && !empty( $this->components )) {
832
      foreach( $subcfg as $cfgkey => $cfgvalue ) {
833
        foreach( $this->components as $cix => $component ) {
834
          $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE );
835
          if( !$res )
836
            break 2;
837
          $this->components[$cix] = $component->copy(); // PHP4 compliant
838
        }
839
      }
840
    }
841
    return $res;
842
  }
843
/*********************************************************************************/
844
/**
845
 * add calendar component to container
846
 *
847
 * alias to setComponent
848
 *
849
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
850
 * @since 1.x.x - 2007-04-24
851
 * @param object $component calendar component
852
 * @uses vcalendar::setComponent()
853
 * @return void
854
 */
855
  function addComponent( $component ) {
856
    $this->setComponent( $component );
857
  }
858
/**
859
 * delete calendar component from container
860
 *
861
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
862
 * @since 2.8.8 - 2011-03-15
863
 * @param mixed $arg1 ordno / component type / component uid
864
 * @param mixed $arg2 optional, ordno if arg1 = component type
865
 * @uses vcalendar::$components
866
 * @uses calendarComponent::$objName
867
 * @uses calendarComponent::getProperty()
868
 * @return void
869
 */
870
  function deleteComponent( $arg1, $arg2=FALSE  ) {
871
    $argType = $index = null;
872
    if ( ctype_digit( (string) $arg1 )) {
873
      $argType = 'INDEX';
874
      $index   = (int) $arg1 - 1;
875
    }
876
    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) {
877
      $argType = strtolower( $arg1 );
878
      $index   = ( !empty( $arg2 ) && ctype_digit( (string) $arg2 )) ? (( int ) $arg2 - 1 ) : 0;
879
    }
880
    $cix1dC = 0;
881
    foreach ( $this->components as $cix => $component) {
882
      if( empty( $component )) continue;
883
      if(( 'INDEX' == $argType ) && ( $index == $cix )) {
884
        unset( $this->components[$cix] );
885
        return TRUE;
886
      }
887
      elseif( $argType == $component->objName ) {
888
        if( $index == $cix1dC ) {
889
          unset( $this->components[$cix] );
890
          return TRUE;
891
        }
892
        $cix1dC++;
893
      }
894
      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) {
895
        unset( $this->components[$cix] );
896
        return TRUE;
897
      }
898
    }
899
    return FALSE;
900
  }
901
/**
902
 * get calendar component from container
903
 *
904
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
905
 * @since 2.21.11 - 2015-03-21
906
 * @param mixed $arg1 optional, ordno/component type/ component uid
907
 * @param mixed $arg2 optional, ordno if arg1 = component type
908
 * @uses vcalendar::$compix
909
 * @uses vcalendar::$components
910
 * @uses iCalUtilityFunctions::$dateProps
911
 * @uses iCalUtilityFunctions::$otherProps
912
 * @uses iCalUtilityFunctions::$mProps1
913
 * @uses calendarComponent::copy()
914
 * @uses calendarComponent::$objName
915
 * @uses calendarComponent::_getProperties()
916
 * @uses calendarComponent::getProperty()
917
 * @uses iCalUtilityFunctions::$fmt
918
 * @return object
919
 */
920
  function getComponent( $arg1=FALSE, $arg2=FALSE ) {
921
    $index = $argType = null;
922
    if ( !$arg1 ) { // first or next in component chain
923
      $argType = 'INDEX';
924
      $index   = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1;
925
    }
926
    elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
927
      $arg2  = implode( '-', array_keys( $arg1 ));
928
      $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1;
929
    }
930
    elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain
931
      $argType = 'INDEX';
932
      $index   = (int) $arg1;
933
      unset( $this->compix );
934
    }
935
    elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name
936
      unset( $this->compix['INDEX'] );
937
      $argType = strtolower( $arg1 );
938
      if( !$arg2 )
939
        $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1;
940
      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
941
        $index = (int) $arg2;
942
    }
943
    elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument
944
      if( !$arg2 )
945
        $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1;
946
      elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 ))
947
        $index = (int) $arg2;
948
    }
949
    if( isset( $index ))
950
      $index  -= 1;
951
    $ckeys = array_keys( $this->components );
952
    if( !empty( $index) && ( $index > end(  $ckeys )))
953
      return FALSE;
954
    $cix1gC = 0;
955
    foreach ( $this->components as $cix => $component) {
956
      if( empty( $component )) continue;
957
      if(( 'INDEX' == $argType ) && ( $index == $cix ))
958
        return $component->copy();
959
      elseif( $argType == $component->objName ) {
960
        if( $index == $cix1gC )
961
          return $component->copy();
962
        $cix1gC++;
963
      }
964
      elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
965
        $hit  = array();
966
        $arg1 = array_change_key_case( $arg1, CASE_UPPER );
967
        foreach( $arg1 as $pName => $pValue ) {
968
          if( !in_array( $pName, iCalUtilityFunctions::$dateProps ) && !in_array( $pName, iCalUtilityFunctions::$otherProps ))
969
            continue;
970
          if( in_array( $pName, iCalUtilityFunctions::$mProps1 )) { // multiple occurrence
971
            $propValues = array();
972
            $component->_getProperties( $pName, $propValues );
973
            $propValues = array_keys( $propValues );
974
            $hit[] = ( in_array( $pValue, $propValues )) ? TRUE : FALSE;
975
            continue;
976
          } // end   if(.. .// multiple occurrence
977
          if( FALSE === ( $value = $component->getProperty( $pName ))) { // single occurrence
978
            $hit[] = FALSE; // missing property
979
            continue;
980
          }
981
          if( 'SUMMARY' == $pName ) { // exists within (any case)
982
            $hit[] = ( FALSE !== stripos( $value, $pValue )) ? TRUE : FALSE;
983
            continue;
984
          }
985
          if( in_array( $pName, iCalUtilityFunctions::$dateProps )) {
986
            $valuedate = sprintf( iCalUtilityFunctions::$fmt['Ymd'], (int) $value['year'], (int) $value['month'], (int) $value['day'] );
987
            if( 8 < strlen( $pValue )) {
988
              if( isset( $value['hour'] )) {
989
                if( 'T' == substr( $pValue, 8, 1 ))
990
                  $pValue = str_replace( 'T', '', $pValue );
991
                $valuedate .= sprintf( iCalUtilityFunctions::$fmt['His'], (int) $value['hour'], (int) $value['min'], (int) $value['sec'] );
992
              }
993
              else
994
                $pValue = substr( $pValue, 0, 8 );
995
            }
996
            $hit[] = ( $pValue == $valuedate ) ? TRUE : FALSE;
997
            continue;
998
          }
999
          elseif( !is_array( $value ))
1000
            $value = array( $value );
1001
          foreach( $value as $part ) {
1002
            $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part );
1003
            foreach( $part as $subPart ) {
1004
              if( $pValue == $subPart ) {
1005
                $hit[] = TRUE;
1006
                continue 3;
1007
              }
1008
            }
1009
          } // end foreach( $value as $part )
1010
          $hit[] = FALSE; // no hit in property
1011
        } // end  foreach( $arg1 as $pName => $pValue )
1012
        if( in_array( TRUE, $hit )) {
1013
          if( $index == $cix1gC )
1014
            return $component->copy();
1015
          $cix1gC++;
1016
        }
1017
      } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] )
1018
      elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID
1019
        if( $index == $cix1gC )
1020
          return $component->copy();
1021
        $cix1gC++;
1022
      }
1023
    } // end foreach ( $this->components.. .
1024
            /* not found.. . */
1025
    unset( $this->compix );
1026
    return FALSE;
1027
  }
1028
/**
1029
 * create new calendar component, already included within calendar
1030
 *
1031
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1032
 * @since 2.20.4 - 2014-08-24
1033
 * @param string $compType component type
1034
 * @uses vcalendar::$components
1035
 * @return object
1036
 */
1037
  function & newComponent( $compType ) {
1038
    $config = $this->getConfig();
1039
    $keys   = array_keys( $this->components );
1040
    $ix     = ( empty( $keys )) ? 0 : end( $keys) + 1;
1041
    switch( strtoupper( $compType )) {
1042
      case 'EVENT':
1043
      case 'VEVENT':
1044
        $this->components[$ix] = new vevent( $config );
1045
        break;
1046
      case 'TODO':
1047
      case 'VTODO':
1048
        $this->components[$ix] = new vtodo( $config );
1049
        break;
1050
      case 'JOURNAL':
1051
      case 'VJOURNAL':
1052
        $this->components[$ix] = new vjournal( $config );
1053
        break;
1054
      case 'FREEBUSY':
1055
      case 'VFREEBUSY':
1056
        $this->components[$ix] = new vfreebusy( $config );
1057
        break;
1058
      case 'TIMEZONE':
1059
      case 'VTIMEZONE':
1060
        array_unshift( $this->components, new vtimezone( $config ));
1061
        $ix = 0;
1062
        break;
1063
      default:
1064
        return FALSE;
1065
    }
1066
    return $this->components[$ix];
1067
  }
1068
/**
1069
 * select components from calendar on date or selectOption basis
1070
 *
1071
 * Ensure DTSTART is set for every component.
1072
 * No date controls occurs.
1073
 *
1074
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1075
 * @since 2.21.11 - 2015-03-31
1076
 * @param mixed $startY optional,      (int) start Year,  default current Year
1077
 *                                ALT. (obj) start date (datetime)
1078
 *                                ALT. array selecOptions ( *[ <propName> => <uniqueValue> ] )
1079
 * @param mixed $startM optional,      (int) start Month, default current Month
1080
 *                                ALT. (obj) end date (datetime)
1081
 * @param int   $startD optional, start Day,   default current Day
1082
 * @param int   $endY   optional, end   Year,  default $startY
1083
 * @param int   $endM   optional, end   Month, default $startM
1084
 * @param int   $endD   optional, end   Day,   default $startD
1085
 * @param mixed $cType  optional, calendar component type(-s), default FALSE=all else string/array type(-s)
1086
 * @param bool  $flat   optional, FALSE (default) => output : array[Year][Month][Day][]
1087
 *                                TRUE            => output : array[] (ignores split)
1088
 * @param bool  $any    optional, TRUE (default) - select component(-s) that occurs within period
1089
 *                                FALSE          - only component(-s) that starts within period
1090
 * @param bool  $split  optional, TRUE (default) - one component copy every DAY it occurs during the
1091
 *                                                 period (implies flat=FALSE)
1092
 *                                FALSE          - one occurance of component only in output array
1093
 * @uses vcalendar::$components
1094
 * @uses vcalendar::selectComponents2()
1095
 * @uses iCalUtilityFunctions::$vComps
1096
 * @uses calendarComponent::$objName
1097
 * @uses calendarComponent::getProperty()
1098
 * @uses iCaldateTime::factory()
1099
 * @uses iCaldateTime::getTimezoneName()
1100
 * @uses iCaldateTime::getTime()
1101
 * @uses iCalUtilityFunctions::$fmt
1102
 * @uses iCaldateTime::$SCbools
1103
 * @uses iCaldateTime::format()
1104
 * @uses iCalUtilityFunctions::_strDate2arr()
1105
 * @uses iCalUtilityFunctions::_recur2date()
1106
 * @uses iCalUtilityFunctions::_inScope()
1107
 * @uses calendarComponent::copy()
1108
 * @uses calendarComponent::setProperty()
1109
 * @uses iCalUtilityFunctions::$fmt
1110
 * @uses calendarComponent::deleteProperty()
1111
 * @uses iCalUtilityFunctions::_setSortArgs()
1112
 * @uses iCalUtilityFunctions::_cmpfcn()
1113
 * @return array or FALSE
1114
 */
1115
  function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) {
1116
            /* check  if empty calendar */
1117
    if( 0 >= count( $this->components )) return FALSE;
1118
    if( is_array( $startY ))
1119
      return $this->selectComponents2( $startY );
1120
            /* check default dates */
1121
    if( is_a( $startY, 'DateTime' ) && is_a( $startM, 'DateTime' )) {
1122
      $endY      = $startM->format( 'Y' );
1123
      $endM      = $startM->format( 'm' );
1124
      $endD      = $startM->format( 'd' );
1125
      $startD    = $startY->format( 'd' );
1126
      $startM    = $startY->format( 'm' );
1127
      $startY    = $startY->format( 'Y' );
1128
    }
1129
    else {
1130
      if( ! $startY ) $startY = date( 'Y' );
1131
      if( ! $startM ) $startM = date( 'm' );
1132
      if( ! $startD ) $startD = date( 'd' );
1133
      if( ! $endY )   $endY   = $startY;
1134
      if( ! $endM )   $endM   = $startM;
1135
      if( ! $endD )   $endD   = $startD;
1136
    }
1137
// echo "selectComp args={$startY}-{$startM}-{$startD} - {$endY}-{$endM}-{$endD}<br>\n"; $tcnt = 0;// test ###
1138
            /* check component types */
1139
    if( empty( $cType ))
1140
      $cType     = iCalUtilityFunctions::$vComps;
1141
    else {
1142
      if( ! is_array( $cType ))
1143
        $cType   = array( $cType );
1144
      $cType     = array_map( 'strtolower', $cType );
1145
      foreach( $cType as $cix => $theType ) {
1146
        if( !in_array( $theType, iCalUtilityFunctions::$vComps ))
1147
          $cType[$cix] = 'vevent';
1148
      }
1149
      $cType = array_unique( $cType );
1150
    }
1151
    if(( FALSE === $flat ) && ( FALSE === $any )) // invalid combination
1152
      $split = FALSE;
1153
    if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination
1154
      $split = FALSE;
1155
            /* iterate components */
1156
    $result      = array();
1157
    $this->sort( 'UID' );
1158
    $compUIDcmp  = null;
1159
    $exdatelist  = $recurridList = array();
1160
    $intervalP1D = new DateInterval( 'P1D' );
1161
    foreach ( $this->components as $cix => $component ) {
1162
      if( empty( $component )) continue;
1163
            /* deselect unvalid type components */
1164
      if( !in_array( $component->objName, $cType ))
1165
        continue;
1166
      unset( $compStart, $compEnd );
1167
            /* select start from dtstart or due if dtstart is missing */
1168
      $prop = $component->getProperty( 'dtstart', FALSE, TRUE );
1169
      if( empty( $prop ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $prop = $component->getProperty( 'due', FALSE, TRUE ))))
1170
        continue;
1171
      if( empty( $prop ))
1172
        continue;
1173
            /* get UID */
1174
      $compUID   = $component->getProperty( 'UID' );
1175
      if( $compUIDcmp != $compUID ) {
1176
        $compUIDcmp = $compUID;
1177
        $exdatelist = $recurridList = array();
1178
      }
1179
      $recurrid   = FALSE;
1180
// file_put_contents( '/opt/work/iCal/iCalcreator/iCalcreator-2.20.x/log/log.txt', "#$cix".PHP_EOL.var_export( $component, TRUE ).PHP_EOL.PHP_EOL, FILE_APPEND ); // test ###
1181
      $compStart = iCaldateTime::factory( $prop['value'], $prop['params'], $prop['value'] );
1182
      $dtstartTz = $compStart->getTimezoneName();
1183
      if( isset( $prop['params']['VALUE'] ) && ( 'DATE' == $prop['params']['VALUE'] ))
1184
        $compStartHis = '';
1185
      else {
1186
        $his = $compStart->getTime();
1187
        $compStartHis = sprintf( iCalUtilityFunctions::$fmt['His'], (int) $his[0], (int) $his[1], (int) $his[2] );
1188
      }
1189
            /* get end date from dtend/due/duration properties */
1190
      if( FALSE !== ( $prop = $component->getProperty( 'dtend', FALSE, TRUE ))) {
1191
        $compEnd = iCaldateTime::factory( $prop['value'], $prop['params'], $prop['value'], $dtstartTz );
1192
        $compEnd->SCbools[ 'dtendExist'] = TRUE;
1193
      }
1194
      if( empty( $prop ) && ( $component->objName == 'vtodo' ) && ( FALSE !== ( $prop = $component->getProperty( 'due', FALSE, TRUE )))) {
1195
        $compEnd = iCaldateTime::factory( $prop['value'], $prop['params'], $prop['value'], $dtstartTz );
1196
        $compEnd->SCbools[ 'dueExist'] = TRUE;
1197
      }
1198
      if( empty( $prop ) && ( FALSE !== ( $prop = $component->getProperty( 'duration', FALSE, TRUE, TRUE )))) { // in dtend (array) format
1199
        $compEnd = iCaldateTime::factory( $prop['value'], $prop['params'], $prop['value'], $dtstartTz );
1200
        $compEnd->SCbools[ 'durationExist'] = TRUE;
1201
      }
1202
      if( ! empty( $prop ) && ! isset( $prop['value']['hour'] )) {
1203
          /* a DTEND without time part denotes an end of an event that actually ends the day before,
1204
             for an all-day event DTSTART=20071201 DTEND=20071202, taking place 20071201!!! */
1205
        $compEnd->SCbools[ 'endAllDayEvent'] = TRUE;
1206
        $compEnd->modify( '-1 day' );
1207
        $compEnd->setTime( 23, 59, 59 );
1208
      }
1209
      unset( $prop );
1210
      if( empty( $compEnd )) {
1211
        $compDuration = FALSE;
1212
        $compEnd = clone $compStart;
1213
        $compEnd->setTime( 23, 59, 59 );     //  23:59:59 the same day as start
1214
      }
1215
      else {
1216
        if( $compEnd->format( 'Ymd' ) < $compStart->format( 'Ymd' )) { // MUST be after start date!!
1217
          $compEnd = clone $compStart;
1218
          $compEnd->setTime( 23, 59, 59 );   //  23:59:59 the same day as start or ???
1219
        }
1220
        $compDuration = $compStart->diff( $compEnd ); // DateInterval
1221
      }
1222
            /* check recurrence-id (note, a missing sequence is the same as sequence=0 so don't test for sequence), to alter when hit dtstart/recurlist */
1223
      if( FALSE !== ( $prop = $component->getProperty( 'recurrence-id', FALSE, TRUE ))) {
1224
        $recurrid = iCaldateTime::factory( $prop['value'], $prop['params'], $prop['value'], $dtstartTz );
1225
        $rangeSet = ( isset( $prop['params']['RANGE'] ) && ( 'THISANDFUTURE' == $prop['params']['RANGE'] )) ? TRUE : FALSE;
1226
        $recurridList[$recurrid->key] = array( clone $compStart, clone $compEnd, $compDuration, $rangeSet ); // change recur this day to new YmdHis/duration/range
1227
// echo "adding comp no:$cix with date=".$compStart->format(iCalUtilityFunctions::$fmt['YmdHis2e'])." to recurridList id={$recurrid->key}, newDate={$compStart->key}<br>\n"; // test ###
1228
        unset( $prop );
1229
        continue;                         // ignore any other props in the component
1230
      } // end recurrence-id/sequence test
1231
// else echo "comp no:$cix with date=".$compStart->format().", NO recurrence-id<br>\n"; // test ###
1232
      ksort( $recurridList, SORT_STRING );
1233
// echo 'recurridList='.implode(', ', array_keys( $recurridList ))."<br>\n"; // test ###
1234
      $fcnStart  = clone $compStart;
1235
      $fcnStart->setDate((int) $startY, (int) $startM, (int) $startD );
1236
      $fcnStart->setTime( 0, 0, 0 );
1237
      $fcnEnd    = clone $compEnd;
1238
      $fcnEnd->setDate((int) $endY, (int) $endM, (int) $endD );
1239
      $fcnEnd->setTime( 23, 59, 59 );
1240
// echo 'compStart='.$compStart->format().', compEnd'.$compEnd->format(); if($compDuration)echo ', interval='.$compDuration->format( iCalUtilityFunctions::$fmt['durDHis'] ); echo "<br>\n"; $tcnt = 0;// test ###
1241
            /* *************************************************************
1242
               make a list of optional exclude dates for component occurence from exrule and exdate
1243
               *********************************************************** */
1244
      $workStart = clone $compStart;
1245
      $workStart->sub( $compDuration ? $compDuration : $intervalP1D );
1246
      $workEnd   = clone $fcnEnd;
1247
      $workEnd->add(   $compDuration ? $compDuration : $intervalP1D  );
1248
      while( FALSE !== ( $prop = $component->getProperty( 'EXRULE'  ))) {
1249
        $exdatelist2 = array();
1250
        if( isset( $prop['UNTIL']['hour'] )) {                 // convert until date to dtstart timezone
1251
          $until = iCaldateTime::factory( $prop['UNTIL'], array( 'TZID' => 'UTC' ), null, $dtstartTz );
1252
          $until = $until->format();
1253
          iCalUtilityFunctions::_strDate2arr( $until );
1254
          $prop['UNTIL'] = $until;
1255
        }
1256
        iCalUtilityFunctions::_recur2date( $exdatelist2, $prop, $compStart, $workStart, $workEnd );
1257
        foreach( $exdatelist2 as $k => $v )
1258
          $exdatelist[$k.$compStartHis] = $v;                  // point out exact every excluded ocurrence (incl. opt. His)
1259
        unset( $until, $exdatelist2 );
1260
      }
1261
      while( FALSE !== ( $prop = $component->getProperty( 'EXDATE', FALSE, TRUE ))) { // - start check exdate
1262
        foreach( $prop['value'] as $exdate ) {
1263
          $exdate  = iCaldateTime::factory( $exdate, $prop['params'], $exdate, $dtstartTz );
1264
          $exdatelist[$exdate->key] = TRUE;
1265
        } // end - foreach( $exdate as $exdate )
1266
      }  // end - check exdate
1267
      unset( $prop, $exdate );
1268
// echo 'exdatelist='  .implode(', ', array_keys( $exdatelist ))  ."<br>\n"; // test ###
1269
            /* *************************************************************
1270
               select only components within.. .
1271
               *********************************************************** */
1272
      $xRecurrence  = 1;
1273
      if(( ! $any && iCalUtilityFunctions::_inScope( $compStart, $fcnStart, $compStart, $fcnEnd, $compStart->dateFormat )) ||
1274
         (   $any && iCalUtilityFunctions::_inScope( $fcnEnd, $compStart, $fcnStart, $compEnd, $compStart->dateFormat ))) {
1275
            /* add the selected component (WITHIN valid dates) to output array */
1276
        if( $flat ) { // any=true/false, ignores split
1277
          if( !$recurrid )
1278
            $result[$compUID] = $component->copy(); // copy original to output (but not anyone with recurrence-id)
1279
        }
1280
        elseif( $split ) { // split the original component
1281
// echo 'split org.:'.$compStart->format().' < '.$fcnStart->format( 'Ymd His e' )."<br>\n"; // test ###
1282
          if( $compStart->format( iCalUtilityFunctions::$fmt['YmdHis2'] ) < $fcnStart->format( iCalUtilityFunctions::$fmt['YmdHis2'] ))
1283
            $rstart = clone $fcnStart;
1284
          else
1285
            $rstart = clone $compStart;
1286
          if( $compEnd->format(   iCalUtilityFunctions::$fmt['YmdHis2'] ) > $fcnEnd->format( iCalUtilityFunctions::$fmt['YmdHis2'] ))
1287
            $rend   = clone $fcnEnd;
1288
          else
1289
            $rend   = clone $compEnd;
1290
// echo "going to test comp no:$cix, rstart=".$rstart->format( iCalUtilityFunctions::$fmt['YmdHis2e'] )." (key={$rstart->key}), end=".$rend->format( iCalUtilityFunctions::$fmt['YmdHis2e'] )."<br>\n"; // test ###
1291
          if( ! isset( $exdatelist[$rstart->key] )) {     // not excluded in exrule/exdate
1292
            if( isset( $recurridList[$rstart->key] )) {   // change start day to new YmdHis/duration
1293
              $k        = $rstart->key;
1294
// echo "recurridList HIT, key={$k}, recur Date=".$recurridList[$k][0]->key."<br>\n"; // test ###
1295
              $rstart   = clone $recurridList[$k][0];
1296
              $startHis = $rstart->getTime();
1297
              $rend     = clone $rstart;
1298
              if( FALSE !== $recurridList[$k][2] )
1299
                $rend->add( $recurridList[$k][2] );
1300
              elseif( FALSE !== $compDuration )
1301
                $rend->add( $compDuration );
1302
              $endHis   = $rend->getTime();
1303
              unset( $recurridList[$k] );
1304
            }
1305
            else {
1306
              $startHis = $compStart->getTime();
1307
              $endHis   = $compEnd->getTime();
1308
            }
1309
// echo "_____testing comp no:$cix, rstart=".$rstart->format( iCalUtilityFunctions::$fmt['YmdHis2e'] )." (key={$rstart->key}), end=".$rend->format( iCalUtilityFunctions::$fmt['YmdHis2e'] )."<br>\n"; // test ###
1310
            $cnt      = 0;                                       // exclude any recurrence START date, found in exdatelist or recurridList but accept the reccurence-id comp itself
1311
            $occurenceDays = 1 + (int) $rstart->diff( $rend )->format( '%a' );  // count the days (incl start day)
1312
            while( $rstart->format( iCalUtilityFunctions::$fmt['Ymd2'] ) <= $rend->format( iCalUtilityFunctions::$fmt['Ymd2'] )) {
1313
              $cnt += 1;
1314
              if( 1 < $occurenceDays )
1315
                $component->setProperty( 'X-OCCURENCE', sprintf( iCalUtilityFunctions::$fmt['dayOfDays'], $cnt, $occurenceDays ));
1316
              if( 1 < $cnt )
1317
                $rstart->setTime( 0, 0, 0 );
1318
              else {
1319
                $rstart->setTime( $startHis[0], $startHis[1], $startHis[2] );
1320
                $exdatelist[$rstart->key] = $compDuration; // make sure to exclude start day from the recurrence pattern
1321
              }
1322
              $component->setProperty( 'X-CURRENT-DTSTART', $rstart->format( $compStart->dateFormat ));
1323
              if( FALSE !== $compDuration ) {
1324
                $propName = ( isset( $compEnd->SCbools[ 'dueExist'] )) ? 'X-CURRENT-DUE' : 'X-CURRENT-DTEND';
1325
                if( $cnt < $occurenceDays )
1326
                  $rstart->setTime( 23, 59, 59 );
1327
                else
1328
                  $rstart->setTime( $endHis[0], $endHis[1], $endHis[2] );
1329
                $component->setProperty( $propName, $rstart->format( $compEnd->dateFormat ));
1330
              }
1331
              $result[(int)$rstart->format( 'Y' )][(int)$rstart->format( 'm' )][(int)$rstart->format( 'd' )][$compUID] = $component->copy(); // copy to output
1332
              $rstart->add( $intervalP1D );
1333
            } // end while(( $rstart->format( 'Ymd' ) < $rend->format( 'Ymd' ))
1334
            unset( $cnt, $occurenceDays );
1335
          } // end if( ! isset( $exdatelist[$rstart->key] ))
1336
// else echo "skip no:$cix with date=".$compStart->format()."<br>\n"; // test ###
1337
          unset( $rstart, $rend );
1338
        } // end elseif( $split )   -  else use component date
1339
        else { // !$flat && !$split, i.e. no flat array and DTSTART within period
1340
          $tstart = ( isset( $recurridList[$compStart->key] )) ? clone $recurridList[$k][0] : clone $compStart;
1341
// echo "going to test comp no:$cix with checkDate={$compStart->key} with recurridList=".implode(',',array_keys($recurridList)); // test ###
1342
          if( ! $any || ! isset( $exdatelist[$tstart->key] )) {  // exclude any recurrence date, found in exdatelist
1343
// echo " and copied to output<br>\n"; // test ###
1344
            $result[(int)$tstart->format( 'Y' )][(int)$tstart->format( 'm' )][(int)$tstart->format( 'd' )][$compUID] = $component->copy(); // copy to output
1345
          }
1346
          unset( $tstart );
1347
        }
1348
      } // end (dt)start within the period OR occurs within the period
1349
            /* *************************************************************
1350
               if 'any' components, check components with reccurrence rules, removing all excluding dates
1351
               *********************************************************** */
1352
      if( TRUE === $any ) {
1353
        $recurlist = array();
1354
            /* make a list of optional repeating dates for component occurence, rrule, rdate */
1355
        while( FALSE !== ( $prop = $component->getProperty( 'RRULE' ))) {  // get all rrule dates (multiple values allowed)
1356
          $recurlist2 = array();
1357
          if( isset( $prop['UNTIL']['hour'] )) {                           // convert $rrule['UNTIL'] to the same timezone as DTSTART !!
1358
            $until = iCaldateTime::factory( $prop['UNTIL'], array( 'TZID' => 'UTC' ), null, $dtstartTz );
1359
            $until = $until->format();
1360
            iCalUtilityFunctions::_strDate2arr( $until );
1361
            $prop['UNTIL'] = $until;
1362
          }
1363
          iCalUtilityFunctions::_recur2date( $recurlist2, $prop, $compStart, $workStart, $workEnd );
1364
          foreach( $recurlist2 as $recurkey => $recurvalue ) {             // recurkey=Ymd
1365
            $recurkey .= $compStartHis;                                    // add opt His
1366
            if( ! isset( $exdatelist[$recurkey] ))
1367
              $recurlist[$recurkey] = $compDuration;                       // DateInterval or FALSE
1368
          }
1369
          unset( $prop, $until, $recurlist2 );
1370
        }
1371
        $workStart    = clone $fcnStart;
1372
        $workStart->sub( $compDuration ? $compDuration : $intervalP1D );
1373
        $format       = $compStart->dateFormat;
1374
        while( FALSE !== ( $prop = $component->getProperty( 'RDATE', FALSE, TRUE ))) {
1375
          $rdateFmt   = ( isset( $prop['params']['VALUE'] )) ? $prop['params']['VALUE'] : 'DATETIME';
1376
          $params     = $prop['params'];
1377
          $prop       = $prop['value'];
1378
          foreach( $prop as $theRdate ) {
1379
            if( 'PERIOD' == $rdateFmt ) {                  // all days within PERIOD
1380
              $rdate = iCaldateTime::factory( $theRdate[0], $params, $theRdate[0], $dtstartTz );
1381
              if( ! iCalUtilityFunctions::_inScope( $rdate, $workStart, $rdate, $fcnEnd, $format ) || isset( $exdatelist[$rdate->key] ))
1382
                continue;
1383
              if( isset( $theRdate[1]['year'] ))           // date-date period end
1384
                $recurlist[$rdate->key] = $rdate->diff( iCaldateTime::factory( $theRdate[1], $params, $theRdate[1], $dtstartTz ));
1385
              else                                         // period duration
1386
                $recurlist[$rdate->key] = new DateInterval( iCalUtilityFunctions::_duration2str( $theRdate[1] ));
1387
            } // end if( 'PERIOD' == $rdateFmt )
1388
            elseif( 'DATE' == $rdateFmt ) { // single recurrence, date
1389
              $rdate  = iCaldateTime::factory( $theRdate, array_merge( $params, array( 'TZID' => $dtstartTz )), null, $dtstartTz );
1390
              if( iCalUtilityFunctions::_inScope( $rdate, $workStart, $rdate, $fcnEnd, $format ) && ! isset( $exdatelist[$rdate->key] ))
1391
                $recurlist[$rdate->key.$compStartHis] = $compDuration; // set start date for recurrence + DateInterval/FALSE (+opt His)
1392
            } // end DATE
1393
            else { // start DATETIME
1394
              $rdate = iCaldateTime::factory( $theRdate, $params, $theRdate, $dtstartTz );
1395
              if( iCalUtilityFunctions::_inScope( $rdate, $workStart, $rdate, $fcnEnd, $format ) && ! isset( $exdatelist[$rdate->key] ))
1396
                $recurlist[$rdate->key] = $compDuration; // set start datetime for recurrence DateInterval/FALSE
1397
            } // end DATETIME
1398
          } // end foreach( $prop as $theRdate )
1399
        }  // end while( FALSE !== ( $prop = $component->getProperty( 'rdate', FALSE, TRUE )))
1400
        unset( $prop, $workStart, $format, $theRdate, $rdate, $rend );
1401
        foreach( $recurridList as $rKey => $rVal ) { // check for recurrence-id, i.e. alter recur Ymd[His] and duration
1402
          if( isset( $recurlist[$rKey] )) {
1403
            unset( $recurlist[$rKey] );
1404
            $recurlist[$rVal[0]->key] = ( FALSE !== $rVal[2] ) ? $rVal[2] : $compDuration;
1405
// echo "alter recurfrom {$rKey} to {$rVal[0]->key} ";if(FALSE!==$dur)echo " ({$dur->format( '%a days, %h-%i-%s' )})";echo "<br>\n"; // test ###
1406
          }
1407
        }
1408
        ksort( $recurlist, SORT_STRING );
1409
// echo 'recurlist='   .implode(', ', array_keys( $recurlist ))   ."<br>\n"; // test ###
1410
// echo 'recurridList='   .implode(', ', array_keys( $recurridList ))   ."<br>\n"; // test ###
1411
            /* *************************************************************
1412
               output all remaining components in recurlist
1413
               *********************************************************** */
1414
        if( 0 < count( $recurlist )) {
1415
          $component2  = $component->copy();
1416
          $compUID     = $component2->getProperty( 'UID' );
1417
          $workStart   = clone $fcnStart;
1418
          $workStart->sub( $compDuration ? $compDuration : $intervalP1D );
1419
          $YmdOld      = null;
1420
          foreach( $recurlist as $recurkey => $durvalue ) {
1421
            if( $YmdOld == substr( $recurkey, 0, 8 ))                         // skip overlapping recur the same day, i.e. RDATE before RRULE
1422
              continue;
1423
            $YmdOld    = substr( $recurkey, 0, 8 );
1424
            $rstart    = clone $compStart;
1425
            $rstart->setDate((int) substr( $recurkey, 0, 4 ), (int) substr( $recurkey,  4, 2 ), (int) substr( $recurkey, 6,  2 ));
1426
            $rstart->setTime((int) substr( $recurkey, 8, 2 ), (int) substr( $recurkey, 10, 2 ), (int) substr( $recurkey, 12, 2 ));
1427
// echo "recur start=".$rstart->format( iCalUtilityFunctions::$fmt['YmdHis2e'] )."<br>\n"; // test ###;
1428
           /* add recurring components within valid dates to output array, only start date set */
1429
            if( $flat ) {
1430
              if( !isset( $result[$compUID] )) // only one comp
1431
                $result[$compUID] = $component2->copy(); // copy to output
1432
            }
1433
           /* add recurring components within valid dates to output array, split for each day */
1434
            elseif( $split ) {
1435
              $rend    = clone $rstart;
1436
              if( FALSE !== $durvalue )
1437
                $rend->add( $durvalue );
1438
              if( $rend->format( iCalUtilityFunctions::$fmt['Ymd2'] ) > $fcnEnd->format( iCalUtilityFunctions::$fmt['Ymd2'] ))
1439
                $rend  = clone $fcnEnd;
1440
// echo "recur 1={$recurkey}, start=".$rstart->format( iCalUtilityFunctions::$fmt['YmdHis2e'] ).", end=".$rend->format( iCalUtilityFunctions::$fmt['YmdHis2e'] );if($durvalue) echo ", duration=".$durvalue->format( iCalUtilityFunctions::$fmt['durDHis'] );echo "<br>\n"; // test ###
1441
              $xRecurrence += 1;
1442
              $cnt     = 0;
1443
              $occurenceDays = 1 + (int) $rstart->diff( $rend )->format( '%a' );  // count the days (incl start day)
1444
              while( $rstart->format( iCalUtilityFunctions::$fmt['Ymd2'] ) <= $rend->format( iCalUtilityFunctions::$fmt['Ymd2'] )) {    // iterate.. .
1445
                $cnt  += 1;
1446
                if( $rstart->format( iCalUtilityFunctions::$fmt['Ymd2'] ) < $fcnStart->format( iCalUtilityFunctions::$fmt['Ymd2'] )) { // date before dtstart
1447
// echo "recur 3, start=".$rstart->format( 'Y-m-d H:i:s' )." &gt;= fcnStart=".$fcnStart->format( 'Y-m-d H:i:s' )."<br>\n"; // test ###
1448
                  $rstart->add( $intervalP1D );
1449
                  $rstart->setTime( 0, 0, 0 );
1450
                  continue;
1451
                }
1452
                elseif( 2 == $cnt )
1453
                  $rstart->setTime( 0, 0, 0 );
1454
                $component2->setProperty(      'X-RECURRENCE', $xRecurrence );
1455
                if( 1 < $occurenceDays )
1456
                  $component2->setProperty(    'X-OCCURENCE', sprintf( iCalUtilityFunctions::$fmt['dayOfDays'], $cnt, $occurenceDays ));
1457
                else
1458
                  $component2->deleteProperty( 'X-OCCURENCE' );
1459
                $component2->setProperty(      'X-CURRENT-DTSTART', $rstart->format( $compStart->dateFormat ));
1460
                $propName = ( isset( $compEnd->SCbools[ 'dueExist'] )) ? 'X-CURRENT-DUE' : 'X-CURRENT-DTEND';
1461
                if( FALSE !== $durvalue ) {
1462
                  if( $cnt < $occurenceDays )
1463
                    $rstart->setTime( 23, 59, 59 );
1464
                  else {
1465
                    $His    = $rend->getTime();                             // set end time
1466
                    $rstart->setTime( $His[0], $His[1], $His[2] );
1467
                  }
1468
                  $component2->setProperty( $propName, $rstart->format( $compEnd->dateFormat ));
1469
// echo "checking date, (day {$cnt} of {$occurenceDays}), _end_=".$rstart->format( 'Y-m-d H:i:s e' )."<br>"; // test ###;
1470
                }
1471
                else
1472
                  $component2->deleteProperty( $propName );
1473
                $result[(int)$rstart->format( 'Y' )][(int)$rstart->format( 'm' )][(int)$rstart->format( 'd' )][$compUID] = $component2->copy(); // copy to output
1474
                $rstart->add( $intervalP1D );
1475
              } // end while( $rstart->format( 'Ymd' ) <= $rend->format( 'Ymd' ))
1476
              unset( $rstart, $rend );
1477
            } // end elseif( $split )
1478
            elseif( $rstart->format( iCalUtilityFunctions::$fmt['Ymd2'] ) >= $fcnStart->format( iCalUtilityFunctions::$fmt['Ymd2'] )) {
1479
              $xRecurrence += 1;                                            // date within period  //* flat=FALSE && split=FALSE => one comp every recur startdate *//
1480
              $component2->setProperty( 'X-RECURRENCE', $xRecurrence );
1481
              $component2->setProperty( 'X-CURRENT-DTSTART', $rstart->format( $compStart->dateFormat ));
1482
              $propName   = ( isset( $compEnd->SCbools[ 'dueExist'] )) ? 'X-CURRENT-DUE' : 'X-CURRENT-DTEND';
1483
              if( FALSE !== $durvalue ) {
1484
                $rstart->add( $durvalue );
1485
                $component2->setProperty( $propName, $rstart->format( $compEnd->dateFormat ));
1486
              }
1487
              else
1488
                $component2->deleteProperty( $propName );
1489
              $result[(int)$rstart->format( 'Y' )][(int)$rstart->format( 'm' )][(int)$rstart->format( 'd' )][$compUID] = $component2->copy(); // copy to output
1490
            } // end elseif( $rstart >= $fcnStart )
1491
            unset( $rstart );
1492
          } // end foreach( $recurlist as $recurkey => $durvalue )
1493
          unset( $component2, $xRecurrence, $compUID, $workStart, $rstart );
1494
        } // end if( 0 < count( $recurlist ))
1495
      } // end if( TRUE === $any )
1496
      unset( $component );
1497
    } // end foreach ( $this->components as $cix => $component )
1498
    unset( $recurrid, $recurridList, $fcnStart, $fcnEnd, $compStart, $compEnd, $exdatelist, $recurlist ); // clean up
1499
    if( 0 >= count( $result ))
1500
      return FALSE;
1501
    elseif( !$flat ) {
1502
      foreach( $result as $y => $yeararr ) {
1503
        foreach( $yeararr as $m => $montharr ) {
1504
          foreach( $montharr as $d => $dayarr ) {
1505
            if( empty( $result[$y][$m][$d] ))
1506
                unset( $result[$y][$m][$d] );
1507
            else {
1508
              $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index
1509
              if( 1 < count( $result[$y][$m][$d] )) {
1510
                foreach( $result[$y][$m][$d] as & $c )       // sort
1511
                  iCalUtilityFunctions::_setSortArgs( $c );
1512
                usort( $result[$y][$m][$d], array( 'iCalUtilityFunctions', '_cmpfcn' ));
1513
              }
1514
            }
1515
          } // end foreach( $montharr as $d => $dayarr )
1516
          if( empty( $result[$y][$m] ))
1517
              unset( $result[$y][$m] );
1518
          else
1519
            ksort( $result[$y][$m] );
1520
        } // end foreach( $yeararr as $m => $montharr )
1521
        if( empty( $result[$y] ))
1522
            unset( $result[$y] );
1523
        else
1524
          ksort( $result[$y] );
1525
      }// end foreach( $result as $y => $yeararr )
1526
      if( empty( $result ))
1527
          unset( $result );
1528
      else
1529
        ksort( $result );
1530
    } // end elseif( !$flat )
1531
    if( 0 >= count( $result ))
1532
      return FALSE;
1533
    return $result;
1534
  }
1535
/**
1536
 * select components from calendar on based on specific property value(-s)
1537
 *
1538
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1539
 * @since 2.21.11 - 2015-03-21
1540
 * @param array $selectOptions (string) key => (mixed) value, (key=propertyName)
1541
 * @uses vcalendar::$components
1542
 * @uses calendarComponent::$objName
1543
 * @uses iCalUtilityFunctions::$vComps
1544
 * @uses calendarComponent::getProperty()
1545
 * @uses iCalUtilityFunctions::$otherProps
1546
 * @uses calendarComponent::copy()
1547
 * @uses iCalUtilityFunctions::$mProps1
1548
 * @uses calendarComponent::_getProperties()
1549
 * @return array
1550
 */
1551
  function selectComponents2( $selectOptions ) {
1552
//     $output = array();
1553
    $selectOptions     = array_change_key_case( $selectOptions, CASE_UPPER );
1554
    foreach( $this->components as $cix => $component3 ) {
1555
      if( !in_array( $component3->objName, iCalUtilityFunctions::$vComps ))
1556
        continue;
1557
      $uid = $component3->getProperty( 'UID' );
1558
      foreach( $selectOptions as $propName => $pvalue ) {
1559
        if( !in_array( $propName, iCalUtilityFunctions::$otherProps ))
1560
          continue;
1561
        if( !is_array( $pvalue ))
1562
          $pvalue = array( $pvalue );
1563
        if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) {
1564
          $output[$uid][] = $component3->copy();
1565
          continue;
1566
        }
1567
        elseif( in_array( $propName, iCalUtilityFunctions::$mProps1 )) {
1568
          $propValues = array();
1569
          $component3->_getProperties( $propName, $propValues );
1570
          $propValues = array_keys( $propValues );
1571
          foreach( $pvalue as $theValue ) {
1572
            if( in_array( $theValue, $propValues )) { //  && !isset( $output[$uid] )) {
1573
              $output[$uid][] = $component3->copy();
1574
              break;
1575
            }
1576
          }
1577
          continue;
1578
        } // end   elseif( // multiple occurrence?
1579
        elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single occurrence
1580
          continue;
1581
        if( is_array( $d )) {
1582
          foreach( $d as $part ) {
1583
            if( in_array( $part, $pvalue ) && !isset( $output[$uid] ))
1584
              $output[$uid][] = $component3->copy();
1585
          }
1586
        }
1587
        elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) {
1588
          foreach( $pvalue as $pval ) {
1589
            if( FALSE !== stripos( $d, $pval )) {
1590
              $output[$uid][] = $component3->copy();
1591
              break;
1592
            }
1593
          }
1594
        }
1595
        elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] ))
1596
          $output[$uid][] = $component3->copy();
1597
      } // end foreach( $selectOptions as $propName => $pvalue ) {
1598
    } // end foreach( $this->components as $cix => $component3 ) {
1599
    if( !empty( $output )) {
1600
      ksort( $output ); // uid order
1601
      $output2 = array();
1602
      foreach( $output as $uid => $components ) {
1603
        foreach( $components as $component )
1604
          $output2[] = $component;
1605
      }
1606
      $output = $output2;
1607
    }
1608
    return $output;
1609
  }
1610
/**
1611
 * replace calendar component in vcalendar
1612
 *
1613
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1614
 * @since 2.21.11 - 2015-03-21
1615
 * @param object $component calendar component
1616
 * @uses calendarComponent::$objName
1617
 * @uses iCalUtilityFunctions::$vComps
1618
 * @uses vcalendar::setComponent()
1619
 * @uses calendarComponent::getProperty()
1620
 * @return bool
1621
 */
1622
  function replaceComponent( $component  ) {
1623
    if( in_array( $component->objName, iCalUtilityFunctions::$vComps ))
1624
      return $this->setComponent( $component, $component->getProperty( 'UID' ));
1625
    if(( 'vtimezone' != $component->objName ) || ( FALSE === ( $tzid = $component->getProperty( 'TZID' ))))
1626
      return FALSE;
1627
    foreach( $this->components as $cix => $comp ) {
1628
      if( 'vtimezone' != $component->objName )
1629
        continue;
1630
      if( $tzid == $comp->getComponent( 'TZID' )) {
1631
        unset( $component->propix, $component->compix );
1632
        $this->components[$cix] = $component;
1633
        return TRUE;
1634
      }
1635
    }
1636
    return FALSE;
1637
  }
1638
/**
1639
 * add calendar component to calendar
1640
 *
1641
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1642
 * @since 2.21.11 - 2015-03-21
1643
 * @param object $component calendar component
1644
 * @param mixed $arg1 optional, ordno/component type/ component uid
1645
 * @param mixed $arg2 optional, ordno if arg1 = component type
1646
 * @uses calendarComponent::setConfig()
1647
 * @uses vcalendar::getConfig()
1648
 * @uses calendarComponent::$objName
1649
 * @uses calendarComponent::getProperty()
1650
 * @uses vcalendar::$components
1651
 * @uses iCalUtilityFunctions::$mComps
1652
 * @uses calendarComponent::copy()
1653
 * @return bool
1654
 */
1655
  function setComponent( $component, $arg1=FALSE, $arg2=FALSE  ) {
1656
    $component->setConfig( $this->getConfig(), FALSE, TRUE );
1657
    if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) {
1658
            /* make sure dtstamp and uid is set */
1659
      $dummy1 = $component->getProperty( 'dtstamp' );
1660
      $dummy2 = $component->getProperty( 'uid' );
1661
    }
1662
    unset( $component->propix, $component->compix );
1663
    if( !$arg1 ) { // plain insert, last in chain
1664
      $this->components[] = $component->copy();
1665
      return TRUE;
1666
    }
1667
    $argType = $index = null;
1668
    if ( ctype_digit( (string) $arg1 )) { // index insert/replace
1669
      $argType = 'INDEX';
1670
      $index   = (int) $arg1 - 1;
1671
    }
1672
    elseif( in_array( strtolower( $arg1 ), iCalUtilityFunctions::$mComps )) {
1673
      $argType = strtolower( $arg1 );
1674
      $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0;
1675
    }
1676
    // else if arg1 is set, arg1 must be an UID
1677
    $cix1sC = 0;
1678
    foreach ( $this->components as $cix => $component2) {
1679
      if( empty( $component2 )) continue;
1680
      if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace
1681
        $this->components[$cix] = $component->copy();
1682
        return TRUE;
1683
      }
1684
      elseif( $argType == $component2->objName ) { // component Type index insert/replace
1685
        if( $index == $cix1sC ) {
1686
          $this->components[$cix] = $component->copy();
1687
          return TRUE;
1688
        }
1689
        $cix1sC++;
1690
      }
1691
      elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace
1692
        $this->components[$cix] = $component->copy();
1693
        return TRUE;
1694
      }
1695
    }
1696
            /* arg1=index and not found.. . insert at index .. .*/
1697
    if( 'INDEX' == $argType ) {
1698
      $this->components[$index] = $component->copy();
1699
      ksort( $this->components, SORT_NUMERIC );
1700
    }
1701
    else    /* not found.. . insert last in chain anyway .. .*/
1702
      $this->components[] = $component->copy();
1703
    return TRUE;
1704
  }
1705
/**
1706
 * sort iCal compoments
1707
 *
1708
 * ascending sort on properties (if exist) x-current-dtstart, dtstart,
1709
 * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
1710
 * otherwise sorting on specific (argument) property values
1711
 *
1712
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1713
 * @since 2.21.11 - 2015-03-21
1714
 * @param string $sortArg
1715
 * @uses vcalendar::$components
1716
 * @uses iCalUtilityFunctions::$otherProps
1717
 * @uses iCalUtilityFunctions::_setSortArgs()
1718
 * @uses iCalUtilityFunctions::_cmpfcn()
1719
 * @return void
1720
 */
1721
  function sort( $sortArg=FALSE ) {
1722
    if( ! is_array( $this->components ) || ( 2 > count( $this->components )))
1723
      return;
1724
    if( $sortArg ) {
1725
      $sortArg   = strtoupper( $sortArg );
1726
      if( ! in_array( $sortArg, iCalUtilityFunctions::$otherProps ) && ( 'DTSTAMP' != $sortArg ))
1727
        $sortArg = FALSE;
1728
    }
1729
    foreach( $this->components as & $c )
1730
      iCalUtilityFunctions::_setSortArgs( $c, $sortArg );
1731
    usort( $this->components, array( 'iCalUtilityFunctions', '_cmpfcn' ));
1732
  }
1733
/**
1734
 * parse iCal text/file into vcalendar, components, properties and parameters
1735
 *
1736
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1737
 * @since 2.21.12 - 2014-03-10
1738
 * @param mixed    $unparsedtext  strict rfc2445 formatted, single property string or array of property strings
1739
 * @param resource $context       PHP resource context
1740
 * @uses iCalUtilityFunctions::convEolChar()
1741
 * @uses vcalendar::getConfig()
1742
 * @uses vcalendar::$components
1743
 * @uses calendarComponent::copy()
1744
 * @uses vevent::construct()
1745
 * @uses vfreebusy::construct()
1746
 * @uses vjournal::construct()
1747
 * @uses vtodo::construct()
1748
 * @uses vtimezone::construct()
1749
 * @uses vcalendar::$unparsed
1750
 * @uses iCalUtilityFunctions::_splitContent()
1751
 * @uses iCalUtilityFunctions::_strunrep()
1752
 * @uses vcalendar::setProperty()
1753
 * @uses calendarComponent::$unparsed
1754
 * @uses calendarComponent::parse()
1755
 * @return bool FALSE if error occurs during parsing
1756
 */
1757
  function parse( $unparsedtext=FALSE, $context=null ) {
1758
    $nl = $this->getConfig( 'nl' );
1759
    if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) {
1760
            /* directory+filename is set previously via setConfig url or directory+filename  */
1761
      if(   FALSE === ( $file = $this->getConfig( 'url' ))) {
1762
        if( FALSE === ( $file = $this->getConfig( 'dirfile' )))
1763
          return FALSE;                 /* err 1 */
1764
        if( ! is_file( $file ))
1765
          return FALSE;                 /* err 2 */
1766
        if( ! is_readable( $file ))
1767
          return FALSE;                 /* err 3 */
1768
      }
1769
            /* READ FILE */
1770
      if( ! empty( $context ) && filter_var( $file, FILTER_VALIDATE_URL ) &&
1771
             ( FALSE === ( $rows = file_get_contents( $file, FALSE, $context ))))
1772
        return FALSE;                 /* err 6 */
1773
      elseif(  FALSE === ( $rows = file_get_contents( $file )))
1774
        return FALSE;                 /* err 5 */
1775
    } // end if(( FALSE === $unparsedtext ) || empty( $unparsedtext ))
1776
    elseif( is_array( $unparsedtext ))
1777
      $rows =  implode( '\n'.$nl, $unparsedtext );
1778
    else
1779
      $rows = & $unparsedtext;
1780
            /* fix line folding */
1781
    $rows = iCalUtilityFunctions::convEolChar( $rows, $nl );
1782
            /* skip leading (empty/invalid) lines (and remove leading BOM chars etc) */
1783
    foreach( $rows as $lix => $line ) {
1784
      if( FALSE !== stripos( $line, 'BEGIN:VCALENDAR' )) {
1785
        $rows[$lix] = 'BEGIN:VCALENDAR';
1786
        break;
1787
      }
1788
      unset( $rows[$lix] );
1789
    }
1790
    $rcnt = count( $rows );
1791
    if( 3 > $rcnt )                  /* err 10 */
1792
      return FALSE;
1793
            /* skip trailing empty lines and ensure an end row */
1794
    $lix  = array_keys( $rows );
1795
    $lix  = end( $lix );
1796
    while( 3 < $lix ) {
1797
      $tst = trim( $rows[$lix] );
1798
      if(( '\n' == $tst ) || empty( $tst )) {
1799
        unset( $rows[$lix] );
1800
        $lix--;
1801
        continue;
1802
      }
1803
      if( FALSE === stripos( $rows[$lix], 'END:VCALENDAR' ))
1804
        $rows[] = 'END:VCALENDAR';
1805
      else
1806
        $rows[$lix] = 'END:VCALENDAR';
1807
      break;
1808
    }
1809
    $comp    = & $this;
1810
    $calsync = $compsync = 0;
1811
            /* identify components and update unparsed data within component */
1812
    $config = $this->getConfig();
1813
    $endtxt = array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' );
1814
    foreach( $rows as $lix => $line ) {
1815
      if(     'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) {
1816
        $calsync++;
1817
        continue;
1818
      }
1819
      elseif( 'END:VCALENDAR'   == strtoupper( substr( $line, 0, 13 ))) {
1820
        if( 0 < $compsync )
1821
          $this->components[] = $comp->copy();
1822
        $compsync--;
1823
        $calsync--;
1824
        break;
1825
      }
1826
      elseif( 1 != $calsync )
1827
        return FALSE;                 /* err 20 */
1828
      elseif( in_array( strtoupper( substr( $line, 0, 6 )), $endtxt )) {
1829
        $this->components[] = $comp->copy();
1830
        $compsync--;
1831
        continue;
1832
      }
1833
      if(     'BEGIN:VEVENT'    == strtoupper( substr( $line, 0, 12 ))) {
1834
        $comp = new vevent( $config );
1835
        $compsync++;
1836
      }
1837
      elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) {
1838
        $comp = new vfreebusy( $config );
1839
        $compsync++;
1840
      }
1841
      elseif( 'BEGIN:VJOURNAL'  == strtoupper( substr( $line, 0, 14 ))) {
1842
        $comp = new vjournal( $config );
1843
        $compsync++;
1844
      }
1845
      elseif( 'BEGIN:VTODO'     == strtoupper( substr( $line, 0, 11 ))) {
1846
        $comp = new vtodo( $config );
1847
        $compsync++;
1848
      }
1849
      elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) {
1850
        $comp = new vtimezone( $config );
1851
        $compsync++;
1852
      }
1853
      else { /* update component with unparsed data */
1854
        $comp->unparsed[] = $line;
1855
      }
1856
    } // end foreach( $rows as $line )
1857
    unset( $config, $endtxt );
1858
            /* parse data for calendar (this) object */
1859
    if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) {
1860
            /* concatenate property values spread over several lines */
1861
      $propnames = array( 'calscale','method','prodid','version','x-' );
1862
      $proprows  = array();
1863
      for( $i = 0; $i < count( $this->unparsed ); $i++ ) { // concatenate lines
1864
        $line = rtrim( $this->unparsed[$i], $nl );
1865
        while( isset( $this->unparsed[$i+1] ) && !empty( $this->unparsed[$i+1] ) && ( ' ' == $this->unparsed[$i+1]{0} ))
1866
          $line .= rtrim( substr( $this->unparsed[++$i], 1 ), $nl );
1867
        $proprows[] = $line;
1868
      }
1869
      foreach( $proprows as $line ) {
1870
        if( '\n' == substr( $line, -2 ))
1871
          $line = substr( $line, 0, -2 );
1872
            /* get property name */
1873
        $propname  = '';
1874
        $cix       = 0;
1875
        while( FALSE !== ( $char = substr( $line, $cix, 1 ))) {
1876
          if( in_array( $char, array( ':', ';' )))
1877
            break;
1878
          else
1879
            $propname .= $char;
1880
          $cix++;
1881
        }
1882
            /* skip non standard property names */
1883
        if(( 'x-' != strtolower( substr( $propname, 0, 2 ))) && !in_array( strtolower( $propname ), $propnames ))
1884
          continue;
1885
            /* ignore version/prodid properties */
1886
        if( in_array( strtolower( $propname ), array( 'version', 'prodid' )))
1887
          continue;
1888
            /* rest of the line is opt.params and value */
1889
        $line = substr( $line, $cix);
1890
            /* separate attributes from value */
1891
        iCalUtilityFunctions::_splitContent( $line, $propAttr );
1892
            /* update Property */
1893
        if( FALSE !== strpos( $line, ',' )) {
1894
          $content  = array( 0 => '' );
1895
          $cix = $lix = 0;
1896
          while( FALSE !== substr( $line, $lix, 1 )) {
1897
            if(( 0 < $lix ) && ( ',' == $line[$lix] ) && ( "\\" != $line[( $lix - 1 )])) {
1898
              $cix++;
1899
              $content[$cix] = '';
1900
            }
1901
            else
1902
              $content[$cix] .= $line[$lix];
1903
            $lix++;
1904
          }
1905
          if( 1 < count( $content )) {
1906
            foreach( $content as $cix => $contentPart )
1907
              $content[$cix] = iCalUtilityFunctions::_strunrep( $contentPart );
1908
            $this->setProperty( $propname, $content, $propAttr );
1909
            continue;
1910
          }
1911
          else
1912
            $line = reset( $content );
1913
          $line = iCalUtilityFunctions::_strunrep( $line );
1914
        }
1915
        $this->setProperty( $propname, rtrim( $line, "\x00..\x1F" ), $propAttr );
1916
      } // end - foreach( $this->unparsed.. .
1917
    } // end - if( is_array( $this->unparsed.. .
1918
    unset( $unparsedtext, $rows, $this->unparsed, $proprows );
1919
            /* parse Components */
1920
    if( is_array( $this->components ) && ( 0 < count( $this->components ))) {
1921
      $ckeys = array_keys( $this->components );
1922
      foreach( $ckeys as $ckey ) {
1923
        if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) {
1924
          $this->components[$ckey]->parse();
1925
        }
1926
      }
1927
    }
1928
    else
1929
      return FALSE;                   /* err 91 or something.. . */
1930
    return TRUE;
1931
  }
1932
/*********************************************************************************/
1933
/**
1934
 * creates formatted output for calendar object instance
1935
 *
1936
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
1937
 * @since 2.21.07 - 2015-03-31
1938
 * @uses vcalendar::$format
1939
 * @uses vcalendar::$nl
1940
 * @uses vcalendar::createVersion()
1941
 * @uses vcalendar::createProdid()
1942
 * @uses vcalendar::createCalscale()
1943
 * @uses vcalendar::createMethod()
1944
 * @uses vcalendar::createXprop()
1945
 * @uses vcalendar::$components
1946
 * @uses calendarComponent::setConfig()
1947
 * @uses vcalendar::getConfig()
1948
 * @uses calendarComponent::createComponent()
1949
 * @uses vcalendar::$xcaldecl
1950
 * @return string
1951
 */
1952
  function createCalendar() {
1953
    parent::_createFormat();
1954
    $calendarInit = $calendarxCaldecl = $calendarStart = $calendar = '';
1955
    switch( $this->format ) {
1956
      case 'xcal':
1957
        $calendarInit  = '<?xml version="1.0" encoding="UTF-8"?>'.$this->nl.
1958
                         '<!DOCTYPE vcalendar PUBLIC "-//IETF//DTD XCAL/iCalendar XML//EN"'.$this->nl.
1959
                         '"http://www.ietf.org/internet-drafts/draft-ietf-calsch-many-xcal-01.txt"';
1960
        $calendarStart = '>'.$this->nl.'<vcalendar';
1961
        break;
1962
      default:
1963
        $calendarStart = 'BEGIN:VCALENDAR'.$this->nl;
1964
        break;
1965
    }
1966
    $calendarStart .= $this->createVersion();
1967
    $calendarStart .= $this->createProdid();
1968
    $calendarStart .= $this->createCalscale();
1969
    $calendarStart .= $this->createMethod();
1970
    if( 'xcal' == $this->format )
1971
      $calendarStart .= '>'.$this->nl;
1972
    $calendar .= $this->createXprop();
1973
    foreach( $this->components as $component ) {
1974
      if( empty( $component )) continue;
1975
      $component->setConfig( $this->getConfig(), FALSE, TRUE );
1976
      $calendar .= $component->createComponent( $this->xcaldecl );
1977
    }
1978
    if(( 'xcal' == $this->format ) && ( 0 < count( $this->xcaldecl ))) { // xCal only
1979
      $calendarInit .= ' [';
1980
      $old_xcaldecl  = array();
1981
      foreach( $this->xcaldecl as $declix => $declPart ) {
1982
        if(( 0 < count( $old_xcaldecl))    &&
1983
             isset( $declPart['uri'] )     && isset( $declPart['external'] )     &&
1984
             isset( $old_xcaldecl['uri'] ) && isset( $old_xcaldecl['external'] ) &&
1985
           ( in_array( $declPart['uri'],      $old_xcaldecl['uri'] ))            &&
1986
           ( in_array( $declPart['external'], $old_xcaldecl['external'] )))
1987
          continue; // no duplicate uri and ext. references
1988
        if(( 0 < count( $old_xcaldecl))    &&
1989
            !isset( $declPart['uri'] )     && !isset( $declPart['uri'] )         &&
1990
             isset( $declPart['ref'] )     && isset( $old_xcaldecl['ref'] )      &&
1991
           ( in_array( $declPart['ref'],      $old_xcaldecl['ref'] )))
1992
          continue; // no duplicate element declarations
1993
        $calendarxCaldecl .= $this->nl.'<!';
1994
        foreach( $declPart as $declKey => $declValue ) {
1995
          switch( $declKey ) {                    // index
1996
            case 'xmldecl':                       // no 1
1997
              $calendarxCaldecl .= $declValue.' ';
1998
              break;
1999
            case 'uri':                           // no 2
2000
              $calendarxCaldecl .= $declValue.' ';
2001
              $old_xcaldecl['uri'][] = $declValue;
2002
              break;
2003
            case 'ref':                           // no 3
2004
              $calendarxCaldecl .= $declValue.' ';
2005
              $old_xcaldecl['ref'][] = $declValue;
2006
              break;
2007
            case 'external':                      // no 4
2008
              $calendarxCaldecl .= '"'.$declValue.'" ';
2009
              $old_xcaldecl['external'][] = $declValue;
2010
              break;
2011
            case 'type':                          // no 5
2012
              $calendarxCaldecl .= $declValue.' ';
2013
              break;
2014
            case 'type2':                         // no 6
2015
              $calendarxCaldecl .= $declValue;
2016
              break;
2017
          }
2018
        }
2019
        $calendarxCaldecl .= '>';
2020
      }
2021
      $calendarxCaldecl .= $this->nl.']';
2022
    } // end if(( 'xcal'...
2023
    switch( $this->format ) {
2024
      case 'xcal':
2025
        $calendar .= '</vcalendar>'.$this->nl;
2026
        break;
2027
      default:
2028
        $calendar .= 'END:VCALENDAR'.$this->nl;
2029
        break;
2030
    }
2031
    return $calendarInit.$calendarxCaldecl.$calendarStart.$calendar;
2032
  }
2033
/**
2034
 * a HTTP redirect header is sent with created, updated and/or parsed calendar
2035
 *
2036
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2037
 * @since 2.21.5 - 2015-03-29
2038
 * @param bool $utf8Encode
2039
 * @param bool $gzip
2040
 * @param bool $cdType       TRUE : Content-Disposition: attachment... (default), FALSE : ...inline...
2041
 * @uses vcalendar::getConfig()
2042
 * @uses vcalendar::createCalendar()
2043
 * @uses vcalendar::$headers
2044
 * @uses vcalendar::$format
2045
 * @return bool TRUE on success, FALSE on error
2046
 */
2047
  function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE, $cdType=TRUE ) {
2048
    $filename = $this->getConfig( 'filename' );
2049
    $output   = $this->createCalendar();
2050
    if( $utf8Encode )
2051
      $output = utf8_encode( $output );
2052
    $fsize    = null;
2053
    if( $gzip ) {
2054
      $output = gzencode( $output, 9 );
2055
      $fsize  = strlen( $output );
2056
      header( self::$headers[0] );
2057
      header( self::$headers[1] );
2058
    }
2059
    else {
2060
      if( FALSE !== ( $temp = tempnam( sys_get_temp_dir(), 'iCr' ))) {
2061
        if( FALSE !== file_put_contents( $temp, $output ))
2062
          $fsize = @filesize( $temp );
2063
        unlink( $temp );
2064
      }
2065
    }
2066
    if( ! empty( $fsize ))
2067
      header( sprintf( self::$headers[2], $fsize ));
2068
    if( 'xcal' == $this->format )
2069
      header( self::$headers[3] );
2070
    else
2071
      header( self::$headers[4] );
2072
    $cdType = ( $cdType ) ? 5 : 6;
2073
    header( sprintf( self::$headers[$cdType], $filename ));
2074
    header( self::$headers[7] );
2075
    echo $output;
2076
    return TRUE;
2077
  }
2078
/**
2079
 * save content in a file
2080
 *
2081
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2082
 * @since 2.21.5 - 2015-03-29
2083
 * @uses vcalendar::createComponent()
2084
 * @uses vcalendar::getConfig()
2085
 * @return bool TRUE on success, FALSE on error
2086
 */
2087
  function saveCalendar() {
2088
    $output = $this->createCalendar();
2089
    if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
2090
      $dirfile = $this->getConfig( 'dirfile' );
2091
    return ( FALSE === file_put_contents( $dirfile, $output, LOCK_EX )) ? FALSE : TRUE;
2092
  }
2093
/**
2094
 * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent
2095
 * else FALSE is returned
2096
 *
2097
 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
2098
 * @since 2.21.5 - 2015-03-29
2099
 * @param int  $timeout  default 3600 sec
2100
 * @param bool $cdType   TRUE : Content-Disposition: attachment... (default), FALSE : ...inline...
2101
 * @uses vcalendar::getConfig()
2102
 * @uses vcalendar::$headers
2103
 * @uses vcalendar::$format
2104
 * @return bool TRUE on success, FALSE on error
2105
 */
2106
  function useCachedCalendar( $timeout=3600, $cdType=TRUE ) {
2107
    if( FALSE === ( $dirfile = $this->getConfig( 'url' )))
2108
      $dirfile = $this->getConfig( 'dirfile' );
2109
    if( ! is_file( $dirfile ) || ! is_readable( $dirfile ))
2110
      return FALSE;
2111
    if( time() - filemtime( $dirfile ) > $timeout )
2112
      return FALSE;
2113
    clearstatcache();
2114
    $dirfile   = $this->getConfig( 'dirfile' );
2115
    $fsize     = @filesize( $dirfile );
2116
    $filename  = $this->getConfig( 'filename' );
2117
    if( 'xcal' == $this->format )
2118
      header( self::$headers[3] );
2119
    else
2120
      header( self::$headers[4] );
2121
    if( ! empty( $fsize ))
2122
      header( sprintf( self::$headers[2], $fsize ));
2123
    $cdType    = ( $cdType ) ? 5 : 6;
2124
    header( sprintf( self::$headers[$cdType], $filename ));
2125
    header( self::$headers[7] );
2126
    if( FALSE === ( $fp = @fopen( $dirfile, 'r' )))
2127
      return FALSE;
2128
    fpassthru( $fp );
2129
    fclose( $fp );
2130
    return TRUE;
2131
  }
2132
}