Add support for task attachments (#895)
This commit is contained in:
parent
a16a9c5810
commit
05a92b0b92
11 changed files with 775 additions and 44 deletions
|
@ -26,7 +26,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
{
|
||||
// features supported by the backend
|
||||
public $alarms = false;
|
||||
public $attachments = false;
|
||||
public $attachments = true;
|
||||
public $undelete = false; // task undelete action
|
||||
|
||||
private $rc;
|
||||
|
@ -346,6 +346,18 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
$task['changed'] = $record['dtstamp'];
|
||||
}
|
||||
|
||||
if (!empty($record['_attachments'])) {
|
||||
foreach ($record['_attachments'] as $key => $attachment) {
|
||||
if ($attachment !== false) {
|
||||
if (!$attachment['name'])
|
||||
$attachment['name'] = $key;
|
||||
$attachments[] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
$task['attachments'] = $attachments;
|
||||
}
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
|
@ -387,6 +399,47 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
$object[$key] = $val;
|
||||
}
|
||||
|
||||
// delete existing attachment(s)
|
||||
if (!empty($task['deleted_attachments'])) {
|
||||
foreach ($task['deleted_attachments'] as $attachment) {
|
||||
if (is_array($object['_attachments'])) {
|
||||
foreach ($object['_attachments'] as $idx => $att) {
|
||||
if ($att['id'] == $attachment)
|
||||
$object['_attachments'][$idx] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($task['deleted_attachments']);
|
||||
}
|
||||
|
||||
// in kolab_storage attachments are indexed by content-id
|
||||
if (is_array($task['attachments'])) {
|
||||
foreach ($task['attachments'] as $idx => $attachment) {
|
||||
$key = null;
|
||||
// Roundcube ID has nothing to do with the storage ID, remove it
|
||||
if ($attachment['content']) {
|
||||
unset($attachment['id']);
|
||||
}
|
||||
else {
|
||||
foreach ((array)$old['_attachments'] as $cid => $oldatt) {
|
||||
if ($oldatt && $attachment['id'] == $oldatt['id'])
|
||||
$key = $cid;
|
||||
}
|
||||
}
|
||||
|
||||
// replace existing entry
|
||||
if ($key) {
|
||||
$object['_attachments'][$key] = $attachment;
|
||||
}
|
||||
// append as new attachment
|
||||
else {
|
||||
$object['_attachments'][] = $attachment;
|
||||
}
|
||||
}
|
||||
|
||||
unset($task['attachments']);
|
||||
}
|
||||
|
||||
unset($object['tempid'], $object['raw']);
|
||||
return $object;
|
||||
}
|
||||
|
@ -442,7 +495,8 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
$saved = false;
|
||||
}
|
||||
else {
|
||||
$task['id'] = $task['uid'];
|
||||
$task = $this->_to_rcube_task($object);
|
||||
$task['list'] = $list_id;
|
||||
$this->tasks[$task['uid']] = $task;
|
||||
}
|
||||
|
||||
|
@ -480,6 +534,54 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get attachment properties
|
||||
*
|
||||
* @param string $id Attachment identifier
|
||||
* @param array $task Hash array with event properties:
|
||||
* id: Task identifier
|
||||
* list: List identifier
|
||||
*
|
||||
* @return array Hash array with attachment properties:
|
||||
* id: Attachment identifier
|
||||
* name: Attachment name
|
||||
* mimetype: MIME content type of the attachment
|
||||
* size: Attachment size
|
||||
*/
|
||||
public function get_attachment($id, $task)
|
||||
{
|
||||
$task['uid'] = $task['id'];
|
||||
$task = $this->get_task($task);
|
||||
|
||||
if ($task && !empty($task['attachments'])) {
|
||||
foreach ($task['attachments'] as $att) {
|
||||
if ($att['id'] == $id)
|
||||
return $att;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment body
|
||||
*
|
||||
* @param string $id Attachment identifier
|
||||
* @param array $task Hash array with event properties:
|
||||
* id: Task identifier
|
||||
* list: List identifier
|
||||
*
|
||||
* @return string Attachment body
|
||||
*/
|
||||
public function get_attachment_body($id, $task)
|
||||
{
|
||||
if ($storage = $this->folders[$task['list']]) {
|
||||
return $storage->get_attachment($task['id'], $id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -174,6 +174,34 @@ abstract class tasklist_driver
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment properties
|
||||
*
|
||||
* @param string $id Attachment identifier
|
||||
* @param array $task Hash array with event properties:
|
||||
* id: Task identifier
|
||||
* list: List identifier
|
||||
*
|
||||
* @return array Hash array with attachment properties:
|
||||
* id: Attachment identifier
|
||||
* name: Attachment name
|
||||
* mimetype: MIME content type of the attachment
|
||||
* size: Attachment size
|
||||
*/
|
||||
public function get_attachment($id, $task) { }
|
||||
|
||||
/**
|
||||
* Get attachment body
|
||||
*
|
||||
* @param string $id Attachment identifier
|
||||
* @param array $task Hash array with event properties:
|
||||
* id: Task identifier
|
||||
* list: List identifier
|
||||
*
|
||||
* @return string Attachment body
|
||||
*/
|
||||
public function get_attachment_body($id, $task) { }
|
||||
|
||||
/**
|
||||
* List availabale categories
|
||||
* The default implementation reads them from config/user prefs
|
||||
|
|
|
@ -36,6 +36,11 @@ $labels['save'] = 'Speichern';
|
|||
$labels['cancel'] = 'Abbrechen';
|
||||
$labels['addsubtask'] = 'Neue Teilaufgabe';
|
||||
|
||||
$labels['tabsummary'] = 'Übersicht';
|
||||
$labels['tabrecurrence'] = 'Wiederholung';
|
||||
$labels['tabattachments'] = 'Anhänge';
|
||||
$labels['tabsharing'] = 'Freigabe';
|
||||
|
||||
$labels['editlist'] = 'Ressource bearbeiten';
|
||||
$labels['createlist'] = 'Neue Ressource';
|
||||
$labels['listactions'] = 'Ressourcenoptionen...';
|
||||
|
|
|
@ -36,6 +36,11 @@ $labels['save'] = 'Save';
|
|||
$labels['cancel'] = 'Cancel';
|
||||
$labels['addsubtask'] = 'Add subtask';
|
||||
|
||||
$labels['tabsummary'] = 'Summary';
|
||||
$labels['tabrecurrence'] = 'Recurrence';
|
||||
$labels['tabattachments'] = 'Attachments';
|
||||
$labels['tabsharing'] = 'Sharing';
|
||||
|
||||
$labels['editlist'] = 'Edit resource';
|
||||
$labels['createlist'] = 'Add resource';
|
||||
$labels['listactions'] = 'Resource options...';
|
||||
|
|
|
@ -373,13 +373,14 @@ ul.toolbarmenu li span.icon.taskadd {
|
|||
}
|
||||
|
||||
.taskhead .tags {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
top: 3px;
|
||||
right: 110px;
|
||||
max-width: 14em;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 4px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
@ -388,7 +389,7 @@ ul.toolbarmenu li span.icon.taskadd {
|
|||
background: #d9ecf4;
|
||||
border: 1px solid #c2dae5;
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
padding: 1px 7px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
|
@ -529,6 +530,13 @@ ul.toolbarmenu li span.delete {
|
|||
display:none;
|
||||
}
|
||||
|
||||
#taskedit {
|
||||
position: relative;
|
||||
top: -1.5em;
|
||||
padding: 0.5em 0.1em;
|
||||
margin: 0 -0.2em;
|
||||
}
|
||||
|
||||
#taskshow h2 {
|
||||
margin-top: -0.5em;
|
||||
}
|
||||
|
@ -553,11 +561,43 @@ a.morelink:hover {
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#taskedit .ui-tabs-panel {
|
||||
min-height: 24em;
|
||||
}
|
||||
|
||||
#taskeditform input.text,
|
||||
#taskeditform textarea {
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
#taskeditform .formbuttons {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
#taskedit-attachments {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
#taskedit-attachments ul li {
|
||||
display: block;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
padding: 8px 4px 3px 30px;
|
||||
text-shadow: 0px 1px 1px #fff;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#taskedit-attachments ul li a.file {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#taskedit-attachments-form {
|
||||
margin-top: 1em;
|
||||
padding-top: 0.8em;
|
||||
border-top: 2px solid #fafafa;
|
||||
}
|
||||
|
||||
div.form-section {
|
||||
position: relative;
|
||||
margin-top: 0.2em;
|
||||
|
@ -568,6 +608,7 @@ div.form-section {
|
|||
display: inline-block;
|
||||
min-width: 7em;
|
||||
padding-right: 0.5em;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
label.block {
|
||||
|
@ -587,6 +628,38 @@ label.block {
|
|||
width: 97%;
|
||||
}
|
||||
|
||||
#taskedit .droptarget {
|
||||
background-image: url(../../../../skins/larry/images/filedrop.png) !important;
|
||||
background-position: center bottom !important;
|
||||
background-repeat: no-repeat !important;
|
||||
}
|
||||
|
||||
#taskedit .droptarget.hover,
|
||||
#taskedit .droptarget.active {
|
||||
border-color: #019bc6;
|
||||
box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
|
||||
-moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
|
||||
-webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
|
||||
-o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
|
||||
}
|
||||
|
||||
#taskedit .droptarget.hover {
|
||||
background-color: #d9ecf4;
|
||||
box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
|
||||
-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
|
||||
-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
|
||||
-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
|
||||
}
|
||||
|
||||
#task-attachments .attachmentslist li {
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#task-attachments .attachmentslist li a {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Styles of the tagedit inputsforms
|
||||
|
|
36
plugins/tasklist/skins/larry/templates/attachment.html
Normal file
36
plugins/tasklist/skins/larry/templates/attachment.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<roundcube:object name="doctype" value="html5" />
|
||||
<html>
|
||||
<head>
|
||||
<title><roundcube:object name="pagetitle" /></title>
|
||||
<roundcube:include file="/includes/links.html" />
|
||||
</head>
|
||||
<body class="extwin">
|
||||
|
||||
<div id="header">
|
||||
<div id="topline">
|
||||
<div class="topright">
|
||||
<a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="topnav">
|
||||
<roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
|
||||
</div>
|
||||
|
||||
<br style="clear:both" />
|
||||
</div>
|
||||
|
||||
<div id="mainscreen">
|
||||
<div id="partheader" class="uibox">
|
||||
<roundcube:object name="plugin.attachmentcontrols" class="headers-table" />
|
||||
</div>
|
||||
|
||||
<div id="attachmentcontainer" class="uibox">
|
||||
<roundcube:object name="plugin.attachmentframe" id="attachmentframe" class="header-table" style="width:100%" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -120,6 +120,10 @@
|
|||
<label><roundcube:label name="tasklist.complete" /></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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<roundcube:include file="/templates/taskedit.html" />
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<div id="taskedit">
|
||||
<div id="taskedit" class="uidialog uidialog-tabbed">
|
||||
<form id="taskeditform" action="#" method="post" enctype="multipart/form-data">
|
||||
<ul>
|
||||
<li><a href="#taskedit-tab-1"><roundcube:label name="tasklist.tabsummary" /></a></li><li id="taskedit-tab-attachments"><a href="#taskedit-tab-2"><roundcube:label name="tasklist.tabattachments" /></a></li>
|
||||
</ul>
|
||||
<!-- basic info -->
|
||||
<div id="taskedit-tab-1">
|
||||
<div class="form-section">
|
||||
<label for="edit-title"><roundcube:label name="tasklist.title" /></label>
|
||||
<br />
|
||||
|
@ -35,5 +40,16 @@
|
|||
<label for="edit-tasklist"><roundcube:label name="tasklist.list" /></label>
|
||||
<roundcube:object name="plugin.tasklist_select" id="edit-tasklist" tabindex="26" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- attachments list (with upload form) -->
|
||||
<div id="taskedit-tab-2">
|
||||
<div id="taskedit-attachments">
|
||||
<roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" />
|
||||
</div>
|
||||
<div id="taskedit-attachments-form">
|
||||
<roundcube:object name="plugin.attachments_form" id="tasklist-attachment-form" attachmentFieldSize="30" />
|
||||
</div>
|
||||
<roundcube:object name="plugin.filedroparea" id="taskedit-tab-2" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -95,6 +95,8 @@ function rcube_tasklist_ui(settings)
|
|||
|
||||
/* basic initializations */
|
||||
|
||||
$('#taskedit').tabs();
|
||||
|
||||
var completeness_slider = $('#edit-completeness-slider').slider({
|
||||
range: 'min',
|
||||
slide: function(e, ui){
|
||||
|
@ -568,6 +570,7 @@ function rcube_tasklist_ui(settings)
|
|||
}
|
||||
|
||||
// remove from list index
|
||||
var oldlist = listindex.join('%%%');
|
||||
var oldindex = listindex.indexOf(rec.id);
|
||||
if (oldindex >= 0) {
|
||||
slice = listindex.slice(0,oldindex);
|
||||
|
@ -610,6 +613,9 @@ function rcube_tasklist_ui(settings)
|
|||
slice.push(rec.id);
|
||||
listindex = slice.concat(listindex.slice(index));
|
||||
}
|
||||
else { // restore old list index
|
||||
listindex = oldlist.split('%%%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -729,6 +735,15 @@ function rcube_tasklist_ui(settings)
|
|||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
var buttons = {};
|
||||
buttons[rcmail.gettext('edit','tasklist')] = function() {
|
||||
|
@ -769,11 +784,15 @@ function rcube_tasklist_ui(settings)
|
|||
list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] :
|
||||
(me.selected_list ? me.tasklists[me.selected_list] : { editable: action=='new' });
|
||||
|
||||
if (list.readonly || (action == 'edit' && (!rec || rec.readonly || rec.temp)))
|
||||
if (list.readonly || (action == 'edit' && (!rec || rec.readonly)))
|
||||
return false;
|
||||
|
||||
me.selected_task = $.extend({}, rec); // clone task object
|
||||
|
||||
// assign temporary id
|
||||
if (!me.selected_task.id)
|
||||
me.selected_task.id = -(++idcount);
|
||||
|
||||
// fill form data
|
||||
var title = $('#edit-title').val(rec.title || '');
|
||||
var description = $('#edit-description').val(rec.description || '');
|
||||
|
@ -810,6 +829,26 @@ function rcube_tasklist_ui(settings)
|
|||
return false;
|
||||
})
|
||||
|
||||
// attachments
|
||||
rcmail.enable_command('remove-attachment', !list.readonly);
|
||||
me.selected_task.deleted_attachments = [];
|
||||
// we're sharing some code for uploads handling with app.js
|
||||
rcmail.env.attachments = [];
|
||||
rcmail.env.compose_id = me.selected_task.id; // for rcmail.async_upload_form()
|
||||
|
||||
if ($.isArray(rec.attachments)) {
|
||||
task_show_attachments(rec.attachments, $('#taskedit-attachments'), rec, true);
|
||||
}
|
||||
else {
|
||||
$('#taskedit-attachments > ul').empty();
|
||||
}
|
||||
|
||||
// show/hide tabs according to calendar's feature support
|
||||
$('#taskedit-tab-attachments')[(list.attachments?'show':'hide')]();
|
||||
|
||||
// activate the first tab
|
||||
$('#eventtabs').tabs('select', 0);
|
||||
|
||||
// define dialog buttons
|
||||
var buttons = {};
|
||||
buttons[rcmail.gettext('save', 'tasklist')] = function() {
|
||||
|
@ -818,6 +857,7 @@ function rcube_tasklist_ui(settings)
|
|||
me.selected_task[key] = input.val();
|
||||
});
|
||||
me.selected_task.tags = [];
|
||||
me.selected_task.attachments = [];
|
||||
|
||||
// do some basic input validation
|
||||
if (me.selected_task.startdate && me.selected_task.date) {
|
||||
|
@ -834,6 +874,12 @@ function rcube_tasklist_ui(settings)
|
|||
me.selected_task.tags.push(elem.value);
|
||||
});
|
||||
|
||||
// uploaded attachments list
|
||||
for (var i in rcmail.env.attachments) {
|
||||
if (i.match(/^rcmfile(.+)/))
|
||||
me.selected_task.attachments.push(RegExp.$1);
|
||||
}
|
||||
|
||||
if (me.selected_task.list && me.selected_task.list != rec.list)
|
||||
me.selected_task._fromlist = rec.list;
|
||||
|
||||
|
@ -878,6 +924,91 @@ function rcube_tasklist_ui(settings)
|
|||
title.select();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open a task attachment either in a browser window for inline view or download it
|
||||
*/
|
||||
function load_attachment(rec, att)
|
||||
{
|
||||
var qstring = '_id='+urlencode(att.id)+'&_t='+urlencode(rec.recurrence_id||rec.id)+'&_list='+urlencode(rec.list);
|
||||
|
||||
// open attachment in frame if it's of a supported mimetype
|
||||
// similar as in app.js and calendar_ui.js
|
||||
if (att.id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
|
||||
rcmail.attachment_win = window.open(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', 'rcubetaskattachment');
|
||||
if (rcmail.attachment_win) {
|
||||
window.setTimeout(function() { rcmail.attachment_win.focus(); }, 10);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rcmail.goto_url('get-attachment', qstring+'&_download=1', false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Build task attachments list
|
||||
*/
|
||||
function task_show_attachments(list, container, rec, edit)
|
||||
{
|
||||
var i, id, len, content, li, elem,
|
||||
ul = $('<ul>').addClass('attachmentslist');
|
||||
|
||||
for (i=0, len=list.length; i<len; i++) {
|
||||
elem = list[i];
|
||||
li = $('<li>').addClass(elem.classname);
|
||||
|
||||
if (edit) {
|
||||
rcmail.env.attachments[elem.id] = elem;
|
||||
// delete icon
|
||||
content = $('<a>')
|
||||
.attr('href', '#delete')
|
||||
.attr('title', rcmail.gettext('delete'))
|
||||
.addClass('delete')
|
||||
.click({ id:elem.id }, function(e) {
|
||||
remove_attachment(this, e.data.id);
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!rcmail.env.deleteicon) {
|
||||
content.html(rcmail.gettext('delete'));
|
||||
}
|
||||
else {
|
||||
$('<img>').attr('src', rcmail.env.deleteicon).attr('alt', rcmail.gettext('delete')).appendTo(content);
|
||||
}
|
||||
|
||||
li.append(content);
|
||||
}
|
||||
|
||||
// name/link
|
||||
$('<a>')
|
||||
.attr('href', '#load')
|
||||
.addClass('file')
|
||||
.html(elem.name).click({ task:rec, att:elem }, function(e) {
|
||||
load_attachment(e.data.task, e.data.att);
|
||||
return false;
|
||||
}).appendTo(li);
|
||||
|
||||
ul.append(li);
|
||||
}
|
||||
|
||||
if (edit && rcmail.gui_objects.attachmentlist) {
|
||||
ul.id = rcmail.gui_objects.attachmentlist.id;
|
||||
rcmail.gui_objects.attachmentlist = ul.get(0);
|
||||
}
|
||||
|
||||
container.empty().append(ul);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
var remove_attachment = function(elem, id)
|
||||
{
|
||||
$(elem.parentNode).hide();
|
||||
me.selected_task.deleted_attachments.push(id);
|
||||
delete rcmail.env.attachments[id];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -83,6 +83,8 @@ class tasklist extends rcube_plugin
|
|||
$this->register_action('fetch', array($this, 'fetch_tasks'));
|
||||
$this->register_action('inlineui', array($this, 'get_inline_ui'));
|
||||
$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'));
|
||||
}
|
||||
else if ($this->rc->task == 'mail') {
|
||||
// TODO: register hooks to catch ical/vtodo email attachments
|
||||
|
@ -296,6 +298,24 @@ class tasklist extends rcube_plugin
|
|||
}
|
||||
}
|
||||
|
||||
$attachments = array();
|
||||
$taskid = $rec['id'];
|
||||
if (is_array($_SESSION['tasklist_session']) && $_SESSION['tasklist_session']['id'] == $taskid) {
|
||||
if (!empty($_SESSION['tasklist_session']['attachments'])) {
|
||||
foreach ($_SESSION['tasklist_session']['attachments'] as $id => $attachment) {
|
||||
if (is_array($rec['attachments']) && in_array($id, $rec['attachments'])) {
|
||||
$attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment);
|
||||
unset($attachments[$id]['abort'], $attachments[$id]['group']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rec['attachments'] = $attachments;
|
||||
|
||||
if (is_numeric($rec['id']) && $rec['id'] < 0)
|
||||
unset($rec['id']);
|
||||
|
||||
return $rec;
|
||||
}
|
||||
|
||||
|
@ -464,6 +484,10 @@ class tasklist extends rcube_plugin
|
|||
}
|
||||
}
|
||||
|
||||
foreach ((array)$rec['attachments'] as $k => $attachment) {
|
||||
$rec['attachments'][$k]['classname'] = rcmail_filetype2classname($attachment['mimetype'], $attachment['name']);
|
||||
}
|
||||
|
||||
if (!isset($rec['_depth'])) {
|
||||
$rec['_depth'] = 0;
|
||||
$parent_id = $this->task_tree[$rec['id']];
|
||||
|
@ -571,6 +595,229 @@ class tasklist extends rcube_plugin
|
|||
}
|
||||
|
||||
|
||||
/******* Attachment handling *******/
|
||||
/*** pretty much the same as in plugins/calendar/calendar.php ***/
|
||||
|
||||
/**
|
||||
* Handler for attachments upload
|
||||
*/
|
||||
public function attachment_upload()
|
||||
{
|
||||
// Upload progress update
|
||||
if (!empty($_GET['_progress'])) {
|
||||
rcube_upload_progress();
|
||||
}
|
||||
|
||||
$taskid = get_input_value('_id', RCUBE_INPUT_GPC);
|
||||
$uploadid = get_input_value('_uploadid', RCUBE_INPUT_GPC);
|
||||
|
||||
// prepare session storage
|
||||
if (!is_array($_SESSION['tasklist_session']) || $_SESSION['tasklist_session']['id'] != $taskid) {
|
||||
$_SESSION['tasklist_session'] = array();
|
||||
$_SESSION['tasklist_session']['id'] = $taskid;
|
||||
$_SESSION['tasklist_session']['attachments'] = array();
|
||||
}
|
||||
|
||||
// clear all stored output properties (like scripts and env vars)
|
||||
$this->rc->output->reset();
|
||||
|
||||
if (is_array($_FILES['_attachments']['tmp_name'])) {
|
||||
foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
|
||||
// Process uploaded attachment if there is no error
|
||||
$err = $_FILES['_attachments']['error'][$i];
|
||||
|
||||
if (!$err) {
|
||||
$attachment = array(
|
||||
'path' => $filepath,
|
||||
'size' => $_FILES['_attachments']['size'][$i],
|
||||
'name' => $_FILES['_attachments']['name'][$i],
|
||||
'mimetype' => rc_mime_content_type($filepath, $_FILES['_attachments']['name'][$i], $_FILES['_attachments']['type'][$i]),
|
||||
'group' => $eventid,
|
||||
);
|
||||
|
||||
$attachment = $this->rc->plugins->exec_hook('attachment_upload', $attachment);
|
||||
}
|
||||
|
||||
if (!$err && $attachment['status'] && !$attachment['abort']) {
|
||||
$id = $attachment['id'];
|
||||
|
||||
// store new attachment in session
|
||||
unset($attachment['status'], $attachment['abort']);
|
||||
$_SESSION['tasklist_session']['attachments'][$id] = $attachment;
|
||||
|
||||
$content = html::a(array(
|
||||
'href' => "#delete",
|
||||
'class' => 'delete',
|
||||
'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", JS_OBJECT_NAME, $id),
|
||||
'title' => rcube_label('delete'),
|
||||
), Q(rcube_label('delete')));
|
||||
|
||||
$content .= Q($attachment['name']);
|
||||
|
||||
$this->rc->output->command('add2attachment_list', "rcmfile$id", array(
|
||||
'html' => $content,
|
||||
'name' => $attachment['name'],
|
||||
'mimetype' => $attachment['mimetype'],
|
||||
'classname' => rcmail_filetype2classname($attachment['mimetype'], $attachment['name']),
|
||||
'complete' => true), $uploadid);
|
||||
}
|
||||
else { // upload failed
|
||||
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
|
||||
$msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
|
||||
'size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
|
||||
}
|
||||
else if ($attachment['error']) {
|
||||
$msg = $attachment['error'];
|
||||
}
|
||||
else {
|
||||
$msg = rcube_label('fileuploaderror');
|
||||
}
|
||||
|
||||
$this->rc->output->command('display_message', $msg, 'error');
|
||||
$this->rc->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
// if filesize exceeds post_max_size then $_FILES array is empty,
|
||||
// show filesizeerror instead of fileuploaderror
|
||||
if ($maxsize = ini_get('post_max_size')) {
|
||||
$msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
|
||||
'size' => show_bytes(parse_bytes($maxsize)))));
|
||||
}
|
||||
else {
|
||||
$msg = rcube_label('fileuploaderror');
|
||||
}
|
||||
|
||||
$this->rc->output->command('display_message', $msg, 'error');
|
||||
$this->rc->output->command('remove_from_attachment_list', $uploadid);
|
||||
}
|
||||
|
||||
$this->rc->output->send('iframe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for attachments download/displaying
|
||||
*/
|
||||
public function attachment_get()
|
||||
{
|
||||
$task = get_input_value('_t', RCUBE_INPUT_GPC);
|
||||
$list = get_input_value('_list', RCUBE_INPUT_GPC);
|
||||
$id = get_input_value('_id', RCUBE_INPUT_GPC);
|
||||
|
||||
$task = array('id' => $task, 'list' => $list);
|
||||
|
||||
// show loading page
|
||||
if (!empty($_GET['_preload'])) {
|
||||
$url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']);
|
||||
$message = rcube_label('loadingdata');
|
||||
|
||||
header('Content-Type: text/html; charset=' . RCMAIL_CHARSET);
|
||||
print "<html>\n<head>\n"
|
||||
. '<meta http-equiv="refresh" content="0; url='.Q($url).'">' . "\n"
|
||||
. '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'">' . "\n"
|
||||
. "</head>\n<body>\n$message\n</body>\n</html>";
|
||||
exit;
|
||||
}
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
$attachment = $this->attachment = $this->driver->get_attachment($id, $task);
|
||||
|
||||
// show part page
|
||||
if (!empty($_GET['_frame'])) {
|
||||
$this->register_handler('plugin.attachmentframe', array($this, 'attachment_frame'));
|
||||
$this->register_handler('plugin.attachmentcontrols', array($this->ui, 'attachment_controls'));
|
||||
$this->rc->output->send('tasklist.attachment');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($attachment) {
|
||||
// allow post-processing of the attachment body
|
||||
$part = new rcube_message_part;
|
||||
$part->filename = $attachment['name'];
|
||||
$part->size = $attachment['size'];
|
||||
$part->mimetype = $attachment['mimetype'];
|
||||
|
||||
$plugin = $this->rc->plugins->exec_hook('message_part_get', array(
|
||||
'body' => $this->driver->get_attachment_body($id, $task),
|
||||
'mimetype' => strtolower($attachment['mimetype']),
|
||||
'download' => !empty($_GET['_download']),
|
||||
'part' => $part,
|
||||
));
|
||||
|
||||
if ($plugin['abort'])
|
||||
exit;
|
||||
|
||||
$mimetype = $plugin['mimetype'];
|
||||
list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
|
||||
|
||||
$browser = $this->rc->output->browser;
|
||||
|
||||
// send download headers
|
||||
if ($plugin['download']) {
|
||||
header("Content-Type: application/octet-stream");
|
||||
if ($browser->ie)
|
||||
header("Content-Type: application/force-download");
|
||||
}
|
||||
else if ($ctype_primary == 'text') {
|
||||
header("Content-Type: text/$ctype_secondary");
|
||||
}
|
||||
else {
|
||||
header("Content-Type: $mimetype");
|
||||
header("Content-Transfer-Encoding: binary");
|
||||
}
|
||||
|
||||
// display page, @TODO: support text/plain (and maybe some other text formats)
|
||||
if ($mimetype == 'text/html' && empty($_GET['_download'])) {
|
||||
$OUTPUT = new rcube_html_page();
|
||||
// @TODO: use washtml on $body
|
||||
$OUTPUT->write($plugin['body']);
|
||||
}
|
||||
else {
|
||||
// don't kill the connection if download takes more than 30 sec.
|
||||
@set_time_limit(0);
|
||||
|
||||
$filename = $attachment['name'];
|
||||
$filename = preg_replace('[\r\n]', '', $filename);
|
||||
|
||||
if ($browser->ie && $browser->ver < 7)
|
||||
$filename = rawurlencode(abbreviate_string($filename, 55));
|
||||
else if ($browser->ie)
|
||||
$filename = rawurlencode($filename);
|
||||
else
|
||||
$filename = addcslashes($filename, '"');
|
||||
|
||||
$disposition = !empty($_GET['_download']) ? 'attachment' : 'inline';
|
||||
header("Content-Disposition: $disposition; filename=\"$filename\"");
|
||||
|
||||
echo $plugin['body'];
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// if we arrive here, the requested part was not found
|
||||
header('HTTP/1.1 404 Not Found');
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template object for attachment display frame
|
||||
*/
|
||||
public function attachment_frame($attrib)
|
||||
{
|
||||
$attachment = $this->attachment;
|
||||
|
||||
$mimetype = strtolower($attachment['mimetype']);
|
||||
list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
|
||||
|
||||
$attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary == 'text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
|
||||
|
||||
return html::iframe($attrib);
|
||||
}
|
||||
|
||||
|
||||
/******* Email related function *******/
|
||||
|
||||
public function mail_message2task()
|
||||
|
|
|
@ -81,6 +81,16 @@ class tasklist_ui
|
|||
$this->plugin->register_handler('plugin.tasks', array($this, 'tasks_resultview'));
|
||||
$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.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'));
|
||||
|
||||
// define list of file types which can be displayed inline
|
||||
// same as in program/steps/mail/show.inc
|
||||
$mimetypes = $this->rc->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash');
|
||||
$settings = $this->rc->output->get_env('tasklist_settings');
|
||||
$settings['mimetypes'] = is_string($mimetypes) ? explode(',', $mimetypes) : (array)$mimetypes;
|
||||
$this->rc->output->set_env('tasklist_settings', $settings);
|
||||
|
||||
$this->plugin->include_script('jquery.tagedit.js');
|
||||
$this->plugin->include_script('tasklist.js');
|
||||
|
@ -102,6 +112,7 @@ class tasklist_ui
|
|||
$prop['alarms'] = $this->plugin->driver->alarms;
|
||||
$prop['undelete'] = $this->plugin->driver->undelete;
|
||||
$prop['sortable'] = $this->plugin->driver->sortable;
|
||||
$prop['attachments'] = $this->plugin->driver->attachments;
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
$html_id = html_identifier($id);
|
||||
|
@ -234,4 +245,77 @@ class tasklist_ui
|
|||
return html::div($attrib, $input->show(''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML element for attachments list
|
||||
*/
|
||||
function attachments_list($attrib = array())
|
||||
{
|
||||
if (!$attrib['id'])
|
||||
$attrib['id'] = 'rcmAttachmentList';
|
||||
|
||||
$this->rc->output->add_gui_object('attachmentlist', $attrib['id']);
|
||||
|
||||
return html::tag('ul', $attrib, '', html::$common_attrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the form for event attachments upload
|
||||
*/
|
||||
function attachments_form($attrib = array())
|
||||
{
|
||||
// add ID if not given
|
||||
if (!$attrib['id'])
|
||||
$attrib['id'] = 'rcmUploadForm';
|
||||
|
||||
// Get max filesize, enable upload progress bar
|
||||
$max_filesize = rcube_upload_init();
|
||||
|
||||
$button = new html_inputfield(array('type' => 'button'));
|
||||
$input = new html_inputfield(array(
|
||||
'type' => 'file',
|
||||
'name' => '_attachments[]',
|
||||
'multiple' => 'multiple',
|
||||
'size' => $attrib['attachmentfieldsize'],
|
||||
));
|
||||
|
||||
return html::div($attrib,
|
||||
html::div(null, $input->show()) .
|
||||
html::div('formbuttons', $button->show(rcube_label('upload'), array('class' => 'button mainaction',
|
||||
'onclick' => JS_OBJECT_NAME . ".upload_file(this.form)"))) .
|
||||
html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register UI object for HTML5 drag & drop file upload
|
||||
*/
|
||||
function file_drop_area($attrib = array())
|
||||
{
|
||||
if ($attrib['id']) {
|
||||
$this->rc->output->add_gui_object('filedrop', $attrib['id']);
|
||||
$this->rc->output->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function attachment_controls($attrib = array())
|
||||
{
|
||||
$table = new html_table(array('cols' => 3));
|
||||
|
||||
if (!empty($this->plugin->attachment['name'])) {
|
||||
$table->add('title', Q(rcube_label('filename')));
|
||||
$table->add('header', Q($this->plugin->attachment['name']));
|
||||
$table->add('download-link', html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))));
|
||||
}
|
||||
|
||||
if (!empty($this->plugin->attachment['size'])) {
|
||||
$table->add('title', Q(rcube_label('filesize')));
|
||||
$table->add('header', Q(show_bytes($this->plugin->attachment['size'])));
|
||||
}
|
||||
|
||||
return $table->show($attrib);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue