Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1820433
EventIterator.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Size
13 KB
Subscribers
None
EventIterator.php
View Options
<?php
namespace
Sabre\VObject\Recur
;
use
InvalidArgumentException
;
use
DateTime
;
use
DateTimeZone
;
use
Sabre\VObject\Component
;
use
Sabre\VObject\Component\VEvent
;
/**
* This class is used to determine new for a recurring event, when the next
* events occur.
*
* This iterator may loop infinitely in the future, therefore it is important
* that if you use this class, you set hard limits for the amount of iterations
* you want to handle.
*
* Note that currently there is not full support for the entire iCalendar
* specification, as it's very complex and contains a lot of permutations
* that's not yet used very often in software.
*
* For the focus has been on features as they actually appear in Calendaring
* software, but this may well get expanded as needed / on demand
*
* The following RRULE properties are supported
* * UNTIL
* * INTERVAL
* * COUNT
* * FREQ=DAILY
* * BYDAY
* * BYHOUR
* * BYMONTH
* * FREQ=WEEKLY
* * BYDAY
* * BYHOUR
* * WKST
* * FREQ=MONTHLY
* * BYMONTHDAY
* * BYDAY
* * BYSETPOS
* * FREQ=YEARLY
* * BYMONTH
* * BYMONTHDAY (only if BYMONTH is also set)
* * BYDAY (only if BYMONTH is also set)
*
* Anything beyond this is 'undefined', which means that it may get ignored, or
* you may get unexpected results. The effect is that in some applications the
* specified recurrence may look incorrect, or is missing.
*
* The recurrence iterator also does not yet support THISANDFUTURE.
*
* @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
* @author Evert Pot (http://evertpot.com/)
* @license http://sabre.io/license/ Modified BSD License
*/
class
EventIterator
implements
\Iterator
{
/**
* Reference timeZone for floating dates and times.
*
* @var DateTimeZone
*/
protected
$timeZone
;
/**
* True if we're iterating an all-day event.
*
* @var bool
*/
protected
$allDay
=
false
;
/**
* Creates the iterator
*
* There's three ways to set up the iterator.
*
* 1. You can pass a VCALENDAR component and a UID.
* 2. You can pass an array of VEVENTs (all UIDS should match).
* 3. You can pass a single VEVENT component.
*
* Only the second method is recomended. The other 1 and 3 will be removed
* at some point in the future.
*
* The $uid parameter is only required for the first method.
*
* @param Component|array $input
* @param string|null $uid
* @param DateTimeZone $timeZone Reference timezone for floating dates and
* times.
*/
public
function
__construct
(
$input
,
$uid
=
null
,
DateTimeZone
$timeZone
=
null
)
{
if
(
is_null
(
$this
->
timeZone
))
{
$timeZone
=
new
DateTimeZone
(
'UTC'
);
}
$this
->
timeZone
=
$timeZone
;
if
(
is_array
(
$input
))
{
$events
=
$input
;
}
elseif
(
$input
instanceof
VEvent
)
{
// Single instance mode.
$events
=
array
(
$input
);
}
else
{
// Calendar + UID mode.
$uid
=
(
string
)
$uid
;
if
(!
$uid
)
{
throw
new
InvalidArgumentException
(
'The UID argument is required when a VCALENDAR is passed to this constructor'
);
}
if
(!
isset
(
$input
->
VEVENT
))
{
throw
new
InvalidArgumentException
(
'No events found in this calendar'
);
}
$events
=
$input
->
getByUID
(
$uid
);
}
foreach
(
$events
as
$vevent
)
{
if
(!
isset
(
$vevent
->{
'RECURRENCE-ID'
}))
{
$this
->
masterEvent
=
$vevent
;
}
else
{
$this
->
exceptions
[
$vevent
->{
'RECURRENCE-ID'
}->
getDateTime
(
$this
->
timeZone
)->
getTimeStamp
()
]
=
true
;
$this
->
overriddenEvents
[]
=
$vevent
;
}
}
if
(!
$this
->
masterEvent
)
{
// No base event was found. CalDAV does allow cases where only
// overridden instances are stored.
//
// In this particular case, we're just going to grab the first
// event and use that instead. This may not always give the
// desired result.
if
(!
count
(
$this
->
overriddenEvents
))
{
throw
new
InvalidArgumentException
(
'This VCALENDAR did not have an event with UID: '
.
$uid
);
}
$this
->
masterEvent
=
array_shift
(
$this
->
overriddenEvents
);
}
$this
->
startDate
=
$this
->
masterEvent
->
DTSTART
->
getDateTime
(
$this
->
timeZone
);
$this
->
allDay
=
!
$this
->
masterEvent
->
DTSTART
->
hasTime
();
if
(
isset
(
$this
->
masterEvent
->
EXDATE
))
{
foreach
(
$this
->
masterEvent
->
EXDATE
as
$exDate
)
{
foreach
(
$exDate
->
getDateTimes
(
$this
->
timeZone
)
as
$dt
)
{
$this
->
exceptions
[
$dt
->
getTimeStamp
()]
=
true
;
}
}
}
if
(
isset
(
$this
->
masterEvent
->
DTEND
))
{
$this
->
eventDuration
=
$this
->
masterEvent
->
DTEND
->
getDateTime
(
$this
->
timeZone
)->
getTimeStamp
()
-
$this
->
startDate
->
getTimeStamp
();
}
elseif
(
isset
(
$this
->
masterEvent
->
DURATION
))
{
$duration
=
$this
->
masterEvent
->
DURATION
->
getDateInterval
();
$end
=
clone
$this
->
startDate
;
$end
->
add
(
$duration
);
$this
->
eventDuration
=
$end
->
getTimeStamp
()
-
$this
->
startDate
->
getTimeStamp
();
}
elseif
(
$this
->
allDay
)
{
$this
->
eventDuration
=
3600
*
24
;
}
else
{
$this
->
eventDuration
=
0
;
}
if
(
isset
(
$this
->
masterEvent
->
RDATE
))
{
$this
->
recurIterator
=
new
RDateIterator
(
$this
->
masterEvent
->
RDATE
->
getParts
(),
$this
->
startDate
);
}
elseif
(
isset
(
$this
->
masterEvent
->
RRULE
))
{
$this
->
recurIterator
=
new
RRuleIterator
(
$this
->
masterEvent
->
RRULE
->
getParts
(),
$this
->
startDate
);
}
else
{
$this
->
recurIterator
=
new
RRuleIterator
(
array
(
'FREQ'
=>
'DAILY'
,
'COUNT'
=>
1
,
),
$this
->
startDate
);
}
$this
->
rewind
();
if
(!
$this
->
valid
())
{
throw
new
NoInstancesException
(
'This recurrence rule does not generate any valid instances'
);
}
}
/**
* Returns the date for the current position of the iterator.
*
* @return DateTime
*/
public
function
current
()
{
if
(
$this
->
currentDate
)
{
return
clone
$this
->
currentDate
;
}
}
/**
* This method returns the start date for the current iteration of the
* event.
*
* @return DateTime
*/
public
function
getDtStart
()
{
if
(
$this
->
currentDate
)
{
return
clone
$this
->
currentDate
;
}
}
/**
* This method returns the end date for the current iteration of the
* event.
*
* @return DateTime
*/
public
function
getDtEnd
()
{
if
(!
$this
->
valid
())
{
return
null
;
}
$end
=
clone
$this
->
currentDate
;
$end
->
modify
(
'+'
.
$this
->
eventDuration
.
' seconds'
);
return
$end
;
}
/**
* Returns a VEVENT for the current iterations of the event.
*
* This VEVENT will have a recurrence id, and it's DTSTART and DTEND
* altered.
*
* @return VEvent
*/
public
function
getEventObject
()
{
if
(
$this
->
currentOverriddenEvent
)
{
return
$this
->
currentOverriddenEvent
;
}
$event
=
clone
$this
->
masterEvent
;
// Ignoring the following block, because PHPUnit's code coverage
// ignores most of these lines, and this messes with our stats.
//
// @codeCoverageIgnoreStart
unset
(
$event
->
RRULE
,
$event
->
EXDATE
,
$event
->
RDATE
,
$event
->
EXRULE
,
$event
->{
'RECURRENCE-ID'
}
);
// @codeCoverageIgnoreEnd
$event
->
DTSTART
->
setDateTime
(
$this
->
getDtStart
(),
$event
->
DTSTART
->
isFloating
());
if
(
isset
(
$event
->
DTEND
))
{
$event
->
DTEND
->
setDateTime
(
$this
->
getDtEnd
(),
$event
->
DTEND
->
isFloating
());
}
// Including a RECURRENCE-ID to the object, unless this is the first
// object.
//
// The inner recurIterator is always one step ahead, this is why we're
// checking for the key being higher than 1.
if
(
$this
->
recurIterator
->
key
()
>
1
)
{
$recurid
=
clone
$event
->
DTSTART
;
$recurid
->
name
=
'RECURRENCE-ID'
;
$event
->
add
(
$recurid
);
}
return
$event
;
}
/**
* Returns the current position of the iterator.
*
* This is for us simply a 0-based index.
*
* @return int
*/
public
function
key
()
{
// The counter is always 1 ahead.
return
$this
->
counter
-
1
;
}
/**
* This is called after next, to see if the iterator is still at a valid
* position, or if it's at the end.
*
* @return bool
*/
public
function
valid
()
{
return
!!
$this
->
currentDate
;
}
/**
* Sets the iterator back to the starting point.
*/
public
function
rewind
()
{
$this
->
recurIterator
->
rewind
();
// re-creating overridden event index.
$index
=
array
();
foreach
(
$this
->
overriddenEvents
as
$key
=>
$event
)
{
$stamp
=
$event
->
DTSTART
->
getDateTime
(
$this
->
timeZone
)->
getTimeStamp
();
$index
[
$stamp
]
=
$key
;
}
krsort
(
$index
);
$this
->
counter
=
0
;
$this
->
overriddenEventsIndex
=
$index
;
$this
->
currentOverriddenEvent
=
null
;
$this
->
nextDate
=
null
;
$this
->
currentDate
=
clone
$this
->
startDate
;
$this
->
next
();
}
/**
* Advances the iterator with one step.
*
* @return void
*/
public
function
next
()
{
$this
->
currentOverriddenEvent
=
null
;
$this
->
counter
++;
if
(
$this
->
nextDate
)
{
// We had a stored value.
$nextDate
=
$this
->
nextDate
;
$this
->
nextDate
=
null
;
}
else
{
// We need to ask rruleparser for the next date.
// We need to do this until we find a date that's not in the
// exception list.
do
{
if
(!
$this
->
recurIterator
->
valid
())
{
$nextDate
=
null
;
break
;
}
$nextDate
=
$this
->
recurIterator
->
current
();
$this
->
recurIterator
->
next
();
}
while
(
isset
(
$this
->
exceptions
[
$nextDate
->
getTimeStamp
()]));
}
// $nextDate now contains what rrule thinks is the next one, but an
// overridden event may cut ahead.
if
(
$this
->
overriddenEventsIndex
)
{
$offset
=
end
(
$this
->
overriddenEventsIndex
);
$timestamp
=
key
(
$this
->
overriddenEventsIndex
);
if
(!
$nextDate
||
$timestamp
<
$nextDate
->
getTimeStamp
())
{
// Overridden event comes first.
$this
->
currentOverriddenEvent
=
$this
->
overriddenEvents
[
$offset
];
// Putting the rrule next date aside.
$this
->
nextDate
=
$nextDate
;
$this
->
currentDate
=
$this
->
currentOverriddenEvent
->
DTSTART
->
getDateTime
(
$this
->
timeZone
);
// Ensuring that this item will only be used once.
array_pop
(
$this
->
overriddenEventsIndex
);
// Exit point!
return
;
}
}
$this
->
currentDate
=
$nextDate
;
}
/**
* Quickly jump to a date in the future.
*
* @param DateTime $dateTime
*/
public
function
fastForward
(
DateTime
$dateTime
)
{
while
(
$this
->
valid
()
&&
$this
->
getDtEnd
()
<
$dateTime
)
{
$this
->
next
();
}
}
/**
* Returns true if this recurring event never ends.
*
* @return bool
*/
public
function
isInfinite
()
{
return
$this
->
recurIterator
->
isInfinite
();
}
/**
* RRULE parser
*
* @var RRuleIterator
*/
protected
$recurIterator
;
/**
* The duration, in seconds, of the master event.
*
* We use this to calculate the DTEND for subsequent events.
*/
protected
$eventDuration
;
/**
* A reference to the main (master) event.
*
* @var VEVENT
*/
protected
$masterEvent
;
/**
* List of overridden events.
*
* @var array
*/
protected
$overriddenEvents
=
array
();
/**
* Overridden event index.
*
* Key is timestamp, value is the index of the item in the $overriddenEvent
* property.
*
* @var array
*/
protected
$overriddenEventsIndex
;
/**
* A list of recurrence-id's that are either part of EXDATE, or are
* overridden.
*
* @var array
*/
protected
$exceptions
=
array
();
/**
* Internal event counter
*
* @var int
*/
protected
$counter
;
/**
* The very start of the iteration process.
*
* @var DateTime
*/
protected
$startDate
;
/**
* Where we are currently in the iteration process
*
* @var DateTime
*/
protected
$currentDate
;
/**
* The next date from the rrule parser.
*
* Sometimes we need to temporary store the next date, because an
* overridden event came before.
*
* @var DateTime
*/
protected
$nextDate
;
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sun, Dec 22, 3:07 AM (5 d, 8 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
915813
Default Alt Text
EventIterator.php (13 KB)
Attached To
rDAVCAL DokuWiki DAVCal PlugIn
Event Timeline
Log In to Comment