Mercurial > hg > rc1
comparison plugins/zipdownload/zipdownload.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:50:29 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1e000243b222 |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * ZipDownload | |
5 * | |
6 * Plugin to allow the download of all message attachments in one zip file | |
7 * and also download of many messages in one go. | |
8 * | |
9 * @requires php_zip extension (including ZipArchive class) | |
10 * | |
11 * @author Philip Weir | |
12 * @author Thomas Bruderli | |
13 * @author Aleksander Machniak | |
14 */ | |
15 class zipdownload extends rcube_plugin | |
16 { | |
17 public $task = 'mail'; | |
18 | |
19 private $charset = 'ASCII'; | |
20 | |
21 private $names = []; | |
22 | |
23 // RFC4155: mbox date format | |
24 const MBOX_DATE_FORMAT = 'D M d H:i:s Y'; | |
25 | |
26 /** | |
27 * Plugin initialization | |
28 */ | |
29 public function init() | |
30 { | |
31 // check requirements first | |
32 if (!class_exists('ZipArchive', false)) { | |
33 rcmail::raise_error(array( | |
34 'code' => 520, | |
35 'file' => __FILE__, | |
36 'line' => __LINE__, | |
37 'message' => "php_zip extension is required for the zipdownload plugin"), true, false); | |
38 return; | |
39 } | |
40 | |
41 $rcmail = rcmail::get_instance(); | |
42 | |
43 $this->load_config(); | |
44 $this->charset = $rcmail->config->get('zipdownload_charset', RCUBE_CHARSET); | |
45 $this->add_texts('localization'); | |
46 | |
47 if ($rcmail->config->get('zipdownload_attachments', 1) > -1 && ($rcmail->action == 'show' || $rcmail->action == 'preview')) { | |
48 $this->add_hook('template_object_messageattachments', array($this, 'attachment_ziplink')); | |
49 } | |
50 | |
51 $this->register_action('plugin.zipdownload.attachments', array($this, 'download_attachments')); | |
52 $this->register_action('plugin.zipdownload.messages', array($this, 'download_messages')); | |
53 | |
54 if (!$rcmail->action && $rcmail->config->get('zipdownload_selection')) { | |
55 $this->download_menu(); | |
56 } | |
57 } | |
58 | |
59 /** | |
60 * Place a link/button after attachments listing to trigger download | |
61 */ | |
62 public function attachment_ziplink($p) | |
63 { | |
64 $rcmail = rcmail::get_instance(); | |
65 | |
66 // only show the link if there is more than the configured number of attachments | |
67 if (substr_count($p['content'], '<li') > $rcmail->config->get('zipdownload_attachments', 1)) { | |
68 $href = $rcmail->url(array( | |
69 '_action' => 'plugin.zipdownload.attachments', | |
70 '_mbox' => $rcmail->output->env['mailbox'], | |
71 '_uid' => $rcmail->output->env['uid'], | |
72 ), false, false, true); | |
73 | |
74 $link = html::a(array('href' => $href, 'class' => 'button zipdownload'), | |
75 rcube::Q($this->gettext('downloadall')) | |
76 ); | |
77 | |
78 // append link to attachments list, slightly different in some skins | |
79 switch (rcmail::get_instance()->config->get('skin')) { | |
80 case 'classic': | |
81 $p['content'] = str_replace('</ul>', html::tag('li', array('class' => 'zipdownload'), $link) . '</ul>', $p['content']); | |
82 break; | |
83 | |
84 default: | |
85 $p['content'] .= $link; | |
86 break; | |
87 } | |
88 | |
89 $this->include_stylesheet($this->local_skin_path() . '/zipdownload.css'); | |
90 } | |
91 | |
92 return $p; | |
93 } | |
94 | |
95 /** | |
96 * Adds download options menu to the page | |
97 */ | |
98 public function download_menu() | |
99 { | |
100 $this->include_script('zipdownload.js'); | |
101 $this->add_label('download'); | |
102 | |
103 $rcmail = rcmail::get_instance(); | |
104 $menu = array(); | |
105 $ul_attr = array('role' => 'menu', 'aria-labelledby' => 'aria-label-zipdownloadmenu'); | |
106 if ($rcmail->config->get('skin') != 'classic') { | |
107 $ul_attr['class'] = 'toolbarmenu'; | |
108 } | |
109 | |
110 foreach (array('eml', 'mbox', 'maildir') as $type) { | |
111 $menu[] = html::tag('li', null, $rcmail->output->button(array( | |
112 'command' => "download-$type", | |
113 'label' => "zipdownload.download$type", | |
114 'classact' => 'active', | |
115 ))); | |
116 } | |
117 | |
118 $rcmail->output->add_footer(html::div(array('id' => 'zipdownload-menu', 'class' => 'popupmenu', 'aria-hidden' => 'true'), | |
119 html::tag('h2', array('class' => 'voice', 'id' => 'aria-label-zipdownloadmenu'), "Message Download Options Menu") . | |
120 html::tag('ul', $ul_attr, implode('', $menu)))); | |
121 } | |
122 | |
123 /** | |
124 * Handler for attachment download action | |
125 */ | |
126 public function download_attachments() | |
127 { | |
128 $rcmail = rcmail::get_instance(); | |
129 | |
130 // require CSRF protected request | |
131 $rcmail->request_security_check(rcube_utils::INPUT_GET); | |
132 | |
133 $imap = $rcmail->get_storage(); | |
134 $temp_dir = $rcmail->config->get('temp_dir'); | |
135 $tmpfname = tempnam($temp_dir, 'zipdownload'); | |
136 $tempfiles = array($tmpfname); | |
137 $message = new rcube_message(rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GET)); | |
138 | |
139 // open zip file | |
140 $zip = new ZipArchive(); | |
141 $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE); | |
142 | |
143 foreach ($message->attachments as $part) { | |
144 $pid = $part->mime_id; | |
145 $part = $message->mime_parts[$pid]; | |
146 $disp_name = $this->_create_displayname($part); | |
147 | |
148 $tmpfn = tempnam($temp_dir, 'zipattach'); | |
149 $tmpfp = fopen($tmpfn, 'w'); | |
150 $tempfiles[] = $tmpfn; | |
151 | |
152 $message->get_part_body($part->mime_id, false, 0, $tmpfp); | |
153 $zip->addFile($tmpfn, $disp_name); | |
154 fclose($tmpfp); | |
155 } | |
156 | |
157 $zip->close(); | |
158 | |
159 $filename = ($this->_filename_from_subject($message->subject) ?: 'attachments') . '.zip'; | |
160 | |
161 $this->_deliver_zipfile($tmpfname, $filename); | |
162 | |
163 // delete temporary files from disk | |
164 foreach ($tempfiles as $tmpfn) { | |
165 unlink($tmpfn); | |
166 } | |
167 | |
168 exit; | |
169 } | |
170 | |
171 /** | |
172 * Handler for message download action | |
173 */ | |
174 public function download_messages() | |
175 { | |
176 $rcmail = rcmail::get_instance(); | |
177 | |
178 if ($rcmail->config->get('zipdownload_selection') && !empty($_POST['_uid'])) { | |
179 $messageset = rcmail::get_uids(); | |
180 if (count($messageset)) { | |
181 $this->_download_messages($messageset); | |
182 } | |
183 } | |
184 } | |
185 | |
186 /** | |
187 * Create and get display name of attachment part to add on zip file | |
188 * | |
189 * @param $part stdClass Part of attachment on message | |
190 * | |
191 * @return string Display name of attachment part | |
192 */ | |
193 private function _create_displayname($part) | |
194 { | |
195 $rcmail = rcmail::get_instance(); | |
196 $filename = $part->filename; | |
197 | |
198 if ($filename === null || $filename === '') { | |
199 $ext = (array) rcube_mime::get_mime_extensions($part->mimetype); | |
200 $ext = array_shift($ext); | |
201 $filename = $rcmail->gettext('messagepart') . ' ' . $part->mime_id; | |
202 if ($ext) { | |
203 $filename .= '.' . $ext; | |
204 } | |
205 } | |
206 | |
207 $displayname = $this->_convert_filename($filename); | |
208 | |
209 /** | |
210 * Adding a number before dot of extension on a name of file with same name on zip | |
211 * Ext: attach(1).txt on attach filename that has a attach.txt filename on same zip | |
212 */ | |
213 if (isset($this->name[$displayname])) { | |
214 list($filename, $ext) = preg_split("/\.(?=[^\.]*$)/", $displayname); | |
215 $displayname = $filename . '(' . ($this->names[$displayname]++) . ').' . $ext; | |
216 $this->names[$displayname] = 1; | |
217 } | |
218 else { | |
219 $this->names[$displayname] = 1; | |
220 } | |
221 | |
222 return $displayname; | |
223 } | |
224 | |
225 /** | |
226 * Helper method to packs all the given messages into a zip archive | |
227 * | |
228 * @param array List of message UIDs to download | |
229 */ | |
230 private function _download_messages($messageset) | |
231 { | |
232 $rcmail = rcmail::get_instance(); | |
233 $imap = $rcmail->get_storage(); | |
234 $mode = rcube_utils::get_input_value('_mode', rcube_utils::INPUT_POST); | |
235 $temp_dir = $rcmail->config->get('temp_dir'); | |
236 $tmpfname = tempnam($temp_dir, 'zipdownload'); | |
237 $tempfiles = array($tmpfname); | |
238 $folders = count($messageset) > 1; | |
239 | |
240 // @TODO: file size limit | |
241 | |
242 // open zip file | |
243 $zip = new ZipArchive(); | |
244 $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE); | |
245 | |
246 if ($mode == 'mbox') { | |
247 $tmpfp = fopen($tmpfname . '.mbox', 'w'); | |
248 } | |
249 | |
250 foreach ($messageset as $mbox => $uids) { | |
251 $imap->set_folder($mbox); | |
252 $path = $folders ? str_replace($imap->get_hierarchy_delimiter(), '/', $mbox) . '/' : ''; | |
253 | |
254 if ($uids === '*') { | |
255 $index = $imap->index($mbox, null, null, true); | |
256 $uids = $index->get(); | |
257 } | |
258 | |
259 foreach ($uids as $uid) { | |
260 $headers = $imap->get_message_headers($uid); | |
261 | |
262 if ($mode == 'mbox') { | |
263 // Sender address | |
264 $from = rcube_mime::decode_address_list($headers->from, null, true, $headers->charset, true); | |
265 $from = array_shift($from); | |
266 $from = preg_replace('/\s/', '-', $from); | |
267 | |
268 // Received (internal) date | |
269 $date = rcube_utils::anytodatetime($headers->internaldate); | |
270 if ($date) { | |
271 $date->setTimezone(new DateTimeZone('UTC')); | |
272 $date = $date->format(self::MBOX_DATE_FORMAT); | |
273 } | |
274 | |
275 // Mbox format header (RFC4155) | |
276 $header = sprintf("From %s %s\r\n", | |
277 $from ?: 'MAILER-DAEMON', | |
278 $date ?: '' | |
279 ); | |
280 | |
281 fwrite($tmpfp, $header); | |
282 | |
283 // Use stream filter to quote "From " in the message body | |
284 stream_filter_register('mbox_filter', 'zipdownload_mbox_filter'); | |
285 $filter = stream_filter_append($tmpfp, 'mbox_filter'); | |
286 $imap->get_raw_body($uid, $tmpfp); | |
287 stream_filter_remove($filter); | |
288 fwrite($tmpfp, "\r\n"); | |
289 } | |
290 else { // maildir | |
291 $subject = rcube_mime::decode_header($headers->subject, $headers->charset); | |
292 $subject = $this->_filename_from_subject(mb_substr($subject, 0, 16)); | |
293 $subject = $this->_convert_filename($subject); | |
294 | |
295 $disp_name = $path . $uid . ($subject ? " $subject" : '') . '.eml'; | |
296 | |
297 $tmpfn = tempnam($temp_dir, 'zipmessage'); | |
298 $tmpfp = fopen($tmpfn, 'w'); | |
299 $imap->get_raw_body($uid, $tmpfp); | |
300 $tempfiles[] = $tmpfn; | |
301 fclose($tmpfp); | |
302 $zip->addFile($tmpfn, $disp_name); | |
303 } | |
304 } | |
305 } | |
306 | |
307 $filename = $folders ? 'messages' : $imap->get_folder(); | |
308 | |
309 if ($mode == 'mbox') { | |
310 $tempfiles[] = $tmpfname . '.mbox'; | |
311 fclose($tmpfp); | |
312 $zip->addFile($tmpfname . '.mbox', $filename . '.mbox'); | |
313 } | |
314 | |
315 $zip->close(); | |
316 | |
317 $this->_deliver_zipfile($tmpfname, $filename . '.zip'); | |
318 | |
319 // delete temporary files from disk | |
320 foreach ($tempfiles as $tmpfn) { | |
321 unlink($tmpfn); | |
322 } | |
323 | |
324 exit; | |
325 } | |
326 | |
327 /** | |
328 * Helper method to send the zip archive to the browser | |
329 */ | |
330 private function _deliver_zipfile($tmpfname, $filename) | |
331 { | |
332 $browser = new rcube_browser; | |
333 $rcmail = rcmail::get_instance(); | |
334 | |
335 $rcmail->output->nocacheing_headers(); | |
336 | |
337 if ($browser->ie) | |
338 $filename = rawurlencode($filename); | |
339 else | |
340 $filename = addcslashes($filename, '"'); | |
341 | |
342 // send download headers | |
343 header("Content-Type: application/octet-stream"); | |
344 if ($browser->ie) { | |
345 header("Content-Type: application/force-download"); | |
346 } | |
347 | |
348 // don't kill the connection if download takes more than 30 sec. | |
349 @set_time_limit(0); | |
350 header("Content-Disposition: attachment; filename=\"". $filename ."\""); | |
351 header("Content-length: " . filesize($tmpfname)); | |
352 readfile($tmpfname); | |
353 } | |
354 | |
355 /** | |
356 * Helper function to convert filenames to the configured charset | |
357 */ | |
358 private function _convert_filename($str) | |
359 { | |
360 $str = strtr($str, array(':' => '', '/' => '-')); | |
361 | |
362 return rcube_charset::convert($str, RCUBE_CHARSET, $this->charset); | |
363 } | |
364 | |
365 /** | |
366 * Helper function to convert message subject into filename | |
367 */ | |
368 private function _filename_from_subject($str) | |
369 { | |
370 $str = preg_replace('/[\t\n\r\0\x0B]+\s*/', ' ', $str); | |
371 | |
372 return trim($str, " ./_"); | |
373 } | |
374 } | |
375 | |
376 class zipdownload_mbox_filter extends php_user_filter | |
377 { | |
378 function filter($in, $out, &$consumed, $closing) | |
379 { | |
380 while ($bucket = stream_bucket_make_writeable($in)) { | |
381 // messages are read line by line | |
382 if (preg_match('/^>*From /', $bucket->data)) { | |
383 $bucket->data = '>' . $bucket->data; | |
384 $bucket->datalen += 1; | |
385 } | |
386 | |
387 $consumed += $bucket->datalen; | |
388 stream_bucket_append($out, $bucket); | |
389 } | |
390 | |
391 return PSFS_PASS_ON; | |
392 } | |
393 } |