PluginProbe
Import Social Events / trunk
Import Social Events vtrunk
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 / IcalBase.php

IcalBase.php in Import Social Events trunk, at includes/lib/icalcreator/src/IcalBase.php

941 lines 29.9 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.10
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 namespace Kigkonsult\Icalcreator;
31
32 if ( ! defined( 'ABSPATH' ) ) {
33 exit;
34 }
35
36 use InvalidArgumentException;
37 use Kigkonsult\Icalcreator\Util\ParameterFactory;
38 use Kigkonsult\Icalcreator\Util\Util;
39
40 use function define;
41 use function defined;
42 use function array_change_key_case;
43 use function array_filter;
44 use function array_keys;
45 use function array_slice;
46 use function count;
47 use function ctype_digit;
48 use function get_object_vars;
49 use function is_array;
50 use function is_null;
51 use function is_object;
52 use function key;
53 use function ksort;
54 use function property_exists;
55 use function sprintf;
56 use function strtolower;
57 use function strtoupper;
58 use function trim;
59 use function ucfirst;
60
61 /**
62 * Do NOT alter or remove the constant!!
63 */
64 if( ! defined( 'ICALCREATOR_VERSION' )) {
65 define( 'ICALCREATOR_VERSION', 'iCalcreator 2.30.10' );
66 }
67
68 /**
69 * iCalcreator base class
70 *
71 * Properties and methods shared by Vcalendar and CalendarComponents
72 *
73 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
74 * @since 2.29.4 - 2019-07-01
75 */
76 abstract class IcalBase implements IcalInterface
77 {
78 use Traits\X_PROPtrait;
79
80 /**
81 * @var string
82 */
83 protected static $INDEX = 'INDEX';
84
85 /**
86 * @var array iCal V*-component collection
87 * @usedby IcalBase+Vcalendar+SelectFactory
88 */
89 public static $VCOMPS = [
90 Vcalendar::VEVENT,
91 Vcalendar::VTODO,
92 Vcalendar::VJOURNAL,
93 Vcalendar::VFREEBUSY
94 ];
95
96 /**
97 * @var array iCal timezone component collection
98 * @usedby DTSTARTtrait+RexdateFactory
99 */
100 public static $TZCOMPS = [
101 Vcalendar::VTIMEZONE,
102 Vcalendar::STANDARD,
103 Vcalendar::DAYLIGHT
104 ];
105
106 /**
107 * @var array iCal component collection
108 */
109 protected static $CALCOMPS = [
110 Vcalendar::VEVENT,
111 Vcalendar::VTODO,
112 Vcalendar::VJOURNAL,
113 Vcalendar::VFREEBUSY,
114 Vcalendar::VALARM,
115 Vcalendar::VTIMEZONE
116 ];
117
118 /**
119 * @var array iCal sub-component collection
120 * @usedby CalendarComponent+IcalBase+DTSTAMPtrait
121 */
122 protected static $SUBCOMPS = [
123 Vcalendar::VALARM,
124 Vcalendar::VTIMEZONE,
125 Vcalendar::STANDARD,
126 Vcalendar::DAYLIGHT
127 ];
128
129 /**
130 * @var array iCal component multiple property sub-collection
131 * @usedby CalendarComponent + Vcalendar + SelectFactory + SortFactory
132 */
133 public static $MPROPS1 = [
134 Vcalendar::ATTENDEE, Vcalendar::CATEGORIES, Vcalendar::CONTACT,
135 Vcalendar::RELATED_TO, Vcalendar::RESOURCES,
136 ];
137
138 /**
139 * @var array iCal component multiple property collection
140 * @since 2.27.20 - 2019-05-20
141 * @usedby IcalBase
142 */
143 protected static $MPROPS2 = [
144 Vcalendar::ATTACH, Vcalendar::ATTENDEE, Vcalendar::CATEGORIES,
145 Vcalendar::COMMENT, Vcalendar::CONTACT, Vcalendar::DESCRIPTION,
146 Vcalendar::EXDATE, Vcalendar::FREEBUSY, Vcalendar::RDATE,
147 Vcalendar::RELATED_TO, Vcalendar::RESOURCES,
148 Vcalendar::REQUEST_STATUS, Vcalendar::TZNAME, Vcalendar::X_PROP,
149 ];
150
151 /**
152 * @var array iCal component misc. property collection
153 * @usedby Vcalendar + SelectFactory
154 */
155 public static $OTHERPROPS = [
156 Vcalendar::ATTENDEE, Vcalendar::CATEGORIES, Vcalendar::CONTACT, Vcalendar::LOCATION,
157 Vcalendar::ORGANIZER, Vcalendar::PRIORITY, Vcalendar::RELATED_TO, Vcalendar::RESOURCES,
158 Vcalendar::STATUS, Vcalendar::SUMMARY, Vcalendar::UID, Vcalendar::URL,
159 ];
160
161 /**
162 * @var array iCal component TEXT properties
163 * @usedby Vcalendar + CalendarComponent
164 */
165 protected static $TEXTPROPS = [
166 self::CATEGORIES,
167 self::COLOR,
168 self::COMMENT,
169 self::DESCRIPTION,
170 self::NAME,
171 self::SUMMARY,
172 ];
173
174 /**
175 * @var string
176 */
177 protected static $FMTERRPROPFMT = 'Invalid %s input format (%s)';
178
179 /**
180 * @var array
181 */
182 protected static $ALTRPLANGARR = [ self::ALTREP, self::LANGUAGE ];
183
184 /**
185 * @var array container for sub-components
186 */
187 protected $components = [];
188
189 /**
190 * @var array $unparsed calendar/components in 'raw' text...
191 */
192 protected $unparsed = null;
193
194 /**
195 * @var array $config configuration with defaults
196 */
197 protected $config = [
198 self::ALLOWEMPTY => true,
199 ];
200
201 /**
202 * @var string component type
203 */
204 protected $compType = null;
205
206 /**
207 * @var array component index
208 */
209 protected $compix = [];
210
211 /**
212 * @var array get multi property index
213 */
214 protected $propIx = [];
215
216 /**
217 * @var array delete multi property index
218 */
219 protected $propDelIx = [];
220
221 /**
222 * __clone method
223 *
224 * @link https://php.net/manual/en/language.oop5.cloning.php#116329
225 * @since 2.26 - 2018-11-10
226 */
227 public function __clone()
228 {
229 $object_vars = get_object_vars( $this );
230 foreach( $object_vars as $attr_name => $attr_value ) {
231 if( is_object( $this->$attr_name )) {
232 $this->$attr_name = clone $this->$attr_name;
233 }
234 else if( is_array( $this->$attr_name )) {
235 // Note: This copies only one dimension arrays
236 foreach( $this->$attr_name as & $attr_array_value ) {
237 if( is_object( $attr_array_value )) {
238 $attr_array_value = clone $attr_array_value;
239 }
240 unset( $attr_array_value );
241 }
242 }
243 } // end foreach
244 $this->compix = [];
245 $this->propIx = [];
246 $this->propDelIx = [];
247 }
248
249 /**
250 * Reset internal counters
251 *
252 * @return static
253 * @since 2.27.14 - 2019-03-11
254 */
255 public function reset()
256 {
257 $this->compix = [];
258 return $this;
259 }
260
261 /**
262 * Return component type
263 *
264 * @return string
265 * @since 2.27.2 - 2018-12-21
266 */
267 public function getCompType()
268 {
269 return $this->compType;
270 }
271
272 /**
273 * Remove Vcalendar/component config key value
274 *
275 * @param string $key
276 * @return static
277 * @since 2.27.14 - 2019-02-04
278 */
279 public function deleteConfig( $key )
280 {
281 $key = strtoupper( $key );
282 if( isset( $this->config[$key] )) {
283 unset( $this->config[$key] );
284 }
285 return $this;
286 }
287
288 /**
289 * Return Vcalendar/component config value, false on not found
290 *
291 * @param mixed $config
292 * @return mixed bool false on not found or empty
293 * @since 2.29.4 - 2019-07-02
294 */
295 public function getConfig( $config = null )
296 {
297 static $LCORDNO = 'ordno';
298 static $LCTYPE = 'type';
299 static $LCUID = 'uid';
300 static $LCPROPS = 'props';
301 static $LCSUB = 'sub';
302 if( is_null( $config )) {
303 $output = [];
304 $output[self::ALLOWEMPTY] = $this->getConfig( self::ALLOWEMPTY );
305 if( false !== ( $cfg = $this->getConfig( self::LANGUAGE ))) {
306 $output[self::LANGUAGE] = $cfg;
307 }
308 $output[self::UNIQUE_ID] = $this->getConfig( self::UNIQUE_ID );
309 return $output;
310 }
311 switch( strtoupper( $config )) {
312 case self::ALLOWEMPTY:
313 if( ! isset( $this->config[self::ALLOWEMPTY] )) { // default
314 $this->config[self::ALLOWEMPTY] = true;
315 }
316 return $this->config[self::ALLOWEMPTY];
317 break;
318 case self::UNIQUE_ID:
319 if( isset( $this->config[self::UNIQUE_ID] )) {
320 return $this->config[self::UNIQUE_ID];
321 }
322 break;
323 case self::LANGUAGE: // get language for calendar component as defined in [RFC 1766]
324 if( isset( $this->config[self::LANGUAGE] )) {
325 return $this->config[self::LANGUAGE];
326 }
327 break;
328 case self::COMPSINFO:
329 $this->compix = [];
330 $info = [];
331 if( ! empty( $this->components )) {
332 foreach( $this->components as $cix => $component ) {
333 if( empty( $component )) {
334 continue;
335 }
336 $info[$cix][$LCORDNO] = $cix + 1;
337 $info[$cix][$LCTYPE] = $component->getCompType();
338 if( ! Util::isCompInList( $component->getCompType(), self::$SUBCOMPS )) {
339 $info[$cix][$LCUID] = $component->getUid();
340 }
341 $info[$cix][$LCPROPS] = $component->getConfig( self::PROPINFO );
342 $info[$cix][$LCSUB] = $component->getConfig( self::COMPSINFO );
343 } // end foreach
344 }
345 return $info;
346 break;
347 case self::PROPINFO:
348 return $this->getpropInfo();
349 break;
350 case self::SETPROPERTYNAMES:
351 return array_keys( $this->getConfig( self::PROPINFO ));
352 break;
353 default :
354 break;
355 } // end switch
356 return false;
357 }
358
359 /**
360 * Return array( propertyName => count )
361 *
362 * @return array
363 * @since 2.29.05 - 2019-06-20
364 */
365 protected function getpropInfo()
366 {
367 static $PROPNAMES = [
368 self::ACTION, self::ATTACH, self::ATTENDEE, self::CATEGORIES,
369 self::KLASS, self::COLOR, self::COMMENT,
370 self::COMPLETED, self::CONFERENCE, self::CONTACT,
371 self::CREATED, self::DESCRIPTION, self::DTEND, self::DTSTAMP,
372 self::DTSTART, self::DUE, self::DURATION, self::EXDATE, self::EXRULE,
373 self::FREEBUSY, self::GEO, self::IMAGE,
374 self::LAST_MODIFIED, self::LOCATION, self::NAME,
375 self::ORGANIZER, self::PERCENT_COMPLETE, self::PRIORITY,
376 self::RECURRENCE_ID, self::REFRESH_INTERVAL, self::RELATED_TO, self::REPEAT,
377 self::REQUEST_STATUS, self::RESOURCES, self::RRULE, self::RDATE,
378 self::SEQUENCE, self::SOURCE, self::STATUS, self::SUMMARY, self::TRANSP,
379 self::TRIGGER, self::TZNAME, self::TZID, self::TZOFFSETFROM,
380 self::TZOFFSETTO, self::TZURL, self::UID, self::URL, self::X_PROP,
381 ];
382 if( ! Util::isCompInList( $this->getCompType(), self::$SUBCOMPS )) {
383 $this->getUid();
384 $this->getDtstamp();
385 }
386 $output = [];
387 foreach( $PROPNAMES as $propName ) {
388 $propName2 = IcalBase::getInternalPropName( $propName );
389 switch( true ) {
390 case ( ! property_exists( $this, $propName2 )) :
391 break;
392 case ( empty( $this->{$propName2} )) :
393 break;
394 case ( self::X_PROP == $propName ) :
395 foreach( array_keys( $this->{$propName2}) as $propName3 ) {
396 $output[$propName3] = 1;
397 }
398 break;
399 case ( Util::isPropInList( $propName, self::$MPROPS2 )) :
400 $output[$propName] = count( $this->{$propName2} );
401 break;
402 default :
403 $output[$propName] = 1;
404 } // end switch
405 } // end foreach
406 return $output;
407 }
408
409 /**
410 * Set Vcalendar/component config
411 *
412 * @param mixed $config
413 * @param string $value
414 * @param bool $softUpdate
415 * @return static
416 * @throws InvalidArgumentException
417 * @since 2.29.4 - 2019-07-02
418 */
419 public function setConfig( $config, $value = null, $softUpdate = null )
420 {
421 static $ERRMSG9 = 'Invalid config value %s';
422 $isComponent = ( ! property_exists(
423 $this,
424 self::getInternalPropName( self::PRODID )
425 ));
426 if( is_array( $config )) {
427 $config = array_change_key_case( $config, CASE_UPPER );
428 foreach( $config as $cKey => $cValue ) {
429 $this->setConfig( $cKey, $cValue );
430 }
431 return $this;
432 }
433 $key = strtoupper( $config );
434 $subCfg = null;
435 switch( true ) {
436 case ( self::ALLOWEMPTY == $key ) :
437 $this->config[self::ALLOWEMPTY] = $value;
438 $subCfg = [ self::ALLOWEMPTY => $value ];
439 break;
440 case ( self::LANGUAGE == $key ) :
441 // set language for calendar component as defined in [RFC 1766]
442 $value = trim( $value );
443 if( empty( $this->config[self::LANGUAGE] ) || ! $softUpdate ) { // ??
444 $this->config[self::LANGUAGE] = $value;
445 }
446 $subCfg = [ self::LANGUAGE => $value ];
447 break;
448 case ( self::UNIQUE_ID == $key ) :
449 $value = trim( $value );
450 $this->config[self::UNIQUE_ID] = $value;
451 $subCfg = [ self::UNIQUE_ID => $value ];
452 break;
453 case ( $isComponent ) :
454 break;
455 default: // any invalid config key.. .
456 throw new InvalidArgumentException( sprintf( $ERRMSG9, $config )); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
457 } // end switch
458 if( ! empty( $subCfg ) && ! empty( $this->components )) {
459 foreach( $subCfg as $cfgkey => $cfgValue ) {
460 foreach( $this->components as $cix => $component ) {
461 $this->components[$cix]->setConfig( $cfgkey, $cfgValue, true );
462 }
463 }
464 }
465 return $this;
466 }
467
468 /**
469 * Assert empty value
470 *
471 * @param mixed $value
472 * @param string $propName
473 * @throws InvalidArgumentException
474 * @since 2.27.1 - 2018-12-12
475 */
476 protected function assertEmptyValue( $value, $propName )
477 {
478 static $ERRMSG = 'Empty %s value not allowed';
479 if( empty( $value ) && ! $this->getConfig( self::ALLOWEMPTY )) {
480 throw new InvalidArgumentException( sprintf( $ERRMSG, $propName )); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
481 }
482 }
483
484 /**
485 * Return index
486 *
487 * @param array $indexArr
488 * @param mixed $propName
489 * @param int $index
490 * @return bool true on success
491 * @since 2.27.1 - 2018-12-15
492 */
493 protected static function getIndex( array & $indexArr, $propName, $index = null )
494 {
495 if( is_null( $index )) {
496 $index = ( isset( $indexArr[$propName] )) ? $indexArr[$propName] + 2 : 1;
497 }
498 $index -= 1;
499 $indexArr[$propName] = $index;
500 return $index;
501 }
502
503 /**
504 * Return internal name for property
505 *
506 * @param string $propName
507 * @return string
508 * @since 2.27.1 - 2018-12-16
509 */
510 protected static function getInternalPropName( $propName )
511 {
512 $internalName = strtolower( $propName );
513 if( false !== strpos( $internalName, Util::$MINUS )) {
514 $internalName = implode( explode( Util::$MINUS, $internalName ));
515 }
516 return $internalName;
517 }
518
519 /**
520 * Return method from format and propName
521 *
522 * @param string $format
523 * @param string $propName
524 * @return string
525 * @since 2.27.14 - 2019-02-18
526 */
527 private static function getMethodName( $format, $propName )
528 {
529 return sprintf( $format, ucfirst( self::getInternalPropName( $propName )));
530 }
531
532 /**
533 * Return name for property delete-method
534 *
535 * @param string $propName
536 * @return string
537 * @since 2.27.1 - 2019-01-17
538 */
539 public static function getCreateMethodName( $propName )
540 {
541 static $FMT = 'create%s';
542 return self::getMethodName( $FMT, $propName );
543 }
544
545 /**
546 * Return name for property delete-method
547 *
548 * @param string $propName
549 * @return string
550 * @since 2.27.1 - 2018-12-12
551 */
552 public static function getDeleteMethodName( $propName )
553 {
554 static $FMT = 'delete%s';
555 return self::getMethodName( $FMT, $propName );
556 }
557
558 /**
559 * Return name for property get-method
560 *
561 * @param string $propName
562 * @return string
563 * @since 2.27.1 - 2018-12-12
564 */
565 public static function getGetMethodName( $propName )
566 {
567 static $FMT = 'get%s';
568 return self::getMethodName( $FMT, $propName );
569 }
570
571 /**
572 * Return name for property set-method
573 *
574 * @param string $propName
575 * @return string
576 * @since 2.27.1 - 2018-12-16
577 */
578 public static function getSetMethodName( $propName )
579 {
580 static $FMT = 'set%s';
581 return self::getMethodName( $FMT, $propName );
582 }
583
584 /**
585 * Recount property propIx, used at consecutive getProperty calls
586 *
587 * @param array $propArr component (multi-)property
588 * @param int $propIx getter counter
589 * @return bool true
590 * @since 2.27.1 - 2018-12-15
591 */
592 protected static function recountMvalPropix( array $propArr, & $propIx )
593 {
594 if( empty( $propArr )) {
595 return false;
596 }
597 $last = key( array_slice( $propArr, -1, 1, true ));
598 while( ! isset( $propArr[$propIx] ) && ( $last > $propIx )) {
599 $propIx++;
600 }
601 return true;
602 }
603
604 /**
605 * Check index and set (an indexed) content in a multiple value array
606 *
607 * @param array $valArr
608 * @param mixed $value
609 * @param array $params
610 * @param array $defaults
611 * @param int $index
612 * @since 2.22.23 - 2017-04-08
613 */
614 protected static function setMval(
615 & $valArr,
616 $value,
617 $params = null,
618 $defaults = null,
619 $index = null
620 ) {
621 if( ! is_array( $valArr )) {
622 $valArr = [];
623 }
624 if( empty( $params )) {
625 $params = [];
626 }
627 $params = ParameterFactory::setParams( $params, $defaults );
628 if( is_null( $index )) { // i.e. next
629 $valArr[] = [
630 Util::$LCvalue => $value,
631 Util::$LCparams => $params,
632 ];
633 return;
634 }
635 $index = $index - 1;
636 if( isset( $valArr[$index] )) { // replace
637 $valArr[$index] = [
638 Util::$LCvalue => $value,
639 Util::$LCparams => $params,
640 ];
641 return;
642 }
643 $valArr[$index] = [
644 Util::$LCvalue => $value,
645 Util::$LCparams => $params,
646 ];
647 ksort( $valArr ); // order
648 }
649
650 /**
651 * Delete calendar component multiProp property[ix]
652 *
653 * @param array $multiProp component (multi-)property
654 * @param mixed $propName
655 * @param int $propDelIx specific property in case of multiply occurrence
656 * @return bool true on success
657 * @since 2.27.1 - 2018-12-15
658 */
659 protected function deletePropertyM( & $multiProp, $propName, $propDelIx = null )
660 {
661 if( empty( $multiProp )) {
662 unset( $this->propDelIx[$propName] );
663 return false;
664 }
665 if( false === $propDelIx ) {
666 $propDelIx = null; // tidy up, altered default value
667 }
668 $propDelIx = self::getIndex( $this->propDelIx, $propName, $propDelIx );
669 if( isset( $multiProp[$propDelIx] )) {
670 unset( $multiProp[$propDelIx] );
671 }
672 if( empty( $multiProp )) {
673 $multiProp = null;
674 unset( $this->propDelIx[$propName] );
675 return false;
676 }
677 return true;
678 }
679
680 /**
681 * Get calendar component multpProp property
682 *
683 * @param array $multiProp component (multi-)property
684 * @param string $propName
685 * @param int $propIx specific property in case of multiply occurrence
686 * @param bool $inclParam
687 * @return bool|array
688 * @since 2.27.1 - 2018-12-15
689 */
690 protected function getPropertyM(
691 $multiProp,
692 $propName,
693 $propIx = null,
694 $inclParam = false
695 ) {
696 if( empty( $multiProp )) {
697 unset( $this->propIx[$propName] );
698 return false;
699 }
700 if( false === $propIx ) {
701 $propIx = null; // tidy up, altered default value
702 }
703 $propIx = self::getIndex( $this->propIx, $propName, $propIx );
704 if( ! self::recountMvalPropix( $multiProp, $propIx )) {
705 unset( $this->propIx[$propName] );
706 return false;
707 }
708 $this->propIx[$propName] = $propIx;
709 if( ! isset( $multiProp[$propIx] )) {
710 unset( $this->propIx[$propName] );
711 return false;
712 }
713 return ( $inclParam )
714 ? $multiProp[$propIx]
715 : $multiProp[$propIx][Util::$LCvalue];
716 }
717
718 /**
719 * Return number of components
720 *
721 * @return int
722 * @since 2.23.5 - 2017-04-13
723 */
724 public function countComponents()
725 {
726 return ( empty( $this->components )) ? 0 : count( $this->components );
727 }
728
729
730 /**
731 * Delete calendar subcomponent from component container
732 *
733 * @param mixed $arg1 ordno / component type / component uid
734 * @param mixed $arg2 ordno if arg1 = component type
735 * @return bool true on success, false on not found (last one deleted)
736 * @since 2.26.14 - 2019-02-25
737 * @todo Exception mgnt on unknown component
738 */
739 public function deleteComponent( $arg1, $arg2 = false )
740 {
741 if( ! isset( $this->components )) {
742 return false;
743 }
744 $argType = $index = null;
745 if( ctype_digit((string) $arg1 )) {
746 $argType = self::$INDEX;
747 $index = (int) $arg1 - 1;
748 }
749 elseif( property_exists( $this, self::getInternalPropName( self::PRODID )) &&
750 ( Util::isCompInList( $arg1, self::$CALCOMPS ) &&
751 ( 0 != strcasecmp( $arg1, self::VALARM )))) {
752 $argType = ucfirst( strtolower( $arg1 ));
753 $index = ( ! empty( $arg2 ) && ctype_digit((string) $arg2 ))
754 ? (( int ) $arg2 - 1 )
755 : 0;
756 }
757 $cix2dC = 0;
758 $remove = false;
759 foreach( $this->components as $cix => $component ) {
760 if(( self::$INDEX == $argType ) && ( $index == $cix )) {
761 unset( $this->components[$cix] );
762 $remove = true;
763 break;
764 }
765 elseif( $argType == $component->getCompType()) {
766 if( $index == $cix2dC ) {
767 unset( $this->components[$cix] );
768 $argType = strtolower( $argType );
769 if( isset( $this->compix[$argType] )) {
770 unset( $this->compix[$argType] );
771 }
772 $remove = true;
773 break;
774 }
775 $cix2dC++;
776 }
777 elseif( ! $argType &&
778 ! Util::isCompInList( $component->getCompType(), self::$SUBCOMPS ) &&
779 ( $arg1 == $component->getUid())) {
780 unset( $this->components[$cix] );
781 $remove = true;
782 break;
783 }
784 } // end foreach( $this->components as $cix => $component )
785 if( $remove ) {
786 $this->components = array_filter( $this->components );
787 return true;
788 }
789 return false;
790 }
791
792 /**
793 * Return next component index
794 *
795 * @return int
796 * @since 2.27.2 - 2018-12-21
797 */
798 protected function getNextComponentIndex()
799 {
800 return ( empty( $this->components ))
801 ? 0
802 : key( array_slice( $this->components, -1, 1, true )) + 1;
803 }
804
805 /**
806 * Add calendar component as subcomponent to container for subcomponents
807 *
808 * @param CalendarComponent $component
809 * @param mixed $arg1 ordno/component type/ component uid
810 * @param mixed $arg2 ordno if arg1 = component type
811 * @throws InvalidArgumentException
812 * @return static
813 * @since 2.27.3 - 2018-12-21
814 */
815 public function setComponent(
816 CalendarComponent $component,
817 $arg1 = false,
818 $arg2 = false
819 ) {
820 $component->setConfig( $this->getConfig(), false, true );
821 if( ! Util::isCompInList( $component->getCompType(), self::$SUBCOMPS )) {
822 /* make sure dtstamp and uid is set */
823 $component->getUid();
824 $component->getDtstamp();
825 }
826 if( ! $arg1 ) { // plain insert, last in chain
827 self::assertComponents( $this, $component );
828 $this->components[] = clone $component;
829 return $this;
830 }
831 $argType = $index = null;
832 if( ctype_digit((string) $arg1 )) { // index insert/replace
833 $argType = self::$INDEX;
834 $index = (int) $arg1 - 1;
835 }
836 elseif( Util::isCompInList( $arg1, self::$CALCOMPS )) {
837 $argType = ucfirst( strtolower( $arg1 ));
838 $index = ( ctype_digit((string) $arg2 )) ? ((int) $arg2 ) - 1 : 0;
839 }
840 // else if arg1 is set, arg1 must be an UID
841 $cix2sC = 0;
842 foreach( $this->components as $cix => $component2 ) {
843 if( empty( $component2 )) {
844 continue;
845 }
846 if(( self::$INDEX == $argType ) && ( $index == $cix )) {
847 // index insert/replace
848 $this->components[$cix] = clone $component;
849 return $this;
850 }
851 elseif( $argType == $component2->getCompType()) {
852 // component Type index insert/replace
853 if( $index == $cix2sC ) {
854 $this->components[$cix] = clone $component;
855 return $this;
856 }
857 $cix2sC++;
858 }
859 elseif( ! $argType &&
860 ! Util::isCompInList( $component2->getCompType(), self::$SUBCOMPS ) &&
861 ( $arg1 == $component2->getUid())) { // UID insert/replace
862 $this->components[$cix] = clone $component;
863 return $this;
864 }
865 } // end foreach
866 if( self::$INDEX == $argType ) { // arg1=index and not found.. . insert at index .. .
867 $this->components[$index] = clone $component;
868 ksort( $this->components, SORT_NUMERIC );
869 }
870 else { /* not found.. . insert last in chain anyway .. .*/
871 $this->components[] = clone $component;
872 }
873 return $this;
874 }
875
876 /**
877 * Assert base components
878 *
879 * @param IcalBase $component
880 * @throws InvalidArgumentException
881 * @since 2.27.6 - 2018-12-28
882 */
883 protected static function assertBaseComponents( IcalBase $component )
884 {
885 static $ERRMSG = 'Invalid component type \'%s\'';
886 $compType = $component->getCompType();
887 if( ! Util::isCompInList( $compType, self::$VCOMPS ) &&
888 ( self::VTIMEZONE != $compType )) {
889 throw new InvalidArgumentException( sprintf( $ERRMSG, $compType )); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
890 }
891 }
892
893 /**
894 * Assert components
895 *
896 * @param IcalBase $comp
897 * @param CalendarComponent $subComp
898 * @throws InvalidArgumentException
899 * @since 2.27.6 - 2018-12-28
900 */
901 private static function assertComponents(
902 IcalBase $comp,
903 CalendarComponent $subComp
904 ) {
905 static $MSG = 'Unknown component %s';
906 $type = $comp->getCompType();
907 $subType = $subComp->getCompType();
908 switch( $type ) {
909 case Vcalendar::VCALENDAR :
910 self::assertBaseComponents( $subComp );
911 break;
912 case self::VTIMEZONE :
913 switch( $subType ) {
914 case self::STANDARD :
915 break 2;
916 case self::DAYLIGHT :
917 break 2;
918 default :
919 throw new InvalidArgumentException( sprintf( $MSG, $subType )); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
920 }
921 break;
922 case self::VEVENT :
923 // fall through
924 case self::VTODO :
925 switch( $subType ) {
926 case self::VALARM :
927 break 2;
928 default :
929 throw new InvalidArgumentException( sprintf( $MSG, $subType )); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
930 }
931 break;
932 case self::VFREEBUSY :
933 break;
934 case self::VJOURNAL :
935 break;
936 default :
937 throw new InvalidArgumentException( sprintf( $MSG, $type )); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
938 } // end switch
939 }
940 }
941