1<?php 2 3/** 4 * Shared code for attachments handling in Kolab plugins 5 * 6 * @version @package_version@ 7 * @author Thomas Bruederli <bruederli@kolabsys.com> 8 * @author Aleksander Machniak <machniak@kolabsys.com> 9 * 10 * Copyright (C) 2012-2018, Kolab Systems AG <contact@kolabsys.com> 11 * 12 * This program is free software: you can redistribute it and/or modify 13 * it under the terms of the GNU Affero General Public License as 14 * published by the Free Software Foundation, either version 3 of the 15 * License, or (at your option) any later version. 16 * 17 * This program is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU Affero General Public License for more details. 21 * 22 * You should have received a copy of the GNU Affero General Public License 23 * along with this program. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26class kolab_attachments_handler 27{ 28 private $rc; 29 private $attachment; 30 31 public function __construct() 32 { 33 $this->rc = rcmail::get_instance(); 34 } 35 36 public static function ui() 37 { 38 $rcmail = rcmail::get_instance(); 39 $self = new self; 40 41 $rcmail->output->add_handler('plugin.attachments_form', array($self, 'files_form')); 42 $rcmail->output->add_handler('plugin.attachments_list', array($self, 'files_list')); 43 $rcmail->output->add_handler('plugin.filedroparea', array($self, 'files_drop_area')); 44 } 45 46 /** 47 * Generate HTML element for attachments list 48 */ 49 public function files_list($attrib = array()) 50 { 51 if (!$attrib['id']) { 52 $attrib['id'] = 'kolabattachmentlist'; 53 } 54 55 // define list of file types which can be displayed inline 56 // same as in program/steps/mail/show.inc 57 $this->rc->output->set_env('mimetypes', (array)$this->rc->config->get('client_mimetypes')); 58 59 $this->rc->output->add_gui_object('attachmentlist', $attrib['id']); 60 61 return html::tag('ul', $attrib, '', html::$common_attrib); 62 } 63 64 /** 65 * Generate the form for event attachments upload 66 */ 67 public function files_form($attrib = array()) 68 { 69 // add ID if not given 70 if (!$attrib['id']) { 71 $attrib['id'] = 'kolabuploadform'; 72 } 73 74 return $this->rc->upload_form($attrib, 'uploadform', 'upload-file', array('multiple' => true)); 75 } 76 77 /** 78 * Register UI object for HTML5 drag & drop file upload 79 */ 80 public function files_drop_area($attrib = array()) 81 { 82 // add ID if not given 83 if (!$attrib['id']) { 84 $attrib['id'] = 'kolabfiledroparea'; 85 } 86 87 $this->rc->output->add_gui_object('filedrop', $attrib['id']); 88 $this->rc->output->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments')); 89 } 90 91 /** 92 * Displays attachment preview page 93 */ 94 public function attachment_page($attachment) 95 { 96 $this->attachment = $attachment; 97 98 $this->rc->plugins->include_script('libkolab/libkolab.js'); 99 100 $this->rc->output->add_handler('plugin.attachmentframe', array($this, 'attachment_frame')); 101 $this->rc->output->add_handler('plugin.attachmentcontrols', array($this, 'attachment_header')); 102 $this->rc->output->set_env('filename', $attachment['name']); 103 $this->rc->output->set_env('mimetype', $attachment['mimetype']); 104 $this->rc->output->send('libkolab.attachment'); 105 } 106 107 /** 108 * Handler for attachment uploads 109 */ 110 public function attachment_upload($session_key, $id_prefix = '') 111 { 112 // Upload progress update 113 if (!empty($_GET['_progress'])) { 114 $this->rc->upload_progress(); 115 } 116 117 $recid = $id_prefix . rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC); 118 $uploadid = rcube_utils::get_input_value('_uploadid', rcube_utils::INPUT_GPC); 119 120 if (!is_array($_SESSION[$session_key]) || $_SESSION[$session_key]['id'] != $recid) { 121 $_SESSION[$session_key] = array(); 122 $_SESSION[$session_key]['id'] = $recid; 123 $_SESSION[$session_key]['attachments'] = array(); 124 } 125 126 // clear all stored output properties (like scripts and env vars) 127 $this->rc->output->reset(); 128 129 if (is_array($_FILES['_attachments']['tmp_name'])) { 130 foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) { 131 // Process uploaded attachment if there is no error 132 $err = $_FILES['_attachments']['error'][$i]; 133 134 if (!$err) { 135 $filename = $_FILES['_attachments']['name'][$i]; 136 $attachment = array( 137 'path' => $filepath, 138 'size' => $_FILES['_attachments']['size'][$i], 139 'name' => $filename, 140 'mimetype' => rcube_mime::file_content_type($filepath, $filename, $_FILES['_attachments']['type'][$i]), 141 'group' => $recid, 142 ); 143 144 $attachment = $this->rc->plugins->exec_hook('attachment_upload', $attachment); 145 } 146 147 if (!$err && $attachment['status'] && !$attachment['abort']) { 148 $id = $attachment['id']; 149 150 // store new attachment in session 151 unset($attachment['status'], $attachment['abort']); 152 $this->rc->session->append($session_key . '.attachments', $id, $attachment); 153 154 if (($icon = $_SESSION[$session_key . '_deleteicon']) && is_file($icon)) { 155 $button = html::img(array( 156 'src' => $icon, 157 'alt' => $this->rc->gettext('delete') 158 )); 159 } 160 else if ($_SESSION[$session_key . '_textbuttons']) { 161 $button = rcube::Q($this->rc->gettext('delete')); 162 } 163 else { 164 $button = ''; 165 } 166 167 $link_content = sprintf('<span class="attachment-name">%s</span><span class="attachment-size">(%s)</span>', 168 rcube::Q($attachment['name']), $this->rc->show_bytes($attachment['size'])); 169 170 $delete_link = html::a(array( 171 'href' => "#delete", 172 'class' => 'delete', 173 'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", rcmail_output::JS_OBJECT_NAME, $id), 174 'title' => $this->rc->gettext('delete'), 175 'aria-label' => $this->rc->gettext('delete') . ' ' . $attachment['name'], 176 ), $button); 177 178 $content_link = html::a(array( 179 'href' => "#load", 180 'class' => 'filename', 181 'onclick' => 'return false', // sprintf("return %s.command('load-attachment','rcmfile%s', this, event)", rcmail_output::JS_OBJECT_NAME, $id), 182 ), $link_content); 183 184 $content .= $_SESSION[$session_key . '_icon_pos'] == 'left' ? $delete_link.$content_link : $content_link.$delete_link; 185 186 $this->rc->output->command('add2attachment_list', "rcmfile$id", array( 187 'html' => $content, 188 'name' => $attachment['name'], 189 'mimetype' => $attachment['mimetype'], 190 'classname' => 'no-menu ' . rcube_utils::file2class($attachment['mimetype'], $attachment['name']), 191 'complete' => true 192 ), $uploadid); 193 } 194 else { // upload failed 195 if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) { 196 $msg = $this->rc->gettext(array('name' => 'filesizeerror', 'vars' => array( 197 'size' => $this->rc->show_bytes(parse_bytes(ini_get('upload_max_filesize')))))); 198 } 199 else if ($attachment['error']) { 200 $msg = $attachment['error']; 201 } 202 else { 203 $msg = $this->rc->gettext('fileuploaderror'); 204 } 205 206 $this->rc->output->command('display_message', $msg, 'error'); 207 $this->rc->output->command('remove_from_attachment_list', $uploadid); 208 } 209 } 210 } 211 else if ($_SERVER['REQUEST_METHOD'] == 'POST') { 212 // if filesize exceeds post_max_size then $_FILES array is empty, 213 // show filesizeerror instead of fileuploaderror 214 if ($maxsize = ini_get('post_max_size')) 215 $msg = $this->rc->gettext(array('name' => 'filesizeerror', 'vars' => array( 216 'size' => $this->rc->show_bytes(parse_bytes($maxsize))))); 217 else 218 $msg = $this->rc->gettext('fileuploaderror'); 219 220 $this->rc->output->command('display_message', $msg, 'error'); 221 $this->rc->output->command('remove_from_attachment_list', $uploadid); 222 } 223 224 $this->rc->output->send('iframe'); 225 } 226 227 228 /** 229 * Deliver an event/task attachment to the client 230 * (similar as in Roundcube core program/steps/mail/get.inc) 231 */ 232 public function attachment_get($attachment) 233 { 234 ob_end_clean(); 235 236 if ($attachment && $attachment['body']) { 237 // allow post-processing of the attachment body 238 $part = new rcube_message_part; 239 $part->filename = $attachment['name']; 240 $part->size = $attachment['size']; 241 $part->mimetype = $attachment['mimetype']; 242 243 $plugin = $this->rc->plugins->exec_hook('message_part_get', array( 244 'body' => $attachment['body'], 245 'mimetype' => strtolower($attachment['mimetype']), 246 'download' => !empty($_GET['_download']), 247 'part' => $part, 248 )); 249 250 if ($plugin['abort']) 251 exit; 252 253 $mimetype = $plugin['mimetype']; 254 list($ctype_primary, $ctype_secondary) = explode('/', $mimetype); 255 256 $browser = $this->rc->output->browser; 257 258 // send download headers 259 if ($plugin['download']) { 260 header("Content-Type: application/octet-stream"); 261 if ($browser->ie) 262 header("Content-Type: application/force-download"); 263 } 264 else if ($ctype_primary == 'text') { 265 header("Content-Type: text/$ctype_secondary"); 266 } 267 else { 268 header("Content-Type: $mimetype"); 269 header("Content-Transfer-Encoding: binary"); 270 } 271 272 // display page, @TODO: support text/plain (and maybe some other text formats) 273 if ($mimetype == 'text/html' && empty($_GET['_download'])) { 274 $OUTPUT = new rcmail_html_page(); 275 // @TODO: use washtml on $body 276 $OUTPUT->write($plugin['body']); 277 } 278 else { 279 // don't kill the connection if download takes more than 30 sec. 280 @set_time_limit(0); 281 282 $filename = $attachment['name']; 283 $filename = preg_replace('[\r\n]', '', $filename); 284 285 if ($browser->ie && $browser->ver < 7) 286 $filename = rawurlencode(abbreviate_string($filename, 55)); 287 else if ($browser->ie) 288 $filename = rawurlencode($filename); 289 else 290 $filename = addcslashes($filename, '"'); 291 292 $disposition = !empty($_GET['_download']) ? 'attachment' : 'inline'; 293 header("Content-Disposition: $disposition; filename=\"$filename\""); 294 295 echo $plugin['body']; 296 } 297 298 exit; 299 } 300 301 // if we arrive here, the requested part was not found 302 header('HTTP/1.1 404 Not Found'); 303 exit; 304 } 305 306 /** 307 * Show "loading..." page in attachment iframe 308 */ 309 public function attachment_loading_page() 310 { 311 $url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']); 312 $message = $this->rc->gettext('loadingdata'); 313 314 header('Content-Type: text/html; charset=' . RCUBE_CHARSET); 315 print "<html>\n<head>\n" 316 . '<meta http-equiv="refresh" content="0; url='.rcube::Q($url).'">' . "\n" 317 . '<meta http-equiv="content-type" content="text/html; charset='.RCUBE_CHARSET.'">' . "\n" 318 . "</head>\n<body>\n$message\n</body>\n</html>"; 319 exit; 320 } 321 322 /** 323 * Template object for attachment display frame 324 */ 325 public function attachment_frame($attrib = array()) 326 { 327 $mimetype = strtolower($this->attachment['mimetype']); 328 list($ctype_primary, $ctype_secondary) = explode('/', $mimetype); 329 330 $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary == 'text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']); 331 332 $this->rc->output->add_gui_object('attachmentframe', $attrib['id']); 333 334 return html::iframe($attrib); 335 } 336 337 /** 338 * 339 */ 340 public function attachment_header($attrib = array()) 341 { 342 $rcmail = rcmail::get_instance(); 343 $dl_link = strtolower($attrib['downloadlink']) == 'true'; 344 $dl_url = $this->rc->url(array('_frame' => null, '_download' => 1) + $_GET); 345 $table = new html_table(array('cols' => $dl_link ? 3 : 2)); 346 347 if (!empty($this->attachment['name'])) { 348 $table->add('title', rcube::Q($this->rc->gettext('filename'))); 349 $table->add('header', rcube::Q($this->attachment['name'])); 350 351 if ($dl_link) { 352 $table->add('download-link', html::a($dl_url, rcube::Q($this->rc->gettext('download')))); 353 } 354 } 355 356 if (!empty($this->attachment['mimetype'])) { 357 $table->add('title', rcube::Q($this->rc->gettext('type'))); 358 $table->add('header', rcube::Q($this->attachment['mimetype'])); 359 } 360 361 if (!empty($this->attachment['size'])) { 362 $table->add('title', rcube::Q($this->rc->gettext('filesize'))); 363 $table->add('header', rcube::Q($this->rc->show_bytes($this->attachment['size']))); 364 } 365 366 $this->rc->output->set_env('attachment_download_url', $dl_url); 367 368 return $table->show($attrib); 369 } 370} 371