Happy New Year
[squirrelmail.git] / functions / imap_mailbox.php
index 5f81aebe0cc2e875fa40500a359237d69906dbb7..9b2f2b84a54d6ce7da38cbb7cad3fc5d195edf5e 100755 (executable)
@@ -5,20 +5,16 @@
  *
  * This implements all functions that manipulate mailboxes
  *
- * @copyright © 1999-2006 The SquirrelMail Project Team
+ * @copyright 1999-2020 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  * @subpackage imap
  */
 
-/** @ignore */
-if (! defined('SM_PATH')) define('SM_PATH','../');
-
 /** UTF7 support */
 require_once(SM_PATH . 'functions/imap_utf7_local.php');
 
-global $boxesnew;
 
 /**
  * Mailboxes class
@@ -39,7 +35,7 @@ class mailboxes {
     var $mailboxname_full = '', $mailboxname_sub= '', $is_noselect = false, $is_noinferiors = false,
         $is_special = false, $is_root = false, $is_inbox = false, $is_sent = false,
         $is_trash = false, $is_draft = false,  $mbxs = array(),
-        $unseen = false, $total = false;
+        $unseen = false, $total = false, $recent = false;
 
     function addMbx($mbx, $delimiter, $start, $specialfirst) {
         $ary = explode($delimiter, $mbx->mailboxname_full);
@@ -114,8 +110,7 @@ function compact_mailboxes_response($ary) {
      */
     for ($i = 0, $iCnt=count($ary); $i < $iCnt; $i++) {
         if (isset($ary[$i + 1]) && substr($ary[$i], -3) == "}\r\n") {
-            if (ereg("^(\\* [A-Z]+.*)\\{[0-9]+\\}([ \n\r\t]*)$",
-                 $ary[$i], $regs)) {
+            if (preg_match('/^(\* [A-Z]+.*)\{[0-9]+\}([ \n\r\t]*)$/', $ary[$i], $regs)) {
                 $ary[$i] = $regs[1] . '"' . addslashes(trim($ary[$i+1])) . '"' . $regs[2];
                 array_splice($ary, $i+1, 2);
             }
@@ -221,56 +216,125 @@ function isBoxBelow( $subbox, $parentbox ) {
  * Defines special mailboxes: given a mailbox name, it checks if this is a
  * "special" one: INBOX, Trash, Sent or Draft.
  *
- * Since 1.2.5 function includes special_mailbox hook.<br>
+ * Since 1.2.5 function includes special_mailbox hook.
+ *
  * Since 1.4.3 hook supports more than one plugin.
+ *
+//FIXME: make $subfolders_of_inbox_are_special a configuration setting in conf.pl and config.php
+ * Since 1.4.22/1.5.2, the administrator can add
+ * $subfolders_of_inbox_are_special = TRUE;
+ * to config/config_local.php and all subfolders
+ * of the INBOX will be treated as special.
+ *
  * @param string $box mailbox name
+ * @param boolean $include_subs (since 1.5.2) if true, subfolders of system 
+ *  folders are special. if false, subfolders are not special mailboxes 
+ *  unless they are tagged as special in 'special_mailbox' hook.
  * @return boolean
  * @since 1.2.3
  */
-function isSpecialMailbox( $box ) {
-    $ret = ( (strtolower($box) == 'inbox') ||
-             isTrashMailbox($box) || isSentMailbox($box) || isDraftMailbox($box) );
+function isSpecialMailbox($box,$include_subs=true) {
+    global $subfolders_of_inbox_are_special;
+    $ret = ( ($subfolders_of_inbox_are_special && isInboxMailbox($box,$include_subs)) ||
+             (!$subfolders_of_inbox_are_special && strtolower($box) == 'inbox') ||
+             isTrashMailbox($box,$include_subs) || 
+             isSentMailbox($box,$include_subs) || 
+             isDraftMailbox($box,$include_subs) );
 
     if ( !$ret ) {
-        $ret = boolean_hook_function('special_mailbox',$box,1);
+        $ret = boolean_hook_function('special_mailbox', $box, 1);
     }
     return $ret;
 }
 
+/**
+ * Detects if mailbox is the Inbox folder or subfolder of the Inbox
+ *
+ * @param string $box The mailbox name to test
+ * @param boolean $include_subs If true, subfolders of system folders
+ *                              are special.  If false, subfolders are
+ *                              not special mailboxes.
+ *
+ * @return boolean Whether this is the Inbox or a child thereof.
+ *
+ * @since 1.4.22
+ */
+function isInboxMailbox($box, $include_subs=TRUE) {
+   return ((strtolower($box) == 'inbox')
+        || ($include_subs && isBoxBelow(strtolower($box), 'inbox')));
+}
+
 /**
  * Detects if mailbox is a Trash folder or subfolder of Trash
  * @param string $box mailbox name
+ * @param boolean $include_subs (since 1.5.2) if true, subfolders of system 
+ *  folders are special. if false, subfolders are not special mailboxes.
  * @return bool whether this is a Trash folder
  * @since 1.4.0
  */
-function isTrashMailbox ($box) {
+function isTrashMailbox ($box,$include_subs=true) {
     global $trash_folder, $move_to_trash;
     return $move_to_trash && $trash_folder &&
-           ( $box == $trash_folder || isBoxBelow($box, $trash_folder) );
+           ( $box == $trash_folder || 
+             ($include_subs && isBoxBelow($box, $trash_folder)) );
 }
 
 /**
  * Detects if mailbox is a Sent folder or subfolder of Sent
  * @param string $box mailbox name
+ * @param boolean $include_subs (since 1.5.2) if true, subfolders of system 
+ *  folders are special. if false, subfolders are not special mailboxes.
  * @return bool whether this is a Sent folder
  * @since 1.4.0
  */
-function isSentMailbox($box) {
+function isSentMailbox($box,$include_subs=true) {
    global $sent_folder, $move_to_sent;
    return $move_to_sent && $sent_folder &&
-          ( $box == $sent_folder || isBoxBelow($box, $sent_folder) );
+          ( $box == $sent_folder || 
+            ($include_subs && isBoxBelow($box, $sent_folder)) );
 }
 
 /**
  * Detects if mailbox is a Drafts folder or subfolder of Drafts
  * @param string $box mailbox name
+ * @param boolean $include_subs (since 1.5.2) if true, subfolders of system 
+ *  folders are special. if false, subfolders are not special mailboxes.
  * @return bool whether this is a Draft folder
  * @since 1.4.0
  */
-function isDraftMailbox($box) {
+function isDraftMailbox($box,$include_subs=true) {
    global $draft_folder, $save_as_draft;
    return $save_as_draft &&
-          ( $box == $draft_folder || isBoxBelow($box, $draft_folder) );
+          ( $box == $draft_folder || 
+            ($include_subs && isBoxBelow($box, $draft_folder)) );
+}
+
+/**
+ * Is the given folder "sent-like" in nature?
+ *
+ * The most obvious use of this is to know what folders you usually
+ * want to show the To field instead of the From field on the mailbox list
+ *
+ * This function returns TRUE if the given folder is the sent
+ * folder (or any of its subfolders) or if it is the draft
+ * folder (or any of its subfolders)
+ *
+ * @param string $mailbox
+ *
+ * @return boolean See explanation above
+ *
+ */
+function handleAsSent($mailbox) {
+    global $handleAsSent_result;
+
+    /* First check if this is the sent or draft folder. */
+    $handleAsSent_result = isSentMailbox($mailbox) || isDraftMailbox($mailbox);
+
+    /* Then check the result of the handleAsSent hook. */
+    do_hook('check_handleAsSent_result', $mailbox);
+
+    /* And return the result. */
+    return $handleAsSent_result;
 }
 
 /**
@@ -343,21 +407,47 @@ function sqimap_mailbox_exists ($imap_stream, $mailbox, $mailboxlist=null) {
 
 /**
  * Selects a mailbox
- * Before 1.3.0 used more arguments and returned data depended on those argumements.
+ * Before 1.3.0 used more arguments and returned data depended on those arguments.
  * @param stream $imap_stream imap connection resource
  * @param string $mailbox mailbox name
+ * @param boolean $handle_errors When TRUE, IMAP errors
+ *                               are handled herein, causing
+ *                               an error to be displayed on
+ *                               screen and execution to stop
+ *                               and when FALSE, error status
+ *                               is returned to the caller
+ *                               (OPTIONAL; default is TRUE)
  * @return array results of select command (on success - permanentflags, flags and rights)
+ *               (on failure (and when $handle_errors is false), empty array)
  * @since 1.0 or older
  */
-function sqimap_mailbox_select ($imap_stream, $mailbox) {
-    // FIX ME: WHAAAA DO NOT USE "None" for something that does not exist. Use false or NULL instead
-    if ($mailbox == 'None') {
+function sqimap_mailbox_select ($imap_stream, $mailbox, $handle_errors=true) {
+    if (empty($mailbox)) {
         return;
     }
+
     // cleanup $mailbox in order to prevent IMAP injection attacks
     $mailbox = str_replace(array("\r","\n"), array("",""),$mailbox);
+
+    /**
+     * Default UW IMAP server configuration allows to access other files
+     * on server. $imap_server_type is not checked because interface can
+     * be used with 'other' or any other server type setting. $mailbox
+     * variable can be modified in any script that uses variable from GET
+     * or POST. This code blocks all standard SquirrelMail IMAP API requests
+     * that use mailbox with full path (/etc/passwd) or with ../ characters
+     * in path (../../etc/passwd)
+     */
+    if (strstr($mailbox, '../') || substr($mailbox, 0, 1) == '/') {
+        global $oTemplate;
+        error_box(sprintf(_("Invalid mailbox name: %s"),sm_encode_html_special_chars($mailbox)));
+        sqimap_logout($imap_stream);
+        $oTemplate->display('footer.tpl');
+        die();
+    }
+
     $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
-                               true, $response, $message);
+                               $handle_errors, $response, $message);
     $result = array();
     for ($i = 0, $cnt = count($read); $i < $cnt; $i++) {
         if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
@@ -374,7 +464,7 @@ function sqimap_mailbox_select ($imap_stream, $mailbox) {
             }
         }
     }
-    if (!isset($result['PERMANENTFLAGS'])) {
+    if (!empty($result) && !isset($result['PERMANENTFLAGS'])) {
         $result['PERMANENTFLAGS'] = $result['FLAGS'];
     }
     if (preg_match('/^\[(.+)\]/',$message, $regs)) {
@@ -401,11 +491,13 @@ function sqimap_mailbox_select ($imap_stream, $mailbox) {
 function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
     global $delimiter;
     if (strtolower($type) == 'noselect') {
-        $mailbox .= $delimiter;
+        $create_mailbox = $mailbox . $delimiter;
+    } else {
+        $create_mailbox = $mailbox;
     }
 
     $read_ary = sqimap_run_command($imap_stream, 'CREATE ' .
-                                   sqimap_encode_mailbox_name($mailbox),
+                                   sqimap_encode_mailbox_name($create_mailbox),
                                    true, $response, $message);
     sqimap_subscribe ($imap_stream, $mailbox);
 }
@@ -455,7 +547,8 @@ function sqimap_mailbox_delete ($imap_stream, $mailbox) {
             // subscribe again
             sqimap_subscribe ($imap_stream, $mailbox);
         } else {
-            do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
+            $temp = array(&$mailbox, 'delete', '');
+            do_hook('rename_or_delete_folder', $temp);
             removePref($data_dir, $username, "thread_$mailbox");
             removePref($data_dir, $username, "collapse_folder_$mailbox");
         }
@@ -510,7 +603,8 @@ function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
         sqimap_subscribe($imap_stream, $new_name.$postfix);
         setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref_thread);
         setPref($data_dir, $username, 'collapse_folder_'.$new_name.$postfix, $oldpref_collapse);
-        do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
+        $temp = array(&$old_name, 'rename', &$new_name);
+        do_hook('rename_or_delete_folder', $temp);
         $l = strlen( $old_name ) + 1;
         $p = 'unformatted';
 
@@ -536,8 +630,8 @@ function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
                 }
                 setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref_thread);
                 setPref($data_dir, $username, 'collapse_folder_'.$new_sub, $oldpref_collapse);
-                do_hook_function('rename_or_delete_folder',
-                                 $args = array($box[$p], 'rename', $new_sub));
+                $temp = array(&$box[$p], 'rename', &$new_sub);
+                do_hook('rename_or_delete_folder', $temp);
             }
         }
     }
@@ -570,7 +664,10 @@ function sqimap_mailbox_parse ($line) {
     global $folder_prefix, $delimiter;
 
     /* Process each folder line */
-    for ($g = 0, $cnt = count($line); $g < $cnt; ++$g) {
+    ksort($line); // get physical ordering same as alphabetical sort we did before now (might be a better place for this)
+    foreach ($line as $g => $l)
+    // was this but array not guaranteed to be contiguous: for ($g = 0, $cnt = count($line); $g < $cnt; ++$g)
+    {
         /* Store the raw IMAP reply */
         if (isset($line[$g])) {
             $boxesall[$g]['raw'] = $line[$g];
@@ -616,8 +713,7 @@ function sqimap_mailbox_parse ($line) {
         $boxesall[$g]['id'] = $g;
 
         $boxesall[$g]['flags'] = array();
-        if (isset($line[$g])) {
-            ereg("\(([^)]*)\)",$line[$g],$regs);
+        if (isset($line[$g]) && preg_match('/\(([^)]*)\)/',$line[$g],$regs) ) {
             /**
              * Since 1.5.1 flags are stored with RFC3501 naming
              * and also the old way for backwards compatibility
@@ -639,39 +735,31 @@ function sqimap_mailbox_parse ($line) {
 }
 
 /**
- * Returns list of options (to be echoed into select statement
- * based on available mailboxes and separators
- * Caller should surround options with <select ...> </select> and
- * any formatting.
- * @param stream $imap_stream imap connection resource to query for mailboxes
- * @param array $show_selected array containing list of mailboxes to pre-select (0 if none)
- * @param array $folder_skip array of folders to keep out of option list (compared in lower)
- * @param $boxes list of already fetched boxes (for places like folder panel, where
- *            you know these options will be shown 3 times in a row.. (most often unset).
- * @param string $flag (since 1.4.1) flag to check for in mailbox flags, used to filter out mailboxes.
- *           'noselect' by default to remove unselectable mailboxes.
- *           'noinferiors' used to filter out folders that can not contain subfolders.
- *           NULL to avoid flag check entirely.
- *           NOTE: noselect and noiferiors are used internally. The IMAP representation is
- *                 \NoSelect and \NoInferiors
- * @param boolean $use_long_format (since 1.4.1) override folder display preference and always show full folder name.
- * @return string html formated mailbox selection options
- * @since 1.3.2
+ * Returns an array of mailboxes available.  Separated from sqimap_mailbox_option_list()
+ * below for template development.
+ * 
+ * @author Steve Brown
+ * @since 1.5.2
  */
-function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
+function sqimap_mailbox_option_array($imap_stream, $folder_skip = 0, $boxes = 0,
                                     $flag = 'noselect', $use_long_format = false ) {
-    global $username, $data_dir;
+    global $username, $data_dir, $translate_special_folders, $sent_folder, 
+        $trash_folder, $draft_folder;
+
+    $delimiter = sqimap_get_delimiter($imap_stream);
+
     $mbox_options = '';
     if ( $use_long_format ) {
         $shorten_box_names = 0;
     } else {
-        $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF);
+        $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_MAILBOX_SELECT_INDENTED);
     }
 
     if ($boxes == 0) {
         $boxes = sqimap_mailbox_list($imap_stream);
     }
 
+    $a = array();
     foreach ($boxes as $boxes_part) {
         if ($flag == NULL || (is_array($boxes_part['flags'])
                       && !in_array($flag, $boxes_part['flags']))) {
@@ -689,25 +777,98 @@ function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_sk
             } else {
                 switch ($shorten_box_names)
                 {
-                  case 2:   /* delimited, style = 2 */
-                    $box2 = str_replace('&amp;nbsp;&amp;nbsp;', '.&nbsp;', htmlspecialchars($boxes_part['formatted']));
+                  case SMPREF_MAILBOX_SELECT_DELIMITED:
+                      if ($translate_special_folders && $boxes_part['unformatted-dm']==$sent_folder) {
+                          /*
+                           * calculate pad level from number of delimiters. do it inside if control in order 
+                           * to reduce number of calculations. Other folders don't need it.
+                           */
+                          $pad = str_pad('',7 * (count(explode($delimiter,$boxes_part['unformatted-dm']))-1),'.&nbsp;');
+                          // i18n: Name of Sent folder
+                          $box2 = $pad . _("Sent");
+                      } elseif ($translate_special_folders && $boxes_part['unformatted-dm']==$trash_folder) {
+                          $pad = str_pad('',7 * (count(explode($delimiter,$boxes_part['unformatted-dm']))-1),'.&nbsp;');
+                          // i18n: Name of Trash folder
+                          $box2 = $pad . _("Trash");
+                      } elseif ($translate_special_folders && $boxes_part['unformatted-dm']==$draft_folder) {
+                          $pad = str_pad('',7 * (count(explode($delimiter,$boxes_part['unformatted-dm']))-1),'.&nbsp;');
+                          // i18n: Name of Drafts folder
+                          $box2 = $pad . _("Drafts");
+                      } else {
+                          $box2 = str_replace('&amp;nbsp;&amp;nbsp;', '.&nbsp;', sm_encode_html_special_chars($boxes_part['formatted']));
+                      }
                     break;
-                  case 1:   /* indent, style = 1 */
-                    $box2 = str_replace('&amp;nbsp;&amp;nbsp;', '&nbsp;&nbsp;', htmlspecialchars($boxes_part['formatted']));
+                  case SMPREF_MAILBOX_SELECT_INDENTED:
+                      if ($translate_special_folders && $boxes_part['unformatted-dm']==$sent_folder) {
+                          $pad = str_pad('',12 * (count(explode($delimiter,$boxes_part['unformatted-dm']))-1),'&nbsp;&nbsp;');
+                          $box2 = $pad . _("Sent");
+                      } elseif ($translate_special_folders && $boxes_part['unformatted-dm']==$trash_folder) {
+                          $pad = str_pad('',12 * (count(explode($delimiter,$boxes_part['unformatted-dm']))-1),'&nbsp;&nbsp;');
+                          $box2 = $pad . _("Trash");
+                      } elseif ($translate_special_folders && $boxes_part['unformatted-dm']==$draft_folder) {
+                          $pad = str_pad('',12 * (count(explode($delimiter,$boxes_part['unformatted-dm']))-1),'&nbsp;&nbsp;');
+                          $box2 = $pad . _("Drafts");
+                      } else {
+                          $box2 = str_replace('&amp;nbsp;&amp;nbsp;', '&nbsp;&nbsp;', sm_encode_html_special_chars($boxes_part['formatted']));
+                      }
                     break;
                   default:  /* default, long names, style = 0 */
-                    $box2 = str_replace(' ', '&nbsp;', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
+                    $box2 = str_replace(' ', '&nbsp;', sm_encode_html_special_chars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
                     break;
                 }
             }
-            if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
-                $mbox_options .= '<option value="' . htmlspecialchars($box) .'" selected="selected">'.$box2.'</option>' . "\n";
-            } else {
-                $mbox_options .= '<option value="' . htmlspecialchars($box) .'">'.$box2.'</option>' . "\n";
+            
+            $a[sm_encode_html_special_chars($box)] = $box2;
+        }
+    }
+    
+    return $a;
+}
+
+/**
+ * Returns list of options (to be echoed into select statement
+ * based on available mailboxes and separators
+ * Caller should surround options with <select ...> </select> and
+ * any formatting.
+ * @param stream $imap_stream imap connection resource to query for mailboxes
+ * @param array $show_selected array containing list of mailboxes to pre-select (0 if none)
+ * @param array $folder_skip array of folders to keep out of option list (compared in lower)
+ * @param $boxes list of already fetched boxes (for places like folder panel, where
+ *            you know these options will be shown 3 times in a row.. (most often unset).
+ * @param string $flag (since 1.4.1) flag to check for in mailbox flags, used to filter out mailboxes.
+ *           'noselect' by default to remove unselectable mailboxes.
+ *           'noinferiors' used to filter out folders that can not contain subfolders.
+ *           NULL to avoid flag check entirely.
+ *           NOTE: noselect and noiferiors are used internally. The IMAP representation is
+ *                 \NoSelect and \NoInferiors
+ * @param boolean $use_long_format (since 1.4.1) override folder display preference and always show full folder name.
+ * @return string html formated mailbox selection options
+ * @since 1.3.2
+ */
+function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
+                                    $flag = 'noselect', $use_long_format = false ) {
+    global $username, $data_dir, $translate_special_folders, $sent_folder, 
+        $trash_folder, $draft_folder;
+
+    $boxes = sqimap_mailbox_option_array($imap_stream, $folder_skip, $boxes, $flag, $use_long_format);
+    
+    $str = '';
+    foreach ($boxes as $value=>$option) {
+        $lowerbox = strtolower(sm_encode_html_special_chars($value));
+        $sel = false;
+        if ($show_selected != 0) {
+            reset($show_selected);
+            while (!$sel && (list($x, $val) = each($show_selected))) {
+                if (strtolower($value) == strtolower(sm_encode_html_special_chars($val))) {
+                    $sel = true;
+                }
             }
         }
+        
+        $str .= '<option value="'. $value .'"'. ($sel ? ' selected="selected"' : '').'>'. $option ."</option>\n";
     }
-    return $mbox_options;
+    
+    return $str;
 }
 
 /**
@@ -767,7 +928,7 @@ function sqimap_get_mailboxes($imap_stream,$force=false,$show_only_subscribed=tr
 
     if ($show_only_subscribed) { $show_only_subscribed=$show_only_subscribed_folders; }
 
-    require_once(SM_PATH . 'include/load_prefs.php');
+    //require_once(SM_PATH . 'include/load_prefs.php');
 
     /**
      * There are three main listing commands we can use in IMAP:
@@ -812,7 +973,6 @@ function sqimap_get_mailboxes($imap_stream,$force=false,$show_only_subscribed=tr
 
     // get subscribed mailbox list from cache (session)
     // if not there, then get it from the imap server and store in cache
-    sqsession_is_active();
 
     if (!$force) {
         sqgetGlobalVar($sub_cache_name,$lsub_cache,SQ_SESSION);
@@ -939,7 +1099,9 @@ function sqimap_get_mailboxes($imap_stream,$force=false,$show_only_subscribed=tr
     $cnt = count($boxesall);
     $used = array_pad($used,$cnt,false);
     $has_inbox = false;
-    for($k = 0; $k < $cnt; ++$k) {
+    foreach ($boxesall as $k => $b)
+    // was this but array not guaranteed to be contiguous: for($k = 0; $k < $cnt; ++$k)
+    {
         if (strtoupper($boxesall[$k]['unformatted']) == 'INBOX') {
             $boxesnew[] = $boxesall[$k];
             $used[$k] = true;
@@ -968,7 +1130,9 @@ function sqimap_get_mailboxes($imap_stream,$force=false,$show_only_subscribed=tr
 
     /* List special folders and their subfolders, if requested. */
     if ($list_special_folders_first) {
-        for($k = 0; $k < $cnt; ++$k) {
+        foreach ($boxesall as $k => $b)
+        // was this but array not guaranteed to be contiguous: for($k = 0; $k < $cnt; ++$k)
+        {
             if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
                 $boxesnew[] = $boxesall[$k];
                 $used[$k]   = true;
@@ -977,7 +1141,9 @@ function sqimap_get_mailboxes($imap_stream,$force=false,$show_only_subscribed=tr
     }
 
     /* Find INBOX's children */
-    for($k = 0; $k < $cnt; ++$k) {
+    foreach ($boxesall as $k => $b)
+    // was this but array not guaranteed to be contiguous: for($k = 0; $k < $cnt; ++$k)
+    {
         $isboxbelow=isBoxBelow(strtoupper($boxesall[$k]['unformatted']),'INBOX');
         if (strtoupper($boxesall[$k]['unformatted']) == 'INBOX') {
             $is_inbox=1;
@@ -992,7 +1158,9 @@ function sqimap_get_mailboxes($imap_stream,$force=false,$show_only_subscribed=tr
     }
 
     /* Rest of the folders */
-    for($k = 0; $k < $cnt; $k++) {
+    foreach ($boxesall as $k => $b)
+    // was this but array not guaranteed to be contiguous: for($k = 0; $k < $cnt; ++$k)
+    {
         if (!$used[$k]) {
             $boxesnew[] = $boxesall[$k];
         }
@@ -1092,7 +1260,6 @@ function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
     $trail_del = false;
     $start = 0;
 
-
     if (isset($folder_prefix) && ($folder_prefix != '')) {
         $start = substr_count($folder_prefix,$delimiter);
         if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
@@ -1156,17 +1323,27 @@ function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
  * @since 1.5.0
  */
 function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
+    global $draft_folder, $sent_folder, $trash_folder, $translate_special_folders;
+
+    /* decode folder name and set mailboxname_sub */
+    if ($translate_special_folders && strtoupper($mbx_tree->mailboxname_full) == 'INBOX') {
+        $mbx_tree->mailboxname_sub = _("INBOX");
+    } elseif ($translate_special_folders && $mbx_tree->mailboxname_full == $draft_folder) {
+        $mbx_tree->mailboxname_sub = _("Drafts");
+    } elseif ($translate_special_folders && $mbx_tree->mailboxname_full == $sent_folder) {
+        $mbx_tree->mailboxname_sub = _("Sent");
+    } elseif ($translate_special_folders && $mbx_tree->mailboxname_full == $trash_folder) {
+        $mbx_tree->mailboxname_sub = _("Trash");
+    } else {
+        $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
+    }
 
-   if (strtoupper($mbx_tree->mailboxname_full) == 'INBOX')
-       $mbx_tree->mailboxname_sub = _("INBOX");
-   else
-       $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
-   if ($mbx_tree->mbxs) {
-      $iCnt = count($mbx_tree->mbxs);
-      for ($i=0;$i<$iCnt;++$i) {
+    if ($mbx_tree->mbxs) {
+        $iCnt = count($mbx_tree->mbxs);
+        for ($i=0;$i<$iCnt;++$i) {
             sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs[$i]);
-      }
-   }
+        }
+    }
 }
 
 /**
@@ -1205,13 +1382,13 @@ function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
                 $mbx = $oMbx->mailboxname_full;
                 if ($unseen_type == 2 ||
                    ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
-                   $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
+                   $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN RECENT)';
                 } else {
-                   $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
+                   $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN RECENT)';
                 }
                 sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
             } else {
-                $oMbx->unseen = $oMbx->total = false;
+                $oMbx->unseen = $oMbx->total = $oMbx->recent = false;
                 $tag = false;
             }
             $oMbx->tag = $tag;
@@ -1231,6 +1408,10 @@ function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
                 if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
                     $oMbx->total = $regs[1];
                 }
+                if (preg_match('/RECENT\s+([0-9]+)/i', $sResponse, $regs)) {
+                    $oMbx->recent = $regs[1];
+                }
+
            }
            unset($oMbx->tag);
         }
@@ -1245,6 +1426,7 @@ function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
                     $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full);
                     $oMbx->unseen = $aStatus['UNSEEN'];
                     $oMbx->total  = $aStatus['MESSAGES'];
+                    $oMbx->recent = $aStatus['RECENT'];
                 } else {
                     $oMbx->unseen = sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full);
                 }
@@ -1260,6 +1442,21 @@ function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
             }
         }
     }
+
+    $cnt = count($aMbxs);
+    for($i=0;$i<$cnt;++$i) {
+         $oMbx =& $aMbxs[$i];
+         unset($hook_status);
+         if (!empty($oMbx->unseen)) { $hook_status['UNSEEN']=$oMbx->unseen; }
+         if (!empty($oMbx->total)) { $hook_status['MESSAGES']=$oMbx->total; }
+         if (!empty($oMbx->recent)) { $hook_status['RECENT']=$oMbx->recent; }
+         if (!empty($hook_status))
+         {
+              $hook_status['MAILBOX']=$oMbx->mailboxname_full;
+              $hook_status['CALLER']='sqimap_get_status_mbx_tree'; // helps w/ debugging
+              do_hook('folder_status', $hook_status);
+         }
+    }
 }
 
 /**
@@ -1303,5 +1500,3 @@ function sqimap_mailbox_is_noinferiors($oImapStream,$sImapFolder,&$oBoxes) {
     }
     return false;
 }
-
-?>