Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube

This commit is contained in:
Thomas Bruederli 2011-07-04 12:50:57 +02:00
commit 1f7d51be85
12 changed files with 886 additions and 67 deletions

View file

@ -5,7 +5,7 @@
+ Edit: 3.16: Reminder set
+ Edit: 3.17: Priority: High/Low
- Edit: 3.18: Recurrence (in line with Kontact)
- Edit: 3.19: Attachment Upload
+ Edit: 3.19: Attachment Upload
- Edit: 3.20: Print
- Add/Manage Attendees
- Edit: 3.21: Required / Optional / Resource specification

View file

@ -106,6 +106,8 @@ class calendar extends rcube_plugin
$this->register_action('load_events', array($this, 'load_events'));
$this->register_action('search_events', array($this, 'search_events'));
$this->register_action('export_events', array($this, 'export_events'));
$this->register_action('upload', array($this, 'attachment_upload'));
$this->register_action('get-attachment', array($this, 'attachment_get'));
$this->register_action('randomdata', array($this, 'generate_randomdata'));
}
else if ($this->rc->task == 'settings') {
@ -156,7 +158,7 @@ class calendar extends rcube_plugin
// Add JS files to the page header
$this->ui->addJS();
$this->register_handler('plugin.calendar_css', array($this->ui, 'calendar_css'));
$this->register_handler('plugin.calendar_list', array($this->ui, 'calendar_list'));
$this->register_handler('plugin.calendar_select', array($this->ui, 'calendar_select'));
@ -167,14 +169,16 @@ class calendar extends rcube_plugin
$this->register_handler('plugin.alarm_select', array($this->ui, 'alarm_select'));
$this->register_handler('plugin.snooze_select', array($this->ui, 'snooze_select'));
$this->register_handler('plugin.recurrence_form', array($this->ui, 'recurrence_form'));
$this->register_handler('plugin.attachments_form', array($this->ui, 'attachments_form'));
$this->register_handler('plugin.attachments_list', array($this->ui, 'attachments_list'));
$this->register_handler('plugin.edit_recurring_warning', array($this->ui, 'recurring_event_warning'));
$this->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template
$this->rc->output->add_label('low','normal','high');
$this->rc->output->add_label('low','normal','high','delete','cancel','uploading');
$this->rc->output->send("calendar.calendar");
}
/**
* Handler for preferences_sections_list hook.
* Adds Calendar settings sections into preferences sections list.
@ -426,16 +430,20 @@ class calendar extends rcube_plugin
$action = get_input_value('action', RCUBE_INPUT_POST);
$event = get_input_value('e', RCUBE_INPUT_POST);
$success = $reload = false;
switch ($action) {
case "new":
// create UID for new event
$event['uid'] = $this->generate_uid();
$success = $this->driver->new_event($event);
$this->prepare_event($event);
if ($success = $this->driver->new_event($event))
$this->cleanup_event($event);
$reload = true;
break;
case "edit":
$success = $this->driver->edit_event($event);
$this->prepare_event($event);
if ($success = $this->driver->edit_event($event))
$this->cleanup_event($event);
$reload = true;
break;
case "resize":
@ -616,7 +624,7 @@ class calendar extends rcube_plugin
$event['alarms_text'] = $this->_alarms_text($event['alarms']);
if ($event['recurrence'])
$event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
$json[] = array(
'start' => gmdate('c', $this->fromGMT($event['start'])), // client treats date strings as they were in users's timezone
'end' => gmdate('c', $this->fromGMT($event['end'])), // so shift timestamps to users's timezone and render a date string
@ -842,4 +850,250 @@ class calendar extends rcube_plugin
$this->rc->output->redirect('');
}
/**
* Handler for attachments upload
*/
public function attachment_upload()
{
$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 = $calendar.':'.$event;
if (!is_array($_SESSION['event_session']) || $_SESSION['event_session']['id'] != $eventid) {
$_SESSION['event_session'] = array();
$_SESSION['event_session']['id'] = $eventid;
$_SESSION['event_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['event_session']['attachments'][$id] = $attachment;
if (($icon = $_SESSION['calendar_deleteicon']) && is_file($icon)) {
$button = html::img(array(
'src' => $icon,
'alt' => rcube_label('delete')
));
}
else {
$button = Q(rcube_label('delete'));
}
$content = html::a(array(
'href' => "#delete",
'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", JS_OBJECT_NAME, $id),
'title' => rcube_label('delete'),
), $button);
$content .= Q($attachment['name']);
$this->rc->output->command('add2attachment_list', "rcmfile$id", array(
'html' => $content,
'name' => $attachment['name'],
'mimetype' => $attachment['mimetype'],
'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()
{
$event = get_input_value('_event', RCUBE_INPUT_GPC);
$calendar = get_input_value('_cal', RCUBE_INPUT_GPC);
$id = get_input_value('_id', RCUBE_INPUT_GPC);
$event = array('id' => $event, 'calendar' => $calendar);
// 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();
send_nocacheing_headers();
if (isset($_SESSION['calendar_attachment']))
$attachment = $_SESSION['calendar_attachment'];
else
$attachment = $_SESSION['calendar_attachment'] = $this->driver->get_attachment($id, $event);
// show part page
if (!empty($_GET['_frame'])) {
$this->rc->output->add_handlers(array('attachmentframe' => array($this, 'attachment_frame')));
$this->rc->output->send('calendar.attachment');
exit;
}
unset($_SESSION['calendar_attachment']);
if ($attachment) {
$mimetype = strtolower($attachment['mimetype']);
list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
$browser = $this->rc->output->browser;
// send download headers
if ($_GET['_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 {
// $mimetype = rcmail_fix_mimetype($mimetype);
header("Content-Type: $mimetype");
header("Content-Transfer-Encoding: binary");
}
$body = $this->driver->get_attachment_body($id, $event);
// 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($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\"");
}
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 = $_SESSION['calendar_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);
}
/**
* Prepares new/edited event properties before save
*/
private function prepare_event(&$event)
{
$eventid = $event['calendar'].':'.$event['id'];
$attachments = array();
if (is_array($_SESSION['event_session']) && $_SESSION['event_session']['id'] == $eventid) {
if (!empty($_SESSION['event_session']['attachments'])) {
foreach ($_SESSION['event_session']['attachments'] as $id => $attachment) {
if (is_array($event['attachments']) && in_array($id, $event['attachments'])) {
$attachments[$id] = $attachment;
}
}
}
}
$event['attachments'] = $attachments;
}
/**
* Releases some resources after successful event save
*/
private function cleanup_event(&$event)
{
// remove temp. attachment files
if (!empty($_SESSION['event_session']) && ($eventid = $_SESSION['event_session']['id'])) {
$this->rc->plugins->exec_hook('attachments_cleanup', array('group' => $eventid));
unset($_SESSION['event_session']);
}
}
}

View file

@ -114,6 +114,78 @@ function rcube_calendar_ui(settings)
return fromto;
};
var load_attachment = function(event, att)
{
var qstring = '_id='+urlencode(att.id)+'&_event='+urlencode(event.id)+'&_cal='+urlencode(event.calendar);
// open attachment in frame if it's of a supported mimetype
if (id && att.mimetype && $.inArray(att.mimetype, rcmail.mimetypes)>=0) {
rcmail.attachment_win = window.open(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', 'rcubeeventattachment');
if (rcmail.attachment_win) {
window.setTimeout(function() { rcmail.attachment_win.focus(); }, 10);
return;
}
}
rcmail.goto_url('get-attachment', qstring+'&_download=1', false);
};
// build event attachments list
var event_show_attachments = function(list, container, event, edit)
{
var i, id, len, img, content, li, elem,
ul = document.createElement('UL');
for (i=0, len=list.length; i<len; i++) {
li = document.createElement('LI');
elem = list[i];
if (edit) {
rcmail.env.attachments[elem.id] = elem;
// delete icon
content = document.createElement('A');
content.href = '#delete';
content.title = rcmail.gettext('delete');
$(content).click({id: elem.id}, function(e) { remove_attachment(this, e.data.id); return false; });
if (!rcmail.env.deleteicon)
content.innerHTML = rcmail.gettext('delete');
else {
img = document.createElement('IMG');
img.src = rcmail.env.deleteicon;
img.alt = rcmail.gettext('delete');
content.appendChild(img);
}
li.appendChild(content);
}
// name/link
content = document.createElement('A');
content.innerHTML = list[i].name;
content.href = '#load';
$(content).click({event: event, att: elem}, function(e) {
load_attachment(e.data.event, e.data.att); return false; });
li.appendChild(content);
ul.appendChild(li);
}
if (edit && rcmail.gui_objects.attachmentlist) {
ul.id = rcmail.gui_objects.attachmentlist.id;
rcmail.gui_objects.attachmentlist = ul;
}
container.empty().append(ul);
};
var remove_attachment = function(elem, id)
{
$(elem.parentNode).hide();
rcmail.env.deleted_attachments.push(id);
delete rcmail.env.attachments[id];
};
// event details dialog (show only)
var event_show_dialog = function(event)
{
@ -151,7 +223,18 @@ function rcube_calendar_ui(settings)
var sensitivitylabels = { 0:rcmail.gettext('public'), 1:rcmail.gettext('private'), 2:rcmail.gettext('confidential') };
$('#event-sensitivity').show().children('.event-text').html(Q(sensitivitylabels[event.sensitivity]));
}
// create attachments list
if ($.isArray(event.attachments)) {
event_show_attachments(event.attachments, $('#event-attachments').children('.event-text'), event);
if (event.attachments.length > 0) {
$('#event-attachments').show();
}
}
else if (calendar.attachments) {
// fetch attachments, some drivers doesn't set 'attachments' popr of the event
}
var buttons = {};
if (calendar.editable && event.editable !== false) {
buttons[rcmail.gettext('edit', 'calendar')] = function() {
@ -306,7 +389,24 @@ function rcube_calendar_ui(settings)
}
else
$('#edit-recurring-warning').hide();
// attachments
if (calendar.attachments) {
rcmail.enable_command('remove-attachment', !calendar.readonly);
rcmail.env.deleted_attachments = [];
// we're sharing some code for uploads handling with app.js
rcmail.env.attachments = [];
rcmail.env.compose_id = event.id; // for rcmail.async_upload_form()
if ($.isArray(event.attachments)) {
event_show_attachments(event.attachments, $('#edit-attachments'), event, true);
}
else {
$('#edit-attachments > ul').empty();
// fetch attachments, some drivers doesn't set 'attachments' array for event
}
}
// buttons
var buttons = {};
@ -334,9 +434,10 @@ function rcube_calendar_ui(settings)
priority: priority.val(),
sensitivity: sensitivity.val(),
recurrence: '',
alarms: ''
alarms: '',
deleted_attachments: rcmail.env.deleted_attachments
};
// serialize alarm settings
// TODO: support multiple alarm entries
var alarm = $('select.edit-alarm-type').val();
@ -347,7 +448,14 @@ function rcube_calendar_ui(settings)
else if ((val = parseInt($('input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
data.alarms = offset[0] + val + offset[1] + ':' + alarm;
}
// uploaded attachments list
var attachments = [];
for (var i in rcmail.env.attachments)
if (i.match(/^rcmfile([0-9a-z]+)/))
attachments.push(RegExp.$1);
data.attachments = attachments;
// gather recurrence settings
var freq;
if ((freq = recurrence.val()) != '') {
@ -1065,7 +1173,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
// configure toobar buttons
rcmail.register_command('addevent', function(){ cal.add_event(); }, true);
// configure list operations
rcmail.register_command('calendar-create', function(){ cal.calendar_edit_dialog(null); }, true);
rcmail.register_command('calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false);

View file

@ -51,6 +51,13 @@
* 'sensitivity' => 0|1|2, // Event sensitivity (0=public, 1=private, 2=confidential)
* 'alarms' => '-15M:DISPLAY', // Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
* 'savemode' => 'all|future|current|new', // How changes on recurring event should be handled
* 'attachments' => array( // List of attachments
* 'name' => 'File name',
* 'mimetype' => 'Content type',
* 'size' => 1..n, // in bytes
* 'id' => 'Attachment identifier'
* ),
* 'deleted_attachments' => array(), // array of attachment identifiers to delete when event is updated
* );
*/
@ -196,14 +203,49 @@ abstract class calendar_driver
abstract function dismiss_alarm($event_id, $snooze = 0);
/**
* Save an attachment related to the given event
* Get list of event's attachments.
* Drivers can return list of attachments as event property.
* If they will do not do this list_attachments() method will be used.
*
* @param array $event Hash array with event properties:
* id: Event identifier
* calendar: Calendar identifier
*
* @return array List of attachments, each as hash array:
* id: Attachment identifier
* name: Attachment name
* mimetype: MIME content type of the attachment
* size: Attachment size
*/
public function add_attachment($attachment, $event_id) { }
public function list_attachments($event) { }
/**
* Remove a specific attachment from the given event
* Get attachment properties
*
* @param string $id Attachment identifier
* @param array $event Hash array with event properties:
* id: Event identifier
* calendar: Calendar 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 remove_attachment($attachment, $event_id) { }
public function get_attachment($id, $event) { }
/**
* Get attachment body
*
* @param string $id Attachment identifier
* @param array $event Hash array with event properties:
* id: Event identifier
* calendar: Calendar identifier
*
* @return string Attachment body
*/
public function get_attachment_body($id, $event) { }
/**
* List availabale categories

View file

@ -156,22 +156,15 @@ class database_driver extends calendar_driver
{
if (!$this->calendars[$prop['id']])
return false;
// delete all events of this calendar
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_events . "
WHERE calendar_id=?",
$prop['id']
);
// TODO: also delete linked attachments
// events and attachments will be deleted by foreign key cascade
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_calendars . "
WHERE calendar_id=?",
$prop['id']
);
return $this->rc->db->affected_rows($query);
}
@ -213,11 +206,28 @@ class database_driver extends calendar_driver
$event['alarms'],
$event['notifyat']
);
if ($success = $this->rc->db->insert_id($this->sequence_events))
$event_id = $this->rc->db->insert_id($this->sequence_events);
if ($event_id) {
// add attachments
if (!empty($event['attachments'])) {
foreach ($event['attachments'] as $attachment) {
$attachment = $this->rc->plugins->exec_hook('attachment_get', $attachment);
if (!$attachment['data']) {
$attachments['data'] = file_get_contents($attachment['path']);
}
$this->add_attachment($attachment, $event_id);
unset($attachment);
}
}
$this->_update_recurring($event);
return $success;
}
return $event_id;
}
return false;
@ -403,11 +413,33 @@ class database_driver extends calendar_driver
),
$event['id']
);
$success = $this->rc->db->affected_rows($query);
// add attachments
if ($success && !empty($event['attachments'])) {
foreach ($event['attachments'] as $attachment) {
$attachment = $this->rc->plugins->exec_hook('attachment_get', $attachment);
if (!$attachment['data']) {
$attachments['data'] = file_get_contents($attachment['path']);
}
$this->add_attachment($attachment, $event['id']);
unset($attachment);
}
}
// remove attachments
if ($success && !empty($event['deleted_attachments'])) {
foreach ($event['deleted_attachments'] as $attachment) {
$this->remove_attachment($attachment, $event['id']);
}
}
if ($success && $update_recurring)
$this->_update_recurring($event);
return $success;
}
@ -736,19 +768,110 @@ class database_driver extends calendar_driver
/**
* Save an attachment related to the given event
*/
public function add_attachment($attachment, $event_id)
private function add_attachment($attachment, $event_id)
{
// TBD.
return false;
$query = $this->rc->db->query(sprintf(
"INSERT INTO " . $this->db_attachments .
" (event_id, filename, mimetype, size, data)" .
" VALUES (?, ?, ?, ?, ?)",
$event_id,
$attachment['name'],
$attachment['mimetype'],
strlen($attachment['data']),
base64_encode($attachment['data']),
);
return $this->rc->db->affected_rows($query);
}
/**
* Remove a specific attachment from the given event
*/
public function remove_attachment($attachment, $event_id)
private function remove_attachment($attachment_id, $event_id)
{
// TBD.
return false;
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_attachments .
" WHERE attachment_id = ?" .
" AND event_id IN (SELECT event_id FROM " . $this->db_events .
" WHERE event_id = ?" .
" AND calendar_id IN (" . $this->calendar_ids . "))",
$attachment_id,
$event_id
);
return $this->rc->db->affected_rows($query);
}
/**
* List attachments of specified event
*/
public function list_attachments($event)
{
$attachments = array();
if (!empty($this->rc->user->ID)) {
$result = $this->rc->db->query(
"SELECT attachment_id AS id, filename AS name, mimetype, size " .
" FROM " . $this->db_attachments .
" WHERE user_id=?".
" AND event_id=?".
"ORDER BY filename",
$this->rc->user->ID,
$event['id']
);
while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
$attachments[] = $arr;
}
}
return $attachments;
}
/**
* Get attachment properties
*/
public function get_attachment($id, $event)
{
if (!empty($this->rc->user->ID)) {
$result = $this->rc->db->query(
"SELECT attachment_id AS id, filename AS name, mimetype, size " .
" FROM " . $this->db_attachments .
" WHERE attachment_id=?".
" AND event_id=?".
$id,
$event['id']
);
if ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
return $arr;
}
}
return null;
}
/**
* Get attachment body
*/
public function get_attachment_body($id, $event)
{
if (!empty($this->rc->user->ID)) {
$result = $this->rc->db->query(
"SELECT data " .
" FROM " . $this->db_attachments .
" WHERE attachment_id=?".
" AND event_id=?".
$id,
$event['id']
);
if ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
return base64_decode($arr['data']);
}
}
return null;
}
/**

View file

@ -23,6 +23,7 @@ class kolab_calendar
public $id;
public $ready = false;
public $readonly = true;
public $attachments = true;
private $cal;
private $storage;
@ -145,6 +146,15 @@ class kolab_calendar
}
/**
* Getter for the attachment body
*/
public function get_attachment_body($id)
{
return $this->storage->getAttachment($id);
}
/**
* Getter for a single event object
*/
@ -206,7 +216,7 @@ class kolab_calendar
$events = array_merge($events, $this->_get_recurring_events($event, $start, $end));
}
}
return $events;
}
@ -222,7 +232,7 @@ class kolab_calendar
{
if (!is_array($event))
return false;
//generate new event from RC input
$object = $this->_from_rcube_event($event);
$saved = $this->storage->save($object);
@ -415,10 +425,24 @@ class kolab_calendar
$rrule['EXDATE'][] = strtotime($excl . date(' H:i:s', $rec['start-date'])); // use time of event start
}
}
$sensitivity_map = array_flip($this->sensitivity_map);
$priority_map = array_flip($this->priority_map);
// @TODO: Horde code assumes that there will be no more than
// one file with the same name, while this is not required by MIME format
// and not forced by the Calendar UI
if (!empty($rec['_attachments'])) {
foreach ($rec['_attachments'] as $name => $attachment) {
// @TODO: 'type' and 'key' are the only supported (no 'size')
$attachments[] = array(
'id' => $attachment['key'],
'mimetype' => $attachment['type'],
'name' => $name,
);
}
}
return array(
'id' => $rec['uid'],
'uid' => $rec['uid'],
@ -431,6 +455,7 @@ class kolab_calendar
'recurrence' => $rrule,
'alarms' => $alarm_value . $alarm_unit,
'categories' => $rec['categories'],
'attachments' => $attachments,
'free_busy' => $rec['show-time-as'],
'priority' => isset($priority_map[$rec['priority']]) ? $priority_map[$rec['priority']] : 1,
'sensitivity' => $sensitivity_map[$rec['sensitivity']],
@ -555,7 +580,18 @@ class kolab_calendar
$object['start-date'] += $tz_offset - date('Z'); // because Horde_Kolab_Format_Date::encodeDate() uses strftime()
$object['_is_all_day'] = 1;
}
// in Horde attachments are indexed by name
$object['_attachments'] = array();
if (!empty($event['attachments'])) {
foreach ($event['attachments'] as $idx => $attachment) {
// Roundcube ID has nothing to Horde ID, remove it
unset($attachment['id']);
$object['_attachments'][$attachment['name']] = $attachment;
unset($event['attachments'][$idx]);
}
}
return $object;
}

View file

@ -26,7 +26,7 @@ class kolab_driver extends calendar_driver
// features this backend supports
public $alarms = true;
public $attendees = false;
public $attachments = false;
public $attachments = true;
public $categoriesimmutable = true;
private $rc;
@ -229,8 +229,20 @@ class kolab_driver extends calendar_driver
public function new_event($event)
{
$cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars));
if ($storage = $this->calendars[$cid])
if ($storage = $this->calendars[$cid]) {
// handle attachments to add
if (!empty($event['attachments'])) {
foreach ($event['attachments'] as $idx => $attachment) {
// we'll read file contacts into memory, Horde/Kolab classes does the same
// So we cannot save memory, rcube_imap class can do this better
$attachment = $this->cal->rc->plugins->exec_hook('attachment_get', $attachment);
$event['attachments'][$idx]['content'] = $attachment['data'] ? $attachment['data'] : file_get_contents($attachment['path']);
}
}
return $storage->insert_event($event);
}
return false;
}
@ -327,21 +339,52 @@ class kolab_driver extends calendar_driver
{
if (!($storage = $this->calendars[$event['calendar']]))
return false;
$success = false;
$savemode = 'all';
$attachments = array();
$old = $master = $storage->get_event($event['id']);
// delete existing attachment(s)
if (!empty($event['deleted_attachments'])) {
foreach ($event['deleted_attachments'] as $attachment) {
if (!empty($old['attachments'])) {
foreach ($old['attachments'] as $idx => $att) {
if ($att['id'] == $attachment) {
unset($old['attachments'][$idx]);
}
}
}
}
}
// handle attachments to add
if (!empty($event['attachments'])) {
foreach ($event['attachments'] as $attachment) {
// we'll read file contacts into memory, Horde/Kolab classes does the same
// So we cannot save memory, rcube_imap class can do this better
$attachment = $this->cal->rc->plugins->exec_hook('attachment_get', $attachment);
$attachments[] = array(
'name' => $attachment['name'],
'type' => $attachment['mimetype'],
'content' => $attachment['data'] ? $attachment['data'] : file_get_contents($attachment['path']),
);
}
}
$event['attachments'] = array_merge($old['attachments'], $attachments);
// modify a recurring event, check submitted savemode to do the right things
if ($old['recurrence'] || $old['recurrence_id']) {
$master = $old['recurrence_id'] ? $storage->get_event($old['recurrence_id']) : $old;
$savemode = $event['savemode'];
}
// keep saved exceptions (not submitted by the client)
if ($old['recurrence']['EXDATE'])
$event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
switch ($savemode) {
case 'new':
// save submitted data as new (non-recurring) event
@ -525,23 +568,51 @@ class kolab_driver extends calendar_driver
return $this->rc->db->affected_rows($query);
}
/**
* Save an attachment related to the given event
* List attachments from the given event
*/
public function add_attachment($attachment, $event_id)
public function list_attachments($event)
{
if (!($storage = $this->calendars[$event['calendar']]))
return false;
$event = $storage->get_event($event['id']);
return $event['attachments'];
}
/**
* Remove a specific attachment from the given event
* Get attachment properties
*/
public function remove_attachment($attachment, $event_id)
public function get_attachment($id, $event)
{
if (!($storage = $this->calendars[$event['calendar']]))
return false;
$event = $storage->get_event($event['id']);
if ($event && !empty($event['attachments'])) {
foreach ($event['attachments'] as $att) {
if ($att['id'] == $id) {
return $att;
}
}
}
return null;
}
/**
* Get attachment body
*/
public function get_attachment_body($id, $event)
{
if (!($storage = $this->calendars[$event['calendar']]))
return false;
return $storage->get_attachment_body($id);
}
/**
* List availabale categories

View file

@ -461,4 +461,60 @@ class calendar_ui
return $select_prefix->show() . '&nbsp;' . $select_wday->show();
}
}
/**
* Generate the form for event attachments upload
*/
function attachments_form($attrib = array())
{
// add ID if not given
if (!$attrib['id'])
$attrib['id'] = 'rcmUploadForm';
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize)
$max_filesize = $max_postsize;
$this->rc->output->set_env('max_filesize', $max_filesize);
$max_filesize = show_bytes($max_filesize);
$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('buttons', $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))))
);
}
/**
* Generate HTML element for attachments list
*/
function attachments_list($attrib = array())
{
if (!$attrib['id'])
$attrib['id'] = 'rcmAttachmentList';
$skin_path = $this->rc->config->get('skin_path');
if ($attrib['deleteicon']) {
$_SESSION['calendar_deleteicon'] = $skin_path . $attrib['deleteicon'];
$this->rc->output->set_env('deleteicon', $skin_path . $attrib['deleteicon']);
}
if ($attrib['cancelicon'])
$this->rc->output->set_env('cancelicon', $skin_path . $attrib['cancelicon']);
if ($attrib['loadingicon'])
$this->rc->output->set_env('loadingicon', $skin_path . $attrib['loadingicon']);
$this->rc->output->add_gui_object('attachmentlist', $attrib['id']);
return html::tag('ul', $attrib, '', html::$common_attrib);
}
}

View file

@ -247,6 +247,71 @@ a.miniColors-trigger {
margin-top: -3px;
}
#attachmentcontainer
{
position: absolute;
top: 80px;
left: 20px;
right: 20px;
bottom: 20px;
}
#attachmentframe
{
width: 100%;
height: 100%;
border: 1px solid #999999;
background-color: #F9F9F9;
}
.attachments-list ul
{
margin: 0px;
padding: 0px;
list-style-image: none;
list-style-type: none;
}
.attachments-list ul li
{
height: 18px;
font-size: 12px;
padding-left: 2px;
padding-top: 2px;
padding-right: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
.attachments-list ul li img
{
padding-right: 2px;
vertical-align: middle;
}
.attachments-list ul li a
{
text-decoration: none;
}
.attachments-list ul li a:hover
{
text-decoration: underline;
}
#eventshow .attachments-list ul
{
display: block;
}
#eventshow .attachments-list ul li
{
float: left;
}
/* jQuery UI overrides */
#eventshow h1 {

View file

@ -0,0 +1,52 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="extwin">
<roundcube:include file="/includes/header.html" />
<div id="partheader">
<!--
<roundcube:object name="attachmentControls" cellpadding="2" cellspacing="0" />
-->
<div style="position:absolute; top:2px; right:0; width:12em; text-align:right">
[<a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>]
</div>
</div>
<div id="attachmentcontainer">
<roundcube:object name="attachmentFrame" id="attachmentframe" width="100%" height="100%" />
</div>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="extwin">
<roundcube:include file="/includes/header.html" />
<div id="partheader">
<!--
<roundcube:object name="attachmentControls" cellpadding="2" cellspacing="0" />
-->
<div style="position:absolute; top:2px; right:0; width:12em; text-align:right">
[<a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>]
</div>
</div>
<div id="attachmentcontainer">
<roundcube:object name="attachmentFrame" id="attachmentframe" width="100%" height="100%" />
</div>
</body>
</html>

View file

@ -7,6 +7,7 @@
<script type="text/javascript" src="/functions.js"></script>
</head>
<body class="calendarmain">
<roundcube:include file="/includes/taskbar.html" />
<roundcube:include file="/includes/header.html" />
@ -75,10 +76,14 @@
<label><roundcube:label name="calendar.sensitivity" /></label>
<span class="event-text"></span>
</div>
<div class="event-section" id="event-attachments">
<label><roundcube:label name="attachments" /></label>
<span class="event-text attachments-list"></span>
</div>
</div>
<div id="eventedit">
<form id="eventtabs" action="#">
<form id="eventtabs" action="#" method="post">
<ul>
<li><a href="#event-tab-1"><roundcube:label name="calendar.tabsummary" /></a></li>
<li id="edit-tab-recurrence"><a href="#event-tab-2"><roundcube:label name="calendar.tabrecurrence" /></a></li>
@ -161,14 +166,19 @@
</div>
<!-- attendees list -->
<div id="event-tab-3">
</div>
<!-- attachments list -->
<!-- attachments list (with upload form) -->
<div id="event-tab-4">
<div class="border-after" id="edit-attachments-form">
<roundcube:object name="plugin.attachments_form" id="calendar-attachment-form" attachmentFieldSize="30" />
</div>
<span id="edit-attachments" class="attachments-list">
<roundcube:object name="plugin.attachments_list" id="attachmentlist" deleteIcon="/images/icons/delete.png" cancelIcon="/images/icons/delete.png" loadingIcon="/images/display/loading_blue.gif" />
</span>
</div>
</form>
<roundcube:object name="plugin.edit_recurring_warning" class="edit-recurring-warning" style="display:none" />
</div>
@ -235,4 +245,4 @@ $(document).ready(function(e){
</script>
</body>
</html>
</html>

View file

@ -25,6 +25,8 @@
class kolab_folders extends rcube_plugin
{
public $task = '?(?!login).*';
public $types = array('mail', 'event', 'journal', 'task', 'note', 'contact');
public $mail_types = array('inbox', 'drafts', 'sentitems', 'outbox', 'wastebasket', 'junkemail');
private $rc;