Thomas Bruederli
2014-04-07 bde3ba5e93f9b02ac8bd1d10e33fa65cc4da31c5
commit | author | age
70bbab 1 <?php
TB 2
3 /**
4  * ZipDownload
5  *
6  * Plugin to allow the download of all message attachments in one zip file
7  *
bde3ba 8  * @version 2.1
70bbab 9  * @requires php_zip extension (including ZipArchive class)
TB 10  * @author Philip Weir
11  * @author Thomas Bruderli
12  */
13 class zipdownload extends rcube_plugin
14 {
eaf9d8 15     public $task = 'mail';
AM 16     private $charset = 'ASCII';
70bbab 17
eaf9d8 18     /**
AM 19      * Plugin initialization
20      */
21     public function init()
22     {
23         // check requirements first
24         if (!class_exists('ZipArchive', false)) {
25             rcmail::raise_error(array(
26                 'code'    => 520,
27                 'file'    => __FILE__,
28                 'line'    => __LINE__,
29                 'message' => "php_zip extension is required for the zipdownload plugin"), true, false);
30             return;
31         }
70bbab 32
eaf9d8 33         $rcmail = rcmail::get_instance();
70bbab 34
eaf9d8 35         $this->load_config();
AM 36         $this->charset = $rcmail->config->get('zipdownload_charset', RCUBE_CHARSET);
37         $this->add_texts('localization');
70bbab 38
eaf9d8 39         if ($rcmail->config->get('zipdownload_attachments', 1) > -1 && ($rcmail->action == 'show' || $rcmail->action == 'preview')) {
AM 40             $this->add_hook('template_object_messageattachments', array($this, 'attachment_ziplink'));
41         }
70bbab 42
eaf9d8 43         $this->register_action('plugin.zipdownload.zip_attachments', array($this, 'download_attachments'));
AM 44         $this->register_action('plugin.zipdownload.zip_messages', array($this, 'download_selection'));
45         $this->register_action('plugin.zipdownload.zip_folder', array($this, 'download_folder'));
70bbab 46
eaf9d8 47         if (($selection = $rcmail->config->get('zipdownload_selection')) || $rcmail->config->get('zipdownload_folder')) {
AM 48             $this->include_script('zipdownload.js');
49             $this->api->output->set_env('zipdownload_selection', $selection);
70bbab 50
eaf9d8 51             if ($rcmail->config->get('zipdownload_folder', false) && ($rcmail->action == '' || $rcmail->action == 'show')) {
AM 52                 $zipdownload = $this->api->output->button(array('command' => 'plugin.zipdownload.zip_folder', 'type' => 'link', 'classact' => 'active', 'content' => $this->gettext('downloadfolder')));
53                 $this->api->add_content(html::tag('li', array('class' => 'separator_above'), $zipdownload), 'mailboxoptions');
54             }
55         }
56     }
70bbab 57
eaf9d8 58     /**
AM 59      * Place a link/button after attachments listing to trigger download
60      */
61     public function attachment_ziplink($p)
62     {
63         $rcmail = rcmail::get_instance();
70bbab 64
eaf9d8 65         // only show the link if there is more than the configured number of attachments
AM 66         if (substr_count($p['content'], '<li') > $rcmail->config->get('zipdownload_attachments', 1)) {
67             $href = $rcmail->url(array(
68                 '_action' => 'plugin.zipdownload.zip_attachments',
69                 '_mbox'   => $rcmail->output->env['mailbox'],
70                 '_uid'    => $rcmail->output->env['uid'],
71             ));
61be82 72
eaf9d8 73             $link = html::a(array('href' => $href, 'class' => 'button zipdownload'),
AM 74                 rcube::Q($this->gettext('downloadall'))
75             );
70bbab 76
eaf9d8 77             // append link to attachments list, slightly different in some skins
AM 78             switch (rcmail::get_instance()->config->get('skin')) {
79                 case 'classic':
80                     $p['content'] = str_replace('</ul>', html::tag('li', array('class' => 'zipdownload'), $link) . '</ul>', $p['content']);
81                     break;
70bbab 82
eaf9d8 83                 default:
AM 84                     $p['content'] .= $link;
85                     break;
86             }
70bbab 87
eaf9d8 88             $this->include_stylesheet($this->local_skin_path() . '/zipdownload.css');
AM 89         }
70bbab 90
eaf9d8 91         return $p;
AM 92     }
70bbab 93
eaf9d8 94     /**
AM 95      * Handler for attachment download action
96      */
97     public function download_attachments()
98     {
99         $rcmail    = rcmail::get_instance();
100         $imap      = $rcmail->get_storage();
101         $temp_dir  = $rcmail->config->get('temp_dir');
102         $tmpfname  = tempnam($temp_dir, 'zipdownload');
103         $tempfiles = array($tmpfname);
104         $message   = new rcube_message(rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GET));
70bbab 105
eaf9d8 106         // open zip file
AM 107         $zip = new ZipArchive();
108         $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
70bbab 109
eaf9d8 110         foreach ($message->attachments as $part) {
AM 111             $pid      = $part->mime_id;
112             $part     = $message->mime_parts[$pid];
113             $filename = $part->filename;
2eef77 114
AM 115             if ($filename === null || $filename === '') {
116                 $ext      = (array) rcube_mime::get_mime_extensions($part->mimetype);
117                 $ext      = array_shift($ext);
118                 $filename = $rcmail->gettext('messagepart') . ' ' . $pid;
119                 if ($ext) {
120                     $filename .= '.' . $ext;
121                 }
122             }
123
eaf9d8 124             $disp_name = $this->_convert_filename($filename);
70bbab 125
eaf9d8 126             if ($part->body) {
AM 127                 $orig_message_raw = $part->body;
128                 $zip->addFromString($disp_name, $orig_message_raw);
129             }
130             else {
131                 $tmpfn = tempnam($temp_dir, 'zipattach');
132                 $tmpfp = fopen($tmpfn, 'w');
133                 $imap->get_message_part($message->uid, $part->mime_id, $part, null, $tmpfp, true);
134                 $tempfiles[] = $tmpfn;
135                 fclose($tmpfp);
136                 $zip->addFile($tmpfn, $disp_name);
137             }
138         }
70bbab 139
eaf9d8 140         $zip->close();
70bbab 141
eaf9d8 142         $filename = ($message->subject ? $message->subject : 'roundcube') . '.zip';
AM 143         $this->_deliver_zipfile($tmpfname, $filename);
70bbab 144
eaf9d8 145         // delete temporary files from disk
AM 146         foreach ($tempfiles as $tmpfn) {
147             unlink($tmpfn);
148         }
70bbab 149
eaf9d8 150         exit;
AM 151     }
70bbab 152
eaf9d8 153     /**
AM 154      * Handler for message download action
155      */
156     public function download_selection()
157     {
158         if (isset($_REQUEST['_uid'])) {
bde3ba 159             $messageset = rcmail::get_uids();
70bbab 160
bde3ba 161             if (sizeof($messageset) > 0) {
TB 162                 $this->_download_messages($messageset);
eaf9d8 163             }
AM 164         }
165     }
70bbab 166
eaf9d8 167     /**
AM 168      * Handler for folder download action
169      */
170     public function download_folder()
171     {
172         $imap      = rcmail::get_instance()->get_storage();
173         $mbox_name = $imap->get_folder();
70bbab 174
eaf9d8 175         // initialize searching result if search_filter is used
AM 176         if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') {
177             $imap->search($mbox_name, $_SESSION['search_filter'], RCUBE_CHARSET);
178         }
70bbab 179
eaf9d8 180         // fetch message headers for all pages
AM 181         $uids = array();
182         if ($count = $imap->count($mbox_name, $imap->get_threading() ? 'THREADS' : 'ALL', FALSE)) {
183             for ($i = 0; ($i * $imap->get_pagesize()) <= $count; $i++) {
184                 $a_headers = $imap->list_messages($mbox_name, ($i + 1));
70bbab 185
eaf9d8 186                 foreach ($a_headers as $header) {
AM 187                     if (empty($header))
188                         continue;
70bbab 189
eaf9d8 190                     array_push($uids, $header->uid);
AM 191                 }
192             }
193         }
70bbab 194
eaf9d8 195         if (sizeof($uids) > 0)
bde3ba 196             $this->_download_messages(array($mbox_name => $uids));
eaf9d8 197     }
70bbab 198
eaf9d8 199     /**
AM 200      * Helper method to packs all the given messages into a zip archive
201      *
202      * @param array List of message UIDs to download
203      */
bde3ba 204     private function _download_messages($messageset)
eaf9d8 205     {
AM 206         $rcmail    = rcmail::get_instance();
207         $imap      = $rcmail->get_storage();
208         $temp_dir  = $rcmail->config->get('temp_dir');
209         $tmpfname  = tempnam($temp_dir, 'zipdownload');
210         $tempfiles = array($tmpfname);
bde3ba 211         $folders   = count($messageset) > 1;
70bbab 212
eaf9d8 213         // open zip file
AM 214         $zip = new ZipArchive();
215         $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE);
70bbab 216
bde3ba 217         foreach ($messageset as $mbox => $uids){
TB 218             $imap->set_folder($mbox);
219             $path = $folders ? str_replace($imap->get_hierarchy_delimiter(), '/', $mbox) . '/' : '';
70bbab 220
bde3ba 221             foreach ($uids as $uid){
TB 222                 $headers = $imap->get_message_headers($uid);
223                 $subject = rcube_mime::decode_mime_string((string)$headers->subject);
224                 $subject = $this->_convert_filename($subject);
225                 $subject = substr($subject, 0, 16);
dafb50 226
bde3ba 227                 $disp_name = ($subject ? $subject : 'message_rfc822') . ".eml";
TB 228                 $disp_name = $path . $uid . "_" . $disp_name;
229
230                 $tmpfn = tempnam($temp_dir, 'zipmessage');
231                 $tmpfp = fopen($tmpfn, 'w');
232                 $imap->get_raw_body($uid, $tmpfp);
233                 $tempfiles[] = $tmpfn;
234                 fclose($tmpfp);
235                 $zip->addFile($tmpfn, $disp_name);
236             }
eaf9d8 237         }
70bbab 238
eaf9d8 239         $zip->close();
70bbab 240
bde3ba 241         $filename = $folders ? 'messages' : $imap->get_folder();
TB 242         $this->_deliver_zipfile($tmpfname, $filename . '.zip');
70bbab 243
eaf9d8 244         // delete temporary files from disk
AM 245         foreach ($tempfiles as $tmpfn) {
246             unlink($tmpfn);
247         }
70bbab 248
eaf9d8 249         exit;
AM 250     }
70bbab 251
eaf9d8 252     /**
AM 253      * Helper method to send the zip archive to the browser
254      */
255     private function _deliver_zipfile($tmpfname, $filename)
256     {
257         $browser = new rcube_browser;
258         $rcmail  = rcmail::get_instance();
61be82 259
eaf9d8 260         $rcmail->output->nocacheing_headers();
70bbab 261
eaf9d8 262         if ($browser->ie && $browser->ver < 7)
AM 263             $filename = rawurlencode(abbreviate_string($filename, 55));
264         else if ($browser->ie)
265             $filename = rawurlencode($filename);
266         else
267             $filename = addcslashes($filename, '"');
70bbab 268
eaf9d8 269         // send download headers
AM 270         header("Content-Type: application/octet-stream");
271         if ($browser->ie) {
272             header("Content-Type: application/force-download");
273         }
70bbab 274
eaf9d8 275         // don't kill the connection if download takes more than 30 sec.
AM 276         @set_time_limit(0);
277         header("Content-Disposition: attachment; filename=\"". $filename ."\"");
278         header("Content-length: " . filesize($tmpfname));
279         readfile($tmpfname);
280     }
70bbab 281
eaf9d8 282     /**
AM 283      * Helper function to convert filenames to the configured charset
284      */
285     private function _convert_filename($str)
286     {
492247 287         $str = rcube_charset::convert($str, RCUBE_CHARSET, $this->charset);
dafb50 288
eaf9d8 289         return strtr($str, array(':'=>'', '/'=>'-'));
AM 290     }
70bbab 291 }