Use status attribute to set a task as 'complete' (#3026)

This commit is contained in:
Thomas Bruederli 2014-05-19 18:20:23 +02:00
parent d87c46acac
commit 3a2d5eed5a
13 changed files with 87 additions and 28 deletions

View file

@ -44,7 +44,7 @@ class kolab_format_task extends kolab_format_xcal
$this->obj->setPercentComplete(intval($object['complete']));
$status = kolabformat::StatusUndefined;
if ($object['complete'] == 100)
if ($object['complete'] == 100 && !array_key_exists('status', $object))
$status = kolabformat::StatusCompleted;
else if ($object['status'] && array_key_exists($object['status'], $this->status_map))
$status = $this->status_map[$object['status']];
@ -113,7 +113,7 @@ class kolab_format_task extends kolab_format_xcal
{
$tags = array();
if ($this->data['status'] == 'COMPLETED' || $this->data['complete'] == 100)
if ($this->data['status'] == 'COMPLETED' || ($this->data['complete'] == 100 && empty($this->data['status'])))
$tags[] = 'x-complete';
if ($this->data['priority'] == 1)

View file

@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS `tasks` (
`starttime` varchar(5) DEFAULT NULL,
`flagged` tinyint(4) NOT NULL DEFAULT '0',
`complete` float NOT NULL DEFAULT '0',
`status` enum('','NEEDS-ACTION','IN-PROCESS','COMPLETED','CANCELLED') NOT NULL DEFAULT '',
`alarms` varchar(255) DEFAULT NULL,
`recurrence` varchar(255) DEFAULT NULL,
`organizer` varchar(255) DEFAULT NULL,
@ -48,4 +49,4 @@ CREATE TABLE IF NOT EXISTS `tasks` (
REFERENCES `tasklists`(`tasklist_id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci */;
REPLACE INTO `system` (`name`, `value`) VALUES ('tasklist-database-version', '2013011000');
REPLACE INTO `system` (`name`, `value`) VALUES ('tasklist-database-version', '2014051900');

View file

@ -0,0 +1,3 @@
ALTER TABLE `tasks` ADD `status` ENUM('','NEEDS-ACTION','IN-PROCESS','COMPLETED','CANCELLED') NOT NULL DEFAULT '' AFTER `complete`;
UPDATE `tasks` SET status='COMPLETED' WHERE complete=1.0 AND status='';

View file

@ -49,6 +49,7 @@ CREATE TABLE tasks (
starttime varchar(5) DEFAULT NULL,
flagged smallint NOT NULL DEFAULT 0,
complete float NOT NULL DEFAULT 0,
status varchar(16) NOT NULL DEFAULT '',
alarms varchar(255) DEFAULT NULL,
recurrence varchar(255) DEFAULT NULL,
organizer varchar(255) DEFAULT NULL,
@ -60,4 +61,4 @@ CREATE TABLE tasks (
CREATE INDEX tasks_tasklisting_idx ON tasks (tasklist_id, del, date);
CREATE INDEX tasks_uid_idx ON tasks (uid);
INSERT INTO system (name, value) VALUES ('tasklist-database-version', '2013011000');
INSERT INTO system (name, value) VALUES ('tasklist-database-version', '2014051900');

View file

@ -24,6 +24,8 @@
class tasklist_database_driver extends tasklist_driver
{
const IS_COMPLETE_SQL = "(status='COMPLETED' OR (complete=1 AND status=''))";
public $undelete = true; // yes, we can
public $sortable = false;
public $alarm_types = array('DISPLAY');
@ -224,7 +226,7 @@ class tasklist_database_driver extends tasklist_driver
$result = $this->rc->db->query(sprintf(
"SELECT task_id, flagged, date FROM " . $this->db_tasks . "
WHERE tasklist_id IN (%s)
AND del=0 AND complete<1",
AND del=0 AND NOT " . self::IS_COMPLETE_SQL,
join(',', $list_ids)
));
@ -286,9 +288,9 @@ class tasklist_database_driver extends tasklist_driver
$sql_add = ' AND date IS NULL';
if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE)
$sql_add .= ' AND complete=1';
$sql_add .= ' AND ' . self::IS_COMPLETE_SQL;
else if (empty($filter['since'])) // don't show complete tasks by default
$sql_add .= ' AND complete<1';
$sql_add .= ' AND NOT ' . self::IS_COMPLETE_SQL;
if ($filter['mask'] & tasklist::FILTER_MASK_FLAGGED)
$sql_add .= ' AND flagged=1';
@ -435,7 +437,7 @@ class tasklist_database_driver extends tasklist_driver
$result = $this->rc->db->query(sprintf(
"SELECT * FROM " . $this->db_tasks . "
WHERE tasklist_id IN (%s)
AND notify <= %s AND complete < 1",
AND notify <= %s AND NOT " . self::IS_COMPLETE_SQL,
join(',', $list_ids),
$this->rc->db->fromunixtime($time)
));
@ -529,7 +531,7 @@ class tasklist_database_driver extends tasklist_driver
$prop['recurrence'] = $this->serialize_recurrence($prop['recurrence']);
}
foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms', 'recurrence') as $col) {
foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms', 'recurrence', 'status') as $col) {
if (empty($prop[$col]))
$prop[$col] = null;
}
@ -537,8 +539,8 @@ class tasklist_database_driver extends tasklist_driver
$notify_at = $this->_get_notification($prop);
$result = $this->rc->db->query(sprintf(
"INSERT INTO " . $this->db_tasks . "
(tasklist_id, uid, parent_id, created, changed, title, date, time, startdate, starttime, description, tags, flagged, complete, alarms, recurrence, notify)
VALUES (?, ?, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(tasklist_id, uid, parent_id, created, changed, title, date, time, startdate, starttime, description, tags, flagged, complete, status, alarms, recurrence, notify)
VALUES (?, ?, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
$this->rc->db->now(),
$this->rc->db->now()
),
@ -554,6 +556,7 @@ class tasklist_database_driver extends tasklist_driver
join(',', (array)$prop['tags']),
$prop['flagged'] ? 1 : 0,
intval($prop['complete']),
$prop['status'],
$prop['alarms'],
$prop['recurrence'],
$notify_at
@ -586,7 +589,7 @@ class tasklist_database_driver extends tasklist_driver
if (isset($prop[$col]))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($prop[$col]);
}
foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms', 'recurrence') as $col) {
foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms', 'recurrence', 'status') as $col) {
if (isset($prop[$col]))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . (empty($prop[$col]) ? 'NULL' : $this->rc->db->quote($prop[$col]));
}
@ -694,7 +697,7 @@ class tasklist_database_driver extends tasklist_driver
*/
private function _get_notification($task)
{
if ($task['valarms'] && $task['complete'] < 1) {
if ($task['valarms'] && !$this->is_complete($task)) {
$alarm = libcalendaring::get_next_alarm($task, 'task');
if ($alarm['time'] && in_array($alarm['action'], $this->alarm_types))

View file

@ -307,7 +307,7 @@ class tasklist_kolab_driver extends tasklist_driver
foreach ($folder->select(array(array('tags','!~','x-complete'))) as $record) {
$rec = $this->_to_rcube_task($record);
if ($rec['complete'] >= 1.0) // don't count complete tasks
if ($this->is_complete($rec)) // don't count complete tasks
continue;
$counts['all']++;
@ -603,7 +603,8 @@ class tasklist_kolab_driver extends tasklist_driver
'description' => $record['description'],
'tags' => array_filter((array)$record['categories']),
'flagged' => $record['priority'] == 1,
'complete' => $record['status'] == 'COMPLETED' ? 1 : floatval($record['complete'] / 100),
'complete' => floatval($record['complete'] / 100),
'status' => $record['status'],
'parent_id' => $record['parent_id'],
'recurrence' => $record['recurrence'],
);
@ -672,7 +673,7 @@ class tasklist_kolab_driver extends tasklist_driver
}
$object['complete'] = $task['complete'] * 100;
if ($task['complete'] == 1.0)
if ($task['complete'] == 1.0 && empty($task['complete']))
$object['status'] = 'COMPLETED';
if ($task['flagged'])

View file

@ -41,7 +41,7 @@
* 'categories' => 'Task category',
* 'flagged' => 'Boolean value whether this record is flagged',
* 'complete' => 'Float value representing the completeness state (range 0..1)',
* 'sensitivity' => 0|1|2, // Event sensitivity (0=public, 1=private, 2=confidential)
* 'status' => 'Task status string according to (NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED) RFC 2445',
* 'valarms' => array( // List of reminders (new format), each represented as a hash array:
* array(
* 'trigger' => '-PT90M', // ISO 8601 period string prefixed with '+' or '-', or DateTime object
@ -270,6 +270,17 @@ abstract class tasklist_driver
*/
public function get_attachment_body($id, $task) { }
/**
* Helper method to determine whether the given task is considered "complete"
*
* @param array $task Hash array with event properties:
* @return boolean True if complete, False otherwiese
*/
public function is_complete($task)
{
return ($task['complete'] >= 1.0 && empty($task['status'])) || $task['status'] === 'COMPLETED';
}
/**
* List availabale categories
* The default implementation reads them from config/user prefs

View file

@ -19,6 +19,11 @@ $labels['datetime'] = 'Due';
$labels['start'] = 'Start';
$labels['alarms'] = 'Reminder';
$labels['repeat'] = 'Repeat';
$labels['status'] = 'Status';
$labels['status-needs-action'] = 'Needs action';
$labels['status-in-process'] = 'In process';
$labels['status-completed'] = 'Completed';
$labels['status-cancelled'] = 'Cancelled';
$labels['all'] = 'All';
$labels['flagged'] = 'Flagged';

View file

@ -141,6 +141,10 @@
<label><roundcube:label name="tasklist.complete" /></label>
<span class="task-text"></span>
</div>
<div id="task-status" class="form-section">
<label><roundcube:label name="tasklist.status" /></label>
<span class="task-text"></span>
</div>
<div id="task-attachments" class="form-section">
<label><roundcube:label name="attachments" /></label>
<div class="task-text"></div>

View file

@ -46,6 +46,10 @@
<input type="text" name="title" id="taskedit-completeness" size="3" tabindex="25" />&nbsp;%
<div id="taskedit-completeness-slider"></div>
</div>
<div class="form-section">
<label for="taskedit-status"><roundcube:label name="tasklist.status" /></label>
<roundcube:object name="plugin.status_select" id="taskedit-status" tabindex="26" />
</div>
<div class="form-section" id="tasklist-select">
<label for="taskedit-tasklist"><roundcube:label name="tasklist.list" /></label>
<roundcube:object name="plugin.tasklist_select" id="taskedit-tasklist" tabindex="26" />

View file

@ -264,7 +264,7 @@ function rcube_tasklist_ui(settings)
if (rcmail.busy)
return false;
rec.complete = e.target.checked ? 1 : 0;
rec.status = e.target.checked ? 'COMPLETED' : (rec.complete == 1 ? 'NEEDS-ACTION' : '');
li.toggleClass('complete');
save_task(rec, 'edit');
return true;
@ -815,7 +815,7 @@ function rcube_tasklist_ui(settings)
var div = $('<div>').addClass('taskhead').html(
'<div class="progressbar"><div class="progressvalue" style="width:' + (rec.complete * 100) + '%"></div></div>' +
'<input type="checkbox" name="completed[]" value="1" class="complete" ' + (rec.complete == 1.0 ? 'checked="checked" ' : '') + '/>' +
'<input type="checkbox" name="completed[]" value="1" class="complete" ' + (is_complete(rec) ? 'checked="checked" ' : '') + '/>' +
'<span class="flagged"></span>' +
'<span class="title">' + text2html(Q(rec.title)) + '</span>' +
'<span class="tags">' + tags_html + '</span>' +
@ -835,7 +835,7 @@ function rcube_tasklist_ui(settings)
revertDuration: 300
});
if (rec.complete == 1.0)
if (is_complete(rec))
div.addClass('complete');
if (rec.flagged)
div.addClass('flagged');
@ -951,12 +951,20 @@ function rcube_tasklist_ui(settings)
*/
function task_cmp(a, b)
{
var d = Math.floor(a.complete) - Math.floor(b.complete);
var d = is_complete(a) - is_complete(b);
if (!d) d = (b._hasdate-0) - (a._hasdate-0);
if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
return d;
}
/**
* Determine whether the given task should be displayed as "complete"
*/
function is_complete(rec)
{
return ((rec.complete == 1.0 && !rec.status) || rec.status === 'COMPLETED') ? 1 : 0;
}
/**
*
*/
@ -1144,6 +1152,7 @@ function rcube_tasklist_ui(settings)
$('#task-starttime').html(Q(rec.starttime || ''));
$('#task-alarm')[(rec.alarms_text ? 'show' : 'hide')]().children('.task-text').html(Q(rec.alarms_text));
$('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%');
$('#task-status')[(rec.status ? 'show' : 'hide')]().children('.task-text').html(rcmail.gettext('status-'+String(rec.status).toLowerCase(),'tasklist'));
$('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : ''));
var itags = get_inherited_tags(rec);
@ -1257,6 +1266,7 @@ function rcube_tasklist_ui(settings)
var recstarttime = $('#taskedit-starttime').val(rec.starttime || '');
var complete = $('#taskedit-completeness').val((rec.complete || 0) * 100);
completeness_slider.slider('value', complete.val());
var taskstatus = $('#taskedit-status').val(rec.status || '');
var tasklist = $('#taskedit-tasklist').val(rec.list || me.selected_list).prop('disabled', rec.parent_id ? true : false);
// tag-edit line
@ -1308,7 +1318,7 @@ function rcube_tasklist_ui(settings)
var buttons = {};
buttons[rcmail.gettext('save', 'tasklist')] = function() {
// copy form field contents into task object to save
$.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, list:tasklist }, function(key,input){
$.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, status:taskstatus, list:tasklist }, function(key,input){
me.selected_task[key] = input.val();
});
me.selected_task.tags = [];

View file

@ -214,7 +214,7 @@ class tasklist extends rcube_plugin
$child = array('id' => $cid, 'list' => $rec['list'], '_fromlist' => $rec['_fromlist']);
if ($this->driver->move_task($child)) {
$r = $this->driver->get_task($child);
if ((bool)($filter & self::FILTER_MASK_COMPLETE) == ($r['complete'] == 1.0)) {
if ((bool)($filter & self::FILTER_MASK_COMPLETE) == $this->driver->is_complete($r)) {
$refresh[] = $r;
}
}
@ -237,7 +237,7 @@ class tasklist extends rcube_plugin
$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)) {
if ((bool)($filter & self::FILTER_MASK_COMPLETE) == $this->driver->is_complete($r)) {
$refresh[] = $r;
}
}
@ -524,7 +524,7 @@ class tasklist extends rcube_plugin
private function handle_recurrence(&$rec, $old)
{
$clone = null;
if ($rec['complete'] == 1.0 && $old && $old['complete'] < 1.0 && is_array($rec['recurrence'])) {
if ($this->driver->is_complete($rec) && $old && $this->driver->is_complete($old) && is_array($rec['recurrence'])) {
$engine = libcalendaring::get_recurrence();
$rrule = $rec['recurrence'];
$updates = array();
@ -569,6 +569,7 @@ class tasklist extends rcube_plugin
// update the task but unset completed flag
$rec = array_merge($rec, $updates);
$rec['complete'] = $old['complete'];
$rec['satus'] = $old['satus'];
}
}
@ -718,7 +719,7 @@ class tasklist extends rcube_plugin
$tags = array_merge($tags, (array)$rec['tags']);
// apply filter; don't trust the driver on this :-)
if ((!$f && $rec['complete'] < 1.0) || ($rec['mask'] & $f))
if ((!$f && !$this->driver->is_complete($rec)) || ($rec['mask'] & $f))
$data[] = $rec;
}
@ -845,7 +846,7 @@ class tasklist extends rcube_plugin
if ($rec['flagged'])
$mask |= self::FILTER_MASK_FLAGGED;
if ($rec['complete'] == 1.0)
if ($this->driver->is_complete($rec))
$mask |= self::FILTER_MASK_COMPLETE;
if (empty($rec['date']))

View file

@ -67,7 +67,7 @@ class tasklist_ui
{
$this->plugin->register_handler('plugin.tasklists', array($this, 'tasklists'));
$this->plugin->register_handler('plugin.tasklist_select', array($this, 'tasklist_select'));
$this->plugin->register_handler('plugin.category_select', array($this, 'category_select'));
$this->plugin->register_handler('plugin.status_select', array($this, 'status_select'));
$this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form'));
$this->plugin->register_handler('plugin.quickaddform', array($this, 'quickadd_form'));
$this->plugin->register_handler('plugin.tasks', array($this, 'tasks_resultview'));
@ -129,6 +129,21 @@ class tasklist_ui
return html::tag('ul', $attrib, $li, html::$common_attrib);
}
/**
* Render HTML form for task status selector
*/
function status_select($attrib = array())
{
$attrib['name'] = 'status';
$select = new html_select($attrib);
$select->add('---', '');
$select->add($this->plugin->gettext('status-needs-action'), 'NEEDS-ACTION');
$select->add($this->plugin->gettext('status-in-process'), 'IN-PROCESS');
$select->add($this->plugin->gettext('status-completed'), 'COMPLETED');
$select->add($this->plugin->gettext('status-cancelled'), 'CANCELLED');
return $select->show(null);
}
/**
* Render a HTML select box for list selection