From 62a6e00458e3b2debe0115ca946f53dd24330328 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 26 Sep 2012 12:14:42 +0200 Subject: [PATCH] Fully implement deletion of tasks: either delete all subtasks or re-assign childs to new parent; refactored moving to another list to move all childs, too --- .../database/tasklist_database_driver.php | 48 ++++++++++ .../drivers/kolab/tasklist_kolab_driver.php | 61 ++++++++++-- plugins/tasklist/drivers/tasklist_driver.php | 9 ++ plugins/tasklist/localization/de_CH.inc | 6 ++ plugins/tasklist/localization/en_US.inc | 5 + plugins/tasklist/tasklist.js | 95 +++++++++++++++---- plugins/tasklist/tasklist.php | 59 ++++++++++-- 7 files changed, 252 insertions(+), 31 deletions(-) diff --git a/plugins/tasklist/drivers/database/tasklist_database_driver.php b/plugins/tasklist/drivers/database/tasklist_database_driver.php index bcc994c2..8e56923d 100644 --- a/plugins/tasklist/drivers/database/tasklist_database_driver.php +++ b/plugins/tasklist/drivers/database/tasklist_database_driver.php @@ -357,6 +357,54 @@ class tasklist_database_driver extends tasklist_driver return false; } + /** + * Get all decendents of the given task record + * + * @param mixed Hash array with task properties or task UID + * @param boolean True if all childrens children should be fetched + * @return array List of all child task IDs + */ + public function get_childs($prop, $recursive = false) + { + // resolve UID first + if (is_string($prop)) { + $result = $this->rc->db->query(sprintf( + "SELECT task_id AS id, tasklist_id AS list FROM " . $this->db_tasks . " + WHERE tasklist_id IN (%s) + AND uid=?", + $this->list_ids + ), + $prop); + $prop = $this->rc->db->fetch_assoc($result); + } + + $childs = array(); + $task_ids = array($prop['id']); + + // query for childs (recursively) + while (!empty($task_ids)) { + $result = $this->rc->db->query(sprintf( + "SELECT task_id AS id FROM " . $this->db_tasks . " + WHERE tasklist_id IN (%s) + AND parent_id IN (%s) + AND del=0", + $this->list_ids, + join(',', array_map(array($this->rc->db, 'quote'), $task_ids)) + )); + + $task_ids = array(); + while ($result && ($rec = $this->rc->db->fetch_assoc($result))) { + $childs[] = $rec['id']; + $task_ids[] = $rec['id']; + } + + if (!$recursive) + break; + } + + return $childs; + } + /** * Get a list of pending alarms to be displayed to the user * diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php index 60f9ccbb..b70a4a09 100644 --- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php +++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php @@ -351,14 +351,17 @@ class tasklist_kolab_driver extends tasklist_driver */ public function get_task($prop) { - $id = is_array($prop) ? $prop['uid'] : $prop; + $id = is_array($prop) ? ($prop['uid'] ?: $prop['id']) : $prop; $list_id = is_array($prop) ? $prop['list'] : null; $folders = $list_id ? array($list_id => $this->folders[$list_id]) : $this->folders; // find task in the available folders - foreach ($folders as $folder) { + foreach ($folders as $list_id => $folder) { + if (is_numeric($list_id)) + continue; if (!$this->tasks[$id] && ($object = $folder->get_object($id))) { $this->tasks[$id] = $this->_to_rcube_task($object); + $this->tasks[$id]['list'] = $list_id; break; } } @@ -366,6 +369,48 @@ class tasklist_kolab_driver extends tasklist_driver return $this->tasks[$id]; } + /** + * Get all decendents of the given task record + * + * @param mixed Hash array with task properties or task UID + * @param boolean True if all childrens children should be fetched + * @return array List of all child task IDs + */ + public function get_childs($prop, $recursive = false) + { + if (is_string($prop)) { + $task = $this->get_task($prop); + $prop = array('id' => $task['id'], 'list' => $task['list']); + } + + $childs = array(); + $list_id = $prop['list']; + $task_ids = array($prop['id']); + $folder = $this->folders[$list_id]; + + // query for childs (recursively) + while ($folder && !empty($task_ids)) { + $query_ids = array(); + foreach ($task_ids as $task_id) { + $query = array(array('tags','=','x-parent:' . $task_id)); + foreach ((array)$folder->select($query) as $record) { + // don't rely on kolab_storage_folder filtering + if ($record['parent_id'] == $task_id) { + $childs[] = $record['uid']; + $query_ids[] = $record['uid']; + } + } + } + + if (!$recursive) + break; + + $task_ids = $query_ids; + } + + return $childs; + } + /** * Get a list of pending alarms to be displayed to the user * @@ -641,7 +686,7 @@ class tasklist_kolab_driver extends tasklist_driver // moved from another folder if ($task['_fromlist'] && ($fromfolder = $this->folders[$task['_fromlist']])) { - if (!$fromfolder->move($task['uid'], $folder->name)) + if (!$fromfolder->move($task['id'], $folder->name)) return false; unset($task['_fromlist']); @@ -649,9 +694,13 @@ class tasklist_kolab_driver extends tasklist_driver // load previous version of this task to merge if ($task['id']) { - $old = $folder->get_object($task['uid']); + $old = $folder->get_object($task['id']); if (!$old || PEAR::isError($old)) return false; + + // merge existing properties if the update isn't complete + if (!isset($task['title']) || !isset($task['complete'])) + $task += $this->_to_rcube_task($old); } // generate new task object from RC input @@ -669,7 +718,7 @@ class tasklist_kolab_driver extends tasklist_driver else { $task = $this->_to_rcube_task($object); $task['list'] = $list_id; - $this->tasks[$task['uid']] = $task; + $this->tasks[$task['id']] = $task; } return $saved; @@ -710,7 +759,7 @@ class tasklist_kolab_driver extends tasklist_driver if (!$list_id || !($folder = $this->folders[$list_id])) return false; - return $folder->delete($task['uid']); + return $folder->delete($task['id']); } /** diff --git a/plugins/tasklist/drivers/tasklist_driver.php b/plugins/tasklist/drivers/tasklist_driver.php index 4e987f67..f8107996 100644 --- a/plugins/tasklist/drivers/tasklist_driver.php +++ b/plugins/tasklist/drivers/tasklist_driver.php @@ -159,6 +159,15 @@ abstract class tasklist_driver */ abstract public function get_task($prop); + /** + * Get decendents of the given task record + * + * @param mixed Hash array with task properties or task UID + * @param boolean True if all childrens children should be fetched + * @return array List of all child task IDs + */ + abstract public function get_childs($prop, $recursive = false); + /** * Add a single task to the database * diff --git a/plugins/tasklist/localization/de_CH.inc b/plugins/tasklist/localization/de_CH.inc index ac060951..4fbb9768 100644 --- a/plugins/tasklist/localization/de_CH.inc +++ b/plugins/tasklist/localization/de_CH.inc @@ -36,6 +36,10 @@ $labels['edittask'] = 'Aufgabe bearbeiten'; $labels['save'] = 'Speichern'; $labels['cancel'] = 'Abbrechen'; $labels['addsubtask'] = 'Neue Teilaufgabe'; +$labels['deletetask'] = 'Aufgabe löschen'; +$labels['deletethisonly'] = 'Nur diese Aufgabe löschen'; +$labels['deletewithchilds'] = 'Mit allen Teilaufhaben löschen'; + $labels['tabsummary'] = 'Übersicht'; $labels['tabrecurrence'] = 'Wiederholung'; @@ -60,4 +64,6 @@ $labels['savingdata'] = 'Daten werden gespeichert...'; $labels['errorsaving'] = 'Fehler beim Speichern.'; $labels['notasksfound'] = 'Für die aktuellen Kriterien wurden keine Aufgaben gefunden.'; $labels['invalidstartduedates'] = 'Beginn der Aufgabe darf nicht grösser als das Enddatum sein.'; +$labels['deletetasktconfirm'] = 'Möchten Sie diese Aufgabe wirklich löschen?'; +$labels['deleteparenttasktconfirm'] = 'Möchten Sie diese Aufgabe inklusive aller Teilaufgaben wirklich löschen?'; $labels['deletelistconfirm'] = 'Möchten Sie diese Liste mit allen Aufgaben wirklich löschen?'; diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc index c6575165..7d5415ab 100644 --- a/plugins/tasklist/localization/en_US.inc +++ b/plugins/tasklist/localization/en_US.inc @@ -36,6 +36,9 @@ $labels['edittask'] = 'Edit Task'; $labels['save'] = 'Save'; $labels['cancel'] = 'Cancel'; $labels['addsubtask'] = 'Add subtask'; +$labels['deletetask'] = 'Delete task'; +$labels['deletethisonly'] = 'Delete this task only'; +$labels['deletewithchilds'] = 'Delete with all subtasks'; $labels['tabsummary'] = 'Summary'; $labels['tabrecurrence'] = 'Recurrence'; @@ -60,4 +63,6 @@ $labels['savingdata'] = 'Saving data...'; $labels['errorsaving'] = 'Failed to save data.'; $labels['notasksfound'] = 'No tasks found for the given criteria'; $labels['invalidstartduedates'] = 'Start date must not be greater than due date.'; +$labels['deletetasktconfirm'] = 'Do you really want to delete this task?'; +$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?'; $labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?'; diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js index 2e885e6f..56f29cdc 100644 --- a/plugins/tasklist/tasklist.js +++ b/plugins/tasklist/tasklist.js @@ -815,11 +815,7 @@ function rcube_tasklist_ui(settings) // dropped on another list -> move if ($(this).data('type') == 'tasklist') { if (rec) { - var ids = [ rec.id ], - childs = get_all_childs(rec.id); - if (childs.length) - ids = ids.concat(childs); - save_task({ id:ids, list:drop_id, _fromlist:rec.list }, 'move'); + save_task({ id:rec.id, list:drop_id, _fromlist:rec.list }, 'move'); rec.list = drop_id; } } @@ -1057,11 +1053,6 @@ function rcube_tasklist_ui(settings) // task assigned to a new list if (me.selected_task.list && me.selected_task.list != rec.list) { me.selected_task._fromlist = rec.list; - - // also move all childs - var childs = get_all_childs(me.selected_task.id); - if (childs.length) - save_task({ id:childs, list:me.selected_task.list, _fromlist:rec.list }, 'move'); } me.selected_task.complete = complete.val() / 100; @@ -1209,14 +1200,84 @@ function rcube_tasklist_ui(settings) function delete_task(id) { var rec = listdata[id]; - if (rec && confirm("Delete this?")) { - saving_lock = rcmail.set_busy(true, 'tasklist.savingdata'); - rcmail.http_post('task', { action:'delete', t:rec, filter:filtermask }); - $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).hide(); - return true; + if (!rec || rec.readonly) + return false; + + var html, buttons = [{ + text: rcmail.gettext('cancel', 'tasklist'), + click: function() { + $(this).dialog('close'); + } + }]; + + if (rec.children && rec.children.length) { + html = rcmail.gettext('deleteparenttasktconfirm','tasklist'); + buttons.push({ + text: rcmail.gettext('deletethisonly','tasklist'), + click: function() { + _delete_task(id, 0); + $(this).dialog('close'); + } + }); + buttons.push({ + text: rcmail.gettext('deletewithchilds','tasklist'), + click: function() { + _delete_task(id, 1); + $(this).dialog('close'); + } + }); } - - return false; + else { + html = rcmail.gettext('deletetasktconfirm','tasklist'); + buttons.push({ + text: rcmail.gettext('delete','tasklist'), + click: function() { + _delete_task(id, 0); + $(this).dialog('close'); + } + }); + } + + var $dialog = $('
').html(html); + $dialog.dialog({ + modal: true, + width: 520, + dialogClass: 'warning', + title: rcmail.gettext('deletetask', 'tasklist'), + buttons: buttons, + close: function(){ + $dialog.dialog('destroy').hide(); + } + }).addClass('tasklist-confirm').show(); + + return true; + } + + /** + * Subfunction to submit the delete command after confirm + */ + function _delete_task(id, mode) + { + var rec = listdata[id], + li = $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).hide(); + + saving_lock = rcmail.set_busy(true, 'tasklist.savingdata'); + rcmail.http_post('task', { action:'delete', t:{ id:rec.id, list:rec.list }, mode:mode, filter:filtermask }); + + // move childs to parent/root + if (mode != 1) { + var parent_node = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > .childtasks', rcmail.gui_objects.resultlist) : null; + if (!parent_node || !parent_node.length) + parent_node = rcmail.gui_objects.resultlist; + + $.each(rec.children, function(i,cid) { + var child = listdata[cid]; + child.parent_id = rec.parent_id; + resort_task(child, $('li[rel="'+cid+'"]').appendTo(parent_node), true); + }); + } + + li.remove(); } /** diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php index 1c12c6b8..b28b2e39 100644 --- a/plugins/tasklist/tasklist.php +++ b/plugins/tasklist/tasklist.php @@ -157,6 +157,7 @@ class tasklist extends rcube_plugin */ public function task_action() { + $filter = intval(get_input_value('filter', RCUBE_INPUT_GPC)); $action = get_input_value('action', RCUBE_INPUT_GPC); $rec = get_input_value('t', RCUBE_INPUT_POST, true); $oldrec = $rec; @@ -184,27 +185,64 @@ class tasklist extends rcube_plugin break; case 'move': - $recs = array(); foreach ((array)$rec['id'] as $id) { $r = $rec; $r['id'] = $id; if ($this->driver->move_task($r)) { - $r = $this->driver->get_task($r); - $this->encode_task($r); - $refresh[] = $r; + $refresh[] = $this->driver->get_task($r); $success = true; + + // move all childs, too + foreach ($this->driver->get_childs(array('id' => $rec['id'], 'list' => $rec['_fromlist'])) as $cid) { + $child = $rec; + $child['id'] = $cid; + if ($this->driver->move_task($child)) { + $r = $this->driver->get_task($child); + if ((bool)($filter & self::FILTER_MASK_COMPLETE) == ($r['complete'] == 1.0)) { + $refresh[] = $r; + } + } + } } } break; case 'delete': - if (!($success = $this->driver->delete_task($rec, false))) + $mode = intval(get_input_value('mode', RCUBE_INPUT_POST)); + $oldrec = $this->driver->get_task($rec); + if ($success = $this->driver->delete_task($rec, false)) { + // delete/modify all childs + foreach ($this->driver->get_childs($rec, $mode) as $cid) { + $child = array('id' => $cid, 'list' => $rec['list']); + + if ($mode == 1) { // delete all childs + if ($this->driver->delete_task($child, false)) { + if ($this->driver->undelete) + $_SESSION['tasklist_undelete'][$rec['id']][] = $cid; + } + else + $success = false; + } + else { + $child['parent_id'] = strval($oldrec['parent_id']); + $this->driver->edit_task($child); + } + } + } + + if (!$success) $this->rc->output->command('plugin.reload_data'); break; case 'undelete': - if ($success = $this->driver->undelete_task($rec)) - $refresh = $this->driver->get_task($rec); + if ($success = $this->driver->undelete_task($rec)) { + $refresh[] = $this->driver->get_task($rec); + foreach ((array)$_SESSION['tasklist_undelete'][$rec['id']] as $cid) { + if ($this->driver->undelete_task($rec)) { + $refresh[] = $this->driver->get_task($rec); + } + } + } break; case 'collapse': @@ -232,8 +270,13 @@ class tasklist extends rcube_plugin $this->rc->output->command('plugin.unlock_saving'); if ($refresh) { - if ($refresh['id']) + if ($refresh['id']) { $this->encode_task($refresh); + } + else if (is_array($refresh)) { + foreach ($refresh as $i => $r) + $this->encode_task($refresh[$i]); + } $this->rc->output->command('plugin.refresh_task', $refresh); } }