diff --git a/plugins/libkolab/lib/kolab_attachments_handler.php b/plugins/libkolab/lib/kolab_attachments_handler.php index f9996091..e3e08453 100644 --- a/plugins/libkolab/lib/kolab_attachments_handler.php +++ b/plugins/libkolab/lib/kolab_attachments_handler.php @@ -1,454 +1,491 @@ <?php /** * Shared code for attachments handling in Kolab plugins * * @version @package_version@ * @author Thomas Bruederli <bruederli@kolabsys.com> * @author Aleksander Machniak <machniak@kolabsys.com> * * Copyright (C) 2012-2018, Kolab Systems AG <contact@kolabsys.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ class kolab_attachments_handler { private $rc; private $attachment; public function __construct() { $this->rc = rcmail::get_instance(); } public static function ui() { $rcmail = rcmail::get_instance(); $self = new self; $rcmail->output->add_handler('plugin.attachments_form', [$self, 'files_form']); $rcmail->output->add_handler('plugin.attachments_list', [$self, 'files_list']); $rcmail->output->add_handler('plugin.filedroparea', [$self, 'files_drop_area']); } /** * Generate HTML element for attachments list */ public function files_list($attrib = []) { if (empty($attrib['id'])) { $attrib['id'] = 'kolabattachmentlist'; } // define list of file types which can be displayed inline // same as in program/steps/mail/show.inc $this->rc->output->set_env('mimetypes', (array)$this->rc->config->get('client_mimetypes')); $this->rc->output->add_gui_object('attachmentlist', $attrib['id']); return html::tag('ul', $attrib, '', html::$common_attrib); } /** * Generate the form for event attachments upload */ public function files_form($attrib = []) { // add ID if not given if (empty($attrib['id'])) { $attrib['id'] = 'kolabuploadform'; } return $this->rc->upload_form($attrib, 'uploadform', 'upload-file', ['multiple' => true]); } /** * Register UI object for HTML5 drag & drop file upload */ public function files_drop_area($attrib = []) { // add ID if not given if (empty($attrib['id'])) { $attrib['id'] = 'kolabfiledroparea'; } $this->rc->output->add_gui_object('filedrop', $attrib['id']); $this->rc->output->set_env('filedrop', ['action' => 'upload', 'fieldname' => '_attachments']); } /** * Displays attachment preview page */ public function attachment_page($attachment) { $this->attachment = $attachment; $this->rc->plugins->include_script('libkolab/libkolab.js'); $this->rc->output->add_handler('plugin.attachmentframe', [$this, 'attachment_frame']); $this->rc->output->add_handler('plugin.attachmentcontrols', [$this, 'attachment_header']); $this->rc->output->set_env('filename', $attachment['name']); $this->rc->output->set_env('mimetype', $attachment['mimetype']); $this->rc->output->send('libkolab.attachment'); } /** * Handler for attachment uploads */ public function attachment_upload($session_key, $id_prefix = '') { // Upload progress update if (!empty($_GET['_progress'])) { $this->rc->upload_progress(); } + $uploads_api = method_exists($this->rc, 'insert_uploaded_file'); // Roundcube >= 1.7 + $id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC); $uploadid = rcube_utils::get_input_value('_uploadid', rcube_utils::INPUT_GPC); - $recid = $id_prefix . ($id ?: 'new'); + $recid = $uploads_api ? $session_key : ($id_prefix . ($id ?: 'new')); - if (empty($_SESSION[$session_key]) || $_SESSION[$session_key]['id'] != $recid) { - $_SESSION[$session_key] = []; - $_SESSION[$session_key]['id'] = $recid; - $_SESSION[$session_key]['attachments'] = []; + if (!$uploads_api) { + if (empty($_SESSION[$session_key]) || $_SESSION[$session_key]['id'] != $recid) { + $_SESSION[$session_key] = []; + $_SESSION[$session_key]['id'] = $recid; + $_SESSION[$session_key]['attachments'] = []; + } } // 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) { $filename = $_FILES['_attachments']['name'][$i]; $attachment = [ 'path' => $filepath, 'size' => $_FILES['_attachments']['size'][$i], 'name' => $filename, 'mimetype' => rcube_mime::file_content_type($filepath, $filename, $_FILES['_attachments']['type'][$i]), 'group' => $recid, ]; - $attachment = $this->rc->plugins->exec_hook('attachment_upload', $attachment); + if ($uploads_api) { + $err = !$this->rc->insert_uploaded_file($attachment); + } + else { + $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']); - $this->rc->session->append($session_key . '.attachments', $id, $attachment); + + if (!$uploads_api) { + // store new attachment in session + $this->rc->session->append($session_key . '.attachments', $id, $attachment); + } if (!empty($_SESSION[$session_key . '_deleteicon']) && ($icon = $_SESSION[$session_key . '_deleteicon']) && is_file($icon) ) { $button = html::img([ 'src' => $icon, 'alt' => $this->rc->gettext('delete') ]); } else if (!empty($_SESSION[$session_key . '_textbuttons'])) { $button = rcube::Q($this->rc->gettext('delete')); } else { $button = ''; } $link_content = sprintf('<span class="attachment-name">%s</span><span class="attachment-size">(%s)</span>', rcube::Q($attachment['name']), $this->rc->show_bytes($attachment['size'])); $delete_link = html::a([ 'href' => "#delete", 'class' => 'delete', 'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", rcmail_output::JS_OBJECT_NAME, $id), 'title' => $this->rc->gettext('delete'), 'aria-label' => $this->rc->gettext('delete') . ' ' . $attachment['name'], ], $button); $content_link = html::a([ 'href' => "#load", 'class' => 'filename', 'onclick' => 'return false', // sprintf("return %s.command('load-attachment','rcmfile%s', this, event)", rcmail_output::JS_OBJECT_NAME, $id), ], $link_content); $left = !empty($_SESSION[$session_key . '_icon_pos']) && $_SESSION[$session_key . '_icon_pos'] == 'left'; $content = $left ? $delete_link.$content_link : $content_link.$delete_link; $this->rc->output->command('add2attachment_list', "rcmfile$id", [ 'html' => $content, 'name' => $attachment['name'], 'mimetype' => $attachment['mimetype'], 'classname' => 'no-menu ' . rcube_utils::file2class($attachment['mimetype'], $attachment['name']), 'complete' => true ], $uploadid); } else { // upload failed if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) { $msg = $this->rc->gettext(['name' => 'filesizeerror', 'vars' => [ 'size' => $this->rc->show_bytes(parse_bytes(ini_get('upload_max_filesize')))]]); } else if (!empty($attachment['error'])) { $msg = $attachment['error']; } else { $msg = $this->rc->gettext('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 = $this->rc->gettext(['name' => 'filesizeerror', 'vars' => [ 'size' => $this->rc->show_bytes(parse_bytes($maxsize))]]); } else { $msg = $this->rc->gettext('fileuploaderror'); } $this->rc->output->command('display_message', $msg, 'error'); $this->rc->output->command('remove_from_attachment_list', $uploadid); } $this->rc->output->send('iframe'); } /** * Deliver an event/task attachment to the client * (similar as in Roundcube core program/steps/mail/get.inc) */ public function attachment_get($attachment) { ob_end_clean(); if ($attachment && !empty($attachment['body'])) { // 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', [ 'body' => $attachment['body'], 'mimetype' => strtolower($attachment['mimetype']), 'download' => !empty($_GET['_download']), 'part' => $part, ]); if (!empty($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 rcmail_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) { $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; } /** * Show "loading..." page in attachment iframe */ public function attachment_loading_page() { $url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']); $message = $this->rc->gettext('loadingdata'); header('Content-Type: text/html; charset=' . RCUBE_CHARSET); print "<html>\n<head>\n" . '<meta http-equiv="refresh" content="0; url='.rcube::Q($url).'">' . "\n" . '<meta http-equiv="content-type" content="text/html; charset='.RCUBE_CHARSET.'">' . "\n" . "</head>\n<body>\n$message\n</body>\n</html>"; exit; } /** * Template object for attachment display frame */ public function attachment_frame($attrib = []) { $mimetype = strtolower($this->attachment['mimetype']); list($ctype_primary, $ctype_secondary) = explode('/', $mimetype); $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary == 'text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']); $this->rc->output->add_gui_object('attachmentframe', $attrib['id']); return html::iframe($attrib); } /** * Template object for attachment metadata */ public function attachment_header($attrib = []) { $rcmail = rcmail::get_instance(); $dl_link = strtolower($attrib['downloadlink'] ?? '') == 'true'; $dl_url = $this->rc->url(['_frame' => null, '_download' => 1] + $_GET); $table = new html_table(['cols' => $dl_link ? 3 : 2]); if (!empty($this->attachment['name'])) { $table->add('title', rcube::Q($this->rc->gettext('filename'))); $table->add('header', rcube::Q($this->attachment['name'])); if ($dl_link) { $table->add('download-link', html::a($dl_url, rcube::Q($this->rc->gettext('download')))); } } if (!empty($this->attachment['mimetype'])) { $table->add('title', rcube::Q($this->rc->gettext('type'))); $table->add('header', rcube::Q($this->attachment['mimetype'])); } if (!empty($this->attachment['size'])) { $table->add('title', rcube::Q($this->rc->gettext('filesize'))); $table->add('header', rcube::Q($this->rc->show_bytes($this->attachment['size']))); } $this->rc->output->set_env('attachment_download_url', $dl_url); return $table->show($attrib); } /** * Remove uploaded attachments */ public function attachments_cleanup($session_key) { - // remove temp. attachments - if (!empty($_SESSION[$session_key]) && ($group = $_SESSION[$session_key]['id'])) { + // Roundcube >= 1.7 + if (method_exists($this->rc, 'delete_uploaded_files')) { + $this->rc->delete_uploaded_files($session_key); + } + else if (!empty($_SESSION[$session_key]) && ($group = $_SESSION[$session_key]['id'])) { $this->rc->plugins->exec_hook('attachments_cleanup', ['group' => $group]); $this->rc->session->remove($session_key); } } /** * Collect uploaded attachments */ public function attachments_set($session_key, $group, $ids = []) { $attachments = []; - if (!empty($_SESSION[$session_key]) && $_SESSION[$session_key]['id'] == $group) { + // Roundcube >= 1.7 + if (method_exists($this->rc, 'list_uploaded_files')) { + foreach ($this->rc->list_uploaded_files($session_key) as $attachment) { + if (is_array($ids) && in_array($attachment['id'], $ids)) { + $attachments[$attachment['id']] = $attachment; + } + } + } + else if (!empty($_SESSION[$session_key]) && $_SESSION[$session_key]['id'] == $group) { if (!empty($_SESSION[$session_key]['attachments'])) { foreach ($_SESSION[$session_key]['attachments'] as $id => $attachment) { if (is_array($ids) && in_array($id, $ids)) { - $attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment); - unset($attachments[$id]['abort'], $attachments[$id]['group']); + $attachments[$id] = $attachment; } } } } + foreach ($attachments as $id => $attachment) { + $attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment); + unset($attachments[$id]['abort'], $attachments[$id]['group']); + } + return $attachments; } /** * Register mail message attachments */ public function copy_mail_attachments($session_key, $group, $message) { $result = []; $imap = $this->rc->get_storage(); - - if (!empty($_SESSION[$session_key]) || $_SESSION[$session_key]['id'] != $group) { - $_SESSION[$session_key] = [ - 'id' => $group, - 'attachments' => [], - ]; + $uploads_api = method_exists($this->rc, 'insert_uploaded_file'); // Roundcube >= 1.7 + + if (!$uploads_api) { + if (!empty($_SESSION[$session_key]) || $_SESSION[$session_key]['id'] != $group) { + $_SESSION[$session_key] = [ + 'id' => $group, + 'attachments' => [], + ]; + } } foreach ((array) $message->attachments as $part) { $attachment = [ 'data' => $imap->get_message_part($message->uid, $part->mime_id, $part), 'size' => $part->size, 'name' => $part->filename, 'mimetype' => $part->mimetype, 'group' => $group, ]; - $attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment); + if ($uploads_api) { + $attachment['status'] = $this->rc->insert_uploaded_file($attachment, 'attachment_save'); + } + else { + $attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment); + } if (!empty($attachment['status']) && empty($attachment['abort'])) { $id = $attachment['id']; $attachment['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']); - // store new attachment in session unset($attachment['status'], $attachment['abort'], $attachment['data']); - $_SESSION[$session_key]['attachments'][$id] = $attachment; + if (!$uploads_api) { + // store new attachment in session + $_SESSION[$session_key]['attachments'][$id] = $attachment; + } $attachment['id'] = 'rcmfile' . $attachment['id']; // add prefix to consider it 'new' $result[] = $attachment; } } return $result; } }