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 / CalendarComponent.php

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

567 lines 20.4 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.3
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 Exception;
34 use Kigkonsult\Icalcreator\Util\CalAddressFactory;
35 use Kigkonsult\Icalcreator\Util\RecurFactory;
36 use Kigkonsult\Icalcreator\Util\RexdateFactory;
37 use Kigkonsult\Icalcreator\Util\StringFactory;
38 use Kigkonsult\Icalcreator\Util\Util;
39 use UnexpectedValueException;
40
41 use function array_keys;
42 use function count;
43 use function ctype_digit;
44 use function end;
45 use function explode;
46 use function get_class;
47 use function implode;
48 use function is_array;
49 use function is_null;
50 use function ksort;
51 use function method_exists;
52 use function property_exists;
53 use function sprintf;
54 use function strcasecmp;
55 use function stripos;
56 use function strpos;
57 use function strtolower;
58 use function strtoupper;
59 use function substr;
60 use function trim;
61 use function ucfirst;
62
63 /**
64 * Parent class for calendar components
65 *
66 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
67 * @since 2.30.3 - 2021-02-15
68 */
69 abstract class CalendarComponent extends IcalBase
70 {
71 /**
72 * @var array compoment sort params
73 */
74 public $srtk = null;
75
76 /**
77 * @var string component number
78 */
79 public $cno = 0;
80
81 /**
82 * @var string misc. values
83 */
84 protected static $FMTBEGIN = "BEGIN:%s\r\n";
85 protected static $FMTEND = "END:%s\r\n";
86
87 /**
88 * @var string
89 */
90 protected static $compSgn = 'xx';
91
92 /**
93 * Constructor for calendar component
94 *
95 * @param array $config
96 * @since 2.27.14 - 2019-07-03
97 */
98 public function __construct( $config = [] )
99 {
100 static $objectNo = 0;
101 $class = get_class( $this );
102 $this->compType = ucfirst(
103 strtolower( StringFactory::afterLast( StringFactory::$BS2, $class ))
104 );
105 $this->cno = $class::$compSgn . ++$objectNo;
106 $this->setConfig( $config );
107 }
108
109 /**
110 * Returns calendar property unique values
111 *
112 * For ATTENDEE, CATEGORIES, CONTACT, RELATED_TO or RESOURCES (keys)
113 * and for each, number of occurrence (values)
114 *
115 * @param string $propName
116 * @param array $output incremented result array
117 * @since 2.29.17 - 2020-01-25
118 */
119 public function getProperties( $propName, & $output )
120 {
121 if( empty( $output )) {
122 $output = [];
123 }
124 if( ! Util::isPropInList( $propName, self::$MPROPS1 )) {
125 return;
126 }
127 $method = parent::getGetMethodName( $propName );
128 if( ! method_exists( $this, $method )) {
129 return;
130 }
131 while( false !== ( $content = $this->{$method}())) {
132 if( empty( $content )) {
133 continue;
134 }
135 if( is_array( $content )) {
136 foreach( $content as $part ) {
137 if( false !== strpos( $part, Util::$COMMA )) {
138 $part = explode( Util::$COMMA, $part );
139 foreach( $part as $contentPart ) {
140 $contentPart = trim( $contentPart );
141 if( ! empty( $contentPart )) {
142 if( ! isset( $output[$contentPart] )) {
143 $output[$contentPart] = 1;
144 }
145 else {
146 $output[$contentPart] += 1;
147 }
148 }
149 } // end foreach
150 }
151 else {
152 $part = trim( $part );
153 if( ! isset( $output[$part] )) {
154 $output[$part] = 1;
155 }
156 else {
157 $output[$part] += 1;
158 }
159 }
160 } // end foreach
161 } // end if( is_array( $content ))
162 elseif( false !== strpos( $content, Util::$COMMA )) {
163 $content = explode( Util::$COMMA, $content );
164 foreach( $content as $contentPart ) {
165 $contentPart = trim( $contentPart );
166 if( ! empty( $contentPart )) {
167 if( ! isset( $output[$contentPart] )) {
168 $output[$contentPart] = 1;
169 }
170 else {
171 $output[$contentPart] += 1;
172 }
173 }
174 } // end foreach
175 } // end elseif( false !== strpos( $content, Util::$COMMA ))
176 else {
177 $content = trim( $content );
178 if( ! empty( $content )) {
179 if( ! isset( $output[$content] )) {
180 $output[$content] = 1;
181 }
182 else {
183 $output[$content] += 1;
184 }
185 }
186 }
187 } // end while
188 ksort( $output );
189 }
190
191 /*
192 * @var string
193 */
194 protected static $NLCHARS = '\n';
195 protected static $BEGIN = 'BEGIN:';
196
197 /**
198 * Parse data into component properties
199 *
200 * @param string|array $unParsedText strict rfc2445 formatted, single property string or array of strings
201 * @return static
202 * @throws Exception
203 * @throws UnexpectedValueException;
204 * @since 2.29.3 - 2019-06-20
205 * @// todo report invalid properties, Exception.. ??
206 */
207 public function parse( $unParsedText = null )
208 {
209 $rows = $this->parse1prepInput( $unParsedText );
210 $this->parse2intoComps( $rows );
211 $this->parse3thisProperties();
212 $this->parse4subComps();
213 return $this;
214 }
215
216 /**
217 * Return rows to parse
218 *
219 * @param string|array $unParsedText strict rfc2445 formatted, single property string or array of strings
220 * @return array
221 * @since 2.29.3 - 2019-06-20
222 */
223 private function parse1prepInput( $unParsedText = null )
224 {
225 switch( true ) {
226 case ( ! empty( $unParsedText )) :
227 $arrParse = false;
228 if( is_array( $unParsedText ) ) {
229 $unParsedText = implode(
230 self::$NLCHARS . Util::$CRLF,
231 $unParsedText
232 );
233 $arrParse = true;
234 }
235 $rows = StringFactory::convEolChar( $unParsedText );
236 if( $arrParse ) {
237 foreach( $rows as $lix => $row ) {
238 $rows[$lix] = StringFactory::trimTrailNL( $row );
239 }
240 }
241 break;
242 case empty( $this->unparsed ) :
243 $rows = [];
244 break;
245 default :
246 $rows = $this->unparsed;
247 break;
248 } // end switch
249 /* skip leading (empty/invalid) lines */
250 foreach( $rows as $lix => $row ) {
251 if( false !== ( $pos = stripos( $row, self::$BEGIN ))) {
252 $rows[$lix] = substr( $row, $pos );
253 break;
254 }
255 $tst = trim( $row );
256 if(( self::$NLCHARS == $tst ) || empty( $tst )) {
257 unset( $rows[$lix] );
258 }
259 } // end foreach
260 return $rows;
261 }
262
263 /**
264 * Parse into this and sub-components data
265 *
266 * @param array $rows
267 * @since 2.29.3 - 2019-08-26
268 */
269 private function parse2intoComps( array $rows )
270 {
271 static $ENDALARM = 'END:VALARM';
272 static $ENDDAYLIGHT = 'END:DAYLIGHT';
273 static $ENDSTANDARD = 'END:STANDARD';
274 static $END = 'END:';
275 static $BEGINVALARM = 'BEGIN:VALARM';
276 static $BEGINSTANDARD = 'BEGIN:STANDARD';
277 static $BEGINDAYLIGHT = 'BEGIN:DAYLIGHT';
278 $this->unparsed = [];
279 $comp = $this;
280 $compSync = $subSync = 0;
281 foreach( $rows as $lix => $row ) {
282 switch( true ) {
283 case ( StringFactory::startsWith( $row, $ENDALARM ) ||
284 StringFactory::startsWith( $row, $ENDDAYLIGHT ) ||
285 StringFactory::startsWith( $row, $ENDSTANDARD )) :
286 if( 1 != $subSync ) {
287 throw new UnexpectedValueException(
288 self::getErrorMsg( $rows, $lix )
289 );
290 }
291 $subSync -= 1;
292 break;
293 case StringFactory::startsWith( $row, $END ) :
294 if( 1 != $compSync ) { // end:<component>
295 throw new UnexpectedValueException(
296 self::getErrorMsg( $rows, $lix )
297 );
298 }
299 $compSync -= 1;
300 break 2; /* skip trailing empty lines.. */
301 case StringFactory::startsWith( $row, $BEGINVALARM ) :
302 $comp = $this->newValarm();
303 $subSync += 1;
304 break;
305 case StringFactory::startsWith( $row, $BEGINSTANDARD ) :
306 $comp = $this->newStandard();
307 $subSync += 1;
308 break;
309 case StringFactory::startsWith( $row, $BEGINDAYLIGHT ) :
310 $comp = $this->newDaylight();
311 $subSync += 1;
312 break;
313 case StringFactory::startsWith( $row, self::$BEGIN ) :
314 $compSync += 1; // begin:<component>
315 break;
316 default :
317 $comp->unparsed[] = $row;
318 break;
319 } // end switch( true )
320 } // end foreach( $rows as $lix => $row )
321 }
322
323 /**
324 * Parse this properties
325 *
326 * @since 2.30.3 - 2021-02-15
327 * @todo report invalid properties ??
328 */
329 private function parse3thisProperties()
330 {
331 /* concatenate property values spread over several lines */
332 $this->unparsed = StringFactory::concatRows( $this->unparsed );
333 /* parse each property 'line' */
334 foreach( $this->unparsed as $lix => $row ) {
335 /* get propname + split property name and opt.params and value */
336 list( $propName, $row ) = StringFactory::getPropName( $row );
337 if( StringFactory::isXprefixed( $propName )) {
338 list( $value, $propAttr ) = StringFactory::splitContent( $row );
339 $this->setXprop(
340 $propName,
341 StringFactory::strunrep( $value ),
342 $propAttr
343 );
344 continue;
345 }
346 if( ! property_exists( $this, parent::getInternalPropName( $propName ))) {
347 continue; // todo report invalid properties ??
348 } // skip property names not in comp
349 /* separate attributes from value */
350 list( $value, $propAttr ) = StringFactory::splitContent( $row, $propName );
351 if(( self::$NLCHARS == strtolower( substr( $value, -2 ))) &&
352 ! Util::isPropInList( $propName, self::$TEXTPROPS ) &&
353 ( ! StringFactory::isXprefixed( $propName ))) {
354 $value = StringFactory::trimTrailNL( $value );
355 }
356 /* call set<Propname>(.. . */
357 $method = parent::getSetMethodName( $propName );
358 switch( strtoupper( $propName )) {
359 case self::ATTENDEE :
360 list( $value, $propAttr ) =
361 CalAddressFactory::parseAttendee( $value, $propAttr );
362 $this->{$method}( $value, $propAttr );
363 break;
364 case self::CATEGORIES : // fall through
365 case self::RESOURCES : // fall through
366 case self::COLOR : // fall through
367 case self::COMMENT : // fall through
368 case self::CONTACT : // fall through
369 case self::DESCRIPTION : // fall through
370 case self::LOCATION : // fall through
371 case self::SUMMARY :
372 if( empty( $value )) {
373 $propAttr = null;
374 }
375 $this->{$method}( StringFactory::strunrep( $value ), $propAttr );
376 break;
377 case self::REQUEST_STATUS :
378 $values = explode( Util::$SEMIC, $value, 3 );
379 $values[1] = ( isset( $values[1] ))
380 ? StringFactory::strunrep( $values[1] )
381 : null;
382 $values[2] = ( isset( $values[2] ))
383 ? StringFactory::strunrep( $values[2] )
384 : null;
385 $this->{$method}( $values[0], $values[1], $values[2], $propAttr );
386 break;
387 case self::FREEBUSY :
388 $class = get_class( $this );
389 list( $fbtype, $values, $propAttr ) =
390 $class::parseFreebusy( $value, $propAttr );
391 $this->{$method}( $fbtype, $values, $propAttr );
392 break;
393 case self::GEO :
394 $values = explode( Util::$SEMIC, $value, 2 );
395 if( 2 > count( $values )) {
396 $values[1] = null;
397 }
398 $this->{$method}( $values[0], $values[1], $propAttr );
399 break;
400 case self::EXDATE :
401 $values = ( empty( $value ))
402 ? null
403 : explode( Util::$COMMA, $value );
404 $this->{$method}( $values, $propAttr );
405 break;
406 case self::RDATE :
407 list( $values, $propAttr ) =
408 RexdateFactory::parseRexdate( $value, $propAttr );
409 $this->{$method}( $values, $propAttr );
410 break;
411 case self::EXRULE : // fall through
412 case self::RRULE :
413 $recur = RecurFactory::parseRexrule( $value );
414 $this->{$method}( $recur, $propAttr );
415 break;
416 case self::ACTION : // fall through
417 case self::STATUS : // fall through
418 case self::TRANSP : // fall through
419 case self::UID : // fall through
420 case self::TZID : // fall through
421 case self::RELATED_TO : // fall through
422 case self::TZNAME :
423 $value = StringFactory::strunrep( $value );
424 // fall through
425 default:
426 $this->{$method}( $value, $propAttr );
427 break;
428 } // end switch( $propName.. .
429 } // end foreach( $this->unparsed as $lix => $row )
430 unset( $this->unparsed );
431 }
432
433 /**
434 * Parse sub-components
435 *
436 * @since 2.29.3 - 2019-06-20
437 */
438 private function parse4subComps()
439 {
440 if( empty( $this->countComponents())) {
441 return;
442 }
443 foreach( array_keys( $this->components ) as $ckey ) {
444 if( ! empty( $this->components[$ckey] ) &&
445 ! empty( $this->components[$ckey]->unparsed )) {
446 $this->components[$ckey]->parse();
447 }
448 } // end foreach
449 }
450
451 /**
452 * Return error message
453 *
454 * @param array $rows
455 * @param int $lix
456 * @return string
457 * @since 2.26.3 - 2018-12-28
458 */
459 private static function getErrorMsg( array $rows, $lix )
460 {
461 static $ERR = 'Calendar component content not in sync (row %d)%s%s';
462 return sprintf( $ERR, $lix, PHP_EOL, implode( PHP_EOL, $rows ));
463 }
464
465 /**
466 * Return calendar component subcomponent from component container
467 *
468 * @param mixed $arg1 ordno/component type/ component uid
469 * @param mixed $arg2 ordno if arg1 = component type
470 * @return mixed CalendarComponent|bool
471 * @since 2.26.1 - 2018-11-17
472 * @todo throw InvalidArgumentException on unknown component
473 */
474 public function getComponent( $arg1 = null, $arg2 = null )
475 {
476 if( empty( $this->components )) {
477 return false;
478 }
479 $index = $argType = null;
480 switch( true ) {
481 case ( is_null( $arg1 )) :
482 $argType = self::$INDEX;
483 $this->compix[self::$INDEX] = ( isset( $this->compix[self::$INDEX] ))
484 ? $this->compix[self::$INDEX] + 1 : 1;
485 $index = $this->compix[self::$INDEX];
486 break;
487 case ( ctype_digit((string) $arg1 )) :
488 $argType = self::$INDEX;
489 $index = (int) $arg1;
490 $this->compix = [];
491 break;
492 case ( Util::isCompInList( $arg1, self::$SUBCOMPS )) : // class name
493 unset( $this->compix[self::$INDEX] );
494 $argType = strtolower( $arg1 );
495 if( is_null( $arg2 )) {
496 $index = $this->compix[$argType] =
497 ( isset( $this->compix[$argType] ))
498 ? $this->compix[$argType] + 1
499 : 1;
500 }
501 else {
502 $index = (int) $arg2;
503 }
504 break;
505 } // end switch
506 $index -= 1;
507 $ckeys = array_keys( $this->components );
508 if( ! empty( $index ) && ( $index > end( $ckeys ))) {
509 return false;
510 }
511 $cix2gC = 0;
512 foreach( $ckeys as $cix ) {
513 if( empty( $this->components[$cix] )) {
514 continue;
515 }
516 if(( self::$INDEX == $argType ) && ( $index == $cix )) {
517 return clone $this->components[$cix];
518 }
519 elseif( 0 == strcasecmp(
520 $this->components[$cix]->getCompType(),
521 $argType
522 )) {
523 if( $index == $cix2gC ) {
524 return clone $this->components[$cix];
525 }
526 $cix2gC++;
527 }
528 } // end foreach
529 /* not found.. . */
530 $this->compix = [];
531 return false;
532 }
533
534 /**
535 * Add calendar component as subcomponent to container for subcomponents
536 *
537 * @param CalendarComponent $component
538 * @return static
539 * @since 1.x.x - 2007-04-24
540 */
541 public function addSubComponent( CalendarComponent $component )
542 {
543 $this->setComponent( $component );
544 return $this;
545 }
546
547 /**
548 * Return formatted output for subcomponents
549 *
550 * @return string
551 * @since 2.27.2 - 2018-12-21
552 * @throws Exception (on Valarm/Standard/Daylight) err)
553 */
554 public function createSubComponent()
555 {
556 $config = $this->getConfig();
557 $output = null;
558 foreach( array_keys( $this->components ) as $cix ) {
559 if( ! empty( $this->components[$cix] )) {
560 $this->components[$cix]->setConfig( $config, false, true );
561 $output .= $this->components[$cix]->createComponent();
562 }
563 }
564 return $output;
565 }
566 }
567