2 commits - plugins/calendar plugins/tasklist
Thomas Brüderli
bruederli at kolabsys.com
Wed Aug 1 15:52:36 CEST 2012
plugins/calendar/calendar.php | 1
plugins/calendar/drivers/kolab/kolab_calendar.php | 1
plugins/calendar/lib/calendar_ui.php | 2
plugins/calendar/skins/classic/calendar.css | 2
plugins/calendar/skins/larry/calendar.css | 2
plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php | 106 ++++++
plugins/tasklist/drivers/tasklist_driver.php | 28 +
plugins/tasklist/localization/de_CH.inc | 5
plugins/tasklist/localization/en_US.inc | 5
plugins/tasklist/skins/larry/tasklist.css | 79 ++++
plugins/tasklist/skins/larry/templates/attachment.html | 36 ++
plugins/tasklist/skins/larry/templates/mainview.html | 4
plugins/tasklist/skins/larry/templates/taskedit.html | 84 +++--
plugins/tasklist/tasklist.js | 141 ++++++++
plugins/tasklist/tasklist.php | 247 +++++++++++++++
plugins/tasklist/tasklist_ui.php | 84 +++++
16 files changed, 778 insertions(+), 49 deletions(-)
New commits:
commit 05a92b0b928a3e31f9c4cc56b0c51ac428d16f43
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed Aug 1 15:52:28 2012 +0200
Add support for task attachments (#895)
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index 7322e1f..c3a220a 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -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;
}
@@ -481,6 +535,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;
+ }
+
+ /**
*
*/
public function tasklist_edit_form($formfields)
diff --git a/plugins/tasklist/drivers/tasklist_driver.php b/plugins/tasklist/drivers/tasklist_driver.php
index c86eca8..924da21 100644
--- a/plugins/tasklist/drivers/tasklist_driver.php
+++ b/plugins/tasklist/drivers/tasklist_driver.php
@@ -175,6 +175,34 @@ abstract class 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) { }
+
+ /**
+ * 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
*/
diff --git a/plugins/tasklist/localization/de_CH.inc b/plugins/tasklist/localization/de_CH.inc
index a702a70..9d21692 100644
--- a/plugins/tasklist/localization/de_CH.inc
+++ b/plugins/tasklist/localization/de_CH.inc
@@ -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...';
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index ccf9358..ba7be2e 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -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...';
diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css
index ad7d05a..32eee74 100644
--- a/plugins/tasklist/skins/larry/tasklist.css
+++ b/plugins/tasklist/skins/larry/tasklist.css
@@ -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
diff --git a/plugins/tasklist/skins/larry/templates/attachment.html b/plugins/tasklist/skins/larry/templates/attachment.html
new file mode 100644
index 0000000..4d4789d
--- /dev/null
+++ b/plugins/tasklist/skins/larry/templates/attachment.html
@@ -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>
+
diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html
index 114b7ec..773badf 100644
--- a/plugins/tasklist/skins/larry/templates/mainview.html
+++ b/plugins/tasklist/skins/larry/templates/mainview.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" />
diff --git a/plugins/tasklist/skins/larry/templates/taskedit.html b/plugins/tasklist/skins/larry/templates/taskedit.html
index cdce896..8cad89b 100644
--- a/plugins/tasklist/skins/larry/templates/taskedit.html
+++ b/plugins/tasklist/skins/larry/templates/taskedit.html
@@ -1,39 +1,55 @@
-<div id="taskedit">
+<div id="taskedit" class="uidialog uidialog-tabbed">
<form id="taskeditform" action="#" method="post" enctype="multipart/form-data">
- <div class="form-section">
- <label for="edit-title"><roundcube:label name="tasklist.title" /></label>
- <br />
- <input type="text" class="text" name="title" id="edit-title" size="60" tabindex="1" />
+ <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 />
+ <input type="text" class="text" name="title" id="edit-title" size="60" tabindex="1" />
+ </div>
+ <div class="form-section">
+ <label for="edit-description"><roundcube:label name="tasklist.description" /></label>
+ <br />
+ <textarea name="description" id="edit-description" class="text" rows="5" cols="60" tabindex="2"></textarea>
+ </div>
+ <div class="form-section">
+ <label for="edit-tags"><roundcube:label name="tasklist.tags" /></label>
+ <roundcube:object name="plugin.tags_editline" id="edit-tagline" class="tagedit" tabindex="3" />
+ </div>
+ <div class="form-section">
+ <label for="edit-date"><roundcube:label name="tasklist.datetime" /></label>
+ <input type="text" name="date" size="10" id="edit-date" tabindex="20" />
+ <input type="text" name="time" size="6" id="edit-time" tabindex="21" />
+ <a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#edit-date,#edit-time"><roundcube:label name="tasklist.nodate" /></a>
+ </div>
+ <div class="form-section">
+ <label for="edit-startdate"><roundcube:label name="tasklist.start" /></label>
+ <input type="text" name="startdate" size="10" id="edit-startdate" tabindex="23" />
+ <input type="text" name="starttime" size="6" id="edit-starttime" tabindex="24" />
+ <a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#edit-startdate,#edit-starttime"><roundcube:label name="tasklist.nodate" /></a>
+ </div>
+ <div class="form-section">
+ <label for="edit-completeness"><roundcube:label name="tasklist.complete" /></label>
+ <input type="text" name="title" id="edit-completeness" size="3" tabindex="25" /> %
+ <div id="edit-completeness-slider"></div>
+ </div>
+ <div class="form-section" id="tasklist-select">
+ <label for="edit-tasklist"><roundcube:label name="tasklist.list" /></label>
+ <roundcube:object name="plugin.tasklist_select" id="edit-tasklist" tabindex="26" />
+ </div>
</div>
- <div class="form-section">
- <label for="edit-description"><roundcube:label name="tasklist.description" /></label>
- <br />
- <textarea name="description" id="edit-description" class="text" rows="5" cols="60" tabindex="2"></textarea>
- </div>
- <div class="form-section">
- <label for="edit-tags"><roundcube:label name="tasklist.tags" /></label>
- <roundcube:object name="plugin.tags_editline" id="edit-tagline" class="tagedit" tabindex="3" />
- </div>
- <div class="form-section">
- <label for="edit-date"><roundcube:label name="tasklist.datetime" /></label>
- <input type="text" name="date" size="10" id="edit-date" tabindex="20" />
- <input type="text" name="time" size="6" id="edit-time" tabindex="21" />
- <a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#edit-date,#edit-time"><roundcube:label name="tasklist.nodate" /></a>
- </div>
- <div class="form-section">
- <label for="edit-startdate"><roundcube:label name="tasklist.start" /></label>
- <input type="text" name="startdate" size="10" id="edit-startdate" tabindex="23" />
- <input type="text" name="starttime" size="6" id="edit-starttime" tabindex="24" />
- <a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#edit-startdate,#edit-starttime"><roundcube:label name="tasklist.nodate" /></a>
- </div>
- <div class="form-section">
- <label for="edit-completeness"><roundcube:label name="tasklist.complete" /></label>
- <input type="text" name="title" id="edit-completeness" size="3" tabindex="25" /> %
- <div id="edit-completeness-slider"></div>
- </div>
- <div class="form-section" id="tasklist-select">
- <label for="edit-tasklist"><roundcube:label name="tasklist.list" /></label>
- <roundcube:object name="plugin.tasklist_select" id="edit-tasklist" tabindex="26" />
+ <!-- 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>
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 39636d6..e5a874a 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -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() {
@@ -748,7 +763,7 @@ function rcube_tasklist_ui(settings)
closeOnEscape: true,
title: rcmail.gettext('taskdetails', 'tasklist'),
close: function() {
- $dialog.dialog('destroy').appendTo(document.body);
+ $dialog.dialog('destroy').appendTo(document.body);
},
buttons: buttons,
minWidth: 500,
@@ -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,8 +874,14 @@ 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;
+ me.selected_task._fromlist = rec.list;
me.selected_task.complete = complete.val() / 100;
if (isNaN(me.selected_task.complete))
@@ -866,8 +912,8 @@ function rcube_tasklist_ui(settings)
closeOnEscape: false,
title: rcmail.gettext((action == 'edit' ? 'edittask' : 'newtask'), 'tasklist'),
close: function() {
- editform.hide().appendTo(document.body);
- $dialog.dialog('destroy').remove();
+ editform.hide().appendTo(document.body);
+ $dialog.dialog('destroy').remove();
},
buttons: buttons,
minHeight: 340,
@@ -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];
+ };
+
/**
*
*/
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 4247e14..6dec0d4 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -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()
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index 43f79c9..370237e 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -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);
+ }
+
}
commit a16a9c581079516e100c00d24ede77efc3bcc038
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed Aug 1 15:47:50 2012 +0200
Small code and style cleanup
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 77719c7..335c672 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1469,7 +1469,6 @@ class calendar extends rcube_plugin
}
$event = get_input_value('_id', RCUBE_INPUT_GPC);
- $calendar = get_input_value('calendar', RCUBE_INPUT_GPC);
$uploadid = get_input_value('_uploadid', RCUBE_INPUT_GPC);
$eventid = 'cal:'.$event;
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 91ba5b8..813b391 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -462,7 +462,6 @@ class kolab_calendar
// in kolab_storage attachments are indexed by content-id
$object['_attachments'] = array();
if (is_array($event['attachments'])) {
- $collisions = array();
foreach ($event['attachments'] as $idx => $attachment) {
$key = null;
// Roundcube ID has nothing to do with the storage ID, remove it
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index a063a4d..1cbcf0a 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -623,7 +623,7 @@ class calendar_ui
return html::div($attrib,
html::div(null, $input->show()) .
- html::div('buttons', $button->show(rcube_label('upload'), array('class' => 'button mainaction',
+ 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))))
);
diff --git a/plugins/calendar/skins/classic/calendar.css b/plugins/calendar/skins/classic/calendar.css
index 21dcb1b..e4afea4 100644
--- a/plugins/calendar/skins/classic/calendar.css
+++ b/plugins/calendar/skins/classic/calendar.css
@@ -364,7 +364,7 @@ a.miniColors-trigger {
padding-top: 1.2em;
}
-#edit-attachments-form .buttons {
+#edit-attachments-form .formbuttons {
margin: 0.5em 0;
}
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index d0d108d..6765211 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -371,7 +371,7 @@ a.miniColors-trigger {
border-top: 2px solid #fafafa;
}
-#edit-attachments-form .buttons {
+#edit-attachments-form .formbuttons {
margin: 0.5em 0;
}
More information about the commits
mailing list