Support reminders relative to end/due date (RELATED=END) (T656)

Conflicts:

	plugins/libcalendaring/libvcalendar.php
This commit is contained in:
Aleksander Machniak 2015-08-07 06:43:59 -04:00
parent dc0b4bd39a
commit 326e4ad9ad
9 changed files with 109 additions and 44 deletions

View file

@ -337,9 +337,10 @@ function rcube_libcalendaring(settings)
$(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
});
$(prefix+' select.edit-alarm-offset').change(function(){
var val = $(this).val();
$(this).parent().find('.edit-alarm-date, .edit-alarm-time')[val == '@' ? 'show' : 'hide']();
$(this).parent().find('.edit-alarm-value').prop('disabled', val === '@' || val === '0');
var val = $(this).val(), parent = $(this).parent();
parent.find('.edit-alarm-date, .edit-alarm-time')[val == '@' ? 'show' : 'hide']();
parent.find('.edit-alarm-value').prop('disabled', val === '@' || val === '0');
parent.find('.edit-alarm-related')[val == '@' ? 'hide' : 'show']();
});
$(prefix+' .edit-alarm-date').removeClass('hasDatepicker').removeAttr('id').datepicker(datepicker_settings);
@ -389,6 +390,7 @@ function rcube_libcalendaring(settings)
}
$('select.edit-alarm-type', domnode).val(alarm.action);
$('select.edit-alarm-related', domnode).val(/END/i.test(alarm.related) ? 'end' : 'start');
if (String(alarm.trigger).match(/@(\d+)/)) {
var ondate = this.fromunixtime(parseInt(RegExp.$1));
@ -417,7 +419,11 @@ function rcube_libcalendaring(settings)
var valarms = [];
$(prefix + ' .edit-alarm-item').each(function(i, elem) {
var val, offset, alarm = { action: $('select.edit-alarm-type', elem).val() };
var val, offset, alarm = {
action: $('select.edit-alarm-type', elem).val(),
related: $('select.edit-alarm-related', elem).val()
};
if (alarm.action) {
offset = $('select.edit-alarm-offset', elem).val();
if (offset == '@') {

View file

@ -328,8 +328,10 @@ class libcalendaring extends rcube_plugin
$input_value = new html_inputfield(array('name' => 'alarmvalue[]', 'class' => 'edit-alarm-value', 'size' => 3));
$input_date = new html_inputfield(array('name' => 'alarmdate[]', 'class' => 'edit-alarm-date', 'size' => 10));
$input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
$select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type', 'id' => $attrib['id']));
$select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
$select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type', 'id' => $attrib['id']));
$select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
$select_related = new html_select(array('name' => 'alarmrelated[]', 'class' => 'edit-alarm-related'));
$object_type = $attrib['_type'] ?: 'event';
$select_type->add($this->gettext('none'), '');
foreach ($alarm_types as $type)
@ -342,6 +344,9 @@ class libcalendaring extends rcube_plugin
if ($absolute_time)
$select_offset->add($this->gettext('trigger@'), '@');
$select_related->add($this->gettext('relatedstart'), 'start');
$select_related->add($this->gettext('relatedend' . $object_type), 'end');
// pre-set with default values from user settings
$preset = self::parse_alarm_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
$hidden = array('style' => 'display:none');
@ -350,6 +355,7 @@ class libcalendaring extends rcube_plugin
html::span(array('class' => 'edit-alarm-values', 'style' => 'display:none'),
$input_value->show($preset[0]) . ' ' .
$select_offset->show($preset[1]) . ' ' .
$select_related->show() . ' ' .
$input_date->show('', $hidden) . ' ' .
$input_time->show('', $hidden)
)
@ -527,10 +533,11 @@ class libcalendaring extends rcube_plugin
}
else {
$trigger = $alarm['trigger'];
$action = $alarm['action'];
$action = $alarm['action'];
$related = $alarm['related'];
}
$text = '';
$text = '';
$rcube = rcube::get_instance();
switch ($action) {
@ -558,11 +565,15 @@ class libcalendaring extends rcube_plugin
));
}
else if ($val = self::parse_alarm_value($trigger)) {
$r = strtoupper($related ?: 'start') == 'END' ? 'end' : '';
// TODO: for all-day events say 'on date of event at XX' ?
if ($val[0] == 0)
$text .= ' ' . $rcube->gettext('libcalendaring.triggerattime');
else
$text .= ' ' . intval($val[0]) . ' ' . $rcube->gettext('libcalendaring.trigger' . $val[1]);
if ($val[0] == 0) {
$text .= ' ' . $rcube->gettext('libcalendaring.triggerattime' . $r);
}
else {
$label = 'libcalendaring.trigger' . $r . $val[1];
$text .= ' ' . intval($val[0]) . ' ' . $rcube->gettext($label);
}
}
else {
return false;
@ -601,7 +612,7 @@ class libcalendaring extends rcube_plugin
}
}
$expires = new DateTime('now - 12 hours');
$expires = new DateTime('now - 12 hours');
$alarm_id = $rec['id']; // alarm ID eq. record ID by default to keep backwards compatibility
// handle multiple alarms
@ -613,8 +624,7 @@ class libcalendaring extends rcube_plugin
$notify_time = $alarm['trigger'];
}
else if (is_string($alarm['trigger'])) {
// $refdate = $alarm['trigger'][0] == '+' ? $rec['end'] : $rec['start'];
$refdate = $rec['start'];
$refdate = $alarm['related'] == 'END' ? $rec['end'] : $rec['start'];
// abort if no reference date is available to compute notification time
if (!is_a($refdate, 'DateTime'))
@ -624,7 +634,7 @@ class libcalendaring extends rcube_plugin
try {
$interval = new DateInterval(trim($alarm['trigger'], '+-'));
$interval->invert = $alarm['trigger'][0] != '+';
$interval->invert = $alarm['trigger'][0] == '-';
$notify_time = clone $refdate;
$notify_time->add($interval);
}

View file

@ -611,18 +611,23 @@ class libvcalendar implements Iterator
// find alarms
foreach ($ve->select('VALARM') as $valarm) {
$action = 'DISPLAY';
$action = 'DISPLAY';
$trigger = null;
$alarm = array();
$alarm = array();
foreach ($valarm->children as $prop) {
$value = strval($prop);
switch ($prop->name) {
case 'TRIGGER':
if ($prop['VALUE'] == 'DATE-TIME') {
$trigger = '@' . $prop->getDateTime()->format('U');
$alarm['trigger'] = $prop->getDateTime();
foreach ($prop->parameters as $param) {
if ($param->name == 'VALUE' && $param->value == 'DATE-TIME') {
$trigger = '@' . $prop->getDateTime()->format('U');
$alarm['trigger'] = $prop->getDateTime();
}
else if ($param->name == 'RELATED') {
$alarm['related'] = $param->value;
}
}
if (!$trigger && ($values = libcalendaring::parse_alarm_value($value))) {
$trigger = $values[2];
@ -1111,7 +1116,11 @@ class libvcalendar implements Iterator
$va->add($this->datetime_prop($cal, 'TRIGGER', $alarm['trigger'], true, null, true));
}
else {
$va->add('TRIGGER', $alarm['trigger']);
$alarm_props = array();
if (strtoupper($alarm['related']) == 'END') {
$alarm_props['RELATED'] = 'END';
}
$va->add('TRIGGER', $alarm['trigger'], $alarm_props);
}
if ($alarm['action'] == 'EMAIL') {
@ -1388,5 +1397,4 @@ class vobject_location_property extends VObject\Property\Text
// iCalendar
'REQUEST-STATUS',
);
}

View file

@ -29,8 +29,18 @@ $labels['trigger-D'] = 'days before';
$labels['trigger+M'] = 'minutes after';
$labels['trigger+H'] = 'hours after';
$labels['trigger+D'] = 'days after';
$labels['triggerend-M'] = 'minutes before end';
$labels['triggerend-H'] = 'hours before end';
$labels['triggerend-D'] = 'days before end';
$labels['triggerend+M'] = 'minutes after end';
$labels['triggerend+H'] = 'hours after end';
$labels['triggerend+D'] = 'days after end';
$labels['trigger0'] = 'on time';
$labels['triggerattime'] = 'at time';
$labels['triggerattime'] = 'at start time';
$labels['triggerattimeend'] = 'at end time';
$labels['relatedstart'] = 'start';
$labels['relatedendevent'] = 'end';
$labels['relatedendtask'] = 'due time';
$labels['addalarm'] = 'Add alarm';
$labels['removealarm'] = 'Remove alarm';

View file

@ -64,7 +64,7 @@ class libcalendaring_test extends PHPUnit_Framework_TestCase
$date = date('Ymd', strtotime('today + 2 days'));
$event = array(
'start' => new DateTime($date . 'T160000Z'),
'end' => new DateTime($date . 'T180000Z'),
'end' => new DateTime($date . 'T200000Z'),
'valarms' => array(
array(
'trigger' => '-PT10M',
@ -76,7 +76,7 @@ class libcalendaring_test extends PHPUnit_Framework_TestCase
$this->assertEquals($event['valarms'][0]['action'], $alarm['action']);
$this->assertEquals(strtotime($date . 'T155000Z'), $alarm['time']);
// alarm 1 hour after before event
// alarm 1 hour after event start
$event['valarms'] = array(
array(
'trigger' => '+PT1H',
@ -84,8 +84,30 @@ class libcalendaring_test extends PHPUnit_Framework_TestCase
);
$alarm = libcalendaring::get_next_alarm($event);
$this->assertEquals('DISPLAY', $alarm['action']);
$this->assertEquals(strtotime($date . 'T170000Z'), $alarm['time']);
// alarm 1 hour before event end
$event['valarms'] = array(
array(
'trigger' => '-PT1H',
'related' => 'END',
),
);
$alarm = libcalendaring::get_next_alarm($event);
$this->assertEquals('DISPLAY', $alarm['action']);
$this->assertEquals(strtotime($date . 'T190000Z'), $alarm['time']);
// alarm 1 hour after event end
$event['valarms'] = array(
array(
'trigger' => 'PT1H',
'related' => 'END',
),
);
$alarm = libcalendaring::get_next_alarm($event);
$this->assertEquals('DISPLAY', $alarm['action']);
$this->assertEquals(strtotime($date . 'T210000Z'), $alarm['time']);
// ignore past alarms
$event['start'] = new DateTime('today 22:00:00');
$event['end'] = new DateTime('today 23:00:00');
@ -159,6 +181,4 @@ class libcalendaring_test extends PHPUnit_Framework_TestCase
$this->assertRegExp('/BYDAY='.$rrule['BYDAY'].'/', $s, "Recurrence BYDAY");
$this->assertRegExp('/UNTIL=20250501T160000Z/', $s, "Recurrence End date (in UTC)");
}
}

View file

@ -203,6 +203,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals('DISPLAY', $event['valarms'][0]['action'], "Full alarm item (action)");
$this->assertEquals('-PT12H', $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
$this->assertEquals('END', $event['valarms'][0]['related'], "Full alarm item (related)");
// alarm trigger with 0 values
$events = $ical->import_from_file(__DIR__ . '/resources/alarms.ics', 'UTF-8');
@ -216,6 +217,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals('-PT30M', $alarm[3], "Unified alarm string (stripped zero-values)");
$this->assertEquals('DISPLAY', $event['valarms'][0]['action'], "First alarm action");
$this->assertEquals('', $event['valarms'][0]['related'], "First alarm related property");
$this->assertEquals('This is the first event reminder', $event['valarms'][0]['description'], "First alarm text");
$this->assertEquals(3, count($event['valarms']), "List all VALARM blocks");
@ -354,7 +356,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertEquals(100, $completed['complete'], "Task percent complete value");
$ics = $ical->export(array($completed));
$this->assertRegExp('/COMPLETED:[0-9TZ]+/', $ics, "Export COMPLETED property");
$this->assertRegExp('/COMPLETED(;VALUE=DATE-TIME)?:[0-9TZ]+/', $ics, "Export COMPLETED property");
}
/**
@ -410,7 +412,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
$this->assertRegExp('/EXDATE.*:20131218/', $ics, "Export Recurrence EXDATE");
$this->assertContains('BEGIN:VALARM', $ics, "Export VALARM");
$this->assertContains('TRIGGER:-PT12H', $ics, "Export Alarm trigger");
$this->assertContains('TRIGGER;RELATED=END:-PT12H', $ics, "Export Alarm trigger");
$this->assertRegExp('/ATTACH.*;VALUE=BINARY/', $ics, "Embed attachment");
$this->assertRegExp('/ATTACH.*;ENCODING=BASE64/', $ics, "Attachment B64 encoding");

View file

@ -36,7 +36,7 @@ RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20140718T215959Z;BYDAY=3WE
EXDATE;TZID=Europe/Zurich:20131218T080000
EXDATE;TZID=Europe/Zurich:20140415T080000
BEGIN:VALARM
TRIGGER:-PT12H
TRIGGER;RELATED=END:-PT12H
ACTION:DISPLAY
END:VALARM
END:VEVENT

View file

@ -231,12 +231,12 @@ abstract class kolab_format_xcal extends kolab_format
$object['valarms'] = array();
for ($i=0; $i < $valarms->size(); $i++) {
$alarm = $valarms->get($i);
$type = $alarm_types[$alarm->type()];
$type = $alarm_types[$alarm->type()];
if ($type == 'DISPLAY' || $type == 'EMAIL' || $type == 'AUDIO') { // only some alarms are supported
$valarm = array(
'action' => $type,
'summary' => $alarm->summary(),
'action' => $type,
'summary' => $alarm->summary(),
'description' => $alarm->description(),
);
@ -254,12 +254,14 @@ abstract class kolab_format_xcal extends kolab_format
}
if ($start = self::php_datetime($alarm->start())) {
$object['alarms'] = '@' . $start->format('U');
$object['alarms'] = '@' . $start->format('U');
$valarm['trigger'] = $start;
}
else if ($offset = $alarm->relativeStart()) {
$prefix = $alarm->relativeTo() == kolabformat::End ? '+' : '-';
$value = $time = '';
$prefix = $offset->isNegative() ? '-' : '+';
$value = '';
$time = '';
if ($w = $offset->weeks()) $value .= $w . 'W';
else if ($d = $offset->days()) $value .= $d . 'D';
else if ($h = $offset->hours()) $time .= $h . 'H';
@ -269,23 +271,29 @@ abstract class kolab_format_xcal extends kolab_format
// assume 'at event time'
if (empty($value) && empty($time)) {
$prefix = '';
$time = '0S';
$time = '0S';
}
$object['alarms'] = $prefix . $value . $time;
$object['alarms'] = $prefix . $value . $time;
$valarm['trigger'] = $prefix . 'P' . $value . ($time ? 'T' . $time : '');
if ($alarm->relativeTo() == kolabformat::End) {
$valarm['related'] == 'END';
}
}
// read alarm duration and repeat properties
if (($duration = $alarm->duration()) && $duration->isValid()) {
$value = $time = '';
if ($w = $duration->weeks()) $value .= $w . 'W';
else if ($d = $duration->days()) $value .= $d . 'D';
else if ($h = $duration->hours()) $time .= $h . 'H';
else if ($m = $duration->minutes()) $time .= $m . 'M';
else if ($s = $duration->seconds()) $time .= $s . 'S';
$valarm['duration'] = 'P' . $value . ($time ? 'T' . $time : '');
$valarm['repeat'] = $alarm->numrepeat();
$valarm['repeat'] = $alarm->numrepeat();
}
$object['alarms'] .= ':' . $type; // legacy property
@ -508,9 +516,8 @@ abstract class kolab_format_xcal extends kolab_format
}
else {
try {
$prefix = $valarm['trigger'][0];
$period = new DateInterval(preg_replace('/[^0-9PTWDHMS]/', '', $valarm['trigger']));
$duration = new Duration($period->d, $period->h, $period->i, $period->s, $prefix == '-');
$period = new DateInterval(preg_replace('/[^0-9PTWDHMS]/', '', $valarm['trigger']));
$duration = new Duration($period->d, $period->h, $period->i, $period->s, $valarm['trigger'][0] == '-');
}
catch (Exception $e) {
// skip alarm with invalid trigger values
@ -518,7 +525,8 @@ abstract class kolab_format_xcal extends kolab_format
continue;
}
$alarm->setRelativeStart($duration, $prefix == '-' ? kolabformat::Start : kolabformat::End);
$related = strtoupper($valarm['related']) == 'END' ? kolabformat::End : kolabformat::Start;
$alarm->setRelativeStart($duration, $related);
}
if ($valarm['duration']) {

View file

@ -355,6 +355,7 @@ class tasklist_ui
*/
function alarm_select($attrib = array())
{
$attrib['_type'] = 'task';
return $this->plugin->lib->alarm_select($attrib, $this->plugin->driver->alarm_types, $this->plugin->driver->alarm_absolute);
}