6 * Copyright (c) 1999-2005 The SquirrelMail Project Team
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
9 * This impliments all functions that manipulate mailboxes
12 * @package squirrelmail
17 require_once(SM_PATH
. 'functions/imap_utf7_local.php');
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
35 var $mailboxname_full = '', $mailboxname_sub= '', $is_noselect = false, $is_noinferiors = false,
36 $is_special = false, $is_root = false, $is_inbox = false, $is_sent = false,
37 $is_trash = false, $is_draft = false, $mbxs = array(),
38 $unseen = false, $total = false;
40 function addMbx($mbx, $delimiter, $start, $specialfirst) {
41 $ary = explode($delimiter, $mbx->mailboxname_full
);
43 for ($i = $start, $c = count($ary)-1; $i < $c; $i++
) {
44 $mbx_childs =& $mbx_parent->mbxs
;
47 foreach ($mbx_childs as $key => $parent) {
48 if ($parent->mailboxname_sub
== $ary[$i]) {
49 $mbx_parent =& $mbx_parent->mbxs
[$key];
56 $no_select_mbx = new mailboxes();
57 if (isset($mbx_parent->mailboxname_full
) && $mbx_parent->mailboxname_full
!= '') {
58 $no_select_mbx->mailboxname_full
= $mbx_parent->mailboxname_full
.$delimiter.$ary[$i];
60 $no_select_mbx->mailboxname_full
= $ary[$i];
62 $no_select_mbx->mailboxname_sub
= $ary[$i];
63 $no_select_mbx->is_noselect
= true;
64 $mbx_parent->mbxs
[] = $no_select_mbx;
68 $mbx_parent->mbxs
[] = $mbx;
69 if ($mbx->is_special
&& $specialfirst) {
70 usort($mbx_parent->mbxs
, 'sortSpecialMbx');
76 * array callback used for sorting in mailboxes class
79 * @return integer see php strnatcasecmp()
82 function sortSpecialMbx($a, $b) {
84 $acmp = '0'. $a->mailboxname_full
;
85 } else if ($a->is_special
) {
86 $acmp = '1'. $a->mailboxname_full
;
88 $acmp = '2' . $a->mailboxname_full
;
91 $bcmp = '0'. $b->mailboxname_full
;
92 }else if ($b->is_special
) {
93 $bcmp = '1' . $b->mailboxname_full
;
95 $bcmp = '2' . $b->mailboxname_full
;
97 return strnatcasecmp($acmp, $bcmp);
105 function compact_mailboxes_response($ary) {
107 * Workaround for mailboxes returned as literal
108 * FIXME : Doesn't work if the mailbox name is multiple lines
109 * (larger then fgets buffer)
111 for ($i = 0, $iCnt=count($ary); $i < $iCnt; $i++
) {
112 if (isset($ary[$i +
1]) && substr($ary[$i], -3) == "}\r\n") {
113 if (ereg("^(\\* [A-Z]+.*)\\{[0-9]+\\}([ \n\r\t]*)$",
115 $ary[$i] = $regs[1] . '"' . addslashes(trim($ary[$i+
1])) . '"' . $regs[2];
116 array_splice($ary, $i+
1, 2);
120 /* remove duplicates and ensure array is contiguous */
121 return array_values(array_unique($ary));
125 * Extract the mailbox name from an untagged LIST (7.2.2) or LSUB (7.2.3) answer
126 * (LIST|LSUB) (<Flags list>) (NIL|"<separator atom>") <mailbox name string>\r\n
127 * mailbox name in quoted string MUST be unquoted and stripslashed (sm API)
129 * Originally stored in functions/strings.php. Since 1.2.6 stored in
130 * functions/imap_mailbox.php
131 * @param string $line imap LIST/LSUB response line
132 * @return string mailbox name
134 function find_mailbox_name($line) {
135 if (preg_match('/^\* (?:LIST|LSUB) \([^\)]*\) (?:NIL|\"[^\"]*\") ([^\r\n]*)[\r\n]*$/i', $line, $regs)) {
136 if (substr($regs[1], 0, 1) == '"')
137 return stripslashes(substr($regs[1], 1, -1));
144 * Detects if mailbox has noselect flag (can't store messages)
145 * @param string $lsub_line mailbox line from untagged LIST or LSUB response
146 * @return bool whether this is a Noselect mailbox.
149 function check_is_noselect ($lsub_line) {
150 return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noselect[^\)]*\)/i", $lsub_line);
154 * Detects if mailbox has noinferiors flag (can't store subfolders)
155 * @param string $lsub_line mailbox line from untagged LIST or LSUB response
156 * @return bool whether this is a Noinferiors mailbox.
159 function check_is_noinferiors ($lsub_line) {
160 return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noinferiors[^\)]*\)/i", $lsub_line);
164 * Detects mailbox's parent folder
166 * If $haystack is a full mailbox name, and $needle is the mailbox
167 * separator character, returns the second last part of the full
168 * mailbox name (i.e. the mailbox's parent mailbox)
170 * Originally stored in functions/strings.php. Since 1.2.6 stored in
171 * functions/imap_mailbox.php
172 * @param string $haystack full mailbox name
173 * @param string $needle delimiter
174 * @return string parent mailbox
176 function readMailboxParent($haystack, $needle) {
180 $parts = explode($needle, $haystack);
181 $elem = array_pop($parts);
182 while ($elem == '' && count($parts)) {
183 $elem = array_pop($parts);
185 $ret = join($needle, $parts);
191 * Check if $subbox is below the specified $parentbox
192 * @param string $subbox potential sub folder
193 * @param string $parentbox potential parent
197 function isBoxBelow( $subbox, $parentbox ) {
200 * Eliminate the obvious mismatch, where the
201 * subfolder path is shorter than that of the potential parent
203 if ( strlen($subbox) < strlen($parentbox) ) {
206 /* check for delimiter */
207 if (substr($parentbox,-1) != $delimiter) {
208 $parentbox .= $delimiter;
211 return (substr($subbox,0,strlen($parentbox)) == $parentbox);
215 * Defines special mailboxes: given a mailbox name, it checks if this is a
216 * "special" one: INBOX, Trash, Sent or Draft.
218 * Since 1.2.5 function includes special_mailbox hook.<br>
219 * Since 1.4.3 hook supports more than one plugin.
220 * @param string $box mailbox name
224 function isSpecialMailbox( $box ) {
225 $ret = ( (strtolower($box) == 'inbox') ||
226 isTrashMailbox($box) ||
isSentMailbox($box) ||
isDraftMailbox($box) );
229 $ret = boolean_hook_function('special_mailbox',$box,1);
235 * Detects if mailbox is a Trash folder or subfolder of Trash
236 * @param string $box mailbox name
237 * @return bool whether this is a Trash folder
240 function isTrashMailbox ($box) {
241 global $trash_folder, $move_to_trash;
242 return $move_to_trash && $trash_folder &&
243 ( $box == $trash_folder ||
isBoxBelow($box, $trash_folder) );
247 * Detects if mailbox is a Sent folder or subfolder of Sent
248 * @param string $box mailbox name
249 * @return bool whether this is a Sent folder
252 function isSentMailbox($box) {
253 global $sent_folder, $move_to_sent;
254 return $move_to_sent && $sent_folder &&
255 ( $box == $sent_folder ||
isBoxBelow($box, $sent_folder) );
259 * Detects if mailbox is a Drafts folder or subfolder of Drafts
260 * @param string $box mailbox name
261 * @return bool whether this is a Draft folder
264 function isDraftMailbox($box) {
265 global $draft_folder, $save_as_draft;
266 return $save_as_draft &&
267 ( $box == $draft_folder ||
isBoxBelow($box, $draft_folder) );
273 * WARNING: Select mailbox before calling this function.
275 * permanently removes all messages that have the \Deleted flag
276 * set from the selected mailbox. See EXPUNGE command chapter in
278 * @param stream $imap_stream imap connection resource
279 * @param string $mailbox mailbox name (unused since 1.1.3).
280 * @param boolean $handle_errors error handling control (displays error_box on error).
281 * @param mixed $id (since 1.3.0) integer message id or array with integer ids
282 * @return integer number of expunged messages
283 * @since 1.0 or older
285 function sqimap_mailbox_expunge ($imap_stream, $mailbox, $handle_errors = true, $id='') {
288 $id = sqimap_message_list_squisher($id);
295 $read = sqimap_run_command($imap_stream, 'EXPUNGE'.$id, $handle_errors,
296 $response, $message, $uid);
299 if (is_array($read)) {
300 foreach ($read as $r) {
301 if (preg_match('/^\*\s[0-9]+\sEXPUNGE/AUi',$r,$regs)) {
310 * Checks whether or not the specified mailbox exists
311 * @param stream $imap_stream imap connection resource
312 * @param string $mailbox mailbox name
314 * @since 1.0 or older
316 function sqimap_mailbox_exists ($imap_stream, $mailbox) {
317 if (!isset($mailbox) ||
empty($mailbox)) {
320 $mbx = sqimap_run_command($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mailbox),
321 true, $response, $message);
322 return isset($mbx[0]);
327 * Before 1.3.0 used more arguments and returned data depended on those argumements.
328 * @param stream $imap_stream imap connection resource
329 * @param string $mailbox mailbox name
330 * @return array results of select command (on success - permanentflags, flags and rights)
331 * @since 1.0 or older
333 function sqimap_mailbox_select ($imap_stream, $mailbox) {
334 if ($mailbox == 'None') {
338 $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
339 true, $response, $message);
341 for ($i = 0, $cnt = count($read); $i < $cnt; $i++
) {
342 if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
343 $result[strtoupper($regs[1])] = $regs[2];
344 } else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i], $regs)) {
345 $result[strtoupper($regs[2])] = $regs[1];
347 if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i], $regs)) {
348 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/","/\]/") ,'', $regs[1])) ;
349 $result['PERMANENTFLAGS'] = explode(' ',strtolower($regs[1]));
350 } else if (preg_match("/FLAGS(.*)/i",$read[$i], $regs)) {
351 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/") ,'', $regs[1])) ;
352 $result['FLAGS'] = explode(' ',strtolower($regs[1]));
356 if (!isset($result['PERMANENTFLAGS'])) {
357 $result['PERMANENTFLAGS'] = $result['FLAGS'];
359 if (preg_match('/^\[(.+)\]/',$message, $regs)) {
360 $result['RIGHTS']=strtoupper($regs[1]);
369 * Mailbox is automatically subscribed.
371 * Set $type to string that does not match 'noselect' (case insensitive),
372 * if you don't want to prepend delimiter to mailbox name. Please note
373 * that 'noinferiors' might be used someday as keyword for folders
374 * that store only messages.
375 * @param stream $imap_steam imap connection resource
376 * @param string $mailbox mailbox name
377 * @param string $type folder type.
378 * @since 1.0 or older
380 function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
382 if (strtolower($type) == 'noselect') {
383 $mailbox .= $delimiter;
386 $read_ary = sqimap_run_command($imap_stream, 'CREATE ' .
387 sqimap_encode_mailbox_name($mailbox),
388 true, $response, $message);
389 sqimap_subscribe ($imap_stream, $mailbox);
393 * Subscribes to an existing folder.
394 * @param stream $imap_stream imap connection resource
395 * @param string $mailbox mailbox name
396 * @param boolean $debug (since 1.5.1)
397 * @since 1.0 or older
399 function sqimap_subscribe ($imap_stream, $mailbox,$debug=true) {
400 $read_ary = sqimap_run_command($imap_stream, 'SUBSCRIBE ' .
401 sqimap_encode_mailbox_name($mailbox),
402 $debug, $response, $message);
406 * Unsubscribes from an existing folder
407 * @param stream $imap_stream imap connection resource
408 * @param string $mailbox mailbox name
409 * @since 1.0 or older
411 function sqimap_unsubscribe ($imap_stream, $mailbox) {
412 $read_ary = sqimap_run_command($imap_stream, 'UNSUBSCRIBE ' .
413 sqimap_encode_mailbox_name($mailbox),
414 false, $response, $message);
418 * Deletes the given folder
419 * Since 1.2.6 and 1.3.0 contains rename_or_delete_folder hook
420 * @param stream $imap_stream imap connection resource
421 * @param string $mailbox mailbox name
422 * @since 1.0 or older
424 function sqimap_mailbox_delete ($imap_stream, $mailbox) {
425 global $data_dir, $username;
426 sqimap_unsubscribe ($imap_stream, $mailbox);
427 $read_ary = sqimap_run_command($imap_stream, 'DELETE ' .
428 sqimap_encode_mailbox_name($mailbox),
429 true, $response, $message);
430 if ($response !== 'OK') {
432 sqimap_subscribe ($imap_stream, $mailbox);
434 do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
435 removePref($data_dir, $username, "thread_$mailbox");
436 removePref($data_dir, $username, "collapse_folder_$mailbox");
441 * Determines if the user is subscribed to the folder or not
442 * @param stream $imap_stream imap connection resource
443 * @param string $mailbox mailbox name
447 function sqimap_mailbox_is_subscribed($imap_stream, $folder) {
448 $boxesall = sqimap_mailbox_list ($imap_stream);
449 foreach ($boxesall as $ref) {
450 if ($ref['unformatted'] == $folder) {
459 * Since 1.2.6 and 1.3.0 contains rename_or_delete_folder hook
460 * @param stream $imap_stream imap connection resource
461 * @param string $old_name mailbox name
462 * @param string $new_name new mailbox name
465 function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
466 if ( $old_name != $new_name ) {
467 global $delimiter, $imap_server_type, $data_dir, $username;
468 if ( substr( $old_name, -1 ) == $delimiter ) {
469 $old_name = substr( $old_name, 0, strlen( $old_name ) - 1 );
470 $new_name = substr( $new_name, 0, strlen( $new_name ) - 1 );
471 $postfix = $delimiter;
476 $boxesall = sqimap_mailbox_list_all($imap_stream);
477 $cmd = 'RENAME ' . sqimap_encode_mailbox_name($old_name) .
478 ' ' . sqimap_encode_mailbox_name($new_name);
479 $data = sqimap_run_command($imap_stream, $cmd, true, $response, $message);
480 sqimap_unsubscribe($imap_stream, $old_name.$postfix);
481 $oldpref_thread = getPref($data_dir, $username, 'thread_'.$old_name.$postfix);
482 $oldpref_collapse = getPref($data_dir, $username, 'collapse_folder_'.$old_name.$postfix);
483 removePref($data_dir, $username, 'thread_'.$old_name.$postfix);
484 removePref($data_dir, $username, 'collapse_folder_'.$old_name.$postfix);
485 sqimap_subscribe($imap_stream, $new_name.$postfix);
486 setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref_thread);
487 setPref($data_dir, $username, 'collapse_folder_'.$new_name.$postfix, $oldpref_collapse);
488 do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
489 $l = strlen( $old_name ) +
1;
492 foreach ($boxesall as $box) {
493 if (substr($box[$p], 0, $l) == $old_name . $delimiter) {
494 $new_sub = $new_name . $delimiter . substr($box[$p], $l);
495 /* With Cyrus IMAPd >= 2.0 rename is recursive, so don't check for errors here */
496 if ($imap_server_type == 'cyrus') {
497 $cmd = 'RENAME "' . $box[$p] . '" "' . $new_sub . '"';
498 $data = sqimap_run_command($imap_stream, $cmd, false,
499 $response, $message);
501 $was_subscribed = sqimap_mailbox_is_subscribed($imap_stream, $box[$p]);
502 if ( $was_subscribed ) {
503 sqimap_unsubscribe($imap_stream, $box[$p]);
505 $oldpref_thread = getPref($data_dir, $username, 'thread_'.$box[$p]);
506 $oldpref_collapse = getPref($data_dir, $username, 'collapse_folder_'.$box[$p]);
507 removePref($data_dir, $username, 'thread_'.$box[$p]);
508 removePref($data_dir, $username, 'collapse_folder_'.$box[$p]);
509 if ( $was_subscribed ) {
510 sqimap_subscribe($imap_stream, $new_sub);
512 setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref_thread);
513 setPref($data_dir, $username, 'collapse_folder_'.$new_sub, $oldpref_collapse);
514 do_hook_function('rename_or_delete_folder',
515 $args = array($box[$p], 'rename', $new_sub));
522 * Formats a mailbox into parts for the $boxesall array
526 * <li>raw - Raw LIST/LSUB response from the IMAP server
527 * <li>formatted - nicely formatted folder name
528 * <li>unformatted - unformatted, but with delimiter at end removed
529 * <li>unformatted-dm - folder name as it appears in raw response
530 * <li>unformatted-disp - unformatted without $folder_prefix
531 * <li>id - TODO: document me
532 * <li>flags - TODO: document me
534 * Before 1.2.0 used third argument for delimiter.
538 * @since 1.0 or older
539 * @todo document id and flags keys in boxes array and function arguments.
541 function sqimap_mailbox_parse ($line, $line_lsub) {
542 global $folder_prefix, $delimiter;
544 /* Process each folder line */
545 for ($g = 0, $cnt = count($line); $g < $cnt; ++
$g) {
546 /* Store the raw IMAP reply */
547 if (isset($line[$g])) {
548 $boxesall[$g]['raw'] = $line[$g];
550 $boxesall[$g]['raw'] = '';
553 /* Count number of delimiters ($delimiter) in folder name */
554 $mailbox = /*trim(*/$line_lsub[$g]/*)*/;
555 $dm_count = substr_count($mailbox, $delimiter);
556 if (substr($mailbox, -1) == $delimiter) {
557 /* If name ends in delimiter, decrement count by one */
561 /* Format folder name, but only if it's a INBOX.* or has a parent. */
562 $boxesallbyname[$mailbox] = $g;
563 $parentfolder = readMailboxParent($mailbox, $delimiter);
564 if ( (strtolower(substr($mailbox, 0, 5)) == "inbox") ||
565 (substr($mailbox, 0, strlen($folder_prefix)) == $folder_prefix) ||
566 (isset($boxesallbyname[$parentfolder]) &&
567 (strlen($parentfolder) > 0) ) ) {
568 $indent = $dm_count - (substr_count($folder_prefix, $delimiter));
570 $boxesall[$g]['formatted'] = str_repeat(' ', $indent);
572 $boxesall[$g]['formatted'] = '';
574 $boxesall[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
576 $boxesall[$g]['formatted'] = imap_utf7_decode_local($mailbox);
579 $boxesall[$g]['unformatted-dm'] = $mailbox;
580 if (substr($mailbox, -1) == $delimiter) {
581 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
583 $boxesall[$g]['unformatted'] = $mailbox;
584 if (substr($mailbox,0,strlen($folder_prefix))==$folder_prefix) {
585 $mailbox = substr($mailbox, strlen($folder_prefix));
587 $boxesall[$g]['unformatted-disp'] = $mailbox;
588 $boxesall[$g]['id'] = $g;
590 $boxesall[$g]['flags'] = array();
591 if (isset($line[$g])) {
592 ereg("\(([^)]*)\)",$line[$g],$regs);
593 // FIXME Flags do contain the \ character. \NoSelect \NoInferiors
594 // and $MDNSent <= last one doesn't have the \
595 // It's better to follow RFC3501 instead of using our own naming.
596 $flags = trim(strtolower(str_replace('\\', '',$regs[1])));
598 $boxesall[$g]['flags'] = explode(' ', $flags);
606 * Returns list of options (to be echoed into select statement
607 * based on available mailboxes and separators
608 * Caller should surround options with <select ...> </select> and
610 * @param stream $imap_stream imap connection resource to query for mailboxes
611 * @param array $show_selected array containing list of mailboxes to pre-select (0 if none)
612 * @param array $folder_skip array of folders to keep out of option list (compared in lower)
613 * @param $boxes list of already fetched boxes (for places like folder panel, where
614 * you know these options will be shown 3 times in a row.. (most often unset).
615 * @param string $flag (since 1.4.1) flag to check for in mailbox flags, used to filter out mailboxes.
616 * 'noselect' by default to remove unselectable mailboxes.
617 * 'noinferiors' used to filter out folders that can not contain subfolders.
618 * NULL to avoid flag check entirely.
619 * NOTE: noselect and noiferiors are used internally. The IMAP representation is
620 * \NoSelect and \NoInferiors
621 * @param boolean $use_long_format (since 1.4.1) override folder display preference and always show full folder name.
622 * @return string html formated mailbox selection options
625 function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
626 $flag = 'noselect', $use_long_format = false ) {
627 global $username, $data_dir;
629 if ( $use_long_format ) {
630 $shorten_box_names = 0;
632 $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF
);
636 $boxes = sqimap_mailbox_list($imap_stream);
639 foreach ($boxes as $boxes_part) {
640 if ($flag == NULL ||
(is_array($boxes_part['flags'])
641 && !in_array($flag, $boxes_part['flags']))) {
642 $box = $boxes_part['unformatted'];
644 if ($folder_skip != 0 && in_array($box, $folder_skip) ) {
647 $lowerbox = strtolower($box);
648 // mailboxes are casesensitive => inbox.sent != inbox.Sent
649 // nevermind, to many dependencies this should be fixed!
651 if (strtolower($box) == 'inbox') { // inbox is special and not casesensitive
654 switch ($shorten_box_names)
656 case 2: /* delimited, style = 2 */
657 $box2 = str_replace('&nbsp;&nbsp;', '. ', htmlspecialchars($boxes_part['formatted']));
659 case 1: /* indent, style = 1 */
660 $box2 = str_replace('&nbsp;&nbsp;', ' ', htmlspecialchars($boxes_part['formatted']));
662 default: /* default, long names, style = 0 */
663 $box2 = str_replace(' ', ' ', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
667 if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
668 $mbox_options .= '<option value="' . htmlspecialchars($box) .'" selected="selected">'.$box2.'</option>' . "\n";
670 $mbox_options .= '<option value="' . htmlspecialchars($box) .'">'.$box2.'</option>' . "\n";
674 return $mbox_options;
678 * Returns sorted mailbox lists in several different ways.
679 * See comment on sqimap_mailbox_parse() for info about the returned array.
680 * @param resource $imap_stream imap connection resource
681 * @param boolean $force force update of mailbox listing. available since 1.4.2 and 1.5.0
682 * @return array list of mailboxes
683 * @since 1.0 or older
685 function sqimap_mailbox_list($imap_stream, $force=false) {
686 if (!sqgetGlobalVar('boxesnew',$boxesnew,SQ_SESSION
) ||
$force) {
687 global $data_dir, $username, $list_special_folders_first,
688 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
689 $move_to_trash, $move_to_sent, $save_as_draft,
690 $delimiter, $noselect_fix_enable, $imap_server_type,
691 $show_only_subscribed_folders;
692 $inbox_subscribed = false;
693 $listsubscribed = sqimap_capability($imap_stream,'LIST-SUBSCRIBED');
695 require_once(SM_PATH
. 'include/load_prefs.php');
697 if (!$show_only_subscribed_folders) {
699 } elseif ($listsubscribed) {
700 $lsub = 'LIST (SUBSCRIBED)';
705 if ($noselect_fix_enable) {
706 $lsub_args = "$lsub \"$folder_prefix\" \"*%\"";
708 $lsub_args = "$lsub \"$folder_prefix\" \"*\"";
711 $lsub_ary = sqimap_run_command ($imap_stream, $lsub_args,
712 true, $response, $message);
713 $lsub_ary = compact_mailboxes_response($lsub_ary);
715 $sorted_lsub_ary = array();
716 for ($i = 0, $cnt = count($lsub_ary);$i < $cnt; $i++
) {
718 $temp_mailbox_name = find_mailbox_name($lsub_ary[$i]);
719 $sorted_lsub_ary[] = $temp_mailbox_name;
720 if (!$inbox_subscribed && strtoupper($temp_mailbox_name) == 'INBOX') {
721 $inbox_subscribed = true;
725 /* natural sort mailboxes */
726 if (isset($sorted_lsub_ary)) {
727 usort($sorted_lsub_ary, 'strnatcasecmp');
730 * The LSUB response doesn't provide us information about \Noselect
731 * mail boxes. The LIST response does, that's why we need to do a LIST
732 * call to retrieve the flags for the mailbox
733 * Note: according RFC2060 an imap server may provide \NoSelect flags in the LSUB response.
734 * in other words, we cannot rely on it.
736 $sorted_list_ary = array();
737 // if (!$listsubscribed) {
738 for ($i=0; $i < count($sorted_lsub_ary); $i++
) {
739 if (substr($sorted_lsub_ary[$i], -1) == $delimiter) {
740 $mbx = substr($sorted_lsub_ary[$i], 0, strlen($sorted_lsub_ary[$i])-1);
743 $mbx = $sorted_lsub_ary[$i];
746 $read = sqimap_run_command ($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mbx),
747 true, $response, $message);
749 $read = compact_mailboxes_response($read);
751 if (isset($read[0])) {
752 $sorted_list_ary[$i] = $read[0];
754 $sorted_list_ary[$i] = '';
759 * Just in case they're not subscribed to their inbox,
760 * we'll get it for them anyway
762 if (!$inbox_subscribed) {
763 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" "INBOX"',
764 true, $response, $message);
765 $sorted_list_ary[] = implode('',compact_mailboxes_response($inbox_ary));
766 $sorted_lsub_ary[] = find_mailbox_name($inbox_ary[0]);
769 $boxesall = sqimap_mailbox_parse ($sorted_list_ary, $sorted_lsub_ary);
771 /* Now, lets sort for special folders */
772 $boxesnew = $used = array();
775 $cnt = count($boxesall);
776 $used = array_pad($used,$cnt,false);
777 for($k = 0; $k < $cnt; ++
$k) {
778 if (strtolower($boxesall[$k]['unformatted']) == 'inbox') {
779 $boxesnew[] = $boxesall[$k];
784 /* List special folders and their subfolders, if requested. */
785 if ($list_special_folders_first) {
786 for($k = 0; $k < $cnt; ++
$k) {
787 if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
788 $boxesnew[] = $boxesall[$k];
794 /* Find INBOX's children */
795 for($k = 0; $k < $cnt; ++
$k) {
796 if (!$used[$k] && isBoxBelow(strtolower($boxesall[$k]['unformatted']), 'inbox') &&
797 strtolower($boxesall[$k]['unformatted']) != 'inbox') {
798 $boxesnew[] = $boxesall[$k];
803 /* Rest of the folders */
804 for($k = 0; $k < $cnt; $k++
) {
806 $boxesnew[] = $boxesall[$k];
809 sqsession_register($boxesnew,'boxesnew');
815 * Returns a list of all folders, subscribed or not
816 * @param stream $imap_stream imap connection resource
817 * @return array see sqimap_mailbox_parse()
818 * @since 1.0 or older
820 function sqimap_mailbox_list_all($imap_stream) {
821 global $list_special_folders_first, $folder_prefix, $delimiter;
823 $read_ary = sqimap_run_command($imap_stream,"LIST \"$folder_prefix\" *",true,$response, $message,false);
824 $read_ary = compact_mailboxes_response($read_ary);
827 $fld_pre_length = strlen($folder_prefix);
828 for ($i = 0, $cnt = count($read_ary); $i < $cnt; $i++
) {
829 /* Store the raw IMAP reply */
830 $boxes[$g]['raw'] = $read_ary[$i];
832 /* Count number of delimiters ($delimiter) in folder name */
833 $mailbox = find_mailbox_name($read_ary[$i]);
834 $dm_count = substr_count($mailbox, $delimiter);
835 if (substr($mailbox, -1) == $delimiter) {
836 /* If name ends in delimiter - decrement count by one */
840 /* Format folder name, but only if it's a INBOX.* or has a parent. */
841 $boxesallbyname[$mailbox] = $g;
842 $parentfolder = readMailboxParent($mailbox, $delimiter);
843 if((eregi('^inbox'.quotemeta($delimiter), $mailbox)) ||
844 (ereg('^'.$folder_prefix, $mailbox)) ||
845 ( isset($boxesallbyname[$parentfolder]) && (strlen($parentfolder) > 0) ) ) {
847 $boxes[$g]['formatted'] = str_repeat(' ', $dm_count);
849 $boxes[$g]['formatted'] = '';
851 $boxes[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
853 $boxes[$g]['formatted'] = imap_utf7_decode_local($mailbox);
856 $boxes[$g]['unformatted-dm'] = $mailbox;
857 if (substr($mailbox, -1) == $delimiter) {
858 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
860 $boxes[$g]['unformatted'] = $mailbox;
861 $boxes[$g]['unformatted-disp'] = substr($mailbox,$fld_pre_length);
863 $boxes[$g]['id'] = $g;
865 /* Now lets get the flags for this mailbox */
866 $read_mlbx = $read_ary[$i];
867 $flags = substr($read_mlbx, strpos($read_mlbx, '(')+
1);
868 $flags = substr($flags, 0, strpos($flags, ')'));
869 $flags = str_replace('\\', '', $flags);
870 $flags = trim(strtolower($flags));
872 $boxes[$g]['flags'] = explode(' ', $flags);
874 $boxes[$g]['flags'] = array();
878 if(is_array($boxes)) {
886 * @param stream $imap_stream imap connection resource
887 * @return object see mailboxes class.
890 function sqimap_mailbox_tree($imap_stream) {
891 global $default_folder_prefix;
893 global $data_dir, $username, $list_special_folders_first,
894 $folder_prefix, $delimiter, $trash_folder, $move_to_trash,
895 $imap_server_type, $show_only_subscribed_folders;
898 $noinferiors = false;
900 require_once(SM_PATH
. 'include/load_prefs.php');
902 if ($show_only_subscribed_folders) {
909 $lsub_ary = sqimap_run_command ($imap_stream, "$lsub_cmd \"$folder_prefix\" \"*\"",
910 true, $response, $message);
911 $lsub_ary = compact_mailboxes_response($lsub_ary);
913 /* Check to see if we have an INBOX */
916 for ($i = 0, $cnt = count($lsub_ary); $i < $cnt; $i++
) {
917 if (preg_match("/^\*\s+$lsub_cmd.*\s\"?INBOX\"?[^(\/\.)].*$/i",$lsub_ary[$i])) {
918 $lsub_ary[$i] = strtoupper($lsub_ary[$i]);
919 // in case of an unsubscribed inbox an imap server can
920 // return the inbox in the lsub results with a \NoSelect
922 if (!preg_match("/\*\s+$lsub_cmd\s+\(.*\\\\NoSelect.*\).*/i",$lsub_ary[$i])) {
925 // remove the result and request it again with a list
926 // response at a later stage.
927 unset($lsub_ary[$i]);
928 // re-index the array otherwise the addition of the LIST
929 // response will fail in PHP 4.1.2 and probably other older versions
930 $lsub_ary = array_values($lsub_ary);
936 if ($has_inbox == false) {
937 // do a list request for inbox because we should always show
938 // inbox even if the user isn't subscribed to it.
939 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" "INBOX"',
940 true, $response, $message);
941 $inbox_ary = compact_mailboxes_response($inbox_ary);
942 if (count($inbox_ary)) {
943 $lsub_ary[] = $inbox_ary[0];
948 * Section about removing the last element was removed
949 * We don't return "* OK" anymore from sqimap_read_data
952 $sorted_lsub_ary = array();
953 $cnt = count($lsub_ary);
954 for ($i = 0; $i < $cnt; $i++
) {
955 $mbx = find_mailbox_name($lsub_ary[$i]);
957 // only do the noselect test if !uw, is checked later. FIX ME see conf.pl setting
958 if ($imap_server_type != "uw") {
959 $noselect = check_is_noselect($lsub_ary[$i]);
960 $noinferiors = check_is_noinferiors($lsub_ary[$i]);
962 if (substr($mbx, -1) == $delimiter) {
963 $mbx = substr($mbx, 0, strlen($mbx) - 1);
965 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
967 // FIX ME this requires a config setting inside conf.pl instead of checking on server type
968 if ($imap_server_type == "uw") {
971 // prepare an array with queries
972 foreach ($sorted_lsub_ary as $aMbx) {
973 $mbx = stripslashes($aMbx['mbx']);
974 sqimap_prepare_pipelined_query('LIST "" ' . sqimap_encode_mailbox_name($mbx), $tag, $aQuery, false);
977 $sorted_lsub_ary = array();
978 // execute all the queries at once
979 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
980 foreach($aTag as $tag => $mbx) {
981 if ($aServerResponse[$tag] == 'OK') {
982 $sResponse = implode('', $aResponse[$tag]);
983 $noselect = check_is_noselect($sResponse);
984 $noinferiors = check_is_noinferiors($sResponse);
985 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
988 $cnt = count($sorted_lsub_ary);
990 $sorted_lsub_ary = array_values($sorted_lsub_ary);
991 usort($sorted_lsub_ary, 'mbxSort');
992 $boxestree = sqimap_fill_mailbox_tree($sorted_lsub_ary,false,$imap_stream);
998 * Callback function used for sorting mailboxes in sqimap_mailbox_tree
1001 * @return integer see php strnatcasecmp()
1004 function mbxSort($a, $b) {
1005 return strnatcasecmp($a['mbx'], $b['mbx']);
1009 * @param array $mbx_ary
1011 * @param stream $imap_stream (since 1.5.0) imap connection resource
1012 * @return object see mailboxes class
1015 function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
1016 global $data_dir, $username, $list_special_folders_first,
1017 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
1018 $move_to_trash, $move_to_sent, $save_as_draft,
1019 $delimiter, $imap_server_type;
1021 // $special_folders = array ('INBOX', $sent_folder, $draft_folder, $trash_folder);
1023 /* create virtual root node */
1024 $mailboxes= new mailboxes();
1025 $mailboxes->is_root
= true;
1030 if (isset($folder_prefix) && ($folder_prefix != '')) {
1031 $start = substr_count($folder_prefix,$delimiter);
1032 if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
1033 $mailboxes->mailboxname_full
= substr($folder_prefix,0, (strlen($folder_prefix)-1));
1035 $mailboxes->mailboxname_full
= $folder_prefix;
1038 $mailboxes->mailboxname_sub
= $mailboxes->mailboxname_full
;
1043 $cnt = count($mbx_ary);
1044 for ($i=0; $i < $cnt; $i++
) {
1045 if ($mbx_ary[$i]['mbx'] !='' ) {
1046 $mbx = new mailboxes();
1047 $mailbox = $mbx_ary[$i]['mbx'];
1050 sent subfolders messes up using existing code as subfolders
1051 were marked, but the parents were ordered somewhere else in
1052 the list, despite having "special folders at top" option set.
1053 Need a better method than this.
1056 if ($mailbox == 'INBOX') {
1057 $mbx->is_special = true;
1058 } elseif (stristr($trash_folder , $mailbox)) {
1059 $mbx->is_special = true;
1060 } elseif (stristr($sent_folder , $mailbox)) {
1061 $mbx->is_special = true;
1062 } elseif (stristr($draft_folder , $mailbox)) {
1063 $mbx->is_special = true;
1068 $mbx->is_inbox = true;
1069 $mbx->is_special = true;
1070 $mbx_ary[$i]['noselect'] = false;
1073 $mbx->is_trash = true;
1074 $mbx->is_special = true;
1077 $mbx->is_sent = true;
1078 $mbx->is_special = true;
1081 $mbx->is_draft = true;
1082 $mbx->is_special = true;
1086 $mbx->is_special |
= ($mbx->is_inbox
= (strtoupper($mailbox) == 'INBOX'));
1087 $mbx->is_special |
= ($mbx->is_trash
= isTrashMailbox($mailbox));
1088 $mbx->is_special |
= ($mbx->is_sent
= isSentMailbox($mailbox));
1089 $mbx->is_special |
= ($mbx->is_draft
= isDraftMailbox($mailbox));
1090 if (!$mbx->is_special
)
1091 $mbx->is_special
= boolean_hook_function('special_mailbox', $mailbox, 1);
1093 if (isset($mbx_ary[$i]['unseen'])) {
1094 $mbx->unseen
= $mbx_ary[$i]['unseen'];
1096 if (isset($mbx_ary[$i]['nummessages'])) {
1097 $mbx->total
= $mbx_ary[$i]['nummessages'];
1100 $mbx->is_noselect
= $mbx_ary[$i]['noselect'];
1101 $mbx->is_noinferiors
= $mbx_ary[$i]['noinferiors'];
1103 $r_del_pos = strrpos($mbx_ary[$i]['mbx'], $delimiter);
1105 $mbx->mailboxname_sub
= substr($mbx_ary[$i]['mbx'],$r_del_pos+
1);
1106 } else { /* mailbox is root folder */
1107 $mbx->mailboxname_sub
= $mbx_ary[$i]['mbx'];
1109 $mbx->mailboxname_full
= $mbx_ary[$i]['mbx'];
1111 $mailboxes->addMbx($mbx, $delimiter, $start, $list_special_folders_first);
1114 sqimap_utf7_decode_mbx_tree($mailboxes);
1115 sqimap_get_status_mbx_tree($imap_stream,$mailboxes);
1120 * @param object $mbx_tree
1123 function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
1124 if (strtoupper($mbx_tree->mailboxname_full
) == 'INBOX')
1125 $mbx_tree->mailboxname_sub
= _("INBOX");
1127 $mbx_tree->mailboxname_sub
= imap_utf7_decode_local($mbx_tree->mailboxname_sub
);
1128 if ($mbx_tree->mbxs
) {
1129 $iCnt = count($mbx_tree->mbxs
);
1130 for ($i=0;$i<$iCnt;++
$i) {
1131 $mbxs_tree->mbxs
[$i] = sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs
[$i]);
1137 * @param object $mbx_tree
1138 * @param array $aMbxs
1141 function sqimap_tree_to_ref_array(&$mbx_tree,&$aMbxs) {
1143 $aMbxs[] =& $mbx_tree;
1144 if ($mbx_tree->mbxs
) {
1145 $iCnt = count($mbx_tree->mbxs
);
1146 for ($i=0;$i<$iCnt;++
$i) {
1147 sqimap_tree_to_ref_array($mbx_tree->mbxs
[$i],$aMbxs);
1153 * @param stream $imap_stream imap connection resource
1154 * @param object $mbx_tree
1155 * @since since 1.5.0
1157 function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
1158 global $unseen_notify, $unseen_type, $trash_folder,$move_to_trash;
1159 $aMbxs = $aQuery = array();
1160 sqimap_tree_to_ref_array($mbx_tree,$aMbxs);
1161 // remove the root node
1162 array_shift($aMbxs);
1164 if($unseen_notify == 3) {
1165 $cnt = count($aMbxs);
1166 for($i=0;$i<$cnt;++
$i) {
1167 $oMbx =& $aMbxs[$i];
1168 if (!$oMbx->is_noselect
) {
1169 $mbx = $oMbx->mailboxname_full
;
1170 if ($unseen_type == 2 ||
1171 ($move_to_trash && $oMbx->mailboxname_full
== $trash_folder)) {
1172 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
1174 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
1176 sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
1178 $oMbx->unseen
= $oMbx->total
= false;
1182 $aMbxs[$i] =& $oMbx;
1184 // execute all the queries at once
1185 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
1186 $cnt = count($aMbxs);
1187 for($i=0;$i<$cnt;++
$i) {
1188 $oMbx =& $aMbxs[$i];
1190 if ($tag && $aServerResponse[$tag] == 'OK') {
1191 $sResponse = implode('', $aResponse[$tag]);
1192 if (preg_match('/UNSEEN\s+([0-9]+)/i', $sResponse, $regs)) {
1193 $oMbx->unseen
= $regs[1];
1195 if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
1196 $oMbx->total
= $regs[1];
1201 } else if ($unseen_notify == 2) { // INBOX only
1202 $cnt = count($aMbxs);
1203 for($i=0;$i<$cnt;++
$i) {
1204 $oMbx =& $aMbxs[$i];
1205 if (strtoupper($oMbx->mailboxname_full
) == 'INBOX' ||
1206 ($move_to_trash && $oMbx->mailboxname_full
== $trash_folder)) {
1207 if ($unseen_type == 2 ||
1208 ($oMbx->mailboxname_full
== $trash_folder && $move_to_trash)) {
1209 $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full
);
1210 $oMbx->unseen
= $aStatus['UNSEEN'];
1211 $oMbx->total
= $aStatus['MESSAGES'];
1213 $oMbx->unseen
= sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full
);
1215 $aMbxs[$i] =& $oMbx;
1216 if (!$move_to_trash && $trash_folder) {
1219 // trash comes after INBOX
1220 if ($oMbx->mailboxname_full
== $trash_folder) {