mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-01-27 00:33:34 +00:00
Fix bugs in expansion of events with overridden instances
This commit is contained in:
parent
6a3619aaad
commit
ffa06343a3
@ -1227,6 +1227,26 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
|
||||
$is_date = false;
|
||||
$has_repeats = false;
|
||||
$dtstart_type = 'DTSTART';
|
||||
|
||||
$components_prefix = [];
|
||||
$components_base_events = [];
|
||||
$components_override_events = [];
|
||||
|
||||
foreach ($components AS $k => $comp) {
|
||||
if ( $comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL' ) {
|
||||
// Other types of component (such as VTIMEZONE) go first
|
||||
$components_prefix[] = $comp;
|
||||
} else if ($comp->GetProperty('RECURRENCE-ID') === null) {
|
||||
// This is the base event, we need to handle it first
|
||||
$components_base_events[] = $comp;
|
||||
} else {
|
||||
// This is an override of an event instance, handle it last
|
||||
$components_override_events[] = $comp;
|
||||
}
|
||||
}
|
||||
|
||||
$components = array_merge($components_prefix, $components_base_events, $components_override_events);
|
||||
|
||||
foreach( $components AS $k => $comp ) {
|
||||
if ( $comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL' ) {
|
||||
continue;
|
||||
@ -1371,36 +1391,43 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
|
||||
$p = $comp->GetProperty('RECURRENCE-ID');
|
||||
if ( isset($p) && $p->Value() != '') {
|
||||
$recurrence_id = $p->Value();
|
||||
if ( !isset($new_components[$recurrence_id]) ) {
|
||||
// The component we're replacing is outside the range. Unless the replacement
|
||||
// is *in* the range we will move along to the next one.
|
||||
$dtstart_prop = $comp->GetProperty($dtstart_type);
|
||||
if ( !isset($dtstart_prop) ) continue; // No start: no expansion. Note that we consider 'DUE' to be a start if DTSTART is missing
|
||||
$dtstart = new RepeatRuleDateTime( $dtstart_prop );
|
||||
$is_date = $dtstart->isDate();
|
||||
if ( $return_floating_times ) $dtstart->setAsFloat();
|
||||
$dtstart = $dtstart->FloatOrUTC($return_floating_times);
|
||||
if ( $dtstart > $end_utc ) continue; // Start after end of range, skip it
|
||||
|
||||
$end_type = ($comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND');
|
||||
$duration = $comp->GetProperty('DURATION');
|
||||
if ( !isset($duration) || $duration->Value() == '' ) {
|
||||
$instance_end = $comp->GetProperty($end_type);
|
||||
if ( isset($instance_end) ) {
|
||||
$dtend = new RepeatRuleDateTime( $instance_end );
|
||||
if ( $return_floating_times ) $dtend->setAsFloat();
|
||||
$dtend = $dtend->FloatOrUTC($return_floating_times);
|
||||
}
|
||||
else {
|
||||
$dtend = $dtstart + ($is_date ? $dtstart + 86400 : 0 );
|
||||
}
|
||||
|
||||
$dtstart_prop = $comp->GetProperty('DTSTART');
|
||||
if ( !isset($dtstart_prop) && $comp->GetType() != 'VTODO' ) {
|
||||
$dtstart_prop = $comp->GetProperty('DUE');
|
||||
}
|
||||
|
||||
if ( !isset($new_components[$recurrence_id]) && !isset($dtstart_prop) ) continue; // No start: no expansion. Note that we consider 'DUE' to be a start if DTSTART is missing
|
||||
$dtstart_rrdt = new RepeatRuleDateTime( $dtstart_prop );
|
||||
$is_date = $dtstart_rrdt->isDate();
|
||||
if ( $return_floating_times ) $dtstart_rrdt->setAsFloat();
|
||||
$dtstart = $dtstart_rrdt->FloatOrUTC($return_floating_times);
|
||||
if ( !isset($new_components[$recurrence_id]) && $dtstart > $end_utc ) continue; // Start after end of range, skip it
|
||||
|
||||
$end_type = ($comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND');
|
||||
$duration = $comp->GetProperty('DURATION');
|
||||
|
||||
if ( !isset($duration) || $duration->Value() == '' ) {
|
||||
$instance_end = $comp->GetProperty($end_type);
|
||||
if ( isset($instance_end) ) {
|
||||
$dtend_rrdt = new RepeatRuleDateTime( $instance_end );
|
||||
if ( $return_floating_times ) $dtend_rrdt->setAsFloat();
|
||||
$dtend = $dtend_rrdt->FloatOrUTC($return_floating_times);
|
||||
|
||||
$comp->AddProperty('DURATION', Rfc5545Duration::fromTwoDates($dtstart_rrdt, $dtend_rrdt) );
|
||||
}
|
||||
else {
|
||||
$duration = new Rfc5545Duration($duration->Value());
|
||||
$dtend = $dtstart + $duration->asSeconds();
|
||||
$dtend = $dtstart + ($is_date ? $dtstart + 86400 : 0 );
|
||||
}
|
||||
if ( $dtend < $start_utc ) continue; // End before start of range: skip that too.
|
||||
}
|
||||
else {
|
||||
$duration = new Rfc5545Duration($duration->Value());
|
||||
$dtend = $dtstart + $duration->asSeconds();
|
||||
}
|
||||
|
||||
if ( !isset($new_components[$recurrence_id]) && $dtend < $start_utc ) continue; // End before start of range: skip that too.
|
||||
|
||||
if ( DEBUG_RRULE ) printf( "Replacing overridden instance at %s\n", $recurrence_id);
|
||||
$new_components[$recurrence_id] = $comp;
|
||||
}
|
||||
|
||||
153
testing/phpunit/ExpansionTest.php
Normal file
153
testing/phpunit/ExpansionTest.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
set_include_path(get_include_path() . PATH_SEPARATOR . '/usr/share/awl/inc' . PATH_SEPARATOR . 'inc');
|
||||
|
||||
require_once('RRule.php');
|
||||
require_once('vCalendar.php');
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
// 1PM-2PM Monday-Thursday (only for one week), NZ time
|
||||
$base_cal = new vCalendar("BEGIN:VCALENDAR
|
||||
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
CREATED:20190117T001216Z
|
||||
LAST-MODIFIED:20190117T001233Z
|
||||
DTSTAMP:20190117T001233Z
|
||||
UID:dae6404d-1ce0-42d0-af3b-0d303034197b
|
||||
SUMMARY:New Event
|
||||
RRULE:FREQ=DAILY;UNTIL=20190124T000000Z
|
||||
DTSTART;TZID=Pacific/Auckland:20190121T130000
|
||||
DTEND;TZID=Pacific/Auckland:20190121T140000
|
||||
TRANSP:OPAQUE
|
||||
END:VEVENT
|
||||
END:VCALENDAR");
|
||||
|
||||
$tuesday_renamed_cal = new vCalendar("
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
CREATED:20190117T001216Z
|
||||
LAST-MODIFIED:20190117T001805Z
|
||||
DTSTAMP:20190117T001805Z
|
||||
UID:d0d2df67-df7c-4b07-b729-221af3681c09
|
||||
SUMMARY:New Event
|
||||
RRULE:FREQ=DAILY;UNTIL=20190124T000000Z
|
||||
DTSTART;TZID=Pacific/Auckland:20190121T130000
|
||||
DTEND;TZID=Pacific/Auckland:20190121T140000
|
||||
TRANSP:OPAQUE
|
||||
X-MOZ-GENERATION:1
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CREATED:20190117T001741Z
|
||||
LAST-MODIFIED:20190117T001805Z
|
||||
DTSTAMP:20190117T001805Z
|
||||
UID:d0d2df67-df7c-4b07-b729-221af3681c09
|
||||
SUMMARY:Tuesday has been renamed
|
||||
RECURRENCE-ID;TZID=Pacific/Auckland:20190122T130000
|
||||
DTSTART;TZID=Pacific/Auckland:20190122T130000
|
||||
DTEND;TZID=Pacific/Auckland:20190122T140000
|
||||
TRANSP:OPAQUE
|
||||
X-MOZ-GENERATION:1
|
||||
SEQUENCE:1
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
");
|
||||
|
||||
$tuesday_renamed_cal_order_swapped = new vCalendar("
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
CREATED:20190117T001741Z
|
||||
LAST-MODIFIED:20190117T001805Z
|
||||
DTSTAMP:20190117T001805Z
|
||||
UID:d0d2df67-df7c-4b07-b729-221af3681c09
|
||||
SUMMARY:Tuesday has been renamed
|
||||
RECURRENCE-ID;TZID=Pacific/Auckland:20190122T130000
|
||||
DTSTART;TZID=Pacific/Auckland:20190122T130000
|
||||
DTEND;TZID=Pacific/Auckland:20190122T140000
|
||||
TRANSP:OPAQUE
|
||||
X-MOZ-GENERATION:1
|
||||
SEQUENCE:1
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
CREATED:20190117T001216Z
|
||||
LAST-MODIFIED:20190117T001805Z
|
||||
DTSTAMP:20190117T001805Z
|
||||
UID:d0d2df67-df7c-4b07-b729-221af3681c09
|
||||
SUMMARY:New Event
|
||||
RRULE:FREQ=DAILY;UNTIL=20190124T000000Z
|
||||
DTSTART;TZID=Pacific/Auckland:20190121T130000
|
||||
DTEND;TZID=Pacific/Auckland:20190121T140000
|
||||
TRANSP:OPAQUE
|
||||
X-MOZ-GENERATION:1
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
");
|
||||
|
||||
/**
|
||||
* A simplified model of get_freebusy, which works off of a passed-in vCalendar
|
||||
* rather than making SQL queries
|
||||
*/
|
||||
function get_freebusyish(vCalendar $cal) {
|
||||
$expansion = expand_event_instances($cal)->GetComponents(['VEVENT' => true]);
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($expansion as $k => $instance) {
|
||||
// The same logic used in freebusy-functions (apart from default timezone
|
||||
// handling, which isn't really under test here)
|
||||
|
||||
$start_date = new RepeatRuleDateTime($instance->GetProperty('DTSTART'));
|
||||
|
||||
$duration = $instance->GetProperty('DURATION');
|
||||
$duration = (!isset($duration) ? 'P1D' : $duration->Value());
|
||||
|
||||
$end_date = clone($start_date);
|
||||
$end_date->modify($duration);
|
||||
|
||||
array_push($result, $start_date->UTC() .'/'. $end_date->UTC());
|
||||
}
|
||||
sort($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
final class ExpansionTest extends TestCase
|
||||
{
|
||||
const expected_freebusyish_for_base = [
|
||||
'20190121T000000Z/20190121T010000Z',
|
||||
'20190122T000000Z/20190122T010000Z',
|
||||
'20190123T000000Z/20190123T010000Z',
|
||||
'20190124T000000Z/20190124T010000Z',
|
||||
];
|
||||
|
||||
public function testUnmodifiedCal() {
|
||||
global $base_cal;
|
||||
|
||||
self::assertEquals(
|
||||
self::expected_freebusyish_for_base,
|
||||
get_freebusyish($base_cal)
|
||||
);
|
||||
}
|
||||
|
||||
public function testTueRenamed() {
|
||||
global $tuesday_renamed_cal;
|
||||
|
||||
self::assertEquals(
|
||||
self::expected_freebusyish_for_base,
|
||||
get_freebusyish($tuesday_renamed_cal)
|
||||
);
|
||||
}
|
||||
|
||||
public function testTueRenamedSwapped() {
|
||||
global $tuesday_renamed_cal_order_swapped;
|
||||
|
||||
self::assertEquals(
|
||||
self::expected_freebusyish_for_base,
|
||||
get_freebusyish($tuesday_renamed_cal_order_swapped)
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user