Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117883114
libcalendaring_recurrence.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None
libcalendaring_recurrence.php
View Options
<?php
use
Sabre\VObject\Recur\EventIterator
;
/**
* Recurrence computation class for shared use.
*
* Uitility class to compute recurrence dates from the given rules.
*
* @author Aleksander Machniak <machniak@apheleia-it.ch
*
* Copyright (C) 2012-2022, Apheleia IT AG <contact@apheleia-it.ch>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class
libcalendaring_recurrence
{
protected
$lib
;
protected
$start
;
protected
$engine
;
protected
$recurrence
;
protected
$dateonly
=
false
;
protected
$event
;
protected
$duration
;
/**
* Default constructor
*
* @param libcalendaring $lib The libcalendaring plugin instance
* @param array $event The event object to operate on
*/
function
__construct
(
$lib
,
$event
=
null
)
{
$this
->
lib
=
$lib
;
$this
->
event
=
$event
;
if
(!
empty
(
$event
))
{
if
(!
empty
(
$event
[
'start'
])
&&
is_object
(
$event
[
'start'
])
&&
!
empty
(
$event
[
'end'
])
&&
is_object
(
$event
[
'end'
])
)
{
$this
->
duration
=
$event
[
'start'
]->
diff
(
$event
[
'end'
]);
}
$this
->
init
(
$event
[
'recurrence'
],
$event
[
'start'
]);
}
}
/**
* Initialize recurrence engine
*
* @param array The recurrence properties
* @param DateTime The recurrence start date
*/
public
function
init
(
$recurrence
,
$start
)
{
$this
->
start
=
$start
;
$this
->
dateonly
=
!
empty
(
$start
->
_dateonly
)
||
!
empty
(
$this
->
event
[
'allday'
]);
$this
->
recurrence
=
$recurrence
;
$event
=
[
'uid'
=>
'1'
,
'allday'
=>
$this
->
dateonly
,
'recurrence'
=>
$recurrence
,
'start'
=>
$start
,
// TODO: END/DURATION ???
// TODO: moved occurrences ???
];
$vcalendar
=
new
libcalendaring_vcalendar
(
$this
->
lib
->
timezone
);
$ve
=
$vcalendar
->
toSabreComponent
(
$event
);
$this
->
engine
=
new
EventIterator
(
$ve
,
null
,
$this
->
lib
->
timezone
);
}
/**
* Get date/time of the next occurence of this event, and push the iterator.
*
* @return DateTime|false object or False if recurrence ended
*/
public
function
next_start
()
{
try
{
$this
->
engine
->
next
();
$current
=
$this
->
engine
->
getDtStart
();
}
catch
(
Exception
$e
)
{
// do nothing
}
return
$current
?
$this
->
toDateTime
(
$current
)
:
false
;
}
/**
* Get the next recurring instance of this event
*
* @return array|false Array with event properties or False if recurrence ended
*/
public
function
next_instance
()
{
if
(
$next_start
=
$this
->
next_start
())
{
$next
=
$this
->
event
;
$next
[
'start'
]
=
$next_start
;
if
(
$this
->
duration
)
{
$next
[
'end'
]
=
clone
$next_start
;
$next
[
'end'
]->
add
(
$this
->
duration
);
}
$next
[
'recurrence_date'
]
=
clone
$next_start
;
$next
[
'_instance'
]
=
libcalendaring
::
recurrence_instance_identifier
(
$next
,
!
empty
(
$this
->
event
[
'allday'
]));
unset
(
$next
[
'_formatobj'
]);
return
$next
;
}
return
false
;
}
/**
* Get the date of the end of the last occurrence of this recurrence cycle
*
* @return DateTime|false End datetime of the last occurrence or False if there's no end date
*/
public
function
end
()
{
// recurrence end date is given
if
(
isset
(
$this
->
recurrence
[
'UNTIL'
])
&&
$this
->
recurrence
[
'UNTIL'
]
instanceof
DateTimeInterface
)
{
return
$this
->
recurrence
[
'UNTIL'
];
}
// Run through all items till we reach the end, or limit of iterations
// Note: Sabre has a limits of iteration in VObject\Settings, so it is not an infinite loop
try
{
foreach
(
$this
->
engine
as
$end
)
{
// do nothing
}
}
catch
(
Exception
$e
)
{
// do nothing
}
/*
if (empty($end) && isset($this->event['start']) && $this->event['start'] instanceof DateTimeInterface) {
// determine a reasonable end date if none given
$end = clone $this->event['start'];
$end->add(new DateInterval('P100Y'));
}
*/
return
isset
(
$end
)
?
$this
->
toDateTime
(
$end
)
:
false
;
}
/**
* Find date/time of the first occurrence (excluding start date)
*
* @return DateTime|null First occurrence
*/
public
function
first_occurrence
()
{
$start
=
clone
$this
->
start
;
$interval
=
$this
->
recurrence
[
'INTERVAL'
]
??
1
;
$freq
=
$this
->
recurrence
[
'FREQ'
]
??
null
;
switch
(
$freq
)
{
case
'WEEKLY'
:
if
(
empty
(
$this
->
recurrence
[
'BYDAY'
]))
{
return
$start
;
}
$start
->
sub
(
new
DateInterval
(
"P{$interval}W"
));
break
;
case
'MONTHLY'
:
if
(
empty
(
$this
->
recurrence
[
'BYDAY'
])
&&
empty
(
$this
->
recurrence
[
'BYMONTHDAY'
]))
{
return
$start
;
}
$start
->
sub
(
new
DateInterval
(
"P{$interval}M"
));
break
;
case
'YEARLY'
:
if
(
empty
(
$this
->
recurrence
[
'BYDAY'
])
&&
empty
(
$this
->
recurrence
[
'BYMONTH'
]))
{
return
$start
;
}
$start
->
sub
(
new
DateInterval
(
"P{$interval}Y"
));
break
;
case
'DAILY'
:
if
(!
empty
(
$this
->
recurrence
[
'BYMONTH'
]))
{
break
;
}
default
:
return
$start
;
}
$recurrence
=
$this
->
recurrence
;
if
(!
empty
(
$recurrence
[
'COUNT'
]))
{
// Increase count so we do not stop the loop to early
$recurrence
[
'COUNT'
]
+=
100
;
}
// Create recurrence that starts in the past
$self
=
new
self
(
$this
->
lib
);
$self
->
init
(
$recurrence
,
$start
);
// TODO: This method does not work the same way as the kolab_date_recurrence based on
// kolabcalendaring. I.e. if an event start date does not match the recurrence rule
// it will be returned, kolab_date_recurrence will return the next occurrence in such a case
// which is the intended result of this function.
// See some commented out test cases in tests/RecurrenceTest.php
// find the first occurrence
$found
=
false
;
while
(
$next
=
$self
->
next_start
())
{
$start
=
$next
;
if
(
$next
>=
$this
->
start
)
{
$found
=
true
;
break
;
}
}
if
(!
$found
)
{
rcube
::
raise_error
(
[
'file'
=>
__FILE__
,
'line'
=>
__LINE__
,
'message'
=>
sprintf
(
"Failed to find a first occurrence. Start: %s, Recurrence: %s"
,
$this
->
start
->
format
(
DateTime
::
ISO8601
),
json_encode
(
$recurrence
)),
],
true
);
return
null
;
}
return
$this
->
toDateTime
(
$start
);
}
/**
* Convert any DateTime into libcalendaring_datetime
*/
protected
function
toDateTime
(
$date
,
$useStart
=
true
)
{
if
(
$date
instanceof
DateTimeInterface
)
{
$date
=
libcalendaring_datetime
::
createFromFormat
(
'Y-m-d
\\
TH:i:s'
,
$date
->
format
(
'Y-m-d
\\
TH:i:s'
),
// Sabre will loose timezone on all-day events, use the event start's timezone
$this
->
start
->
getTimezone
()
);
}
$date
->
_dateonly
=
$this
->
dateonly
;
if
(
$useStart
&&
$this
->
dateonly
)
{
// Sabre sets time to 00:00:00 for all-day events,
// let's copy the time from the event's start
$date
->
setTime
((
int
)
$this
->
start
->
format
(
'H'
),
(
int
)
$this
->
start
->
format
(
'i'
),
(
int
)
$this
->
start
->
format
(
's'
));
}
return
$date
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Mon, Apr 6, 12:55 AM (5 d, 6 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
d1/41/0dba2dfcf0303ffcfa30d8ce8673
Default Alt Text
libcalendaring_recurrence.php (8 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline