ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
EventIterator.php
Go to the documentation of this file.
1<?php
2
4
5use DateTimeImmutable;
6use DateTimeInterface;
7use DateTimeZone;
8use InvalidArgumentException;
12
61class EventIterator implements \Iterator {
62
68 protected $timeZone;
69
75 protected $allDay = false;
76
96 function __construct($input, $uid = null, DateTimeZone $timeZone = null) {
97
98 if (is_null($timeZone)) {
99 $timeZone = new DateTimeZone('UTC');
100 }
101 $this->timeZone = $timeZone;
102
103 if (is_array($input)) {
104 $events = $input;
105 } elseif ($input instanceof VEvent) {
106 // Single instance mode.
107 $events = [$input];
108 } else {
109 // Calendar + UID mode.
110 $uid = (string)$uid;
111 if (!$uid) {
112 throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
113 }
114 if (!isset($input->VEVENT)) {
115 throw new InvalidArgumentException('No events found in this calendar');
116 }
117 $events = $input->getByUID($uid);
118
119 }
120
121 foreach ($events as $vevent) {
122
123 if (!isset($vevent->{'RECURRENCE-ID'})) {
124
125 $this->masterEvent = $vevent;
126
127 } else {
128
129 $this->exceptions[
130 $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
131 ] = true;
132 $this->overriddenEvents[] = $vevent;
133
134 }
135
136 }
137
138 if (!$this->masterEvent) {
139 // No base event was found. CalDAV does allow cases where only
140 // overridden instances are stored.
141 //
142 // In this particular case, we're just going to grab the first
143 // event and use that instead. This may not always give the
144 // desired result.
145 if (!count($this->overriddenEvents)) {
146 throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid);
147 }
148 $this->masterEvent = array_shift($this->overriddenEvents);
149 }
150
151 $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
152 $this->allDay = !$this->masterEvent->DTSTART->hasTime();
153
154 if (isset($this->masterEvent->EXDATE)) {
155
156 foreach ($this->masterEvent->EXDATE as $exDate) {
157
158 foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
159 $this->exceptions[$dt->getTimeStamp()] = true;
160 }
161
162 }
163
164 }
165
166 if (isset($this->masterEvent->DTEND)) {
167 $this->eventDuration =
168 $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
169 $this->startDate->getTimeStamp();
170 } elseif (isset($this->masterEvent->DURATION)) {
171 $duration = $this->masterEvent->DURATION->getDateInterval();
172 $end = clone $this->startDate;
173 $end = $end->add($duration);
174 $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
175 } elseif ($this->allDay) {
176 $this->eventDuration = 3600 * 24;
177 } else {
178 $this->eventDuration = 0;
179 }
180
181 if (isset($this->masterEvent->RDATE)) {
182 $this->recurIterator = new RDateIterator(
183 $this->masterEvent->RDATE->getParts(),
184 $this->startDate
185 );
186 } elseif (isset($this->masterEvent->RRULE)) {
187 $this->recurIterator = new RRuleIterator(
188 $this->masterEvent->RRULE->getParts(),
189 $this->startDate
190 );
191 } else {
192 $this->recurIterator = new RRuleIterator(
193 [
194 'FREQ' => 'DAILY',
195 'COUNT' => 1,
196 ],
197 $this->startDate
198 );
199 }
200
201 $this->rewind();
202 if (!$this->valid()) {
203 throw new NoInstancesException('This recurrence rule does not generate any valid instances');
204 }
205
206 }
207
213 function current() {
214
215 if ($this->currentDate) {
216 return clone $this->currentDate;
217 }
218
219 }
220
227 function getDtStart() {
228
229 if ($this->currentDate) {
230 return clone $this->currentDate;
231 }
232
233 }
234
241 function getDtEnd() {
242
243 if (!$this->valid()) {
244 return;
245 }
246 $end = clone $this->currentDate;
247 return $end->modify('+' . $this->eventDuration . ' seconds');
248
249 }
250
259 function getEventObject() {
260
261 if ($this->currentOverriddenEvent) {
262 return $this->currentOverriddenEvent;
263 }
264
265 $event = clone $this->masterEvent;
266
267 // Ignoring the following block, because PHPUnit's code coverage
268 // ignores most of these lines, and this messes with our stats.
269 //
270 // @codeCoverageIgnoreStart
271 unset(
272 $event->RRULE,
273 $event->EXDATE,
274 $event->RDATE,
275 $event->EXRULE,
276 $event->{'RECURRENCE-ID'}
277 );
278 // @codeCoverageIgnoreEnd
279
280 $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
281 if (isset($event->DTEND)) {
282 $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
283 }
284 $recurid = clone $event->DTSTART;
285 $recurid->name = 'RECURRENCE-ID';
286 $event->add($recurid);
287 return $event;
288
289 }
290
298 function key() {
299
300 // The counter is always 1 ahead.
301 return $this->counter - 1;
302
303 }
304
311 function valid() {
312
313 if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) {
314 throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences);
315 }
316 return !!$this->currentDate;
317
318 }
319
323 function rewind() {
324
325 $this->recurIterator->rewind();
326 // re-creating overridden event index.
327 $index = [];
328 foreach ($this->overriddenEvents as $key => $event) {
329 $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
330 $index[$stamp][] = $key;
331 }
332 krsort($index);
333 $this->counter = 0;
334 $this->overriddenEventsIndex = $index;
335 $this->currentOverriddenEvent = null;
336
337 $this->nextDate = null;
338 $this->currentDate = clone $this->startDate;
339
340 $this->next();
341
342 }
343
349 function next() {
350
351 $this->currentOverriddenEvent = null;
352 $this->counter++;
353 if ($this->nextDate) {
354 // We had a stored value.
355 $nextDate = $this->nextDate;
356 $this->nextDate = null;
357 } else {
358 // We need to ask rruleparser for the next date.
359 // We need to do this until we find a date that's not in the
360 // exception list.
361 do {
362 if (!$this->recurIterator->valid()) {
363 $nextDate = null;
364 break;
365 }
366 $nextDate = $this->recurIterator->current();
367 $this->recurIterator->next();
368 } while (isset($this->exceptions[$nextDate->getTimeStamp()]));
369
370 }
371
372
373 // $nextDate now contains what rrule thinks is the next one, but an
374 // overridden event may cut ahead.
375 if ($this->overriddenEventsIndex) {
376
377 $offsets = end($this->overriddenEventsIndex);
378 $timestamp = key($this->overriddenEventsIndex);
379 $offset = end($offsets);
380 if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
381 // Overridden event comes first.
382 $this->currentOverriddenEvent = $this->overriddenEvents[$offset];
383
384 // Putting the rrule next date aside.
385 $this->nextDate = $nextDate;
386 $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
387
388 // Ensuring that this item will only be used once.
389 array_pop($this->overriddenEventsIndex[$timestamp]);
390 if (!$this->overriddenEventsIndex[$timestamp]) {
391 array_pop($this->overriddenEventsIndex);
392 }
393
394 // Exit point!
395 return;
396
397 }
398
399 }
400
401 $this->currentDate = $nextDate;
402
403 }
404
410 function fastForward(DateTimeInterface $dateTime) {
411
412 while ($this->valid() && $this->getDtEnd() <= $dateTime) {
413 $this->next();
414 }
415
416 }
417
423 function isInfinite() {
424
425 return $this->recurIterator->isInfinite();
426
427 }
428
434 protected $recurIterator;
435
441 protected $eventDuration;
442
448 protected $masterEvent;
449
455 protected $overriddenEvents = [];
456
466
473 protected $exceptions = [];
474
480 protected $counter;
481
487 protected $startDate;
488
494 protected $currentDate;
495
504 protected $nextDate;
505
512
513}
$exceptions
Definition: Utf8Test.php:67
foreach($mandatory_scripts as $file) $timestamp
Definition: buildRTE.php:81
An exception for terminatinating execution or to throw for unit testing.
VEvent component.
Definition: VEvent.php:19
This class is used to determine new for a recurring event, when the next events occur.
__construct($input, $uid=null, DateTimeZone $timeZone=null)
Creates the iterator.
key()
Returns the current position of the iterator.
valid()
This is called after next, to see if the iterator is still at a valid position, or if it's at the end...
getEventObject()
Returns a VEVENT for the current iterations of the event.
rewind()
Sets the iterator back to the starting point.
current()
Returns the date for the current position of the iterator.
next()
Advances the iterator with one step.
isInfinite()
Returns true if this recurring event never ends.
getDtEnd()
This method returns the end date for the current iteration of the event.
getDtStart()
This method returns the start date for the current iteration of the event.
$eventDuration
The duration, in seconds, of the master event.
fastForward(DateTimeInterface $dateTime)
Quickly jump to a date in the future.
This exception will get thrown when a recurrence rule generated more than the maximum number of insta...
This class provides a list of global defaults for vobject.
Definition: Settings.php:18
$key
Definition: croninfo.php:18
if($argc< 2) $events
$index
Definition: metadata.php:60
foreach($paths as $path) if($argc< 3) $input