ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
EventIterator.php
Go to the documentation of this file.
1 <?php
2 
4 
7 use DateTimeZone;
12 
61 class 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(),
185  );
186  } elseif (isset($this->masterEvent->RRULE)) {
187  $this->recurIterator = new RRuleIterator(
188  $this->masterEvent->RRULE->getParts(),
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) {
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.
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 }
getEventObject()
Returns a VEVENT for the current iterations of the event.
__construct($input, $uid=null, DateTimeZone $timeZone=null)
Creates the iterator.
VEvent component.
Definition: VEvent.php:19
rewind()
Sets the iterator back to the starting point.
if($argc< 3) $input
current()
Returns the date for the current position of the iterator.
next()
Advances the iterator with one step.
if($argc< 2) $events
$index
Definition: metadata.php:60
$eventDuration
The duration, in seconds, of the master event.
getDtStart()
This method returns the start date for the current iteration of the event.
static $maxRecurrences
The maximum number of recurrences that will be generated.
Definition: Settings.php:54
This class is used to determine new for a recurring event, when the next events occur.
This exception gets thrown when a recurrence iterator produces 0 instances.
valid()
This is called after next, to see if the iterator is still at a valid position, or if it&#39;s at the end...
key()
Returns the current position of the iterator.
This exception will get thrown when a recurrence rule generated more than the maximum number of insta...
getDtEnd()
This method returns the end date for the current iteration of the event.
foreach($mandatory_scripts as $file) $timestamp
Definition: buildRTE.php:81
fastForward(DateTimeInterface $dateTime)
Quickly jump to a date in the future.
$key
Definition: croninfo.php:18
isInfinite()
Returns true if this recurring event never ends.