diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 86f06246..6f11ed5a 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -385,8 +385,9 @@ function rcube_tasklist_ui(settings)
completeness_slider.slider('value', parseInt(this.value))
});
- // register events on alarm fields
+ // register events on alarms and recurrence fields
me.init_alarms_edit('#taskedit-alarms');
+ me.init_recurrence_edit('#eventedit');
$('#taskedit-date, #taskedit-startdate').datepicker(datepicker_settings);
@@ -1160,13 +1161,20 @@ function rcube_tasklist_ui(settings)
});
}
+ if (rec.recurrence && rec.recurrence_text) {
+ $('#task-recurrence').show().children('.task-text').html(Q(rec.recurrence_text));
+ }
+ else {
+ $('#task-recurrence').hide();
+ }
+
// build attachments list
$('#task-attachments').hide();
if ($.isArray(rec.attachments)) {
task_show_attachments(rec.attachments || [], $('#task-attachments').children('.task-text'), rec);
if (rec.attachments.length > 0) {
$('#task-attachments').show();
- }
+ }
}
// define dialog buttons
@@ -1268,6 +1276,9 @@ function rcube_tasklist_ui(settings)
// set alarm(s)
me.set_alarms_edit('#taskedit-alarms', action != 'new' && rec.valarms ? rec.valarms : []);
+ // set recurrence
+ me.set_recurrence_edit(rec);
+
// attachments
rcmail.enable_command('remove-attachment', list.editable);
me.selected_task.deleted_attachments = [];
@@ -1298,6 +1309,7 @@ function rcube_tasklist_ui(settings)
me.selected_task.tags = [];
me.selected_task.attachments = [];
me.selected_task.valarms = me.serialize_alarms('#taskedit-alarms');
+ me.selected_task.recurrence = me.serialize_recurrence(rectime.val());
// do some basic input validation
if (!me.selected_task.title || !me.selected_task.title.length) {
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index ace7e452..be82f82f 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -197,10 +197,16 @@ class tasklist extends rcube_plugin
case 'edit':
$rec = $this->prepare_task($rec);
+ $clone = $this->handle_recurrence($rec, $this->driver->get_task($rec));
if ($success = $this->driver->edit_task($rec)) {
$refresh[] = $this->driver->get_task($rec);
$this->cleanup_task($rec);
+ // add clone from recurring task
+ if ($clone && $this->driver->create_task($clone)) {
+ $refresh[] = $this->driver->get_task($clone);
+ }
+
// move all childs if list assignment was changed
if (!empty($rec['_fromlist']) && !empty($rec['list']) && $rec['_fromlist'] != $rec['list']) {
foreach ($this->driver->get_childs(array('id' => $rec['id'], 'list' => $rec['_fromlist']), true) as $cid) {
@@ -419,6 +425,36 @@ class tasklist extends rcube_plugin
$rec['valarms'] = $valarms;
}
+ // convert the submitted recurrence settings
+ if (is_array($rec['recurrence'])) {
+ $refdate = null;
+ if (!empty($rec['date'])) {
+ $refdate = new DateTime($rec['date'] . ' ' . $rec['time'], $this->timezone);
+ }
+ else if (!empty($rec['startdate'])) {
+ $refdate = new DateTime($rec['startdate'] . ' ' . $rec['starttime'], $this->timezone);
+ }
+
+ if ($refdate) {
+ $rec['recurrence'] = $this->lib->from_client_recurrence($rec['recurrence'], $refdate);
+
+ // translate count into an absolute end date.
+ // why? because when shifting completed tasks to the next recurrence,
+ // the initial start date to count from gets lost.
+ if ($rec['recurrence']['COUNT']) {
+ $engine = libcalendaring::get_recurrence();
+ $engine->init($rec['recurrence'], $refdate);
+ if ($until = $engine->end()) {
+ $rec['recurrence']['UNTIL'] = $until;
+ unset($rec['recurrence']['COUNT']);
+ }
+ }
+ }
+ else { // recurrence requires a reference date
+ $rec['recurrence'] = '';
+ }
+ }
+
$attachments = array();
$taskid = $rec['id'];
if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $taskid) {
@@ -480,6 +516,62 @@ class tasklist extends rcube_plugin
}
}
+ /**
+ * When flagging a recurring task as complete,
+ * clone it and shift dates to the next occurrence
+ */
+ private function handle_recurrence(&$rec, $old)
+ {
+ $clone = null;
+ if ($rec['complete'] == 1.0 && $old && $old['complete'] < 1.0 && is_array($rec['recurrence'])) {
+ $engine = libcalendaring::get_recurrence();
+ $rrule = $rec['recurrence'];
+ $engine->init($rrule);
+ $updates = array();
+
+ // compute the next occurrence of date attributes
+ foreach (array('date'=>'time', 'startdate'=>'starttime') as $date_key => $time_key) {
+ $date = new DateTime($rec[$date_key] . ' ' . $rec[$time_key], $this->timezone);
+ $engine->set_start($date);
+ if ($next = $engine->next()) {
+ $updates[$date_key] = $next->format('Y-m-d');
+ if (!empty($rec[$time_key]))
+ $updates[$time_key] = $next->format('H:i');
+ }
+ }
+
+ // shift absolute alarm dates
+ if (!empty($updates) && is_array($rec['valarms'])) {
+ $updates['valarms'] = array();
+ unset($rrule['UNTIL'], $rrule['COUNT']); // make recurrence rule unlimited
+ $engine->init($rrule);
+
+ foreach ($rec['valarms'] as $i => $alarm) {
+ if ($alarm['trigger'] instanceof DateTime) {
+ $engine->set_start($alarm['trigger']);
+ if ($next = $engine->next()) {
+ $alarm['trigger'] = $next;
+ }
+ }
+ $updates['valarms'][$i] = $alarm;
+ }
+ }
+
+ if (!empty($updates)) {
+ // clone task to save a completed copy
+ $clone = $rec;
+ $clone['uid'] = $this->generate_uid();
+ $clone['parent_id'] = $rec['id'];
+ unset($clone['id'], $clone['recurrence'], $clone['attachments']);
+
+ // update the task but unset completed flag
+ $rec = array_merge($rec, $updates);
+ $rec['complete'] = $old['complete'];
+ }
+ }
+
+ return $clone;
+ }
/**
* Dispatcher for tasklist actions initiated by the client
@@ -677,6 +769,11 @@ class tasklist extends rcube_plugin
$rec['valarms'] = libcalendaring::to_client_alarms($rec['valarms']);
}
+ if ($rec['recurrence']) {
+ $rec['recurrence_text'] = $this->lib->recurrence_text($rec['recurrence']);
+ $rec['recurrence'] = $this->lib->to_client_recurrence($rec['recurrence'], $rec['time'] || $rec['starttime']);
+ }
+
foreach ((array)$rec['attachments'] as $k => $attachment) {
$rec['attachments'][$k]['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']);
}
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index f2a90bef..6988a618 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -74,6 +74,7 @@ class tasklist_ui
$this->plugin->register_handler('plugin.tagslist', array($this, 'tagslist'));
$this->plugin->register_handler('plugin.tags_editline', array($this, 'tags_editline'));
$this->plugin->register_handler('plugin.alarm_select', array($this, 'alarm_select'));
+ $this->plugin->register_handler('plugin.recurrence_form', array($this->plugin->lib, 'recurrence_form'));
$this->plugin->register_handler('plugin.attachments_form', array($this, 'attachments_form'));
$this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
$this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));