PluginProbe
Import Social Events / 1.8.5
Import Social Events v1.8.5
1.9.1 1.9.0 trunk 1.0.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0 1.3.0 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 All 61 releases
import-facebook-events / includes / lib / icalcreator / src / Vcalendar.php

Vcalendar.php in Import Social Events 1.8.5, at includes/lib/icalcreator/src/Vcalendar.php

999 lines 34.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information.
4 *
5 * copyright (c) 2007-2021 Kjell-Inge Gustafsson, kigkonsult, All rights reserved
6 * Link https://kigkonsult.se
7 * Package iCalcreator
8 * Version 2.30
9 * License Subject matter of licence is the software iCalcreator.
10 * The above copyright, link, package and version notices,
11 * this licence notice and the invariant [rfc5545] PRODID result use
12 * as implemented and invoked in iCalcreator shall be included in
13 * all copies or substantial portions of the iCalcreator.
14 *
15 * iCalcreator is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License as published
17 * by the Free Software Foundation, either version 3 of the License,
18 * or (at your option) any later version.
19 *
20 * iCalcreator is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with iCalcreator. If not, see <https://www.gnu.org/licenses/>.
27 *
28 * This file is a part of iCalcreator.
29 */
30
31 namespace Kigkonsult\Icalcreator;
32
33 use DateTime;
34 use DateTimeInterface;
35 use Exception;
36 use InvalidArgumentException;
37 use Kigkonsult\Icalcreator\Util\DateTimeFactory;
38 use Kigkonsult\Icalcreator\Util\HttpFactory;
39 use Kigkonsult\Icalcreator\Util\SelectFactory;
40 use Kigkonsult\Icalcreator\Util\SortFactory;
41 use Kigkonsult\Icalcreator\Util\StringFactory;
42 use Kigkonsult\Icalcreator\Util\Util;
43 use Kigkonsult\Icalcreator\Util\VtimezonePopulateFactory;
44 use UnexpectedValueException;
45
46 use function array_keys;
47 use function count;
48 use function ctype_digit;
49 use function end;
50 use function explode;
51 use function gethostbyname;
52 use function implode;
53 use function in_array;
54 use function is_array;
55 use function is_null;
56 use function is_string;
57 use function ksort;
58 use function method_exists;
59 use function property_exists;
60 use function rtrim;
61 use function strcasecmp;
62 use function strlen;
63 use function stripos;
64 use function strpos;
65 use function strtolower;
66 use function strtoupper;
67 use function substr;
68 use function trim;
69 use function usort;
70
71 /**
72 * Vcalendar class
73 *
74 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
75 * @since 2.29.16 - 2020-01-25
76 */
77 final class Vcalendar extends IcalBase
78 {
79 use Traits\CALSCALEtrait,
80 Traits\METHODtrait,
81 Traits\PRODIDtrait,
82 Traits\VERSIONtrait;
83 // The following are OPTIONAL, but MUST NOT occur more than once.
84 use Traits\UIDrfc7986trait,
85 Traits\LAST_MODIFIEDtrait,
86 Traits\URLtrait,
87 Traits\REFRESH_INTERVALrfc7986trait,
88 Traits\SOURCErfc7986trait,
89 Traits\COLORrfc7986trait;
90 // The following are OPTIONAL, and MAY occur more than once.
91 use Traits\NAMErfc7986trait,
92 Traits\DESCRIPTIONtrait,
93 Traits\CATEGORIEStrait,
94 Traits\IMAGErfc7986trait;
95
96 /**
97 * @const
98 */
99 const VCALENDAR = 'Vcalendar';
100
101 /**
102 * @var string property output formats, used by CALSCALE, METHOD, PRODID and VERSION
103 */
104 private static $FMTICAL = "%s:%s\r\n";
105
106 /**
107 * @var array iCal component date-property collection
108 */
109 private static $DATEPROPS = [
110 self::DTSTART, self::DTEND, self::DUE, self::CREATED, self::COMPLETED,
111 self::DTSTAMP, self::LAST_MODIFIED, self::RECURRENCE_ID,
112 ];
113
114 /**
115 * Constructor for calendar object
116 *
117 * @param array $config
118 * @since 2.29.5 - 2019-06-20
119 */
120 public function __construct( $config = [] )
121 {
122 static $SERVER_NAME = 'SERVER_NAME';
123 static $LOCALHOST = 'localhost';
124 $this->compType = self::VCALENDAR;
125 $this->setConfig(
126 self::UNIQUE_ID,
127 ( isset( $_SERVER[$SERVER_NAME] ))
128 ? gethostbyname( $_SERVER[$SERVER_NAME] )
129 : $LOCALHOST
130 );
131 $this->setConfig( $config );
132 $this->setUid();
133 }
134
135 /**
136 * Destructor
137 *
138 * @since 2.29.5 - 2019-06-20
139 */
140 public function __destruct()
141 {
142 if( ! empty( $this->components )) {
143 foreach( $this->components as $cix => $comp ) {
144 $this->components[$cix]->__destruct();
145 }
146 }
147 unset(
148 $this->compType,
149 $this->xprop,
150 $this->components,
151 $this->unparsed,
152 $this->config,
153 $this->compix,
154 $this->propIx,
155 $this->propDelIx
156 );
157 unset(
158 $this->calscale,
159 $this->method,
160 $this->prodid,
161 $this->version,
162 $this->uid,
163 $this->lastmodified,
164 $this->url,
165 $this->refreshinterval,
166 $this->source,
167 $this->color,
168 $this->name,
169 $this->description,
170 $this->categories,
171 $this->image
172 );
173 }
174
175 /**
176 * Return iCalcreator instance, factory method
177 *
178 * @param array $config
179 * @return static
180 * @since 2.18.5 - 2013-08-29
181 */
182 public static function factory( $config = [] )
183 {
184 return new self( $config );
185 }
186
187 /**
188 * Return iCalcreator version
189 *
190 * @return string
191 * @since 2.18.5 - 2013-08-29
192 */
193 public static function iCalcreatorVersion()
194 {
195 return trim(
196 substr(
197 ICALCREATOR_VERSION,
198 strpos( ICALCREATOR_VERSION, Util::$SP1 )
199 )
200 );
201 }
202
203 /**
204 * Return calendar component properties value(s)
205 *
206 * CATEGORIES, LOCATION, GEOLOCATION, PRIORITY, RESOURCES, STATUS, SUMMARY
207 * DTSTART (Ymd only)
208 * ATTENDEE*, CONTACT, ORGANIZER* *:prefixed by "protocol" like "MAILTO:....
209 * RECURRENCE-ID *4 (alt. "R-UID")
210 * RELATED-TO, URL, UID
211 * @param string $propName
212 * @return mixed
213 * @since 2.29.17 - 2020-01-25
214 */
215 public function getProperty( $propName )
216 {
217 static $PROPS = [
218 self::ATTENDEE,
219 self::CATEGORIES,
220 self::CONTACT,
221 self::DTSTART,
222 self::GEOLOCATION,
223 self::LOCATION,
224 self::ORGANIZER,
225 self::PRIORITY,
226 self::RESOURCES,
227 self::STATUS,
228 self::SUMMARY,
229 'RECURRENCE-ID-UID',
230 self::RELATED_TO,
231 'R-UID',
232 self::UID,
233 self::URL
234 ];
235 $propName = strtoupper( $propName );
236 if( ! Util::isPropInList( $propName, $PROPS )) {
237 return false;
238 }
239 $output = [];
240 foreach( $this->components as $cix => $component ) {
241 switch( true ) {
242 case ( ! Util::isCompInList( $component->getCompType(), self::$VCOMPS )) :
243 continue 2;
244 case ( ! property_exists(
245 $component,
246 self::getInternalPropName( $propName ))
247 ) :
248 continue 2;
249 case ( Util::isPropInList( $propName, self::$MPROPS1 )) :
250 $component->getProperties( $propName, $output );
251 continue 2;
252 case (( 3 < strlen( $propName )) &&
253 ( self::UID == substr( $propName, -3 ))) :
254 if( false !== ( $content = $component->getRecurrenceid())) {
255 $content = $component->getUid();
256 }
257 break;
258 case (( self::GEOLOCATION == $propName ) &&
259 ( ! property_exists( $component, strtolower( self::GEO )) ||
260 ( false === ( $content = $component->getGeoLocation())))) :
261 continue 2;
262 default :
263 $method = parent::getGetMethodName( $propName );
264 if( ! method_exists( $component, $method ) ||
265 ( false === ( $content = $component->{$method}()))) {
266 continue 2;
267 }
268 } // end switch
269 if(( false === $content ) || empty( $content )) {
270 continue;
271 }
272 switch( true ) {
273 case ( $content instanceof DateTime ) :
274 $key = $content->format( DateTimeFactory::$Ymd );
275 if( ! isset( $output[$key] )) {
276 $output[$key] = 1;
277 }
278 else {
279 $output[$key] += 1;
280 }
281 break;
282 case ( is_array( $content )) :
283 foreach( $content as $partKey => $partValue ) {
284 if( ! isset( $output[$partKey] )) {
285 $output[$partKey] = $partValue;
286 }
287 else {
288 $output[$partKey] += $partValue;
289 }
290 } // end foreach
291 break;
292 case ( ! isset( $output[$content] )) :
293 $output[$content] = 1;
294 break;
295 default :
296 $output[$content] += 1;
297 break;
298 } // end switch
299 } // end foreach( $this->components as $cix => $component)
300 if( ! empty( $output )) {
301 ksort( $output );
302 }
303 return $output;
304 }
305
306 /**
307 * Return clone of calendar component
308 *
309 * @param mixed $arg1 ordno/component type/component uid
310 * @param mixed $arg2 ordno if arg1 = component type
311 * @return mixed CalendarComponent|bool (false on error)
312 * @since 2.27.14 - 2019-02-20
313 * @todo throw InvalidArgumentException on unknown component
314 */
315 public function getComponent( $arg1 = null, $arg2 = null )
316 {
317 $index = $argType = null;
318 switch( true ) {
319 case is_null( $arg1 ) : // first or next in component chain
320 $argType = self::$INDEX;
321 if( isset( $this->compix[self::$INDEX] )) {
322 $this->compix[self::$INDEX] = $this->compix[self::$INDEX] + 1;
323 }
324 else {
325 $this->compix[self::$INDEX] = 1;
326 }
327 $index = $this->compix[self::$INDEX];
328 break;
329 case is_array( $arg1 ) : // [ *[propertyName => propertyValue] ]
330 $arg2 = implode( Util::$MINUS, array_keys( $arg1 ));
331 if( isset( $this->compix[$arg2] )) {
332 $this->compix[$arg2] = $this->compix[$arg2] + 1;
333 }
334 else {
335 $this->compix[$arg2] = 1;
336 }
337 $index = $this->compix[$arg2];
338 break;
339 case ctype_digit((string) $arg1 ) : // specific component in chain
340 $argType = self::$INDEX;
341 $index = (int) $arg1;
342 $this->compix = [];
343 break;
344 case Util::isCompInList( $arg1, self::$CALCOMPS ) : // component type
345 unset( $this->compix[self::$INDEX] );
346 $argType = $arg1;
347 if( is_null( $arg2 )) {
348 if( isset( $this->compix[$argType] )) {
349 $this->compix[$argType] = $this->compix[$argType] + 1;
350 }
351 else {
352 $this->compix[$argType] = 1;
353 }
354 $index = $this->compix[$argType];
355 }
356 elseif( isset( $arg2 ) && ctype_digit((string) $arg2 )) {
357 $index = (int) $arg2;
358 }
359 break;
360 case is_string( $arg1 ) : // assume UID as 1st argument
361 if( is_null( $arg2 )) {
362 if( isset( $this->compix[$arg1] )) {
363 $this->compix[$arg1] = $this->compix[$arg1] + 1;
364 }
365 else {
366 $this->compix[$arg1] = 1;
367 }
368 $index = $this->compix[$arg1];
369 }
370 elseif( isset( $arg2 ) && ctype_digit((string) $arg2 )) {
371 $index = (int) $arg2;
372 }
373 break;
374 } // end switch( true )
375 if( isset( $index )) {
376 $index -= 1;
377 }
378 $cKeys = array_keys( $this->components );
379 if( ! empty( $index ) && ( $index > end( $cKeys ))) {
380 return false;
381 }
382 $cix1gC = 0;
383 foreach( $cKeys as $cix ) {
384 switch( true ) {
385 case empty( $this->components[$cix] ) :
386 break;
387 case (( self::$INDEX == $argType ) && ( $index == $cix )) :
388 return clone $this->components[$cix];
389 break;
390 case ( 0 == strcasecmp(
391 $argType,
392 $this->components[$cix]->getCompType()
393 )) :
394 if( $index == $cix1gC ) {
395 return clone $this->components[$cix];
396 }
397 $cix1gC++;
398 break;
399 case is_array( $arg1 ) : // [ *[propertyName => propertyValue] ]
400 if( self::isFoundInCompsProps( $this->components[$cix], $arg1 )) {
401 if( $index == $cix1gC ) {
402 return clone $this->components[$cix];
403 }
404 $cix1gC++;
405 }
406 break;
407 case ( ! $argType && ( $arg1 == $this->components[$cix]->getUid())) :
408 if( $index == $cix1gC ) {
409 return clone $this->components[$cix];
410 }
411 $cix1gC++;
412 break;
413 } // end switch
414 } // end foreach( $cKeys as $cix )
415 /* not found.. . */
416 $this->compix = [];
417 return false;
418 }
419
420 /**
421 * Return bool true on argList values found in any component property
422 *
423 * @param CalendarComponent $component
424 * @param array $argList
425 * @return bool
426 * @since 2.29.17 - 2020-01-25
427 */
428 private static function isFoundInCompsProps(
429 CalendarComponent $component,
430 array $argList
431 ) {
432 foreach( $argList as $propName => $propValue ) {
433 switch( true ) {
434 case ( ! Util::isPropInList( $propName, self::$DATEPROPS ) &&
435 ! Util::isPropInList( $propName, Vcalendar::$OTHERPROPS )) :
436 continue 2;
437 break;
438 case ( ! property_exists( $component, parent::getInternalPropName( $propName ))) :
439 continue 2;
440 break;
441 case ( Util::isPropInList( $propName, self::$MPROPS1 )) : // multiple occurrence
442 $propValues = [];
443 $component->getProperties( $propName, $propValues );
444 if( in_array( $propValue, array_keys( $propValues ))) {
445 return true;
446 }
447 continue 2;
448 break;
449 } // end switch
450 $method = parent::getGetMethodName( $propName );
451 if( ! method_exists( $component, $method )) {
452 continue;
453 }
454 if( false === ( $value = $component->{$method}())) { // single occurrence
455 continue; // missing/empty property
456 }
457 switch( true ) {
458 case ( self::SUMMARY == $propName ) : // exists in (any case)
459 if( false !== stripos( $value, $propValue )) {
460 return true;
461 }
462 continue 2;
463 break;
464 case ( Util::isPropInList( $propName, self::$DATEPROPS )) :
465 $fmt = ( 9 > strlen( $propValue ))
466 ? DateTimeFactory::$Ymd
467 : DateTimeFactory::$YmdHis;
468 $valueDate = $value->format( $fmt );
469 if( $propValue == $valueDate ) {
470 return true;
471 }
472 continue 2;
473 break;
474 case ! is_array( $value ) :
475 $value = [ $value ];
476 break;
477 } // end switch
478 foreach( $value as $part ) {
479 $part = ( false !== strpos( $part, Util::$COMMA ))
480 ? explode( Util::$COMMA, $part )
481 : [ $part ];
482 foreach( $part as $subPart ) {
483 if( $propValue == $subPart ) {
484 return true;
485 }
486 }
487 } // end foreach( $value as $part )
488 } // end foreach( $arg1 as $propName => $propValue )
489 return false;
490 }
491
492 /**
493 * Return Vevent object instance
494 *
495 * @return Vevent
496 * @throws InvalidArgumentException
497 * @throws Exception
498 * @since 2.27.14 - 2018-02-19
499 */
500 public function newVevent()
501 {
502 $comp = new Vevent( $this->getConfig());
503 $comp->getDtstamp();
504 $comp->getUid();
505 $ix = $this->getNextComponentIndex();
506 $this->components[$ix] = $comp;
507 return $comp;
508 }
509
510 /**
511 * Return Vtodo object instance
512 *
513 * @return Vtodo
514 * @throws InvalidArgumentException
515 * @throws Exception
516 * @since 2.27.14 - 2018-02-19
517 */
518 public function newVtodo()
519 {
520 $comp = new Vtodo( $this->getConfig());
521 $comp->getDtstamp();
522 $comp->getUid();
523 $ix = $this->getNextComponentIndex();
524 $this->components[$ix] = $comp;
525 return $comp;
526 }
527
528 /**
529 * Return Vjournal object instance
530 *
531 * @return Vjournal
532 * @throws InvalidArgumentException
533 * @throws Exception
534 * @since 2.27.14 - 2018-02-19
535 */
536 public function newVjournal()
537 {
538 $comp = new Vjournal( $this->getConfig());
539 $comp->getDtstamp();
540 $comp->getUid();
541 $ix = $this->getNextComponentIndex();
542 $this->components[$ix] = $comp;
543 return $comp;
544 }
545
546 /**
547 * Return Vfreebusy object instance
548 *
549 * @return Vfreebusy
550 * @throws InvalidArgumentException
551 * @throws Exception
552 * @since 2.27.14 - 2018-02-19
553 */
554 public function newVfreebusy()
555 {
556 $comp = new Vfreebusy( $this->getConfig());
557 $comp->getDtstamp();
558 $comp->getUid();
559 $ix = $this->getNextComponentIndex();
560 $this->components[$ix] = $comp;
561 return $comp;
562 }
563
564 /**
565 * Return Vtimezone object instance
566 *
567 * @return Vtimezone
568 * @since 2.29.8 - 2019-07-03
569 */
570 public function newVtimezone()
571 {
572 $vTimezones = $others = [];
573 foreach( array_keys( $this->components ) as $cix ) {
574 if( self::VTIMEZONE == $this->components[$cix]->getCompType()) {
575 $vTimezones[] = clone $this->components[$cix];
576 continue;
577 }
578 $others[] = clone $this->components[$cix];
579 } // end foreach
580 $vtix = count( $vTimezones );
581 $vTimezones[$vtix] = new Vtimezone( $this->getConfig());
582 $this->components = [];
583 foreach( array_keys( $vTimezones ) as $cix ) {
584 $this->components[] = $vTimezones[$cix];
585 }
586 foreach( array_keys( $others ) as $cix ) {
587 $this->components[] = $others[$cix];
588 }
589 return $this->components[$vtix];
590 }
591
592 /**
593 * Replace calendar component in Vcalendar
594 *
595 * @param CalendarComponent $component
596 * @return static
597 * @throws InvalidArgumentException
598 * @since 2.27.3 - 2018-12-28
599 */
600 public function replaceComponent( CalendarComponent $component )
601 {
602 static $ERRMSG1 = 'Invalid component type \'%s\'';
603 static $ERRMSG2 = 'Vtimezone with tzid \'%s\' not found, found \'%s\'';
604 if( Util::isCompInList( $component->getCompType(), self::$VCOMPS )) {
605 return $this->setComponent( $component, $component->getUid());
606 }
607 if(( self::VTIMEZONE != $component->getCompType()) ||
608 ( false === ( $tzId = $component->getTzid()))) {
609 throw new InvalidArgumentException(
610 sprintf( $ERRMSG1, $component->getCompType())
611 );
612 }
613 $found = [];
614 foreach( $this->components as $cix => $comp ) {
615 if( self::VTIMEZONE != $component->getCompType()) {
616 continue;
617 }
618 $foundTxid = $comp->getTzid();
619 if( $tzId == $foundTxid ) {
620 $component->compix = [];
621 $this->components[$cix] = $component;
622 return $this;
623 }
624 $found[] = $foundTxid;
625 } // end foreach
626 throw new InvalidArgumentException(
627 sprintf(
628 $ERRMSG2,
629 $component->getCompType(),
630 implode( Util::$COMMA, $found )
631 )
632 );
633 }
634
635 /**
636 * Return selected components from calendar on date or selectOption basis
637 *
638 * DTSTART MUST be set for every component.
639 * No date check.
640 *
641 * @param int|array|DateTimeInterface $startY (int) start Year, default current Year
642 * ALT. DateTime start date
643 * ALT. array selectOptions ( *[ <propName> => <uniqueValue> ] )
644 * @param int|DateTimeInterface $startM (int) start Month, default current Month
645 * ALT. DateTime end date
646 * @param int $startD start Day, default current Day
647 * @param int $endY end Year, default $startY
648 * @param int $endM end Month, default $startM
649 * @param int $endD end Day, default $startD
650 * @param mixed $cType calendar component type(-s), default false=all else string/array type(-s)
651 * @param bool $flat false (default) => output : array[Year][Month][Day][]
652 * true => output : array[] (ignores split)
653 * @param bool $any true (default) - select component(-s) that occurs within period
654 * false - only component(-s) that starts within period
655 * @param bool $split true (default) - one component copy every DAY it occurs during the
656 * period (implies flat=false)
657 * false - one occurance of component only in output array
658 * @return mixed array on success, bool false on error
659 * @throws Exception
660 * @since 2.29.16 - 2020-01-24
661 */
662 public function selectComponents(
663 $startY = null,
664 $startM = null,
665 $startD = null,
666 $endY = null,
667 $endM = null,
668 $endD = null,
669 $cType = null,
670 $flat = null,
671 $any = null,
672 $split = null
673 ) {
674 try {
675 return SelectFactory::selectComponents(
676 $this,
677 $startY, $startM, $startD, $endY, $endM, $endD,
678 $cType, $flat, $any, $split
679 );
680 }
681 catch( Exception $e ) {
682 throw $e;
683 }
684 }
685
686 /**
687 * Sort iCal components
688 *
689 * Ascending sort on properties (if exist) x-current-dtstart, dtstart,
690 * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid if called without arguments,
691 * otherwise sorting on specific (argument) property values
692 *
693 * @param string $sortArg
694 * @return static
695 * @since 2.27.3 - 2018-12-28
696 */
697 public function sort( $sortArg = null )
698 {
699 static $SORTER = [ 'Kigkonsult\Icalcreator\Util\SortFactory', 'cmpfcn' ];
700 if( 2 > $this->countComponents()) {
701 return $this;
702 }
703 if( ! is_null( $sortArg )) {
704 $sortArg = strtoupper( $sortArg );
705 if( ! Util::isPropInList( $sortArg, Vcalendar::$OTHERPROPS ) &&
706 ( self::DTSTAMP != $sortArg )) {
707 $sortArg = null;
708 }
709 }
710 foreach( $this->components as $cix => $component ) {
711 SortFactory::setSortArgs( $this->components[$cix], $sortArg );
712 }
713 usort( $this->components, $SORTER );
714 return $this;
715 }
716
717 /**
718 * Parse iCal text/file into Vcalendar, components, properties and parameters
719 *
720 * @param string|array $unParsedText strict rfc2445 formatted, single property string or array of strings
721 * @return static
722 * @throws Exception
723 * @throws InvalidArgumentException
724 * @throws UnexpectedValueException
725 * @since 2.29.3 2019-08-29
726 */
727 public function parse( $unParsedText )
728 {
729 $rows = StringFactory::conformParseInput( $unParsedText );
730 $this->parse2intoComps( $rows );
731 $this->parse3thisProperties();
732 /* parse Components */
733 if( ! empty( $this->countComponents())) {
734 $this->parse4subComps();
735 }
736 return $this;
737 }
738
739 /**
740 * Parse into calendar and comps data
741 *
742 * @param array $rows
743 * @throws Exception
744 * @throws UnexpectedValueException
745 * @since 2.29.3 - 2019-08-26
746 */
747 private function parse2intoComps( array $rows )
748 {
749 static $ERR20 = 'Ical content not in sync (row %d) %s';
750 static $BEGIN_VCALENDAR = 'BEGIN:VCALENDAR';
751 static $END_VCALENDAR = 'END:VCALENDAR';
752 static $ENDSARR = [ 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ];
753 static $BEGIN_VEVENT = 'BEGIN:VEVENT';
754 static $BEGIN_VFREEBUSY = 'BEGIN:VFREEBUSY';
755 static $BEGIN_VJOURNAL = 'BEGIN:VJOURNAL';
756 static $BEGIN_VTODO = 'BEGIN:VTODO';
757 static $BEGIN_VTIMEZONE = 'BEGIN:VTIMEZONE';
758 $comp = $this;
759 $calSync = $compSync = 0;
760 /* identify components and update unparsed data for components */
761 foreach( $rows as $lix => $row ) {
762 switch( true ) {
763 case StringFactory::startsWith( $row, $BEGIN_VCALENDAR ) :
764 $calSync++;
765 break;
766 case StringFactory::startsWith( $row, $END_VCALENDAR ) :
767 $calSync -= 1;
768 if( 0 != $calSync ) { /* err 20 */
769 throw new UnexpectedValueException(
770 sprintf( $ERR20, $lix, PHP_EOL . implode( PHP_EOL, $rows ))
771 );
772 }
773 break 2;
774 case ( in_array( strtoupper( substr( $row, 0, 6 )), $ENDSARR )) :
775 $compSync -= 1;
776 break;
777 case StringFactory::startsWith( $row, $BEGIN_VEVENT ) :
778 $comp = $this->newVevent();
779 $compSync += 1;
780 break;
781 case StringFactory::startsWith( $row, $BEGIN_VFREEBUSY ) :
782 $comp = $this->newVfreebusy();
783 $compSync += 1;
784 break;
785 case StringFactory::startsWith( $row, $BEGIN_VJOURNAL ) :
786 $comp = $this->newVjournal();
787 $compSync += 1;
788 break;
789 case StringFactory::startsWith( $row, $BEGIN_VTODO ) :
790 $comp = $this->newVtodo();
791 $compSync += 1;
792 break;
793 case StringFactory::startsWith( $row, $BEGIN_VTIMEZONE ) :
794 $comp = $this->newVtimezone();
795 $compSync += 1;
796 break;
797 default : /* update component with unparsed data */
798 $comp->unparsed[] = $row;
799 break;
800 } // switch( true )
801 } // end foreach( $rows as $lix => $row )
802 }
803
804 /**
805 * Parse calendar data
806 *
807 * @throws UnexpectedValueException
808 * @since 2.29.22 - 2020-08-26
809 */
810 private function parse3thisProperties()
811 {
812 static $NLCHARS = '\n';
813 static $BEGIN = 'BEGIN:';
814 static $ERR = 'Unknown ical component (row %d) %s';
815 static $PVPROPS = [ self::PRODID, self::VERSION ];
816 static $CALPROPS = [
817 self::CALSCALE,
818 self::METHOD,
819 self::PRODID,
820 self::VERSION,
821 ];
822 static $RFC7986PROPS = [
823 self::COLOR,
824 self::CATEGORIES,
825 self::DESCRIPTION,
826 self::IMAGE,
827 self::NAME,
828 self::LAST_MODIFIED,
829 self::REFRESH_INTERVAL,
830 self::SOURCE,
831 self::UID,
832 self::URL,
833 ];
834 if( ! isset( $this->unparsed ) ||
835 ! is_array( $this->unparsed ) ||
836 ( 1 > count( $this->unparsed ))) {
837 return;
838 }
839 /* concatenate property values spread over several rows */
840 static $TRIMCHARS = "\x00..\x1F";
841 $rows = StringFactory::concatRows( $this->unparsed );
842 foreach( $rows as $lix => $row ) {
843 if( StringFactory::startsWith( $row, $BEGIN )) {
844 throw new UnexpectedValueException(
845 sprintf( $ERR, $lix, PHP_EOL . implode( PHP_EOL, $rows ))
846 );
847 }
848 /* split property name and opt.params and value */
849 list( $propName, $row ) = StringFactory::getPropName( $row );
850 switch( true ) {
851 case ( StringFactory::isXprefixed( $propName ) ||
852 Util::isPropInList( $propName, $RFC7986PROPS )) :
853 break;
854 case Util::isPropInList( $propName, $PVPROPS ) :
855 continue 2; // ignore version/prodid properties
856 break;
857 case ( ! Util::isPropInList( $propName, $CALPROPS )) :
858 continue 2; // skip non standard property names
859 break;
860 } // end switch
861 /* separate attributes from value */
862 list( $value, $propAttr ) = StringFactory::splitContent( $row );
863 /* update Property */
864 if( StringFactory::isXprefixed( $propName )) {
865 $this->setXprop(
866 $propName,
867 StringFactory::strunrep( $value ),
868 $propAttr
869 );
870 continue;
871 }
872 if(( $NLCHARS == strtolower( substr( $value, -2 ))) &&
873 ! Util::isPropInList( $propName, self::$TEXTPROPS )) {
874 $value = StringFactory::trimTrailNL( $value );
875 }
876 $method = parent::getSetMethodName( $propName );
877 switch( $propName ) {
878 case self::LAST_MODIFIED : // fall through
879 case self::REFRESH_INTERVAL : // fall through
880 case self::URL :
881 $this->{$method}( $value, $propAttr );
882 break;
883 default :
884 $value = StringFactory::strunrep( rtrim( $value, $TRIMCHARS ));
885 $this->{$method}( $value, $propAttr );
886 } // end switch
887 } // end foreach
888 unset( $this->unparsed );
889 }
890
891 /**
892 * Parse sub-components
893 *
894 * @since 2.29.3 - 2019-07-02
895 */
896 private function parse4subComps()
897 {
898 foreach( array_keys( $this->components ) as $ckey ) {
899 if( ! empty( $this->components[$ckey] ) &&
900 ! empty( $this->components[$ckey]->unparsed )) {
901 $this->components[$ckey]->parse();
902 }
903 } // end foreach
904 }
905
906 /**
907 * Return static with (replaced) populated Vtimezone component
908 *
909 * @param string $timezone valid timezone acceptable by PHP5 DateTimeZone
910 * @param array $xProp *[x-propName => x-propValue]
911 * @param DateTimeInterface|int $start .. or unix timestamp
912 * @param DateTimeInterface|int $end .. or unix timestamp
913 * @return Vcalendar
914 * @throws Exception
915 * @throws InvalidArgumentException;
916 * @since 2.29.16 - 2020-01-25
917 */
918 public function vtimezonePopulate(
919 $timezone = null,
920 $xProp = [],
921 $start = null,
922 $end = null
923 ) {
924 return VtimezonePopulateFactory::process(
925 $this,
926 $timezone,
927 $xProp,
928 $start,
929 $end
930 );
931 }
932
933 /**
934 * Return formatted output for calendar object instance
935 *
936 * @return string
937 * @throws Exception
938 * @since 2.29.05 - 2019-07-02
939 */
940 public function createCalendar()
941 {
942 static $BEGIN_VCALENDAR = "BEGIN:VCALENDAR";
943 static $END_VCALENDAR = "END:VCALENDAR";
944 $calendar = $BEGIN_VCALENDAR . Util::$CRLF;
945 $calendar .= $this->createVersion();
946 $calendar .= $this->createProdid();
947 $calendar .= $this->createCalscale();
948 $calendar .= $this->createMethod();
949 $calendar .= $this->createLastmodified();
950 $calendar .= $this->createUid();
951 $calendar .= $this->createUrl();
952 $calendar .= $this->createRefreshinterval();
953 $calendar .= $this->createSource();
954 $calendar .= $this->createColor();
955 $calendar .= $this->createName();
956 $calendar .= $this->createDescription();
957 $calendar .= $this->createCategories();
958 $calendar .= $this->createImage();
959 $calendar .= $this->createXprop();
960 $config = $this->getConfig();
961 $this->reset();
962 foreach( array_keys( $this->components ) as $cix ) {
963 if( ! empty( $this->components[$cix] )) {
964 $this->components[$cix]->setConfig( $config, false, true );
965 $calendar .= $this->components[$cix]->createComponent();
966 }
967 }
968 return $calendar . $END_VCALENDAR . Util::$CRLF;
969 }
970
971 /**
972 * Return created, updated and/or parsed calendar,
973 * sending a HTTP redirect header.
974 *
975 * @param bool $utf8Encode
976 * @param bool $gzip
977 * @param bool $cdType true : Content-Disposition: attachment... (default), false : ...inline...
978 * @param string $fileName
979 * @return bool true on success, false on error
980 * @throws Exception
981 * @since 2.29.15 - 2020-01-19
982 */
983 public function returnCalendar(
984 $utf8Encode = false,
985 $gzip = false,
986 $cdType = true,
987 $fileName = null
988 ) {
989 return HttpFactory::returnCalendar(
990 $this,
991 $utf8Encode,
992 $gzip,
993 $cdType,
994 $fileName
995 );
996 }
997
998 }
999