Add full support for arbitrary recurrence dates (RDATE) to the calendar UI
This commit is contained in:
parent
3ebc2dc548
commit
9375eadbe2
15 changed files with 222 additions and 14 deletions
|
@ -1392,6 +1392,14 @@ class calendar extends rcube_plugin
|
|||
if ($event['recurrence']['UNTIL'])
|
||||
$event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'], $event['allday'])->format('c');
|
||||
unset($event['recurrence']['EXCEPTIONS']);
|
||||
|
||||
// format RDATE values
|
||||
if (is_array($event['recurrence']['RDATE'])) {
|
||||
$libcal = $this->lib;
|
||||
$event['recurrence']['RDATE'] = array_map(function($rdate) use ($libcal) {
|
||||
return $libcal->adjust_timezone($rdate, true)->format('c');
|
||||
}, $event['recurrence']['RDATE']);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$event['attachments'] as $k => $attachment) {
|
||||
|
@ -1425,6 +1433,7 @@ class calendar extends rcube_plugin
|
|||
'end' => $this->lib->adjust_timezone($event['end'], $event['allday'])->format('c'),
|
||||
// 'changed' might be empty for event recurrences (Bug #2185)
|
||||
'changed' => $event['changed'] ? $this->lib->adjust_timezone($event['changed'])->format('c') : null,
|
||||
'created' => $event['created'] ? $this->lib->adjust_timezone($event['created'])->format('c') : null,
|
||||
'title' => strval($event['title']),
|
||||
'description' => strval($event['description']),
|
||||
'location' => strval($event['location']),
|
||||
|
@ -1443,16 +1452,27 @@ class calendar extends rcube_plugin
|
|||
if (empty($rrule['FREQ']) && !empty($rrule['RDATE'])) {
|
||||
$first = $rrule['RDATE'][0];
|
||||
$second = $rrule['RDATE'][1];
|
||||
$third = $rrule['RDATE'][2];
|
||||
if (is_a($first, 'DateTime') && is_a($second, 'DateTime')) {
|
||||
$diff = $first->diff($second);
|
||||
foreach (array('y' => 'YEARLY', 'm' => 'MONTHLY', 'd' => 'DAILY') as $k => $freq) {
|
||||
if ($diff->$k != 0) {
|
||||
$rrule['FREQ'] = $freq;
|
||||
$rrule['INTERVAL'] = $diff->$k;
|
||||
|
||||
// verify interval with next item
|
||||
if (is_a($third, 'DateTime')) {
|
||||
$diff2 = $second->diff($third);
|
||||
if ($diff2->$k != $diff->$k) {
|
||||
unset($rrule['INTERVAL']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$rrule['INTERVAL'])
|
||||
$rrule['FREQ'] = 'RDATE';
|
||||
$rrule['UNTIL'] = end($rrule['RDATE']);
|
||||
}
|
||||
|
||||
|
@ -1607,6 +1627,21 @@ class calendar extends rcube_plugin
|
|||
if (is_array($event['recurrence']) && !empty($event['recurrence']['UNTIL']))
|
||||
$event['recurrence']['UNTIL'] = new DateTime($event['recurrence']['UNTIL'], $this->timezone);
|
||||
|
||||
if (is_array($event['recurrence']) && is_array($event['recurrence']['RDATE'])) {
|
||||
$tz = $this->timezone;
|
||||
$start = $event['start'];
|
||||
$event['recurrence']['RDATE'] = array_map(function($rdate) use ($tz, $start) {
|
||||
try {
|
||||
$dt = new DateTime($rdate, $tz);
|
||||
$dt->setTime($start->format('G'), $start->format('i'));
|
||||
return $dt;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}, $event['recurrence']['RDATE']);
|
||||
}
|
||||
|
||||
$attachments = array();
|
||||
$eventid = 'cal:'.$event['id'];
|
||||
if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $eventid) {
|
||||
|
|
|
@ -514,11 +514,12 @@ function rcube_calendar_ui(settings)
|
|||
var recurrence, interval, rrtimes, rrenddate;
|
||||
var load_recurrence_tab = function()
|
||||
{
|
||||
recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
|
||||
recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ || (event.recurrence.RDATE ? 'RDATE' : '') : '').change();
|
||||
interval = $('#eventedit select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1);
|
||||
rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1);
|
||||
rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(parseISO8601(event.recurrence.UNTIL), settings['date_format']) : '');
|
||||
$('#eventedit input.edit-recurrence-until:checked').prop('checked', false);
|
||||
$('#edit-recurrence-rdates').html('');
|
||||
|
||||
var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
|
||||
var rrepeat_id = '#edit-recurrence-repeat-forever';
|
||||
|
@ -551,6 +552,11 @@ function rcube_calendar_ui(settings)
|
|||
else if (event.start) {
|
||||
$('input.edit-recurrence-yearly-bymonth').val([String(event.start.getMonth()+1)]);
|
||||
}
|
||||
if (event.recurrence && event.recurrence.RDATE) {
|
||||
$.each(event.recurrence.RDATE, function(i,rdate){
|
||||
add_rdate(parseISO8601(rdate));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// show warning if editing a recurring event
|
||||
|
@ -715,6 +721,16 @@ function rcube_calendar_ui(settings)
|
|||
if ((byday = $('#edit-recurrence-yearly-byday').val()))
|
||||
data.recurrence.BYDAY = $('#edit-recurrence-yearly-prefix').val() + byday;
|
||||
}
|
||||
else if (freq == 'RDATE') {
|
||||
data.recurrence = { RDATE:[] };
|
||||
// take selected but not yet added date into account
|
||||
if ($('#edit-recurrence-rdate-input').val() != '') {
|
||||
$('#recurrence-form-rdate input.button.add').click();
|
||||
}
|
||||
$('#edit-recurrence-rdates li').each(function(i, li){
|
||||
data.recurrence.RDATE.push($(li).attr('data-value'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
data.calendar = calendars.val();
|
||||
|
@ -1567,6 +1583,34 @@ function rcube_calendar_ui(settings)
|
|||
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
|
||||
rcmail.http_post('event', { action:'rsvp', e:me.selected_event, status:response });
|
||||
}
|
||||
};
|
||||
|
||||
// add the given date to the RDATE list
|
||||
var add_rdate = function(date)
|
||||
{
|
||||
var li = $('<li>')
|
||||
.attr('data-value', date2servertime(date))
|
||||
.html('<span>' + Q($.fullCalendar.formatDate(date, settings['date_format'])) + '</span>')
|
||||
.appendTo('#edit-recurrence-rdates');
|
||||
|
||||
$('<a>').attr('href', '#del')
|
||||
.addClass('iconbutton delete')
|
||||
.html(rcmail.get_label('delete', 'calendar'))
|
||||
.attr('title', rcmail.get_label('delete', 'calendar'))
|
||||
.appendTo(li);
|
||||
};
|
||||
|
||||
// re-sort the list items by their 'data-value' attribute
|
||||
var sort_rdates = function()
|
||||
{
|
||||
var mylist = $('#edit-recurrence-rdates'),
|
||||
listitems = mylist.children('li').get();
|
||||
listitems.sort(function(a, b) {
|
||||
var compA = $(a).attr('data-value');
|
||||
var compB = $(b).attr('data-value');
|
||||
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
|
||||
})
|
||||
$.each(listitems, function(idx, item) { mylist.append(item); });
|
||||
}
|
||||
|
||||
// post the given event data to server
|
||||
|
@ -2809,13 +2853,31 @@ function rcube_calendar_ui(settings)
|
|||
$('#edit-recurrence-frequency').change(function(e){
|
||||
var freq = $(this).val().toLowerCase();
|
||||
$('.recurrence-form').hide();
|
||||
if (freq)
|
||||
$('#recurrence-form-'+freq+', #recurrence-form-until').show();
|
||||
if (freq) {
|
||||
$('#recurrence-form-'+freq).show();
|
||||
if (freq != 'rdate')
|
||||
$('#recurrence-form-until').show();
|
||||
}
|
||||
});
|
||||
$('#recurrence-form-rdate input.button.add').click(function(e){
|
||||
var dt, dv = $('#edit-recurrence-rdate-input').val();
|
||||
if (dv && (dt = parse_datetime('12:00', dv))) {
|
||||
add_rdate(dt);
|
||||
sort_rdates();
|
||||
$('#edit-recurrence-rdate-input').val('')
|
||||
}
|
||||
else {
|
||||
$('#edit-recurrence-rdate-input').select();
|
||||
}
|
||||
});
|
||||
$('#edit-recurrence-rdates').on('click', 'a.delete', function(e){
|
||||
$(this).closest('li').remove();
|
||||
return false;
|
||||
});
|
||||
$('#edit-recurrence-enddate').datepicker(datepicker_settings).click(function(){ $("#edit-recurrence-repeat-until").prop('checked', true) });
|
||||
$('#edit-recurrence-repeat-times').change(function(e){ $('#edit-recurrence-repeat-count').prop('checked', true); });
|
||||
|
||||
$('#event-export-startdate').datepicker(datepicker_settings);
|
||||
$('#edit-recurrence-rdate-input, #event-export-startdate').datepicker(datepicker_settings);
|
||||
|
||||
// init attendees autocompletion
|
||||
var ac_props;
|
||||
|
|
|
@ -846,6 +846,8 @@ class database_driver extends calendar_driver
|
|||
$rr[2] = intval($rr[2]);
|
||||
else if ($rr[1] == 'UNTIL')
|
||||
$rr[2] = date_create($rr[2]);
|
||||
else if ($rr[1] == 'RDATE')
|
||||
$rr[2] = array_map('date_create', explode(',', $rr[2]));
|
||||
else if ($rr[1] == 'EXDATE')
|
||||
$rr[2] = array_map('date_create', explode(',', $rr[2]));
|
||||
$event['recurrence'][$rr[1]] = $rr[2];
|
||||
|
|
|
@ -596,7 +596,7 @@ class kolab_calendar
|
|||
unset($record['recurrence']);
|
||||
|
||||
// remove internals
|
||||
unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments']);
|
||||
unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments'], $record['x-custom']);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
@ -647,9 +647,9 @@ class kolab_calendar
|
|||
|
||||
$event['_owner'] = $identity['email'];
|
||||
|
||||
# copy RDATE values as the UI doesn't yet support these
|
||||
if (empty($event['recurrence']['FREQ']) && $old['recurrence']['RDATE'] && empty($old['recurrence']['FREQ'])) {
|
||||
$event['recurrence']['RDATE'] = $old['recurrence']['RDATE'];
|
||||
# remove EXDATE values if RDATE is given
|
||||
if (!empty($event['recurrence']['RDATE'])) {
|
||||
$event['recurrence']['EXDATE'] = array();
|
||||
}
|
||||
|
||||
// remove some internal properties which should not be saved
|
||||
|
|
|
@ -500,6 +500,15 @@ class kolab_driver extends calendar_driver
|
|||
if ($master['recurrence']['COUNT'])
|
||||
$master['recurrence']['COUNT']--;
|
||||
}
|
||||
// remove the matching RDATE entry
|
||||
else if ($master['recurrence']['RDATE']) {
|
||||
foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
|
||||
if ($rdate->format('Ymd') == $event['start']->format('Ymd')) {
|
||||
unset($master['recurrence']['RDATE'][$j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // add exception to master event
|
||||
$master['recurrence']['EXDATE'][] = $event['start'];
|
||||
}
|
||||
|
@ -516,8 +525,18 @@ class kolab_driver extends calendar_driver
|
|||
unset($master['recurrence']['COUNT']);
|
||||
|
||||
// if all future instances are deleted, remove recurrence rule entirely (bug #1677)
|
||||
if ($master['recurrence']['UNTIL']->format('Ymd') == $master['start']->format('Ymd'))
|
||||
if ($master['recurrence']['UNTIL']->format('Ymd') == $master['start']->format('Ymd')) {
|
||||
$master['recurrence'] = array();
|
||||
}
|
||||
// remove matching RDATE entries
|
||||
else if ($master['recurrence']['RDATE']) {
|
||||
foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
|
||||
if ($rdate->format('Ymd') == $event['start']->format('Ymd')) {
|
||||
$master['recurrence']['RDATE'] = array_slice($master['recurrence']['RDATE'], 0, $j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$success = $storage->update_event($master);
|
||||
break;
|
||||
|
@ -674,8 +693,24 @@ class kolab_driver extends calendar_driver
|
|||
}
|
||||
}
|
||||
|
||||
$add_exception = true;
|
||||
|
||||
// adjust matching RDATE entry if dates changed
|
||||
if ($savemode == 'current' && $master['recurrence']['RDATE'] && ($old_date = $old['start']->format('Ymd')) != $event['start']->format('Ymd')) {
|
||||
foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
|
||||
if ($rdate->format('Ymd') == $old_date) {
|
||||
$master['recurrence']['RDATE'][$j] = $event['start'];
|
||||
sort($master['recurrence']['RDATE']);
|
||||
$add_exception = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save as new exception to master event
|
||||
$master['recurrence']['EXCEPTIONS'][] = $event;
|
||||
if ($add_exception) {
|
||||
$master['recurrence']['EXCEPTIONS'][] = $event;
|
||||
}
|
||||
$success = $storage->update_event($master);
|
||||
break;
|
||||
|
||||
|
|
|
@ -57,12 +57,18 @@ class calendar_recurrence
|
|||
$this->engine->fromRRule20(libcalendaring::to_rrule($event['recurrence']));
|
||||
|
||||
if (is_array($event['recurrence']['EXDATE'])) {
|
||||
foreach ($event['recurrence']['EXDATE'] as $exdate)
|
||||
$this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
|
||||
foreach ($event['recurrence']['EXDATE'] as $exdate) {
|
||||
if (is_a($exdate, 'DateTime')) {
|
||||
$this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($event['recurrence']['RDATE'])) {
|
||||
foreach ($event['recurrence']['RDATE'] as $rdate)
|
||||
$this->engine->addRDate($rdate->format('Y'), $rdate->format('n'), $rdate->format('j'));
|
||||
foreach ($event['recurrence']['RDATE'] as $rdate) {
|
||||
if (is_a($rdate, 'DateTime')) {
|
||||
$this->engine->addRDate($rdate->format('Y'), $rdate->format('n'), $rdate->format('j'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -392,6 +392,7 @@ class calendar_ui
|
|||
$select->add($this->cal->gettext('weekly'), 'WEEKLY');
|
||||
$select->add($this->cal->gettext('monthly'), 'MONTHLY');
|
||||
$select->add($this->cal->gettext('yearly'), 'YEARLY');
|
||||
$select->add($this->cal->gettext('rdate'), 'RDATE');
|
||||
$html = html::label('edit-frequency', $this->cal->gettext('frequency')) . $select->show('');
|
||||
break;
|
||||
|
||||
|
@ -480,6 +481,13 @@ class calendar_ui
|
|||
$this->cal->gettext('untildate') . ' ' . $input->show(''));
|
||||
$html = $table->show();
|
||||
break;
|
||||
|
||||
case 'rdate':
|
||||
$ul = html::tag('ul', array('id' => 'edit-recurrence-rdates'), '');
|
||||
$input = new html_inputfield(array('name' => 'rdate', 'id' => 'edit-recurrence-rdate-input', 'size' => "10"));
|
||||
$button = new html_inputfield(array('type' => 'button', 'class' => 'button add', 'value' => $this->cal->gettext('addrdate')));
|
||||
$html .= html::div($attrib, $ul . html::div('inputform', $input->show() . $button->show()));
|
||||
break;
|
||||
}
|
||||
|
||||
return $html;
|
||||
|
|
|
@ -179,6 +179,7 @@ $labels['daily'] = 'täglich';
|
|||
$labels['weekly'] = 'wöchentlich';
|
||||
$labels['monthly'] = 'monatlich';
|
||||
$labels['yearly'] = 'jährlich';
|
||||
$labels['rdate'] = 'per Datum';
|
||||
$labels['every'] = 'Alle';
|
||||
$labels['days'] = 'Tag(e)';
|
||||
$labels['weeks'] = 'Woche(n)';
|
||||
|
@ -198,6 +199,7 @@ $labels['third'] = 'dritter';
|
|||
$labels['fourth'] = 'vierter';
|
||||
$labels['last'] = 'letzter';
|
||||
$labels['dayofmonth'] = 'Tag des Montats';
|
||||
$labels['addrdate'] = 'Datum hinzufügen';
|
||||
$labels['changeeventconfirm'] = 'Termin ändern';
|
||||
$labels['removeeventconfirm'] = 'Termin löschen';
|
||||
$labels['changerecurringeventwarning'] = 'Dies ist eine Terminreihe. Möchten Sie nur den aktuellen, diesen und alle zukünftigen oder alle Termine bearbeiten oder die Änderungen als neuen Termin speichern?';
|
||||
|
|
|
@ -179,6 +179,7 @@ $labels['daily'] = 'täglich';
|
|||
$labels['weekly'] = 'wöchentlich';
|
||||
$labels['monthly'] = 'monatlich';
|
||||
$labels['yearly'] = 'jährlich';
|
||||
$labels['rdate'] = 'per Datum';
|
||||
$labels['every'] = 'Alle';
|
||||
$labels['days'] = 'Tag(e)';
|
||||
$labels['weeks'] = 'Woche(n)';
|
||||
|
@ -198,6 +199,7 @@ $labels['third'] = 'dritter';
|
|||
$labels['fourth'] = 'vierter';
|
||||
$labels['last'] = 'letzter';
|
||||
$labels['dayofmonth'] = 'Tag des Montats';
|
||||
$labels['addrdate'] = 'Datum hinzufügen';
|
||||
$labels['changeeventconfirm'] = 'Termin ändern';
|
||||
$labels['removeeventconfirm'] = 'Termin löschen';
|
||||
$labels['changerecurringeventwarning'] = 'Dies ist eine Terminreihe. Möchten Sie nur den aktuellen, diesen und alle zukünftigen oder alle Termine bearbeiten oder die Änderungen als neuen Termin speichern?';
|
||||
|
|
|
@ -210,6 +210,7 @@ $labels['daily'] = 'daily';
|
|||
$labels['weekly'] = 'weekly';
|
||||
$labels['monthly'] = 'monthly';
|
||||
$labels['yearly'] = 'annually';
|
||||
$labels['rdate'] = 'on dates';
|
||||
$labels['every'] = 'Every';
|
||||
$labels['days'] = 'day(s)';
|
||||
$labels['weeks'] = 'week(s)';
|
||||
|
@ -229,6 +230,7 @@ $labels['third'] = 'third';
|
|||
$labels['fourth'] = 'fourth';
|
||||
$labels['last'] = 'last';
|
||||
$labels['dayofmonth'] = 'Day of month';
|
||||
$labels['addrdate'] = 'Add repeat date';
|
||||
|
||||
$labels['changeeventconfirm'] = 'Change event';
|
||||
$labels['removeeventconfirm'] = 'Remove event';
|
||||
|
|
|
@ -541,6 +541,28 @@ td.topalign {
|
|||
margin-left: 7.5em;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates {
|
||||
display: block;
|
||||
list-style: none;
|
||||
margin: 0 0 0.8em 0;
|
||||
padding: 0;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 14em;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li a.delete {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#eventedit .recurrence-form {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,9 @@
|
|||
<div class="recurrence-form" id="recurrence-form-until">
|
||||
<roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
|
||||
</div>
|
||||
<div class="recurrence-form" id="recurrence-form-rdate">
|
||||
<roundcube:object name="plugin.recurrence_form" part="rdate" class="event-section" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-tab-3">
|
||||
|
|
|
@ -606,6 +606,31 @@ td.topalign {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates {
|
||||
display: block;
|
||||
list-style: none;
|
||||
margin: 0 0 0.8em 0;
|
||||
padding: 0;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 12em;
|
||||
padding: 4px 0 4px 0;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li a.delete {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
background-position: -7px -337px;
|
||||
}
|
||||
|
||||
#eventedit .formtable td {
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
|
|
@ -81,6 +81,9 @@
|
|||
<div class="recurrence-form" id="recurrence-form-until">
|
||||
<roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
|
||||
</div>
|
||||
<div class="recurrence-form" id="recurrence-form-rdate">
|
||||
<roundcube:object name="plugin.recurrence_form" part="rdate" class="event-section" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-tab-3">
|
||||
|
|
|
@ -757,6 +757,7 @@ class libcalendaring extends rcube_plugin
|
|||
case 'UNTIL':
|
||||
$val = $val->format('Ymd\THis');
|
||||
break;
|
||||
case 'RDATE':
|
||||
case 'EXDATE':
|
||||
foreach ((array)$val as $i => $ex)
|
||||
$val[$i] = $ex->format('Ymd\THis');
|
||||
|
|
Loading…
Add table
Reference in a new issue