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 / Util / SelectFactory.php

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

1,217 lines 52.5 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
31 namespace Kigkonsult\Icalcreator\Util;
32
33 use DateInterval;
34 use DateTime;
35 use DateTimeInterface;
36 use Exception;
37 use Kigkonsult\Icalcreator\CalendarComponent;
38 use Kigkonsult\Icalcreator\Vcalendar;
39 use RuntimeException;
40
41 use function array_change_key_case;
42 use function array_keys;
43 use function array_unique;
44 use function count;
45 use function in_array;
46 use function is_array;
47 use function is_null;
48 use function ksort;
49 use function method_exists;
50 use function sprintf;
51 use function stripos;
52 use function strtolower;
53 use function substr;
54 use function ucfirst;
55 use function usort;
56
57 /**
58 * iCalcreator geo support class
59 *
60 * @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se>
61 * @since 2.30.10 - 2021-12-02
62 */
63 class SelectFactory
64 {
65 /**
66 * @var string component end date properties
67 * @static
68 */
69 private static $DTENDEXIST = 'dtendExist';
70 private static $DUEEXIST = 'dueExist';
71 private static $DURATIONEXIST = 'durationExist';
72 private static $ENDALLDAYEVENT = 'endAllDayEvent';
73
74 /**
75 * Return selected components from calendar on date or selectOption basis
76 *
77 * DTSTART MUST be set for every component.
78 * No check of date.
79 *
80 * @param Vcalendar $calendar
81 * @param int|array|DateTimeInterface $startY (int) start Year, default current Year
82 * ALT. (object) DateTimeInterface start date
83 * ALT. array selectOptions ( *[ <propName> => <uniqueValue> ] )
84 * @param int|DateTimeInterface $startM (int) start Month, default current Month
85 * ALT. (object) DateTimeInterface end date
86 * @param int $startD start Day, default current Day
87 * @param int $endY end Year, default $startY
88 * @param int $endM end Month, default $startM
89 * @param int $endD end Day, default $startD
90 * @param mixed $cType calendar component type(-s), default false=all else string/array type(-s)
91 * @param bool $flat false (default) => output : array[Year][Month][Day][]
92 * true => output : array[] (ignores split)
93 * @param bool $any true (default) - select component(-s) that occurs within period
94 * false - only component(-s) that starts within period
95 * @param bool $split true (default) - one component copy every DAY it occurs during the
96 * period (implies flat=false)
97 * false - one occurance of component only in output array
98 * @return array|false array on success, bool false on select error
99 * @throws RuntimeException
100 * @throws Exception
101 * @static
102 * @since 2.30.10 - 2021-12-01
103 */
104 public static function selectComponents(
105 Vcalendar $calendar,
106 $startY = null,
107 $startM = null,
108 $startD = null,
109 $endY = null,
110 $endM = null,
111 $endD = null,
112 $cType = null,
113 $flat = null,
114 $any = null,
115 $split = null
116 ) {
117 static $P1D = 'P1D';
118 static $YMDHIS2 = 'YmdHis';
119 static $PRA = '%a';
120 static $YMDn = 'Ymd';
121 static $HIS = '%02d%02d%02d';
122 static $DAYOFDAYS = 'day %d of %d';
123 static $SORTER = [ SortFactory::class, 'cmpfcn' ];
124 /* check if empty calendar */
125 if( 1 > $calendar->countComponents()) {
126 return false;
127 }
128 if( is_array( $startY )) {
129 return self::selectComponents2( $calendar, $startY );
130 }
131 /* assert boundary dates */
132 $argStart = ( $startY instanceof DateTimeInterface ) ? clone $startY : null;
133 $argEnd = ( $startM instanceof DateTimeInterface ) ? clone $startM : null;
134 self::assertDateArguments( $startY, $startM, $startD, $endY, $endM, $endD );
135 /* assert component types */
136 $cType = self::assertComponentTypes( $cType );
137 /* assert bool args */
138 self:: assertBoolArguments( $flat, $any, $split );
139 /* iterate components */
140 $result = [];
141 $calendar = clone $calendar;
142 $calendar->sort( Vcalendar::UID );
143 $compUIDold = null;
144 $exdateList = $recurIdList = [];
145 $INTERVAL_P1D = DateIntervalFactory::factory( $P1D );
146 while( $component = $calendar->getComponent()) {
147 if( empty( $component )) {
148 continue;
149 }
150 /* skip invalid type components */
151 if( ! Util::isCompInList( $component->getCompType(), $cType )) {
152 continue;
153 }
154 /* select start from dtstart or due if dtstart is missing */
155 if(( false === ( $prop = $component->getDtstart( true ))) &&
156 (( Vcalendar::VTODO === $component->getCompType()) &&
157 ( false === ( $prop = $component->getDue( true ))))) {
158 continue;
159 }
160 $compStart = UtilDateTime::factory(
161 $prop[Util::$LCvalue],
162 $prop[Util::$LCparams]
163 );
164 $dtStartTz = $compStart->getTimezoneName();
165 if( ParameterFactory::isParamsValueSet( $prop, Vcalendar::DATE )) {
166 $compStartHis = null;
167 }
168 else {
169 $his = $compStart->getTime();
170 $compStartHis = sprintf( $HIS, $his[0], $his[1], $his[2] );
171 }
172 /* get end date from dtend/due/duration properties */
173 $compEnd = self::getCompEndDate( $component, $dtStartTz );
174 if( empty( $compEnd )) {
175 $compDuration = null; // DateInterval: no duration
176 $compEnd = clone $compStart;
177 $compEnd->setTime( 23, 59, 59 ); // 23:59:59 the same day as start
178 }
179 else {
180 if( $compEnd->format( $YMDn ) < $compStart->format( $YMDn )) { // MUST be after start date!!
181 $compEnd = $compStart->getClone();
182 $compEnd->setTime( 23, 59, 59 ); // 23:59:59 the same day as start or ???
183 }
184 $compDuration = $compStart->diff( $compEnd ); // DateInterval
185 }
186 /* get UID */
187 $compUID = $component->getUid();
188 if( $compUIDold !== $compUID ) {
189 $compUIDold = $compUID;
190 $exdateList = $recurIdList = [];
191 }
192 $compType = $component->getCompType();
193 $isFreebusyCompType = ( Vcalendar::VFREEBUSY === $compType );
194 /**
195 * Component with recurrence-id sorted before any rDate/rRule comp
196 * to alter date(time) when found in dtstart/recurlist.
197 * (Note, a missing sequence (expected here) is the same as sequence=0 so don't test for sequence.)
198 * Highest sequence always last, will replace any previous
199 */
200 $recurId = null;
201 if( ! $isFreebusyCompType &&
202 ( false !== ( $prop = $component->getRecurrenceid( true )))) {
203 $recurId = UtilDateTime::factory( $prop[Util::$LCvalue], $prop[Util::$LCparams], $dtStartTz );
204 $rangeSet = Util::issetKeyAndEquals(
205 $prop[Util::$LCparams],
206 Vcalendar::RANGE,
207 Vcalendar::THISANDFUTURE
208 );
209 $recurIdList[$recurId->key] = [
210 $compStart->getClone(),
211 $compEnd->getClone(),
212 $compDuration, // DateInterval
213 $rangeSet,
214 clone $component,
215 ]; // save recur day to altered YmdHis/duration/range
216 continue; // ignore all but first prop in the recurrence_id component
217 } // end HAS recurrence-id/sequence
218 ksort( $recurIdList, SORT_STRING );
219 self::updateRecurrIdComps( $component, $recurIdList );
220 /* prepare */
221 if( null !== $argStart ) {
222 $fcnStart = UtilDateTime::factory( $argStart );
223 }
224 else {
225 $fcnStart = $compStart->getClone();
226 $fcnStart->setDate( (int)$startY, (int)$startM, (int)$startD );
227 $fcnStart->setTime( 0, 0 );
228 }
229 $fcnStartYmd = $fcnStart->format( $YMDn );
230 $fcnStartYmdHis = $fcnStart->format( $YMDHIS2 );
231 if( null !== $argEnd ) {
232 $fcnEnd = UtilDateTime::factory( $argEnd );
233 }
234 else {
235 $fcnEnd = $compEnd->getClone();
236 $fcnEnd->setDate( (int)$endY, (int)$endM, (int)$endD );
237 $fcnEnd->setTime( 23, 59, 59 );
238 }
239 /* set up work dates */
240 $workStart = $compStart->getClone();
241 $duration = ( empty( $compDuration )) ? $INTERVAL_P1D : $compDuration; // DateInterval
242 $workStart->sub( $duration );
243 $workEnd = clone $fcnEnd;// !!
244 $workEnd->add( $duration );
245 /* make a list of optional exclude dates for component occurence from exrule and exdate */
246 if( ! $isFreebusyCompType ) {
247 self::getAllEXRULEdates(
248 $component, $exdateList,
249 $dtStartTz, $compStart, $workStart, $workEnd,
250 $compStartHis
251 );
252 self::getAllEXDATEdates( $component, $exdateList, $dtStartTz );
253 }
254 /* select only components within.. . */
255 $xRecurrence = 1;
256 // (dt)start within the period
257 if(( ! $any && self::inScope( $compStart, $fcnStart, $compStart, $fcnEnd, $compStart->dateFormat )) ||
258 // occurs within the period
259 ( $any && self::inScope( $fcnEnd, $compStart, $fcnStart, $compEnd, $compStart->dateFormat ))) {
260 $propEndName = ( isset( $compEnd->SCbools[self::$DUEEXIST] ))
261 ? Vcalendar::X_CURRENT_DUE
262 : Vcalendar::X_CURRENT_DTEND;
263 /* add the selected component (WITHIN valid dates) to output array */
264 if( $flat ) { // any=true/false, ignores split
265 if( empty( $recurId )) {
266 $result[$compUID] = clone $component;
267 } // copy original to output (but not anyone with recurrence-id)
268 }
269 elseif( $split ) { // split the original component
270 $rStart = ( $compStart->format( $YMDHIS2 ) < $fcnStart->format( $YMDHIS2 ))
271 ? $fcnStart->getClone()
272 : $compStart->getClone();
273 $rEnd = ( $compEnd->format( $YMDHIS2 ) > $fcnEnd->format( $YMDHIS2 ))
274 ? $fcnEnd->getClone()
275 : $compEnd->getClone();
276 $k = $rStart->key;
277 if( ! isset( $exdateList[$k] )) { // not excluded in exrule/exdate
278 if( isset( $recurIdList[$k] )) { // change start day to new YmdHis/duration
279 $rStart = $recurIdList[$k][0]->getClone(); // UtilDateTime
280 $startHis = $rStart->getTime();
281 $rEnd = $rStart->getClone();
282 if( ! empty( $recurIdList[$k][2] )) { // DateInterval
283 $rEnd->add( $recurIdList[$k][2] );
284 }
285 elseif( ! empty( $compDuration )) { // DateInterval
286 $rEnd->add( $compDuration );
287 }
288 $endHis = $rEnd->getTime();
289 $component2 = ( isset( $recurIdList[$k][4] ))
290 ? clone $recurIdList[$k][4]
291 : clone $component;
292 } // end in recurIdList
293 else {
294 $startHis = $compStart->getTime();
295 $endHis = $compEnd->getTime();
296 $component2 = clone $component;
297 }
298 $cnt = 0;
299 $tDate = clone $compStart;
300 while( $tDate->format( $YMDn ) < $fcnStart->format( $YMDn )) {
301 $cnt++; // in case fcnStart DAY AFTER cmpStart
302 $tDate->add( $INTERVAL_P1D );
303 }
304 // exclude any recurrence START date, found in exdatelist or recurrIdList
305 // but accept the reccurence-id comp itself
306 // count the days (incl start day)
307 $occurenceDays = 1 +
308 (int) $compStart->getClone()
309 ->setTime( 0, 0 )
310 ->diff( $compEnd->getClone()->setTime( 0, 0 ))
311 ->format( $PRA );
312 $rEndYmd = $rEnd->format( $YMDn );
313 while( $rStart->format( $YMDn ) <= $rEndYmd ) {
314 ++$cnt;
315 if( 1 < $occurenceDays ) {
316 $component2->setXprop(
317 Vcalendar::X_OCCURENCE,
318 sprintf( $DAYOFDAYS, $cnt, $occurenceDays )
319 );
320 }
321 if( 1 < $cnt ) {
322 $rStart->setTime( 0, 0 );
323 }
324 else {
325 // make sure to exclude start day from the recurrence pattern
326 $rStart->setTime( $startHis[0], $startHis[1], $startHis[2] );
327 $exdateList[$rStart->key] = $compDuration; // DateInterval
328 }
329 $component2->setXprop(
330 Vcalendar::X_CURRENT_DTSTART,
331 $rStart->format( $compStart->dateFormat )
332 );
333 if( ! empty( $compDuration )) { // DateInterval
334 $rWdate = $rStart->getClone();
335 self::setDurationEndTime( $rWdate, $rEnd, $cnt, $occurenceDays, $endHis );
336 $component2->setXprop( $propEndName, $rWdate->format( $compEnd->dateFormat ));
337 } // end if
338 list( $xY, $xM, $xD ) = self::getArrayYMDkeys( $rStart );
339 if( ! isset( $result[$xY][$xM][$xD][$compUID] )) {
340 $result[$xY][$xM][$xD][$compUID] = [];
341 }
342 $result[$xY][$xM][$xD][$compUID][] = clone $component2; // copy to output
343 $rStart->add( $INTERVAL_P1D );
344 } // end while(( $rStart->format( 'Ymd' ) < $rEnd->format( 'Ymd' ))
345 } // end if( ! isset( $exdateList[$rStart->key] ))
346 } // end elseif( $split ) - else use component date
347 else { // !$flat && !$split, i.e. no flat array and DTSTART within period
348 if( isset( $recurIdList[$compStart->key] )) {
349 $rStart = $recurIdList[$compStart->key][0]->getClone();
350 $rEnd = $recurIdList[$compStart->key][1]->getClone();
351 $component2 = ( isset( $recurIdList[$compStart->key][4] ))
352 ? $recurIdList[$compStart->key][4]
353 : clone $component;
354 }
355 else {
356 $rStart = $compStart->getClone();
357 $rEnd = $compEnd->getClone();
358 $component2 = clone $component;
359 }
360 if( ! $any || ! isset( $exdateList[$rStart->key] )) {
361 // exclude any recurrence date, found in exdatelist
362 $component2->setXprop( Vcalendar::X_CURRENT_DTSTART, $rStart->format( $compStart->dateFormat ));
363 if( ! empty( $compDuration )) { // DateInterval
364 $component2->setXprop( $propEndName, $rEnd->format( $compEnd->dateFormat ));
365 } // end if
366 list( $xY, $xM, $xD ) = self::getArrayYMDkeys( $rStart );
367 if( ! isset( $result[$xY][$xM][$xD][$compUID] )) {
368 $result[$xY][$xM][$xD][$compUID] = [];
369 }
370 $result[$xY][$xM][$xD][$compUID][] = clone $component2; // copy to output
371 }
372 } // end else
373 } // end (dt)start within the period OR occurs within the period
374 /* *************************************************************
375 if 'any' components, check components with reccurrence rules, removing all excluding dates
376 *********************************************************** */
377 if( true === $any ) {
378 $recurList = [];
379 if( ! $isFreebusyCompType ) {
380 /* make a list of optional repeating dates for component occurence, rrule, rdate */
381 self::getAllRRULEdates(
382 $component, $recurList,
383 $dtStartTz, $compStart, $workStart, $workEnd,
384 $compStartHis, $exdateList, $compDuration
385 );
386 $workStart = $fcnStart->getClone();
387 $workStart->sub( ( empty( $compDuration ))
388 ? $INTERVAL_P1D
389 : $compDuration
390 );
391 self::getAllRDATEdates(
392 $component, $recurList,
393 $dtStartTz, $workStart, $fcnEnd, $compStart->dateFormat,
394 $exdateList, $compStartHis, $compDuration
395 );
396 unset( $workStart );
397 ksort( $recurList, SORT_STRING );
398 } // end if( Vcalendar::VFREEBUSY != $compType )
399 /* output all remaining components in recurlist */
400 if( 0 < count( $recurList )) {
401 $component2 = clone $component;
402 $compUID = $component2->getUid();
403 $YmdOld = null;
404 $fcnEndYmdHis = $fcnEnd->format( $YMDHIS2 );
405 foreach( $recurList as $recurKey => $durationInterval ) {
406 $recurKeyYmd = substr( $recurKey, 0, 8 );
407 if( $YmdOld === $recurKeyYmd ) {
408 continue; // skip overlapping recur the same day, i.e. RDATE before RRULE
409 }
410 $YmdOld = $recurKeyYmd;
411 $rStart = $compStart->getClone();
412 $rStart->setDateTimeFromString( $recurKey );
413 /* add recurring components within valid dates to output array, only start date set */
414 if( $flat ) {
415 if( ! isset( $result[$compUID] )) { // only one comp
416 $result[$compUID] = clone $component2; // copy to output
417 }
418 }
419 /* add recurring components within valid dates to output array, split for each day */
420 elseif( $split ) {
421 /* check and alter current component to recurr-comp if YMD match */
422 $component3 = null;
423 $recurFound = false;
424 foreach( $recurIdList as $k => $v ) {
425 if( 0 === strpos( $k, $recurKeyYmd )) {
426 $rStart = $v[0]->getClone();
427 $durationInterval2 = empty( $v[2] ) ? null : $v[2]; // DateInterval
428 $component3 = clone $v[4];
429 $recurFound = true;
430 break;
431 }
432 } // end foreach
433 if( ! $recurFound ) {
434 $component3 = clone $component2;
435 $durationInterval2 = ( ! empty( $durationInterval ))
436 ? $durationInterval
437 : null;
438 }
439 $rEnd = $rStart->getClone();
440 if( ! empty( $durationInterval2 )) {
441 $rEnd->add( $durationInterval2 );
442 }
443 if( $rEnd->format( $YMDHIS2 ) > $fcnEndYmdHis ) {
444 $rEnd = clone $fcnEnd;
445 }
446 $endHis = $rEnd->getTime();
447 ++$xRecurrence;
448 $cnt = 0;
449 // count the days (incl start day)
450 $occurenceDays = 1 +
451 (int) $rStart->getClone()
452 ->setTime( 0, 0 )
453 ->diff( clone $rEnd->getClone()->setTime( 0, 0 ))
454 ->format( $PRA );
455 $rEndYmd = $rEnd->format( $YMDn );
456 $propEndName = ( isset( $compEnd->SCbools[self::$DUEEXIST] ))
457 ? Vcalendar::X_CURRENT_DUE
458 : Vcalendar::X_CURRENT_DTEND;
459 while( $rStart->format( $YMDn ) <= $rEndYmd ) { // iterate.. .
460 ++$cnt;
461 if( $rStart->format( $YMDn ) < $fcnStartYmd ) { // start date day before dtstart DAY
462 $rStart->add( $INTERVAL_P1D ); // cycle rstart to dtstart DAY
463 $rStart->setTime( 0, 0 );
464 continue;
465 }
466 if( 2 === $cnt ) {
467 $rStart->setTime( 0, 0 );
468 }
469 $component3->deleteXprop( $propEndName );
470 $component3->deleteXprop( Vcalendar::X_OCCURENCE );
471 $component3->setXprop( Vcalendar::X_RECURRENCE, $xRecurrence );
472 if( 1 < $occurenceDays ) {
473 $component3->setXprop(
474 Vcalendar::X_OCCURENCE,
475 sprintf( $DAYOFDAYS, $cnt, $occurenceDays )
476 );
477 }
478 $component3->setXprop(
479 Vcalendar::X_CURRENT_DTSTART,
480 $rStart->format( $compStart->dateFormat )
481 );
482 if( ! empty( $durationInterval2 )) {
483 $rWdate = $rStart->getClone();
484 self::setDurationEndTime( $rWdate, $rEnd, $cnt, $occurenceDays, $endHis );
485 $component3->setXprop( $propEndName, $rWdate->format( $compEnd->dateFormat ));
486 }
487 list( $xY, $xM, $xD ) = self::getArrayYMDkeys( $rStart );
488 if( ! isset( $result[$xY][$xM][$xD][$compUID] )) {
489 $result[$xY][$xM][$xD][$compUID] = [];
490 }
491 $result[$xY][$xM][$xD][$compUID][] = clone $component3; // copy to output
492 $rStart->add( $INTERVAL_P1D );
493 } // end while( $rStart->format( 'Ymd' ) <= $rEnd->format( 'Ymd' ))
494 unset( $rStart, $rEnd );
495 } // end elseif( $split )
496 elseif(( $rStartYmdHis = $rStart->format( $YMDHIS2 )) &&
497 ( $rStartYmdHis >= $fcnStartYmdHis ) && // date equal/after start
498 ( $rStartYmdHis <= $fcnEndYmdHis )) { // date before/equal end
499 // date within period, flat=false && split=false => one comp every recur startdate
500 ++$xRecurrence;
501 $component2->setXprop( Vcalendar::X_RECURRENCE, $xRecurrence );
502 $component2->setXprop(
503 Vcalendar::X_CURRENT_DTSTART,
504 $rStart->format( $compStart->dateFormat )
505 );
506 $propEndName = ( isset( $compEnd->SCbools[self::$DUEEXIST] ))
507 ? Vcalendar::X_CURRENT_DUE
508 : Vcalendar::X_CURRENT_DTEND;
509 if( empty( $durationInterval )) {
510 $component2->deleteXprop( $propEndName );
511 }
512 else {
513 $component2->setXprop(
514 $propEndName,
515 $rStart->getClone()->add( $durationInterval )->format( $compEnd->dateFormat )
516 );
517 }
518 list( $xY, $xM, $xD ) = self::getArrayYMDkeys( $rStart );
519 if( ! isset( $result[$xY][$xM][$xD][$compUID] )) {
520 $result[$xY][$xM][$xD][$compUID] = [];
521 }
522 $result[$xY][$xM][$xD][$compUID][] = clone $component2; // copy to output
523 } // end elseif( $rStart >= $fcnStart )
524 } // end foreach( $recurList as $recurKey => $durationInterval )
525 } // end if( 0 < count( $recurList ))
526 } // end if( true === $any )
527 } // end while( $component = $calendar->getComponent())
528 if( 0 >= count( $result )) {
529 return false;
530 }
531 if( ! $flat ) {
532 foreach( $result as $y => $yList ) {
533 foreach( $yList as $m => $mList ) {
534 foreach( $mList as $d => $dList ) {
535 if( empty( $dList )) {
536 unset( $result[$y][$m][$d] );
537 }
538 else { // skip tricky UID-index
539 $temp = [];
540 foreach( $dList as $compVal ) {
541 if( is_array( $compVal )) {
542 foreach( $compVal as $comp ) {
543 $temp[] = $comp;
544 }
545 continue;
546 }
547 $temp[] = $compVal;
548 } // end foreach
549 $result[$y][$m][$d] = $temp;
550 if( 1 < count( $result[$y][$m][$d] )) { // sort
551 foreach( $result[$y][$m][$d] as $comp ) {
552 SortFactory::setSortArgs( $comp );
553 }
554 usort( $result[$y][$m][$d], $SORTER );
555 }
556 } // end else
557 } // end foreach( $mList as $d => $dList )
558 if( empty( $result[$y][$m] )) {
559 unset( $result[$y][$m] );
560 }
561 else {
562 ksort( $result[$y][$m] );
563 }
564 } // end foreach( $yList as $m => $mList )
565 if( empty( $result[$y] )) {
566 unset( $result[$y] );
567 }
568 else {
569 ksort( $result[$y] );
570 }
571 } // end foreach( $result as $y => $yList )
572 if( empty( $result )) {
573 unset( $result );
574 }
575 else {
576 ksort( $result );
577 }
578 } // end elseif( !$flat )
579 return $result;
580 }
581
582 /**
583 * Return bool true if dates are in scope
584 *
585 * @param UtilDateTime $start
586 * @param UtilDateTime $scopeStart
587 * @param UtilDateTime $end
588 * @param UtilDateTime $scopeEnd
589 * @param string $format
590 * @return bool
591 * @static
592 */
593 private static function inScope(
594 UtilDateTime $start,
595 UtilDateTime $scopeStart,
596 UtilDateTime $end,
597 UtilDateTime $scopeEnd,
598 $format
599 ) {
600 return (( $start->format( $format ) >= $scopeStart->format( $format )) &&
601 ( $end->format( $format ) <= $scopeEnd->format( $format )));
602 }
603
604 /**
605 * Get all EXRULE dates (multiple values allowed)
606 *
607 * @param CalendarComponent $component
608 * @param array $exdateList
609 * @param string $dtStartTz
610 * @param UtilDateTime $compStart
611 * @param UtilDateTime $workStart
612 * @param UtilDateTime $workEnd
613 * @param string $compStartHis
614 * @throws Exception
615 * @since 2.27.14 - 2019-02-27
616 */
617 private static function getAllEXRULEdates(
618 CalendarComponent $component,
619 array & $exdateList,
620 $dtStartTz,
621 UtilDateTime $compStart,
622 UtilDateTime $workStart,
623 UtilDateTime $workEnd,
624 $compStartHis
625 ) {
626 if( false !== ( $prop = $component->getExrule( true ))) {
627 $isValueDate = ParameterFactory::isParamsValueSet( $prop, Vcalendar::DATE );
628 $prop = $prop[Util::$LCvalue];
629 if( isset( $prop[Vcalendar::UNTIL] ) && ! $isValueDate ) {
630 // convert UNTIL date to DTSTART timezone
631 $prop[Vcalendar::UNTIL] = UtilDateTime::factory(
632 $prop[Vcalendar::UNTIL],
633 [ Vcalendar::TZID => Vcalendar::UTC ],
634 $dtStartTz
635 );
636 }
637 $exdateList2 = [];
638 RecurFactory::recur2date(
639 $exdateList2,
640 $prop,
641 $compStart,
642 $workStart,
643 $workEnd
644 );
645 foreach( $exdateList2 as $k => $v ) { // point out exact every excluded ocurrence (incl. opt. His)
646 $exdateList[$k . $compStartHis] = $v;
647 }
648 }
649 }
650
651 /**
652 * Get all EXDATE dates (multiple values allowed)
653 *
654 * @param CalendarComponent $component
655 * @param array $exdateList
656 * @param string $dtStartTz
657 * @throws Exception
658 * @since 2.27.2 - 2018-12-29
659 */
660 private static function getAllEXDATEdates(
661 CalendarComponent $component,
662 array & $exdateList,
663 $dtStartTz
664 ) {
665 while( false !== ( $prop = $component->getExdate( false, true ))) {
666 foreach( $prop[Util::$LCvalue] as $exdate ) {
667 $exdate = UtilDateTime::factory(
668 $exdate,
669 $prop[Util::$LCparams],
670 $dtStartTz
671 );
672 $exdateList[$exdate->key] = true;
673 } // end - foreach( $exdate as $exdate )
674 } // end while
675 }
676
677 /**
678 * Update $recurList all RRULE dates (multiple values allowed)
679 *
680 * @param CalendarComponent $component
681 * @param array $recurList
682 * @param string $dtStartTz
683 * @param UtilDateTime $compStart
684 * @param UtilDateTime $workStart
685 * @param UtilDateTime $workEnd
686 * @param string $compStartHis
687 * @param array $exdateList
688 * @param DateInterval $compDuration
689 * @throws Exception
690 * @since 2.27.14 - 2019-02-27
691 */
692 private static function getAllRRULEdates(
693 CalendarComponent $component,
694 array & $recurList,
695 $dtStartTz,
696 UtilDateTime $compStart,
697 UtilDateTime $workStart,
698 UtilDateTime $workEnd,
699 $compStartHis,
700 array & $exdateList,
701 DateInterval $compDuration = null
702 ) {
703 $exdateYmdList = self::getYmdList( $exdateList );
704 $recurYmdList = self::getYmdList( $recurList );
705 if( false !== ( $prop = $component->getRrule( true ))) {
706 $isValueDate = ParameterFactory::isParamsValueSet( $prop, Vcalendar::DATE );
707 $prop = $prop[Util::$LCvalue];
708 if( isset( $prop[Vcalendar::UNTIL] ) && ! $isValueDate ) {
709 // convert RRULE['UNTIL'] to same timezone as DTSTART !!
710 $prop[Vcalendar::UNTIL] = UtilDateTime::factory(
711 $prop[Vcalendar::UNTIL],
712 [ Vcalendar::TZID => Vcalendar::UTC ],
713 $dtStartTz
714 );
715 }
716 $recurList2 = [];
717 RecurFactory::recur2date(
718 $recurList2,
719 $prop,
720 $compStart,
721 $workStart,
722 $workEnd
723 );
724 foreach( $recurList2 as $recurKey => $recurValue ) { // recurkey=Ymd
725 if( isset( $exdateYmdList[$recurKey] )) { // exclude on Ymd basis
726 continue;
727 }
728 $YmdHisKey = $recurKey . $compStartHis; // add opt His
729 if( isset( $recurYmdList[$recurKey] )) { // replace on Ymd basis
730 $exdateList[$YmdHisKey] = true;
731 continue;
732 }
733 if( ! isset( $exdateList[$YmdHisKey] )) {
734 $recurList[$YmdHisKey] = $compDuration; // DateInterval or false
735 }
736 } // end foreach
737 } // end while
738 }
739
740 /**
741 * Update $recurList with RDATE dates (overwrite if exists)
742 *
743 * @param CalendarComponent $component
744 * @param array $recurList
745 * @param string $dtStartTz
746 * @param UtilDateTime $workStart
747 * @param UtilDateTime $fcnEnd
748 * @param string $format
749 * @param array $exdateList
750 * @param string $compStartHis
751 * @param DateInterval $compDuration
752 * @throws Exception
753 * @since 2.27.2 - 2018-12-29
754 */
755 private static function getAllRDATEdates(
756 CalendarComponent $component,
757 array & $recurList,
758 $dtStartTz,
759 UtilDateTime $workStart,
760 UtilDateTime $fcnEnd,
761 $format,
762 array & $exdateList,
763 $compStartHis,
764 DateInterval $compDuration = null
765 ) {
766 $exdateYmdList = self::getYmdList( $exdateList );
767 $recurYmdList = self::getYmdList( $recurList );
768 while( false !== ( $prop = $component->getRdate( false, true ))) {
769 $rDateFmt = ( isset( $prop[Util::$LCparams][Vcalendar::VALUE] ))
770 ? $prop[Util::$LCparams][Vcalendar::VALUE] // DATE or PERIOD
771 : Vcalendar::DATE_TIME;
772 $params = $prop[Util::$LCparams];
773 $prop = $prop[Util::$LCvalue];
774 foreach( $prop as $theRdate ) {
775 if( Vcalendar::PERIOD === $rDateFmt ) { // all days within PERIOD
776 $rDate = UtilDateTime::factory( $theRdate[0], $params, $dtStartTz );
777 if( ! self::inScope( $rDate, $workStart, $rDate, $fcnEnd, $format )) {
778 continue;
779 }
780 $cmpKey = substr( $rDate->key, 0, 8 );
781 // exclude on Ymd basis (rRules already excluded)
782 if( isset( $exdateYmdList[$cmpKey] )) {
783 continue;
784 }
785 // exclude on Ymd(His) basis
786 if( isset( $exdateList[$rDate->key] )) {
787 continue;
788 }
789 // rDate replaces rRule, update excludeList
790 if( isset( $recurYmdList[$cmpKey] )) {
791 $exdateList[$recurYmdList[$cmpKey]] = true;
792 }
793 if( $theRdate[1] instanceof DateTime ) { // date-date period end
794 $recurList[$rDate->key] = $rDate->diff( // save duration
795 UtilDateTime::factory( $theRdate[1], $params, $dtStartTz )
796 );
797 continue;
798 }
799 try { // period duration
800 $recurList[$rDate->key] =
801 DateIntervalFactory::DateIntervalArr2DateInterval(
802 $theRdate[1]
803 );
804 }
805 catch( Exception $e ) {
806 throw $e;
807 }
808 continue;
809 } // end if( Vcalendar::PERIOD == $rDateFmt )
810 if( Vcalendar::DATE === $rDateFmt ) { // single recurrence, DATE (=Ymd)
811 $rDate = UtilDateTime::factory(
812 $theRdate,
813 array_merge( $params, [ Vcalendar::TZID => $dtStartTz ] ),
814 $dtStartTz
815 );
816 $rDateYmdHisKey = $rDate->key . $compStartHis;
817 }
818 else { // single recurrence, DATETIME
819 $rDate = UtilDateTime::factory( $theRdate, $params, $dtStartTz );
820 // set start date for recurrence + DateInterval/false (+opt His)
821 $rDateYmdHisKey = $rDate->key;
822 }
823 $cmpKey = substr( $rDate->key, 0, 8 );
824 switch( true ) {
825 case ( isset( $exdateYmdList[$cmpKey] )) : // excluded on Ymd basis
826 break;
827 case ( ! self::inScope(
828 $rDate,
829 $workStart,
830 $rDate,
831 $fcnEnd,
832 $format
833 )) :
834 break;
835 default :
836 if( isset( $recurYmdList[$cmpKey] )) { // rDate replaces rRule
837 $exdateList[$recurYmdList[$cmpKey]] = true;
838 }
839 $recurList[$rDateYmdHisKey] = $compDuration;
840 break;
841 } // end switch
842 } // end foreach
843 } // end while
844 }
845
846 /**
847 * Return YmdList from YmdHis keyed array
848 *
849 * @param array $YmdHisArr
850 * @return array
851 * @static
852 * @since 2.26.2 - 2018-11-15
853 */
854 private static function getYmdList( array $YmdHisArr )
855 {
856 $res = [];
857 foreach( $YmdHisArr as $key => $value ) {
858 $res[substr( $key, 0, 8 )] = $key;
859 }
860 return $res;
861 }
862
863 /**
864 * Assert date arguments
865 *
866 * @param int|DateTimeInterface $startY
867 * @param int|DateTimeInterface $startM
868 * @param int $startD
869 * @param int $endY
870 * @param int $endM
871 * @param int $endD
872 * @static
873 * @since 2.29.16 - 2020-01-24
874 */
875 private static function assertDateArguments(
876 & $startY = null,
877 & $startM = null,
878 & $startD = null,
879 & $endY = null,
880 & $endM = null,
881 & $endD = null
882 ) {
883 static $Y = 'Y';
884 static $M = 'm';
885 static $D = 'd';
886 if(( $startY instanceof DateTimeInterface ) &&
887 ( $startM instanceof DateTimeInterface )) {
888 $endY = (int) $startM->format( $Y );
889 $endM = (int) $startM->format( $M );
890 $endD = (int) $startM->format( $D );
891 $startD = (int) $startY->format( $D );
892 $startM = (int) $startY->format( $M );
893 $startY = (int) $startY->format( $Y );
894 }
895 else {
896 if( empty( $startY )) {
897 $startY = (int) date( $Y );
898 }
899 if( empty( $startM )) {
900 $startM = (int) date( $M );
901 }
902 if( empty( $startD )) {
903 $startD = (int) date( $D );
904 }
905 if( empty( $endY )) {
906 $endY = $startY;
907 }
908 if( empty( $endM )) {
909 $endM = $startM;
910 }
911 if( empty( $endD )) {
912 $endD = $startD;
913 }
914 }
915 }
916
917 /**
918 * Return reviewed component types
919 *
920 * @param array|string $cType
921 * @return array
922 * @static
923 * @since 2.27.18 - 2019-04-07
924 */
925 private static function assertComponentTypes( $cType = null )
926 {
927 if( empty( $cType )) {
928 return Vcalendar::$VCOMPS;
929 }
930 if( ! is_array( $cType )) {
931 $cType = [ $cType ];
932 }
933 foreach( $cType as & $theType ) {
934 $theType = ucfirst( strtolower( $theType ));
935 if( ! Util::isCompInList( $theType, Vcalendar::$VCOMPS )) {
936 $theType = Vcalendar::VEVENT;
937 }
938 }
939 return array_unique( $cType );
940 }
941
942 /**
943 * Assert bool arguments
944 *
945 * @param bool $flat
946 * @param bool $any
947 * @param bool $split
948 * @static
949 * @since 2.26.2 - 2018-11-15
950 */
951 private static function assertBoolArguments(
952 & $flat = null,
953 & $any = null,
954 & $split = null
955 ) {
956 // defaults
957 $flat = ! is_null( $flat ) && (bool)$flat;
958 $any = is_null( $any ) || (bool)$any;
959 $split = is_null( $split ) || (bool)$split;
960 if(( false === $flat ) && ( false === $any )) {
961 // invalid combination
962 $split = false;
963 }
964 if(( true === $flat ) && ( true === $split )) {
965 // invalid combination
966 $split = false;
967 }
968 }
969
970 /**
971 * Return comp end date(time) from dtend/due/duration properties
972 *
973 * @param CalendarComponent $component
974 * @param string $dtStartTz
975 * @return UtilDateTime
976 * @throws Exception
977 * @static
978 * @since 2.27.6 - 2018-12-29
979 */
980 private static function getCompEndDate(
981 CalendarComponent $component,
982 $dtStartTz
983 ) {
984 static $MINUS1DAY = '-1 day';
985 $compEnd = $prop = null;
986 $compType = $component->getCompType();
987 $isFreebusyCompType = ( Vcalendar::VFREEBUSY === $compType );
988 $isVtodoCompType = ( Vcalendar::VTODO === $compType );
989 $isVeventCompType = ( Vcalendar::VEVENT === $compType );
990 if(( $isVeventCompType || $isFreebusyCompType ) &&
991 ( false !== ( $prop = $component->getDtend( true )))) {
992 $compEnd = UtilDateTime::factory( $prop[Util::$LCvalue], $prop[Util::$LCparams], $dtStartTz );
993 $compEnd->SCbools[self::$DTENDEXIST] = true;
994 }
995 if( empty( $prop ) &&
996 $isVtodoCompType &&
997 ( false !== ( $prop = $component->getDue( true )))) {
998 $compEnd = UtilDateTime::factory( $prop[Util::$LCvalue], $prop[Util::$LCparams], $dtStartTz );
999 $compEnd->SCbools[self::$DUEEXIST] = true;
1000 }
1001 if( empty( $prop ) && // duration in dtend (array) format
1002 ( $isVeventCompType || $isVtodoCompType ) &&
1003 ( false !== ( $prop = $component->getDuration( true, true )))) {
1004 $compEnd = UtilDateTime::factory( $prop[Util::$LCvalue], $prop[Util::$LCparams], $dtStartTz );
1005 $compEnd->SCbools[self::$DURATIONEXIST] = true;
1006 }
1007 if( ! empty( $prop ) &&
1008 ParameterFactory::isParamsValueSet( $prop, Vcalendar::DATE )) {
1009 /* a DTEND without time part denotes an end of an event that actually ends the day before,
1010 for an all-day event DTSTART=20071201 DTEND=20071202, taking place 20071201!!! */
1011 $compEnd->SCbools[self::$ENDALLDAYEVENT] = true;
1012 $compEnd->modify( $MINUS1DAY );
1013 $compEnd->setTime( 23, 59, 59 );
1014 }
1015 return $compEnd;
1016 }
1017
1018 /**
1019 * Set duration end time
1020 *
1021 * @param UtilDateTime $rStart
1022 * @param UtilDateTime $rEnd
1023 * @param int $cnt
1024 * @param int $occurenceDays
1025 * @param array $endHis
1026 * @static
1027 * @since 2.26 - 2018-11-10
1028 */
1029 private static function setDurationEndTime(
1030 UtilDateTime $rStart,
1031 UtilDateTime $rEnd,
1032 $cnt,
1033 $occurenceDays,
1034 array $endHis
1035 )
1036 {
1037 static $YMD = 'Ymd';
1038 if( $cnt < $occurenceDays ) {
1039 $rStart->setTime( 23, 59, 59 );
1040 }
1041 elseif( $rStart->format( $YMD ) < $rEnd->format( $YMD )) {
1042 if( ( 0 === $endHis[0] ) && ( 0 === $endHis[1] ) && ( 0 === $endHis[2] )) {
1043 $rStart->setTime( 24, 0 ); // end exactly at midnight // 24:00:00
1044 }
1045 else {
1046 $rStart->setTime( 23, 59, 59 ); // break at midnight, cont next day // 23:59:59
1047 }
1048 }
1049 else {
1050 $rStart->setTime( $endHis[0], $endHis[1], $endHis[2] );
1051 }
1052 }
1053
1054 /**
1055 * Get array Y, m, d keys
1056 *
1057 * @param UtilDateTime $icaldateTime
1058 * @return array
1059 * @static
1060 * @since 2.26.2 - 2018-11-15
1061 */
1062 private static function getArrayYMDkeys(
1063 UtilDateTime $icaldateTime
1064 ) {
1065 static $Y = 'Y';
1066 static $M = 'm';
1067 static $D = 'd';
1068 return [
1069 (int) $icaldateTime->format( $Y ),
1070 (int) $icaldateTime->format( $M ),
1071 (int) $icaldateTime->format( $D )
1072 ];
1073 }
1074
1075 /**
1076 * Update recurr-id-comps properties summary, description and comment if missing
1077 *
1078 * @param CalendarComponent $component (Vevent/Vtodo/Vjournal)
1079 * @param array $recurIdComps
1080 * @static
1081 * @since 2.27.1 - 2018-12-16
1082 */
1083 private static function updateRecurrIdComps(
1084 CalendarComponent $component,
1085 array & $recurIdComps
1086 ) {
1087 if( empty( $recurIdComps )) {
1088 return;
1089 }
1090 if( Vcalendar::VFREEBUSY === $component->getCompType()) {
1091 return;
1092 }
1093 $summary = $component->getSummary( true );
1094 $description = $component->getDescription( null, true );
1095 $comments = [];
1096 while( false !== ( $comment = $component->getComment( null, true ))) {
1097 $comments[] = $comment;
1098 }
1099 foreach( array_keys( $recurIdComps ) as $RecurrIdKey ) {
1100 if( ! empty( $summary )) {
1101 $value = $recurIdComps[$RecurrIdKey][4]->getSummary();
1102 if( empty( $value )) {
1103 $recurIdComps[$RecurrIdKey][4]->setSummary(
1104 $summary[Util::$LCvalue],
1105 $summary[Util::$LCparams] );
1106 }
1107 }
1108 if( ! empty( $description )) {
1109 $value = $recurIdComps[$RecurrIdKey][4]->getDescription();
1110 if( empty( $value )) {
1111 $recurIdComps[$RecurrIdKey][4]->setDescription(
1112 $description[Util::$LCvalue],
1113 $description[Util::$LCparams]
1114 );
1115 }
1116 } // end if
1117 if( empty( $comments )) {
1118 continue;
1119 }
1120 $value = $recurIdComps[$RecurrIdKey][4]->getComment();
1121 if( ! empty( $value )) {
1122 continue;
1123 }
1124 foreach( $comments as $prop ) {
1125 $recurIdComps[$RecurrIdKey][4]->setComment(
1126 $prop[Util::$LCvalue],
1127 $prop[Util::$LCparams] );
1128 }
1129 } // end foreach
1130 }
1131
1132 /**
1133 * Return array with selected components values from calendar based on specific property value(-s)
1134 *
1135 * @param Vcalendar $calendar
1136 * @param array $selectOptions (string) key => (mixed) value, (key=propertyName)
1137 * @return array
1138 * @static
1139 * @since 2.27.17 - 2020-01-25
1140 */
1141 private static function selectComponents2(
1142 Vcalendar $calendar,
1143 array $selectOptions
1144 ) {
1145 $output = [];
1146 $selectOptions = array_change_key_case( $selectOptions, CASE_UPPER );
1147 while( $component3 = $calendar->getComponent()) {
1148 if( empty( $component3 )) {
1149 continue;
1150 }
1151 if( ! Util::isCompInList( $component3->getCompType(), Vcalendar::$VCOMPS )) {
1152 continue;
1153 }
1154 $uid = $component3->getUid();
1155 foreach( $selectOptions as $propName => $propValue ) {
1156 if( ! Util::isPropInList( $propName, Vcalendar::$OTHERPROPS )) {
1157 continue;
1158 }
1159 if( ! is_array( $propValue )) {
1160 $propValue = [ $propValue ];
1161 }
1162 if(( Vcalendar::UID === $propName ) && in_array( $uid, $propValue )) {
1163 $output[$uid][] = $component3;
1164 continue;
1165 }
1166 if( Util::isPropInList( $propName, Vcalendar::$MPROPS1 )) {
1167 $propValues = [];
1168 $component3->getProperties( $propName, $propValues );
1169 $propValues = array_keys( $propValues );
1170 foreach( $propValue as $theValue ) {
1171 if( in_array( $theValue, $propValues )) { // && ! isset( $output[$uid] )) {
1172 $output[$uid][] = $component3;
1173 break;
1174 }
1175 }
1176 continue;
1177 } // end elseif( // multiple occurrence?
1178 $method = Vcalendar::getGetMethodName( $propName );
1179 if( ! method_exists( $component3, $method ) ||
1180 ( false === ( $d = $component3->{$method}()))) { // single occurrence
1181 continue;
1182 }
1183 $outputUidIsSet = isset( $output[$uid] );
1184 if( is_array( $d )) {
1185 foreach( $d as $part ) {
1186 if( ! $outputUidIsSet && in_array( $part, $propValue )) {
1187 $output[$uid][] = $component3;
1188 }
1189 }
1190 }
1191 elseif(( ! $outputUidIsSet && Vcalendar::SUMMARY === $propName )) {
1192 foreach( $propValue as $pval ) {
1193 if( false !== stripos( $d, $pval )) {
1194 $output[$uid][] = $component3;
1195 break;
1196 }
1197 }
1198 }
1199 elseif( ! $outputUidIsSet && in_array( $d, $propValue )) {
1200 $output[$uid][] = $component3;
1201 }
1202 } // end foreach( $selectOptions as $propName => $propValue )
1203 } // end while( $component3 = $calendar->getComponent()) {
1204 if( ! empty( $output )) {
1205 ksort( $output ); // uid order
1206 $output2 = [];
1207 foreach( $output as $uList ) { // $uid => $uList
1208 foreach( $uList as $uValue ) {
1209 $output2[] = $uValue;
1210 }
1211 }
1212 $output = $output2;
1213 }
1214 return $output;
1215 }
1216 }
1217