Implemented Tasks Export (T147, #3861)
Summary: Fixes T147 Reviewers: #roundcube_kolab_plugins_developers Maniphest Tasks: T147 Differential Revision: https://git.kolab.org/D165
This commit is contained in:
parent
98b6513dce
commit
27df671c91
13 changed files with 244 additions and 13 deletions
|
@ -332,6 +332,10 @@ class tasklist_database_driver extends tasklist_driver
|
|||
$sql_add .= ' AND changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $filter['since']));
|
||||
}
|
||||
|
||||
if ($filter['uid']) {
|
||||
$sql_add .= ' AND `uid` IN (' . implode(',', array_map(array($this->rc->db, 'quote'), $filter['uid'])) . ')');
|
||||
}
|
||||
|
||||
$tasks = array();
|
||||
if (!empty($list_ids)) {
|
||||
$result = $this->rc->db->query(sprintf(
|
||||
|
|
|
@ -550,6 +550,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
* - from: Date range start as string (Y-m-d)
|
||||
* - to: Date range end as string (Y-m-d)
|
||||
* - search: Search query string
|
||||
* - uid: Task UIDs
|
||||
* @param array List of lists to get tasks from
|
||||
* @return array List of tasks records matchin the criteria
|
||||
*/
|
||||
|
@ -585,6 +586,10 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
$query[] = array('changed', '>=', $filter['since']);
|
||||
}
|
||||
|
||||
if ($filter['uid']) {
|
||||
$query[] = array('uid', '=', (array) $filter['uid']);
|
||||
}
|
||||
|
||||
foreach ($lists as $list_id) {
|
||||
if (!$folder = $this->get_folder($list_id)) {
|
||||
continue;
|
||||
|
|
|
@ -13,6 +13,10 @@ $labels['navtitle'] = 'Tasks';
|
|||
$labels['lists'] = 'Tasklists';
|
||||
$labels['list'] = 'Tasklist';
|
||||
$labels['tags'] = 'Tags';
|
||||
$labels['export'] = 'Export';
|
||||
$labels['exporttitle'] = 'Export to iCalendar';
|
||||
$labels['exportattachments'] = 'With attachments';
|
||||
$labels['currentview'] = 'current view';
|
||||
$labels['tasklistsubscribe'] = 'List permanently';
|
||||
$labels['listsearchresults'] = 'Available Tasklists';
|
||||
$labels['findlists'] = 'Find tasklists...';
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.2 KiB |
BIN
plugins/tasklist/skins/larry/images/buttons.png
Normal file
BIN
plugins/tasklist/skins/larry/images/buttons.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
|
||||
#taskbar a.button-tasklist span.button-inner {
|
||||
background-image: url(buttons.png);
|
||||
background-image: url(images/buttons.png);
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,22 @@
|
|||
|
||||
ul.toolbarmenu li span.icon.taskadd,
|
||||
#attachmentmenu li a.tasklistlink span.icon.taskadd {
|
||||
background-image: url(buttons.png);
|
||||
background-image: url(images/buttons.png);
|
||||
background-position: -4px -90px;
|
||||
}
|
||||
|
||||
#taskstoolbar a.button.export {
|
||||
background-image: url(images/buttons.png);
|
||||
background-position: center -179px;
|
||||
min-width: 50px;
|
||||
max-width: 70px;
|
||||
}
|
||||
|
||||
#taskstoolbar a.button.import {
|
||||
background-image: url(images/buttons.png);
|
||||
background-position: center -139px;
|
||||
}
|
||||
|
||||
#taskedit.uidialog,
|
||||
.tasklistview div.uidialog {
|
||||
display: none;
|
||||
|
@ -296,7 +308,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: url(sprites.png) right 20px no-repeat;
|
||||
background: url(images/sprites.png) right 20px no-repeat;
|
||||
}
|
||||
|
||||
.quickview-active #tasklistsbox .treelist li input,
|
||||
|
@ -341,7 +353,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
height: 16px;
|
||||
padding: 0;
|
||||
margin-right: 4px;
|
||||
background: url(sprites.png) -200px 0 no-repeat;
|
||||
background: url(images/sprites.png) -200px 0 no-repeat;
|
||||
overflow: hidden;
|
||||
text-indent: -5000px;
|
||||
cursor: pointer;
|
||||
|
@ -461,7 +473,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
}
|
||||
|
||||
#taskstoolbar a.button.newtask {
|
||||
background-image: url(buttons.png);
|
||||
background-image: url(images/buttons.png);
|
||||
background-position: center -53px;
|
||||
}
|
||||
|
||||
|
@ -547,7 +559,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
|
||||
.buttonbar-right a.iconbutton {
|
||||
padding: 0;
|
||||
background-image: url(sprites.png);
|
||||
background-image: url(images/sprites.png);
|
||||
background-position: 0 -238px;
|
||||
}
|
||||
|
||||
|
@ -599,7 +611,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: url(sprites.png) -2px -80px no-repeat;
|
||||
background: url(images/sprites.png) -2px -80px no-repeat;
|
||||
text-indent: -1000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -648,7 +660,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(sprites.png) 1000px -3px no-repeat;
|
||||
background: url(images/sprites.png) 1000px -3px no-repeat;
|
||||
margin: -3px 1em 0 0;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
|
@ -730,7 +742,7 @@ ul.toolbarmenu li span.icon.taskadd,
|
|||
right: 6px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: url(sprites.png) 1000px -80px no-repeat;
|
||||
background: url(images/sprites.png) 1000px -80px no-repeat;
|
||||
text-indent: -5000px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
@ -772,7 +784,7 @@ ul.toolbarmenu li span.expand,
|
|||
ul.toolbarmenu li span.collapse,
|
||||
ul.toolbarmenu li span.history,
|
||||
ul.toolbarmenu.iconized .selected span.icon {
|
||||
background-image: url(sprites.png);
|
||||
background-image: url(images/sprites.png);
|
||||
}
|
||||
|
||||
ul.toolbarmenu li span.add {
|
||||
|
@ -1162,7 +1174,7 @@ label.block {
|
|||
|
||||
#taskedit-links .attachmentslist li.message.eml,
|
||||
#task-links .attachmentslist li.message.eml {
|
||||
background-image: url(sprites.png);
|
||||
background-image: url(images/sprites.png);
|
||||
background-position: -2px -388px;
|
||||
}
|
||||
|
||||
|
@ -1411,7 +1423,7 @@ div.messagetasklinks::before {
|
|||
left: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: url(buttons.png) -6px -115px no-repeat;
|
||||
background: url(images/buttons.png) -6px -115px no-repeat;
|
||||
}
|
||||
|
||||
div.messagetasklinks ul.tasklist {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<div id="taskstoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
|
||||
<roundcube:button command="newtask" type="link" class="button newtask disabled" classAct="button newtask" classSel="button newtask pressed" label="tasklist.newtask" title="tasklist.newtask" />
|
||||
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" title="tasklist.printtitle" />
|
||||
<roundcube:button command="export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="tasklist.export" title="tasklist.exporttitle" />
|
||||
<roundcube:container name="toolbar" id="taskstoolbar" />
|
||||
</div>
|
||||
|
||||
|
@ -320,6 +321,10 @@
|
|||
<roundcube:container name="tasklistform" id="tasklistform" />
|
||||
</div>
|
||||
|
||||
<div id="tasksexport" class="uidialog">
|
||||
<roundcube:object name="plugin.tasks_export_form" id="tasks-export-form" />
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// UI startup
|
||||
|
|
|
@ -820,6 +820,92 @@ function rcube_tasklist_ui(settings)
|
|||
loadstate.lists = active_lists();
|
||||
}
|
||||
|
||||
// open a tasks export dialog
|
||||
this.export_tasks = function()
|
||||
{
|
||||
// close show dialog first
|
||||
var $dialog = $("#tasksexport"),
|
||||
form = rcmail.gui_objects.exportform,
|
||||
buttons = {};
|
||||
|
||||
if (!form)
|
||||
return;
|
||||
|
||||
if ($dialog.is(':ui-dialog'))
|
||||
$dialog.dialog('close');
|
||||
|
||||
$("#task-export-list").val('');
|
||||
|
||||
buttons[rcmail.gettext('export', 'tasklist')] = function() {
|
||||
var source = $('#task-export-list option:selected').val();
|
||||
|
||||
// "current view" export, use hidden form to POST task IDs
|
||||
if (source === '') {
|
||||
var cache = {}, tasks = [], inputs = [],
|
||||
postform = $('#tasks-export-form-post');
|
||||
|
||||
$.each(listindex || [], function() {
|
||||
var rec = listdata[this];
|
||||
if (match_filter(rec, cache)) {
|
||||
tasks.push(rec.id);
|
||||
}
|
||||
});
|
||||
|
||||
// copy form inputs, there may be controls added by other plugins
|
||||
$('#tasksexport select, #tasksexport input').each(function() {
|
||||
if (this.type != 'checkbox' || this.checked)
|
||||
inputs.push($('<input>').attr({type: 'hidden', name: this.name, value: this.value}));
|
||||
});
|
||||
|
||||
inputs.push($('<input>').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token}));
|
||||
inputs.push($('<input>').attr({type: 'hidden', name: 'id', value: tasks.join(',')}));
|
||||
|
||||
if (!postform.length)
|
||||
postform = $('<form>')
|
||||
.attr({style: 'display: none', method: 'POST', action: '?_task=tasks&_action=export'})
|
||||
.appendTo('body');
|
||||
|
||||
postform.html('').append(inputs).submit();
|
||||
}
|
||||
// otherwise we can use simple GET
|
||||
else {
|
||||
rcmail.goto_url('export', {source: source, attachments: attach});
|
||||
}
|
||||
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
buttons[rcmail.gettext('cancel', 'tasklist')] = function() {
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
// open jquery UI dialog
|
||||
$dialog.dialog({
|
||||
modal: true,
|
||||
resizable: false,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext('exporttitle', 'tasklist'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
|
||||
$dialog.dialog("destroy").hide();
|
||||
},
|
||||
buttons: buttons,
|
||||
width: 520
|
||||
}).show();
|
||||
};
|
||||
/*
|
||||
// download the selected task as iCal
|
||||
this.task_download = function(task)
|
||||
{
|
||||
if (task && task.id) {
|
||||
rcmail.goto_url('export', {source: task.list, id: task.id, attachments: 1});
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
* Modify query parameters for refresh requests
|
||||
*/
|
||||
|
@ -3367,6 +3453,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.register_command('list-remove', function(){ rctasks.list_remove(rctasks.selected_list); }, false);
|
||||
rcmail.register_command('list-showurl', function(){ rctasks.list_showurl(rctasks.selected_list); }, false);
|
||||
|
||||
rcmail.register_command('export', function(){ rctasks.export_tasks(); }, true);
|
||||
rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true);
|
||||
rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true);
|
||||
rcmail.register_command('expand-all', function(){ rctasks.expand_collapse(true); }, true);
|
||||
|
|
|
@ -119,6 +119,7 @@ class tasklist extends rcube_plugin
|
|||
$this->register_action('mail2task', array($this, 'mail_message2task'));
|
||||
$this->register_action('get-attachment', array($this, 'attachment_get'));
|
||||
$this->register_action('upload', array($this, 'attachment_upload'));
|
||||
$this->register_action('export', array($this, 'export_tasks'));
|
||||
$this->register_action('mailimportitip', array($this, 'mail_import_itip'));
|
||||
$this->register_action('mailimportattach', array($this, 'mail_import_attachment'));
|
||||
$this->register_action('itip-status', array($this, 'task_itip_status'));
|
||||
|
@ -1578,6 +1579,79 @@ class tasklist extends rcube_plugin
|
|||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the ics file for exporting tasks to iCalendar format
|
||||
*/
|
||||
function export_tasks()
|
||||
{
|
||||
$source = rcube_utils::get_input_value('source', rcube_utils::INPUT_GPC);
|
||||
$task_id = rcube_utils::get_input_value('id', rcube_utils::INPUT_GPC);
|
||||
$attachments = (bool) rcube_utils::get_input_value('attachments', rcube_utils::INPUT_GPC);
|
||||
|
||||
$this->load_driver();
|
||||
|
||||
$browser = new rcube_browser;
|
||||
$lists = $this->driver->get_lists();
|
||||
$tasks = array();
|
||||
$filter = array();
|
||||
|
||||
// get message UIDs for filter
|
||||
if ($source && ($list = $lists[$source])) {
|
||||
$filename = html_entity_decode($list['name']) ?: $sorce;
|
||||
$filter = array($source => true);
|
||||
}
|
||||
else if ($task_id) {
|
||||
$filename = 'tasks';
|
||||
foreach (explode(',', $task_id) as $id) {
|
||||
list($list_id, $task_id) = explode(':', $id, 2);
|
||||
if ($list_id && $task_id) {
|
||||
$filter[$list_id][] = $task_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get tasks
|
||||
foreach ($filter as $list_id => $uids) {
|
||||
$_filter = is_array($uids) ? array('uid' => $uids) : null;
|
||||
$_tasks = $this->driver->list_tasks($_filter, $list_id);
|
||||
if (!empty($_tasks)) {
|
||||
$tasks = array_merge($tasks, $_tasks);
|
||||
}
|
||||
}
|
||||
|
||||
// Set file name
|
||||
if ($source && count($tasks) == 1) {
|
||||
$filename = $tasks[0]['title'] ?: 'task';
|
||||
}
|
||||
$filename .= '.ics';
|
||||
$filename = $browser->ie ? rawurlencode($filename) : addcslashes($filename, '"');
|
||||
|
||||
$tasks = array_map(array($this, 'to_libcal'), $tasks);
|
||||
|
||||
// Give plugins a possibility to implement other output formats or modify the result
|
||||
$plugin = $this->rc->plugins->exec_hook('tasks_export', array(
|
||||
'result' => $tasks,
|
||||
'attachments' => $attachments,
|
||||
'filename' => $filename,
|
||||
'plugin' => $this,
|
||||
));
|
||||
|
||||
if ($plugin['abort']) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->rc->output->nocacheing_headers();
|
||||
|
||||
// don't kill the connection if download takes more than 30 sec.
|
||||
@set_time_limit(0);
|
||||
header("Content-Type: text/calendar");
|
||||
header("Content-Disposition: inline; filename=\"". $plugin['filename'] ."\"");
|
||||
|
||||
$this->get_ical()->export($plugin['tasks'], '', true,
|
||||
$plugins['attachments'] ? array($this->driver, 'get_attachment_body') : null);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
/******* Attachment handling *******/
|
||||
|
||||
|
|
|
@ -161,6 +161,7 @@ class tasklist_ui
|
|||
$this->plugin->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
|
||||
$this->plugin->register_handler('plugin.task_rsvp_buttons', array($this->plugin->itip, 'itip_rsvp_buttons'));
|
||||
$this->plugin->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table'));
|
||||
$this->plugin->register_handler('plugin.tasks_export_form', array($this, 'tasks_export_form'));
|
||||
|
||||
jqueryui::tagedit();
|
||||
|
||||
|
@ -310,11 +311,18 @@ class tasklist_ui
|
|||
*/
|
||||
function tasklist_select($attrib = array())
|
||||
{
|
||||
$attrib['name'] = 'list';
|
||||
if (empty($attrib['name'])) {
|
||||
$attrib['name'] = 'list';
|
||||
}
|
||||
|
||||
$attrib['is_escaped'] = true;
|
||||
$select = new html_select($attrib);
|
||||
$default = null;
|
||||
|
||||
foreach ((array) $attrib['extra'] as $id => $name) {
|
||||
$select->add($name, $id);
|
||||
}
|
||||
|
||||
foreach ((array)$this->plugin->driver->get_lists() as $id => $prop) {
|
||||
if ($prop['editable'] || strpos($prop['rights'], 'i') !== false) {
|
||||
$select->add($prop['name'], $id);
|
||||
|
@ -525,6 +533,38 @@ class tasklist_ui
|
|||
return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->plugin->gettext('sendnotifications')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to select options for exporting tasks
|
||||
*/
|
||||
function tasks_export_form($attrib = array())
|
||||
{
|
||||
if (!$attrib['id']) {
|
||||
$attrib['id'] = 'rcmTaskExportForm';
|
||||
}
|
||||
|
||||
$html .= html::div('form-section',
|
||||
html::label('task-export-list', $this->plugin->gettext('list')) .
|
||||
$this->tasklist_select(array(
|
||||
'name' => 'source',
|
||||
'id' => 'task-export-list',
|
||||
'extra' => array('' => '- ' . $this->plugin->gettext('currentview') . ' -'),
|
||||
))
|
||||
);
|
||||
|
||||
$checkbox = new html_checkbox(array('name' => 'attachments', 'id' => 'task-export-attachments', 'value' => 1));
|
||||
$html .= html::div('form-section',
|
||||
html::label('task-export-attachments', $this->plugin->gettext('exportattachments')) .
|
||||
$checkbox->show(1)
|
||||
);
|
||||
|
||||
$this->register_gui_object('exportform', $attrib['id']);
|
||||
|
||||
return html::tag('form', array('action' => $this->rc->url(array('task' => 'tasklist', 'action' => 'export')),
|
||||
'method' => "post", 'id' => $attrib['id']),
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for rcube_output_html::add_gui_object()
|
||||
*/
|
||||
|
|
BIN
source/tasklist_buttons.psd
Normal file
BIN
source/tasklist_buttons.psd
Normal file
Binary file not shown.
BIN
source/tasklist_sprites.psd
Normal file
BIN
source/tasklist_sprites.psd
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue