Respect the permanentflags output. Do not show the alter flags buttons
[squirrelmail.git] / functions / imap_mailbox.php
1 <?php
2 /**
3 * imap_mailbox.php
4 *
5 * Copyright (c) 1999-2004 The SquirrelMail Project Team
6 * Licensed under the GNU GPL. For full terms see the file COPYING.
7 *
8 * This impliments all functions that manipulate mailboxes
9 *
10 * @version $Id$
11 * @package squirrelmail
12 * @subpackage imap
13 */
14
15 /** UTF7 support */
16 require_once(SM_PATH . 'functions/imap_utf7_local.php');
17
18 global $boxesnew;
19
20 /**
21 * Mailboxes class
22 *
23 * FIXME. This class should be extracted and placed in a separate file that
24 * can be included before we start the session. That makes caching of the tree
25 * possible. On a refresh mailboxes from left_main.php the only function that
26 * should be called is the sqimap_get_status_mbx_tree. In case of subscribe
27 * / rename / delete / new we have to create methods for adding/changing the
28 * mailbox in the mbx_tree without the need for a refresh.
29 * @package squirrelmail
30 */
31
32 class mailboxes {
33 var $mailboxname_full = '', $mailboxname_sub= '', $is_noselect = false, $is_noinferiors = false,
34 $is_special = false, $is_root = false, $is_inbox = false, $is_sent = false,
35 $is_trash = false, $is_draft = false, $mbxs = array(),
36 $unseen = false, $total = false;
37
38 function addMbx($mbx, $delimiter, $start, $specialfirst) {
39 $ary = explode($delimiter, $mbx->mailboxname_full);
40 $mbx_parent =& $this;
41 for ($i = $start, $c = count($ary)-1; $i < $c; $i++) {
42 $mbx_childs =& $mbx_parent->mbxs;
43 $found = false;
44 if ($mbx_childs) {
45 foreach ($mbx_childs as $key => $parent) {
46 if ($parent->mailboxname_sub == $ary[$i]) {
47 $mbx_parent =& $mbx_parent->mbxs[$key];
48 $found = true;
49 break;
50 }
51 }
52 }
53 if (!$found) {
54 $no_select_mbx = new mailboxes();
55 if (isset($mbx_parent->mailboxname_full) && $mbx_parent->mailboxname_full != '') {
56 $no_select_mbx->mailboxname_full = $mbx_parent->mailboxname_full.$delimiter.$ary[$i];
57 } else {
58 $no_select_mbx->mailboxname_full = $ary[$i];
59 }
60 $no_select_mbx->mailboxname_sub = $ary[$i];
61 $no_select_mbx->is_noselect = true;
62 $mbx_parent->mbxs[] = $no_select_mbx;
63 $i--;
64 }
65 }
66 $mbx_parent->mbxs[] = $mbx;
67 if ($mbx->is_special && $specialfirst) {
68 usort($mbx_parent->mbxs, 'sortSpecialMbx');
69 }
70 }
71 }
72
73 function sortSpecialMbx($a, $b) {
74 if ($a->is_inbox) {
75 $acmp = '0'. $a->mailboxname_full;
76 } else if ($a->is_special) {
77 $acmp = '1'. $a->mailboxname_full;
78 } else {
79 $acmp = '2' . $a->mailboxname_full;
80 }
81 if ($b->is_inbox) {
82 $bcmp = '0'. $b->mailboxname_full;
83 }else if ($b->is_special) {
84 $bcmp = '1' . $b->mailboxname_full;
85 } else {
86 $bcmp = '2' . $b->mailboxname_full;
87 }
88 return strnatcasecmp($acmp, $bcmp);
89 }
90
91 function compact_mailboxes_response($ary)
92 {
93 /*
94 * Workaround for mailboxes returned as literal
95 * FIXME : Doesn't work if the mailbox name is multiple lines
96 * (larger then fgets buffer)
97 */
98 for ($i = 0, $iCnt=count($ary); $i < $iCnt; $i++) {
99 if (isset($ary[$i + 1]) && substr($ary[$i], -3) == "}\r\n") {
100 if (ereg("^(\\* [A-Z]+.*)\\{[0-9]+\\}([ \n\r\t]*)$",
101 $ary[$i], $regs)) {
102 $ary[$i] = $regs[1] . '"' . addslashes(trim($ary[$i+1])) . '"' . $regs[2];
103 array_splice($ary, $i+1, 2);
104 }
105 }
106 }
107 /* remove duplicates and ensure array is contiguous */
108 return array_values(array_unique($ary));
109 }
110
111 /**
112 * Extract the mailbox name from an untagged LIST (7.2.2) or LSUB (7.2.3) answer
113 * (LIST|LSUB) (<Flags list>) (NIL|"<separator atom>") <mailbox name string>\r\n
114 * mailbox name in quoted string MUST be unquoted and stripslashed (sm API)
115 */
116 function find_mailbox_name($line)
117 {
118 if (preg_match('/^\* (?:LIST|LSUB) \([^\)]*\) (?:NIL|\"[^\"]*\") ([^\r\n]*)[\r\n]*$/i', $line, $regs)) {
119 if (substr($regs[1], 0, 1) == '"')
120 return stripslashes(substr($regs[1], 1, -1));
121 return $regs[1];
122 }
123 return '';
124 }
125
126 /**
127 * @return bool whether this is a Noselect mailbox.
128 */
129 function check_is_noselect ($lsub_line) {
130 return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noselect[^\)]*\)/i", $lsub_line);
131 }
132
133 /**
134 * @return bool whether this is a Noinferiors mailbox.
135 */
136 function check_is_noinferiors ($lsub_line) {
137 return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noinferiors[^\)]*\)/i", $lsub_line);
138 }
139
140 /**
141 * If $haystack is a full mailbox name, and $needle is the mailbox
142 * separator character, returns the second last part of the full
143 * mailbox name (i.e. the mailbox's parent mailbox)
144 */
145 function readMailboxParent($haystack, $needle) {
146 if ($needle == '') {
147 $ret = '';
148 } else {
149 $parts = explode($needle, $haystack);
150 $elem = array_pop($parts);
151 while ($elem == '' && count($parts)) {
152 $elem = array_pop($parts);
153 }
154 $ret = join($needle, $parts);
155 }
156 return( $ret );
157 }
158
159 /**
160 * Check if $subbox is below the specified $parentbox
161 */
162 function isBoxBelow( $subbox, $parentbox ) {
163 global $delimiter;
164 /*
165 * Eliminate the obvious mismatch, where the
166 * subfolder path is shorter than that of the potential parent
167 */
168 if ( strlen($subbox) < strlen($parentbox) ) {
169 return false;
170 }
171 /* check for delimiter */
172 if (!substr($parentbox,-1) == $delimiter) {
173 $parentbox.=$delimiter;
174 }
175 if (substr($subbox,0,strlen($parentbox)) == $parentbox) {
176 return true;
177 } else {
178 return false;
179 }
180 }
181
182 /**
183 * Defines special mailboxes: given a mailbox name, it checks if this is a
184 * "special" one: INBOX, Trash, Sent or Draft.
185 */
186 function isSpecialMailbox( $box ) {
187 $ret = ( (strtolower($box) == 'inbox') ||
188 isTrashMailbox($box) || isSentMailbox($box) || isDraftMailbox($box) );
189
190 if ( !$ret ) {
191 $ret = boolean_hook_function('special_mailbox',$box,1);
192 }
193 return $ret;
194 }
195
196 /**
197 * @return bool whether this is a Trash folder
198 */
199 function isTrashMailbox ($box) {
200 global $trash_folder, $move_to_trash;
201 return $move_to_trash && $trash_folder &&
202 ( $box == $trash_folder || isBoxBelow($box, $trash_folder) );
203 }
204
205 /**
206 * @return bool whether this is a Sent folder
207 */
208 function isSentMailbox($box) {
209 global $sent_folder, $move_to_sent;
210 return $move_to_sent && $sent_folder &&
211 ( $box == $sent_folder || isBoxBelow($box, $sent_folder) );
212 }
213
214 /**
215 * @return bool whether this is a Draft folder
216 */
217 function isDraftMailbox($box) {
218 global $draft_folder, $save_as_draft;
219 return $save_as_draft &&
220 ( $box == $draft_folder || isBoxBelow($box, $draft_folder) );
221 }
222
223 /**
224 * Expunges a mailbox, ie. delete all contents.
225 */
226 function sqimap_mailbox_expunge ($imap_stream, $mailbox, $handle_errors = true, $id='') {
227 if ($id) {
228 if (is_array($id)) {
229 $id = sqimap_message_list_squisher($id);
230 }
231 $id = ' '.$id;
232 $uid = TRUE;
233 } else {
234 $uid = false;
235 }
236 $read = sqimap_run_command($imap_stream, 'EXPUNGE'.$id, $handle_errors,
237 $response, $message, $uid);
238 $cnt = 0;
239
240 if (is_array($read)) {
241 foreach ($read as $r) {
242 if (preg_match('/^\*\s[0-9]+\sEXPUNGE/AUi',$r,$regs)) {
243 $cnt++;
244 }
245 }
246 }
247 return $cnt;
248 }
249
250 /**
251 * Expunge specified message, updated $msgs and $msort
252 *
253 * Until Marc and I come up with a better way to maintain
254 * these stupid arrays, we'll use this wrapper function to
255 * remove the message with the matching UID .. the order
256 * won't be changed - the array element for the message
257 * will just be removed.
258 */
259 function sqimap_mailbox_expunge_dmn($message_id, $aMbxResponse, &$server_sort_array)
260 {
261 global $msgs, $msort, $sort, $imapConnection,
262 $mailbox, $auto_expunge,
263 $sort, $allow_server_sort, $thread_sort_messages, $allow_thread_sort,
264 $username, $data_dir;
265 $cnt = 0;
266
267 if (!isset($sort) || $sort === false) {
268 sqgetGlobalVar('sort',$sort,SQ_GET);
269 }
270 // Got to grab this out of prefs, since it isn't saved from mailbox_view.php
271 if ($allow_thread_sort) {
272 $thread_sort_messages = getPref($data_dir, $username, "thread_$mailbox",0);
273 }
274
275 for ($i = 0; $i < count($msort); $i++) {
276 if ($msgs[$i]['ID'] == $message_id) {
277 break;
278 }
279 }
280
281 if ( isset($msgs) ) {
282 unset($msgs[$i]);
283 $msgs = array_values($msgs);
284 sqsession_register($msgs, 'msgs');
285 }
286
287 if ( isset($msort) ) {
288 unset($msort[$i]);
289 $msort = array_values($msort);
290 sqsession_register($msort, 'msort');
291 }
292
293 if ($auto_expunge) {
294 $cnt = sqimap_mailbox_expunge($imapConnection, $mailbox, true);
295 } else {
296 return $cnt;
297 }
298
299 // And after all that mucking around, update the sort list!
300 // Remind me why the hell we need those two arrays again?!
301
302 if ( $allow_thread_sort && $thread_sort_messages ) {
303 $server_sort_array = get_thread_sort($imapConnection);
304 } elseif ( $allow_server_sort ) {
305 if (is_array($server_sort_array)) {
306 $key = array_search($message_id,$server_sort_array,true);
307 if ($key !== false) {
308 unset($server_sort_array[$key]);
309 $server_sort_array = array_values($server_sort_array);
310 } else {
311 $server_sort_array = sqimap_get_sort_order($imapConnection,$sort,$aMbxResponse);
312 }
313 } else {
314 $server_sort_array = sqimap_get_sort_order($imapConnection,$sort,$aMbxResponse);
315 }
316 } else {
317 $server_sort_array = sqimap_get_php_sort_order($imapConnection,
318 $sort,$aMbxResponse);
319 }
320 sqsession_register($server_sort_array,'server_sort_array');
321 return $cnt;
322 }
323
324 /**
325 * Checks whether or not the specified mailbox exists
326 */
327 function sqimap_mailbox_exists ($imap_stream, $mailbox) {
328 if (!isset($mailbox) || empty($mailbox)) {
329 return false;
330 }
331 $mbx = sqimap_run_command($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mailbox),
332 true, $response, $message);
333 return isset($mbx[0]);
334 }
335
336 /**
337 * Selects a mailbox
338 */
339 function sqimap_mailbox_select ($imap_stream, $mailbox) {
340 if ($mailbox == 'None') {
341 return;
342 }
343
344 $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
345 true, $response, $message);
346 $result = array();
347 for ($i = 0, $cnt = count($read); $i < $cnt; $i++) {
348 if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
349 $result[strtoupper($regs[1])] = $regs[2];
350 } else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i], $regs)) {
351 $result[strtoupper($regs[2])] = $regs[1];
352 } else {
353 if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i], $regs)) {
354 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/","/\]/") ,'', $regs[1])) ;
355 $result['PERMANENTFLAGS'] = explode(' ',strtolower($regs[1]));
356 } else if (preg_match("/FLAGS(.*)/i",$read[$i], $regs)) {
357 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/") ,'', $regs[1])) ;
358 $result['FLAGS'] = explode(' ',strtolower($regs[1]));
359 }
360 }
361 }
362 if (!isset($result['PERMANENTFLAGS'])) {
363 $result['PERMANENTFLAGS'] = $result['FLAGS'];
364 }
365 if (preg_match('/^\[(.+)\]/',$message, $regs)) {
366 $result['RIGHTS']=strtoupper($regs[1]);
367 }
368
369 return $result;
370 }
371
372 /**
373 * Creates a folder.
374 */
375 function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
376 global $delimiter;
377 if (strtolower($type) == 'noselect') {
378 $mailbox .= $delimiter;
379 }
380
381 $read_ary = sqimap_run_command($imap_stream, 'CREATE ' .
382 sqimap_encode_mailbox_name($mailbox),
383 true, $response, $message);
384 sqimap_subscribe ($imap_stream, $mailbox);
385 }
386
387 /**
388 * Subscribes to an existing folder.
389 */
390 function sqimap_subscribe ($imap_stream, $mailbox,$debug=true) {
391 $read_ary = sqimap_run_command($imap_stream, 'SUBSCRIBE ' .
392 sqimap_encode_mailbox_name($mailbox),
393 $debug, $response, $message);
394 }
395
396 /**
397 * Unsubscribes from an existing folder
398 */
399 function sqimap_unsubscribe ($imap_stream, $mailbox) {
400 $read_ary = sqimap_run_command($imap_stream, 'UNSUBSCRIBE ' .
401 sqimap_encode_mailbox_name($mailbox),
402 false, $response, $message);
403 }
404
405 /**
406 * Deletes the given folder
407 */
408 function sqimap_mailbox_delete ($imap_stream, $mailbox) {
409 global $data_dir, $username;
410 sqimap_unsubscribe ($imap_stream, $mailbox);
411 $read_ary = sqimap_run_command($imap_stream, 'DELETE ' .
412 sqimap_encode_mailbox_name($mailbox),
413 true, $response, $message);
414 if ($response !== 'OK') {
415 // subscribe again
416 sqimap_subscribe ($imap_stream, $mailbox);
417 } else {
418 do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
419 removePref($data_dir, $username, "thread_$mailbox");
420 }
421 }
422
423 /**
424 * Determines if the user is subscribed to the folder or not
425 */
426 function sqimap_mailbox_is_subscribed($imap_stream, $folder) {
427 $boxesall = sqimap_mailbox_list ($imap_stream);
428 foreach ($boxesall as $ref) {
429 if ($ref['unformatted'] == $folder) {
430 return true;
431 }
432 }
433 return false;
434 }
435
436 /**
437 * Renames a mailbox.
438 */
439 function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
440 if ( $old_name != $new_name ) {
441 global $delimiter, $imap_server_type, $data_dir, $username;
442 if ( substr( $old_name, -1 ) == $delimiter ) {
443 $old_name = substr( $old_name, 0, strlen( $old_name ) - 1 );
444 $new_name = substr( $new_name, 0, strlen( $new_name ) - 1 );
445 $postfix = $delimiter;
446 } else {
447 $postfix = '';
448 }
449
450 $boxesall = sqimap_mailbox_list($imap_stream);
451 $cmd = 'RENAME ' . sqimap_encode_mailbox_name($old_name) .
452 ' ' . sqimap_encode_mailbox_name($new_name);
453 $data = sqimap_run_command($imap_stream, $cmd, true, $response, $message);
454 sqimap_unsubscribe($imap_stream, $old_name.$postfix);
455 $oldpref = getPref($data_dir, $username, 'thread_'.$old_name.$postfix);
456 removePref($data_dir, $username, 'thread_'.$old_name.$postfix);
457 sqimap_subscribe($imap_stream, $new_name.$postfix);
458 setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref);
459 do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
460 $l = strlen( $old_name ) + 1;
461 $p = 'unformatted';
462
463 foreach ($boxesall as $box) {
464 if (substr($box[$p], 0, $l) == $old_name . $delimiter) {
465 $new_sub = $new_name . $delimiter . substr($box[$p], $l);
466 if ($imap_server_type == 'cyrus') {
467 $cmd = 'RENAME "' . $box[$p] . '" "' . $new_sub . '"';
468 $data = sqimap_run_command($imap_stream, $cmd, true,
469 $response, $message);
470 }
471 sqimap_unsubscribe($imap_stream, $box[$p]);
472 $oldpref = getPref($data_dir, $username, 'thread_'.$box[$p]);
473 removePref($data_dir, $username, 'thread_'.$box[$p]);
474 sqimap_subscribe($imap_stream, $new_sub);
475 setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref);
476 do_hook_function('rename_or_delete_folder',
477 $args = array($box[$p], 'rename', $new_sub));
478 }
479 }
480 }
481 }
482
483 /**
484 * Formats a mailbox into parts for the $boxesall array
485 *
486 * The parts are:
487 *
488 * raw - Raw LIST/LSUB response from the IMAP server
489 * formatted - nicely formatted folder name
490 * unformatted - unformatted, but with delimiter at end removed
491 * unformatted-dm - folder name as it appears in raw response
492 * unformatted-disp - unformatted without $folder_prefix
493 */
494 function sqimap_mailbox_parse ($line, $line_lsub) {
495 global $folder_prefix, $delimiter;
496
497 /* Process each folder line */
498 for ($g = 0, $cnt = count($line); $g < $cnt; ++$g) {
499 /* Store the raw IMAP reply */
500 if (isset($line[$g])) {
501 $boxesall[$g]['raw'] = $line[$g];
502 } else {
503 $boxesall[$g]['raw'] = '';
504 }
505
506 /* Count number of delimiters ($delimiter) in folder name */
507 $mailbox = /*trim(*/$line_lsub[$g]/*)*/;
508 $dm_count = substr_count($mailbox, $delimiter);
509 if (substr($mailbox, -1) == $delimiter) {
510 /* If name ends in delimiter, decrement count by one */
511 $dm_count--;
512 }
513
514 /* Format folder name, but only if it's a INBOX.* or has a parent. */
515 $boxesallbyname[$mailbox] = $g;
516 $parentfolder = readMailboxParent($mailbox, $delimiter);
517 if ( (strtolower(substr($mailbox, 0, 5)) == "inbox") ||
518 (substr($mailbox, 0, strlen($folder_prefix)) == $folder_prefix) ||
519 (isset($boxesallbyname[$parentfolder]) &&
520 (strlen($parentfolder) > 0) ) ) {
521 $indent = $dm_count - (substr_count($folder_prefix, $delimiter));
522 if ($indent > 0) {
523 $boxesall[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $indent);
524 } else {
525 $boxesall[$g]['formatted'] = '';
526 }
527 $boxesall[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
528 } else {
529 $boxesall[$g]['formatted'] = imap_utf7_decode_local($mailbox);
530 }
531
532 $boxesall[$g]['unformatted-dm'] = $mailbox;
533 if (substr($mailbox, -1) == $delimiter) {
534 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
535 }
536 $boxesall[$g]['unformatted'] = $mailbox;
537 if (substr($mailbox,0,strlen($folder_prefix))==$folder_prefix) {
538 $mailbox = substr($mailbox, strlen($folder_prefix));
539 }
540 $boxesall[$g]['unformatted-disp'] = $mailbox;
541 $boxesall[$g]['id'] = $g;
542
543 $boxesall[$g]['flags'] = array();
544 if (isset($line[$g])) {
545 ereg("\(([^)]*)\)",$line[$g],$regs);
546 // FIXME Flags do contain the \ character. \NoSelect \NoInferiors
547 // and $MDNSent <= last one doesn't have the \
548 // It's better to follow RFC3501 instead of using our own naming.
549 $flags = trim(strtolower(str_replace('\\', '',$regs[1])));
550 if ($flags) {
551 $boxesall[$g]['flags'] = explode(' ', $flags);
552 }
553 }
554 }
555 return $boxesall;
556 }
557
558 /**
559 * Returns list of options (to be echoed into select statement
560 * based on available mailboxes and separators
561 * Caller should surround options with <SELECT..> </SELECT> and
562 * any formatting.
563 * $imap_stream - $imapConnection to query for mailboxes
564 * $show_selected - array containing list of mailboxes to pre-select (0 if none)
565 * $folder_skip - array of folders to keep out of option list (compared in lower)
566 * $boxes - list of already fetched boxes (for places like folder panel, where
567 * you know these options will be shown 3 times in a row.. (most often unset).
568 * $flag - flag to check for in mailbox flags, used to filter out mailboxes.
569 * 'noselect' by default to remove unselectable mailboxes.
570 * 'noinferiors' used to filter out folders that can not contain subfolders.
571 * NULL to avoid flag check entirely.
572 * NOTE: noselect and noiferiors are used internally. The IMAP representation is
573 * \NoSelect and \NoInferiors
574 * $use_long_format - override folder display preference and always show full folder name.
575 */
576 function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
577 $flag = 'noselect', $use_long_format = false ) {
578 global $username, $data_dir;
579 $mbox_options = '';
580 if ( $use_long_format ) {
581 $shorten_box_names = 0;
582 } else {
583 $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF);
584 }
585
586 if ($boxes == 0) {
587 $boxes = sqimap_mailbox_list($imap_stream);
588 }
589
590 foreach ($boxes as $boxes_part) {
591 if ($flag == NULL || !in_array($flag, $boxes_part['flags'])) {
592 $box = $boxes_part['unformatted'];
593
594 if ($folder_skip != 0 && in_array($box, $folder_skip) ) {
595 continue;
596 }
597 $lowerbox = strtolower($box);
598 // mailboxes are casesensitive => inbox.sent != inbox.Sent
599 // nevermind, to many dependencies this should be fixed!
600
601 if (strtolower($box) == 'inbox') { // inbox is special and not casesensitive
602 $box2 = _("INBOX");
603 } else {
604 switch ($shorten_box_names)
605 {
606 case 2: /* delimited, style = 2 */
607 $box2 = str_replace('&nbsp;&nbsp;', '.&nbsp;', $boxes_part['formatted']);
608 break;
609 case 1: /* indent, style = 1 */
610 $box2 = $boxes_part['formatted'];
611 break;
612 default: /* default, long names, style = 0 */
613 $box2 = str_replace(' ', '&nbsp;', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
614 break;
615 }
616 }
617 if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
618 $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'" SELECTED>'.$box2.'</OPTION>' . "\n";
619 } else {
620 $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'">'.$box2.'</OPTION>' . "\n";
621 }
622 }
623 }
624 return $mbox_options;
625 }
626
627 /**
628 * Returns sorted mailbox lists in several different ways.
629 * See comment on sqimap_mailbox_parse() for info about the returned array.
630 */
631
632
633 function sqimap_mailbox_list($imap_stream, $force=false) {
634 global $default_folder_prefix;
635
636 if (!sqgetGlobalVar('boxesnew',$boxesnew,SQ_SESSION) || $force) {
637 global $data_dir, $username, $list_special_folders_first,
638 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
639 $move_to_trash, $move_to_sent, $save_as_draft,
640 $delimiter, $noselect_fix_enable, $imap_server_type;
641 $inbox_in_list = false;
642 $inbox_subscribed = false;
643 $listsubscribed = sqimap_capability($imap_stream,'LIST-SUBSCRIBED');
644
645 require_once(SM_PATH . 'include/load_prefs.php');
646
647
648 if ($listsubscribed) {
649 $lsub = 'LIST (SUBSCRIBED)';
650 } else {
651 $lsub = 'LSUB';
652 }
653
654 if ($noselect_fix_enable) {
655
656 $lsub_args = "$lsub \"$folder_prefix\" \"*%\"";
657 } else {
658 $lsub_args = "$lsub \"$folder_prefix\" \"*\"";
659 }
660 /* LSUB array */
661 $lsub_ary = sqimap_run_command ($imap_stream, $lsub_args,
662 true, $response, $message);
663 $lsub_ary = compact_mailboxes_response($lsub_ary);
664
665 $sorted_lsub_ary = array();
666 for ($i = 0, $cnt = count($lsub_ary);$i < $cnt; $i++) {
667
668 $temp_mailbox_name = find_mailbox_name($lsub_ary[$i]);
669 $sorted_lsub_ary[] = $temp_mailbox_name;
670 if (!$inbox_subscribed && strtoupper($temp_mailbox_name) == 'INBOX') {
671 $inbox_subscribed = true;
672 }
673 }
674
675 /* natural sort mailboxes */
676 if (isset($sorted_lsub_ary)) {
677 usort($sorted_lsub_ary, 'strnatcasecmp');
678 }
679 /*
680 * The LSUB response doesn't provide us information about \Noselect
681 * mail boxes. The LIST response does, that's why we need to do a LIST
682 * call to retrieve the flags for the mailbox
683 * Note: according RFC2060 an imap server may provide \NoSelect flags in the LSUB response.
684 * in other words, we cannot rely on it.
685 */
686 $sorted_list_ary = array();
687 // if (!$listsubscribed) {
688 for ($i=0; $i < count($sorted_lsub_ary); $i++) {
689 if (substr($sorted_lsub_ary[$i], -1) == $delimiter) {
690 $mbx = substr($sorted_lsub_ary[$i], 0, strlen($sorted_lsub_ary[$i])-1);
691 }
692 else {
693 $mbx = $sorted_lsub_ary[$i];
694 }
695
696 $read = sqimap_run_command ($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mbx),
697 true, $response, $message);
698
699 $read = compact_mailboxes_response($read);
700
701 if (isset($read[0])) {
702 $sorted_list_ary[$i] = $read[0];
703 } else {
704 $sorted_list_ary[$i] = '';
705 }
706 }
707 // }
708 /*
709 * Just in case they're not subscribed to their inbox,
710 * we'll get it for them anyway
711 */
712 if (!$inbox_subscribed) {
713 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" "INBOX"',
714 true, $response, $message);
715 $sorted_list_ary[] = implode('',compact_mailboxes_response($inbox_ary));
716 $sorted_lsub_ary[] = find_mailbox_name($inbox_ary[0]);
717 }
718
719 $boxesall = sqimap_mailbox_parse ($sorted_list_ary, $sorted_lsub_ary);
720
721 /* Now, lets sort for special folders */
722 $boxesnew = $used = array();
723
724 /* Find INBOX */
725 $cnt = count($boxesall);
726 $used = array_pad($used,$cnt,false);
727 for($k = 0; $k < $cnt; ++$k) {
728 if (strtolower($boxesall[$k]['unformatted']) == 'inbox') {
729 $boxesnew[] = $boxesall[$k];
730 $used[$k] = true;
731 break;
732 }
733 }
734 /* List special folders and their subfolders, if requested. */
735 if ($list_special_folders_first) {
736 for($k = 0; $k < $cnt; ++$k) {
737 if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
738 $boxesnew[] = $boxesall[$k];
739 $used[$k] = true;
740 }
741 }
742 }
743 /* Rest of the folders */
744 for($k = 0; $k < $cnt; $k++) {
745 if (!$used[$k]) {
746 $boxesnew[] = $boxesall[$k];
747 }
748 }
749 sqsession_register($boxesnew,'boxesnew');
750 }
751 return $boxesnew;
752 }
753
754 /**
755 * Returns a list of all folders, subscribed or not
756 */
757 function sqimap_mailbox_list_all($imap_stream) {
758 global $list_special_folders_first, $folder_prefix, $delimiter;
759
760 $read_ary = sqimap_run_command($imap_stream,"LIST \"$folder_prefix\" *",true,$response, $message,false);
761 $read_ary = compact_mailboxes_response($read_ary);
762
763 $g = 0;
764 $phase = 'inbox';
765 $fld_pre_length = strlen($folder_prefix);
766 for ($i = 0, $cnt = count($read_ary); $i < $cnt; $i++) {
767 /* Store the raw IMAP reply */
768 $boxes[$g]['raw'] = $read_ary[$i];
769
770 /* Count number of delimiters ($delimiter) in folder name */
771 $mailbox = find_mailbox_name($read_ary[$i]);
772 $dm_count = substr_count($mailbox, $delimiter);
773 if (substr($mailbox, -1) == $delimiter) {
774 /* If name ends in delimiter - decrement count by one */
775 $dm_count--;
776 }
777
778 /* Format folder name, but only if it's a INBOX.* or has a parent. */
779 $boxesallbyname[$mailbox] = $g;
780 $parentfolder = readMailboxParent($mailbox, $delimiter);
781 if((eregi('^inbox'.quotemeta($delimiter), $mailbox)) ||
782 (ereg('^'.$folder_prefix, $mailbox)) ||
783 ( isset($boxesallbyname[$parentfolder]) && (strlen($parentfolder) > 0) ) ) {
784 if ($dm_count) {
785 $boxes[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $dm_count);
786 } else {
787 $boxes[$g]['formatted'] = '';
788 }
789 $boxes[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
790 } else {
791 $boxes[$g]['formatted'] = imap_utf7_decode_local($mailbox);
792 }
793
794 $boxes[$g]['unformatted-dm'] = $mailbox;
795 if (substr($mailbox, -1) == $delimiter) {
796 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
797 }
798 $boxes[$g]['unformatted'] = $mailbox;
799 $boxes[$g]['unformatted-disp'] = substr($mailbox,$fld_pre_length);
800
801 $boxes[$g]['id'] = $g;
802
803 /* Now lets get the flags for this mailbox */
804 $read_mlbx = $read_ary[$i];
805 $flags = substr($read_mlbx, strpos($read_mlbx, '(')+1);
806 $flags = substr($flags, 0, strpos($flags, ')'));
807 $flags = str_replace('\\', '', $flags);
808 $flags = trim(strtolower($flags));
809 if ($flags) {
810 $boxes[$g]['flags'] = explode(' ', $flags);
811 } else {
812 $boxes[$g]['flags'] = array();
813 }
814 $g++;
815 }
816 if(is_array($boxes)) {
817 sort ($boxes);
818 }
819
820 return $boxes;
821 }
822
823 function sqimap_mailbox_tree($imap_stream) {
824 global $boxesnew, $default_folder_prefix, $unseen_notify, $unseen_type;
825 if (true) {
826
827 global $data_dir, $username, $list_special_folders_first,
828 $folder_prefix, $delimiter, $trash_folder, $move_to_trash,
829 $imap_server_type;
830
831
832 $inbox_in_list = false;
833 $inbox_subscribed = false;
834 $noselect = false;
835 $noinferiors = false;
836
837 require_once(SM_PATH . 'include/load_prefs.php');
838
839 /* LSUB array */
840 $lsub_ary = sqimap_run_command ($imap_stream, "LSUB \"$folder_prefix\" \"*\"",
841 true, $response, $message);
842 $lsub_ary = compact_mailboxes_response($lsub_ary);
843
844 /* Check to see if we have an INBOX */
845 $has_inbox = false;
846
847 for ($i = 0, $cnt = count($lsub_ary); $i < $cnt; $i++) {
848 if (preg_match("/^\*\s+LSUB.*\s\"?INBOX\"?[^(\/\.)].*$/i",$lsub_ary[$i])) {
849 $lsub_ary[$i] = strtoupper($lsub_ary[$i]);
850 // in case of an unsubscribed inbox an imap server can
851 // return the inbox in the lsub results with a \NoSelect
852 // flag.
853 if (!preg_match("/\*\s+LSUB\s+\(.*\\\\NoSelect.*\).*/i",$lsub_ary[$i])) {
854 $has_inbox = true;
855 } else {
856 // remove the result and request it again with a list
857 // response at a later stage.
858 unset($lsub_ary[$i]);
859 // re-index the array otherwise the addition of the LIST
860 // response will fail in PHP 4.1.2 and probably other older versions
861 $lsub_ary = array_values($lsub_ary);
862 }
863 break;
864 }
865 }
866
867 if ($has_inbox == false) {
868 // do a list request for inbox because we should always show
869 // inbox even if the user isn't subscribed to it.
870 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" INBOX',
871 true, $response, $message);
872 $inbox_ary = compact_mailboxes_response($inbox_ary);
873 if (count($inbox_ary)) {
874 $lsub_ary[] = $inbox_ary[0];
875 }
876 }
877
878 /*
879 * Section about removing the last element was removed
880 * We don't return "* OK" anymore from sqimap_read_data
881 */
882
883 $sorted_lsub_ary = array();
884 $cnt = count($lsub_ary);
885 for ($i = 0; $i < $cnt; $i++) {
886 $mbx = find_mailbox_name($lsub_ary[$i]);
887
888 // only do the noselect test if !uw, is checked later. FIX ME see conf.pl setting
889 if ($imap_server_type != "uw") {
890 $noselect = check_is_noselect($lsub_ary[$i]);
891 $noinferiors = check_is_noinferiors($lsub_ary[$i]);
892 }
893 if (substr($mbx, -1) == $delimiter) {
894 $mbx = substr($mbx, 0, strlen($mbx) - 1);
895 }
896 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
897 }
898 // FIX ME this requires a config setting inside conf.pl instead of checking on server type
899 if ($imap_server_type == "uw") {
900 $aQuery = array();
901 $aTag = array();
902 // prepare an array with queries
903 foreach ($sorted_lsub_ary as $aMbx) {
904 $mbx = stripslashes($aMbx['mbx']);
905 sqimap_prepare_pipelined_query('LIST "" ' . sqimap_encode_mailbox_name($mbx), $tag, $aQuery, false);
906 $aTag[$tag] = $mbx;
907 }
908 $sorted_lsub_ary = array();
909 // execute all the queries at once
910 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
911 foreach($aTag as $tag => $mbx) {
912 if ($aServerResponse[$tag] == 'OK') {
913 $sResponse = implode('', $aResponse[$tag]);
914 $noselect = check_is_noselect($sResponse);
915 $noinferiors = check_is_noinferiors($sResponse);
916 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
917 }
918 }
919 $cnt = count($sorted_lsub_ary);
920 }
921 $sorted_lsub_ary = array_values($sorted_lsub_ary);
922 array_multisort($sorted_lsub_ary, SORT_ASC, SORT_REGULAR);
923 $boxesnew = sqimap_fill_mailbox_tree($sorted_lsub_ary,false,$imap_stream);
924 return $boxesnew;
925 }
926 }
927
928 function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
929 global $data_dir, $username, $list_special_folders_first,
930 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
931 $move_to_trash, $move_to_sent, $save_as_draft,
932 $delimiter, $imap_server_type;
933
934 $special_folders = array ('INBOX', $sent_folder, $draft_folder, $trash_folder);
935
936 /* create virtual root node */
937 $mailboxes= new mailboxes();
938 $mailboxes->is_root = true;
939 $trail_del = false;
940 $start = 0;
941
942
943 if (isset($folder_prefix) && ($folder_prefix != '')) {
944 $start = substr_count($folder_prefix,$delimiter);
945 if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
946 $trail_del = true;
947 $mailboxes->mailboxname_full = substr($folder_prefix,0, (strlen($folder_prefix)-1));
948 } else {
949 $mailboxes->mailboxname_full = $folder_prefix;
950 $start++;
951 }
952 $mailboxes->mailboxname_sub = $mailboxes->mailboxname_full;
953 } else {
954 $start = 0;
955 }
956
957 $cnt = count($mbx_ary);
958 for ($i=0; $i < $cnt; $i++) {
959 if ($mbx_ary[$i]['mbx'] !='' ) {
960 $mbx = new mailboxes();
961 $mailbox = $mbx_ary[$i]['mbx'];
962
963 /*
964 sent subfolders messes up using existing code as subfolders
965 were marked, but the parents were ordered somewhere else in
966 the list, despite having "special folders at top" option set.
967 Need a better method than this.
968 */
969 /*
970 if ($mailbox == 'INBOX') {
971 $mbx->is_special = true;
972 } elseif (stristr($trash_folder , $mailbox)) {
973 $mbx->is_special = true;
974 } elseif (stristr($sent_folder , $mailbox)) {
975 $mbx->is_special = true;
976 } elseif (stristr($draft_folder , $mailbox)) {
977 $mbx->is_special = true;
978 }
979
980 switch ($mailbox) {
981 case 'INBOX':
982 $mbx->is_inbox = true;
983 $mbx->is_special = true;
984 $mbx_ary[$i]['noselect'] = false;
985 break;
986 case $trash_folder:
987 $mbx->is_trash = true;
988 $mbx->is_special = true;
989 break;
990 case $sent_folder:
991 $mbx->is_sent = true;
992 $mbx->is_special = true;
993 break;
994 case $draft_folder:
995 $mbx->is_draft = true;
996 $mbx->is_special = true;
997 break;
998 }
999 */
1000 $mbx->is_special |= ($mbx->is_inbox = (strtoupper($mailbox) == 'INBOX'));
1001 $mbx->is_special |= ($mbx->is_trash = isTrashMailbox($mailbox));
1002 $mbx->is_special |= ($mbx->is_sent = isSentMailbox($mailbox));
1003 $mbx->is_special |= ($mbx->is_draft = isDraftMailbox($mailbox));
1004 if (!$mbx->is_special)
1005 $mbx->is_special = boolean_hook_function('special_mailbox', $mailbox, 1);
1006
1007 if (isset($mbx_ary[$i]['unseen'])) {
1008 $mbx->unseen = $mbx_ary[$i]['unseen'];
1009 }
1010 if (isset($mbx_ary[$i]['nummessages'])) {
1011 $mbx->total = $mbx_ary[$i]['nummessages'];
1012 }
1013
1014 $mbx->is_noselect = $mbx_ary[$i]['noselect'];
1015 $mbx->is_noinferiors = $mbx_ary[$i]['noinferiors'];
1016
1017 $r_del_pos = strrpos($mbx_ary[$i]['mbx'], $delimiter);
1018 if ($r_del_pos) {
1019 $mbx->mailboxname_sub = substr($mbx_ary[$i]['mbx'],$r_del_pos+1);
1020 } else { /* mailbox is root folder */
1021 $mbx->mailboxname_sub = $mbx_ary[$i]['mbx'];
1022 }
1023 $mbx->mailboxname_full = $mbx_ary[$i]['mbx'];
1024
1025 $mailboxes->addMbx($mbx, $delimiter, $start, $list_special_folders_first);
1026 }
1027 }
1028 sqimap_utf7_decode_mbx_tree($mailboxes);
1029 sqimap_get_status_mbx_tree($imap_stream,$mailboxes);
1030 return $mailboxes;
1031 }
1032
1033 function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
1034 if (strtoupper($mbx_tree->mailboxname_full) == 'INBOX')
1035 $mbx_tree->mailboxname_sub = _("INBOX");
1036 else
1037 $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
1038 if ($mbx_tree->mbxs) {
1039 $iCnt = count($mbx_tree->mbxs);
1040 for ($i=0;$i<$iCnt;++$i) {
1041 $mbxs_tree->mbxs[$i] = sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs[$i]);
1042 }
1043 }
1044 }
1045
1046
1047 function sqimap_tree_to_ref_array(&$mbx_tree,&$aMbxs) {
1048 if ($mbx_tree)
1049 $aMbxs[] =& $mbx_tree;
1050 if ($mbx_tree->mbxs) {
1051 $iCnt = count($mbx_tree->mbxs);
1052 for ($i=0;$i<$iCnt;++$i) {
1053 sqimap_tree_to_ref_array($mbx_tree->mbxs[$i],$aMbxs);
1054 }
1055 }
1056 }
1057
1058 function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
1059 global $unseen_notify, $unseen_type, $trash_folder,$move_to_trash;
1060 $aMbxs = $aQuery = $aTag = array();
1061 sqimap_tree_to_ref_array($mbx_tree,$aMbxs);
1062 // remove the root node
1063 array_shift($aMbxs);
1064
1065 if($unseen_notify == 3) {
1066 $cnt = count($aMbxs);
1067 for($i=0;$i<$cnt;++$i) {
1068 $oMbx =& $aMbxs[$i];
1069 if (!$oMbx->is_noselect) {
1070 $mbx = $oMbx->mailboxname_full;
1071 if ($unseen_type == 2 ||
1072 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1073 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
1074 } else {
1075 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
1076 }
1077 sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
1078 } else {
1079 $oMbx->unseen = $oMbx->total = false;
1080 $tag = false;
1081 }
1082 $oMbx->tag = $tag;
1083 $aMbxs[$i] =& $oMbx;
1084 }
1085 // execute all the queries at once
1086 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
1087 $cnt = count($aMbxs);
1088 for($i=0;$i<$cnt;++$i) {
1089 $oMbx =& $aMbxs[$i];
1090 $tag = $oMbx->tag;
1091 if ($tag && $aServerResponse[$tag] == 'OK') {
1092 $sResponse = implode('', $aResponse[$tag]);
1093 if (preg_match('/UNSEEN\s+([0-9]+)/i', $sResponse, $regs)) {
1094 $oMbx->unseen = $regs[1];
1095 }
1096 if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
1097 $oMbx->total = $regs[1];
1098 }
1099 }
1100 unset($oMbx->tag);
1101 }
1102 } else if ($unseen_notify == 2) { // INBOX only
1103 $cnt = count($aMbxs);
1104 for($i=0;$i<$cnt;++$i) {
1105 $oMbx =& $aMbxs[$i];
1106 if (strtoupper($oMbx->mailboxname_full) == 'INBOX' ||
1107 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1108 if ($unseen_type == 2 ||
1109 ($oMbx->mailboxname_full == $trash_folder && $move_to_trash)) {
1110 $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full);
1111 $oMbx->unseen = $aStatus['UNSEEN'];
1112 $oMbx->total = $aStatus['MESSAGES'];
1113 } else {
1114 $oMbx->unseen = sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full);
1115 }
1116 $aMbxs[$i] =& $oMbx;
1117 if (!$move_to_trash && $trash_folder) {
1118 break;
1119 } else {
1120 // trash comes after INBOX
1121 if ($oMbx->mailboxname_full == $trash_folder) {
1122 break;
1123 }
1124 }
1125 }
1126 }
1127 }
1128 }
1129
1130 ?>