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