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