due to caching the dropdown mailbox list $boxesnew was set in the global
[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
298 sqgetGlobalVar('server_sort_array',$server_sort_array,SQ_SESSION);
299
300
301 if ( $allow_thread_sort && $thread_sort_messages ) {
302 $server_sort_array = get_thread_sort($imapConnection);
303 } elseif ( $allow_server_sort ) {
304 $key = array_search($message_id,$server_sort_array,true);
305 if ($key !== false) {
306 unset($server_sort_array[$key]);
307 $server_sort_array = array_values($server_sort_array);
308 }
309 } else {
310 $server_sort_array = sqimap_get_php_sort_order($imapConnection, $mbx_response);
311 }
312 sqsession_register('server_sort_array',$server_sort_array);
313 return $cnt;
314 }
315
316 /**
317 * Checks whether or not the specified mailbox exists
318 */
319 function sqimap_mailbox_exists ($imap_stream, $mailbox) {
320 if (!isset($mailbox) || empty($mailbox)) {
321 return false;
322 }
323 $mbx = sqimap_run_command($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mailbox),
324 true, $response, $message);
325 return isset($mbx[0]);
326 }
327
328 /**
329 * Selects a mailbox
330 */
331 function sqimap_mailbox_select ($imap_stream, $mailbox) {
332 if ($mailbox == 'None') {
333 return;
334 }
335
336 $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
337 true, $response, $message);
338 $result = array();
339 for ($i = 0, $cnt = count($read); $i < $cnt; $i++) {
340 if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
341 $result[strtoupper($regs[1])] = $regs[2];
342 } else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i], $regs)) {
343 $result[strtoupper($regs[2])] = $regs[1];
344 } else {
345 if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i], $regs)) {
346 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/","/\]/") ,'', $regs[1])) ;
347 $result['PERMANENTFLAGS'] = $regs[1];
348 } else if (preg_match("/FLAGS(.*)/i",$read[$i], $regs)) {
349 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/") ,'', $regs[1])) ;
350 $result['FLAGS'] = $regs[1];
351 }
352 }
353 }
354 if (preg_match('/^\[(.+)\]/',$message, $regs)) {
355 $result['RIGHTS']=$regs[1];
356 }
357
358 return $result;
359 }
360
361 /**
362 * Creates a folder.
363 */
364 function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
365 global $delimiter;
366 if (strtolower($type) == 'noselect') {
367 $mailbox .= $delimiter;
368 }
369
370 $read_ary = sqimap_run_command($imap_stream, 'CREATE ' .
371 sqimap_encode_mailbox_name($mailbox),
372 true, $response, $message);
373 sqimap_subscribe ($imap_stream, $mailbox);
374 }
375
376 /**
377 * Subscribes to an existing folder.
378 */
379 function sqimap_subscribe ($imap_stream, $mailbox,$debug=true) {
380 $read_ary = sqimap_run_command($imap_stream, 'SUBSCRIBE ' .
381 sqimap_encode_mailbox_name($mailbox),
382 $debug, $response, $message);
383 }
384
385 /**
386 * Unsubscribes from an existing folder
387 */
388 function sqimap_unsubscribe ($imap_stream, $mailbox) {
389 $read_ary = sqimap_run_command($imap_stream, 'UNSUBSCRIBE ' .
390 sqimap_encode_mailbox_name($mailbox),
391 false, $response, $message);
392 }
393
394 /**
395 * Deletes the given folder
396 */
397 function sqimap_mailbox_delete ($imap_stream, $mailbox) {
398 global $data_dir, $username;
399 sqimap_unsubscribe ($imap_stream, $mailbox);
400 $read_ary = sqimap_run_command($imap_stream, 'DELETE ' .
401 sqimap_encode_mailbox_name($mailbox),
402 true, $response, $message);
403 if ($response !== 'OK') {
404 // subscribe again
405 sqimap_subscribe ($imap_stream, $mailbox);
406 } else {
407 do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
408 removePref($data_dir, $username, "thread_$mailbox");
409 }
410 }
411
412 /**
413 * Determines if the user is subscribed to the folder or not
414 */
415 function sqimap_mailbox_is_subscribed($imap_stream, $folder) {
416 $boxesall = sqimap_mailbox_list ($imap_stream);
417 foreach ($boxesall as $ref) {
418 if ($ref['unformatted'] == $folder) {
419 return true;
420 }
421 }
422 return false;
423 }
424
425 /**
426 * Renames a mailbox.
427 */
428 function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
429 if ( $old_name != $new_name ) {
430 global $delimiter, $imap_server_type, $data_dir, $username;
431 if ( substr( $old_name, -1 ) == $delimiter ) {
432 $old_name = substr( $old_name, 0, strlen( $old_name ) - 1 );
433 $new_name = substr( $new_name, 0, strlen( $new_name ) - 1 );
434 $postfix = $delimiter;
435 } else {
436 $postfix = '';
437 }
438
439 $boxesall = sqimap_mailbox_list($imap_stream);
440 $cmd = 'RENAME ' . sqimap_encode_mailbox_name($old_name) .
441 ' ' . sqimap_encode_mailbox_name($new_name);
442 $data = sqimap_run_command($imap_stream, $cmd, true, $response, $message);
443 sqimap_unsubscribe($imap_stream, $old_name.$postfix);
444 $oldpref = getPref($data_dir, $username, 'thread_'.$old_name.$postfix);
445 removePref($data_dir, $username, 'thread_'.$old_name.$postfix);
446 sqimap_subscribe($imap_stream, $new_name.$postfix);
447 setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref);
448 do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
449 $l = strlen( $old_name ) + 1;
450 $p = 'unformatted';
451
452 foreach ($boxesall as $box) {
453 if (substr($box[$p], 0, $l) == $old_name . $delimiter) {
454 $new_sub = $new_name . $delimiter . substr($box[$p], $l);
455 if ($imap_server_type == 'cyrus') {
456 $cmd = 'RENAME "' . $box[$p] . '" "' . $new_sub . '"';
457 $data = sqimap_run_command($imap_stream, $cmd, true,
458 $response, $message);
459 }
460 sqimap_unsubscribe($imap_stream, $box[$p]);
461 $oldpref = getPref($data_dir, $username, 'thread_'.$box[$p]);
462 removePref($data_dir, $username, 'thread_'.$box[$p]);
463 sqimap_subscribe($imap_stream, $new_sub);
464 setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref);
465 do_hook_function('rename_or_delete_folder',
466 $args = array($box[$p], 'rename', $new_sub));
467 }
468 }
469 }
470 }
471
472 /**
473 * Formats a mailbox into parts for the $boxesall array
474 *
475 * The parts are:
476 *
477 * raw - Raw LIST/LSUB response from the IMAP server
478 * formatted - nicely formatted folder name
479 * unformatted - unformatted, but with delimiter at end removed
480 * unformatted-dm - folder name as it appears in raw response
481 * unformatted-disp - unformatted without $folder_prefix
482 */
483 function sqimap_mailbox_parse ($line, $line_lsub) {
484 global $folder_prefix, $delimiter;
485
486 /* Process each folder line */
487 for ($g = 0, $cnt = count($line); $g < $cnt; ++$g) {
488 /* Store the raw IMAP reply */
489 if (isset($line[$g])) {
490 $boxesall[$g]['raw'] = $line[$g];
491 } else {
492 $boxesall[$g]['raw'] = '';
493 }
494
495 /* Count number of delimiters ($delimiter) in folder name */
496 $mailbox = /*trim(*/$line_lsub[$g]/*)*/;
497 $dm_count = substr_count($mailbox, $delimiter);
498 if (substr($mailbox, -1) == $delimiter) {
499 /* If name ends in delimiter, decrement count by one */
500 $dm_count--;
501 }
502
503 /* Format folder name, but only if it's a INBOX.* or has a parent. */
504 $boxesallbyname[$mailbox] = $g;
505 $parentfolder = readMailboxParent($mailbox, $delimiter);
506 if ( (strtolower(substr($mailbox, 0, 5)) == "inbox") ||
507 (substr($mailbox, 0, strlen($folder_prefix)) == $folder_prefix) ||
508 (isset($boxesallbyname[$parentfolder]) &&
509 (strlen($parentfolder) > 0) ) ) {
510 $indent = $dm_count - (substr_count($folder_prefix, $delimiter));
511 if ($indent > 0) {
512 $boxesall[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $indent);
513 } else {
514 $boxesall[$g]['formatted'] = '';
515 }
516 $boxesall[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
517 } else {
518 $boxesall[$g]['formatted'] = imap_utf7_decode_local($mailbox);
519 }
520
521 $boxesall[$g]['unformatted-dm'] = $mailbox;
522 if (substr($mailbox, -1) == $delimiter) {
523 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
524 }
525 $boxesall[$g]['unformatted'] = $mailbox;
526 if (substr($mailbox,0,strlen($folder_prefix))==$folder_prefix) {
527 $mailbox = substr($mailbox, strlen($folder_prefix));
528 }
529 $boxesall[$g]['unformatted-disp'] = $mailbox;
530 $boxesall[$g]['id'] = $g;
531
532 $boxesall[$g]['flags'] = array();
533 if (isset($line[$g])) {
534 ereg("\(([^)]*)\)",$line[$g],$regs);
535 // FIXME Flags do contain the \ character. \NoSelect \NoInferiors
536 // and $MDNSent <= last one doesn't have the \
537 // It's better to follow RFC3501 instead of using our own naming.
538 $flags = trim(strtolower(str_replace('\\', '',$regs[1])));
539 if ($flags) {
540 $boxesall[$g]['flags'] = explode(' ', $flags);
541 }
542 }
543 }
544 return $boxesall;
545 }
546
547 /**
548 * Returns list of options (to be echoed into select statement
549 * based on available mailboxes and separators
550 * Caller should surround options with <SELECT..> </SELECT> and
551 * any formatting.
552 * $imap_stream - $imapConnection to query for mailboxes
553 * $show_selected - array containing list of mailboxes to pre-select (0 if none)
554 * $folder_skip - array of folders to keep out of option list (compared in lower)
555 * $boxes - list of already fetched boxes (for places like folder panel, where
556 * you know these options will be shown 3 times in a row.. (most often unset).
557 * $flag - flag to check for in mailbox flags, used to filter out mailboxes.
558 * 'noselect' by default to remove unselectable mailboxes.
559 * 'noinferiors' used to filter out folders that can not contain subfolders.
560 * NULL to avoid flag check entirely.
561 * NOTE: noselect and noiferiors are used internally. The IMAP representation is
562 * \NoSelect and \NoInferiors
563 * $use_long_format - override folder display preference and always show full folder name.
564 */
565 function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
566 $flag = 'noselect', $use_long_format = false ) {
567 global $username, $data_dir;
568 $mbox_options = '';
569 if ( $use_long_format ) {
570 $shorten_box_names = 0;
571 } else {
572 $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF);
573 }
574
575 if ($boxes == 0) {
576 $boxes = sqimap_mailbox_list($imap_stream);
577 }
578
579 foreach ($boxes as $boxes_part) {
580 if ($flag == NULL || !in_array($flag, $boxes_part['flags'])) {
581 $box = $boxes_part['unformatted'];
582
583 if ($folder_skip != 0 && in_array($box, $folder_skip) ) {
584 continue;
585 }
586 $lowerbox = strtolower($box);
587 // mailboxes are casesensitive => inbox.sent != inbox.Sent
588 // nevermind, to many dependencies this should be fixed!
589
590 if (strtolower($box) == 'inbox') { // inbox is special and not casesensitive
591 $box2 = _("INBOX");
592 } else {
593 switch ($shorten_box_names)
594 {
595 case 2: /* delimited, style = 2 */
596 $box2 = str_replace('&nbsp;&nbsp;', '.&nbsp;', $boxes_part['formatted']);
597 break;
598 case 1: /* indent, style = 1 */
599 $box2 = $boxes_part['formatted'];
600 break;
601 default: /* default, long names, style = 0 */
602 $box2 = str_replace(' ', '&nbsp;', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
603 break;
604 }
605 }
606 if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
607 $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'" SELECTED>'.$box2.'</OPTION>' . "\n";
608 } else {
609 $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'">'.$box2.'</OPTION>' . "\n";
610 }
611 }
612 }
613 return $mbox_options;
614 }
615
616 /**
617 * Returns sorted mailbox lists in several different ways.
618 * See comment on sqimap_mailbox_parse() for info about the returned array.
619 */
620
621
622 function sqimap_mailbox_list($imap_stream, $force=false) {
623 global $default_folder_prefix;
624
625 if (!sqgetGlobalVar('boxesnew',$boxesnew,SQ_SESSION) || $force) {
626 global $data_dir, $username, $list_special_folders_first,
627 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
628 $move_to_trash, $move_to_sent, $save_as_draft,
629 $delimiter, $noselect_fix_enable, $imap_server_type;
630 $inbox_in_list = false;
631 $inbox_subscribed = false;
632 $listsubscribed = sqimap_capability($imap_stream,'LIST-SUBSCRIBED');
633
634 require_once(SM_PATH . 'include/load_prefs.php');
635
636
637 if ($listsubscribed) {
638 $lsub = 'LIST (SUBSCRIBED)';
639 } else {
640 $lsub = 'LSUB';
641 }
642
643 if ($noselect_fix_enable) {
644
645 $lsub_args = "$lsub \"$folder_prefix\" \"*%\"";
646 } else {
647 $lsub_args = "$lsub \"$folder_prefix\" \"*\"";
648 }
649 /* LSUB array */
650 $lsub_ary = sqimap_run_command ($imap_stream, $lsub_args,
651 true, $response, $message);
652 $lsub_ary = compact_mailboxes_response($lsub_ary);
653
654 $sorted_lsub_ary = array();
655 for ($i = 0, $cnt = count($lsub_ary);$i < $cnt; $i++) {
656
657 $temp_mailbox_name = find_mailbox_name($lsub_ary[$i]);
658 $sorted_lsub_ary[] = $temp_mailbox_name;
659 if (!$inbox_subscribed && strtoupper($temp_mailbox_name) == 'INBOX') {
660 $inbox_subscribed = true;
661 }
662 }
663
664 /* natural sort mailboxes */
665 if (isset($sorted_lsub_ary)) {
666 usort($sorted_lsub_ary, 'strnatcasecmp');
667 }
668 /*
669 * The LSUB response doesn't provide us information about \Noselect
670 * mail boxes. The LIST response does, that's why we need to do a LIST
671 * call to retrieve the flags for the mailbox
672 * Note: according RFC2060 an imap server may provide \NoSelect flags in the LSUB response.
673 * in other words, we cannot rely on it.
674 */
675 $sorted_list_ary = array();
676 // if (!$listsubscribed) {
677 for ($i=0; $i < count($sorted_lsub_ary); $i++) {
678 if (substr($sorted_lsub_ary[$i], -1) == $delimiter) {
679 $mbx = substr($sorted_lsub_ary[$i], 0, strlen($sorted_lsub_ary[$i])-1);
680 }
681 else {
682 $mbx = $sorted_lsub_ary[$i];
683 }
684
685 $read = sqimap_run_command ($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mbx),
686 true, $response, $message);
687
688 $read = compact_mailboxes_response($read);
689
690 if (isset($read[0])) {
691 $sorted_list_ary[$i] = $read[0];
692 } else {
693 $sorted_list_ary[$i] = '';
694 }
695 }
696 // }
697 /*
698 * Just in case they're not subscribed to their inbox,
699 * we'll get it for them anyway
700 */
701 if (!$inbox_subscribed) {
702 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" "INBOX"',
703 true, $response, $message);
704 $sorted_list_ary[] = implode('',compact_mailboxes_response($inbox_ary));
705 $sorted_lsub_ary[] = find_mailbox_name($inbox_ary[0]);
706 }
707
708 $boxesall = sqimap_mailbox_parse ($sorted_list_ary, $sorted_lsub_ary);
709
710 /* Now, lets sort for special folders */
711 $boxesnew = $used = array();
712
713 /* Find INBOX */
714 $cnt = count($boxesall);
715 $used = array_pad($used,$cnt,false);
716 for($k = 0; $k < $cnt; ++$k) {
717 if (strtolower($boxesall[$k]['unformatted']) == 'inbox') {
718 $boxesnew[] = $boxesall[$k];
719 $used[$k] = true;
720 break;
721 }
722 }
723 /* List special folders and their subfolders, if requested. */
724 if ($list_special_folders_first) {
725 for($k = 0; $k < $cnt; ++$k) {
726 if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
727 $boxesnew[] = $boxesall[$k];
728 $used[$k] = true;
729 }
730 }
731 }
732 /* Rest of the folders */
733 for($k = 0; $k < $cnt; $k++) {
734 if (!$used[$k]) {
735 $boxesnew[] = $boxesall[$k];
736 }
737 }
738 sqsession_register($boxesnew,'boxesnew');
739 }
740 return $boxesnew;
741 }
742
743 /**
744 * Returns a list of all folders, subscribed or not
745 */
746 function sqimap_mailbox_list_all($imap_stream) {
747 global $list_special_folders_first, $folder_prefix, $delimiter;
748
749 $read_ary = sqimap_run_command($imap_stream,"LIST \"$folder_prefix\" *",true,$response, $message,false);
750 $read_ary = compact_mailboxes_response($read_ary);
751
752 $g = 0;
753 $phase = 'inbox';
754 $fld_pre_length = strlen($folder_prefix);
755 for ($i = 0, $cnt = count($read_ary); $i < $cnt; $i++) {
756 /* Store the raw IMAP reply */
757 $boxes[$g]['raw'] = $read_ary[$i];
758
759 /* Count number of delimiters ($delimiter) in folder name */
760 $mailbox = find_mailbox_name($read_ary[$i]);
761 $dm_count = substr_count($mailbox, $delimiter);
762 if (substr($mailbox, -1) == $delimiter) {
763 /* If name ends in delimiter - decrement count by one */
764 $dm_count--;
765 }
766
767 /* Format folder name, but only if it's a INBOX.* or has a parent. */
768 $boxesallbyname[$mailbox] = $g;
769 $parentfolder = readMailboxParent($mailbox, $delimiter);
770 if((eregi('^inbox'.quotemeta($delimiter), $mailbox)) ||
771 (ereg('^'.$folder_prefix, $mailbox)) ||
772 ( isset($boxesallbyname[$parentfolder]) && (strlen($parentfolder) > 0) ) ) {
773 if ($dm_count) {
774 $boxes[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $dm_count);
775 } else {
776 $boxes[$g]['formatted'] = '';
777 }
778 $boxes[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
779 } else {
780 $boxes[$g]['formatted'] = imap_utf7_decode_local($mailbox);
781 }
782
783 $boxes[$g]['unformatted-dm'] = $mailbox;
784 if (substr($mailbox, -1) == $delimiter) {
785 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
786 }
787 $boxes[$g]['unformatted'] = $mailbox;
788 $boxes[$g]['unformatted-disp'] = substr($mailbox,$fld_pre_length);
789
790 $boxes[$g]['id'] = $g;
791
792 /* Now lets get the flags for this mailbox */
793 $read_mlbx = $read_ary[$i];
794 $flags = substr($read_mlbx, strpos($read_mlbx, '(')+1);
795 $flags = substr($flags, 0, strpos($flags, ')'));
796 $flags = str_replace('\\', '', $flags);
797 $flags = trim(strtolower($flags));
798 if ($flags) {
799 $boxes[$g]['flags'] = explode(' ', $flags);
800 } else {
801 $boxes[$g]['flags'] = array();
802 }
803 $g++;
804 }
805 if(is_array($boxes)) {
806 sort ($boxes);
807 }
808
809 return $boxes;
810 }
811
812 function sqimap_mailbox_tree($imap_stream) {
813 global $boxesnew, $default_folder_prefix, $unseen_notify, $unseen_type;
814 if (true) {
815
816 global $data_dir, $username, $list_special_folders_first,
817 $folder_prefix, $delimiter, $trash_folder, $move_to_trash,
818 $imap_server_type;
819
820
821 $inbox_in_list = false;
822 $inbox_subscribed = false;
823 $noselect = false;
824 $noinferiors = false;
825
826 require_once(SM_PATH . 'include/load_prefs.php');
827
828 /* LSUB array */
829 $lsub_ary = sqimap_run_command ($imap_stream, "LSUB \"$folder_prefix\" \"*\"",
830 true, $response, $message);
831 $lsub_ary = compact_mailboxes_response($lsub_ary);
832
833 /* Check to see if we have an INBOX */
834 $has_inbox = false;
835
836 for ($i = 0, $cnt = count($lsub_ary); $i < $cnt; $i++) {
837 if (preg_match("/^\*\s+LSUB.*\s\"?INBOX\"?[^(\/\.)].*$/i",$lsub_ary[$i])) {
838 $lsub_ary[$i] = strtoupper($lsub_ary[$i]);
839 // in case of an unsubscribed inbox an imap server can
840 // return the inbox in the lsub results with a \NoSelect
841 // flag.
842 if (!preg_match("/\*\s+LSUB\s+\(.*\\\\NoSelect.*\).*/i",$lsub_ary[$i])) {
843 $has_inbox = true;
844 } else {
845 // remove the result and request it again with a list
846 // response at a later stage.
847 unset($lsub_ary[$i]);
848 // re-index the array otherwise the addition of the LIST
849 // response will fail in PHP 4.1.2 and probably other older versions
850 $lsub_ary = array_values($lsub_ary);
851 }
852 break;
853 }
854 }
855
856 if ($has_inbox == false) {
857 // do a list request for inbox because we should always show
858 // inbox even if the user isn't subscribed to it.
859 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" INBOX',
860 true, $response, $message);
861 $inbox_ary = compact_mailboxes_response($inbox_ary);
862 if (count($inbox_ary)) {
863 $lsub_ary[] = $inbox_ary[0];
864 }
865 }
866
867 /*
868 * Section about removing the last element was removed
869 * We don't return "* OK" anymore from sqimap_read_data
870 */
871
872 $sorted_lsub_ary = array();
873 $cnt = count($lsub_ary);
874 for ($i = 0; $i < $cnt; $i++) {
875 $mbx = find_mailbox_name($lsub_ary[$i]);
876
877 // only do the noselect test if !uw, is checked later. FIX ME see conf.pl setting
878 if ($imap_server_type != "uw") {
879 $noselect = check_is_noselect($lsub_ary[$i]);
880 $noinferiors = check_is_noinferiors($lsub_ary[$i]);
881 }
882 if (substr($mbx, -1) == $delimiter) {
883 $mbx = substr($mbx, 0, strlen($mbx) - 1);
884 }
885 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
886 }
887 // FIX ME this requires a config setting inside conf.pl instead of checking on server type
888 if ($imap_server_type == "uw") {
889 $aQuery = array();
890 $aTag = array();
891 // prepare an array with queries
892 foreach ($sorted_lsub_ary as $aMbx) {
893 $mbx = stripslashes($aMbx['mbx']);
894 sqimap_prepare_pipelined_query('LIST "" ' . sqimap_encode_mailbox_name($mbx), $tag, $aQuery, false);
895 $aTag[$tag] = $mbx;
896 }
897 $sorted_lsub_ary = array();
898 // execute all the queries at once
899 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
900 foreach($aTag as $tag => $mbx) {
901 if ($aServerResponse[$tag] == 'OK') {
902 $sResponse = implode('', $aResponse[$tag]);
903 $noselect = check_is_noselect($sResponse);
904 $noinferiors = check_is_noinferiors($sResponse);
905 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
906 }
907 }
908 $cnt = count($sorted_lsub_ary);
909 }
910 $sorted_lsub_ary = array_values($sorted_lsub_ary);
911 array_multisort($sorted_lsub_ary, SORT_ASC, SORT_REGULAR);
912 $boxesnew = sqimap_fill_mailbox_tree($sorted_lsub_ary,false,$imap_stream);
913 return $boxesnew;
914 }
915 }
916
917 function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
918 global $data_dir, $username, $list_special_folders_first,
919 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
920 $move_to_trash, $move_to_sent, $save_as_draft,
921 $delimiter, $imap_server_type;
922
923 $special_folders = array ('INBOX', $sent_folder, $draft_folder, $trash_folder);
924
925 /* create virtual root node */
926 $mailboxes= new mailboxes();
927 $mailboxes->is_root = true;
928 $trail_del = false;
929 $start = 0;
930
931
932 if (isset($folder_prefix) && ($folder_prefix != '')) {
933 $start = substr_count($folder_prefix,$delimiter);
934 if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
935 $trail_del = true;
936 $mailboxes->mailboxname_full = substr($folder_prefix,0, (strlen($folder_prefix)-1));
937 } else {
938 $mailboxes->mailboxname_full = $folder_prefix;
939 $start++;
940 }
941 $mailboxes->mailboxname_sub = $mailboxes->mailboxname_full;
942 } else {
943 $start = 0;
944 }
945
946 $cnt = count($mbx_ary);
947 for ($i=0; $i < $cnt; $i++) {
948 if ($mbx_ary[$i]['mbx'] !='' ) {
949 $mbx = new mailboxes();
950 $mailbox = $mbx_ary[$i]['mbx'];
951
952 /*
953 sent subfolders messes up using existing code as subfolders
954 were marked, but the parents were ordered somewhere else in
955 the list, despite having "special folders at top" option set.
956 Need a better method than this.
957 */
958 /*
959 if ($mailbox == 'INBOX') {
960 $mbx->is_special = true;
961 } elseif (stristr($trash_folder , $mailbox)) {
962 $mbx->is_special = true;
963 } elseif (stristr($sent_folder , $mailbox)) {
964 $mbx->is_special = true;
965 } elseif (stristr($draft_folder , $mailbox)) {
966 $mbx->is_special = true;
967 }
968
969 switch ($mailbox) {
970 case 'INBOX':
971 $mbx->is_inbox = true;
972 $mbx->is_special = true;
973 $mbx_ary[$i]['noselect'] = false;
974 break;
975 case $trash_folder:
976 $mbx->is_trash = true;
977 $mbx->is_special = true;
978 break;
979 case $sent_folder:
980 $mbx->is_sent = true;
981 $mbx->is_special = true;
982 break;
983 case $draft_folder:
984 $mbx->is_draft = true;
985 $mbx->is_special = true;
986 break;
987 }
988 */
989 $mbx->is_special |= ($mbx->is_inbox = (strtoupper($mailbox) == 'INBOX'));
990 $mbx->is_special |= ($mbx->is_trash = isTrashMailbox($mailbox));
991 $mbx->is_special |= ($mbx->is_sent = isSentMailbox($mailbox));
992 $mbx->is_special |= ($mbx->is_draft = isDraftMailbox($mailbox));
993 if (!$mbx->is_special)
994 $mbx->is_special = boolean_hook_function('special_mailbox', $mailbox, 1);
995
996 if (isset($mbx_ary[$i]['unseen'])) {
997 $mbx->unseen = $mbx_ary[$i]['unseen'];
998 }
999 if (isset($mbx_ary[$i]['nummessages'])) {
1000 $mbx->total = $mbx_ary[$i]['nummessages'];
1001 }
1002
1003 $mbx->is_noselect = $mbx_ary[$i]['noselect'];
1004 $mbx->is_noinferiors = $mbx_ary[$i]['noinferiors'];
1005
1006 $r_del_pos = strrpos($mbx_ary[$i]['mbx'], $delimiter);
1007 if ($r_del_pos) {
1008 $mbx->mailboxname_sub = substr($mbx_ary[$i]['mbx'],$r_del_pos+1);
1009 } else { /* mailbox is root folder */
1010 $mbx->mailboxname_sub = $mbx_ary[$i]['mbx'];
1011 }
1012 $mbx->mailboxname_full = $mbx_ary[$i]['mbx'];
1013
1014 $mailboxes->addMbx($mbx, $delimiter, $start, $list_special_folders_first);
1015 }
1016 }
1017 sqimap_utf7_decode_mbx_tree($mailboxes);
1018 sqimap_get_status_mbx_tree($imap_stream,$mailboxes);
1019 return $mailboxes;
1020 }
1021
1022 function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
1023 if (strtoupper($mbx_tree->mailboxname_full) == 'INBOX')
1024 $mbx_tree->mailboxname_sub = _("INBOX");
1025 else
1026 $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
1027 if ($mbx_tree->mbxs) {
1028 $iCnt = count($mbx_tree->mbxs);
1029 for ($i=0;$i<$iCnt;++$i) {
1030 $mbxs_tree->mbxs[$i] = sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs[$i]);
1031 }
1032 }
1033 }
1034
1035
1036 function sqimap_tree_to_ref_array(&$mbx_tree,&$aMbxs) {
1037 if ($mbx_tree)
1038 $aMbxs[] =& $mbx_tree;
1039 if ($mbx_tree->mbxs) {
1040 $iCnt = count($mbx_tree->mbxs);
1041 for ($i=0;$i<$iCnt;++$i) {
1042 sqimap_tree_to_ref_array($mbx_tree->mbxs[$i],$aMbxs);
1043 }
1044 }
1045 }
1046
1047 function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
1048 global $unseen_notify, $unseen_type, $trash_folder,$move_to_trash;
1049 $aMbxs = $aQuery = $aTag = array();
1050 sqimap_tree_to_ref_array($mbx_tree,$aMbxs);
1051 // remove the root node
1052 array_shift($aMbxs);
1053
1054 if($unseen_notify == 3) {
1055 $cnt = count($aMbxs);
1056 for($i=0;$i<$cnt;++$i) {
1057 $oMbx =& $aMbxs[$i];
1058 if (!$oMbx->is_noselect) {
1059 $mbx = $oMbx->mailboxname_full;
1060 if ($unseen_type == 2 ||
1061 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1062 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
1063 } else {
1064 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
1065 }
1066 sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
1067 } else {
1068 $oMbx->unseen = $oMbx->total = false;
1069 $tag = false;
1070 }
1071 $oMbx->tag = $tag;
1072 $aMbxs[$i] =& $oMbx;
1073 }
1074 // execute all the queries at once
1075 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
1076 $cnt = count($aMbxs);
1077 for($i=0;$i<$cnt;++$i) {
1078 $oMbx =& $aMbxs[$i];
1079 $tag = $oMbx->tag;
1080 if ($tag && $aServerResponse[$tag] == 'OK') {
1081 $sResponse = implode('', $aResponse[$tag]);
1082 if (preg_match('/UNSEEN\s+([0-9]+)/i', $sResponse, $regs)) {
1083 $oMbx->unseen = $regs[1];
1084 }
1085 if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
1086 $oMbx->total = $regs[1];
1087 }
1088 }
1089 unset($oMbx->tag);
1090 }
1091 } else if ($unseen_notify == 2) { // INBOX only
1092 $cnt = count($aMbxs);
1093 for($i=0;$i<$cnt;++$i) {
1094 $oMbx =& $aMbxs[$i];
1095 if (strtoupper($oMbx->mailboxname_full) == 'INBOX' ||
1096 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1097 if ($unseen_type == 2 ||
1098 ($oMbx->mailboxname_full == $trash_folder && $move_to_trash)) {
1099 $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full);
1100 $oMbx->unseen = $aStatus['UNSEEN'];
1101 $oMbx->total = $aStatus['MESSAGES'];
1102 } else {
1103 $oMbx->unseen = sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full);
1104 }
1105 $aMbxs[$i] =& $oMbx;
1106 if (!$move_to_trash && $trash_folder) {
1107 break;
1108 } else {
1109 // trash comes after INBOX
1110 if ($oMbx->mailboxname_full == $trash_folder) {
1111 break;
1112 }
1113 }
1114 }
1115 }
1116 }
1117 }
1118
1119 ?>