@@ -58,450 +58,450 @@ |
||
58 | 58 | */ |
59 | 59 | class EventIterator implements \Iterator { |
60 | 60 | |
61 | - /** |
|
62 | - * Reference timeZone for floating dates and times. |
|
63 | - * |
|
64 | - * @var DateTimeZone |
|
65 | - */ |
|
66 | - protected $timeZone; |
|
67 | - |
|
68 | - /** |
|
69 | - * True if we're iterating an all-day event. |
|
70 | - * |
|
71 | - * @var bool |
|
72 | - */ |
|
73 | - protected $allDay = false; |
|
74 | - |
|
75 | - /** |
|
76 | - * Creates the iterator. |
|
77 | - * |
|
78 | - * There's three ways to set up the iterator. |
|
79 | - * |
|
80 | - * 1. You can pass a VCALENDAR component and a UID. |
|
81 | - * 2. You can pass an array of VEVENTs (all UIDS should match). |
|
82 | - * 3. You can pass a single VEVENT component. |
|
83 | - * |
|
84 | - * Only the second method is recomended. The other 1 and 3 will be removed |
|
85 | - * at some point in the future. |
|
86 | - * |
|
87 | - * The $uid parameter is only required for the first method. |
|
88 | - * |
|
89 | - * @param Component|array $input |
|
90 | - * @param string|null $uid |
|
91 | - * @param DateTimeZone $timeZone Reference timezone for floating dates and |
|
92 | - * times. |
|
93 | - */ |
|
94 | - public function __construct($input, $uid = null, DateTimeZone $timeZone = null) { |
|
95 | - |
|
96 | - if (is_null($timeZone)) { |
|
97 | - $timeZone = new DateTimeZone('UTC'); |
|
98 | - } |
|
99 | - $this->timeZone = $timeZone; |
|
100 | - |
|
101 | - if (is_array($input)) { |
|
102 | - $events = $input; |
|
103 | - } elseif ($input instanceof VEvent) { |
|
104 | - // Single instance mode. |
|
105 | - $events = [$input]; |
|
106 | - } else { |
|
107 | - // Calendar + UID mode. |
|
108 | - $uid = (string)$uid; |
|
109 | - if (!$uid) { |
|
110 | - throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); |
|
111 | - } |
|
112 | - if (!isset($input->VEVENT)) { |
|
113 | - throw new InvalidArgumentException('No events found in this calendar'); |
|
114 | - } |
|
115 | - $events = $input->getByUID($uid); |
|
116 | - |
|
117 | - } |
|
118 | - |
|
119 | - foreach ($events as $vevent) { |
|
120 | - |
|
121 | - if (!isset($vevent->{'RECURRENCE-ID'})) { |
|
122 | - |
|
123 | - $this->masterEvent = $vevent; |
|
124 | - |
|
125 | - } else { |
|
126 | - |
|
127 | - $this->exceptions[ |
|
128 | - $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() |
|
129 | - ] = true; |
|
130 | - $this->overriddenEvents[] = $vevent; |
|
131 | - |
|
132 | - } |
|
133 | - |
|
134 | - } |
|
135 | - |
|
136 | - if (!$this->masterEvent) { |
|
137 | - // No base event was found. CalDAV does allow cases where only |
|
138 | - // overridden instances are stored. |
|
139 | - // |
|
140 | - // In this particular case, we're just going to grab the first |
|
141 | - // event and use that instead. This may not always give the |
|
142 | - // desired result. |
|
143 | - if (!count($this->overriddenEvents)) { |
|
144 | - throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); |
|
145 | - } |
|
146 | - $this->masterEvent = array_shift($this->overriddenEvents); |
|
147 | - } |
|
148 | - |
|
149 | - $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); |
|
150 | - $this->allDay = !$this->masterEvent->DTSTART->hasTime(); |
|
151 | - |
|
152 | - if (isset($this->masterEvent->EXDATE)) { |
|
153 | - |
|
154 | - foreach ($this->masterEvent->EXDATE as $exDate) { |
|
155 | - |
|
156 | - foreach ($exDate->getDateTimes($this->timeZone) as $dt) { |
|
157 | - $this->exceptions[$dt->getTimeStamp()] = true; |
|
158 | - } |
|
159 | - |
|
160 | - } |
|
161 | - |
|
162 | - } |
|
163 | - |
|
164 | - if (isset($this->masterEvent->DTEND)) { |
|
165 | - $this->eventDuration = |
|
166 | - $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - |
|
167 | - $this->startDate->getTimeStamp(); |
|
168 | - } elseif (isset($this->masterEvent->DURATION)) { |
|
169 | - $duration = $this->masterEvent->DURATION->getDateInterval(); |
|
170 | - $end = clone $this->startDate; |
|
171 | - $end = $end->add($duration); |
|
172 | - $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); |
|
173 | - } elseif ($this->allDay) { |
|
174 | - $this->eventDuration = 3600 * 24; |
|
175 | - } else { |
|
176 | - $this->eventDuration = 0; |
|
177 | - } |
|
178 | - |
|
179 | - if (isset($this->masterEvent->RDATE)) { |
|
180 | - $this->recurIterator = new RDateIterator( |
|
181 | - $this->masterEvent->RDATE->getParts(), |
|
182 | - $this->startDate |
|
183 | - ); |
|
184 | - } elseif (isset($this->masterEvent->RRULE)) { |
|
185 | - $this->recurIterator = new RRuleIterator( |
|
186 | - $this->masterEvent->RRULE->getParts(), |
|
187 | - $this->startDate |
|
188 | - ); |
|
189 | - } else { |
|
190 | - $this->recurIterator = new RRuleIterator( |
|
191 | - [ |
|
192 | - 'FREQ' => 'DAILY', |
|
193 | - 'COUNT' => 1, |
|
194 | - ], |
|
195 | - $this->startDate |
|
196 | - ); |
|
197 | - } |
|
198 | - |
|
199 | - $this->rewind(); |
|
200 | - if (!$this->valid()) { |
|
201 | - throw new NoInstancesException('This recurrence rule does not generate any valid instances'); |
|
202 | - } |
|
203 | - |
|
204 | - } |
|
205 | - |
|
206 | - /** |
|
207 | - * Returns the date for the current position of the iterator. |
|
208 | - * |
|
209 | - * @return DateTimeImmutable |
|
210 | - */ |
|
211 | - public function current() { |
|
212 | - |
|
213 | - if ($this->currentDate) { |
|
214 | - return clone $this->currentDate; |
|
215 | - } |
|
216 | - |
|
217 | - } |
|
218 | - |
|
219 | - /** |
|
220 | - * This method returns the start date for the current iteration of the |
|
221 | - * event. |
|
222 | - * |
|
223 | - * @return DateTimeImmutable |
|
224 | - */ |
|
225 | - public function getDtStart() { |
|
226 | - |
|
227 | - if ($this->currentDate) { |
|
228 | - return clone $this->currentDate; |
|
229 | - } |
|
230 | - |
|
231 | - } |
|
232 | - |
|
233 | - /** |
|
234 | - * This method returns the end date for the current iteration of the |
|
235 | - * event. |
|
236 | - * |
|
237 | - * @return DateTimeImmutable |
|
238 | - */ |
|
239 | - public function getDtEnd() { |
|
240 | - |
|
241 | - if (!$this->valid()) { |
|
242 | - return; |
|
243 | - } |
|
244 | - $end = clone $this->currentDate; |
|
245 | - return $end->modify('+' . $this->eventDuration . ' seconds'); |
|
246 | - |
|
247 | - } |
|
248 | - |
|
249 | - /** |
|
250 | - * Returns a VEVENT for the current iterations of the event. |
|
251 | - * |
|
252 | - * This VEVENT will have a recurrence id, and it's DTSTART and DTEND |
|
253 | - * altered. |
|
254 | - * |
|
255 | - * @return VEvent |
|
256 | - */ |
|
257 | - public function getEventObject() { |
|
258 | - |
|
259 | - if ($this->currentOverriddenEvent) { |
|
260 | - return $this->currentOverriddenEvent; |
|
261 | - } |
|
262 | - |
|
263 | - $event = clone $this->masterEvent; |
|
264 | - |
|
265 | - // Ignoring the following block, because PHPUnit's code coverage |
|
266 | - // ignores most of these lines, and this messes with our stats. |
|
267 | - // |
|
268 | - // @codeCoverageIgnoreStart |
|
269 | - unset( |
|
270 | - $event->RRULE, |
|
271 | - $event->EXDATE, |
|
272 | - $event->RDATE, |
|
273 | - $event->EXRULE, |
|
274 | - $event->{'RECURRENCE-ID'} |
|
275 | - ); |
|
276 | - // @codeCoverageIgnoreEnd |
|
277 | - |
|
278 | - $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); |
|
279 | - if (isset($event->DTEND)) { |
|
280 | - $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); |
|
281 | - } |
|
282 | - $recurid = clone $event->DTSTART; |
|
283 | - $recurid->name = 'RECURRENCE-ID'; |
|
284 | - $event->add($recurid); |
|
285 | - return $event; |
|
286 | - |
|
287 | - } |
|
288 | - |
|
289 | - /** |
|
290 | - * Returns the current position of the iterator. |
|
291 | - * |
|
292 | - * This is for us simply a 0-based index. |
|
293 | - * |
|
294 | - * @return int |
|
295 | - */ |
|
296 | - public function key() { |
|
297 | - |
|
298 | - // The counter is always 1 ahead. |
|
299 | - return $this->counter - 1; |
|
300 | - |
|
301 | - } |
|
302 | - |
|
303 | - /** |
|
304 | - * This is called after next, to see if the iterator is still at a valid |
|
305 | - * position, or if it's at the end. |
|
306 | - * |
|
307 | - * @return bool |
|
308 | - */ |
|
309 | - public function valid() { |
|
310 | - |
|
311 | - if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) { |
|
312 | - throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences); |
|
313 | - } |
|
314 | - return !!$this->currentDate; |
|
315 | - |
|
316 | - } |
|
317 | - |
|
318 | - /** |
|
319 | - * Sets the iterator back to the starting point. |
|
320 | - */ |
|
321 | - public function rewind() { |
|
322 | - |
|
323 | - $this->recurIterator->rewind(); |
|
324 | - // re-creating overridden event index. |
|
325 | - $index = []; |
|
326 | - foreach ($this->overriddenEvents as $key => $event) { |
|
327 | - $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); |
|
328 | - $index[$stamp] = $key; |
|
329 | - } |
|
330 | - krsort($index); |
|
331 | - $this->counter = 0; |
|
332 | - $this->overriddenEventsIndex = $index; |
|
333 | - $this->currentOverriddenEvent = null; |
|
334 | - |
|
335 | - $this->nextDate = null; |
|
336 | - $this->currentDate = clone $this->startDate; |
|
337 | - |
|
338 | - $this->next(); |
|
339 | - |
|
340 | - } |
|
341 | - |
|
342 | - /** |
|
343 | - * Advances the iterator with one step. |
|
344 | - * |
|
345 | - * @return void |
|
346 | - */ |
|
347 | - public function next() { |
|
348 | - |
|
349 | - $this->currentOverriddenEvent = null; |
|
350 | - $this->counter++; |
|
351 | - if ($this->nextDate) { |
|
352 | - // We had a stored value. |
|
353 | - $nextDate = $this->nextDate; |
|
354 | - $this->nextDate = null; |
|
355 | - } else { |
|
356 | - // We need to ask rruleparser for the next date. |
|
357 | - // We need to do this until we find a date that's not in the |
|
358 | - // exception list. |
|
359 | - do { |
|
360 | - if (!$this->recurIterator->valid()) { |
|
361 | - $nextDate = null; |
|
362 | - break; |
|
363 | - } |
|
364 | - $nextDate = $this->recurIterator->current(); |
|
365 | - $this->recurIterator->next(); |
|
366 | - } while (isset($this->exceptions[$nextDate->getTimeStamp()])); |
|
367 | - |
|
368 | - } |
|
369 | - |
|
370 | - |
|
371 | - // $nextDate now contains what rrule thinks is the next one, but an |
|
372 | - // overridden event may cut ahead. |
|
373 | - if ($this->overriddenEventsIndex) { |
|
374 | - |
|
375 | - $offset = end($this->overriddenEventsIndex); |
|
376 | - $timestamp = key($this->overriddenEventsIndex); |
|
377 | - if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { |
|
378 | - // Overridden event comes first. |
|
379 | - $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; |
|
380 | - |
|
381 | - // Putting the rrule next date aside. |
|
382 | - $this->nextDate = $nextDate; |
|
383 | - $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); |
|
384 | - |
|
385 | - // Ensuring that this item will only be used once. |
|
386 | - array_pop($this->overriddenEventsIndex); |
|
387 | - |
|
388 | - // Exit point! |
|
389 | - return; |
|
390 | - |
|
391 | - } |
|
392 | - |
|
393 | - } |
|
394 | - |
|
395 | - $this->currentDate = $nextDate; |
|
396 | - |
|
397 | - } |
|
398 | - |
|
399 | - /** |
|
400 | - * Quickly jump to a date in the future. |
|
401 | - * |
|
402 | - * @param DateTimeInterface $dateTime |
|
403 | - */ |
|
404 | - public function fastForward(DateTimeInterface $dateTime) { |
|
405 | - |
|
406 | - while ($this->valid() && $this->getDtEnd() < $dateTime) { |
|
407 | - $this->next(); |
|
408 | - } |
|
409 | - |
|
410 | - } |
|
411 | - |
|
412 | - /** |
|
413 | - * Returns true if this recurring event never ends. |
|
414 | - * |
|
415 | - * @return bool |
|
416 | - */ |
|
417 | - public function isInfinite() { |
|
418 | - |
|
419 | - return $this->recurIterator->isInfinite(); |
|
420 | - |
|
421 | - } |
|
422 | - |
|
423 | - /** |
|
424 | - * RRULE parser. |
|
425 | - * |
|
426 | - * @var RRuleIterator |
|
427 | - */ |
|
428 | - protected $recurIterator; |
|
429 | - |
|
430 | - /** |
|
431 | - * The duration, in seconds, of the master event. |
|
432 | - * |
|
433 | - * We use this to calculate the DTEND for subsequent events. |
|
434 | - */ |
|
435 | - protected $eventDuration; |
|
436 | - |
|
437 | - /** |
|
438 | - * A reference to the main (master) event. |
|
439 | - * |
|
440 | - * @var VEVENT |
|
441 | - */ |
|
442 | - protected $masterEvent; |
|
443 | - |
|
444 | - /** |
|
445 | - * List of overridden events. |
|
446 | - * |
|
447 | - * @var array |
|
448 | - */ |
|
449 | - protected $overriddenEvents = []; |
|
450 | - |
|
451 | - /** |
|
452 | - * Overridden event index. |
|
453 | - * |
|
454 | - * Key is timestamp, value is the index of the item in the $overriddenEvent |
|
455 | - * property. |
|
456 | - * |
|
457 | - * @var array |
|
458 | - */ |
|
459 | - protected $overriddenEventsIndex; |
|
460 | - |
|
461 | - /** |
|
462 | - * A list of recurrence-id's that are either part of EXDATE, or are |
|
463 | - * overridden. |
|
464 | - * |
|
465 | - * @var array |
|
466 | - */ |
|
467 | - protected $exceptions = []; |
|
468 | - |
|
469 | - /** |
|
470 | - * Internal event counter. |
|
471 | - * |
|
472 | - * @var int |
|
473 | - */ |
|
474 | - protected $counter; |
|
475 | - |
|
476 | - /** |
|
477 | - * The very start of the iteration process. |
|
478 | - * |
|
479 | - * @var DateTimeImmutable |
|
480 | - */ |
|
481 | - protected $startDate; |
|
482 | - |
|
483 | - /** |
|
484 | - * Where we are currently in the iteration process. |
|
485 | - * |
|
486 | - * @var DateTimeImmutable |
|
487 | - */ |
|
488 | - protected $currentDate; |
|
489 | - |
|
490 | - /** |
|
491 | - * The next date from the rrule parser. |
|
492 | - * |
|
493 | - * Sometimes we need to temporary store the next date, because an |
|
494 | - * overridden event came before. |
|
495 | - * |
|
496 | - * @var DateTimeImmutable |
|
497 | - */ |
|
498 | - protected $nextDate; |
|
499 | - |
|
500 | - /** |
|
501 | - * The event that overwrites the current iteration |
|
502 | - * |
|
503 | - * @var VEVENT |
|
504 | - */ |
|
505 | - protected $currentOverriddenEvent; |
|
61 | + /** |
|
62 | + * Reference timeZone for floating dates and times. |
|
63 | + * |
|
64 | + * @var DateTimeZone |
|
65 | + */ |
|
66 | + protected $timeZone; |
|
67 | + |
|
68 | + /** |
|
69 | + * True if we're iterating an all-day event. |
|
70 | + * |
|
71 | + * @var bool |
|
72 | + */ |
|
73 | + protected $allDay = false; |
|
74 | + |
|
75 | + /** |
|
76 | + * Creates the iterator. |
|
77 | + * |
|
78 | + * There's three ways to set up the iterator. |
|
79 | + * |
|
80 | + * 1. You can pass a VCALENDAR component and a UID. |
|
81 | + * 2. You can pass an array of VEVENTs (all UIDS should match). |
|
82 | + * 3. You can pass a single VEVENT component. |
|
83 | + * |
|
84 | + * Only the second method is recomended. The other 1 and 3 will be removed |
|
85 | + * at some point in the future. |
|
86 | + * |
|
87 | + * The $uid parameter is only required for the first method. |
|
88 | + * |
|
89 | + * @param Component|array $input |
|
90 | + * @param string|null $uid |
|
91 | + * @param DateTimeZone $timeZone Reference timezone for floating dates and |
|
92 | + * times. |
|
93 | + */ |
|
94 | + public function __construct($input, $uid = null, DateTimeZone $timeZone = null) { |
|
95 | + |
|
96 | + if (is_null($timeZone)) { |
|
97 | + $timeZone = new DateTimeZone('UTC'); |
|
98 | + } |
|
99 | + $this->timeZone = $timeZone; |
|
100 | + |
|
101 | + if (is_array($input)) { |
|
102 | + $events = $input; |
|
103 | + } elseif ($input instanceof VEvent) { |
|
104 | + // Single instance mode. |
|
105 | + $events = [$input]; |
|
106 | + } else { |
|
107 | + // Calendar + UID mode. |
|
108 | + $uid = (string)$uid; |
|
109 | + if (!$uid) { |
|
110 | + throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); |
|
111 | + } |
|
112 | + if (!isset($input->VEVENT)) { |
|
113 | + throw new InvalidArgumentException('No events found in this calendar'); |
|
114 | + } |
|
115 | + $events = $input->getByUID($uid); |
|
116 | + |
|
117 | + } |
|
118 | + |
|
119 | + foreach ($events as $vevent) { |
|
120 | + |
|
121 | + if (!isset($vevent->{'RECURRENCE-ID'})) { |
|
122 | + |
|
123 | + $this->masterEvent = $vevent; |
|
124 | + |
|
125 | + } else { |
|
126 | + |
|
127 | + $this->exceptions[ |
|
128 | + $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() |
|
129 | + ] = true; |
|
130 | + $this->overriddenEvents[] = $vevent; |
|
131 | + |
|
132 | + } |
|
133 | + |
|
134 | + } |
|
135 | + |
|
136 | + if (!$this->masterEvent) { |
|
137 | + // No base event was found. CalDAV does allow cases where only |
|
138 | + // overridden instances are stored. |
|
139 | + // |
|
140 | + // In this particular case, we're just going to grab the first |
|
141 | + // event and use that instead. This may not always give the |
|
142 | + // desired result. |
|
143 | + if (!count($this->overriddenEvents)) { |
|
144 | + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); |
|
145 | + } |
|
146 | + $this->masterEvent = array_shift($this->overriddenEvents); |
|
147 | + } |
|
148 | + |
|
149 | + $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); |
|
150 | + $this->allDay = !$this->masterEvent->DTSTART->hasTime(); |
|
151 | + |
|
152 | + if (isset($this->masterEvent->EXDATE)) { |
|
153 | + |
|
154 | + foreach ($this->masterEvent->EXDATE as $exDate) { |
|
155 | + |
|
156 | + foreach ($exDate->getDateTimes($this->timeZone) as $dt) { |
|
157 | + $this->exceptions[$dt->getTimeStamp()] = true; |
|
158 | + } |
|
159 | + |
|
160 | + } |
|
161 | + |
|
162 | + } |
|
163 | + |
|
164 | + if (isset($this->masterEvent->DTEND)) { |
|
165 | + $this->eventDuration = |
|
166 | + $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - |
|
167 | + $this->startDate->getTimeStamp(); |
|
168 | + } elseif (isset($this->masterEvent->DURATION)) { |
|
169 | + $duration = $this->masterEvent->DURATION->getDateInterval(); |
|
170 | + $end = clone $this->startDate; |
|
171 | + $end = $end->add($duration); |
|
172 | + $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); |
|
173 | + } elseif ($this->allDay) { |
|
174 | + $this->eventDuration = 3600 * 24; |
|
175 | + } else { |
|
176 | + $this->eventDuration = 0; |
|
177 | + } |
|
178 | + |
|
179 | + if (isset($this->masterEvent->RDATE)) { |
|
180 | + $this->recurIterator = new RDateIterator( |
|
181 | + $this->masterEvent->RDATE->getParts(), |
|
182 | + $this->startDate |
|
183 | + ); |
|
184 | + } elseif (isset($this->masterEvent->RRULE)) { |
|
185 | + $this->recurIterator = new RRuleIterator( |
|
186 | + $this->masterEvent->RRULE->getParts(), |
|
187 | + $this->startDate |
|
188 | + ); |
|
189 | + } else { |
|
190 | + $this->recurIterator = new RRuleIterator( |
|
191 | + [ |
|
192 | + 'FREQ' => 'DAILY', |
|
193 | + 'COUNT' => 1, |
|
194 | + ], |
|
195 | + $this->startDate |
|
196 | + ); |
|
197 | + } |
|
198 | + |
|
199 | + $this->rewind(); |
|
200 | + if (!$this->valid()) { |
|
201 | + throw new NoInstancesException('This recurrence rule does not generate any valid instances'); |
|
202 | + } |
|
203 | + |
|
204 | + } |
|
205 | + |
|
206 | + /** |
|
207 | + * Returns the date for the current position of the iterator. |
|
208 | + * |
|
209 | + * @return DateTimeImmutable |
|
210 | + */ |
|
211 | + public function current() { |
|
212 | + |
|
213 | + if ($this->currentDate) { |
|
214 | + return clone $this->currentDate; |
|
215 | + } |
|
216 | + |
|
217 | + } |
|
218 | + |
|
219 | + /** |
|
220 | + * This method returns the start date for the current iteration of the |
|
221 | + * event. |
|
222 | + * |
|
223 | + * @return DateTimeImmutable |
|
224 | + */ |
|
225 | + public function getDtStart() { |
|
226 | + |
|
227 | + if ($this->currentDate) { |
|
228 | + return clone $this->currentDate; |
|
229 | + } |
|
230 | + |
|
231 | + } |
|
232 | + |
|
233 | + /** |
|
234 | + * This method returns the end date for the current iteration of the |
|
235 | + * event. |
|
236 | + * |
|
237 | + * @return DateTimeImmutable |
|
238 | + */ |
|
239 | + public function getDtEnd() { |
|
240 | + |
|
241 | + if (!$this->valid()) { |
|
242 | + return; |
|
243 | + } |
|
244 | + $end = clone $this->currentDate; |
|
245 | + return $end->modify('+' . $this->eventDuration . ' seconds'); |
|
246 | + |
|
247 | + } |
|
248 | + |
|
249 | + /** |
|
250 | + * Returns a VEVENT for the current iterations of the event. |
|
251 | + * |
|
252 | + * This VEVENT will have a recurrence id, and it's DTSTART and DTEND |
|
253 | + * altered. |
|
254 | + * |
|
255 | + * @return VEvent |
|
256 | + */ |
|
257 | + public function getEventObject() { |
|
258 | + |
|
259 | + if ($this->currentOverriddenEvent) { |
|
260 | + return $this->currentOverriddenEvent; |
|
261 | + } |
|
262 | + |
|
263 | + $event = clone $this->masterEvent; |
|
264 | + |
|
265 | + // Ignoring the following block, because PHPUnit's code coverage |
|
266 | + // ignores most of these lines, and this messes with our stats. |
|
267 | + // |
|
268 | + // @codeCoverageIgnoreStart |
|
269 | + unset( |
|
270 | + $event->RRULE, |
|
271 | + $event->EXDATE, |
|
272 | + $event->RDATE, |
|
273 | + $event->EXRULE, |
|
274 | + $event->{'RECURRENCE-ID'} |
|
275 | + ); |
|
276 | + // @codeCoverageIgnoreEnd |
|
277 | + |
|
278 | + $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); |
|
279 | + if (isset($event->DTEND)) { |
|
280 | + $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); |
|
281 | + } |
|
282 | + $recurid = clone $event->DTSTART; |
|
283 | + $recurid->name = 'RECURRENCE-ID'; |
|
284 | + $event->add($recurid); |
|
285 | + return $event; |
|
286 | + |
|
287 | + } |
|
288 | + |
|
289 | + /** |
|
290 | + * Returns the current position of the iterator. |
|
291 | + * |
|
292 | + * This is for us simply a 0-based index. |
|
293 | + * |
|
294 | + * @return int |
|
295 | + */ |
|
296 | + public function key() { |
|
297 | + |
|
298 | + // The counter is always 1 ahead. |
|
299 | + return $this->counter - 1; |
|
300 | + |
|
301 | + } |
|
302 | + |
|
303 | + /** |
|
304 | + * This is called after next, to see if the iterator is still at a valid |
|
305 | + * position, or if it's at the end. |
|
306 | + * |
|
307 | + * @return bool |
|
308 | + */ |
|
309 | + public function valid() { |
|
310 | + |
|
311 | + if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) { |
|
312 | + throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences); |
|
313 | + } |
|
314 | + return !!$this->currentDate; |
|
315 | + |
|
316 | + } |
|
317 | + |
|
318 | + /** |
|
319 | + * Sets the iterator back to the starting point. |
|
320 | + */ |
|
321 | + public function rewind() { |
|
322 | + |
|
323 | + $this->recurIterator->rewind(); |
|
324 | + // re-creating overridden event index. |
|
325 | + $index = []; |
|
326 | + foreach ($this->overriddenEvents as $key => $event) { |
|
327 | + $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); |
|
328 | + $index[$stamp] = $key; |
|
329 | + } |
|
330 | + krsort($index); |
|
331 | + $this->counter = 0; |
|
332 | + $this->overriddenEventsIndex = $index; |
|
333 | + $this->currentOverriddenEvent = null; |
|
334 | + |
|
335 | + $this->nextDate = null; |
|
336 | + $this->currentDate = clone $this->startDate; |
|
337 | + |
|
338 | + $this->next(); |
|
339 | + |
|
340 | + } |
|
341 | + |
|
342 | + /** |
|
343 | + * Advances the iterator with one step. |
|
344 | + * |
|
345 | + * @return void |
|
346 | + */ |
|
347 | + public function next() { |
|
348 | + |
|
349 | + $this->currentOverriddenEvent = null; |
|
350 | + $this->counter++; |
|
351 | + if ($this->nextDate) { |
|
352 | + // We had a stored value. |
|
353 | + $nextDate = $this->nextDate; |
|
354 | + $this->nextDate = null; |
|
355 | + } else { |
|
356 | + // We need to ask rruleparser for the next date. |
|
357 | + // We need to do this until we find a date that's not in the |
|
358 | + // exception list. |
|
359 | + do { |
|
360 | + if (!$this->recurIterator->valid()) { |
|
361 | + $nextDate = null; |
|
362 | + break; |
|
363 | + } |
|
364 | + $nextDate = $this->recurIterator->current(); |
|
365 | + $this->recurIterator->next(); |
|
366 | + } while (isset($this->exceptions[$nextDate->getTimeStamp()])); |
|
367 | + |
|
368 | + } |
|
369 | + |
|
370 | + |
|
371 | + // $nextDate now contains what rrule thinks is the next one, but an |
|
372 | + // overridden event may cut ahead. |
|
373 | + if ($this->overriddenEventsIndex) { |
|
374 | + |
|
375 | + $offset = end($this->overriddenEventsIndex); |
|
376 | + $timestamp = key($this->overriddenEventsIndex); |
|
377 | + if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { |
|
378 | + // Overridden event comes first. |
|
379 | + $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; |
|
380 | + |
|
381 | + // Putting the rrule next date aside. |
|
382 | + $this->nextDate = $nextDate; |
|
383 | + $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); |
|
384 | + |
|
385 | + // Ensuring that this item will only be used once. |
|
386 | + array_pop($this->overriddenEventsIndex); |
|
387 | + |
|
388 | + // Exit point! |
|
389 | + return; |
|
390 | + |
|
391 | + } |
|
392 | + |
|
393 | + } |
|
394 | + |
|
395 | + $this->currentDate = $nextDate; |
|
396 | + |
|
397 | + } |
|
398 | + |
|
399 | + /** |
|
400 | + * Quickly jump to a date in the future. |
|
401 | + * |
|
402 | + * @param DateTimeInterface $dateTime |
|
403 | + */ |
|
404 | + public function fastForward(DateTimeInterface $dateTime) { |
|
405 | + |
|
406 | + while ($this->valid() && $this->getDtEnd() < $dateTime) { |
|
407 | + $this->next(); |
|
408 | + } |
|
409 | + |
|
410 | + } |
|
411 | + |
|
412 | + /** |
|
413 | + * Returns true if this recurring event never ends. |
|
414 | + * |
|
415 | + * @return bool |
|
416 | + */ |
|
417 | + public function isInfinite() { |
|
418 | + |
|
419 | + return $this->recurIterator->isInfinite(); |
|
420 | + |
|
421 | + } |
|
422 | + |
|
423 | + /** |
|
424 | + * RRULE parser. |
|
425 | + * |
|
426 | + * @var RRuleIterator |
|
427 | + */ |
|
428 | + protected $recurIterator; |
|
429 | + |
|
430 | + /** |
|
431 | + * The duration, in seconds, of the master event. |
|
432 | + * |
|
433 | + * We use this to calculate the DTEND for subsequent events. |
|
434 | + */ |
|
435 | + protected $eventDuration; |
|
436 | + |
|
437 | + /** |
|
438 | + * A reference to the main (master) event. |
|
439 | + * |
|
440 | + * @var VEVENT |
|
441 | + */ |
|
442 | + protected $masterEvent; |
|
443 | + |
|
444 | + /** |
|
445 | + * List of overridden events. |
|
446 | + * |
|
447 | + * @var array |
|
448 | + */ |
|
449 | + protected $overriddenEvents = []; |
|
450 | + |
|
451 | + /** |
|
452 | + * Overridden event index. |
|
453 | + * |
|
454 | + * Key is timestamp, value is the index of the item in the $overriddenEvent |
|
455 | + * property. |
|
456 | + * |
|
457 | + * @var array |
|
458 | + */ |
|
459 | + protected $overriddenEventsIndex; |
|
460 | + |
|
461 | + /** |
|
462 | + * A list of recurrence-id's that are either part of EXDATE, or are |
|
463 | + * overridden. |
|
464 | + * |
|
465 | + * @var array |
|
466 | + */ |
|
467 | + protected $exceptions = []; |
|
468 | + |
|
469 | + /** |
|
470 | + * Internal event counter. |
|
471 | + * |
|
472 | + * @var int |
|
473 | + */ |
|
474 | + protected $counter; |
|
475 | + |
|
476 | + /** |
|
477 | + * The very start of the iteration process. |
|
478 | + * |
|
479 | + * @var DateTimeImmutable |
|
480 | + */ |
|
481 | + protected $startDate; |
|
482 | + |
|
483 | + /** |
|
484 | + * Where we are currently in the iteration process. |
|
485 | + * |
|
486 | + * @var DateTimeImmutable |
|
487 | + */ |
|
488 | + protected $currentDate; |
|
489 | + |
|
490 | + /** |
|
491 | + * The next date from the rrule parser. |
|
492 | + * |
|
493 | + * Sometimes we need to temporary store the next date, because an |
|
494 | + * overridden event came before. |
|
495 | + * |
|
496 | + * @var DateTimeImmutable |
|
497 | + */ |
|
498 | + protected $nextDate; |
|
499 | + |
|
500 | + /** |
|
501 | + * The event that overwrites the current iteration |
|
502 | + * |
|
503 | + * @var VEVENT |
|
504 | + */ |
|
505 | + protected $currentOverriddenEvent; |
|
506 | 506 | |
507 | 507 | } |
@@ -21,162 +21,162 @@ |
||
21 | 21 | */ |
22 | 22 | class RDateIterator implements Iterator { |
23 | 23 | |
24 | - /** |
|
25 | - * Creates the Iterator. |
|
26 | - * |
|
27 | - * @param string|array $rrule |
|
28 | - * @param DateTimeInterface $start |
|
29 | - */ |
|
30 | - public function __construct($rrule, DateTimeInterface $start) { |
|
24 | + /** |
|
25 | + * Creates the Iterator. |
|
26 | + * |
|
27 | + * @param string|array $rrule |
|
28 | + * @param DateTimeInterface $start |
|
29 | + */ |
|
30 | + public function __construct($rrule, DateTimeInterface $start) { |
|
31 | 31 | |
32 | - $this->startDate = $start; |
|
33 | - $this->parseRDate($rrule); |
|
34 | - $this->currentDate = clone $this->startDate; |
|
35 | - |
|
36 | - } |
|
32 | + $this->startDate = $start; |
|
33 | + $this->parseRDate($rrule); |
|
34 | + $this->currentDate = clone $this->startDate; |
|
35 | + |
|
36 | + } |
|
37 | 37 | |
38 | - /* Implementation of the Iterator interface {{{ */ |
|
39 | - |
|
40 | - public function current() { |
|
41 | - |
|
42 | - if (!$this->valid()) return; |
|
43 | - return clone $this->currentDate; |
|
44 | - |
|
45 | - } |
|
46 | - |
|
47 | - /** |
|
48 | - * Returns the current item number. |
|
49 | - * |
|
50 | - * @return int |
|
51 | - */ |
|
52 | - public function key() { |
|
53 | - |
|
54 | - return $this->counter; |
|
55 | - |
|
56 | - } |
|
57 | - |
|
58 | - /** |
|
59 | - * Returns whether the current item is a valid item for the recurrence |
|
60 | - * iterator. |
|
61 | - * |
|
62 | - * @return bool |
|
63 | - */ |
|
64 | - public function valid() { |
|
65 | - |
|
66 | - return ($this->counter <= count($this->dates)); |
|
67 | - |
|
68 | - } |
|
69 | - |
|
70 | - /** |
|
71 | - * Resets the iterator. |
|
72 | - * |
|
73 | - * @return void |
|
74 | - */ |
|
75 | - public function rewind() { |
|
76 | - |
|
77 | - $this->currentDate = clone $this->startDate; |
|
78 | - $this->counter = 0; |
|
79 | - |
|
80 | - } |
|
81 | - |
|
82 | - /** |
|
83 | - * Goes on to the next iteration. |
|
84 | - * |
|
85 | - * @return void |
|
86 | - */ |
|
87 | - public function next() { |
|
88 | - |
|
89 | - $this->counter++; |
|
90 | - if (!$this->valid()) return; |
|
91 | - |
|
92 | - $this->currentDate = |
|
93 | - DateTimeParser::parse( |
|
94 | - $this->dates[$this->counter - 1], |
|
95 | - $this->startDate->getTimezone() |
|
96 | - ); |
|
97 | - |
|
98 | - } |
|
99 | - |
|
100 | - /* End of Iterator implementation }}} */ |
|
101 | - |
|
102 | - /** |
|
103 | - * Returns true if this recurring event never ends. |
|
104 | - * |
|
105 | - * @return bool |
|
106 | - */ |
|
107 | - public function isInfinite() { |
|
108 | - |
|
109 | - return false; |
|
110 | - |
|
111 | - } |
|
112 | - |
|
113 | - /** |
|
114 | - * This method allows you to quickly go to the next occurrence after the |
|
115 | - * specified date. |
|
116 | - * |
|
117 | - * @param DateTimeInterface $dt |
|
118 | - * |
|
119 | - * @return void |
|
120 | - */ |
|
121 | - public function fastForward(DateTimeInterface $dt) { |
|
122 | - |
|
123 | - while ($this->valid() && $this->currentDate < $dt) { |
|
124 | - $this->next(); |
|
125 | - } |
|
126 | - |
|
127 | - } |
|
128 | - |
|
129 | - /** |
|
130 | - * The reference start date/time for the rrule. |
|
131 | - * |
|
132 | - * All calculations are based on this initial date. |
|
133 | - * |
|
134 | - * @var DateTimeInterface |
|
135 | - */ |
|
136 | - protected $startDate; |
|
137 | - |
|
138 | - /** |
|
139 | - * The date of the current iteration. You can get this by calling |
|
140 | - * ->current(). |
|
141 | - * |
|
142 | - * @var DateTimeInterface |
|
143 | - */ |
|
144 | - protected $currentDate; |
|
145 | - |
|
146 | - /** |
|
147 | - * The current item in the list. |
|
148 | - * |
|
149 | - * You can get this number with the key() method. |
|
150 | - * |
|
151 | - * @var int |
|
152 | - */ |
|
153 | - protected $counter = 0; |
|
154 | - |
|
155 | - /* }}} */ |
|
156 | - |
|
157 | - /** |
|
158 | - * This method receives a string from an RRULE property, and populates this |
|
159 | - * class with all the values. |
|
160 | - * |
|
161 | - * @param string|array $rrule |
|
162 | - * |
|
163 | - * @return void |
|
164 | - */ |
|
165 | - protected function parseRDate($rdate) { |
|
166 | - |
|
167 | - if (is_string($rdate)) { |
|
168 | - $rdate = explode(',', $rdate); |
|
169 | - } |
|
170 | - |
|
171 | - $this->dates = $rdate; |
|
172 | - |
|
173 | - } |
|
174 | - |
|
175 | - /** |
|
176 | - * Array with the RRULE dates |
|
177 | - * |
|
178 | - * @var array |
|
179 | - */ |
|
180 | - protected $dates = []; |
|
38 | + /* Implementation of the Iterator interface {{{ */ |
|
39 | + |
|
40 | + public function current() { |
|
41 | + |
|
42 | + if (!$this->valid()) return; |
|
43 | + return clone $this->currentDate; |
|
44 | + |
|
45 | + } |
|
46 | + |
|
47 | + /** |
|
48 | + * Returns the current item number. |
|
49 | + * |
|
50 | + * @return int |
|
51 | + */ |
|
52 | + public function key() { |
|
53 | + |
|
54 | + return $this->counter; |
|
55 | + |
|
56 | + } |
|
57 | + |
|
58 | + /** |
|
59 | + * Returns whether the current item is a valid item for the recurrence |
|
60 | + * iterator. |
|
61 | + * |
|
62 | + * @return bool |
|
63 | + */ |
|
64 | + public function valid() { |
|
65 | + |
|
66 | + return ($this->counter <= count($this->dates)); |
|
67 | + |
|
68 | + } |
|
69 | + |
|
70 | + /** |
|
71 | + * Resets the iterator. |
|
72 | + * |
|
73 | + * @return void |
|
74 | + */ |
|
75 | + public function rewind() { |
|
76 | + |
|
77 | + $this->currentDate = clone $this->startDate; |
|
78 | + $this->counter = 0; |
|
79 | + |
|
80 | + } |
|
81 | + |
|
82 | + /** |
|
83 | + * Goes on to the next iteration. |
|
84 | + * |
|
85 | + * @return void |
|
86 | + */ |
|
87 | + public function next() { |
|
88 | + |
|
89 | + $this->counter++; |
|
90 | + if (!$this->valid()) return; |
|
91 | + |
|
92 | + $this->currentDate = |
|
93 | + DateTimeParser::parse( |
|
94 | + $this->dates[$this->counter - 1], |
|
95 | + $this->startDate->getTimezone() |
|
96 | + ); |
|
97 | + |
|
98 | + } |
|
99 | + |
|
100 | + /* End of Iterator implementation }}} */ |
|
101 | + |
|
102 | + /** |
|
103 | + * Returns true if this recurring event never ends. |
|
104 | + * |
|
105 | + * @return bool |
|
106 | + */ |
|
107 | + public function isInfinite() { |
|
108 | + |
|
109 | + return false; |
|
110 | + |
|
111 | + } |
|
112 | + |
|
113 | + /** |
|
114 | + * This method allows you to quickly go to the next occurrence after the |
|
115 | + * specified date. |
|
116 | + * |
|
117 | + * @param DateTimeInterface $dt |
|
118 | + * |
|
119 | + * @return void |
|
120 | + */ |
|
121 | + public function fastForward(DateTimeInterface $dt) { |
|
122 | + |
|
123 | + while ($this->valid() && $this->currentDate < $dt) { |
|
124 | + $this->next(); |
|
125 | + } |
|
126 | + |
|
127 | + } |
|
128 | + |
|
129 | + /** |
|
130 | + * The reference start date/time for the rrule. |
|
131 | + * |
|
132 | + * All calculations are based on this initial date. |
|
133 | + * |
|
134 | + * @var DateTimeInterface |
|
135 | + */ |
|
136 | + protected $startDate; |
|
137 | + |
|
138 | + /** |
|
139 | + * The date of the current iteration. You can get this by calling |
|
140 | + * ->current(). |
|
141 | + * |
|
142 | + * @var DateTimeInterface |
|
143 | + */ |
|
144 | + protected $currentDate; |
|
145 | + |
|
146 | + /** |
|
147 | + * The current item in the list. |
|
148 | + * |
|
149 | + * You can get this number with the key() method. |
|
150 | + * |
|
151 | + * @var int |
|
152 | + */ |
|
153 | + protected $counter = 0; |
|
154 | + |
|
155 | + /* }}} */ |
|
156 | + |
|
157 | + /** |
|
158 | + * This method receives a string from an RRULE property, and populates this |
|
159 | + * class with all the values. |
|
160 | + * |
|
161 | + * @param string|array $rrule |
|
162 | + * |
|
163 | + * @return void |
|
164 | + */ |
|
165 | + protected function parseRDate($rdate) { |
|
166 | + |
|
167 | + if (is_string($rdate)) { |
|
168 | + $rdate = explode(',', $rdate); |
|
169 | + } |
|
170 | + |
|
171 | + $this->dates = $rdate; |
|
172 | + |
|
173 | + } |
|
174 | + |
|
175 | + /** |
|
176 | + * Array with the RRULE dates |
|
177 | + * |
|
178 | + * @var array |
|
179 | + */ |
|
180 | + protected $dates = []; |
|
181 | 181 | |
182 | 182 | } |
@@ -25,580 +25,580 @@ |
||
25 | 25 | */ |
26 | 26 | class FreeBusyGenerator { |
27 | 27 | |
28 | - /** |
|
29 | - * Input objects. |
|
30 | - * |
|
31 | - * @var array |
|
32 | - */ |
|
33 | - protected $objects = []; |
|
34 | - |
|
35 | - /** |
|
36 | - * Start of range. |
|
37 | - * |
|
38 | - * @var DateTimeInterface|null |
|
39 | - */ |
|
40 | - protected $start; |
|
41 | - |
|
42 | - /** |
|
43 | - * End of range. |
|
44 | - * |
|
45 | - * @var DateTimeInterface|null |
|
46 | - */ |
|
47 | - protected $end; |
|
48 | - |
|
49 | - /** |
|
50 | - * VCALENDAR object. |
|
51 | - * |
|
52 | - * @var Document |
|
53 | - */ |
|
54 | - protected $baseObject; |
|
55 | - |
|
56 | - /** |
|
57 | - * Reference timezone. |
|
58 | - * |
|
59 | - * When we are calculating busy times, and we come across so-called |
|
60 | - * floating times (times without a timezone), we use the reference timezone |
|
61 | - * instead. |
|
62 | - * |
|
63 | - * This is also used for all-day events. |
|
64 | - * |
|
65 | - * This defaults to UTC. |
|
66 | - * |
|
67 | - * @var DateTimeZone |
|
68 | - */ |
|
69 | - protected $timeZone; |
|
70 | - |
|
71 | - /** |
|
72 | - * A VAVAILABILITY document. |
|
73 | - * |
|
74 | - * If this is set, it's information will be included when calculating |
|
75 | - * freebusy time. |
|
76 | - * |
|
77 | - * @var Document |
|
78 | - */ |
|
79 | - protected $vavailability; |
|
80 | - |
|
81 | - /** |
|
82 | - * Creates the generator. |
|
83 | - * |
|
84 | - * Check the setTimeRange and setObjects methods for details about the |
|
85 | - * arguments. |
|
86 | - * |
|
87 | - * @param DateTimeInterface $start |
|
88 | - * @param DateTimeInterface $end |
|
89 | - * @param mixed $objects |
|
90 | - * @param DateTimeZone $timeZone |
|
91 | - */ |
|
92 | - public function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) { |
|
93 | - |
|
94 | - $this->setTimeRange($start, $end); |
|
95 | - |
|
96 | - if ($objects) { |
|
97 | - $this->setObjects($objects); |
|
98 | - } |
|
99 | - if (is_null($timeZone)) { |
|
100 | - $timeZone = new DateTimeZone('UTC'); |
|
101 | - } |
|
102 | - $this->setTimeZone($timeZone); |
|
103 | - |
|
104 | - } |
|
105 | - |
|
106 | - /** |
|
107 | - * Sets the VCALENDAR object. |
|
108 | - * |
|
109 | - * If this is set, it will not be generated for you. You are responsible |
|
110 | - * for setting things like the METHOD, CALSCALE, VERSION, etc.. |
|
111 | - * |
|
112 | - * The VFREEBUSY object will be automatically added though. |
|
113 | - * |
|
114 | - * @param Document $vcalendar |
|
115 | - * @return void |
|
116 | - */ |
|
117 | - public function setBaseObject(Document $vcalendar) { |
|
118 | - |
|
119 | - $this->baseObject = $vcalendar; |
|
120 | - |
|
121 | - } |
|
122 | - |
|
123 | - /** |
|
124 | - * Sets a VAVAILABILITY document. |
|
125 | - * |
|
126 | - * @param Document $vcalendar |
|
127 | - * @return void |
|
128 | - */ |
|
129 | - public function setVAvailability(Document $vcalendar) { |
|
130 | - |
|
131 | - $this->vavailability = $vcalendar; |
|
132 | - |
|
133 | - } |
|
134 | - |
|
135 | - /** |
|
136 | - * Sets the input objects. |
|
137 | - * |
|
138 | - * You must either specify a valendar object as a string, or as the parse |
|
139 | - * Component. |
|
140 | - * It's also possible to specify multiple objects as an array. |
|
141 | - * |
|
142 | - * @param mixed $objects |
|
143 | - * |
|
144 | - * @return void |
|
145 | - */ |
|
146 | - public function setObjects($objects) { |
|
147 | - |
|
148 | - if (!is_array($objects)) { |
|
149 | - $objects = [$objects]; |
|
150 | - } |
|
151 | - |
|
152 | - $this->objects = []; |
|
153 | - foreach ($objects as $object) { |
|
154 | - |
|
155 | - if (is_string($object)) { |
|
156 | - $this->objects[] = Reader::read($object); |
|
157 | - } elseif ($object instanceof Component) { |
|
158 | - $this->objects[] = $object; |
|
159 | - } else { |
|
160 | - throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); |
|
161 | - } |
|
162 | - |
|
163 | - } |
|
164 | - |
|
165 | - } |
|
166 | - |
|
167 | - /** |
|
168 | - * Sets the time range. |
|
169 | - * |
|
170 | - * Any freebusy object falling outside of this time range will be ignored. |
|
171 | - * |
|
172 | - * @param DateTimeInterface $start |
|
173 | - * @param DateTimeInterface $end |
|
174 | - * |
|
175 | - * @return void |
|
176 | - */ |
|
177 | - public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) { |
|
178 | - |
|
179 | - if (!$start) { |
|
180 | - $start = new DateTimeImmutable(Settings::$minDate); |
|
181 | - } |
|
182 | - if (!$end) { |
|
183 | - $end = new DateTimeImmutable(Settings::$maxDate); |
|
184 | - } |
|
185 | - $this->start = $start; |
|
186 | - $this->end = $end; |
|
187 | - |
|
188 | - } |
|
189 | - |
|
190 | - /** |
|
191 | - * Sets the reference timezone for floating times. |
|
192 | - * |
|
193 | - * @param DateTimeZone $timeZone |
|
194 | - * |
|
195 | - * @return void |
|
196 | - */ |
|
197 | - public function setTimeZone(DateTimeZone $timeZone) { |
|
198 | - |
|
199 | - $this->timeZone = $timeZone; |
|
200 | - |
|
201 | - } |
|
202 | - |
|
203 | - /** |
|
204 | - * Parses the input data and returns a correct VFREEBUSY object, wrapped in |
|
205 | - * a VCALENDAR. |
|
206 | - * |
|
207 | - * @return Component |
|
208 | - */ |
|
209 | - public function getResult() { |
|
210 | - |
|
211 | - $fbData = new FreeBusyData( |
|
212 | - $this->start->getTimeStamp(), |
|
213 | - $this->end->getTimeStamp() |
|
214 | - ); |
|
215 | - if ($this->vavailability) { |
|
216 | - |
|
217 | - $this->calculateAvailability($fbData, $this->vavailability); |
|
218 | - |
|
219 | - } |
|
220 | - |
|
221 | - $this->calculateBusy($fbData, $this->objects); |
|
222 | - |
|
223 | - return $this->generateFreeBusyCalendar($fbData); |
|
224 | - |
|
225 | - |
|
226 | - } |
|
227 | - |
|
228 | - /** |
|
229 | - * This method takes a VAVAILABILITY component and figures out all the |
|
230 | - * available times. |
|
231 | - * |
|
232 | - * @param FreeBusyData $fbData |
|
233 | - * @param VCalendar $vavailability |
|
234 | - * @return void |
|
235 | - */ |
|
236 | - protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) { |
|
237 | - |
|
238 | - $vavailComps = iterator_to_array($vavailability->VAVAILABILITY); |
|
239 | - usort( |
|
240 | - $vavailComps, |
|
241 | - public function($a, $b) { |
|
242 | - |
|
243 | - // We need to order the components by priority. Priority 1 |
|
244 | - // comes first, up until priority 9. Priority 0 comes after |
|
245 | - // priority 9. No priority implies priority 0. |
|
246 | - // |
|
247 | - // Yes, I'm serious. |
|
248 | - $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0; |
|
249 | - $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0; |
|
250 | - |
|
251 | - if ($priorityA === 0) $priorityA = 10; |
|
252 | - if ($priorityB === 0) $priorityB = 10; |
|
253 | - |
|
254 | - return $priorityA - $priorityB; |
|
255 | - |
|
256 | - } |
|
257 | - ); |
|
258 | - |
|
259 | - // Now we go over all the VAVAILABILITY components and figure if |
|
260 | - // there's any we don't need to consider. |
|
261 | - // |
|
262 | - // This is can be because of one of two reasons: either the |
|
263 | - // VAVAILABILITY component falls outside the time we are interested in, |
|
264 | - // or a different VAVAILABILITY component with a higher priority has |
|
265 | - // already completely covered the time-range. |
|
266 | - $old = $vavailComps; |
|
267 | - $new = []; |
|
268 | - |
|
269 | - foreach ($old as $vavail) { |
|
270 | - |
|
271 | - list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); |
|
272 | - |
|
273 | - // We don't care about datetimes that are earlier or later than the |
|
274 | - // start and end of the freebusy report, so this gets normalized |
|
275 | - // first. |
|
276 | - if (is_null($compStart) || $compStart < $this->start) { |
|
277 | - $compStart = $this->start; |
|
278 | - } |
|
279 | - if (is_null($compEnd) || $compEnd > $this->end) { |
|
280 | - $compEnd = $this->end; |
|
281 | - } |
|
282 | - |
|
283 | - // If the item fell out of the timerange, we can just skip it. |
|
284 | - if ($compStart > $this->end || $compEnd < $this->start) { |
|
285 | - continue; |
|
286 | - } |
|
287 | - |
|
288 | - // Going through our existing list of components to see if there's |
|
289 | - // a higher priority component that already fully covers this one. |
|
290 | - foreach ($new as $higherVavail) { |
|
291 | - |
|
292 | - list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); |
|
293 | - if ( |
|
294 | - (is_null($higherStart) || $higherStart < $compStart) && |
|
295 | - (is_null($higherEnd) || $higherEnd > $compEnd) |
|
296 | - ) { |
|
297 | - |
|
298 | - // Component is fully covered by a higher priority |
|
299 | - // component. We can skip this component. |
|
300 | - continue 2; |
|
301 | - |
|
302 | - } |
|
303 | - |
|
304 | - } |
|
305 | - |
|
306 | - // We're keeping it! |
|
307 | - $new[] = $vavail; |
|
28 | + /** |
|
29 | + * Input objects. |
|
30 | + * |
|
31 | + * @var array |
|
32 | + */ |
|
33 | + protected $objects = []; |
|
34 | + |
|
35 | + /** |
|
36 | + * Start of range. |
|
37 | + * |
|
38 | + * @var DateTimeInterface|null |
|
39 | + */ |
|
40 | + protected $start; |
|
41 | + |
|
42 | + /** |
|
43 | + * End of range. |
|
44 | + * |
|
45 | + * @var DateTimeInterface|null |
|
46 | + */ |
|
47 | + protected $end; |
|
48 | + |
|
49 | + /** |
|
50 | + * VCALENDAR object. |
|
51 | + * |
|
52 | + * @var Document |
|
53 | + */ |
|
54 | + protected $baseObject; |
|
55 | + |
|
56 | + /** |
|
57 | + * Reference timezone. |
|
58 | + * |
|
59 | + * When we are calculating busy times, and we come across so-called |
|
60 | + * floating times (times without a timezone), we use the reference timezone |
|
61 | + * instead. |
|
62 | + * |
|
63 | + * This is also used for all-day events. |
|
64 | + * |
|
65 | + * This defaults to UTC. |
|
66 | + * |
|
67 | + * @var DateTimeZone |
|
68 | + */ |
|
69 | + protected $timeZone; |
|
70 | + |
|
71 | + /** |
|
72 | + * A VAVAILABILITY document. |
|
73 | + * |
|
74 | + * If this is set, it's information will be included when calculating |
|
75 | + * freebusy time. |
|
76 | + * |
|
77 | + * @var Document |
|
78 | + */ |
|
79 | + protected $vavailability; |
|
80 | + |
|
81 | + /** |
|
82 | + * Creates the generator. |
|
83 | + * |
|
84 | + * Check the setTimeRange and setObjects methods for details about the |
|
85 | + * arguments. |
|
86 | + * |
|
87 | + * @param DateTimeInterface $start |
|
88 | + * @param DateTimeInterface $end |
|
89 | + * @param mixed $objects |
|
90 | + * @param DateTimeZone $timeZone |
|
91 | + */ |
|
92 | + public function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) { |
|
93 | + |
|
94 | + $this->setTimeRange($start, $end); |
|
95 | + |
|
96 | + if ($objects) { |
|
97 | + $this->setObjects($objects); |
|
98 | + } |
|
99 | + if (is_null($timeZone)) { |
|
100 | + $timeZone = new DateTimeZone('UTC'); |
|
101 | + } |
|
102 | + $this->setTimeZone($timeZone); |
|
103 | + |
|
104 | + } |
|
105 | + |
|
106 | + /** |
|
107 | + * Sets the VCALENDAR object. |
|
108 | + * |
|
109 | + * If this is set, it will not be generated for you. You are responsible |
|
110 | + * for setting things like the METHOD, CALSCALE, VERSION, etc.. |
|
111 | + * |
|
112 | + * The VFREEBUSY object will be automatically added though. |
|
113 | + * |
|
114 | + * @param Document $vcalendar |
|
115 | + * @return void |
|
116 | + */ |
|
117 | + public function setBaseObject(Document $vcalendar) { |
|
118 | + |
|
119 | + $this->baseObject = $vcalendar; |
|
120 | + |
|
121 | + } |
|
122 | + |
|
123 | + /** |
|
124 | + * Sets a VAVAILABILITY document. |
|
125 | + * |
|
126 | + * @param Document $vcalendar |
|
127 | + * @return void |
|
128 | + */ |
|
129 | + public function setVAvailability(Document $vcalendar) { |
|
130 | + |
|
131 | + $this->vavailability = $vcalendar; |
|
132 | + |
|
133 | + } |
|
134 | + |
|
135 | + /** |
|
136 | + * Sets the input objects. |
|
137 | + * |
|
138 | + * You must either specify a valendar object as a string, or as the parse |
|
139 | + * Component. |
|
140 | + * It's also possible to specify multiple objects as an array. |
|
141 | + * |
|
142 | + * @param mixed $objects |
|
143 | + * |
|
144 | + * @return void |
|
145 | + */ |
|
146 | + public function setObjects($objects) { |
|
147 | + |
|
148 | + if (!is_array($objects)) { |
|
149 | + $objects = [$objects]; |
|
150 | + } |
|
151 | + |
|
152 | + $this->objects = []; |
|
153 | + foreach ($objects as $object) { |
|
154 | + |
|
155 | + if (is_string($object)) { |
|
156 | + $this->objects[] = Reader::read($object); |
|
157 | + } elseif ($object instanceof Component) { |
|
158 | + $this->objects[] = $object; |
|
159 | + } else { |
|
160 | + throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); |
|
161 | + } |
|
162 | + |
|
163 | + } |
|
164 | + |
|
165 | + } |
|
166 | + |
|
167 | + /** |
|
168 | + * Sets the time range. |
|
169 | + * |
|
170 | + * Any freebusy object falling outside of this time range will be ignored. |
|
171 | + * |
|
172 | + * @param DateTimeInterface $start |
|
173 | + * @param DateTimeInterface $end |
|
174 | + * |
|
175 | + * @return void |
|
176 | + */ |
|
177 | + public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) { |
|
178 | + |
|
179 | + if (!$start) { |
|
180 | + $start = new DateTimeImmutable(Settings::$minDate); |
|
181 | + } |
|
182 | + if (!$end) { |
|
183 | + $end = new DateTimeImmutable(Settings::$maxDate); |
|
184 | + } |
|
185 | + $this->start = $start; |
|
186 | + $this->end = $end; |
|
187 | + |
|
188 | + } |
|
189 | + |
|
190 | + /** |
|
191 | + * Sets the reference timezone for floating times. |
|
192 | + * |
|
193 | + * @param DateTimeZone $timeZone |
|
194 | + * |
|
195 | + * @return void |
|
196 | + */ |
|
197 | + public function setTimeZone(DateTimeZone $timeZone) { |
|
198 | + |
|
199 | + $this->timeZone = $timeZone; |
|
200 | + |
|
201 | + } |
|
202 | + |
|
203 | + /** |
|
204 | + * Parses the input data and returns a correct VFREEBUSY object, wrapped in |
|
205 | + * a VCALENDAR. |
|
206 | + * |
|
207 | + * @return Component |
|
208 | + */ |
|
209 | + public function getResult() { |
|
210 | + |
|
211 | + $fbData = new FreeBusyData( |
|
212 | + $this->start->getTimeStamp(), |
|
213 | + $this->end->getTimeStamp() |
|
214 | + ); |
|
215 | + if ($this->vavailability) { |
|
216 | + |
|
217 | + $this->calculateAvailability($fbData, $this->vavailability); |
|
218 | + |
|
219 | + } |
|
220 | + |
|
221 | + $this->calculateBusy($fbData, $this->objects); |
|
222 | + |
|
223 | + return $this->generateFreeBusyCalendar($fbData); |
|
224 | + |
|
225 | + |
|
226 | + } |
|
227 | + |
|
228 | + /** |
|
229 | + * This method takes a VAVAILABILITY component and figures out all the |
|
230 | + * available times. |
|
231 | + * |
|
232 | + * @param FreeBusyData $fbData |
|
233 | + * @param VCalendar $vavailability |
|
234 | + * @return void |
|
235 | + */ |
|
236 | + protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) { |
|
237 | + |
|
238 | + $vavailComps = iterator_to_array($vavailability->VAVAILABILITY); |
|
239 | + usort( |
|
240 | + $vavailComps, |
|
241 | + public function($a, $b) { |
|
242 | + |
|
243 | + // We need to order the components by priority. Priority 1 |
|
244 | + // comes first, up until priority 9. Priority 0 comes after |
|
245 | + // priority 9. No priority implies priority 0. |
|
246 | + // |
|
247 | + // Yes, I'm serious. |
|
248 | + $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0; |
|
249 | + $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0; |
|
250 | + |
|
251 | + if ($priorityA === 0) $priorityA = 10; |
|
252 | + if ($priorityB === 0) $priorityB = 10; |
|
253 | + |
|
254 | + return $priorityA - $priorityB; |
|
255 | + |
|
256 | + } |
|
257 | + ); |
|
258 | + |
|
259 | + // Now we go over all the VAVAILABILITY components and figure if |
|
260 | + // there's any we don't need to consider. |
|
261 | + // |
|
262 | + // This is can be because of one of two reasons: either the |
|
263 | + // VAVAILABILITY component falls outside the time we are interested in, |
|
264 | + // or a different VAVAILABILITY component with a higher priority has |
|
265 | + // already completely covered the time-range. |
|
266 | + $old = $vavailComps; |
|
267 | + $new = []; |
|
268 | + |
|
269 | + foreach ($old as $vavail) { |
|
270 | + |
|
271 | + list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); |
|
272 | + |
|
273 | + // We don't care about datetimes that are earlier or later than the |
|
274 | + // start and end of the freebusy report, so this gets normalized |
|
275 | + // first. |
|
276 | + if (is_null($compStart) || $compStart < $this->start) { |
|
277 | + $compStart = $this->start; |
|
278 | + } |
|
279 | + if (is_null($compEnd) || $compEnd > $this->end) { |
|
280 | + $compEnd = $this->end; |
|
281 | + } |
|
282 | + |
|
283 | + // If the item fell out of the timerange, we can just skip it. |
|
284 | + if ($compStart > $this->end || $compEnd < $this->start) { |
|
285 | + continue; |
|
286 | + } |
|
287 | + |
|
288 | + // Going through our existing list of components to see if there's |
|
289 | + // a higher priority component that already fully covers this one. |
|
290 | + foreach ($new as $higherVavail) { |
|
291 | + |
|
292 | + list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); |
|
293 | + if ( |
|
294 | + (is_null($higherStart) || $higherStart < $compStart) && |
|
295 | + (is_null($higherEnd) || $higherEnd > $compEnd) |
|
296 | + ) { |
|
297 | + |
|
298 | + // Component is fully covered by a higher priority |
|
299 | + // component. We can skip this component. |
|
300 | + continue 2; |
|
301 | + |
|
302 | + } |
|
303 | + |
|
304 | + } |
|
305 | + |
|
306 | + // We're keeping it! |
|
307 | + $new[] = $vavail; |
|
308 | 308 | |
309 | - } |
|
309 | + } |
|
310 | 310 | |
311 | - // Lastly, we need to traverse the remaining components and fill in the |
|
312 | - // freebusydata slots. |
|
313 | - // |
|
314 | - // We traverse the components in reverse, because we want the higher |
|
315 | - // priority components to override the lower ones. |
|
316 | - foreach (array_reverse($new) as $vavail) { |
|
317 | - |
|
318 | - $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; |
|
319 | - list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); |
|
320 | - |
|
321 | - // Making the component size no larger than the requested free-busy |
|
322 | - // report range. |
|
323 | - if (!$vavailStart || $vavailStart < $this->start) { |
|
324 | - $vavailStart = $this->start; |
|
325 | - } |
|
326 | - if (!$vavailEnd || $vavailEnd > $this->end) { |
|
327 | - $vavailEnd = $this->end; |
|
328 | - } |
|
329 | - |
|
330 | - // Marking the entire time range of the VAVAILABILITY component as |
|
331 | - // busy. |
|
332 | - $fbData->add( |
|
333 | - $vavailStart->getTimeStamp(), |
|
334 | - $vavailEnd->getTimeStamp(), |
|
335 | - $busyType |
|
336 | - ); |
|
337 | - |
|
338 | - // Looping over the AVAILABLE components. |
|
339 | - if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) { |
|
340 | - |
|
341 | - list($availStart, $availEnd) = $available->getEffectiveStartEnd(); |
|
342 | - $fbData->add( |
|
343 | - $availStart->getTimeStamp(), |
|
344 | - $availEnd->getTimeStamp(), |
|
345 | - 'FREE' |
|
346 | - ); |
|
347 | - |
|
348 | - if ($available->RRULE) { |
|
349 | - // Our favourite thing: recurrence!! |
|
350 | - |
|
351 | - $rruleIterator = new Recur\RRuleIterator( |
|
352 | - $available->RRULE->getValue(), |
|
353 | - $availStart |
|
354 | - ); |
|
355 | - $rruleIterator->fastForward($vavailStart); |
|
356 | - |
|
357 | - $startEndDiff = $availStart->diff($availEnd); |
|
358 | - |
|
359 | - while ($rruleIterator->valid()) { |
|
360 | - |
|
361 | - $recurStart = $rruleIterator->current(); |
|
362 | - $recurEnd = $recurStart->add($startEndDiff); |
|
363 | - |
|
364 | - if ($recurStart > $vavailEnd) { |
|
365 | - // We're beyond the legal timerange. |
|
366 | - break; |
|
367 | - } |
|
368 | - |
|
369 | - if ($recurEnd > $vavailEnd) { |
|
370 | - // Truncating the end if it exceeds the |
|
371 | - // VAVAILABILITY end. |
|
372 | - $recurEnd = $vavailEnd; |
|
373 | - } |
|
374 | - |
|
375 | - $fbData->add( |
|
376 | - $recurStart->getTimeStamp(), |
|
377 | - $recurEnd->getTimeStamp(), |
|
378 | - 'FREE' |
|
379 | - ); |
|
380 | - |
|
381 | - $rruleIterator->next(); |
|
382 | - |
|
383 | - } |
|
384 | - } |
|
385 | - |
|
386 | - } |
|
387 | - |
|
388 | - } |
|
389 | - |
|
390 | - } |
|
391 | - |
|
392 | - /** |
|
393 | - * This method takes an array of iCalendar objects and applies its busy |
|
394 | - * times on fbData. |
|
395 | - * |
|
396 | - * @param FreeBusyData $fbData |
|
397 | - * @param VCalendar[] $objects |
|
398 | - */ |
|
399 | - protected function calculateBusy(FreeBusyData $fbData, array $objects) { |
|
400 | - |
|
401 | - foreach ($objects as $key => $object) { |
|
402 | - |
|
403 | - foreach ($object->getBaseComponents() as $component) { |
|
404 | - |
|
405 | - switch ($component->name) { |
|
406 | - |
|
407 | - case 'VEVENT' : |
|
408 | - |
|
409 | - $FBTYPE = 'BUSY'; |
|
410 | - if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) { |
|
411 | - break; |
|
412 | - } |
|
413 | - if (isset($component->STATUS)) { |
|
414 | - $status = strtoupper($component->STATUS); |
|
415 | - if ($status === 'CANCELLED') { |
|
416 | - break; |
|
417 | - } |
|
418 | - if ($status === 'TENTATIVE') { |
|
419 | - $FBTYPE = 'BUSY-TENTATIVE'; |
|
420 | - } |
|
421 | - } |
|
422 | - |
|
423 | - $times = []; |
|
424 | - |
|
425 | - if ($component->RRULE) { |
|
426 | - try { |
|
427 | - $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone); |
|
428 | - } catch (NoInstancesException $e) { |
|
429 | - // This event is recurring, but it doesn't have a single |
|
430 | - // instance. We are skipping this event from the output |
|
431 | - // entirely. |
|
432 | - unset($this->objects[$key]); |
|
433 | - continue; |
|
434 | - } |
|
435 | - |
|
436 | - if ($this->start) { |
|
437 | - $iterator->fastForward($this->start); |
|
438 | - } |
|
311 | + // Lastly, we need to traverse the remaining components and fill in the |
|
312 | + // freebusydata slots. |
|
313 | + // |
|
314 | + // We traverse the components in reverse, because we want the higher |
|
315 | + // priority components to override the lower ones. |
|
316 | + foreach (array_reverse($new) as $vavail) { |
|
317 | + |
|
318 | + $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; |
|
319 | + list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); |
|
320 | + |
|
321 | + // Making the component size no larger than the requested free-busy |
|
322 | + // report range. |
|
323 | + if (!$vavailStart || $vavailStart < $this->start) { |
|
324 | + $vavailStart = $this->start; |
|
325 | + } |
|
326 | + if (!$vavailEnd || $vavailEnd > $this->end) { |
|
327 | + $vavailEnd = $this->end; |
|
328 | + } |
|
329 | + |
|
330 | + // Marking the entire time range of the VAVAILABILITY component as |
|
331 | + // busy. |
|
332 | + $fbData->add( |
|
333 | + $vavailStart->getTimeStamp(), |
|
334 | + $vavailEnd->getTimeStamp(), |
|
335 | + $busyType |
|
336 | + ); |
|
337 | + |
|
338 | + // Looping over the AVAILABLE components. |
|
339 | + if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) { |
|
340 | + |
|
341 | + list($availStart, $availEnd) = $available->getEffectiveStartEnd(); |
|
342 | + $fbData->add( |
|
343 | + $availStart->getTimeStamp(), |
|
344 | + $availEnd->getTimeStamp(), |
|
345 | + 'FREE' |
|
346 | + ); |
|
347 | + |
|
348 | + if ($available->RRULE) { |
|
349 | + // Our favourite thing: recurrence!! |
|
350 | + |
|
351 | + $rruleIterator = new Recur\RRuleIterator( |
|
352 | + $available->RRULE->getValue(), |
|
353 | + $availStart |
|
354 | + ); |
|
355 | + $rruleIterator->fastForward($vavailStart); |
|
356 | + |
|
357 | + $startEndDiff = $availStart->diff($availEnd); |
|
358 | + |
|
359 | + while ($rruleIterator->valid()) { |
|
360 | + |
|
361 | + $recurStart = $rruleIterator->current(); |
|
362 | + $recurEnd = $recurStart->add($startEndDiff); |
|
363 | + |
|
364 | + if ($recurStart > $vavailEnd) { |
|
365 | + // We're beyond the legal timerange. |
|
366 | + break; |
|
367 | + } |
|
368 | + |
|
369 | + if ($recurEnd > $vavailEnd) { |
|
370 | + // Truncating the end if it exceeds the |
|
371 | + // VAVAILABILITY end. |
|
372 | + $recurEnd = $vavailEnd; |
|
373 | + } |
|
374 | + |
|
375 | + $fbData->add( |
|
376 | + $recurStart->getTimeStamp(), |
|
377 | + $recurEnd->getTimeStamp(), |
|
378 | + 'FREE' |
|
379 | + ); |
|
380 | + |
|
381 | + $rruleIterator->next(); |
|
382 | + |
|
383 | + } |
|
384 | + } |
|
385 | + |
|
386 | + } |
|
387 | + |
|
388 | + } |
|
389 | + |
|
390 | + } |
|
391 | + |
|
392 | + /** |
|
393 | + * This method takes an array of iCalendar objects and applies its busy |
|
394 | + * times on fbData. |
|
395 | + * |
|
396 | + * @param FreeBusyData $fbData |
|
397 | + * @param VCalendar[] $objects |
|
398 | + */ |
|
399 | + protected function calculateBusy(FreeBusyData $fbData, array $objects) { |
|
400 | + |
|
401 | + foreach ($objects as $key => $object) { |
|
402 | + |
|
403 | + foreach ($object->getBaseComponents() as $component) { |
|
404 | + |
|
405 | + switch ($component->name) { |
|
406 | + |
|
407 | + case 'VEVENT' : |
|
408 | + |
|
409 | + $FBTYPE = 'BUSY'; |
|
410 | + if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) { |
|
411 | + break; |
|
412 | + } |
|
413 | + if (isset($component->STATUS)) { |
|
414 | + $status = strtoupper($component->STATUS); |
|
415 | + if ($status === 'CANCELLED') { |
|
416 | + break; |
|
417 | + } |
|
418 | + if ($status === 'TENTATIVE') { |
|
419 | + $FBTYPE = 'BUSY-TENTATIVE'; |
|
420 | + } |
|
421 | + } |
|
422 | + |
|
423 | + $times = []; |
|
424 | + |
|
425 | + if ($component->RRULE) { |
|
426 | + try { |
|
427 | + $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone); |
|
428 | + } catch (NoInstancesException $e) { |
|
429 | + // This event is recurring, but it doesn't have a single |
|
430 | + // instance. We are skipping this event from the output |
|
431 | + // entirely. |
|
432 | + unset($this->objects[$key]); |
|
433 | + continue; |
|
434 | + } |
|
435 | + |
|
436 | + if ($this->start) { |
|
437 | + $iterator->fastForward($this->start); |
|
438 | + } |
|
439 | 439 | |
440 | - $maxRecurrences = Settings::$maxRecurrences; |
|
441 | - |
|
442 | - while ($iterator->valid() && --$maxRecurrences) { |
|
443 | - |
|
444 | - $startTime = $iterator->getDTStart(); |
|
445 | - if ($this->end && $startTime > $this->end) { |
|
446 | - break; |
|
447 | - } |
|
448 | - $times[] = [ |
|
449 | - $iterator->getDTStart(), |
|
450 | - $iterator->getDTEnd(), |
|
451 | - ]; |
|
452 | - |
|
453 | - $iterator->next(); |
|
454 | - |
|
455 | - } |
|
440 | + $maxRecurrences = Settings::$maxRecurrences; |
|
441 | + |
|
442 | + while ($iterator->valid() && --$maxRecurrences) { |
|
443 | + |
|
444 | + $startTime = $iterator->getDTStart(); |
|
445 | + if ($this->end && $startTime > $this->end) { |
|
446 | + break; |
|
447 | + } |
|
448 | + $times[] = [ |
|
449 | + $iterator->getDTStart(), |
|
450 | + $iterator->getDTEnd(), |
|
451 | + ]; |
|
452 | + |
|
453 | + $iterator->next(); |
|
454 | + |
|
455 | + } |
|
456 | 456 | |
457 | - } else { |
|
458 | - |
|
459 | - $startTime = $component->DTSTART->getDateTime($this->timeZone); |
|
460 | - if ($this->end && $startTime > $this->end) { |
|
461 | - break; |
|
462 | - } |
|
463 | - $endTime = null; |
|
464 | - if (isset($component->DTEND)) { |
|
465 | - $endTime = $component->DTEND->getDateTime($this->timeZone); |
|
466 | - } elseif (isset($component->DURATION)) { |
|
467 | - $duration = DateTimeParser::parseDuration((string)$component->DURATION); |
|
468 | - $endTime = clone $startTime; |
|
469 | - $endTime = $endTime->add($duration); |
|
470 | - } elseif (!$component->DTSTART->hasTime()) { |
|
471 | - $endTime = clone $startTime; |
|
472 | - $endTime = $endTime->modify('+1 day'); |
|
473 | - } else { |
|
474 | - // The event had no duration (0 seconds) |
|
475 | - break; |
|
476 | - } |
|
477 | - |
|
478 | - $times[] = [$startTime, $endTime]; |
|
479 | - |
|
480 | - } |
|
481 | - |
|
482 | - foreach ($times as $time) { |
|
483 | - |
|
484 | - if ($this->end && $time[0] > $this->end) break; |
|
485 | - if ($this->start && $time[1] < $this->start) break; |
|
486 | - |
|
487 | - $fbData->add( |
|
488 | - $time[0]->getTimeStamp(), |
|
489 | - $time[1]->getTimeStamp(), |
|
490 | - $FBTYPE |
|
491 | - ); |
|
492 | - } |
|
493 | - break; |
|
494 | - |
|
495 | - case 'VFREEBUSY' : |
|
496 | - foreach ($component->FREEBUSY as $freebusy) { |
|
497 | - |
|
498 | - $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY'; |
|
499 | - |
|
500 | - // Skipping intervals marked as 'free' |
|
501 | - if ($fbType === 'FREE') |
|
502 | - continue; |
|
503 | - |
|
504 | - $values = explode(',', $freebusy); |
|
505 | - foreach ($values as $value) { |
|
506 | - list($startTime, $endTime) = explode('/', $value); |
|
507 | - $startTime = DateTimeParser::parseDateTime($startTime); |
|
508 | - |
|
509 | - if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') { |
|
510 | - $duration = DateTimeParser::parseDuration($endTime); |
|
511 | - $endTime = clone $startTime; |
|
512 | - $endTime = $endTime->add($duration); |
|
513 | - } else { |
|
514 | - $endTime = DateTimeParser::parseDateTime($endTime); |
|
515 | - } |
|
516 | - |
|
517 | - if ($this->start && $this->start > $endTime) continue; |
|
518 | - if ($this->end && $this->end < $startTime) continue; |
|
519 | - $fbData->add( |
|
520 | - $startTime->getTimeStamp(), |
|
521 | - $endTime->getTimeStamp(), |
|
522 | - $fbType |
|
523 | - ); |
|
524 | - |
|
525 | - } |
|
526 | - |
|
527 | - |
|
528 | - } |
|
529 | - break; |
|
530 | - |
|
531 | - } |
|
457 | + } else { |
|
458 | + |
|
459 | + $startTime = $component->DTSTART->getDateTime($this->timeZone); |
|
460 | + if ($this->end && $startTime > $this->end) { |
|
461 | + break; |
|
462 | + } |
|
463 | + $endTime = null; |
|
464 | + if (isset($component->DTEND)) { |
|
465 | + $endTime = $component->DTEND->getDateTime($this->timeZone); |
|
466 | + } elseif (isset($component->DURATION)) { |
|
467 | + $duration = DateTimeParser::parseDuration((string)$component->DURATION); |
|
468 | + $endTime = clone $startTime; |
|
469 | + $endTime = $endTime->add($duration); |
|
470 | + } elseif (!$component->DTSTART->hasTime()) { |
|
471 | + $endTime = clone $startTime; |
|
472 | + $endTime = $endTime->modify('+1 day'); |
|
473 | + } else { |
|
474 | + // The event had no duration (0 seconds) |
|
475 | + break; |
|
476 | + } |
|
477 | + |
|
478 | + $times[] = [$startTime, $endTime]; |
|
479 | + |
|
480 | + } |
|
481 | + |
|
482 | + foreach ($times as $time) { |
|
483 | + |
|
484 | + if ($this->end && $time[0] > $this->end) break; |
|
485 | + if ($this->start && $time[1] < $this->start) break; |
|
486 | + |
|
487 | + $fbData->add( |
|
488 | + $time[0]->getTimeStamp(), |
|
489 | + $time[1]->getTimeStamp(), |
|
490 | + $FBTYPE |
|
491 | + ); |
|
492 | + } |
|
493 | + break; |
|
494 | + |
|
495 | + case 'VFREEBUSY' : |
|
496 | + foreach ($component->FREEBUSY as $freebusy) { |
|
497 | + |
|
498 | + $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY'; |
|
499 | + |
|
500 | + // Skipping intervals marked as 'free' |
|
501 | + if ($fbType === 'FREE') |
|
502 | + continue; |
|
503 | + |
|
504 | + $values = explode(',', $freebusy); |
|
505 | + foreach ($values as $value) { |
|
506 | + list($startTime, $endTime) = explode('/', $value); |
|
507 | + $startTime = DateTimeParser::parseDateTime($startTime); |
|
508 | + |
|
509 | + if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') { |
|
510 | + $duration = DateTimeParser::parseDuration($endTime); |
|
511 | + $endTime = clone $startTime; |
|
512 | + $endTime = $endTime->add($duration); |
|
513 | + } else { |
|
514 | + $endTime = DateTimeParser::parseDateTime($endTime); |
|
515 | + } |
|
516 | + |
|
517 | + if ($this->start && $this->start > $endTime) continue; |
|
518 | + if ($this->end && $this->end < $startTime) continue; |
|
519 | + $fbData->add( |
|
520 | + $startTime->getTimeStamp(), |
|
521 | + $endTime->getTimeStamp(), |
|
522 | + $fbType |
|
523 | + ); |
|
524 | + |
|
525 | + } |
|
526 | + |
|
527 | + |
|
528 | + } |
|
529 | + break; |
|
530 | + |
|
531 | + } |
|
532 | 532 | |
533 | 533 | |
534 | - } |
|
534 | + } |
|
535 | 535 | |
536 | - } |
|
536 | + } |
|
537 | 537 | |
538 | - } |
|
538 | + } |
|
539 | 539 | |
540 | - /** |
|
541 | - * This method takes a FreeBusyData object and generates the VCALENDAR |
|
542 | - * object associated with it. |
|
543 | - * |
|
544 | - * @return VCalendar |
|
545 | - */ |
|
546 | - protected function generateFreeBusyCalendar(FreeBusyData $fbData) { |
|
547 | - |
|
548 | - if ($this->baseObject) { |
|
549 | - $calendar = $this->baseObject; |
|
550 | - } else { |
|
551 | - $calendar = new VCalendar(); |
|
552 | - } |
|
540 | + /** |
|
541 | + * This method takes a FreeBusyData object and generates the VCALENDAR |
|
542 | + * object associated with it. |
|
543 | + * |
|
544 | + * @return VCalendar |
|
545 | + */ |
|
546 | + protected function generateFreeBusyCalendar(FreeBusyData $fbData) { |
|
547 | + |
|
548 | + if ($this->baseObject) { |
|
549 | + $calendar = $this->baseObject; |
|
550 | + } else { |
|
551 | + $calendar = new VCalendar(); |
|
552 | + } |
|
553 | 553 | |
554 | - $vfreebusy = $calendar->createComponent('VFREEBUSY'); |
|
555 | - $calendar->add($vfreebusy); |
|
554 | + $vfreebusy = $calendar->createComponent('VFREEBUSY'); |
|
555 | + $calendar->add($vfreebusy); |
|
556 | 556 | |
557 | - if ($this->start) { |
|
558 | - $dtstart = $calendar->createProperty('DTSTART'); |
|
559 | - $dtstart->setDateTime($this->start); |
|
560 | - $vfreebusy->add($dtstart); |
|
561 | - } |
|
562 | - if ($this->end) { |
|
563 | - $dtend = $calendar->createProperty('DTEND'); |
|
564 | - $dtend->setDateTime($this->end); |
|
565 | - $vfreebusy->add($dtend); |
|
566 | - } |
|
557 | + if ($this->start) { |
|
558 | + $dtstart = $calendar->createProperty('DTSTART'); |
|
559 | + $dtstart->setDateTime($this->start); |
|
560 | + $vfreebusy->add($dtstart); |
|
561 | + } |
|
562 | + if ($this->end) { |
|
563 | + $dtend = $calendar->createProperty('DTEND'); |
|
564 | + $dtend->setDateTime($this->end); |
|
565 | + $vfreebusy->add($dtend); |
|
566 | + } |
|
567 | 567 | |
568 | - $tz = new \DateTimeZone('UTC'); |
|
569 | - $dtstamp = $calendar->createProperty('DTSTAMP'); |
|
570 | - $dtstamp->setDateTime(new DateTimeImmutable('now', $tz)); |
|
571 | - $vfreebusy->add($dtstamp); |
|
568 | + $tz = new \DateTimeZone('UTC'); |
|
569 | + $dtstamp = $calendar->createProperty('DTSTAMP'); |
|
570 | + $dtstamp->setDateTime(new DateTimeImmutable('now', $tz)); |
|
571 | + $vfreebusy->add($dtstamp); |
|
572 | 572 | |
573 | - foreach ($fbData->getData() as $busyTime) { |
|
573 | + foreach ($fbData->getData() as $busyTime) { |
|
574 | 574 | |
575 | - $busyType = strtoupper($busyTime['type']); |
|
575 | + $busyType = strtoupper($busyTime['type']); |
|
576 | 576 | |
577 | - // Ignoring all the FREE parts, because those are already assumed. |
|
578 | - if ($busyType === 'FREE') { |
|
579 | - continue; |
|
580 | - } |
|
577 | + // Ignoring all the FREE parts, because those are already assumed. |
|
578 | + if ($busyType === 'FREE') { |
|
579 | + continue; |
|
580 | + } |
|
581 | 581 | |
582 | - $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz); |
|
583 | - $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz); |
|
582 | + $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz); |
|
583 | + $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz); |
|
584 | 584 | |
585 | - $prop = $calendar->createProperty( |
|
586 | - 'FREEBUSY', |
|
587 | - $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z') |
|
588 | - ); |
|
585 | + $prop = $calendar->createProperty( |
|
586 | + 'FREEBUSY', |
|
587 | + $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z') |
|
588 | + ); |
|
589 | 589 | |
590 | - // Only setting FBTYPE if it's not BUSY, because BUSY is the |
|
591 | - // default anyway. |
|
592 | - if ($busyType !== 'BUSY') { |
|
593 | - $prop['FBTYPE'] = $busyType; |
|
594 | - } |
|
595 | - $vfreebusy->add($prop); |
|
590 | + // Only setting FBTYPE if it's not BUSY, because BUSY is the |
|
591 | + // default anyway. |
|
592 | + if ($busyType !== 'BUSY') { |
|
593 | + $prop['FBTYPE'] = $busyType; |
|
594 | + } |
|
595 | + $vfreebusy->add($prop); |
|
596 | 596 | |
597 | - } |
|
597 | + } |
|
598 | 598 | |
599 | - return $calendar; |
|
599 | + return $calendar; |
|
600 | 600 | |
601 | 601 | |
602 | - } |
|
602 | + } |
|
603 | 603 | |
604 | 604 | } |
@@ -19,137 +19,137 @@ |
||
19 | 19 | */ |
20 | 20 | class Period 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|null |
|
27 | - */ |
|
28 | - public $delimiter = ','; |
|
29 | - |
|
30 | - /** |
|
31 | - * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
32 | - * |
|
33 | - * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
34 | - * not yet done, but parameters are not included. |
|
35 | - * |
|
36 | - * @param string $val |
|
37 | - * |
|
38 | - * @return void |
|
39 | - */ |
|
40 | - public function setRawMimeDirValue($val) { |
|
41 | - |
|
42 | - $this->setValue(explode($this->delimiter, $val)); |
|
43 | - |
|
44 | - } |
|
45 | - |
|
46 | - /** |
|
47 | - * Returns a raw mime-dir representation of the value. |
|
48 | - * |
|
49 | - * @return string |
|
50 | - */ |
|
51 | - public function getRawMimeDirValue() { |
|
52 | - |
|
53 | - return implode($this->delimiter, $this->getParts()); |
|
54 | - |
|
55 | - } |
|
56 | - |
|
57 | - /** |
|
58 | - * Returns the type of value. |
|
59 | - * |
|
60 | - * This corresponds to the VALUE= parameter. Every property also has a |
|
61 | - * 'default' valueType. |
|
62 | - * |
|
63 | - * @return string |
|
64 | - */ |
|
65 | - public function getValueType() { |
|
66 | - |
|
67 | - return 'PERIOD'; |
|
68 | - |
|
69 | - } |
|
70 | - |
|
71 | - /** |
|
72 | - * Sets the json value, as it would appear in a jCard or jCal object. |
|
73 | - * |
|
74 | - * The value must always be an array. |
|
75 | - * |
|
76 | - * @param array $value |
|
77 | - * |
|
78 | - * @return void |
|
79 | - */ |
|
80 | - public function setJsonValue(array $value) { |
|
81 | - |
|
82 | - $value = array_map( |
|
83 | - public function($item) { |
|
84 | - |
|
85 | - return strtr(implode('/', $item), [':' => '', '-' => '']); |
|
86 | - |
|
87 | - }, |
|
88 | - $value |
|
89 | - ); |
|
90 | - parent::setJsonValue($value); |
|
91 | - |
|
92 | - } |
|
93 | - |
|
94 | - /** |
|
95 | - * Returns the value, in the format it should be encoded for json. |
|
96 | - * |
|
97 | - * This method must always return an array. |
|
98 | - * |
|
99 | - * @return array |
|
100 | - */ |
|
101 | - public function getJsonValue() { |
|
102 | - |
|
103 | - $return = []; |
|
104 | - foreach ($this->getParts() as $item) { |
|
105 | - |
|
106 | - list($start, $end) = explode('/', $item, 2); |
|
107 | - |
|
108 | - $start = DateTimeParser::parseDateTime($start); |
|
109 | - |
|
110 | - // This is a duration value. |
|
111 | - if ($end[0] === 'P') { |
|
112 | - $return[] = [ |
|
113 | - $start->format('Y-m-d\\TH:i:s'), |
|
114 | - $end |
|
115 | - ]; |
|
116 | - } else { |
|
117 | - $end = DateTimeParser::parseDateTime($end); |
|
118 | - $return[] = [ |
|
119 | - $start->format('Y-m-d\\TH:i:s'), |
|
120 | - $end->format('Y-m-d\\TH:i:s'), |
|
121 | - ]; |
|
122 | - } |
|
123 | - |
|
124 | - } |
|
125 | - |
|
126 | - return $return; |
|
127 | - |
|
128 | - } |
|
129 | - |
|
130 | - /** |
|
131 | - * This method serializes only the value of a property. This is used to |
|
132 | - * create xCard or xCal documents. |
|
133 | - * |
|
134 | - * @param Xml\Writer $writer XML writer. |
|
135 | - * |
|
136 | - * @return void |
|
137 | - */ |
|
138 | - protected function xmlSerializeValue(Xml\Writer $writer) { |
|
139 | - |
|
140 | - $writer->startElement(strtolower($this->getValueType())); |
|
141 | - $value = $this->getJsonValue(); |
|
142 | - $writer->writeElement('start', $value[0][0]); |
|
143 | - |
|
144 | - if ($value[0][1][0] === 'P') { |
|
145 | - $writer->writeElement('duration', $value[0][1]); |
|
146 | - } |
|
147 | - else { |
|
148 | - $writer->writeElement('end', $value[0][1]); |
|
149 | - } |
|
150 | - |
|
151 | - $writer->endElement(); |
|
152 | - |
|
153 | - } |
|
22 | + /** |
|
23 | + * In case this is a multi-value property. This string will be used as a |
|
24 | + * delimiter. |
|
25 | + * |
|
26 | + * @var string|null |
|
27 | + */ |
|
28 | + public $delimiter = ','; |
|
29 | + |
|
30 | + /** |
|
31 | + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
32 | + * |
|
33 | + * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
34 | + * not yet done, but parameters are not included. |
|
35 | + * |
|
36 | + * @param string $val |
|
37 | + * |
|
38 | + * @return void |
|
39 | + */ |
|
40 | + public function setRawMimeDirValue($val) { |
|
41 | + |
|
42 | + $this->setValue(explode($this->delimiter, $val)); |
|
43 | + |
|
44 | + } |
|
45 | + |
|
46 | + /** |
|
47 | + * Returns a raw mime-dir representation of the value. |
|
48 | + * |
|
49 | + * @return string |
|
50 | + */ |
|
51 | + public function getRawMimeDirValue() { |
|
52 | + |
|
53 | + return implode($this->delimiter, $this->getParts()); |
|
54 | + |
|
55 | + } |
|
56 | + |
|
57 | + /** |
|
58 | + * Returns the type of value. |
|
59 | + * |
|
60 | + * This corresponds to the VALUE= parameter. Every property also has a |
|
61 | + * 'default' valueType. |
|
62 | + * |
|
63 | + * @return string |
|
64 | + */ |
|
65 | + public function getValueType() { |
|
66 | + |
|
67 | + return 'PERIOD'; |
|
68 | + |
|
69 | + } |
|
70 | + |
|
71 | + /** |
|
72 | + * Sets the json value, as it would appear in a jCard or jCal object. |
|
73 | + * |
|
74 | + * The value must always be an array. |
|
75 | + * |
|
76 | + * @param array $value |
|
77 | + * |
|
78 | + * @return void |
|
79 | + */ |
|
80 | + public function setJsonValue(array $value) { |
|
81 | + |
|
82 | + $value = array_map( |
|
83 | + public function($item) { |
|
84 | + |
|
85 | + return strtr(implode('/', $item), [':' => '', '-' => '']); |
|
86 | + |
|
87 | + }, |
|
88 | + $value |
|
89 | + ); |
|
90 | + parent::setJsonValue($value); |
|
91 | + |
|
92 | + } |
|
93 | + |
|
94 | + /** |
|
95 | + * Returns the value, in the format it should be encoded for json. |
|
96 | + * |
|
97 | + * This method must always return an array. |
|
98 | + * |
|
99 | + * @return array |
|
100 | + */ |
|
101 | + public function getJsonValue() { |
|
102 | + |
|
103 | + $return = []; |
|
104 | + foreach ($this->getParts() as $item) { |
|
105 | + |
|
106 | + list($start, $end) = explode('/', $item, 2); |
|
107 | + |
|
108 | + $start = DateTimeParser::parseDateTime($start); |
|
109 | + |
|
110 | + // This is a duration value. |
|
111 | + if ($end[0] === 'P') { |
|
112 | + $return[] = [ |
|
113 | + $start->format('Y-m-d\\TH:i:s'), |
|
114 | + $end |
|
115 | + ]; |
|
116 | + } else { |
|
117 | + $end = DateTimeParser::parseDateTime($end); |
|
118 | + $return[] = [ |
|
119 | + $start->format('Y-m-d\\TH:i:s'), |
|
120 | + $end->format('Y-m-d\\TH:i:s'), |
|
121 | + ]; |
|
122 | + } |
|
123 | + |
|
124 | + } |
|
125 | + |
|
126 | + return $return; |
|
127 | + |
|
128 | + } |
|
129 | + |
|
130 | + /** |
|
131 | + * This method serializes only the value of a property. This is used to |
|
132 | + * create xCard or xCal documents. |
|
133 | + * |
|
134 | + * @param Xml\Writer $writer XML writer. |
|
135 | + * |
|
136 | + * @return void |
|
137 | + */ |
|
138 | + protected function xmlSerializeValue(Xml\Writer $writer) { |
|
139 | + |
|
140 | + $writer->startElement(strtolower($this->getValueType())); |
|
141 | + $value = $this->getJsonValue(); |
|
142 | + $writer->writeElement('start', $value[0][0]); |
|
143 | + |
|
144 | + if ($value[0][1][0] === 'P') { |
|
145 | + $writer->writeElement('duration', $value[0][1]); |
|
146 | + } |
|
147 | + else { |
|
148 | + $writer->writeElement('end', $value[0][1]); |
|
149 | + } |
|
150 | + |
|
151 | + $writer->endElement(); |
|
152 | + |
|
153 | + } |
|
154 | 154 | |
155 | 155 | } |
@@ -3,7 +3,7 @@ discard block |
||
3 | 3 | namespace Sabre\VObject\Property\ICalendar; |
4 | 4 | |
5 | 5 | use |
6 | - Sabre\VObject\Property\Text; |
|
6 | + Sabre\VObject\Property\Text; |
|
7 | 7 | |
8 | 8 | /** |
9 | 9 | * CalAddress property. |
@@ -16,46 +16,46 @@ discard block |
||
16 | 16 | */ |
17 | 17 | class CalAddress 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|null |
|
24 | - */ |
|
25 | - public $delimiter = null; |
|
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 'CAL-ADDRESS'; |
|
38 | - |
|
39 | - } |
|
40 | - |
|
41 | - /** |
|
42 | - * This returns a normalized form of the value. |
|
43 | - * |
|
44 | - * This is primarily used right now to turn mixed-cased schemes in user |
|
45 | - * uris to lower-case. |
|
46 | - * |
|
47 | - * Evolution in particular tends to encode mailto: as MAILTO:. |
|
48 | - * |
|
49 | - * @return string |
|
50 | - */ |
|
51 | - public function getNormalizedValue() { |
|
52 | - |
|
53 | - $input = $this->getValue(); |
|
54 | - if (!strpos($input, ':')) { |
|
55 | - return $input; |
|
56 | - } |
|
57 | - list($schema, $everythingElse) = explode(':', $input, 2); |
|
58 | - return strtolower($schema) . ':' . $everythingElse; |
|
59 | - |
|
60 | - } |
|
19 | + /** |
|
20 | + * In case this is a multi-value property. This string will be used as a |
|
21 | + * delimiter. |
|
22 | + * |
|
23 | + * @var string|null |
|
24 | + */ |
|
25 | + public $delimiter = null; |
|
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 'CAL-ADDRESS'; |
|
38 | + |
|
39 | + } |
|
40 | + |
|
41 | + /** |
|
42 | + * This returns a normalized form of the value. |
|
43 | + * |
|
44 | + * This is primarily used right now to turn mixed-cased schemes in user |
|
45 | + * uris to lower-case. |
|
46 | + * |
|
47 | + * Evolution in particular tends to encode mailto: as MAILTO:. |
|
48 | + * |
|
49 | + * @return string |
|
50 | + */ |
|
51 | + public function getNormalizedValue() { |
|
52 | + |
|
53 | + $input = $this->getValue(); |
|
54 | + if (!strpos($input, ':')) { |
|
55 | + return $input; |
|
56 | + } |
|
57 | + list($schema, $everythingElse) = explode(':', $input, 2); |
|
58 | + return strtolower($schema) . ':' . $everythingElse; |
|
59 | + |
|
60 | + } |
|
61 | 61 | } |
@@ -24,270 +24,270 @@ |
||
24 | 24 | */ |
25 | 25 | class Recur extends Property { |
26 | 26 | |
27 | - /** |
|
28 | - * Updates the current value. |
|
29 | - * |
|
30 | - * This may be either a single, or multiple strings in an array. |
|
31 | - * |
|
32 | - * @param string|array $value |
|
33 | - * |
|
34 | - * @return void |
|
35 | - */ |
|
36 | - public function setValue($value) { |
|
37 | - |
|
38 | - // If we're getting the data from json, we'll be receiving an object |
|
39 | - if ($value instanceof \StdClass) { |
|
40 | - $value = (array)$value; |
|
41 | - } |
|
42 | - |
|
43 | - if (is_array($value)) { |
|
44 | - $newVal = []; |
|
45 | - foreach ($value as $k => $v) { |
|
46 | - |
|
47 | - if (is_string($v)) { |
|
48 | - $v = strtoupper($v); |
|
49 | - |
|
50 | - // The value had multiple sub-values |
|
51 | - if (strpos($v, ',') !== false) { |
|
52 | - $v = explode(',', $v); |
|
53 | - } |
|
54 | - if (strcmp($k, 'until') === 0) { |
|
55 | - $v = strtr($v, [':' => '', '-' => '']); |
|
56 | - } |
|
57 | - } elseif (is_array($v)) { |
|
58 | - $v = array_map('strtoupper', $v); |
|
59 | - } |
|
60 | - |
|
61 | - $newVal[strtoupper($k)] = $v; |
|
62 | - } |
|
63 | - $this->value = $newVal; |
|
64 | - } elseif (is_string($value)) { |
|
65 | - $this->value = self::stringToArray($value); |
|
66 | - } else { |
|
67 | - throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); |
|
68 | - } |
|
69 | - |
|
70 | - } |
|
71 | - |
|
72 | - /** |
|
73 | - * Returns the current value. |
|
74 | - * |
|
75 | - * This method will always return a singular value. If this was a |
|
76 | - * multi-value object, some decision will be made first on how to represent |
|
77 | - * it as a string. |
|
78 | - * |
|
79 | - * To get the correct multi-value version, use getParts. |
|
80 | - * |
|
81 | - * @return string |
|
82 | - */ |
|
83 | - public function getValue() { |
|
84 | - |
|
85 | - $out = []; |
|
86 | - foreach ($this->value as $key => $value) { |
|
87 | - $out[] = $key . '=' . (is_array($value) ? implode(',', $value) : $value); |
|
88 | - } |
|
89 | - return strtoupper(implode(';', $out)); |
|
90 | - |
|
91 | - } |
|
92 | - |
|
93 | - /** |
|
94 | - * Sets a multi-valued property. |
|
95 | - * |
|
96 | - * @param array $parts |
|
97 | - * @return void |
|
98 | - */ |
|
99 | - public function setParts(array $parts) { |
|
100 | - |
|
101 | - $this->setValue($parts); |
|
102 | - |
|
103 | - } |
|
104 | - |
|
105 | - /** |
|
106 | - * Returns a multi-valued property. |
|
107 | - * |
|
108 | - * This method always returns an array, if there was only a single value, |
|
109 | - * it will still be wrapped in an array. |
|
110 | - * |
|
111 | - * @return array |
|
112 | - */ |
|
113 | - public function getParts() { |
|
114 | - |
|
115 | - return $this->value; |
|
116 | - |
|
117 | - } |
|
118 | - |
|
119 | - /** |
|
120 | - * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
121 | - * |
|
122 | - * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
123 | - * not yet done, but parameters are not included. |
|
124 | - * |
|
125 | - * @param string $val |
|
126 | - * |
|
127 | - * @return void |
|
128 | - */ |
|
129 | - public function setRawMimeDirValue($val) { |
|
130 | - |
|
131 | - $this->setValue($val); |
|
132 | - |
|
133 | - } |
|
134 | - |
|
135 | - /** |
|
136 | - * Returns a raw mime-dir representation of the value. |
|
137 | - * |
|
138 | - * @return string |
|
139 | - */ |
|
140 | - public function getRawMimeDirValue() { |
|
141 | - |
|
142 | - return $this->getValue(); |
|
143 | - |
|
144 | - } |
|
145 | - |
|
146 | - /** |
|
147 | - * Returns the type of value. |
|
148 | - * |
|
149 | - * This corresponds to the VALUE= parameter. Every property also has a |
|
150 | - * 'default' valueType. |
|
151 | - * |
|
152 | - * @return string |
|
153 | - */ |
|
154 | - public function getValueType() { |
|
155 | - |
|
156 | - return 'RECUR'; |
|
157 | - |
|
158 | - } |
|
159 | - |
|
160 | - /** |
|
161 | - * Returns the value, in the format it should be encoded for json. |
|
162 | - * |
|
163 | - * This method must always return an array. |
|
164 | - * |
|
165 | - * @return array |
|
166 | - */ |
|
167 | - public function getJsonValue() { |
|
168 | - |
|
169 | - $values = []; |
|
170 | - foreach ($this->getParts() as $k => $v) { |
|
171 | - if (strcmp($k, 'UNTIL') === 0) { |
|
172 | - $date = new DateTime($this->root, null, $v); |
|
173 | - $values[strtolower($k)] = $date->getJsonValue()[0]; |
|
174 | - } elseif (strcmp($k, 'COUNT') === 0) { |
|
175 | - $values[strtolower($k)] = intval($v); |
|
176 | - } else { |
|
177 | - $values[strtolower($k)] = $v; |
|
178 | - } |
|
179 | - } |
|
180 | - return [$values]; |
|
181 | - |
|
182 | - } |
|
183 | - |
|
184 | - /** |
|
185 | - * This method serializes only the value of a property. This is used to |
|
186 | - * create xCard or xCal documents. |
|
187 | - * |
|
188 | - * @param Xml\Writer $writer XML writer. |
|
189 | - * |
|
190 | - * @return void |
|
191 | - */ |
|
192 | - protected function xmlSerializeValue(Xml\Writer $writer) { |
|
193 | - |
|
194 | - $valueType = strtolower($this->getValueType()); |
|
195 | - |
|
196 | - foreach ($this->getJsonValue() as $value) { |
|
197 | - $writer->writeElement($valueType, $value); |
|
198 | - } |
|
199 | - |
|
200 | - } |
|
201 | - |
|
202 | - /** |
|
203 | - * Parses an RRULE value string, and turns it into a struct-ish array. |
|
204 | - * |
|
205 | - * @param string $value |
|
206 | - * |
|
207 | - * @return array |
|
208 | - */ |
|
209 | - static function stringToArray($value) { |
|
210 | - |
|
211 | - $value = strtoupper($value); |
|
212 | - $newValue = []; |
|
213 | - foreach (explode(';', $value) as $part) { |
|
214 | - |
|
215 | - // Skipping empty parts. |
|
216 | - if (empty($part)) { |
|
217 | - continue; |
|
218 | - } |
|
219 | - list($partName, $partValue) = explode('=', $part); |
|
220 | - |
|
221 | - // The value itself had multiple values.. |
|
222 | - if (strpos($partValue, ',') !== false) { |
|
223 | - $partValue = explode(',', $partValue); |
|
224 | - } |
|
225 | - $newValue[$partName] = $partValue; |
|
226 | - |
|
227 | - } |
|
228 | - |
|
229 | - return $newValue; |
|
230 | - } |
|
231 | - |
|
232 | - /** |
|
233 | - * Validates the node for correctness. |
|
234 | - * |
|
235 | - * The following options are supported: |
|
236 | - * Node::REPAIR - May attempt to automatically repair the problem. |
|
237 | - * |
|
238 | - * This method returns an array with detected problems. |
|
239 | - * Every element has the following properties: |
|
240 | - * |
|
241 | - * * level - problem level. |
|
242 | - * * message - A human-readable string describing the issue. |
|
243 | - * * node - A reference to the problematic node. |
|
244 | - * |
|
245 | - * The level means: |
|
246 | - * 1 - The issue was repaired (only happens if REPAIR was turned on) |
|
247 | - * 2 - An inconsequential issue |
|
248 | - * 3 - A severe issue. |
|
249 | - * |
|
250 | - * @param int $options |
|
251 | - * |
|
252 | - * @return array |
|
253 | - */ |
|
254 | - public function validate($options = 0) { |
|
255 | - |
|
256 | - $repair = ($options & self::REPAIR); |
|
257 | - |
|
258 | - $warnings = parent::validate($options); |
|
259 | - $values = $this->getParts(); |
|
260 | - |
|
261 | - foreach ($values as $key => $value) { |
|
262 | - |
|
263 | - if (empty($value)) { |
|
264 | - $warnings[] = [ |
|
265 | - 'level' => $repair ? 3 : 1, |
|
266 | - 'message' => 'Invalid value for ' . $key . ' in ' . $this->name, |
|
267 | - 'node' => $this |
|
268 | - ]; |
|
269 | - if ($repair) { |
|
270 | - unset($values[$key]); |
|
271 | - } |
|
272 | - } |
|
273 | - |
|
274 | - } |
|
275 | - if (!isset($values['FREQ'])) { |
|
276 | - $warnings[] = [ |
|
277 | - 'level' => $repair ? 3 : 1, |
|
278 | - 'message' => 'FREQ is required in ' . $this->name, |
|
279 | - 'node' => $this |
|
280 | - ]; |
|
281 | - if ($repair) { |
|
282 | - $this->parent->remove($this); |
|
283 | - } |
|
284 | - } |
|
285 | - if ($repair) { |
|
286 | - $this->setValue($values); |
|
287 | - } |
|
288 | - |
|
289 | - return $warnings; |
|
290 | - |
|
291 | - } |
|
27 | + /** |
|
28 | + * Updates the current value. |
|
29 | + * |
|
30 | + * This may be either a single, or multiple strings in an array. |
|
31 | + * |
|
32 | + * @param string|array $value |
|
33 | + * |
|
34 | + * @return void |
|
35 | + */ |
|
36 | + public function setValue($value) { |
|
37 | + |
|
38 | + // If we're getting the data from json, we'll be receiving an object |
|
39 | + if ($value instanceof \StdClass) { |
|
40 | + $value = (array)$value; |
|
41 | + } |
|
42 | + |
|
43 | + if (is_array($value)) { |
|
44 | + $newVal = []; |
|
45 | + foreach ($value as $k => $v) { |
|
46 | + |
|
47 | + if (is_string($v)) { |
|
48 | + $v = strtoupper($v); |
|
49 | + |
|
50 | + // The value had multiple sub-values |
|
51 | + if (strpos($v, ',') !== false) { |
|
52 | + $v = explode(',', $v); |
|
53 | + } |
|
54 | + if (strcmp($k, 'until') === 0) { |
|
55 | + $v = strtr($v, [':' => '', '-' => '']); |
|
56 | + } |
|
57 | + } elseif (is_array($v)) { |
|
58 | + $v = array_map('strtoupper', $v); |
|
59 | + } |
|
60 | + |
|
61 | + $newVal[strtoupper($k)] = $v; |
|
62 | + } |
|
63 | + $this->value = $newVal; |
|
64 | + } elseif (is_string($value)) { |
|
65 | + $this->value = self::stringToArray($value); |
|
66 | + } else { |
|
67 | + throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); |
|
68 | + } |
|
69 | + |
|
70 | + } |
|
71 | + |
|
72 | + /** |
|
73 | + * Returns the current value. |
|
74 | + * |
|
75 | + * This method will always return a singular value. If this was a |
|
76 | + * multi-value object, some decision will be made first on how to represent |
|
77 | + * it as a string. |
|
78 | + * |
|
79 | + * To get the correct multi-value version, use getParts. |
|
80 | + * |
|
81 | + * @return string |
|
82 | + */ |
|
83 | + public function getValue() { |
|
84 | + |
|
85 | + $out = []; |
|
86 | + foreach ($this->value as $key => $value) { |
|
87 | + $out[] = $key . '=' . (is_array($value) ? implode(',', $value) : $value); |
|
88 | + } |
|
89 | + return strtoupper(implode(';', $out)); |
|
90 | + |
|
91 | + } |
|
92 | + |
|
93 | + /** |
|
94 | + * Sets a multi-valued property. |
|
95 | + * |
|
96 | + * @param array $parts |
|
97 | + * @return void |
|
98 | + */ |
|
99 | + public function setParts(array $parts) { |
|
100 | + |
|
101 | + $this->setValue($parts); |
|
102 | + |
|
103 | + } |
|
104 | + |
|
105 | + /** |
|
106 | + * Returns a multi-valued property. |
|
107 | + * |
|
108 | + * This method always returns an array, if there was only a single value, |
|
109 | + * it will still be wrapped in an array. |
|
110 | + * |
|
111 | + * @return array |
|
112 | + */ |
|
113 | + public function getParts() { |
|
114 | + |
|
115 | + return $this->value; |
|
116 | + |
|
117 | + } |
|
118 | + |
|
119 | + /** |
|
120 | + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
121 | + * |
|
122 | + * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
123 | + * not yet done, but parameters are not included. |
|
124 | + * |
|
125 | + * @param string $val |
|
126 | + * |
|
127 | + * @return void |
|
128 | + */ |
|
129 | + public function setRawMimeDirValue($val) { |
|
130 | + |
|
131 | + $this->setValue($val); |
|
132 | + |
|
133 | + } |
|
134 | + |
|
135 | + /** |
|
136 | + * Returns a raw mime-dir representation of the value. |
|
137 | + * |
|
138 | + * @return string |
|
139 | + */ |
|
140 | + public function getRawMimeDirValue() { |
|
141 | + |
|
142 | + return $this->getValue(); |
|
143 | + |
|
144 | + } |
|
145 | + |
|
146 | + /** |
|
147 | + * Returns the type of value. |
|
148 | + * |
|
149 | + * This corresponds to the VALUE= parameter. Every property also has a |
|
150 | + * 'default' valueType. |
|
151 | + * |
|
152 | + * @return string |
|
153 | + */ |
|
154 | + public function getValueType() { |
|
155 | + |
|
156 | + return 'RECUR'; |
|
157 | + |
|
158 | + } |
|
159 | + |
|
160 | + /** |
|
161 | + * Returns the value, in the format it should be encoded for json. |
|
162 | + * |
|
163 | + * This method must always return an array. |
|
164 | + * |
|
165 | + * @return array |
|
166 | + */ |
|
167 | + public function getJsonValue() { |
|
168 | + |
|
169 | + $values = []; |
|
170 | + foreach ($this->getParts() as $k => $v) { |
|
171 | + if (strcmp($k, 'UNTIL') === 0) { |
|
172 | + $date = new DateTime($this->root, null, $v); |
|
173 | + $values[strtolower($k)] = $date->getJsonValue()[0]; |
|
174 | + } elseif (strcmp($k, 'COUNT') === 0) { |
|
175 | + $values[strtolower($k)] = intval($v); |
|
176 | + } else { |
|
177 | + $values[strtolower($k)] = $v; |
|
178 | + } |
|
179 | + } |
|
180 | + return [$values]; |
|
181 | + |
|
182 | + } |
|
183 | + |
|
184 | + /** |
|
185 | + * This method serializes only the value of a property. This is used to |
|
186 | + * create xCard or xCal documents. |
|
187 | + * |
|
188 | + * @param Xml\Writer $writer XML writer. |
|
189 | + * |
|
190 | + * @return void |
|
191 | + */ |
|
192 | + protected function xmlSerializeValue(Xml\Writer $writer) { |
|
193 | + |
|
194 | + $valueType = strtolower($this->getValueType()); |
|
195 | + |
|
196 | + foreach ($this->getJsonValue() as $value) { |
|
197 | + $writer->writeElement($valueType, $value); |
|
198 | + } |
|
199 | + |
|
200 | + } |
|
201 | + |
|
202 | + /** |
|
203 | + * Parses an RRULE value string, and turns it into a struct-ish array. |
|
204 | + * |
|
205 | + * @param string $value |
|
206 | + * |
|
207 | + * @return array |
|
208 | + */ |
|
209 | + static function stringToArray($value) { |
|
210 | + |
|
211 | + $value = strtoupper($value); |
|
212 | + $newValue = []; |
|
213 | + foreach (explode(';', $value) as $part) { |
|
214 | + |
|
215 | + // Skipping empty parts. |
|
216 | + if (empty($part)) { |
|
217 | + continue; |
|
218 | + } |
|
219 | + list($partName, $partValue) = explode('=', $part); |
|
220 | + |
|
221 | + // The value itself had multiple values.. |
|
222 | + if (strpos($partValue, ',') !== false) { |
|
223 | + $partValue = explode(',', $partValue); |
|
224 | + } |
|
225 | + $newValue[$partName] = $partValue; |
|
226 | + |
|
227 | + } |
|
228 | + |
|
229 | + return $newValue; |
|
230 | + } |
|
231 | + |
|
232 | + /** |
|
233 | + * Validates the node for correctness. |
|
234 | + * |
|
235 | + * The following options are supported: |
|
236 | + * Node::REPAIR - May attempt to automatically repair the problem. |
|
237 | + * |
|
238 | + * This method returns an array with detected problems. |
|
239 | + * Every element has the following properties: |
|
240 | + * |
|
241 | + * * level - problem level. |
|
242 | + * * message - A human-readable string describing the issue. |
|
243 | + * * node - A reference to the problematic node. |
|
244 | + * |
|
245 | + * The level means: |
|
246 | + * 1 - The issue was repaired (only happens if REPAIR was turned on) |
|
247 | + * 2 - An inconsequential issue |
|
248 | + * 3 - A severe issue. |
|
249 | + * |
|
250 | + * @param int $options |
|
251 | + * |
|
252 | + * @return array |
|
253 | + */ |
|
254 | + public function validate($options = 0) { |
|
255 | + |
|
256 | + $repair = ($options & self::REPAIR); |
|
257 | + |
|
258 | + $warnings = parent::validate($options); |
|
259 | + $values = $this->getParts(); |
|
260 | + |
|
261 | + foreach ($values as $key => $value) { |
|
262 | + |
|
263 | + if (empty($value)) { |
|
264 | + $warnings[] = [ |
|
265 | + 'level' => $repair ? 3 : 1, |
|
266 | + 'message' => 'Invalid value for ' . $key . ' in ' . $this->name, |
|
267 | + 'node' => $this |
|
268 | + ]; |
|
269 | + if ($repair) { |
|
270 | + unset($values[$key]); |
|
271 | + } |
|
272 | + } |
|
273 | + |
|
274 | + } |
|
275 | + if (!isset($values['FREQ'])) { |
|
276 | + $warnings[] = [ |
|
277 | + 'level' => $repair ? 3 : 1, |
|
278 | + 'message' => 'FREQ is required in ' . $this->name, |
|
279 | + 'node' => $this |
|
280 | + ]; |
|
281 | + if ($repair) { |
|
282 | + $this->parent->remove($this); |
|
283 | + } |
|
284 | + } |
|
285 | + if ($repair) { |
|
286 | + $this->setValue($values); |
|
287 | + } |
|
288 | + |
|
289 | + return $warnings; |
|
290 | + |
|
291 | + } |
|
292 | 292 | |
293 | 293 | } |
@@ -18,68 +18,68 @@ |
||
18 | 18 | */ |
19 | 19 | class Duration extends Property { |
20 | 20 | |
21 | - /** |
|
22 | - * In case this is a multi-value property. This string will be used as a |
|
23 | - * delimiter. |
|
24 | - * |
|
25 | - * @var string|null |
|
26 | - */ |
|
27 | - public $delimiter = ','; |
|
28 | - |
|
29 | - /** |
|
30 | - * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
31 | - * |
|
32 | - * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
33 | - * not yet done, but parameters are not included. |
|
34 | - * |
|
35 | - * @param string $val |
|
36 | - * |
|
37 | - * @return void |
|
38 | - */ |
|
39 | - public function setRawMimeDirValue($val) { |
|
40 | - |
|
41 | - $this->setValue(explode($this->delimiter, $val)); |
|
42 | - |
|
43 | - } |
|
44 | - |
|
45 | - /** |
|
46 | - * Returns a raw mime-dir representation of the value. |
|
47 | - * |
|
48 | - * @return string |
|
49 | - */ |
|
50 | - public function getRawMimeDirValue() { |
|
51 | - |
|
52 | - return implode($this->delimiter, $this->getParts()); |
|
53 | - |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * Returns the type of value. |
|
58 | - * |
|
59 | - * This corresponds to the VALUE= parameter. Every property also has a |
|
60 | - * 'default' valueType. |
|
61 | - * |
|
62 | - * @return string |
|
63 | - */ |
|
64 | - public function getValueType() { |
|
65 | - |
|
66 | - return 'DURATION'; |
|
67 | - |
|
68 | - } |
|
69 | - |
|
70 | - /** |
|
71 | - * Returns a DateInterval representation of the Duration property. |
|
72 | - * |
|
73 | - * If the property has more than one value, only the first is returned. |
|
74 | - * |
|
75 | - * @return \DateInterval |
|
76 | - */ |
|
77 | - public function getDateInterval() { |
|
78 | - |
|
79 | - $parts = $this->getParts(); |
|
80 | - $value = $parts[0]; |
|
81 | - return DateTimeParser::parseDuration($value); |
|
82 | - |
|
83 | - } |
|
21 | + /** |
|
22 | + * In case this is a multi-value property. This string will be used as a |
|
23 | + * delimiter. |
|
24 | + * |
|
25 | + * @var string|null |
|
26 | + */ |
|
27 | + public $delimiter = ','; |
|
28 | + |
|
29 | + /** |
|
30 | + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
31 | + * |
|
32 | + * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
33 | + * not yet done, but parameters are not included. |
|
34 | + * |
|
35 | + * @param string $val |
|
36 | + * |
|
37 | + * @return void |
|
38 | + */ |
|
39 | + public function setRawMimeDirValue($val) { |
|
40 | + |
|
41 | + $this->setValue(explode($this->delimiter, $val)); |
|
42 | + |
|
43 | + } |
|
44 | + |
|
45 | + /** |
|
46 | + * Returns a raw mime-dir representation of the value. |
|
47 | + * |
|
48 | + * @return string |
|
49 | + */ |
|
50 | + public function getRawMimeDirValue() { |
|
51 | + |
|
52 | + return implode($this->delimiter, $this->getParts()); |
|
53 | + |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * Returns the type of value. |
|
58 | + * |
|
59 | + * This corresponds to the VALUE= parameter. Every property also has a |
|
60 | + * 'default' valueType. |
|
61 | + * |
|
62 | + * @return string |
|
63 | + */ |
|
64 | + public function getValueType() { |
|
65 | + |
|
66 | + return 'DURATION'; |
|
67 | + |
|
68 | + } |
|
69 | + |
|
70 | + /** |
|
71 | + * Returns a DateInterval representation of the Duration property. |
|
72 | + * |
|
73 | + * If the property has more than one value, only the first is returned. |
|
74 | + * |
|
75 | + * @return \DateInterval |
|
76 | + */ |
|
77 | + public function getDateInterval() { |
|
78 | + |
|
79 | + $parts = $this->getParts(); |
|
80 | + $value = $parts[0]; |
|
81 | + return DateTimeParser::parseDuration($value); |
|
82 | + |
|
83 | + } |
|
84 | 84 | |
85 | 85 | } |
@@ -26,379 +26,379 @@ |
||
26 | 26 | */ |
27 | 27 | class DateTime extends Property { |
28 | 28 | |
29 | - /** |
|
30 | - * In case this is a multi-value property. This string will be used as a |
|
31 | - * delimiter. |
|
32 | - * |
|
33 | - * @var string|null |
|
34 | - */ |
|
35 | - public $delimiter = ','; |
|
36 | - |
|
37 | - /** |
|
38 | - * Sets a multi-valued property. |
|
39 | - * |
|
40 | - * You may also specify DateTime objects here. |
|
41 | - * |
|
42 | - * @param array $parts |
|
43 | - * |
|
44 | - * @return void |
|
45 | - */ |
|
46 | - public function setParts(array $parts) { |
|
47 | - |
|
48 | - if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) { |
|
49 | - $this->setDateTimes($parts); |
|
50 | - } else { |
|
51 | - parent::setParts($parts); |
|
52 | - } |
|
53 | - |
|
54 | - } |
|
55 | - |
|
56 | - /** |
|
57 | - * Updates the current value. |
|
58 | - * |
|
59 | - * This may be either a single, or multiple strings in an array. |
|
60 | - * |
|
61 | - * Instead of strings, you may also use DateTime here. |
|
62 | - * |
|
63 | - * @param string|array|DateTimeInterface $value |
|
64 | - * |
|
65 | - * @return void |
|
66 | - */ |
|
67 | - public function setValue($value) { |
|
68 | - |
|
69 | - if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) { |
|
70 | - $this->setDateTimes($value); |
|
71 | - } elseif ($value instanceof DateTimeInterface) { |
|
72 | - $this->setDateTimes([$value]); |
|
73 | - } else { |
|
74 | - parent::setValue($value); |
|
75 | - } |
|
76 | - |
|
77 | - } |
|
78 | - |
|
79 | - /** |
|
80 | - * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
81 | - * |
|
82 | - * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
83 | - * not yet done, but parameters are not included. |
|
84 | - * |
|
85 | - * @param string $val |
|
86 | - * |
|
87 | - * @return void |
|
88 | - */ |
|
89 | - public function setRawMimeDirValue($val) { |
|
90 | - |
|
91 | - $this->setValue(explode($this->delimiter, $val)); |
|
92 | - |
|
93 | - } |
|
94 | - |
|
95 | - /** |
|
96 | - * Returns a raw mime-dir representation of the value. |
|
97 | - * |
|
98 | - * @return string |
|
99 | - */ |
|
100 | - public function getRawMimeDirValue() { |
|
101 | - |
|
102 | - return implode($this->delimiter, $this->getParts()); |
|
103 | - |
|
104 | - } |
|
105 | - |
|
106 | - /** |
|
107 | - * Returns true if this is a DATE-TIME value, false if it's a DATE. |
|
108 | - * |
|
109 | - * @return bool |
|
110 | - */ |
|
111 | - public function hasTime() { |
|
112 | - |
|
113 | - return strtoupper((string)$this['VALUE']) !== 'DATE'; |
|
114 | - |
|
115 | - } |
|
116 | - |
|
117 | - /** |
|
118 | - * Returns true if this is a floating DATE or DATE-TIME. |
|
119 | - * |
|
120 | - * Note that DATE is always floating. |
|
121 | - */ |
|
122 | - public function isFloating() { |
|
123 | - |
|
124 | - return |
|
125 | - !$this->hasTime() || |
|
126 | - ( |
|
127 | - !isset($this['TZID']) && |
|
128 | - strpos($this->getValue(), 'Z') === false |
|
129 | - ); |
|
130 | - |
|
131 | - } |
|
132 | - |
|
133 | - /** |
|
134 | - * Returns a date-time value. |
|
135 | - * |
|
136 | - * Note that if this property contained more than 1 date-time, only the |
|
137 | - * first will be returned. To get an array with multiple values, call |
|
138 | - * getDateTimes. |
|
139 | - * |
|
140 | - * If no timezone information is known, because it's either an all-day |
|
141 | - * property or floating time, we will use the DateTimeZone argument to |
|
142 | - * figure out the exact date. |
|
143 | - * |
|
144 | - * @param DateTimeZone $timeZone |
|
145 | - * |
|
146 | - * @return DateTimeImmutable |
|
147 | - */ |
|
148 | - public function getDateTime(DateTimeZone $timeZone = null) { |
|
149 | - |
|
150 | - $dt = $this->getDateTimes($timeZone); |
|
151 | - if (!$dt) return; |
|
152 | - |
|
153 | - return $dt[0]; |
|
154 | - |
|
155 | - } |
|
156 | - |
|
157 | - /** |
|
158 | - * Returns multiple date-time values. |
|
159 | - * |
|
160 | - * If no timezone information is known, because it's either an all-day |
|
161 | - * property or floating time, we will use the DateTimeZone argument to |
|
162 | - * figure out the exact date. |
|
163 | - * |
|
164 | - * @param DateTimeZone $timeZone |
|
165 | - * |
|
166 | - * @return DateTimeImmutable[] |
|
167 | - * @return \DateTime[] |
|
168 | - */ |
|
169 | - public function getDateTimes(DateTimeZone $timeZone = null) { |
|
170 | - |
|
171 | - // Does the property have a TZID? |
|
172 | - $tzid = $this['TZID']; |
|
173 | - |
|
174 | - if ($tzid) { |
|
175 | - $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); |
|
176 | - } |
|
177 | - |
|
178 | - $dts = []; |
|
179 | - foreach ($this->getParts() as $part) { |
|
180 | - $dts[] = DateTimeParser::parse($part, $timeZone); |
|
181 | - } |
|
182 | - return $dts; |
|
183 | - |
|
184 | - } |
|
185 | - |
|
186 | - /** |
|
187 | - * Sets the property as a DateTime object. |
|
188 | - * |
|
189 | - * @param DateTimeInterface $dt |
|
190 | - * @param bool isFloating If set to true, timezones will be ignored. |
|
191 | - * |
|
192 | - * @return void |
|
193 | - */ |
|
194 | - public function setDateTime(DateTimeInterface $dt, $isFloating = false) { |
|
195 | - |
|
196 | - $this->setDateTimes([$dt], $isFloating); |
|
197 | - |
|
198 | - } |
|
199 | - |
|
200 | - /** |
|
201 | - * Sets the property as multiple date-time objects. |
|
202 | - * |
|
203 | - * The first value will be used as a reference for the timezones, and all |
|
204 | - * the otehr values will be adjusted for that timezone |
|
205 | - * |
|
206 | - * @param DateTimeInterface[] $dt |
|
207 | - * @param bool isFloating If set to true, timezones will be ignored. |
|
208 | - * |
|
209 | - * @return void |
|
210 | - */ |
|
211 | - public function setDateTimes(array $dt, $isFloating = false) { |
|
212 | - |
|
213 | - $values = []; |
|
214 | - |
|
215 | - if ($this->hasTime()) { |
|
216 | - |
|
217 | - $tz = null; |
|
218 | - $isUtc = false; |
|
219 | - |
|
220 | - foreach ($dt as $d) { |
|
221 | - |
|
222 | - if ($isFloating) { |
|
223 | - $values[] = $d->format('Ymd\\THis'); |
|
224 | - continue; |
|
225 | - } |
|
226 | - if (is_null($tz)) { |
|
227 | - $tz = $d->getTimeZone(); |
|
228 | - $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']); |
|
229 | - if (!$isUtc) { |
|
230 | - $this->offsetSet('TZID', $tz->getName()); |
|
231 | - } |
|
232 | - } else { |
|
233 | - $d = $d->setTimeZone($tz); |
|
234 | - } |
|
235 | - |
|
236 | - if ($isUtc) { |
|
237 | - $values[] = $d->format('Ymd\\THis\\Z'); |
|
238 | - } else { |
|
239 | - $values[] = $d->format('Ymd\\THis'); |
|
240 | - } |
|
241 | - |
|
242 | - } |
|
243 | - if ($isUtc || $isFloating) { |
|
244 | - $this->offsetUnset('TZID'); |
|
245 | - } |
|
246 | - |
|
247 | - } else { |
|
248 | - |
|
249 | - foreach ($dt as $d) { |
|
250 | - |
|
251 | - $values[] = $d->format('Ymd'); |
|
252 | - |
|
253 | - } |
|
254 | - $this->offsetUnset('TZID'); |
|
255 | - |
|
256 | - } |
|
257 | - |
|
258 | - $this->value = $values; |
|
259 | - |
|
260 | - } |
|
261 | - |
|
262 | - /** |
|
263 | - * Returns the type of value. |
|
264 | - * |
|
265 | - * This corresponds to the VALUE= parameter. Every property also has a |
|
266 | - * 'default' valueType. |
|
267 | - * |
|
268 | - * @return string |
|
269 | - */ |
|
270 | - public function getValueType() { |
|
271 | - |
|
272 | - return $this->hasTime() ? 'DATE-TIME' : 'DATE'; |
|
273 | - |
|
274 | - } |
|
275 | - |
|
276 | - /** |
|
277 | - * Returns the value, in the format it should be encoded for JSON. |
|
278 | - * |
|
279 | - * This method must always return an array. |
|
280 | - * |
|
281 | - * @return array |
|
282 | - */ |
|
283 | - public function getJsonValue() { |
|
284 | - |
|
285 | - $dts = $this->getDateTimes(); |
|
286 | - $hasTime = $this->hasTime(); |
|
287 | - $isFloating = $this->isFloating(); |
|
288 | - |
|
289 | - $tz = $dts[0]->getTimeZone(); |
|
290 | - $isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']); |
|
291 | - |
|
292 | - return array_map( |
|
293 | - public function(DateTimeInterface $dt) use ($hasTime, $isUtc) { |
|
294 | - |
|
295 | - if ($hasTime) { |
|
296 | - return $dt->format('Y-m-d\\TH:i:s') . ($isUtc ? 'Z' : ''); |
|
297 | - } else { |
|
298 | - return $dt->format('Y-m-d'); |
|
299 | - } |
|
300 | - |
|
301 | - }, |
|
302 | - $dts |
|
303 | - ); |
|
304 | - |
|
305 | - } |
|
306 | - |
|
307 | - /** |
|
308 | - * Sets the json value, as it would appear in a jCard or jCal object. |
|
309 | - * |
|
310 | - * The value must always be an array. |
|
311 | - * |
|
312 | - * @param array $value |
|
313 | - * |
|
314 | - * @return void |
|
315 | - */ |
|
316 | - public function setJsonValue(array $value) { |
|
317 | - |
|
318 | - // dates and times in jCal have one difference to dates and times in |
|
319 | - // iCalendar. In jCal date-parts are separated by dashes, and |
|
320 | - // time-parts are separated by colons. It makes sense to just remove |
|
321 | - // those. |
|
322 | - $this->setValue( |
|
323 | - array_map( |
|
324 | - public function($item) { |
|
325 | - |
|
326 | - return strtr($item, [':' => '', '-' => '']); |
|
327 | - |
|
328 | - }, |
|
329 | - $value |
|
330 | - ) |
|
331 | - ); |
|
332 | - |
|
333 | - } |
|
334 | - |
|
335 | - /** |
|
336 | - * We need to intercept offsetSet, because it may be used to alter the |
|
337 | - * VALUE from DATE-TIME to DATE or vice-versa. |
|
338 | - * |
|
339 | - * @param string $name |
|
340 | - * @param mixed $value |
|
341 | - * |
|
342 | - * @return void |
|
343 | - */ |
|
344 | - public function offsetSet($name, $value) { |
|
345 | - |
|
346 | - parent::offsetSet($name, $value); |
|
347 | - if (strtoupper($name) !== 'VALUE') { |
|
348 | - return; |
|
349 | - } |
|
350 | - |
|
351 | - // This will ensure that dates are correctly encoded. |
|
352 | - $this->setDateTimes($this->getDateTimes()); |
|
353 | - |
|
354 | - } |
|
355 | - |
|
356 | - /** |
|
357 | - * Validates the node for correctness. |
|
358 | - * |
|
359 | - * The following options are supported: |
|
360 | - * Node::REPAIR - May attempt to automatically repair the problem. |
|
361 | - * |
|
362 | - * This method returns an array with detected problems. |
|
363 | - * Every element has the following properties: |
|
364 | - * |
|
365 | - * * level - problem level. |
|
366 | - * * message - A human-readable string describing the issue. |
|
367 | - * * node - A reference to the problematic node. |
|
368 | - * |
|
369 | - * The level means: |
|
370 | - * 1 - The issue was repaired (only happens if REPAIR was turned on) |
|
371 | - * 2 - An inconsequential issue |
|
372 | - * 3 - A severe issue. |
|
373 | - * |
|
374 | - * @param int $options |
|
375 | - * |
|
376 | - * @return array |
|
377 | - */ |
|
378 | - public function validate($options = 0) { |
|
379 | - |
|
380 | - $messages = parent::validate($options); |
|
381 | - $valueType = $this->getValueType(); |
|
382 | - $values = $this->getParts(); |
|
383 | - try { |
|
384 | - foreach ($values as $value) { |
|
385 | - switch ($valueType) { |
|
386 | - case 'DATE' : |
|
387 | - DateTimeParser::parseDate($value); |
|
388 | - break; |
|
389 | - case 'DATE-TIME' : |
|
390 | - DateTimeParser::parseDateTime($value); |
|
391 | - break; |
|
392 | - } |
|
393 | - } |
|
394 | - } catch (InvalidDataException $e) { |
|
395 | - $messages[] = [ |
|
396 | - 'level' => 3, |
|
397 | - 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, |
|
398 | - 'node' => $this, |
|
399 | - ]; |
|
400 | - } |
|
401 | - return $messages; |
|
402 | - |
|
403 | - } |
|
29 | + /** |
|
30 | + * In case this is a multi-value property. This string will be used as a |
|
31 | + * delimiter. |
|
32 | + * |
|
33 | + * @var string|null |
|
34 | + */ |
|
35 | + public $delimiter = ','; |
|
36 | + |
|
37 | + /** |
|
38 | + * Sets a multi-valued property. |
|
39 | + * |
|
40 | + * You may also specify DateTime objects here. |
|
41 | + * |
|
42 | + * @param array $parts |
|
43 | + * |
|
44 | + * @return void |
|
45 | + */ |
|
46 | + public function setParts(array $parts) { |
|
47 | + |
|
48 | + if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) { |
|
49 | + $this->setDateTimes($parts); |
|
50 | + } else { |
|
51 | + parent::setParts($parts); |
|
52 | + } |
|
53 | + |
|
54 | + } |
|
55 | + |
|
56 | + /** |
|
57 | + * Updates the current value. |
|
58 | + * |
|
59 | + * This may be either a single, or multiple strings in an array. |
|
60 | + * |
|
61 | + * Instead of strings, you may also use DateTime here. |
|
62 | + * |
|
63 | + * @param string|array|DateTimeInterface $value |
|
64 | + * |
|
65 | + * @return void |
|
66 | + */ |
|
67 | + public function setValue($value) { |
|
68 | + |
|
69 | + if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) { |
|
70 | + $this->setDateTimes($value); |
|
71 | + } elseif ($value instanceof DateTimeInterface) { |
|
72 | + $this->setDateTimes([$value]); |
|
73 | + } else { |
|
74 | + parent::setValue($value); |
|
75 | + } |
|
76 | + |
|
77 | + } |
|
78 | + |
|
79 | + /** |
|
80 | + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. |
|
81 | + * |
|
82 | + * This has been 'unfolded', so only 1 line will be passed. Unescaping is |
|
83 | + * not yet done, but parameters are not included. |
|
84 | + * |
|
85 | + * @param string $val |
|
86 | + * |
|
87 | + * @return void |
|
88 | + */ |
|
89 | + public function setRawMimeDirValue($val) { |
|
90 | + |
|
91 | + $this->setValue(explode($this->delimiter, $val)); |
|
92 | + |
|
93 | + } |
|
94 | + |
|
95 | + /** |
|
96 | + * Returns a raw mime-dir representation of the value. |
|
97 | + * |
|
98 | + * @return string |
|
99 | + */ |
|
100 | + public function getRawMimeDirValue() { |
|
101 | + |
|
102 | + return implode($this->delimiter, $this->getParts()); |
|
103 | + |
|
104 | + } |
|
105 | + |
|
106 | + /** |
|
107 | + * Returns true if this is a DATE-TIME value, false if it's a DATE. |
|
108 | + * |
|
109 | + * @return bool |
|
110 | + */ |
|
111 | + public function hasTime() { |
|
112 | + |
|
113 | + return strtoupper((string)$this['VALUE']) !== 'DATE'; |
|
114 | + |
|
115 | + } |
|
116 | + |
|
117 | + /** |
|
118 | + * Returns true if this is a floating DATE or DATE-TIME. |
|
119 | + * |
|
120 | + * Note that DATE is always floating. |
|
121 | + */ |
|
122 | + public function isFloating() { |
|
123 | + |
|
124 | + return |
|
125 | + !$this->hasTime() || |
|
126 | + ( |
|
127 | + !isset($this['TZID']) && |
|
128 | + strpos($this->getValue(), 'Z') === false |
|
129 | + ); |
|
130 | + |
|
131 | + } |
|
132 | + |
|
133 | + /** |
|
134 | + * Returns a date-time value. |
|
135 | + * |
|
136 | + * Note that if this property contained more than 1 date-time, only the |
|
137 | + * first will be returned. To get an array with multiple values, call |
|
138 | + * getDateTimes. |
|
139 | + * |
|
140 | + * If no timezone information is known, because it's either an all-day |
|
141 | + * property or floating time, we will use the DateTimeZone argument to |
|
142 | + * figure out the exact date. |
|
143 | + * |
|
144 | + * @param DateTimeZone $timeZone |
|
145 | + * |
|
146 | + * @return DateTimeImmutable |
|
147 | + */ |
|
148 | + public function getDateTime(DateTimeZone $timeZone = null) { |
|
149 | + |
|
150 | + $dt = $this->getDateTimes($timeZone); |
|
151 | + if (!$dt) return; |
|
152 | + |
|
153 | + return $dt[0]; |
|
154 | + |
|
155 | + } |
|
156 | + |
|
157 | + /** |
|
158 | + * Returns multiple date-time values. |
|
159 | + * |
|
160 | + * If no timezone information is known, because it's either an all-day |
|
161 | + * property or floating time, we will use the DateTimeZone argument to |
|
162 | + * figure out the exact date. |
|
163 | + * |
|
164 | + * @param DateTimeZone $timeZone |
|
165 | + * |
|
166 | + * @return DateTimeImmutable[] |
|
167 | + * @return \DateTime[] |
|
168 | + */ |
|
169 | + public function getDateTimes(DateTimeZone $timeZone = null) { |
|
170 | + |
|
171 | + // Does the property have a TZID? |
|
172 | + $tzid = $this['TZID']; |
|
173 | + |
|
174 | + if ($tzid) { |
|
175 | + $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); |
|
176 | + } |
|
177 | + |
|
178 | + $dts = []; |
|
179 | + foreach ($this->getParts() as $part) { |
|
180 | + $dts[] = DateTimeParser::parse($part, $timeZone); |
|
181 | + } |
|
182 | + return $dts; |
|
183 | + |
|
184 | + } |
|
185 | + |
|
186 | + /** |
|
187 | + * Sets the property as a DateTime object. |
|
188 | + * |
|
189 | + * @param DateTimeInterface $dt |
|
190 | + * @param bool isFloating If set to true, timezones will be ignored. |
|
191 | + * |
|
192 | + * @return void |
|
193 | + */ |
|
194 | + public function setDateTime(DateTimeInterface $dt, $isFloating = false) { |
|
195 | + |
|
196 | + $this->setDateTimes([$dt], $isFloating); |
|
197 | + |
|
198 | + } |
|
199 | + |
|
200 | + /** |
|
201 | + * Sets the property as multiple date-time objects. |
|
202 | + * |
|
203 | + * The first value will be used as a reference for the timezones, and all |
|
204 | + * the otehr values will be adjusted for that timezone |
|
205 | + * |
|
206 | + * @param DateTimeInterface[] $dt |
|
207 | + * @param bool isFloating If set to true, timezones will be ignored. |
|
208 | + * |
|
209 | + * @return void |
|
210 | + */ |
|
211 | + public function setDateTimes(array $dt, $isFloating = false) { |
|
212 | + |
|
213 | + $values = []; |
|
214 | + |
|
215 | + if ($this->hasTime()) { |
|
216 | + |
|
217 | + $tz = null; |
|
218 | + $isUtc = false; |
|
219 | + |
|
220 | + foreach ($dt as $d) { |
|
221 | + |
|
222 | + if ($isFloating) { |
|
223 | + $values[] = $d->format('Ymd\\THis'); |
|
224 | + continue; |
|
225 | + } |
|
226 | + if (is_null($tz)) { |
|
227 | + $tz = $d->getTimeZone(); |
|
228 | + $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']); |
|
229 | + if (!$isUtc) { |
|
230 | + $this->offsetSet('TZID', $tz->getName()); |
|
231 | + } |
|
232 | + } else { |
|
233 | + $d = $d->setTimeZone($tz); |
|
234 | + } |
|
235 | + |
|
236 | + if ($isUtc) { |
|
237 | + $values[] = $d->format('Ymd\\THis\\Z'); |
|
238 | + } else { |
|
239 | + $values[] = $d->format('Ymd\\THis'); |
|
240 | + } |
|
241 | + |
|
242 | + } |
|
243 | + if ($isUtc || $isFloating) { |
|
244 | + $this->offsetUnset('TZID'); |
|
245 | + } |
|
246 | + |
|
247 | + } else { |
|
248 | + |
|
249 | + foreach ($dt as $d) { |
|
250 | + |
|
251 | + $values[] = $d->format('Ymd'); |
|
252 | + |
|
253 | + } |
|
254 | + $this->offsetUnset('TZID'); |
|
255 | + |
|
256 | + } |
|
257 | + |
|
258 | + $this->value = $values; |
|
259 | + |
|
260 | + } |
|
261 | + |
|
262 | + /** |
|
263 | + * Returns the type of value. |
|
264 | + * |
|
265 | + * This corresponds to the VALUE= parameter. Every property also has a |
|
266 | + * 'default' valueType. |
|
267 | + * |
|
268 | + * @return string |
|
269 | + */ |
|
270 | + public function getValueType() { |
|
271 | + |
|
272 | + return $this->hasTime() ? 'DATE-TIME' : 'DATE'; |
|
273 | + |
|
274 | + } |
|
275 | + |
|
276 | + /** |
|
277 | + * Returns the value, in the format it should be encoded for JSON. |
|
278 | + * |
|
279 | + * This method must always return an array. |
|
280 | + * |
|
281 | + * @return array |
|
282 | + */ |
|
283 | + public function getJsonValue() { |
|
284 | + |
|
285 | + $dts = $this->getDateTimes(); |
|
286 | + $hasTime = $this->hasTime(); |
|
287 | + $isFloating = $this->isFloating(); |
|
288 | + |
|
289 | + $tz = $dts[0]->getTimeZone(); |
|
290 | + $isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']); |
|
291 | + |
|
292 | + return array_map( |
|
293 | + public function(DateTimeInterface $dt) use ($hasTime, $isUtc) { |
|
294 | + |
|
295 | + if ($hasTime) { |
|
296 | + return $dt->format('Y-m-d\\TH:i:s') . ($isUtc ? 'Z' : ''); |
|
297 | + } else { |
|
298 | + return $dt->format('Y-m-d'); |
|
299 | + } |
|
300 | + |
|
301 | + }, |
|
302 | + $dts |
|
303 | + ); |
|
304 | + |
|
305 | + } |
|
306 | + |
|
307 | + /** |
|
308 | + * Sets the json value, as it would appear in a jCard or jCal object. |
|
309 | + * |
|
310 | + * The value must always be an array. |
|
311 | + * |
|
312 | + * @param array $value |
|
313 | + * |
|
314 | + * @return void |
|
315 | + */ |
|
316 | + public function setJsonValue(array $value) { |
|
317 | + |
|
318 | + // dates and times in jCal have one difference to dates and times in |
|
319 | + // iCalendar. In jCal date-parts are separated by dashes, and |
|
320 | + // time-parts are separated by colons. It makes sense to just remove |
|
321 | + // those. |
|
322 | + $this->setValue( |
|
323 | + array_map( |
|
324 | + public function($item) { |
|
325 | + |
|
326 | + return strtr($item, [':' => '', '-' => '']); |
|
327 | + |
|
328 | + }, |
|
329 | + $value |
|
330 | + ) |
|
331 | + ); |
|
332 | + |
|
333 | + } |
|
334 | + |
|
335 | + /** |
|
336 | + * We need to intercept offsetSet, because it may be used to alter the |
|
337 | + * VALUE from DATE-TIME to DATE or vice-versa. |
|
338 | + * |
|
339 | + * @param string $name |
|
340 | + * @param mixed $value |
|
341 | + * |
|
342 | + * @return void |
|
343 | + */ |
|
344 | + public function offsetSet($name, $value) { |
|
345 | + |
|
346 | + parent::offsetSet($name, $value); |
|
347 | + if (strtoupper($name) !== 'VALUE') { |
|
348 | + return; |
|
349 | + } |
|
350 | + |
|
351 | + // This will ensure that dates are correctly encoded. |
|
352 | + $this->setDateTimes($this->getDateTimes()); |
|
353 | + |
|
354 | + } |
|
355 | + |
|
356 | + /** |
|
357 | + * Validates the node for correctness. |
|
358 | + * |
|
359 | + * The following options are supported: |
|
360 | + * Node::REPAIR - May attempt to automatically repair the problem. |
|
361 | + * |
|
362 | + * This method returns an array with detected problems. |
|
363 | + * Every element has the following properties: |
|
364 | + * |
|
365 | + * * level - problem level. |
|
366 | + * * message - A human-readable string describing the issue. |
|
367 | + * * node - A reference to the problematic node. |
|
368 | + * |
|
369 | + * The level means: |
|
370 | + * 1 - The issue was repaired (only happens if REPAIR was turned on) |
|
371 | + * 2 - An inconsequential issue |
|
372 | + * 3 - A severe issue. |
|
373 | + * |
|
374 | + * @param int $options |
|
375 | + * |
|
376 | + * @return array |
|
377 | + */ |
|
378 | + public function validate($options = 0) { |
|
379 | + |
|
380 | + $messages = parent::validate($options); |
|
381 | + $valueType = $this->getValueType(); |
|
382 | + $values = $this->getParts(); |
|
383 | + try { |
|
384 | + foreach ($values as $value) { |
|
385 | + switch ($valueType) { |
|
386 | + case 'DATE' : |
|
387 | + DateTimeParser::parseDate($value); |
|
388 | + break; |
|
389 | + case 'DATE-TIME' : |
|
390 | + DateTimeParser::parseDateTime($value); |
|
391 | + break; |
|
392 | + } |
|
393 | + } |
|
394 | + } catch (InvalidDataException $e) { |
|
395 | + $messages[] = [ |
|
396 | + 'level' => 3, |
|
397 | + 'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType, |
|
398 | + 'node' => $this, |
|
399 | + ]; |
|
400 | + } |
|
401 | + return $messages; |
|
402 | + |
|
403 | + } |
|
404 | 404 | } |
@@ -14,31 +14,31 @@ |
||
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 | - |
|
28 | - } |
|
29 | - |
|
30 | - /** |
|
31 | - * Returns the type of value. |
|
32 | - * |
|
33 | - * This corresponds to the VALUE= parameter. Every property also has a |
|
34 | - * 'default' valueType. |
|
35 | - * |
|
36 | - * @return string |
|
37 | - */ |
|
38 | - public function getValueType() { |
|
39 | - |
|
40 | - return 'UNKNOWN'; |
|
41 | - |
|
42 | - } |
|
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 | + } |
|
29 | + |
|
30 | + /** |
|
31 | + * Returns the type of value. |
|
32 | + * |
|
33 | + * This corresponds to the VALUE= parameter. Every property also has a |
|
34 | + * 'default' valueType. |
|
35 | + * |
|
36 | + * @return string |
|
37 | + */ |
|
38 | + public function getValueType() { |
|
39 | + |
|
40 | + return 'UNKNOWN'; |
|
41 | + |
|
42 | + } |
|
43 | 43 | |
44 | 44 | } |