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