Completed
Branch develop (fc1aaa)
by
unknown
18:31
created
htdocs/includes/sabre/sabre/vobject/lib/Recur/EventIterator.php 1 patch
Indentation   +434 added lines, -434 removed lines patch added patch discarded remove patch
@@ -60,438 +60,438 @@
 block discarded – undo
60 60
  */
61 61
 class EventIterator implements \Iterator
62 62
 {
63
-    /**
64
-     * Reference timeZone for floating dates and times.
65
-     *
66
-     * @var DateTimeZone
67
-     */
68
-    protected $timeZone;
69
-
70
-    /**
71
-     * True if we're iterating an all-day event.
72
-     *
73
-     * @var bool
74
-     */
75
-    protected $allDay = false;
76
-
77
-    /**
78
-     * Creates the iterator.
79
-     *
80
-     * There's three ways to set up the iterator.
81
-     *
82
-     * 1. You can pass a VCALENDAR component and a UID.
83
-     * 2. You can pass an array of VEVENTs (all UIDS should match).
84
-     * 3. You can pass a single VEVENT component.
85
-     *
86
-     * Only the second method is recommended. The other 1 and 3 will be removed
87
-     * at some point in the future.
88
-     *
89
-     * The $uid parameter is only required for the first method.
90
-     *
91
-     * @param Component|array $input
92
-     * @param string|null     $uid
93
-     * @param DateTimeZone    $timeZone reference timezone for floating dates and
94
-     *                                  times
95
-     */
96
-    public 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
-        foreach ($events as $vevent) {
121
-            if (!isset($vevent->{'RECURRENCE-ID'})) {
122
-                $this->masterEvent = $vevent;
123
-            } else {
124
-                $this->exceptions[
125
-                    $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
126
-                ] = true;
127
-                $this->overriddenEvents[] = $vevent;
128
-            }
129
-        }
130
-
131
-        if (!$this->masterEvent) {
132
-            // No base event was found. CalDAV does allow cases where only
133
-            // overridden instances are stored.
134
-            //
135
-            // In this particular case, we're just going to grab the first
136
-            // event and use that instead. This may not always give the
137
-            // desired result.
138
-            if (!count($this->overriddenEvents)) {
139
-                throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid);
140
-            }
141
-            $this->masterEvent = array_shift($this->overriddenEvents);
142
-        }
143
-
144
-        $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
145
-        $this->allDay = !$this->masterEvent->DTSTART->hasTime();
146
-
147
-        if (isset($this->masterEvent->EXDATE)) {
148
-            foreach ($this->masterEvent->EXDATE as $exDate) {
149
-                foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
150
-                    $this->exceptions[$dt->getTimeStamp()] = true;
151
-                }
152
-            }
153
-        }
154
-
155
-        if (isset($this->masterEvent->DTEND)) {
156
-            $this->eventDuration =
157
-                $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
158
-                $this->startDate->getTimeStamp();
159
-        } elseif (isset($this->masterEvent->DURATION)) {
160
-            $duration = $this->masterEvent->DURATION->getDateInterval();
161
-            $end = clone $this->startDate;
162
-            $end = $end->add($duration);
163
-            $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
164
-        } elseif ($this->allDay) {
165
-            $this->eventDuration = 3600 * 24;
166
-        } else {
167
-            $this->eventDuration = 0;
168
-        }
169
-
170
-        if (isset($this->masterEvent->RDATE)) {
171
-            $this->recurIterator = new RDateIterator(
172
-                $this->masterEvent->RDATE->getParts(),
173
-                $this->startDate
174
-            );
175
-        } elseif (isset($this->masterEvent->RRULE)) {
176
-            $this->recurIterator = new RRuleIterator(
177
-                $this->masterEvent->RRULE->getParts(),
178
-                $this->startDate
179
-            );
180
-        } else {
181
-            $this->recurIterator = new RRuleIterator(
182
-                [
183
-                    'FREQ' => 'DAILY',
184
-                    'COUNT' => 1,
185
-                ],
186
-                $this->startDate
187
-            );
188
-        }
189
-
190
-        $this->rewind();
191
-        if (!$this->valid()) {
192
-            throw new NoInstancesException('This recurrence rule does not generate any valid instances');
193
-        }
194
-    }
195
-
196
-    /**
197
-     * Returns the date for the current position of the iterator.
198
-     *
199
-     * @return DateTimeImmutable
200
-     */
201
-    #[\ReturnTypeWillChange]
202
-    public function current()
203
-    {
204
-        if ($this->currentDate) {
205
-            return clone $this->currentDate;
206
-        }
207
-    }
208
-
209
-    /**
210
-     * This method returns the start date for the current iteration of the
211
-     * event.
212
-     *
213
-     * @return DateTimeImmutable
214
-     */
215
-    public function getDtStart()
216
-    {
217
-        if ($this->currentDate) {
218
-            return clone $this->currentDate;
219
-        }
220
-    }
221
-
222
-    /**
223
-     * This method returns the end date for the current iteration of the
224
-     * event.
225
-     *
226
-     * @return DateTimeImmutable
227
-     */
228
-    public function getDtEnd()
229
-    {
230
-        if (!$this->valid()) {
231
-            return;
232
-        }
233
-        if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
234
-            return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
235
-        } else {
236
-            $end = clone $this->currentDate;
237
-
238
-            return $end->modify('+'.$this->eventDuration.' seconds');
239
-        }
240
-    }
241
-
242
-    /**
243
-     * Returns a VEVENT for the current iterations of the event.
244
-     *
245
-     * This VEVENT will have a recurrence id, and its DTSTART and DTEND
246
-     * altered.
247
-     *
248
-     * @return VEvent
249
-     */
250
-    public function getEventObject()
251
-    {
252
-        if ($this->currentOverriddenEvent) {
253
-            return $this->currentOverriddenEvent;
254
-        }
255
-
256
-        $event = clone $this->masterEvent;
257
-
258
-        // Ignoring the following block, because PHPUnit's code coverage
259
-        // ignores most of these lines, and this messes with our stats.
260
-        //
261
-        // @codeCoverageIgnoreStart
262
-        unset(
263
-            $event->RRULE,
264
-            $event->EXDATE,
265
-            $event->RDATE,
266
-            $event->EXRULE,
267
-            $event->{'RECURRENCE-ID'}
268
-        );
269
-        // @codeCoverageIgnoreEnd
270
-
271
-        $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
272
-        if (isset($event->DTEND)) {
273
-            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
274
-        }
275
-        $recurid = clone $event->DTSTART;
276
-        $recurid->name = 'RECURRENCE-ID';
277
-        $event->add($recurid);
278
-
279
-        return $event;
280
-    }
281
-
282
-    /**
283
-     * Returns the current position of the iterator.
284
-     *
285
-     * This is for us simply a 0-based index.
286
-     *
287
-     * @return int
288
-     */
289
-    #[\ReturnTypeWillChange]
290
-    public function key()
291
-    {
292
-        // The counter is always 1 ahead.
293
-        return $this->counter - 1;
294
-    }
295
-
296
-    /**
297
-     * This is called after next, to see if the iterator is still at a valid
298
-     * position, or if it's at the end.
299
-     *
300
-     * @return bool
301
-     */
302
-    #[\ReturnTypeWillChange]
303
-    public function valid()
304
-    {
305
-        if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) {
306
-            throw new MaxInstancesExceededException('Recurring events are only allowed to generate '.Settings::$maxRecurrences);
307
-        }
308
-
309
-        return (bool) $this->currentDate;
310
-    }
311
-
312
-    /**
313
-     * Sets the iterator back to the starting point.
314
-     *
315
-     * @return void
316
-     */
317
-    #[\ReturnTypeWillChange]
318
-    public function rewind()
319
-    {
320
-        $this->recurIterator->rewind();
321
-        // re-creating overridden event index.
322
-        $index = [];
323
-        foreach ($this->overriddenEvents as $key => $event) {
324
-            $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
325
-            $index[$stamp][] = $key;
326
-        }
327
-        krsort($index);
328
-        $this->counter = 0;
329
-        $this->overriddenEventsIndex = $index;
330
-        $this->currentOverriddenEvent = null;
331
-
332
-        $this->nextDate = null;
333
-        $this->currentDate = clone $this->startDate;
334
-
335
-        $this->next();
336
-    }
337
-
338
-    /**
339
-     * Advances the iterator with one step.
340
-     *
341
-     * @return void
342
-     */
343
-    #[\ReturnTypeWillChange]
344
-    public function next()
345
-    {
346
-        $this->currentOverriddenEvent = null;
347
-        ++$this->counter;
348
-        if ($this->nextDate) {
349
-            // We had a stored value.
350
-            $nextDate = $this->nextDate;
351
-            $this->nextDate = null;
352
-        } else {
353
-            // We need to ask rruleparser for the next date.
354
-            // We need to do this until we find a date that's not in the
355
-            // exception list.
356
-            do {
357
-                if (!$this->recurIterator->valid()) {
358
-                    $nextDate = null;
359
-                    break;
360
-                }
361
-                $nextDate = $this->recurIterator->current();
362
-                $this->recurIterator->next();
363
-            } while (isset($this->exceptions[$nextDate->getTimeStamp()]));
364
-        }
365
-
366
-        // $nextDate now contains what rrule thinks is the next one, but an
367
-        // overridden event may cut ahead.
368
-        if ($this->overriddenEventsIndex) {
369
-            $offsets = end($this->overriddenEventsIndex);
370
-            $timestamp = key($this->overriddenEventsIndex);
371
-            $offset = end($offsets);
372
-            if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
373
-                // Overridden event comes first.
374
-                $this->currentOverriddenEvent = $this->overriddenEvents[$offset];
375
-
376
-                // Putting the rrule next date aside.
377
-                $this->nextDate = $nextDate;
378
-                $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
379
-
380
-                // Ensuring that this item will only be used once.
381
-                array_pop($this->overriddenEventsIndex[$timestamp]);
382
-                if (!$this->overriddenEventsIndex[$timestamp]) {
383
-                    array_pop($this->overriddenEventsIndex);
384
-                }
385
-
386
-                // Exit point!
387
-                return;
388
-            }
389
-        }
390
-
391
-        $this->currentDate = $nextDate;
392
-    }
393
-
394
-    /**
395
-     * Quickly jump to a date in the future.
396
-     */
397
-    public function fastForward(DateTimeInterface $dateTime)
398
-    {
399
-        while ($this->valid() && $this->getDtEnd() <= $dateTime) {
400
-            $this->next();
401
-        }
402
-    }
403
-
404
-    /**
405
-     * Returns true if this recurring event never ends.
406
-     *
407
-     * @return bool
408
-     */
409
-    public function isInfinite()
410
-    {
411
-        return $this->recurIterator->isInfinite();
412
-    }
413
-
414
-    /**
415
-     * RRULE parser.
416
-     *
417
-     * @var RRuleIterator
418
-     */
419
-    protected $recurIterator;
420
-
421
-    /**
422
-     * The duration, in seconds, of the master event.
423
-     *
424
-     * We use this to calculate the DTEND for subsequent events.
425
-     */
426
-    protected $eventDuration;
427
-
428
-    /**
429
-     * A reference to the main (master) event.
430
-     *
431
-     * @var VEVENT
432
-     */
433
-    protected $masterEvent;
434
-
435
-    /**
436
-     * List of overridden events.
437
-     *
438
-     * @var array
439
-     */
440
-    protected $overriddenEvents = [];
441
-
442
-    /**
443
-     * Overridden event index.
444
-     *
445
-     * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent
446
-     * property.
447
-     *
448
-     * @var array
449
-     */
450
-    protected $overriddenEventsIndex;
451
-
452
-    /**
453
-     * A list of recurrence-id's that are either part of EXDATE, or are
454
-     * overridden.
455
-     *
456
-     * @var array
457
-     */
458
-    protected $exceptions = [];
459
-
460
-    /**
461
-     * Internal event counter.
462
-     *
463
-     * @var int
464
-     */
465
-    protected $counter;
466
-
467
-    /**
468
-     * The very start of the iteration process.
469
-     *
470
-     * @var DateTimeImmutable
471
-     */
472
-    protected $startDate;
473
-
474
-    /**
475
-     * Where we are currently in the iteration process.
476
-     *
477
-     * @var DateTimeImmutable
478
-     */
479
-    protected $currentDate;
480
-
481
-    /**
482
-     * The next date from the rrule parser.
483
-     *
484
-     * Sometimes we need to temporary store the next date, because an
485
-     * overridden event came before.
486
-     *
487
-     * @var DateTimeImmutable
488
-     */
489
-    protected $nextDate;
490
-
491
-    /**
492
-     * The event that overwrites the current iteration.
493
-     *
494
-     * @var VEVENT
495
-     */
496
-    protected $currentOverriddenEvent;
63
+	/**
64
+	 * Reference timeZone for floating dates and times.
65
+	 *
66
+	 * @var DateTimeZone
67
+	 */
68
+	protected $timeZone;
69
+
70
+	/**
71
+	 * True if we're iterating an all-day event.
72
+	 *
73
+	 * @var bool
74
+	 */
75
+	protected $allDay = false;
76
+
77
+	/**
78
+	 * Creates the iterator.
79
+	 *
80
+	 * There's three ways to set up the iterator.
81
+	 *
82
+	 * 1. You can pass a VCALENDAR component and a UID.
83
+	 * 2. You can pass an array of VEVENTs (all UIDS should match).
84
+	 * 3. You can pass a single VEVENT component.
85
+	 *
86
+	 * Only the second method is recommended. The other 1 and 3 will be removed
87
+	 * at some point in the future.
88
+	 *
89
+	 * The $uid parameter is only required for the first method.
90
+	 *
91
+	 * @param Component|array $input
92
+	 * @param string|null     $uid
93
+	 * @param DateTimeZone    $timeZone reference timezone for floating dates and
94
+	 *                                  times
95
+	 */
96
+	public 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
+		foreach ($events as $vevent) {
121
+			if (!isset($vevent->{'RECURRENCE-ID'})) {
122
+				$this->masterEvent = $vevent;
123
+			} else {
124
+				$this->exceptions[
125
+					$vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
126
+				] = true;
127
+				$this->overriddenEvents[] = $vevent;
128
+			}
129
+		}
130
+
131
+		if (!$this->masterEvent) {
132
+			// No base event was found. CalDAV does allow cases where only
133
+			// overridden instances are stored.
134
+			//
135
+			// In this particular case, we're just going to grab the first
136
+			// event and use that instead. This may not always give the
137
+			// desired result.
138
+			if (!count($this->overriddenEvents)) {
139
+				throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid);
140
+			}
141
+			$this->masterEvent = array_shift($this->overriddenEvents);
142
+		}
143
+
144
+		$this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
145
+		$this->allDay = !$this->masterEvent->DTSTART->hasTime();
146
+
147
+		if (isset($this->masterEvent->EXDATE)) {
148
+			foreach ($this->masterEvent->EXDATE as $exDate) {
149
+				foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
150
+					$this->exceptions[$dt->getTimeStamp()] = true;
151
+				}
152
+			}
153
+		}
154
+
155
+		if (isset($this->masterEvent->DTEND)) {
156
+			$this->eventDuration =
157
+				$this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
158
+				$this->startDate->getTimeStamp();
159
+		} elseif (isset($this->masterEvent->DURATION)) {
160
+			$duration = $this->masterEvent->DURATION->getDateInterval();
161
+			$end = clone $this->startDate;
162
+			$end = $end->add($duration);
163
+			$this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
164
+		} elseif ($this->allDay) {
165
+			$this->eventDuration = 3600 * 24;
166
+		} else {
167
+			$this->eventDuration = 0;
168
+		}
169
+
170
+		if (isset($this->masterEvent->RDATE)) {
171
+			$this->recurIterator = new RDateIterator(
172
+				$this->masterEvent->RDATE->getParts(),
173
+				$this->startDate
174
+			);
175
+		} elseif (isset($this->masterEvent->RRULE)) {
176
+			$this->recurIterator = new RRuleIterator(
177
+				$this->masterEvent->RRULE->getParts(),
178
+				$this->startDate
179
+			);
180
+		} else {
181
+			$this->recurIterator = new RRuleIterator(
182
+				[
183
+					'FREQ' => 'DAILY',
184
+					'COUNT' => 1,
185
+				],
186
+				$this->startDate
187
+			);
188
+		}
189
+
190
+		$this->rewind();
191
+		if (!$this->valid()) {
192
+			throw new NoInstancesException('This recurrence rule does not generate any valid instances');
193
+		}
194
+	}
195
+
196
+	/**
197
+	 * Returns the date for the current position of the iterator.
198
+	 *
199
+	 * @return DateTimeImmutable
200
+	 */
201
+	#[\ReturnTypeWillChange]
202
+	public function current()
203
+	{
204
+		if ($this->currentDate) {
205
+			return clone $this->currentDate;
206
+		}
207
+	}
208
+
209
+	/**
210
+	 * This method returns the start date for the current iteration of the
211
+	 * event.
212
+	 *
213
+	 * @return DateTimeImmutable
214
+	 */
215
+	public function getDtStart()
216
+	{
217
+		if ($this->currentDate) {
218
+			return clone $this->currentDate;
219
+		}
220
+	}
221
+
222
+	/**
223
+	 * This method returns the end date for the current iteration of the
224
+	 * event.
225
+	 *
226
+	 * @return DateTimeImmutable
227
+	 */
228
+	public function getDtEnd()
229
+	{
230
+		if (!$this->valid()) {
231
+			return;
232
+		}
233
+		if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
234
+			return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
235
+		} else {
236
+			$end = clone $this->currentDate;
237
+
238
+			return $end->modify('+'.$this->eventDuration.' seconds');
239
+		}
240
+	}
241
+
242
+	/**
243
+	 * Returns a VEVENT for the current iterations of the event.
244
+	 *
245
+	 * This VEVENT will have a recurrence id, and its DTSTART and DTEND
246
+	 * altered.
247
+	 *
248
+	 * @return VEvent
249
+	 */
250
+	public function getEventObject()
251
+	{
252
+		if ($this->currentOverriddenEvent) {
253
+			return $this->currentOverriddenEvent;
254
+		}
255
+
256
+		$event = clone $this->masterEvent;
257
+
258
+		// Ignoring the following block, because PHPUnit's code coverage
259
+		// ignores most of these lines, and this messes with our stats.
260
+		//
261
+		// @codeCoverageIgnoreStart
262
+		unset(
263
+			$event->RRULE,
264
+			$event->EXDATE,
265
+			$event->RDATE,
266
+			$event->EXRULE,
267
+			$event->{'RECURRENCE-ID'}
268
+		);
269
+		// @codeCoverageIgnoreEnd
270
+
271
+		$event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
272
+		if (isset($event->DTEND)) {
273
+			$event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
274
+		}
275
+		$recurid = clone $event->DTSTART;
276
+		$recurid->name = 'RECURRENCE-ID';
277
+		$event->add($recurid);
278
+
279
+		return $event;
280
+	}
281
+
282
+	/**
283
+	 * Returns the current position of the iterator.
284
+	 *
285
+	 * This is for us simply a 0-based index.
286
+	 *
287
+	 * @return int
288
+	 */
289
+	#[\ReturnTypeWillChange]
290
+	public function key()
291
+	{
292
+		// The counter is always 1 ahead.
293
+		return $this->counter - 1;
294
+	}
295
+
296
+	/**
297
+	 * This is called after next, to see if the iterator is still at a valid
298
+	 * position, or if it's at the end.
299
+	 *
300
+	 * @return bool
301
+	 */
302
+	#[\ReturnTypeWillChange]
303
+	public function valid()
304
+	{
305
+		if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) {
306
+			throw new MaxInstancesExceededException('Recurring events are only allowed to generate '.Settings::$maxRecurrences);
307
+		}
308
+
309
+		return (bool) $this->currentDate;
310
+	}
311
+
312
+	/**
313
+	 * Sets the iterator back to the starting point.
314
+	 *
315
+	 * @return void
316
+	 */
317
+	#[\ReturnTypeWillChange]
318
+	public function rewind()
319
+	{
320
+		$this->recurIterator->rewind();
321
+		// re-creating overridden event index.
322
+		$index = [];
323
+		foreach ($this->overriddenEvents as $key => $event) {
324
+			$stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
325
+			$index[$stamp][] = $key;
326
+		}
327
+		krsort($index);
328
+		$this->counter = 0;
329
+		$this->overriddenEventsIndex = $index;
330
+		$this->currentOverriddenEvent = null;
331
+
332
+		$this->nextDate = null;
333
+		$this->currentDate = clone $this->startDate;
334
+
335
+		$this->next();
336
+	}
337
+
338
+	/**
339
+	 * Advances the iterator with one step.
340
+	 *
341
+	 * @return void
342
+	 */
343
+	#[\ReturnTypeWillChange]
344
+	public function next()
345
+	{
346
+		$this->currentOverriddenEvent = null;
347
+		++$this->counter;
348
+		if ($this->nextDate) {
349
+			// We had a stored value.
350
+			$nextDate = $this->nextDate;
351
+			$this->nextDate = null;
352
+		} else {
353
+			// We need to ask rruleparser for the next date.
354
+			// We need to do this until we find a date that's not in the
355
+			// exception list.
356
+			do {
357
+				if (!$this->recurIterator->valid()) {
358
+					$nextDate = null;
359
+					break;
360
+				}
361
+				$nextDate = $this->recurIterator->current();
362
+				$this->recurIterator->next();
363
+			} while (isset($this->exceptions[$nextDate->getTimeStamp()]));
364
+		}
365
+
366
+		// $nextDate now contains what rrule thinks is the next one, but an
367
+		// overridden event may cut ahead.
368
+		if ($this->overriddenEventsIndex) {
369
+			$offsets = end($this->overriddenEventsIndex);
370
+			$timestamp = key($this->overriddenEventsIndex);
371
+			$offset = end($offsets);
372
+			if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
373
+				// Overridden event comes first.
374
+				$this->currentOverriddenEvent = $this->overriddenEvents[$offset];
375
+
376
+				// Putting the rrule next date aside.
377
+				$this->nextDate = $nextDate;
378
+				$this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
379
+
380
+				// Ensuring that this item will only be used once.
381
+				array_pop($this->overriddenEventsIndex[$timestamp]);
382
+				if (!$this->overriddenEventsIndex[$timestamp]) {
383
+					array_pop($this->overriddenEventsIndex);
384
+				}
385
+
386
+				// Exit point!
387
+				return;
388
+			}
389
+		}
390
+
391
+		$this->currentDate = $nextDate;
392
+	}
393
+
394
+	/**
395
+	 * Quickly jump to a date in the future.
396
+	 */
397
+	public function fastForward(DateTimeInterface $dateTime)
398
+	{
399
+		while ($this->valid() && $this->getDtEnd() <= $dateTime) {
400
+			$this->next();
401
+		}
402
+	}
403
+
404
+	/**
405
+	 * Returns true if this recurring event never ends.
406
+	 *
407
+	 * @return bool
408
+	 */
409
+	public function isInfinite()
410
+	{
411
+		return $this->recurIterator->isInfinite();
412
+	}
413
+
414
+	/**
415
+	 * RRULE parser.
416
+	 *
417
+	 * @var RRuleIterator
418
+	 */
419
+	protected $recurIterator;
420
+
421
+	/**
422
+	 * The duration, in seconds, of the master event.
423
+	 *
424
+	 * We use this to calculate the DTEND for subsequent events.
425
+	 */
426
+	protected $eventDuration;
427
+
428
+	/**
429
+	 * A reference to the main (master) event.
430
+	 *
431
+	 * @var VEVENT
432
+	 */
433
+	protected $masterEvent;
434
+
435
+	/**
436
+	 * List of overridden events.
437
+	 *
438
+	 * @var array
439
+	 */
440
+	protected $overriddenEvents = [];
441
+
442
+	/**
443
+	 * Overridden event index.
444
+	 *
445
+	 * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent
446
+	 * property.
447
+	 *
448
+	 * @var array
449
+	 */
450
+	protected $overriddenEventsIndex;
451
+
452
+	/**
453
+	 * A list of recurrence-id's that are either part of EXDATE, or are
454
+	 * overridden.
455
+	 *
456
+	 * @var array
457
+	 */
458
+	protected $exceptions = [];
459
+
460
+	/**
461
+	 * Internal event counter.
462
+	 *
463
+	 * @var int
464
+	 */
465
+	protected $counter;
466
+
467
+	/**
468
+	 * The very start of the iteration process.
469
+	 *
470
+	 * @var DateTimeImmutable
471
+	 */
472
+	protected $startDate;
473
+
474
+	/**
475
+	 * Where we are currently in the iteration process.
476
+	 *
477
+	 * @var DateTimeImmutable
478
+	 */
479
+	protected $currentDate;
480
+
481
+	/**
482
+	 * The next date from the rrule parser.
483
+	 *
484
+	 * Sometimes we need to temporary store the next date, because an
485
+	 * overridden event came before.
486
+	 *
487
+	 * @var DateTimeImmutable
488
+	 */
489
+	protected $nextDate;
490
+
491
+	/**
492
+	 * The event that overwrites the current iteration.
493
+	 *
494
+	 * @var VEVENT
495
+	 */
496
+	protected $currentOverriddenEvent;
497 497
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Recur/RRuleIterator.php 1 patch
Indentation   +983 added lines, -983 removed lines patch added patch discarded remove patch
@@ -24,987 +24,987 @@
 block discarded – undo
24 24
  */
25 25
 class RRuleIterator implements Iterator
26 26
 {
27
-    /**
28
-     * Constant denoting the upper limit on how long into the future
29
-     * we want to iterate. The value is a unix timestamp and currently
30
-     * corresponds to the datetime 9999-12-31 11:59:59 UTC.
31
-     */
32
-    const dateUpperLimit = 253402300799;
33
-
34
-    /**
35
-     * Creates the Iterator.
36
-     *
37
-     * @param string|array $rrule
38
-     */
39
-    public function __construct($rrule, DateTimeInterface $start)
40
-    {
41
-        $this->startDate = $start;
42
-        $this->parseRRule($rrule);
43
-        $this->currentDate = clone $this->startDate;
44
-    }
45
-
46
-    /* Implementation of the Iterator interface {{{ */
47
-
48
-    #[\ReturnTypeWillChange]
49
-    public function current()
50
-    {
51
-        if (!$this->valid()) {
52
-            return;
53
-        }
54
-
55
-        return clone $this->currentDate;
56
-    }
57
-
58
-    /**
59
-     * Returns the current item number.
60
-     *
61
-     * @return int
62
-     */
63
-    #[\ReturnTypeWillChange]
64
-    public function key()
65
-    {
66
-        return $this->counter;
67
-    }
68
-
69
-    /**
70
-     * Returns whether the current item is a valid item for the recurrence
71
-     * iterator. This will return false if we've gone beyond the UNTIL or COUNT
72
-     * statements.
73
-     *
74
-     * @return bool
75
-     */
76
-    #[\ReturnTypeWillChange]
77
-    public function valid()
78
-    {
79
-        if (null === $this->currentDate) {
80
-            return false;
81
-        }
82
-        if (!is_null($this->count)) {
83
-            return $this->counter < $this->count;
84
-        }
85
-
86
-        return is_null($this->until) || $this->currentDate <= $this->until;
87
-    }
88
-
89
-    /**
90
-     * Resets the iterator.
91
-     *
92
-     * @return void
93
-     */
94
-    #[\ReturnTypeWillChange]
95
-    public function rewind()
96
-    {
97
-        $this->currentDate = clone $this->startDate;
98
-        $this->counter = 0;
99
-    }
100
-
101
-    /**
102
-     * Goes on to the next iteration.
103
-     *
104
-     * @return void
105
-     */
106
-    #[\ReturnTypeWillChange]
107
-    public function next()
108
-    {
109
-        // Otherwise, we find the next event in the normal RRULE
110
-        // sequence.
111
-        switch ($this->frequency) {
112
-            case 'hourly':
113
-                $this->nextHourly();
114
-                break;
115
-
116
-            case 'daily':
117
-                $this->nextDaily();
118
-                break;
119
-
120
-            case 'weekly':
121
-                $this->nextWeekly();
122
-                break;
123
-
124
-            case 'monthly':
125
-                $this->nextMonthly();
126
-                break;
127
-
128
-            case 'yearly':
129
-                $this->nextYearly();
130
-                break;
131
-        }
132
-        ++$this->counter;
133
-    }
134
-
135
-    /* End of Iterator implementation }}} */
136
-
137
-    /**
138
-     * Returns true if this recurring event never ends.
139
-     *
140
-     * @return bool
141
-     */
142
-    public function isInfinite()
143
-    {
144
-        return !$this->count && !$this->until;
145
-    }
146
-
147
-    /**
148
-     * This method allows you to quickly go to the next occurrence after the
149
-     * specified date.
150
-     */
151
-    public function fastForward(DateTimeInterface $dt)
152
-    {
153
-        while ($this->valid() && $this->currentDate < $dt) {
154
-            $this->next();
155
-        }
156
-    }
157
-
158
-    /**
159
-     * The reference start date/time for the rrule.
160
-     *
161
-     * All calculations are based on this initial date.
162
-     *
163
-     * @var DateTimeInterface
164
-     */
165
-    protected $startDate;
166
-
167
-    /**
168
-     * The date of the current iteration. You can get this by calling
169
-     * ->current().
170
-     *
171
-     * @var DateTimeInterface
172
-     */
173
-    protected $currentDate;
174
-
175
-    /**
176
-     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
177
-     * yearly.
178
-     *
179
-     * @var string
180
-     */
181
-    protected $frequency;
182
-
183
-    /**
184
-     * The number of recurrences, or 'null' if infinitely recurring.
185
-     *
186
-     * @var int
187
-     */
188
-    protected $count;
189
-
190
-    /**
191
-     * The interval.
192
-     *
193
-     * If for example frequency is set to daily, interval = 2 would mean every
194
-     * 2 days.
195
-     *
196
-     * @var int
197
-     */
198
-    protected $interval = 1;
199
-
200
-    /**
201
-     * The last instance of this recurrence, inclusively.
202
-     *
203
-     * @var DateTimeInterface|null
204
-     */
205
-    protected $until;
206
-
207
-    /**
208
-     * Which seconds to recur.
209
-     *
210
-     * This is an array of integers (between 0 and 60)
211
-     *
212
-     * @var array
213
-     */
214
-    protected $bySecond;
215
-
216
-    /**
217
-     * Which minutes to recur.
218
-     *
219
-     * This is an array of integers (between 0 and 59)
220
-     *
221
-     * @var array
222
-     */
223
-    protected $byMinute;
224
-
225
-    /**
226
-     * Which hours to recur.
227
-     *
228
-     * This is an array of integers (between 0 and 23)
229
-     *
230
-     * @var array
231
-     */
232
-    protected $byHour;
233
-
234
-    /**
235
-     * The current item in the list.
236
-     *
237
-     * You can get this number with the key() method.
238
-     *
239
-     * @var int
240
-     */
241
-    protected $counter = 0;
242
-
243
-    /**
244
-     * Which weekdays to recur.
245
-     *
246
-     * This is an array of weekdays
247
-     *
248
-     * This may also be preceded by a positive or negative integer. If present,
249
-     * this indicates the nth occurrence of a specific day within the monthly or
250
-     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
251
-     * the month, or year.
252
-     *
253
-     * @var array
254
-     */
255
-    protected $byDay;
256
-
257
-    /**
258
-     * Which days of the month to recur.
259
-     *
260
-     * This is an array of days of the months (1-31). The value can also be
261
-     * negative. -5 for instance means the 5th last day of the month.
262
-     *
263
-     * @var array
264
-     */
265
-    protected $byMonthDay;
266
-
267
-    /**
268
-     * Which days of the year to recur.
269
-     *
270
-     * This is an array with days of the year (1 to 366). The values can also
271
-     * be negative. For instance, -1 will always represent the last day of the
272
-     * year. (December 31st).
273
-     *
274
-     * @var array
275
-     */
276
-    protected $byYearDay;
277
-
278
-    /**
279
-     * Which week numbers to recur.
280
-     *
281
-     * This is an array of integers from 1 to 53. The values can also be
282
-     * negative. -1 will always refer to the last week of the year.
283
-     *
284
-     * @var array
285
-     */
286
-    protected $byWeekNo;
287
-
288
-    /**
289
-     * Which months to recur.
290
-     *
291
-     * This is an array of integers from 1 to 12.
292
-     *
293
-     * @var array
294
-     */
295
-    protected $byMonth;
296
-
297
-    /**
298
-     * Which items in an existing st to recur.
299
-     *
300
-     * These numbers work together with an existing by* rule. It specifies
301
-     * exactly which items of the existing by-rule to filter.
302
-     *
303
-     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
304
-     * used to recur the last workday of the month.
305
-     *
306
-     * This would be done by setting frequency to 'monthly', byDay to
307
-     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
308
-     *
309
-     * @var array
310
-     */
311
-    protected $bySetPos;
312
-
313
-    /**
314
-     * When the week starts.
315
-     *
316
-     * @var string
317
-     */
318
-    protected $weekStart = 'MO';
319
-
320
-    /* Functions that advance the iterator {{{ */
321
-
322
-    /**
323
-     * Does the processing for advancing the iterator for hourly frequency.
324
-     */
325
-    protected function nextHourly()
326
-    {
327
-        $this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours');
328
-    }
329
-
330
-    /**
331
-     * Does the processing for advancing the iterator for daily frequency.
332
-     */
333
-    protected function nextDaily()
334
-    {
335
-        if (!$this->byHour && !$this->byDay) {
336
-            $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
337
-
338
-            return;
339
-        }
340
-
341
-        $recurrenceHours = [];
342
-        if (!empty($this->byHour)) {
343
-            $recurrenceHours = $this->getHours();
344
-        }
345
-
346
-        $recurrenceDays = [];
347
-        if (!empty($this->byDay)) {
348
-            $recurrenceDays = $this->getDays();
349
-        }
350
-
351
-        $recurrenceMonths = [];
352
-        if (!empty($this->byMonth)) {
353
-            $recurrenceMonths = $this->getMonths();
354
-        }
355
-
356
-        do {
357
-            if ($this->byHour) {
358
-                if ('23' == $this->currentDate->format('G')) {
359
-                    // to obey the interval rule
360
-                    $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
361
-                }
362
-
363
-                $this->currentDate = $this->currentDate->modify('+1 hours');
364
-            } else {
365
-                $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
366
-            }
367
-
368
-            // Current month of the year
369
-            $currentMonth = $this->currentDate->format('n');
370
-
371
-            // Current day of the week
372
-            $currentDay = $this->currentDate->format('w');
373
-
374
-            // Current hour of the day
375
-            $currentHour = $this->currentDate->format('G');
376
-
377
-            if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
378
-                $this->currentDate = null;
379
-
380
-                return;
381
-            }
382
-        } while (
383
-            ($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
384
-            ($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
385
-            ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
386
-        );
387
-    }
388
-
389
-    /**
390
-     * Does the processing for advancing the iterator for weekly frequency.
391
-     */
392
-    protected function nextWeekly()
393
-    {
394
-        if (!$this->byHour && !$this->byDay) {
395
-            $this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
396
-
397
-            return;
398
-        }
399
-
400
-        $recurrenceHours = [];
401
-        if ($this->byHour) {
402
-            $recurrenceHours = $this->getHours();
403
-        }
404
-
405
-        $recurrenceDays = [];
406
-        if ($this->byDay) {
407
-            $recurrenceDays = $this->getDays();
408
-        }
409
-
410
-        // First day of the week:
411
-        $firstDay = $this->dayMap[$this->weekStart];
412
-
413
-        do {
414
-            if ($this->byHour) {
415
-                $this->currentDate = $this->currentDate->modify('+1 hours');
416
-            } else {
417
-                $this->currentDate = $this->currentDate->modify('+1 days');
418
-            }
419
-
420
-            // Current day of the week
421
-            $currentDay = (int) $this->currentDate->format('w');
422
-
423
-            // Current hour of the day
424
-            $currentHour = (int) $this->currentDate->format('G');
425
-
426
-            // We need to roll over to the next week
427
-            if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
428
-                $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
429
-
430
-                // We need to go to the first day of this week, but only if we
431
-                // are not already on this first day of this week.
432
-                if ($this->currentDate->format('w') != $firstDay) {
433
-                    $this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
434
-                }
435
-            }
436
-
437
-            // We have a match
438
-        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
439
-    }
440
-
441
-    /**
442
-     * Does the processing for advancing the iterator for monthly frequency.
443
-     */
444
-    protected function nextMonthly()
445
-    {
446
-        $currentDayOfMonth = $this->currentDate->format('j');
447
-        if (!$this->byMonthDay && !$this->byDay) {
448
-            // If the current day is higher than the 28th, rollover can
449
-            // occur to the next month. We Must skip these invalid
450
-            // entries.
451
-            if ($currentDayOfMonth < 29) {
452
-                $this->currentDate = $this->currentDate->modify('+'.$this->interval.' months');
453
-            } else {
454
-                $increase = 0;
455
-                do {
456
-                    ++$increase;
457
-                    $tempDate = clone $this->currentDate;
458
-                    $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months');
459
-                } while ($tempDate->format('j') != $currentDayOfMonth);
460
-                $this->currentDate = $tempDate;
461
-            }
462
-
463
-            return;
464
-        }
465
-
466
-        $occurrence = -1;
467
-        while (true) {
468
-            $occurrences = $this->getMonthlyOccurrences();
469
-
470
-            foreach ($occurrences as $occurrence) {
471
-                // The first occurrence thats higher than the current
472
-                // day of the month wins.
473
-                if ($occurrence > $currentDayOfMonth) {
474
-                    break 2;
475
-                }
476
-            }
477
-
478
-            // If we made it all the way here, it means there were no
479
-            // valid occurrences, and we need to advance to the next
480
-            // month.
481
-            //
482
-            // This line does not currently work in hhvm. Temporary workaround
483
-            // follows:
484
-            // $this->currentDate->modify('first day of this month');
485
-            $this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
486
-            // end of workaround
487
-            $this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months');
488
-
489
-            // This goes to 0 because we need to start counting at the
490
-            // beginning.
491
-            $currentDayOfMonth = 0;
492
-
493
-            // For some reason the "until" parameter was not being used here,
494
-            // that's why the workaround of the 10000 year bug was needed at all
495
-            // let's stop it before the "until" parameter date
496
-            if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) {
497
-                return;
498
-            }
499
-
500
-            // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
501
-            // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
502
-            if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
503
-                $this->currentDate = null;
504
-
505
-                return;
506
-            }
507
-        }
508
-
509
-        $this->currentDate = $this->currentDate->setDate(
510
-            (int) $this->currentDate->format('Y'),
511
-            (int) $this->currentDate->format('n'),
512
-            (int) $occurrence
513
-        );
514
-    }
515
-
516
-    /**
517
-     * Does the processing for advancing the iterator for yearly frequency.
518
-     */
519
-    protected function nextYearly()
520
-    {
521
-        $currentMonth = $this->currentDate->format('n');
522
-        $currentYear = $this->currentDate->format('Y');
523
-        $currentDayOfMonth = $this->currentDate->format('j');
524
-
525
-        // No sub-rules, so we just advance by year
526
-        if (empty($this->byMonth)) {
527
-            // Unless it was a leap day!
528
-            if (2 == $currentMonth && 29 == $currentDayOfMonth) {
529
-                $counter = 0;
530
-                do {
531
-                    ++$counter;
532
-                    // Here we increase the year count by the interval, until
533
-                    // we hit a date that's also in a leap year.
534
-                    //
535
-                    // We could just find the next interval that's dividable by
536
-                    // 4, but that would ignore the rule that there's no leap
537
-                    // year every year that's dividable by a 100, but not by
538
-                    // 400. (1800, 1900, 2100). So we just rely on the datetime
539
-                    // functions instead.
540
-                    $nextDate = clone $this->currentDate;
541
-                    $nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
542
-                } while (2 != $nextDate->format('n'));
543
-
544
-                $this->currentDate = $nextDate;
545
-
546
-                return;
547
-            }
548
-
549
-            if (null !== $this->byWeekNo) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
550
-                $dayOffsets = [];
551
-                if ($this->byDay) {
552
-                    foreach ($this->byDay as $byDay) {
553
-                        $dayOffsets[] = $this->dayMap[$byDay];
554
-                    }
555
-                } else {   // default is Monday
556
-                    $dayOffsets[] = 1;
557
-                }
558
-
559
-                $currentYear = $this->currentDate->format('Y');
560
-
561
-                while (true) {
562
-                    $checkDates = [];
563
-
564
-                    // loop through all WeekNo and Days to check all the combinations
565
-                    foreach ($this->byWeekNo as $byWeekNo) {
566
-                        foreach ($dayOffsets as $dayOffset) {
567
-                            $date = clone $this->currentDate;
568
-                            $date = $date->setISODate($currentYear, $byWeekNo, $dayOffset);
569
-
570
-                            if ($date > $this->currentDate) {
571
-                                $checkDates[] = $date;
572
-                            }
573
-                        }
574
-                    }
575
-
576
-                    if (count($checkDates) > 0) {
577
-                        $this->currentDate = min($checkDates);
578
-
579
-                        return;
580
-                    }
581
-
582
-                    // if there is no date found, check the next year
583
-                    $currentYear += $this->interval;
584
-                }
585
-            }
586
-
587
-            if (null !== $this->byYearDay) { // byYearDay is an array with values from -366 to -1, or 1 to 366
588
-                $dayOffsets = [];
589
-                if ($this->byDay) {
590
-                    foreach ($this->byDay as $byDay) {
591
-                        $dayOffsets[] = $this->dayMap[$byDay];
592
-                    }
593
-                } else {   // default is Monday-Sunday
594
-                    $dayOffsets = [1, 2, 3, 4, 5, 6, 7];
595
-                }
596
-
597
-                $currentYear = $this->currentDate->format('Y');
598
-
599
-                while (true) {
600
-                    $checkDates = [];
601
-
602
-                    // loop through all YearDay and Days to check all the combinations
603
-                    foreach ($this->byYearDay as $byYearDay) {
604
-                        $date = clone $this->currentDate;
605
-                        if ($byYearDay > 0) {
606
-                            $date = $date->setDate($currentYear, 1, 1);
607
-                            $date = $date->add(new \DateInterval('P'.($byYearDay - 1).'D'));
608
-                        } else {
609
-                            $date = $date->setDate($currentYear, 12, 31);
610
-                            $date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
611
-                        }
612
-
613
-                        if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
614
-                            $checkDates[] = $date;
615
-                        }
616
-                    }
617
-
618
-                    if (count($checkDates) > 0) {
619
-                        $this->currentDate = min($checkDates);
620
-
621
-                        return;
622
-                    }
623
-
624
-                    // if there is no date found, check the next year
625
-                    $currentYear += $this->interval;
626
-                }
627
-            }
628
-
629
-            // The easiest form
630
-            $this->currentDate = $this->currentDate->modify('+'.$this->interval.' years');
631
-
632
-            return;
633
-        }
634
-
635
-        $currentMonth = $this->currentDate->format('n');
636
-        $currentYear = $this->currentDate->format('Y');
637
-        $currentDayOfMonth = $this->currentDate->format('j');
638
-
639
-        $advancedToNewMonth = false;
640
-
641
-        // If we got a byDay or getMonthDay filter, we must first expand
642
-        // further.
643
-        if ($this->byDay || $this->byMonthDay) {
644
-            $occurrence = -1;
645
-            while (true) {
646
-                $occurrences = $this->getMonthlyOccurrences();
647
-
648
-                foreach ($occurrences as $occurrence) {
649
-                    // The first occurrence that's higher than the current
650
-                    // day of the month wins.
651
-                    // If we advanced to the next month or year, the first
652
-                    // occurrence is always correct.
653
-                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
654
-                        break 2;
655
-                    }
656
-                }
657
-
658
-                // If we made it here, it means we need to advance to
659
-                // the next month or year.
660
-                $currentDayOfMonth = 1;
661
-                $advancedToNewMonth = true;
662
-                do {
663
-                    ++$currentMonth;
664
-                    if ($currentMonth > 12) {
665
-                        $currentYear += $this->interval;
666
-                        $currentMonth = 1;
667
-                    }
668
-                } while (!in_array($currentMonth, $this->byMonth));
669
-
670
-                $this->currentDate = $this->currentDate->setDate(
671
-                    (int) $currentYear,
672
-                    (int) $currentMonth,
673
-                    (int) $currentDayOfMonth
674
-                );
675
-
676
-                // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
677
-                // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
678
-                if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
679
-                    $this->currentDate = null;
680
-
681
-                    return;
682
-                }
683
-            }
684
-
685
-            // If we made it here, it means we got a valid occurrence
686
-            $this->currentDate = $this->currentDate->setDate(
687
-                (int) $currentYear,
688
-                (int) $currentMonth,
689
-                (int) $occurrence
690
-            );
691
-
692
-            return;
693
-        } else {
694
-            // These are the 'byMonth' rules, if there are no byDay or
695
-            // byMonthDay sub-rules.
696
-            do {
697
-                ++$currentMonth;
698
-                if ($currentMonth > 12) {
699
-                    $currentYear += $this->interval;
700
-                    $currentMonth = 1;
701
-                }
702
-            } while (!in_array($currentMonth, $this->byMonth));
703
-            $this->currentDate = $this->currentDate->setDate(
704
-                (int) $currentYear,
705
-                (int) $currentMonth,
706
-                (int) $currentDayOfMonth
707
-            );
708
-
709
-            return;
710
-        }
711
-    }
712
-
713
-    /* }}} */
714
-
715
-    /**
716
-     * This method receives a string from an RRULE property, and populates this
717
-     * class with all the values.
718
-     *
719
-     * @param string|array $rrule
720
-     */
721
-    protected function parseRRule($rrule)
722
-    {
723
-        if (is_string($rrule)) {
724
-            $rrule = Property\ICalendar\Recur::stringToArray($rrule);
725
-        }
726
-
727
-        foreach ($rrule as $key => $value) {
728
-            $key = strtoupper($key);
729
-            switch ($key) {
730
-                case 'FREQ':
731
-                    $value = strtolower($value);
732
-                    if (!in_array(
733
-                        $value,
734
-                        ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
735
-                    )) {
736
-                        throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
737
-                    }
738
-                    $this->frequency = $value;
739
-                    break;
740
-
741
-                case 'UNTIL':
742
-                    $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
743
-
744
-                    // In some cases events are generated with an UNTIL=
745
-                    // parameter before the actual start of the event.
746
-                    //
747
-                    // Not sure why this is happening. We assume that the
748
-                    // intention was that the event only recurs once.
749
-                    //
750
-                    // So we are modifying the parameter so our code doesn't
751
-                    // break.
752
-                    if ($this->until < $this->startDate) {
753
-                        $this->until = $this->startDate;
754
-                    }
755
-                    break;
756
-
757
-                case 'INTERVAL':
758
-                case 'COUNT':
759
-                    $val = (int) $value;
760
-                    if ($val < 1) {
761
-                        throw new InvalidDataException(strtoupper($key).' in RRULE must be a positive integer!');
762
-                    }
763
-                    $key = strtolower($key);
764
-                    $this->$key = $val;
765
-                    break;
766
-
767
-                case 'BYSECOND':
768
-                    $this->bySecond = (array) $value;
769
-                    break;
770
-
771
-                case 'BYMINUTE':
772
-                    $this->byMinute = (array) $value;
773
-                    break;
774
-
775
-                case 'BYHOUR':
776
-                    $this->byHour = (array) $value;
777
-                    break;
778
-
779
-                case 'BYDAY':
780
-                    $value = (array) $value;
781
-                    foreach ($value as $part) {
782
-                        if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
783
-                            throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
784
-                        }
785
-                    }
786
-                    $this->byDay = $value;
787
-                    break;
788
-
789
-                case 'BYMONTHDAY':
790
-                    $this->byMonthDay = (array) $value;
791
-                    break;
792
-
793
-                case 'BYYEARDAY':
794
-                    $this->byYearDay = (array) $value;
795
-                    foreach ($this->byYearDay as $byYearDay) {
796
-                        if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
797
-                            throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
798
-                        }
799
-                    }
800
-                    break;
801
-
802
-                case 'BYWEEKNO':
803
-                    $this->byWeekNo = (array) $value;
804
-                    foreach ($this->byWeekNo as $byWeekNo) {
805
-                        if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
806
-                            throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
807
-                        }
808
-                    }
809
-                    break;
810
-
811
-                case 'BYMONTH':
812
-                    $this->byMonth = (array) $value;
813
-                    foreach ($this->byMonth as $byMonth) {
814
-                        if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) {
815
-                            throw new InvalidDataException('BYMONTH in RRULE must have value(s) between 1 and 12!');
816
-                        }
817
-                    }
818
-                    break;
819
-
820
-                case 'BYSETPOS':
821
-                    $this->bySetPos = (array) $value;
822
-                    break;
823
-
824
-                case 'WKST':
825
-                    $this->weekStart = strtoupper($value);
826
-                    break;
827
-
828
-                default:
829
-                    throw new InvalidDataException('Not supported: '.strtoupper($key));
830
-            }
831
-        }
832
-    }
833
-
834
-    /**
835
-     * Mappings between the day number and english day name.
836
-     *
837
-     * @var array
838
-     */
839
-    protected $dayNames = [
840
-        0 => 'Sunday',
841
-        1 => 'Monday',
842
-        2 => 'Tuesday',
843
-        3 => 'Wednesday',
844
-        4 => 'Thursday',
845
-        5 => 'Friday',
846
-        6 => 'Saturday',
847
-    ];
848
-
849
-    /**
850
-     * Returns all the occurrences for a monthly frequency with a 'byDay' or
851
-     * 'byMonthDay' expansion for the current month.
852
-     *
853
-     * The returned list is an array of integers with the day of month (1-31).
854
-     *
855
-     * @return array
856
-     */
857
-    protected function getMonthlyOccurrences()
858
-    {
859
-        $startDate = clone $this->currentDate;
860
-
861
-        $byDayResults = [];
862
-
863
-        // Our strategy is to simply go through the byDays, advance the date to
864
-        // that point and add it to the results.
865
-        if ($this->byDay) {
866
-            foreach ($this->byDay as $day) {
867
-                $dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
868
-
869
-                // Dayname will be something like 'wednesday'. Now we need to find
870
-                // all wednesdays in this month.
871
-                $dayHits = [];
872
-
873
-                // workaround for missing 'first day of the month' support in hhvm
874
-                $checkDate = new \DateTime($startDate->format('Y-m-1'));
875
-                // workaround modify always advancing the date even if the current day is a $dayName in hhvm
876
-                if ($checkDate->format('l') !== $dayName) {
877
-                    $checkDate = $checkDate->modify($dayName);
878
-                }
879
-
880
-                do {
881
-                    $dayHits[] = $checkDate->format('j');
882
-                    $checkDate = $checkDate->modify('next '.$dayName);
883
-                } while ($checkDate->format('n') === $startDate->format('n'));
884
-
885
-                // So now we have 'all wednesdays' for month. It is however
886
-                // possible that the user only really wanted the 1st, 2nd or last
887
-                // wednesday.
888
-                if (strlen($day) > 2) {
889
-                    $offset = (int) substr($day, 0, -2);
890
-
891
-                    if ($offset > 0) {
892
-                        // It is possible that the day does not exist, such as a
893
-                        // 5th or 6th wednesday of the month.
894
-                        if (isset($dayHits[$offset - 1])) {
895
-                            $byDayResults[] = $dayHits[$offset - 1];
896
-                        }
897
-                    } else {
898
-                        // if it was negative we count from the end of the array
899
-                        // might not exist, fx. -5th tuesday
900
-                        if (isset($dayHits[count($dayHits) + $offset])) {
901
-                            $byDayResults[] = $dayHits[count($dayHits) + $offset];
902
-                        }
903
-                    }
904
-                } else {
905
-                    // There was no counter (first, second, last wednesdays), so we
906
-                    // just need to add the all to the list).
907
-                    $byDayResults = array_merge($byDayResults, $dayHits);
908
-                }
909
-            }
910
-        }
911
-
912
-        $byMonthDayResults = [];
913
-        if ($this->byMonthDay) {
914
-            foreach ($this->byMonthDay as $monthDay) {
915
-                // Removing values that are out of range for this month
916
-                if ($monthDay > $startDate->format('t') ||
917
-                    $monthDay < 0 - $startDate->format('t')) {
918
-                    continue;
919
-                }
920
-                if ($monthDay > 0) {
921
-                    $byMonthDayResults[] = $monthDay;
922
-                } else {
923
-                    // Negative values
924
-                    $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
925
-                }
926
-            }
927
-        }
928
-
929
-        // If there was just byDay or just byMonthDay, they just specify our
930
-        // (almost) final list. If both were provided, then byDay limits the
931
-        // list.
932
-        if ($this->byMonthDay && $this->byDay) {
933
-            $result = array_intersect($byMonthDayResults, $byDayResults);
934
-        } elseif ($this->byMonthDay) {
935
-            $result = $byMonthDayResults;
936
-        } else {
937
-            $result = $byDayResults;
938
-        }
939
-        $result = array_unique($result);
940
-        sort($result, SORT_NUMERIC);
941
-
942
-        // The last thing that needs checking is the BYSETPOS. If it's set, it
943
-        // means only certain items in the set survive the filter.
944
-        if (!$this->bySetPos) {
945
-            return $result;
946
-        }
947
-
948
-        $filteredResult = [];
949
-        foreach ($this->bySetPos as $setPos) {
950
-            if ($setPos < 0) {
951
-                $setPos = count($result) + ($setPos + 1);
952
-            }
953
-            if (isset($result[$setPos - 1])) {
954
-                $filteredResult[] = $result[$setPos - 1];
955
-            }
956
-        }
957
-
958
-        sort($filteredResult, SORT_NUMERIC);
959
-
960
-        return $filteredResult;
961
-    }
962
-
963
-    /**
964
-     * Simple mapping from iCalendar day names to day numbers.
965
-     *
966
-     * @var array
967
-     */
968
-    protected $dayMap = [
969
-        'SU' => 0,
970
-        'MO' => 1,
971
-        'TU' => 2,
972
-        'WE' => 3,
973
-        'TH' => 4,
974
-        'FR' => 5,
975
-        'SA' => 6,
976
-    ];
977
-
978
-    protected function getHours()
979
-    {
980
-        $recurrenceHours = [];
981
-        foreach ($this->byHour as $byHour) {
982
-            $recurrenceHours[] = $byHour;
983
-        }
984
-
985
-        return $recurrenceHours;
986
-    }
987
-
988
-    protected function getDays()
989
-    {
990
-        $recurrenceDays = [];
991
-        foreach ($this->byDay as $byDay) {
992
-            // The day may be preceded with a positive (+n) or
993
-            // negative (-n) integer. However, this does not make
994
-            // sense in 'weekly' so we ignore it here.
995
-            $recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
996
-        }
997
-
998
-        return $recurrenceDays;
999
-    }
1000
-
1001
-    protected function getMonths()
1002
-    {
1003
-        $recurrenceMonths = [];
1004
-        foreach ($this->byMonth as $byMonth) {
1005
-            $recurrenceMonths[] = $byMonth;
1006
-        }
1007
-
1008
-        return $recurrenceMonths;
1009
-    }
27
+	/**
28
+	 * Constant denoting the upper limit on how long into the future
29
+	 * we want to iterate. The value is a unix timestamp and currently
30
+	 * corresponds to the datetime 9999-12-31 11:59:59 UTC.
31
+	 */
32
+	const dateUpperLimit = 253402300799;
33
+
34
+	/**
35
+	 * Creates the Iterator.
36
+	 *
37
+	 * @param string|array $rrule
38
+	 */
39
+	public function __construct($rrule, DateTimeInterface $start)
40
+	{
41
+		$this->startDate = $start;
42
+		$this->parseRRule($rrule);
43
+		$this->currentDate = clone $this->startDate;
44
+	}
45
+
46
+	/* Implementation of the Iterator interface {{{ */
47
+
48
+	#[\ReturnTypeWillChange]
49
+	public function current()
50
+	{
51
+		if (!$this->valid()) {
52
+			return;
53
+		}
54
+
55
+		return clone $this->currentDate;
56
+	}
57
+
58
+	/**
59
+	 * Returns the current item number.
60
+	 *
61
+	 * @return int
62
+	 */
63
+	#[\ReturnTypeWillChange]
64
+	public function key()
65
+	{
66
+		return $this->counter;
67
+	}
68
+
69
+	/**
70
+	 * Returns whether the current item is a valid item for the recurrence
71
+	 * iterator. This will return false if we've gone beyond the UNTIL or COUNT
72
+	 * statements.
73
+	 *
74
+	 * @return bool
75
+	 */
76
+	#[\ReturnTypeWillChange]
77
+	public function valid()
78
+	{
79
+		if (null === $this->currentDate) {
80
+			return false;
81
+		}
82
+		if (!is_null($this->count)) {
83
+			return $this->counter < $this->count;
84
+		}
85
+
86
+		return is_null($this->until) || $this->currentDate <= $this->until;
87
+	}
88
+
89
+	/**
90
+	 * Resets the iterator.
91
+	 *
92
+	 * @return void
93
+	 */
94
+	#[\ReturnTypeWillChange]
95
+	public function rewind()
96
+	{
97
+		$this->currentDate = clone $this->startDate;
98
+		$this->counter = 0;
99
+	}
100
+
101
+	/**
102
+	 * Goes on to the next iteration.
103
+	 *
104
+	 * @return void
105
+	 */
106
+	#[\ReturnTypeWillChange]
107
+	public function next()
108
+	{
109
+		// Otherwise, we find the next event in the normal RRULE
110
+		// sequence.
111
+		switch ($this->frequency) {
112
+			case 'hourly':
113
+				$this->nextHourly();
114
+				break;
115
+
116
+			case 'daily':
117
+				$this->nextDaily();
118
+				break;
119
+
120
+			case 'weekly':
121
+				$this->nextWeekly();
122
+				break;
123
+
124
+			case 'monthly':
125
+				$this->nextMonthly();
126
+				break;
127
+
128
+			case 'yearly':
129
+				$this->nextYearly();
130
+				break;
131
+		}
132
+		++$this->counter;
133
+	}
134
+
135
+	/* End of Iterator implementation }}} */
136
+
137
+	/**
138
+	 * Returns true if this recurring event never ends.
139
+	 *
140
+	 * @return bool
141
+	 */
142
+	public function isInfinite()
143
+	{
144
+		return !$this->count && !$this->until;
145
+	}
146
+
147
+	/**
148
+	 * This method allows you to quickly go to the next occurrence after the
149
+	 * specified date.
150
+	 */
151
+	public function fastForward(DateTimeInterface $dt)
152
+	{
153
+		while ($this->valid() && $this->currentDate < $dt) {
154
+			$this->next();
155
+		}
156
+	}
157
+
158
+	/**
159
+	 * The reference start date/time for the rrule.
160
+	 *
161
+	 * All calculations are based on this initial date.
162
+	 *
163
+	 * @var DateTimeInterface
164
+	 */
165
+	protected $startDate;
166
+
167
+	/**
168
+	 * The date of the current iteration. You can get this by calling
169
+	 * ->current().
170
+	 *
171
+	 * @var DateTimeInterface
172
+	 */
173
+	protected $currentDate;
174
+
175
+	/**
176
+	 * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
177
+	 * yearly.
178
+	 *
179
+	 * @var string
180
+	 */
181
+	protected $frequency;
182
+
183
+	/**
184
+	 * The number of recurrences, or 'null' if infinitely recurring.
185
+	 *
186
+	 * @var int
187
+	 */
188
+	protected $count;
189
+
190
+	/**
191
+	 * The interval.
192
+	 *
193
+	 * If for example frequency is set to daily, interval = 2 would mean every
194
+	 * 2 days.
195
+	 *
196
+	 * @var int
197
+	 */
198
+	protected $interval = 1;
199
+
200
+	/**
201
+	 * The last instance of this recurrence, inclusively.
202
+	 *
203
+	 * @var DateTimeInterface|null
204
+	 */
205
+	protected $until;
206
+
207
+	/**
208
+	 * Which seconds to recur.
209
+	 *
210
+	 * This is an array of integers (between 0 and 60)
211
+	 *
212
+	 * @var array
213
+	 */
214
+	protected $bySecond;
215
+
216
+	/**
217
+	 * Which minutes to recur.
218
+	 *
219
+	 * This is an array of integers (between 0 and 59)
220
+	 *
221
+	 * @var array
222
+	 */
223
+	protected $byMinute;
224
+
225
+	/**
226
+	 * Which hours to recur.
227
+	 *
228
+	 * This is an array of integers (between 0 and 23)
229
+	 *
230
+	 * @var array
231
+	 */
232
+	protected $byHour;
233
+
234
+	/**
235
+	 * The current item in the list.
236
+	 *
237
+	 * You can get this number with the key() method.
238
+	 *
239
+	 * @var int
240
+	 */
241
+	protected $counter = 0;
242
+
243
+	/**
244
+	 * Which weekdays to recur.
245
+	 *
246
+	 * This is an array of weekdays
247
+	 *
248
+	 * This may also be preceded by a positive or negative integer. If present,
249
+	 * this indicates the nth occurrence of a specific day within the monthly or
250
+	 * yearly rrule. For instance, -2TU indicates the second-last tuesday of
251
+	 * the month, or year.
252
+	 *
253
+	 * @var array
254
+	 */
255
+	protected $byDay;
256
+
257
+	/**
258
+	 * Which days of the month to recur.
259
+	 *
260
+	 * This is an array of days of the months (1-31). The value can also be
261
+	 * negative. -5 for instance means the 5th last day of the month.
262
+	 *
263
+	 * @var array
264
+	 */
265
+	protected $byMonthDay;
266
+
267
+	/**
268
+	 * Which days of the year to recur.
269
+	 *
270
+	 * This is an array with days of the year (1 to 366). The values can also
271
+	 * be negative. For instance, -1 will always represent the last day of the
272
+	 * year. (December 31st).
273
+	 *
274
+	 * @var array
275
+	 */
276
+	protected $byYearDay;
277
+
278
+	/**
279
+	 * Which week numbers to recur.
280
+	 *
281
+	 * This is an array of integers from 1 to 53. The values can also be
282
+	 * negative. -1 will always refer to the last week of the year.
283
+	 *
284
+	 * @var array
285
+	 */
286
+	protected $byWeekNo;
287
+
288
+	/**
289
+	 * Which months to recur.
290
+	 *
291
+	 * This is an array of integers from 1 to 12.
292
+	 *
293
+	 * @var array
294
+	 */
295
+	protected $byMonth;
296
+
297
+	/**
298
+	 * Which items in an existing st to recur.
299
+	 *
300
+	 * These numbers work together with an existing by* rule. It specifies
301
+	 * exactly which items of the existing by-rule to filter.
302
+	 *
303
+	 * Valid values are 1 to 366 and -1 to -366. As an example, this can be
304
+	 * used to recur the last workday of the month.
305
+	 *
306
+	 * This would be done by setting frequency to 'monthly', byDay to
307
+	 * 'MO,TU,WE,TH,FR' and bySetPos to -1.
308
+	 *
309
+	 * @var array
310
+	 */
311
+	protected $bySetPos;
312
+
313
+	/**
314
+	 * When the week starts.
315
+	 *
316
+	 * @var string
317
+	 */
318
+	protected $weekStart = 'MO';
319
+
320
+	/* Functions that advance the iterator {{{ */
321
+
322
+	/**
323
+	 * Does the processing for advancing the iterator for hourly frequency.
324
+	 */
325
+	protected function nextHourly()
326
+	{
327
+		$this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours');
328
+	}
329
+
330
+	/**
331
+	 * Does the processing for advancing the iterator for daily frequency.
332
+	 */
333
+	protected function nextDaily()
334
+	{
335
+		if (!$this->byHour && !$this->byDay) {
336
+			$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
337
+
338
+			return;
339
+		}
340
+
341
+		$recurrenceHours = [];
342
+		if (!empty($this->byHour)) {
343
+			$recurrenceHours = $this->getHours();
344
+		}
345
+
346
+		$recurrenceDays = [];
347
+		if (!empty($this->byDay)) {
348
+			$recurrenceDays = $this->getDays();
349
+		}
350
+
351
+		$recurrenceMonths = [];
352
+		if (!empty($this->byMonth)) {
353
+			$recurrenceMonths = $this->getMonths();
354
+		}
355
+
356
+		do {
357
+			if ($this->byHour) {
358
+				if ('23' == $this->currentDate->format('G')) {
359
+					// to obey the interval rule
360
+					$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
361
+				}
362
+
363
+				$this->currentDate = $this->currentDate->modify('+1 hours');
364
+			} else {
365
+				$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
366
+			}
367
+
368
+			// Current month of the year
369
+			$currentMonth = $this->currentDate->format('n');
370
+
371
+			// Current day of the week
372
+			$currentDay = $this->currentDate->format('w');
373
+
374
+			// Current hour of the day
375
+			$currentHour = $this->currentDate->format('G');
376
+
377
+			if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
378
+				$this->currentDate = null;
379
+
380
+				return;
381
+			}
382
+		} while (
383
+			($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
384
+			($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
385
+			($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
386
+		);
387
+	}
388
+
389
+	/**
390
+	 * Does the processing for advancing the iterator for weekly frequency.
391
+	 */
392
+	protected function nextWeekly()
393
+	{
394
+		if (!$this->byHour && !$this->byDay) {
395
+			$this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
396
+
397
+			return;
398
+		}
399
+
400
+		$recurrenceHours = [];
401
+		if ($this->byHour) {
402
+			$recurrenceHours = $this->getHours();
403
+		}
404
+
405
+		$recurrenceDays = [];
406
+		if ($this->byDay) {
407
+			$recurrenceDays = $this->getDays();
408
+		}
409
+
410
+		// First day of the week:
411
+		$firstDay = $this->dayMap[$this->weekStart];
412
+
413
+		do {
414
+			if ($this->byHour) {
415
+				$this->currentDate = $this->currentDate->modify('+1 hours');
416
+			} else {
417
+				$this->currentDate = $this->currentDate->modify('+1 days');
418
+			}
419
+
420
+			// Current day of the week
421
+			$currentDay = (int) $this->currentDate->format('w');
422
+
423
+			// Current hour of the day
424
+			$currentHour = (int) $this->currentDate->format('G');
425
+
426
+			// We need to roll over to the next week
427
+			if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
428
+				$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
429
+
430
+				// We need to go to the first day of this week, but only if we
431
+				// are not already on this first day of this week.
432
+				if ($this->currentDate->format('w') != $firstDay) {
433
+					$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
434
+				}
435
+			}
436
+
437
+			// We have a match
438
+		} while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
439
+	}
440
+
441
+	/**
442
+	 * Does the processing for advancing the iterator for monthly frequency.
443
+	 */
444
+	protected function nextMonthly()
445
+	{
446
+		$currentDayOfMonth = $this->currentDate->format('j');
447
+		if (!$this->byMonthDay && !$this->byDay) {
448
+			// If the current day is higher than the 28th, rollover can
449
+			// occur to the next month. We Must skip these invalid
450
+			// entries.
451
+			if ($currentDayOfMonth < 29) {
452
+				$this->currentDate = $this->currentDate->modify('+'.$this->interval.' months');
453
+			} else {
454
+				$increase = 0;
455
+				do {
456
+					++$increase;
457
+					$tempDate = clone $this->currentDate;
458
+					$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months');
459
+				} while ($tempDate->format('j') != $currentDayOfMonth);
460
+				$this->currentDate = $tempDate;
461
+			}
462
+
463
+			return;
464
+		}
465
+
466
+		$occurrence = -1;
467
+		while (true) {
468
+			$occurrences = $this->getMonthlyOccurrences();
469
+
470
+			foreach ($occurrences as $occurrence) {
471
+				// The first occurrence thats higher than the current
472
+				// day of the month wins.
473
+				if ($occurrence > $currentDayOfMonth) {
474
+					break 2;
475
+				}
476
+			}
477
+
478
+			// If we made it all the way here, it means there were no
479
+			// valid occurrences, and we need to advance to the next
480
+			// month.
481
+			//
482
+			// This line does not currently work in hhvm. Temporary workaround
483
+			// follows:
484
+			// $this->currentDate->modify('first day of this month');
485
+			$this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
486
+			// end of workaround
487
+			$this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months');
488
+
489
+			// This goes to 0 because we need to start counting at the
490
+			// beginning.
491
+			$currentDayOfMonth = 0;
492
+
493
+			// For some reason the "until" parameter was not being used here,
494
+			// that's why the workaround of the 10000 year bug was needed at all
495
+			// let's stop it before the "until" parameter date
496
+			if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) {
497
+				return;
498
+			}
499
+
500
+			// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
501
+			// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
502
+			if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
503
+				$this->currentDate = null;
504
+
505
+				return;
506
+			}
507
+		}
508
+
509
+		$this->currentDate = $this->currentDate->setDate(
510
+			(int) $this->currentDate->format('Y'),
511
+			(int) $this->currentDate->format('n'),
512
+			(int) $occurrence
513
+		);
514
+	}
515
+
516
+	/**
517
+	 * Does the processing for advancing the iterator for yearly frequency.
518
+	 */
519
+	protected function nextYearly()
520
+	{
521
+		$currentMonth = $this->currentDate->format('n');
522
+		$currentYear = $this->currentDate->format('Y');
523
+		$currentDayOfMonth = $this->currentDate->format('j');
524
+
525
+		// No sub-rules, so we just advance by year
526
+		if (empty($this->byMonth)) {
527
+			// Unless it was a leap day!
528
+			if (2 == $currentMonth && 29 == $currentDayOfMonth) {
529
+				$counter = 0;
530
+				do {
531
+					++$counter;
532
+					// Here we increase the year count by the interval, until
533
+					// we hit a date that's also in a leap year.
534
+					//
535
+					// We could just find the next interval that's dividable by
536
+					// 4, but that would ignore the rule that there's no leap
537
+					// year every year that's dividable by a 100, but not by
538
+					// 400. (1800, 1900, 2100). So we just rely on the datetime
539
+					// functions instead.
540
+					$nextDate = clone $this->currentDate;
541
+					$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
542
+				} while (2 != $nextDate->format('n'));
543
+
544
+				$this->currentDate = $nextDate;
545
+
546
+				return;
547
+			}
548
+
549
+			if (null !== $this->byWeekNo) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
550
+				$dayOffsets = [];
551
+				if ($this->byDay) {
552
+					foreach ($this->byDay as $byDay) {
553
+						$dayOffsets[] = $this->dayMap[$byDay];
554
+					}
555
+				} else {   // default is Monday
556
+					$dayOffsets[] = 1;
557
+				}
558
+
559
+				$currentYear = $this->currentDate->format('Y');
560
+
561
+				while (true) {
562
+					$checkDates = [];
563
+
564
+					// loop through all WeekNo and Days to check all the combinations
565
+					foreach ($this->byWeekNo as $byWeekNo) {
566
+						foreach ($dayOffsets as $dayOffset) {
567
+							$date = clone $this->currentDate;
568
+							$date = $date->setISODate($currentYear, $byWeekNo, $dayOffset);
569
+
570
+							if ($date > $this->currentDate) {
571
+								$checkDates[] = $date;
572
+							}
573
+						}
574
+					}
575
+
576
+					if (count($checkDates) > 0) {
577
+						$this->currentDate = min($checkDates);
578
+
579
+						return;
580
+					}
581
+
582
+					// if there is no date found, check the next year
583
+					$currentYear += $this->interval;
584
+				}
585
+			}
586
+
587
+			if (null !== $this->byYearDay) { // byYearDay is an array with values from -366 to -1, or 1 to 366
588
+				$dayOffsets = [];
589
+				if ($this->byDay) {
590
+					foreach ($this->byDay as $byDay) {
591
+						$dayOffsets[] = $this->dayMap[$byDay];
592
+					}
593
+				} else {   // default is Monday-Sunday
594
+					$dayOffsets = [1, 2, 3, 4, 5, 6, 7];
595
+				}
596
+
597
+				$currentYear = $this->currentDate->format('Y');
598
+
599
+				while (true) {
600
+					$checkDates = [];
601
+
602
+					// loop through all YearDay and Days to check all the combinations
603
+					foreach ($this->byYearDay as $byYearDay) {
604
+						$date = clone $this->currentDate;
605
+						if ($byYearDay > 0) {
606
+							$date = $date->setDate($currentYear, 1, 1);
607
+							$date = $date->add(new \DateInterval('P'.($byYearDay - 1).'D'));
608
+						} else {
609
+							$date = $date->setDate($currentYear, 12, 31);
610
+							$date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
611
+						}
612
+
613
+						if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
614
+							$checkDates[] = $date;
615
+						}
616
+					}
617
+
618
+					if (count($checkDates) > 0) {
619
+						$this->currentDate = min($checkDates);
620
+
621
+						return;
622
+					}
623
+
624
+					// if there is no date found, check the next year
625
+					$currentYear += $this->interval;
626
+				}
627
+			}
628
+
629
+			// The easiest form
630
+			$this->currentDate = $this->currentDate->modify('+'.$this->interval.' years');
631
+
632
+			return;
633
+		}
634
+
635
+		$currentMonth = $this->currentDate->format('n');
636
+		$currentYear = $this->currentDate->format('Y');
637
+		$currentDayOfMonth = $this->currentDate->format('j');
638
+
639
+		$advancedToNewMonth = false;
640
+
641
+		// If we got a byDay or getMonthDay filter, we must first expand
642
+		// further.
643
+		if ($this->byDay || $this->byMonthDay) {
644
+			$occurrence = -1;
645
+			while (true) {
646
+				$occurrences = $this->getMonthlyOccurrences();
647
+
648
+				foreach ($occurrences as $occurrence) {
649
+					// The first occurrence that's higher than the current
650
+					// day of the month wins.
651
+					// If we advanced to the next month or year, the first
652
+					// occurrence is always correct.
653
+					if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
654
+						break 2;
655
+					}
656
+				}
657
+
658
+				// If we made it here, it means we need to advance to
659
+				// the next month or year.
660
+				$currentDayOfMonth = 1;
661
+				$advancedToNewMonth = true;
662
+				do {
663
+					++$currentMonth;
664
+					if ($currentMonth > 12) {
665
+						$currentYear += $this->interval;
666
+						$currentMonth = 1;
667
+					}
668
+				} while (!in_array($currentMonth, $this->byMonth));
669
+
670
+				$this->currentDate = $this->currentDate->setDate(
671
+					(int) $currentYear,
672
+					(int) $currentMonth,
673
+					(int) $currentDayOfMonth
674
+				);
675
+
676
+				// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
677
+				// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
678
+				if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
679
+					$this->currentDate = null;
680
+
681
+					return;
682
+				}
683
+			}
684
+
685
+			// If we made it here, it means we got a valid occurrence
686
+			$this->currentDate = $this->currentDate->setDate(
687
+				(int) $currentYear,
688
+				(int) $currentMonth,
689
+				(int) $occurrence
690
+			);
691
+
692
+			return;
693
+		} else {
694
+			// These are the 'byMonth' rules, if there are no byDay or
695
+			// byMonthDay sub-rules.
696
+			do {
697
+				++$currentMonth;
698
+				if ($currentMonth > 12) {
699
+					$currentYear += $this->interval;
700
+					$currentMonth = 1;
701
+				}
702
+			} while (!in_array($currentMonth, $this->byMonth));
703
+			$this->currentDate = $this->currentDate->setDate(
704
+				(int) $currentYear,
705
+				(int) $currentMonth,
706
+				(int) $currentDayOfMonth
707
+			);
708
+
709
+			return;
710
+		}
711
+	}
712
+
713
+	/* }}} */
714
+
715
+	/**
716
+	 * This method receives a string from an RRULE property, and populates this
717
+	 * class with all the values.
718
+	 *
719
+	 * @param string|array $rrule
720
+	 */
721
+	protected function parseRRule($rrule)
722
+	{
723
+		if (is_string($rrule)) {
724
+			$rrule = Property\ICalendar\Recur::stringToArray($rrule);
725
+		}
726
+
727
+		foreach ($rrule as $key => $value) {
728
+			$key = strtoupper($key);
729
+			switch ($key) {
730
+				case 'FREQ':
731
+					$value = strtolower($value);
732
+					if (!in_array(
733
+						$value,
734
+						['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
735
+					)) {
736
+						throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
737
+					}
738
+					$this->frequency = $value;
739
+					break;
740
+
741
+				case 'UNTIL':
742
+					$this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
743
+
744
+					// In some cases events are generated with an UNTIL=
745
+					// parameter before the actual start of the event.
746
+					//
747
+					// Not sure why this is happening. We assume that the
748
+					// intention was that the event only recurs once.
749
+					//
750
+					// So we are modifying the parameter so our code doesn't
751
+					// break.
752
+					if ($this->until < $this->startDate) {
753
+						$this->until = $this->startDate;
754
+					}
755
+					break;
756
+
757
+				case 'INTERVAL':
758
+				case 'COUNT':
759
+					$val = (int) $value;
760
+					if ($val < 1) {
761
+						throw new InvalidDataException(strtoupper($key).' in RRULE must be a positive integer!');
762
+					}
763
+					$key = strtolower($key);
764
+					$this->$key = $val;
765
+					break;
766
+
767
+				case 'BYSECOND':
768
+					$this->bySecond = (array) $value;
769
+					break;
770
+
771
+				case 'BYMINUTE':
772
+					$this->byMinute = (array) $value;
773
+					break;
774
+
775
+				case 'BYHOUR':
776
+					$this->byHour = (array) $value;
777
+					break;
778
+
779
+				case 'BYDAY':
780
+					$value = (array) $value;
781
+					foreach ($value as $part) {
782
+						if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
783
+							throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
784
+						}
785
+					}
786
+					$this->byDay = $value;
787
+					break;
788
+
789
+				case 'BYMONTHDAY':
790
+					$this->byMonthDay = (array) $value;
791
+					break;
792
+
793
+				case 'BYYEARDAY':
794
+					$this->byYearDay = (array) $value;
795
+					foreach ($this->byYearDay as $byYearDay) {
796
+						if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
797
+							throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
798
+						}
799
+					}
800
+					break;
801
+
802
+				case 'BYWEEKNO':
803
+					$this->byWeekNo = (array) $value;
804
+					foreach ($this->byWeekNo as $byWeekNo) {
805
+						if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
806
+							throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
807
+						}
808
+					}
809
+					break;
810
+
811
+				case 'BYMONTH':
812
+					$this->byMonth = (array) $value;
813
+					foreach ($this->byMonth as $byMonth) {
814
+						if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) {
815
+							throw new InvalidDataException('BYMONTH in RRULE must have value(s) between 1 and 12!');
816
+						}
817
+					}
818
+					break;
819
+
820
+				case 'BYSETPOS':
821
+					$this->bySetPos = (array) $value;
822
+					break;
823
+
824
+				case 'WKST':
825
+					$this->weekStart = strtoupper($value);
826
+					break;
827
+
828
+				default:
829
+					throw new InvalidDataException('Not supported: '.strtoupper($key));
830
+			}
831
+		}
832
+	}
833
+
834
+	/**
835
+	 * Mappings between the day number and english day name.
836
+	 *
837
+	 * @var array
838
+	 */
839
+	protected $dayNames = [
840
+		0 => 'Sunday',
841
+		1 => 'Monday',
842
+		2 => 'Tuesday',
843
+		3 => 'Wednesday',
844
+		4 => 'Thursday',
845
+		5 => 'Friday',
846
+		6 => 'Saturday',
847
+	];
848
+
849
+	/**
850
+	 * Returns all the occurrences for a monthly frequency with a 'byDay' or
851
+	 * 'byMonthDay' expansion for the current month.
852
+	 *
853
+	 * The returned list is an array of integers with the day of month (1-31).
854
+	 *
855
+	 * @return array
856
+	 */
857
+	protected function getMonthlyOccurrences()
858
+	{
859
+		$startDate = clone $this->currentDate;
860
+
861
+		$byDayResults = [];
862
+
863
+		// Our strategy is to simply go through the byDays, advance the date to
864
+		// that point and add it to the results.
865
+		if ($this->byDay) {
866
+			foreach ($this->byDay as $day) {
867
+				$dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
868
+
869
+				// Dayname will be something like 'wednesday'. Now we need to find
870
+				// all wednesdays in this month.
871
+				$dayHits = [];
872
+
873
+				// workaround for missing 'first day of the month' support in hhvm
874
+				$checkDate = new \DateTime($startDate->format('Y-m-1'));
875
+				// workaround modify always advancing the date even if the current day is a $dayName in hhvm
876
+				if ($checkDate->format('l') !== $dayName) {
877
+					$checkDate = $checkDate->modify($dayName);
878
+				}
879
+
880
+				do {
881
+					$dayHits[] = $checkDate->format('j');
882
+					$checkDate = $checkDate->modify('next '.$dayName);
883
+				} while ($checkDate->format('n') === $startDate->format('n'));
884
+
885
+				// So now we have 'all wednesdays' for month. It is however
886
+				// possible that the user only really wanted the 1st, 2nd or last
887
+				// wednesday.
888
+				if (strlen($day) > 2) {
889
+					$offset = (int) substr($day, 0, -2);
890
+
891
+					if ($offset > 0) {
892
+						// It is possible that the day does not exist, such as a
893
+						// 5th or 6th wednesday of the month.
894
+						if (isset($dayHits[$offset - 1])) {
895
+							$byDayResults[] = $dayHits[$offset - 1];
896
+						}
897
+					} else {
898
+						// if it was negative we count from the end of the array
899
+						// might not exist, fx. -5th tuesday
900
+						if (isset($dayHits[count($dayHits) + $offset])) {
901
+							$byDayResults[] = $dayHits[count($dayHits) + $offset];
902
+						}
903
+					}
904
+				} else {
905
+					// There was no counter (first, second, last wednesdays), so we
906
+					// just need to add the all to the list).
907
+					$byDayResults = array_merge($byDayResults, $dayHits);
908
+				}
909
+			}
910
+		}
911
+
912
+		$byMonthDayResults = [];
913
+		if ($this->byMonthDay) {
914
+			foreach ($this->byMonthDay as $monthDay) {
915
+				// Removing values that are out of range for this month
916
+				if ($monthDay > $startDate->format('t') ||
917
+					$monthDay < 0 - $startDate->format('t')) {
918
+					continue;
919
+				}
920
+				if ($monthDay > 0) {
921
+					$byMonthDayResults[] = $monthDay;
922
+				} else {
923
+					// Negative values
924
+					$byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
925
+				}
926
+			}
927
+		}
928
+
929
+		// If there was just byDay or just byMonthDay, they just specify our
930
+		// (almost) final list. If both were provided, then byDay limits the
931
+		// list.
932
+		if ($this->byMonthDay && $this->byDay) {
933
+			$result = array_intersect($byMonthDayResults, $byDayResults);
934
+		} elseif ($this->byMonthDay) {
935
+			$result = $byMonthDayResults;
936
+		} else {
937
+			$result = $byDayResults;
938
+		}
939
+		$result = array_unique($result);
940
+		sort($result, SORT_NUMERIC);
941
+
942
+		// The last thing that needs checking is the BYSETPOS. If it's set, it
943
+		// means only certain items in the set survive the filter.
944
+		if (!$this->bySetPos) {
945
+			return $result;
946
+		}
947
+
948
+		$filteredResult = [];
949
+		foreach ($this->bySetPos as $setPos) {
950
+			if ($setPos < 0) {
951
+				$setPos = count($result) + ($setPos + 1);
952
+			}
953
+			if (isset($result[$setPos - 1])) {
954
+				$filteredResult[] = $result[$setPos - 1];
955
+			}
956
+		}
957
+
958
+		sort($filteredResult, SORT_NUMERIC);
959
+
960
+		return $filteredResult;
961
+	}
962
+
963
+	/**
964
+	 * Simple mapping from iCalendar day names to day numbers.
965
+	 *
966
+	 * @var array
967
+	 */
968
+	protected $dayMap = [
969
+		'SU' => 0,
970
+		'MO' => 1,
971
+		'TU' => 2,
972
+		'WE' => 3,
973
+		'TH' => 4,
974
+		'FR' => 5,
975
+		'SA' => 6,
976
+	];
977
+
978
+	protected function getHours()
979
+	{
980
+		$recurrenceHours = [];
981
+		foreach ($this->byHour as $byHour) {
982
+			$recurrenceHours[] = $byHour;
983
+		}
984
+
985
+		return $recurrenceHours;
986
+	}
987
+
988
+	protected function getDays()
989
+	{
990
+		$recurrenceDays = [];
991
+		foreach ($this->byDay as $byDay) {
992
+			// The day may be preceded with a positive (+n) or
993
+			// negative (-n) integer. However, this does not make
994
+			// sense in 'weekly' so we ignore it here.
995
+			$recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
996
+		}
997
+
998
+		return $recurrenceDays;
999
+	}
1000
+
1001
+	protected function getMonths()
1002
+	{
1003
+		$recurrenceMonths = [];
1004
+		foreach ($this->byMonth as $byMonth) {
1005
+			$recurrenceMonths[] = $byMonth;
1006
+		}
1007
+
1008
+		return $recurrenceMonths;
1009
+	}
1010 1010
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Recur/RDateIterator.php 1 patch
Indentation   +151 added lines, -151 removed lines patch added patch discarded remove patch
@@ -21,155 +21,155 @@
 block discarded – undo
21 21
  */
22 22
 class RDateIterator implements Iterator
23 23
 {
24
-    /**
25
-     * Creates the Iterator.
26
-     *
27
-     * @param string|array $rrule
28
-     */
29
-    public function __construct($rrule, DateTimeInterface $start)
30
-    {
31
-        $this->startDate = $start;
32
-        $this->parseRDate($rrule);
33
-        $this->currentDate = clone $this->startDate;
34
-    }
35
-
36
-    /* Implementation of the Iterator interface {{{ */
37
-
38
-    #[\ReturnTypeWillChange]
39
-    public function current()
40
-    {
41
-        if (!$this->valid()) {
42
-            return;
43
-        }
44
-
45
-        return clone $this->currentDate;
46
-    }
47
-
48
-    /**
49
-     * Returns the current item number.
50
-     *
51
-     * @return int
52
-     */
53
-    #[\ReturnTypeWillChange]
54
-    public function key()
55
-    {
56
-        return $this->counter;
57
-    }
58
-
59
-    /**
60
-     * Returns whether the current item is a valid item for the recurrence
61
-     * iterator.
62
-     *
63
-     * @return bool
64
-     */
65
-    #[\ReturnTypeWillChange]
66
-    public function valid()
67
-    {
68
-        return $this->counter <= count($this->dates);
69
-    }
70
-
71
-    /**
72
-     * Resets the iterator.
73
-     *
74
-     * @return void
75
-     */
76
-    #[\ReturnTypeWillChange]
77
-    public function rewind()
78
-    {
79
-        $this->currentDate = clone $this->startDate;
80
-        $this->counter = 0;
81
-    }
82
-
83
-    /**
84
-     * Goes on to the next iteration.
85
-     *
86
-     * @return void
87
-     */
88
-    #[\ReturnTypeWillChange]
89
-    public function next()
90
-    {
91
-        ++$this->counter;
92
-        if (!$this->valid()) {
93
-            return;
94
-        }
95
-
96
-        $this->currentDate =
97
-            DateTimeParser::parse(
98
-                $this->dates[$this->counter - 1],
99
-                $this->startDate->getTimezone()
100
-            );
101
-    }
102
-
103
-    /* End of Iterator implementation }}} */
104
-
105
-    /**
106
-     * Returns true if this recurring event never ends.
107
-     *
108
-     * @return bool
109
-     */
110
-    public function isInfinite()
111
-    {
112
-        return false;
113
-    }
114
-
115
-    /**
116
-     * This method allows you to quickly go to the next occurrence after the
117
-     * specified date.
118
-     */
119
-    public function fastForward(DateTimeInterface $dt)
120
-    {
121
-        while ($this->valid() && $this->currentDate < $dt) {
122
-            $this->next();
123
-        }
124
-    }
125
-
126
-    /**
127
-     * The reference start date/time for the rrule.
128
-     *
129
-     * All calculations are based on this initial date.
130
-     *
131
-     * @var DateTimeInterface
132
-     */
133
-    protected $startDate;
134
-
135
-    /**
136
-     * The date of the current iteration. You can get this by calling
137
-     * ->current().
138
-     *
139
-     * @var DateTimeInterface
140
-     */
141
-    protected $currentDate;
142
-
143
-    /**
144
-     * The current item in the list.
145
-     *
146
-     * You can get this number with the key() method.
147
-     *
148
-     * @var int
149
-     */
150
-    protected $counter = 0;
151
-
152
-    /* }}} */
153
-
154
-    /**
155
-     * This method receives a string from an RRULE property, and populates this
156
-     * class with all the values.
157
-     *
158
-     * @param string|array $rrule
159
-     */
160
-    protected function parseRDate($rdate)
161
-    {
162
-        if (is_string($rdate)) {
163
-            $rdate = explode(',', $rdate);
164
-        }
165
-
166
-        $this->dates = $rdate;
167
-    }
168
-
169
-    /**
170
-     * Array with the RRULE dates.
171
-     *
172
-     * @var array
173
-     */
174
-    protected $dates = [];
24
+	/**
25
+	 * Creates the Iterator.
26
+	 *
27
+	 * @param string|array $rrule
28
+	 */
29
+	public function __construct($rrule, DateTimeInterface $start)
30
+	{
31
+		$this->startDate = $start;
32
+		$this->parseRDate($rrule);
33
+		$this->currentDate = clone $this->startDate;
34
+	}
35
+
36
+	/* Implementation of the Iterator interface {{{ */
37
+
38
+	#[\ReturnTypeWillChange]
39
+	public function current()
40
+	{
41
+		if (!$this->valid()) {
42
+			return;
43
+		}
44
+
45
+		return clone $this->currentDate;
46
+	}
47
+
48
+	/**
49
+	 * Returns the current item number.
50
+	 *
51
+	 * @return int
52
+	 */
53
+	#[\ReturnTypeWillChange]
54
+	public function key()
55
+	{
56
+		return $this->counter;
57
+	}
58
+
59
+	/**
60
+	 * Returns whether the current item is a valid item for the recurrence
61
+	 * iterator.
62
+	 *
63
+	 * @return bool
64
+	 */
65
+	#[\ReturnTypeWillChange]
66
+	public function valid()
67
+	{
68
+		return $this->counter <= count($this->dates);
69
+	}
70
+
71
+	/**
72
+	 * Resets the iterator.
73
+	 *
74
+	 * @return void
75
+	 */
76
+	#[\ReturnTypeWillChange]
77
+	public function rewind()
78
+	{
79
+		$this->currentDate = clone $this->startDate;
80
+		$this->counter = 0;
81
+	}
82
+
83
+	/**
84
+	 * Goes on to the next iteration.
85
+	 *
86
+	 * @return void
87
+	 */
88
+	#[\ReturnTypeWillChange]
89
+	public function next()
90
+	{
91
+		++$this->counter;
92
+		if (!$this->valid()) {
93
+			return;
94
+		}
95
+
96
+		$this->currentDate =
97
+			DateTimeParser::parse(
98
+				$this->dates[$this->counter - 1],
99
+				$this->startDate->getTimezone()
100
+			);
101
+	}
102
+
103
+	/* End of Iterator implementation }}} */
104
+
105
+	/**
106
+	 * Returns true if this recurring event never ends.
107
+	 *
108
+	 * @return bool
109
+	 */
110
+	public function isInfinite()
111
+	{
112
+		return false;
113
+	}
114
+
115
+	/**
116
+	 * This method allows you to quickly go to the next occurrence after the
117
+	 * specified date.
118
+	 */
119
+	public function fastForward(DateTimeInterface $dt)
120
+	{
121
+		while ($this->valid() && $this->currentDate < $dt) {
122
+			$this->next();
123
+		}
124
+	}
125
+
126
+	/**
127
+	 * The reference start date/time for the rrule.
128
+	 *
129
+	 * All calculations are based on this initial date.
130
+	 *
131
+	 * @var DateTimeInterface
132
+	 */
133
+	protected $startDate;
134
+
135
+	/**
136
+	 * The date of the current iteration. You can get this by calling
137
+	 * ->current().
138
+	 *
139
+	 * @var DateTimeInterface
140
+	 */
141
+	protected $currentDate;
142
+
143
+	/**
144
+	 * The current item in the list.
145
+	 *
146
+	 * You can get this number with the key() method.
147
+	 *
148
+	 * @var int
149
+	 */
150
+	protected $counter = 0;
151
+
152
+	/* }}} */
153
+
154
+	/**
155
+	 * This method receives a string from an RRULE property, and populates this
156
+	 * class with all the values.
157
+	 *
158
+	 * @param string|array $rrule
159
+	 */
160
+	protected function parseRDate($rdate)
161
+	{
162
+		if (is_string($rdate)) {
163
+			$rdate = explode(',', $rdate);
164
+		}
165
+
166
+		$this->dates = $rdate;
167
+	}
168
+
169
+	/**
170
+	 * Array with the RRULE dates.
171
+	 *
172
+	 * @var array
173
+	 */
174
+	protected $dates = [];
175 175
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Property/Unknown.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -14,28 +14,28 @@
 block discarded – undo
14 14
  */
15 15
 class Unknown extends Text
16 16
 {
17
-    /**
18
-     * Returns the value, in the format it should be encoded for json.
19
-     *
20
-     * This method must always return an array.
21
-     *
22
-     * @return array
23
-     */
24
-    public function getJsonValue()
25
-    {
26
-        return [$this->getRawMimeDirValue()];
27
-    }
17
+	/**
18
+	 * Returns the value, in the format it should be encoded for json.
19
+	 *
20
+	 * This method must always return an array.
21
+	 *
22
+	 * @return array
23
+	 */
24
+	public function getJsonValue()
25
+	{
26
+		return [$this->getRawMimeDirValue()];
27
+	}
28 28
 
29
-    /**
30
-     * Returns the type of value.
31
-     *
32
-     * This corresponds to the VALUE= parameter. Every property also has a
33
-     * 'default' valueType.
34
-     *
35
-     * @return string
36
-     */
37
-    public function getValueType()
38
-    {
39
-        return 'UNKNOWN';
40
-    }
29
+	/**
30
+	 * Returns the type of value.
31
+	 *
32
+	 * This corresponds to the VALUE= parameter. Every property also has a
33
+	 * 'default' valueType.
34
+	 *
35
+	 * @return string
36
+	 */
37
+	public function getValueType()
38
+	{
39
+		return 'UNKNOWN';
40
+	}
41 41
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Property/Binary.php 1 patch
Indentation   +80 added lines, -80 removed lines patch added patch discarded remove patch
@@ -20,90 +20,90 @@
 block discarded – undo
20 20
  */
21 21
 class Binary extends Property
22 22
 {
23
-    /**
24
-     * In case this is a multi-value property. This string will be used as a
25
-     * delimiter.
26
-     *
27
-     * @var string
28
-     */
29
-    public $delimiter = '';
23
+	/**
24
+	 * In case this is a multi-value property. This string will be used as a
25
+	 * delimiter.
26
+	 *
27
+	 * @var string
28
+	 */
29
+	public $delimiter = '';
30 30
 
31
-    /**
32
-     * Updates the current value.
33
-     *
34
-     * This may be either a single, or multiple strings in an array.
35
-     *
36
-     * @param string|array $value
37
-     */
38
-    public function setValue($value)
39
-    {
40
-        if (is_array($value)) {
41
-            if (1 === count($value)) {
42
-                $this->value = $value[0];
43
-            } else {
44
-                throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
45
-            }
46
-        } else {
47
-            $this->value = $value;
48
-        }
49
-    }
31
+	/**
32
+	 * Updates the current value.
33
+	 *
34
+	 * This may be either a single, or multiple strings in an array.
35
+	 *
36
+	 * @param string|array $value
37
+	 */
38
+	public function setValue($value)
39
+	{
40
+		if (is_array($value)) {
41
+			if (1 === count($value)) {
42
+				$this->value = $value[0];
43
+			} else {
44
+				throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
45
+			}
46
+		} else {
47
+			$this->value = $value;
48
+		}
49
+	}
50 50
 
51
-    /**
52
-     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
53
-     *
54
-     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
55
-     * not yet done, but parameters are not included.
56
-     *
57
-     * @param string $val
58
-     */
59
-    public function setRawMimeDirValue($val)
60
-    {
61
-        $this->value = base64_decode($val);
62
-    }
51
+	/**
52
+	 * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
53
+	 *
54
+	 * This has been 'unfolded', so only 1 line will be passed. Unescaping is
55
+	 * not yet done, but parameters are not included.
56
+	 *
57
+	 * @param string $val
58
+	 */
59
+	public function setRawMimeDirValue($val)
60
+	{
61
+		$this->value = base64_decode($val);
62
+	}
63 63
 
64
-    /**
65
-     * Returns a raw mime-dir representation of the value.
66
-     *
67
-     * @return string
68
-     */
69
-    public function getRawMimeDirValue()
70
-    {
71
-        return base64_encode($this->value);
72
-    }
64
+	/**
65
+	 * Returns a raw mime-dir representation of the value.
66
+	 *
67
+	 * @return string
68
+	 */
69
+	public function getRawMimeDirValue()
70
+	{
71
+		return base64_encode($this->value);
72
+	}
73 73
 
74
-    /**
75
-     * Returns the type of value.
76
-     *
77
-     * This corresponds to the VALUE= parameter. Every property also has a
78
-     * 'default' valueType.
79
-     *
80
-     * @return string
81
-     */
82
-    public function getValueType()
83
-    {
84
-        return 'BINARY';
85
-    }
74
+	/**
75
+	 * Returns the type of value.
76
+	 *
77
+	 * This corresponds to the VALUE= parameter. Every property also has a
78
+	 * 'default' valueType.
79
+	 *
80
+	 * @return string
81
+	 */
82
+	public function getValueType()
83
+	{
84
+		return 'BINARY';
85
+	}
86 86
 
87
-    /**
88
-     * Returns the value, in the format it should be encoded for json.
89
-     *
90
-     * This method must always return an array.
91
-     *
92
-     * @return array
93
-     */
94
-    public function getJsonValue()
95
-    {
96
-        return [base64_encode($this->getValue())];
97
-    }
87
+	/**
88
+	 * Returns the value, in the format it should be encoded for json.
89
+	 *
90
+	 * This method must always return an array.
91
+	 *
92
+	 * @return array
93
+	 */
94
+	public function getJsonValue()
95
+	{
96
+		return [base64_encode($this->getValue())];
97
+	}
98 98
 
99
-    /**
100
-     * Sets the json value, as it would appear in a jCard or jCal object.
101
-     *
102
-     * The value must always be an array.
103
-     */
104
-    public function setJsonValue(array $value)
105
-    {
106
-        $value = array_map('base64_decode', $value);
107
-        parent::setJsonValue($value);
108
-    }
99
+	/**
100
+	 * Sets the json value, as it would appear in a jCard or jCal object.
101
+	 *
102
+	 * The value must always be an array.
103
+	 */
104
+	public function setJsonValue(array $value)
105
+	{
106
+		$value = array_map('base64_decode', $value);
107
+		parent::setJsonValue($value);
108
+	}
109 109
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Property/IntegerValue.php 1 patch
Indentation   +53 added lines, -53 removed lines patch added patch discarded remove patch
@@ -16,61 +16,61 @@
 block discarded – undo
16 16
  */
17 17
 class IntegerValue extends Property
18 18
 {
19
-    /**
20
-     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
21
-     *
22
-     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
23
-     * not yet done, but parameters are not included.
24
-     *
25
-     * @param string $val
26
-     */
27
-    public function setRawMimeDirValue($val)
28
-    {
29
-        $this->setValue((int) $val);
30
-    }
19
+	/**
20
+	 * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
21
+	 *
22
+	 * This has been 'unfolded', so only 1 line will be passed. Unescaping is
23
+	 * not yet done, but parameters are not included.
24
+	 *
25
+	 * @param string $val
26
+	 */
27
+	public function setRawMimeDirValue($val)
28
+	{
29
+		$this->setValue((int) $val);
30
+	}
31 31
 
32
-    /**
33
-     * Returns a raw mime-dir representation of the value.
34
-     *
35
-     * @return string
36
-     */
37
-    public function getRawMimeDirValue()
38
-    {
39
-        return $this->value;
40
-    }
32
+	/**
33
+	 * Returns a raw mime-dir representation of the value.
34
+	 *
35
+	 * @return string
36
+	 */
37
+	public function getRawMimeDirValue()
38
+	{
39
+		return $this->value;
40
+	}
41 41
 
42
-    /**
43
-     * Returns the type of value.
44
-     *
45
-     * This corresponds to the VALUE= parameter. Every property also has a
46
-     * 'default' valueType.
47
-     *
48
-     * @return string
49
-     */
50
-    public function getValueType()
51
-    {
52
-        return 'INTEGER';
53
-    }
42
+	/**
43
+	 * Returns the type of value.
44
+	 *
45
+	 * This corresponds to the VALUE= parameter. Every property also has a
46
+	 * 'default' valueType.
47
+	 *
48
+	 * @return string
49
+	 */
50
+	public function getValueType()
51
+	{
52
+		return 'INTEGER';
53
+	}
54 54
 
55
-    /**
56
-     * Returns the value, in the format it should be encoded for json.
57
-     *
58
-     * This method must always return an array.
59
-     *
60
-     * @return array
61
-     */
62
-    public function getJsonValue()
63
-    {
64
-        return [(int) $this->getValue()];
65
-    }
55
+	/**
56
+	 * Returns the value, in the format it should be encoded for json.
57
+	 *
58
+	 * This method must always return an array.
59
+	 *
60
+	 * @return array
61
+	 */
62
+	public function getJsonValue()
63
+	{
64
+		return [(int) $this->getValue()];
65
+	}
66 66
 
67
-    /**
68
-     * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
69
-     * object.
70
-     */
71
-    public function setXmlValue(array $value)
72
-    {
73
-        $value = array_map('intval', $value);
74
-        parent::setXmlValue($value);
75
-    }
67
+	/**
68
+	 * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
69
+	 * object.
70
+	 */
71
+	public function setXmlValue(array $value)
72
+	{
73
+		$value = array_map('intval', $value);
74
+		parent::setXmlValue($value);
75
+	}
76 76
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Property/Text.php 2 patches
Indentation   +366 added lines, -366 removed lines patch added patch discarded remove patch
@@ -19,374 +19,374 @@
 block discarded – undo
19 19
  */
20 20
 class Text extends Property
21 21
 {
22
-    /**
23
-     * In case this is a multi-value property. This string will be used as a
24
-     * delimiter.
25
-     *
26
-     * @var string
27
-     */
28
-    public $delimiter = ',';
29
-
30
-    /**
31
-     * List of properties that are considered 'structured'.
32
-     *
33
-     * @var array
34
-     */
35
-    protected $structuredValues = [
36
-        // vCard
37
-        'N',
38
-        'ADR',
39
-        'ORG',
40
-        'GENDER',
41
-        'CLIENTPIDMAP',
42
-
43
-        // iCalendar
44
-        'REQUEST-STATUS',
45
-    ];
46
-
47
-    /**
48
-     * Some text components have a minimum number of components.
49
-     *
50
-     * N must for instance be represented as 5 components, separated by ;, even
51
-     * if the last few components are unused.
52
-     *
53
-     * @var array
54
-     */
55
-    protected $minimumPropertyValues = [
56
-        'N' => 5,
57
-        'ADR' => 7,
58
-    ];
59
-
60
-    /**
61
-     * Creates the property.
62
-     *
63
-     * You can specify the parameters either in key=>value syntax, in which case
64
-     * parameters will automatically be created, or you can just pass a list of
65
-     * Parameter objects.
66
-     *
67
-     * @param Component         $root       The root document
68
-     * @param string            $name
69
-     * @param string|array|null $value
70
-     * @param array             $parameters List of parameters
71
-     * @param string            $group      The vcard property group
72
-     */
73
-    public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null)
74
-    {
75
-        // There's two types of multi-valued text properties:
76
-        // 1. multivalue properties.
77
-        // 2. structured value properties
78
-        //
79
-        // The former is always separated by a comma, the latter by semi-colon.
80
-        if (in_array($name, $this->structuredValues)) {
81
-            $this->delimiter = ';';
82
-        }
83
-
84
-        parent::__construct($root, $name, $value, $parameters, $group);
85
-    }
86
-
87
-    /**
88
-     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
89
-     *
90
-     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
91
-     * not yet done, but parameters are not included.
92
-     *
93
-     * @param string $val
94
-     */
95
-    public function setRawMimeDirValue($val)
96
-    {
97
-        $this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
98
-    }
99
-
100
-    /**
101
-     * Sets the value as a quoted-printable encoded string.
102
-     *
103
-     * @param string $val
104
-     */
105
-    public function setQuotedPrintableValue($val)
106
-    {
107
-        $val = quoted_printable_decode($val);
108
-
109
-        // Quoted printable only appears in vCard 2.1, and the only character
110
-        // that may be escaped there is ;. So we are simply splitting on just
111
-        // that.
112
-        //
113
-        // We also don't have to unescape \\, so all we need to look for is a ;
114
-        // that's not preceded with a \.
115
-        $regex = '# (?<!\\\\) ; #x';
116
-        $matches = preg_split($regex, $val);
117
-        $this->setValue($matches);
118
-    }
119
-
120
-    /**
121
-     * Returns a raw mime-dir representation of the value.
122
-     *
123
-     * @return string
124
-     */
125
-    public function getRawMimeDirValue()
126
-    {
127
-        $val = $this->getParts();
128
-
129
-        if (isset($this->minimumPropertyValues[$this->name])) {
130
-            $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
131
-        }
132
-
133
-        foreach ($val as &$item) {
134
-            if (!is_array($item)) {
135
-                $item = [$item];
136
-            }
137
-
138
-            foreach ($item as &$subItem) {
139
-                if (!is_null($subItem)) {
140
-                    $subItem = strtr(
141
-                        $subItem,
142
-                        [
143
-                            '\\' => '\\\\',
144
-                            ';' => '\;',
145
-                            ',' => '\,',
146
-                            "\n" => '\n',
147
-                            "\r" => '',
148
-                        ]
149
-                    );
150
-                }
151
-            }
152
-            $item = implode(',', $item);
153
-        }
154
-
155
-        return implode($this->delimiter, $val);
156
-    }
157
-
158
-    /**
159
-     * Returns the value, in the format it should be encoded for json.
160
-     *
161
-     * This method must always return an array.
162
-     *
163
-     * @return array
164
-     */
165
-    public function getJsonValue()
166
-    {
167
-        // Structured text values should always be returned as a single
168
-        // array-item. Multi-value text should be returned as multiple items in
169
-        // the top-array.
170
-        if (in_array($this->name, $this->structuredValues)) {
171
-            return [$this->getParts()];
172
-        }
173
-
174
-        return $this->getParts();
175
-    }
176
-
177
-    /**
178
-     * Returns the type of value.
179
-     *
180
-     * This corresponds to the VALUE= parameter. Every property also has a
181
-     * 'default' valueType.
182
-     *
183
-     * @return string
184
-     */
185
-    public function getValueType()
186
-    {
187
-        return 'TEXT';
188
-    }
189
-
190
-    /**
191
-     * Turns the object back into a serialized blob.
192
-     *
193
-     * @return string
194
-     */
195
-    public function serialize()
196
-    {
197
-        // We need to kick in a special type of encoding, if it's a 2.1 vcard.
198
-        if (Document::VCARD21 !== $this->root->getDocumentType()) {
199
-            return parent::serialize();
200
-        }
201
-
202
-        $val = $this->getParts();
203
-
204
-        if (isset($this->minimumPropertyValues[$this->name])) {
205
-            $val = \array_pad($val, $this->minimumPropertyValues[$this->name], '');
206
-        }
207
-
208
-        // Imploding multiple parts into a single value, and splitting the
209
-        // values with ;.
210
-        if (\count($val) > 1) {
211
-            foreach ($val as $k => $v) {
212
-                $val[$k] = \str_replace(';', '\;', $v);
213
-            }
214
-            $val = \implode(';', $val);
215
-        } else {
216
-            $val = $val[0];
217
-        }
218
-
219
-        $str = $this->name;
220
-        if ($this->group) {
221
-            $str = $this->group.'.'.$this->name;
222
-        }
223
-        foreach ($this->parameters as $param) {
224
-            if ('QUOTED-PRINTABLE' === $param->getValue()) {
225
-                continue;
226
-            }
227
-            $str .= ';'.$param->serialize();
228
-        }
229
-
230
-        // If the resulting value contains a \n, we must encode it as
231
-        // quoted-printable.
232
-        if (false !== \strpos($val, "\n")) {
233
-            $str .= ';ENCODING=QUOTED-PRINTABLE:';
234
-            $lastLine = $str;
235
-            $out = null;
236
-
237
-            // The PHP built-in quoted-printable-encode does not correctly
238
-            // encode newlines for us. Specifically, the \r\n sequence must in
239
-            // vcards be encoded as =0D=OA and we must insert soft-newlines
240
-            // every 75 bytes.
241
-            for ($ii = 0; $ii < \strlen($val); ++$ii) {
242
-                $ord = \ord($val[$ii]);
243
-                // These characters are encoded as themselves.
244
-                if ($ord >= 32 && $ord <= 126) {
245
-                    $lastLine .= $val[$ii];
246
-                } else {
247
-                    $lastLine .= '='.\strtoupper(\bin2hex($val[$ii]));
248
-                }
249
-                if (\strlen($lastLine) >= 75) {
250
-                    // Soft line break
251
-                    $out .= $lastLine."=\r\n ";
252
-                    $lastLine = null;
253
-                }
254
-            }
255
-            if (!\is_null($lastLine)) {
256
-                $out .= $lastLine."\r\n";
257
-            }
258
-
259
-            return $out;
260
-        } else {
261
-            $str .= ':'.$val;
262
-
263
-            $str = \preg_replace(
264
-                '/(
22
+	/**
23
+	 * In case this is a multi-value property. This string will be used as a
24
+	 * delimiter.
25
+	 *
26
+	 * @var string
27
+	 */
28
+	public $delimiter = ',';
29
+
30
+	/**
31
+	 * List of properties that are considered 'structured'.
32
+	 *
33
+	 * @var array
34
+	 */
35
+	protected $structuredValues = [
36
+		// vCard
37
+		'N',
38
+		'ADR',
39
+		'ORG',
40
+		'GENDER',
41
+		'CLIENTPIDMAP',
42
+
43
+		// iCalendar
44
+		'REQUEST-STATUS',
45
+	];
46
+
47
+	/**
48
+	 * Some text components have a minimum number of components.
49
+	 *
50
+	 * N must for instance be represented as 5 components, separated by ;, even
51
+	 * if the last few components are unused.
52
+	 *
53
+	 * @var array
54
+	 */
55
+	protected $minimumPropertyValues = [
56
+		'N' => 5,
57
+		'ADR' => 7,
58
+	];
59
+
60
+	/**
61
+	 * Creates the property.
62
+	 *
63
+	 * You can specify the parameters either in key=>value syntax, in which case
64
+	 * parameters will automatically be created, or you can just pass a list of
65
+	 * Parameter objects.
66
+	 *
67
+	 * @param Component         $root       The root document
68
+	 * @param string            $name
69
+	 * @param string|array|null $value
70
+	 * @param array             $parameters List of parameters
71
+	 * @param string            $group      The vcard property group
72
+	 */
73
+	public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null)
74
+	{
75
+		// There's two types of multi-valued text properties:
76
+		// 1. multivalue properties.
77
+		// 2. structured value properties
78
+		//
79
+		// The former is always separated by a comma, the latter by semi-colon.
80
+		if (in_array($name, $this->structuredValues)) {
81
+			$this->delimiter = ';';
82
+		}
83
+
84
+		parent::__construct($root, $name, $value, $parameters, $group);
85
+	}
86
+
87
+	/**
88
+	 * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
89
+	 *
90
+	 * This has been 'unfolded', so only 1 line will be passed. Unescaping is
91
+	 * not yet done, but parameters are not included.
92
+	 *
93
+	 * @param string $val
94
+	 */
95
+	public function setRawMimeDirValue($val)
96
+	{
97
+		$this->setValue(MimeDir::unescapeValue($val, $this->delimiter));
98
+	}
99
+
100
+	/**
101
+	 * Sets the value as a quoted-printable encoded string.
102
+	 *
103
+	 * @param string $val
104
+	 */
105
+	public function setQuotedPrintableValue($val)
106
+	{
107
+		$val = quoted_printable_decode($val);
108
+
109
+		// Quoted printable only appears in vCard 2.1, and the only character
110
+		// that may be escaped there is ;. So we are simply splitting on just
111
+		// that.
112
+		//
113
+		// We also don't have to unescape \\, so all we need to look for is a ;
114
+		// that's not preceded with a \.
115
+		$regex = '# (?<!\\\\) ; #x';
116
+		$matches = preg_split($regex, $val);
117
+		$this->setValue($matches);
118
+	}
119
+
120
+	/**
121
+	 * Returns a raw mime-dir representation of the value.
122
+	 *
123
+	 * @return string
124
+	 */
125
+	public function getRawMimeDirValue()
126
+	{
127
+		$val = $this->getParts();
128
+
129
+		if (isset($this->minimumPropertyValues[$this->name])) {
130
+			$val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
131
+		}
132
+
133
+		foreach ($val as &$item) {
134
+			if (!is_array($item)) {
135
+				$item = [$item];
136
+			}
137
+
138
+			foreach ($item as &$subItem) {
139
+				if (!is_null($subItem)) {
140
+					$subItem = strtr(
141
+						$subItem,
142
+						[
143
+							'\\' => '\\\\',
144
+							';' => '\;',
145
+							',' => '\,',
146
+							"\n" => '\n',
147
+							"\r" => '',
148
+						]
149
+					);
150
+				}
151
+			}
152
+			$item = implode(',', $item);
153
+		}
154
+
155
+		return implode($this->delimiter, $val);
156
+	}
157
+
158
+	/**
159
+	 * Returns the value, in the format it should be encoded for json.
160
+	 *
161
+	 * This method must always return an array.
162
+	 *
163
+	 * @return array
164
+	 */
165
+	public function getJsonValue()
166
+	{
167
+		// Structured text values should always be returned as a single
168
+		// array-item. Multi-value text should be returned as multiple items in
169
+		// the top-array.
170
+		if (in_array($this->name, $this->structuredValues)) {
171
+			return [$this->getParts()];
172
+		}
173
+
174
+		return $this->getParts();
175
+	}
176
+
177
+	/**
178
+	 * Returns the type of value.
179
+	 *
180
+	 * This corresponds to the VALUE= parameter. Every property also has a
181
+	 * 'default' valueType.
182
+	 *
183
+	 * @return string
184
+	 */
185
+	public function getValueType()
186
+	{
187
+		return 'TEXT';
188
+	}
189
+
190
+	/**
191
+	 * Turns the object back into a serialized blob.
192
+	 *
193
+	 * @return string
194
+	 */
195
+	public function serialize()
196
+	{
197
+		// We need to kick in a special type of encoding, if it's a 2.1 vcard.
198
+		if (Document::VCARD21 !== $this->root->getDocumentType()) {
199
+			return parent::serialize();
200
+		}
201
+
202
+		$val = $this->getParts();
203
+
204
+		if (isset($this->minimumPropertyValues[$this->name])) {
205
+			$val = \array_pad($val, $this->minimumPropertyValues[$this->name], '');
206
+		}
207
+
208
+		// Imploding multiple parts into a single value, and splitting the
209
+		// values with ;.
210
+		if (\count($val) > 1) {
211
+			foreach ($val as $k => $v) {
212
+				$val[$k] = \str_replace(';', '\;', $v);
213
+			}
214
+			$val = \implode(';', $val);
215
+		} else {
216
+			$val = $val[0];
217
+		}
218
+
219
+		$str = $this->name;
220
+		if ($this->group) {
221
+			$str = $this->group.'.'.$this->name;
222
+		}
223
+		foreach ($this->parameters as $param) {
224
+			if ('QUOTED-PRINTABLE' === $param->getValue()) {
225
+				continue;
226
+			}
227
+			$str .= ';'.$param->serialize();
228
+		}
229
+
230
+		// If the resulting value contains a \n, we must encode it as
231
+		// quoted-printable.
232
+		if (false !== \strpos($val, "\n")) {
233
+			$str .= ';ENCODING=QUOTED-PRINTABLE:';
234
+			$lastLine = $str;
235
+			$out = null;
236
+
237
+			// The PHP built-in quoted-printable-encode does not correctly
238
+			// encode newlines for us. Specifically, the \r\n sequence must in
239
+			// vcards be encoded as =0D=OA and we must insert soft-newlines
240
+			// every 75 bytes.
241
+			for ($ii = 0; $ii < \strlen($val); ++$ii) {
242
+				$ord = \ord($val[$ii]);
243
+				// These characters are encoded as themselves.
244
+				if ($ord >= 32 && $ord <= 126) {
245
+					$lastLine .= $val[$ii];
246
+				} else {
247
+					$lastLine .= '='.\strtoupper(\bin2hex($val[$ii]));
248
+				}
249
+				if (\strlen($lastLine) >= 75) {
250
+					// Soft line break
251
+					$out .= $lastLine."=\r\n ";
252
+					$lastLine = null;
253
+				}
254
+			}
255
+			if (!\is_null($lastLine)) {
256
+				$out .= $lastLine."\r\n";
257
+			}
258
+
259
+			return $out;
260
+		} else {
261
+			$str .= ':'.$val;
262
+
263
+			$str = \preg_replace(
264
+				'/(
265 265
                     (?:^.)?         # 1 additional byte in first line because of missing single space (see next line)
266 266
                     .{1,74}         # max 75 bytes per line (1 byte is used for a single space added after every CRLF)
267 267
                     (?![\x80-\xbf]) # prevent splitting multibyte characters
268 268
                 )/x',
269
-                "$1\r\n ",
270
-                $str
271
-            );
272
-
273
-            // remove single space after last CRLF
274
-            return \substr($str, 0, -1);
275
-        }
276
-    }
277
-
278
-    /**
279
-     * This method serializes only the value of a property. This is used to
280
-     * create xCard or xCal documents.
281
-     *
282
-     * @param Xml\Writer $writer XML writer
283
-     */
284
-    protected function xmlSerializeValue(Xml\Writer $writer)
285
-    {
286
-        $values = $this->getParts();
287
-
288
-        $map = function ($items) use ($values, $writer) {
289
-            foreach ($items as $i => $item) {
290
-                $writer->writeElement(
291
-                    $item,
292
-                    !empty($values[$i]) ? $values[$i] : null
293
-                );
294
-            }
295
-        };
296
-
297
-        switch ($this->name) {
298
-            // Special-casing the REQUEST-STATUS property.
299
-            //
300
-            // See:
301
-            // http://tools.ietf.org/html/rfc6321#section-3.4.1.3
302
-            case 'REQUEST-STATUS':
303
-                $writer->writeElement('code', $values[0]);
304
-                $writer->writeElement('description', $values[1]);
305
-
306
-                if (isset($values[2])) {
307
-                    $writer->writeElement('data', $values[2]);
308
-                }
309
-                break;
310
-
311
-            case 'N':
312
-                $map([
313
-                    'surname',
314
-                    'given',
315
-                    'additional',
316
-                    'prefix',
317
-                    'suffix',
318
-                ]);
319
-                break;
320
-
321
-            case 'GENDER':
322
-                $map([
323
-                    'sex',
324
-                    'text',
325
-                ]);
326
-                break;
327
-
328
-            case 'ADR':
329
-                $map([
330
-                    'pobox',
331
-                    'ext',
332
-                    'street',
333
-                    'locality',
334
-                    'region',
335
-                    'code',
336
-                    'country',
337
-                ]);
338
-                break;
339
-
340
-            case 'CLIENTPIDMAP':
341
-                $map([
342
-                    'sourceid',
343
-                    'uri',
344
-                ]);
345
-                break;
346
-
347
-            default:
348
-                parent::xmlSerializeValue($writer);
349
-        }
350
-    }
351
-
352
-    /**
353
-     * Validates the node for correctness.
354
-     *
355
-     * The following options are supported:
356
-     *   - Node::REPAIR - If something is broken, and automatic repair may
357
-     *                    be attempted.
358
-     *
359
-     * An array is returned with warnings.
360
-     *
361
-     * Every item in the array has the following properties:
362
-     *    * level - (number between 1 and 3 with severity information)
363
-     *    * message - (human readable message)
364
-     *    * node - (reference to the offending node)
365
-     *
366
-     * @param int $options
367
-     *
368
-     * @return array
369
-     */
370
-    public function validate($options = 0)
371
-    {
372
-        $warnings = parent::validate($options);
373
-
374
-        if (isset($this->minimumPropertyValues[$this->name])) {
375
-            $minimum = $this->minimumPropertyValues[$this->name];
376
-            $parts = $this->getParts();
377
-            if (count($parts) < $minimum) {
378
-                $warnings[] = [
379
-                    'level' => $options & self::REPAIR ? 1 : 3,
380
-                    'message' => 'The '.$this->name.' property must have at least '.$minimum.' values. It only has '.count($parts),
381
-                    'node' => $this,
382
-                ];
383
-                if ($options & self::REPAIR) {
384
-                    $parts = array_pad($parts, $minimum, '');
385
-                    $this->setParts($parts);
386
-                }
387
-            }
388
-        }
389
-
390
-        return $warnings;
391
-    }
269
+				"$1\r\n ",
270
+				$str
271
+			);
272
+
273
+			// remove single space after last CRLF
274
+			return \substr($str, 0, -1);
275
+		}
276
+	}
277
+
278
+	/**
279
+	 * This method serializes only the value of a property. This is used to
280
+	 * create xCard or xCal documents.
281
+	 *
282
+	 * @param Xml\Writer $writer XML writer
283
+	 */
284
+	protected function xmlSerializeValue(Xml\Writer $writer)
285
+	{
286
+		$values = $this->getParts();
287
+
288
+		$map = function ($items) use ($values, $writer) {
289
+			foreach ($items as $i => $item) {
290
+				$writer->writeElement(
291
+					$item,
292
+					!empty($values[$i]) ? $values[$i] : null
293
+				);
294
+			}
295
+		};
296
+
297
+		switch ($this->name) {
298
+			// Special-casing the REQUEST-STATUS property.
299
+			//
300
+			// See:
301
+			// http://tools.ietf.org/html/rfc6321#section-3.4.1.3
302
+			case 'REQUEST-STATUS':
303
+				$writer->writeElement('code', $values[0]);
304
+				$writer->writeElement('description', $values[1]);
305
+
306
+				if (isset($values[2])) {
307
+					$writer->writeElement('data', $values[2]);
308
+				}
309
+				break;
310
+
311
+			case 'N':
312
+				$map([
313
+					'surname',
314
+					'given',
315
+					'additional',
316
+					'prefix',
317
+					'suffix',
318
+				]);
319
+				break;
320
+
321
+			case 'GENDER':
322
+				$map([
323
+					'sex',
324
+					'text',
325
+				]);
326
+				break;
327
+
328
+			case 'ADR':
329
+				$map([
330
+					'pobox',
331
+					'ext',
332
+					'street',
333
+					'locality',
334
+					'region',
335
+					'code',
336
+					'country',
337
+				]);
338
+				break;
339
+
340
+			case 'CLIENTPIDMAP':
341
+				$map([
342
+					'sourceid',
343
+					'uri',
344
+				]);
345
+				break;
346
+
347
+			default:
348
+				parent::xmlSerializeValue($writer);
349
+		}
350
+	}
351
+
352
+	/**
353
+	 * Validates the node for correctness.
354
+	 *
355
+	 * The following options are supported:
356
+	 *   - Node::REPAIR - If something is broken, and automatic repair may
357
+	 *                    be attempted.
358
+	 *
359
+	 * An array is returned with warnings.
360
+	 *
361
+	 * Every item in the array has the following properties:
362
+	 *    * level - (number between 1 and 3 with severity information)
363
+	 *    * message - (human readable message)
364
+	 *    * node - (reference to the offending node)
365
+	 *
366
+	 * @param int $options
367
+	 *
368
+	 * @return array
369
+	 */
370
+	public function validate($options = 0)
371
+	{
372
+		$warnings = parent::validate($options);
373
+
374
+		if (isset($this->minimumPropertyValues[$this->name])) {
375
+			$minimum = $this->minimumPropertyValues[$this->name];
376
+			$parts = $this->getParts();
377
+			if (count($parts) < $minimum) {
378
+				$warnings[] = [
379
+					'level' => $options & self::REPAIR ? 1 : 3,
380
+					'message' => 'The '.$this->name.' property must have at least '.$minimum.' values. It only has '.count($parts),
381
+					'node' => $this,
382
+				];
383
+				if ($options & self::REPAIR) {
384
+					$parts = array_pad($parts, $minimum, '');
385
+					$this->setParts($parts);
386
+				}
387
+			}
388
+		}
389
+
390
+		return $warnings;
391
+	}
392 392
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -285,7 +285,7 @@
 block discarded – undo
285 285
     {
286 286
         $values = $this->getParts();
287 287
 
288
-        $map = function ($items) use ($values, $writer) {
288
+        $map = function($items) use ($values, $writer) {
289 289
             foreach ($items as $i => $item) {
290 290
                 $writer->writeElement(
291 291
                     $item,
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Property/Uri.php 1 patch
Indentation   +91 added lines, -91 removed lines patch added patch discarded remove patch
@@ -16,101 +16,101 @@
 block discarded – undo
16 16
  */
17 17
 class Uri extends Text
18 18
 {
19
-    /**
20
-     * In case this is a multi-value property. This string will be used as a
21
-     * delimiter.
22
-     *
23
-     * @var string
24
-     */
25
-    public $delimiter = '';
19
+	/**
20
+	 * In case this is a multi-value property. This string will be used as a
21
+	 * delimiter.
22
+	 *
23
+	 * @var string
24
+	 */
25
+	public $delimiter = '';
26 26
 
27
-    /**
28
-     * Returns the type of value.
29
-     *
30
-     * This corresponds to the VALUE= parameter. Every property also has a
31
-     * 'default' valueType.
32
-     *
33
-     * @return string
34
-     */
35
-    public function getValueType()
36
-    {
37
-        return 'URI';
38
-    }
27
+	/**
28
+	 * Returns the type of value.
29
+	 *
30
+	 * This corresponds to the VALUE= parameter. Every property also has a
31
+	 * 'default' valueType.
32
+	 *
33
+	 * @return string
34
+	 */
35
+	public function getValueType()
36
+	{
37
+		return 'URI';
38
+	}
39 39
 
40
-    /**
41
-     * Returns an iterable list of children.
42
-     *
43
-     * @return array
44
-     */
45
-    public function parameters()
46
-    {
47
-        $parameters = parent::parameters();
48
-        if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
49
-            // If we are encoding a URI value, and this URI value has no
50
-            // VALUE=URI parameter, we add it anyway.
51
-            //
52
-            // This is not required by any spec, but both Apple iCal and Apple
53
-            // AddressBook (at least in version 10.8) will trip over this if
54
-            // this is not set, and so it improves compatibility.
55
-            //
56
-            // See Issue #227 and #235
57
-            $parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI');
58
-        }
40
+	/**
41
+	 * Returns an iterable list of children.
42
+	 *
43
+	 * @return array
44
+	 */
45
+	public function parameters()
46
+	{
47
+		$parameters = parent::parameters();
48
+		if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
49
+			// If we are encoding a URI value, and this URI value has no
50
+			// VALUE=URI parameter, we add it anyway.
51
+			//
52
+			// This is not required by any spec, but both Apple iCal and Apple
53
+			// AddressBook (at least in version 10.8) will trip over this if
54
+			// this is not set, and so it improves compatibility.
55
+			//
56
+			// See Issue #227 and #235
57
+			$parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI');
58
+		}
59 59
 
60
-        return $parameters;
61
-    }
60
+		return $parameters;
61
+	}
62 62
 
63
-    /**
64
-     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
65
-     *
66
-     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
67
-     * not yet done, but parameters are not included.
68
-     *
69
-     * @param string $val
70
-     */
71
-    public function setRawMimeDirValue($val)
72
-    {
73
-        // Normally we don't need to do any type of unescaping for these
74
-        // properties, however.. we've noticed that Google Contacts
75
-        // specifically escapes the colon (:) with a backslash. While I have
76
-        // no clue why they thought that was a good idea, I'm unescaping it
77
-        // anyway.
78
-        //
79
-        // Good thing backslashes are not allowed in urls. Makes it easy to
80
-        // assume that a backslash is always intended as an escape character.
81
-        if ('URL' === $this->name) {
82
-            $regex = '#  (?: (\\\\ (?: \\\\ | : ) ) ) #x';
83
-            $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
84
-            $newVal = '';
85
-            foreach ($matches as $match) {
86
-                switch ($match) {
87
-                    case '\:':
88
-                        $newVal .= ':';
89
-                        break;
90
-                    default:
91
-                        $newVal .= $match;
92
-                        break;
93
-                }
94
-            }
95
-            $this->value = $newVal;
96
-        } else {
97
-            $this->value = strtr($val, ['\,' => ',']);
98
-        }
99
-    }
63
+	/**
64
+	 * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
65
+	 *
66
+	 * This has been 'unfolded', so only 1 line will be passed. Unescaping is
67
+	 * not yet done, but parameters are not included.
68
+	 *
69
+	 * @param string $val
70
+	 */
71
+	public function setRawMimeDirValue($val)
72
+	{
73
+		// Normally we don't need to do any type of unescaping for these
74
+		// properties, however.. we've noticed that Google Contacts
75
+		// specifically escapes the colon (:) with a backslash. While I have
76
+		// no clue why they thought that was a good idea, I'm unescaping it
77
+		// anyway.
78
+		//
79
+		// Good thing backslashes are not allowed in urls. Makes it easy to
80
+		// assume that a backslash is always intended as an escape character.
81
+		if ('URL' === $this->name) {
82
+			$regex = '#  (?: (\\\\ (?: \\\\ | : ) ) ) #x';
83
+			$matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
84
+			$newVal = '';
85
+			foreach ($matches as $match) {
86
+				switch ($match) {
87
+					case '\:':
88
+						$newVal .= ':';
89
+						break;
90
+					default:
91
+						$newVal .= $match;
92
+						break;
93
+				}
94
+			}
95
+			$this->value = $newVal;
96
+		} else {
97
+			$this->value = strtr($val, ['\,' => ',']);
98
+		}
99
+	}
100 100
 
101
-    /**
102
-     * Returns a raw mime-dir representation of the value.
103
-     *
104
-     * @return string
105
-     */
106
-    public function getRawMimeDirValue()
107
-    {
108
-        if (is_array($this->value)) {
109
-            $value = $this->value[0];
110
-        } else {
111
-            $value = $this->value;
112
-        }
101
+	/**
102
+	 * Returns a raw mime-dir representation of the value.
103
+	 *
104
+	 * @return string
105
+	 */
106
+	public function getRawMimeDirValue()
107
+	{
108
+		if (is_array($this->value)) {
109
+			$value = $this->value[0];
110
+		} else {
111
+			$value = $this->value;
112
+		}
113 113
 
114
-        return strtr($value, [',' => '\,']);
115
-    }
114
+		return strtr($value, [',' => '\,']);
115
+	}
116 116
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Property/UtcOffset.php 2 patches
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -13,58 +13,58 @@
 block discarded – undo
13 13
  */
14 14
 class UtcOffset extends Text
15 15
 {
16
-    /**
17
-     * In case this is a multi-value property. This string will be used as a
18
-     * delimiter.
19
-     *
20
-     * @var string
21
-     */
22
-    public $delimiter = '';
16
+	/**
17
+	 * In case this is a multi-value property. This string will be used as a
18
+	 * delimiter.
19
+	 *
20
+	 * @var string
21
+	 */
22
+	public $delimiter = '';
23 23
 
24
-    /**
25
-     * Returns the type of value.
26
-     *
27
-     * This corresponds to the VALUE= parameter. Every property also has a
28
-     * 'default' valueType.
29
-     *
30
-     * @return string
31
-     */
32
-    public function getValueType()
33
-    {
34
-        return 'UTC-OFFSET';
35
-    }
24
+	/**
25
+	 * Returns the type of value.
26
+	 *
27
+	 * This corresponds to the VALUE= parameter. Every property also has a
28
+	 * 'default' valueType.
29
+	 *
30
+	 * @return string
31
+	 */
32
+	public function getValueType()
33
+	{
34
+		return 'UTC-OFFSET';
35
+	}
36 36
 
37
-    /**
38
-     * Sets the JSON value, as it would appear in a jCard or jCal object.
39
-     *
40
-     * The value must always be an array.
41
-     */
42
-    public function setJsonValue(array $value)
43
-    {
44
-        $value = array_map(
45
-            function ($value) {
46
-                return str_replace(':', '', $value);
47
-            },
48
-            $value
49
-        );
50
-        parent::setJsonValue($value);
51
-    }
37
+	/**
38
+	 * Sets the JSON value, as it would appear in a jCard or jCal object.
39
+	 *
40
+	 * The value must always be an array.
41
+	 */
42
+	public function setJsonValue(array $value)
43
+	{
44
+		$value = array_map(
45
+			function ($value) {
46
+				return str_replace(':', '', $value);
47
+			},
48
+			$value
49
+		);
50
+		parent::setJsonValue($value);
51
+	}
52 52
 
53
-    /**
54
-     * Returns the value, in the format it should be encoded for JSON.
55
-     *
56
-     * This method must always return an array.
57
-     *
58
-     * @return array
59
-     */
60
-    public function getJsonValue()
61
-    {
62
-        return array_map(
63
-            function ($value) {
64
-                return substr($value, 0, -2).':'.
65
-                       substr($value, -2);
66
-            },
67
-            parent::getJsonValue()
68
-        );
69
-    }
53
+	/**
54
+	 * Returns the value, in the format it should be encoded for JSON.
55
+	 *
56
+	 * This method must always return an array.
57
+	 *
58
+	 * @return array
59
+	 */
60
+	public function getJsonValue()
61
+	{
62
+		return array_map(
63
+			function ($value) {
64
+				return substr($value, 0, -2).':'.
65
+					   substr($value, -2);
66
+			},
67
+			parent::getJsonValue()
68
+		);
69
+	}
70 70
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -42,7 +42,7 @@  discard block
 block discarded – undo
42 42
     public function setJsonValue(array $value)
43 43
     {
44 44
         $value = array_map(
45
-            function ($value) {
45
+            function($value) {
46 46
                 return str_replace(':', '', $value);
47 47
             },
48 48
             $value
@@ -60,7 +60,7 @@  discard block
 block discarded – undo
60 60
     public function getJsonValue()
61 61
     {
62 62
         return array_map(
63
-            function ($value) {
63
+            function($value) {
64 64
                 return substr($value, 0, -2).':'.
65 65
                        substr($value, -2);
66 66
             },
Please login to merge, or discard this patch.