Location: PHPKode > projects > V-webmail > includes/mailaccess/mysql.php
<?php
/**
* Filename.......: imaext.php
* Project........: V-webmail
* Last Modified..: $Date: 2006/03/06 09:48:21 $
* CVS Revision...: $Revision: 1.3 $
* Copyright......: 2001-2004 Richard Heyes
*/

class mailaccess_imapext extends mailaccess
{
    /**
    * The connection resource
    * @var resource
    */
    var $conn;

    /**
    * Hostname of the server we're connected to
    * @var string
    */
    var $host;
    
    /**
    * Port of the server
    * @var integer
    */
    var $port;
    
    /**
    * Username
    * @var string
    */
    var $user;
    
    /**
    * Password
    * @var string
    */
    var $pass;
    
    /**
    * Type of account we're connected to
    * @var string
    */
    var $type;
    
    /**
    * Mailbox/folder we're connected to
    * @var string
    */
    var $mbox;

    /**
    * Storage for errors
    * @var array
    */
    var $errors;

    /**
    * Returns any errors that have been raised
    *
    * @return string Errors
    */
    function getErrors()
    {
        $this->errors = array_merge(@$this->errors, imap_errors());
        return '(' . implode(', ', $this->errors) . ')';
    }
    
    /**
    * Returns last error. Will call getErrors() first.
    *
    * @return string Last error message
    */
    function getLastError()
    {
        $this->getErrors();

        return !empty($this->errors) ? $this->errors[count($this->errors) - 1] : '';
    }

    /**
    * Connects. Returns true/false.
    *
    * @param string  $host Hostname
    * @param string  $user Username
    * @param string  $pass Password
    * @param integer $port Port
    * @param string  $type Account type
    * @param string  $mbox Mailbox/folder
    *
    * @return boolean Success or not
    */
    function connect($host, $user, $pass, $port, $type, $mbox = 'INBOX')
    {
        $options['type'] = 'mysql';
        $options['user'] = 'root';
        $options['pass'] = '';
        $options['dbas'] = 'vwebmail';

        $this->db = &database::factory($options);
        
        if ($this->db->isConnected()) {

            $this->conn = $imp;
            $this->host = $host;
            $this->port = $port;
            $this->type = $type;
            $this->mbox = $mbox;
            $this->user = $user;
            $this->pass = $pass;

            return true;

        } else {
            return false;
        }
    }

    /**
    * Returns whether a we're connected or not
    *
    * @return boolean Connected or not
    */
    function isConnected()
    {
        return $this->db->isConnected();
    }

    /**
    * Reconnects to server. Used for looking
    * at mailboxes other than the current.
    *
    * @param  string  $mbox Mailbox/folder
    * @return boolean       Success or not
    */
    function reconnect($mbox = 'INBOX')
    {

        $info = $this->getFolderInfo($mbox);

        $this->mbox = $mbox;
    }

    /**
    * Closes a connection
    *
    * @return boolean Success or not
    */
    function close()
    {
        return $this->db->disconnect();
    }

    /**
    * Returns capabilities
    * 
    * @param  string  $attrib Capability to determine
    * @return boolean         Capable or not
    */
    function capabilities($attrib)
    {
        switch ($attrib) {
            case 'folders':
            case 'search':
                return true;
                break;

            default:
                return false;
        }
    }


/* Got here */


    /**
    * Returns structured header information
    */
    function headerinfo($msg_id)
    {
        $info   = @imap_headerinfo($this->conn, $msg_id);

        return $info ? $info : false;
    }

    /**
    * Returns messages raw headers, as is.
    * Includes a blank line at the end.
    */
    function getRawHeaders($msg_id, $mime_id = null)
    {
        $options = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;

        if (!isset($mime_id)) {
            return imap_fetchheader($this->conn, $msg_id, $options);

        } else {
            $mime_id .= '.0';
            return imap_fetchbody($this->conn, $msg_id, $mime_id, $options);
        }
    }

    /**
    * Returns parsed headers
    */
    function getParsedHeaders($msg_id, $mime_id = null, $decode = false)
    {
        $headers = $this->getRawHeaders($msg_id, $mime_id);
        $headers = preg_split('/\r?\n/', trim($headers));

        // Unfold headers
        for ($i=count($headers)-1; $i>=0; $i--) {
            if ($headers[$i][0] == ' ' OR $headers[$i][0] == "\t") {
                $headers[$i-1] .= preg_replace('/^\s+(.*)$/', ' \1', $headers[$i]);
                unset($headers[$i]);
            }
        }

        //$headers = array_values($headers);
        $headers = array_values($headers);
        foreach ($headers as $key => $value) {
            $headers[$key] = decode_header($value);
        }

        // Make associative array out of indexed array
        foreach ($headers as $header) {
            preg_match('/^(.+):(.*)$/U', $header, $matches);

            $header_name = strtolower(trim($matches[1]));

            if (!empty($parsed_headers[$header_name])){
                if (is_array($parsed_headers[$header_name])) {
                    $parsed_headers[$header_name][] = trim($matches[2]);
                } else {
                    $parsed_headers[$header_name] = array_merge(array($parsed_headers[$header_name]), array(trim($matches[2])));
                }
            } else {
                $parsed_headers[$header_name] = trim($matches[2]);
            }
        }

        return $parsed_headers;
    }

    /**
    * Fetches body of msg
    */
    function getEntireMail($msg_id, $mime_id = null)
    {
        if (!empty($mime_id)) {
            return $this->getPart($msg_id, $mime_id);
        } else {
            $options = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;
            return sprintf("%s\r\n\r\n%s", rtrim($this->getRawHeaders($msg_id)), imap_body($this->conn, $msg_id, $options));
        }
    }

    /**
    * Fetchs a part
    */
    function getPart($msg_id, $part)
    {
        $options = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;
        return imap_fetchbody($this->conn, $msg_id, $part, $options);
    }

    /**
    * Returns structure of an email
    */
    function fetchStructure($msg_id)
    {
        $options = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;
        return imap_fetchstructure($this->conn, $msg_id, $options);
    }

    /**
    * Mime decoding function
    */
    function &parseStructure($parts, &$message, $is_related = false)
    {
        global $msg_id;

        $ctypes       = array('text', 'multipart', 'message', 'application', 'audio', 'image', 'video', 'other', 'other', 'binary');
        $encodings    = array('7bit', '8bit', 'binary', 'base64', 'quoted-printable', 'other', 'us-ascii'); // FIXME us-ascii correct ?

        $p_ctype      = $ctypes[(int)@$parts->type];
        $s_ctype      = strtolower($parts->subtype);

        $content_type = $p_ctype . '/' . $s_ctype;
        $encoding     = $encodings[(int)@$parts->encoding];
        $dparameters  = $parts->ifdparameters ? $parts->dparameters : array();
        $cparameters  = $parts->ifparameters  ? $parts->parameters  : array();
        $description  = $parts->ifdescription ? $parts->description : ($p_ctype == 'message' ? 'Attached message' : '');
        $cid          = $parts->ifid ? preg_replace('/^<(.*)>$/U', '\1', trim($parts->id)) : '';
        $size         = @$parts->bytes ? $parts->bytes : 0;
        $partno       = @$parts->mime_id;

        switch ($content_type) {
            // FIXME Optimise this
            case 'multipart/report':
            case 'multipart/signed':
            case 'multipart/mixed':
            case 'multipart/alternative':
            case 'multipart/related':
            case 'multipart/digest':
            case 'multipart/parallel':
                if (!empty($parts->parts)) {
                    for ($i=0; $i<count($parts->parts); $i++) {
                        $message = &$this->parseStructure($parts->parts[$i], $message, $content_type == 'multipart/related');
                    }
                }
                break;

            case 'text/plain':
            case 'text/html':
                // Could be html or text...
                $body_var = 'body_' . $s_ctype;
                $char_var = $p_ctype . '_charset';

                if (!isset($message->$body_var) AND @strtolower($parts->disposition) != 'attachment') {
                    @$message->$body_var .= decode_body($this->getPart($msg_id, $parts->mime_id), $encoding) . "\r\n";
                    $message->$char_var   = (!empty($cparameters[0]->charset)) ? $cparameters[0]->charset : 'iso-8859-1';
                    break;
                }

            case 'message/delivery-status':
            case 'message/rfc822':
            default:
                $member   = $is_related ? 'related' : 'attachments';
                $filename = null;
                $name     = null;

                foreach ($dparameters as $key => $value) {
                    if (strtolower($value->attribute) == 'filename') {
                        $filename = $value->value;
                    }
                }

                foreach ($cparameters as $key => $value) {
                    if (strtolower($value->attribute) == 'name') {
                        $name = $value->value;
                    }
                }

                $message->{$member}[] = array(
                                            'c_type'      => $content_type,
                                            'encoding'    => $encoding,
                                            'mime_id'     => $partno,
                                            'size'        => $size,
                                            'c_id'        => $cid,
                                            'description' => $description,
                                            'filename'    => $filename,
                                            'name'        => $name
                                           );

        }

        return $message;
    }

    /**
    * Assigns mime_ids to the message structure
    */
    function assignMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
    {
        $return = array();
        if (!empty($structure->parts)) {
            if ($mime_number != '') {
                $structure->mime_id = $prepend . $mime_number;
                $return[$prepend . $mime_number] = &$structure;
            }
            for ($i = 0; $i < count($structure->parts); $i++) {

                // 2 == message
                if($structure->type == 2){
                    $prepend      = $prepend . $mime_number . '.';
                    $_mime_number = '';
                }else{
                    $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
                }

                $arr = &$this->assignMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
                foreach ($arr as $key => $val) {
                    $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
                }
            }
        } else {
            if ($mime_number == '') {
                $mime_number = '1';
            }
            $structure->mime_id = $prepend . $mime_number;
            $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
        }
        
        return $return;
    }

    /**
    * Returns number of messages
    */
    function numMsg()
    {
        return (int)imap_num_msg($this->conn);
    }

    /**
    * Marks messages for deletion. $msg_ids
    * can be a single msg_id, or a comma
    * seperated list of msg_ids.
    */
    function delete($msg_ids, &$message, $force = false)
    {
        global $SESSION;

        $deleted_items_folder = $SESSION['userprefs']['settings']['deleted_items_folder'];

        // Need to determine which msgs are currently marked as deleted, and unmark
        // them since expunging would remove them.
        $d_msg_ids = $this->getMsgIds(1, 1, 1, 0, 'DELETED', $num_msgs);
        $this->setflag(implode(',', $d_msg_ids), 'undeleted');

        if (!$this->capabilities('folders') OR $SESSION['email']['mbox'] == $deleted_items_folder OR $force) {
            $options = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;
            imap_delete($this->conn, is_array($msg_ids) ? implode(',', $msg_ids) : $msg_ids, $options);
            $this->expunge();
            $message = null;

        } else {
            // Move to deleted items folder
            if (!$this->folderExists($deleted_items_folder)) {
                if (!$this->createFolder($deleted_items_folder)) {
                    $message = lang('Failed to create Trash folder');
                }
                $this->subscribeFolder($deleted_items_folder);
            }
            
            $message = $this->moveMsg($msg_ids, $deleted_items_folder) ? null : lang('Move of message failed');
            $this->expunge();
        }
        
        $this->setflag(implode(',', $d_msg_ids), 'deleted');
    }

    /**
    * Function to *actually* delete the 
    * messages currently marked for deletion
    */
    function expunge()
    {
        return imap_expunge($this->conn);
    }

    /**
    * Returns msg_ids based on:
    * sorting, asc/desc, start_msg, show_per_page
    */
    function getMsgIds($sort, $ascdesc, $start_msg, $per_page = 0, $search = 'ALL', &$num_msgs)
    {
        global $SESSION, $HOSTINFO;

        switch ($sort) {
            case 'from':
                $sort = SORTFROM;
                break;

            case 'subject':
                $sort = SORTSUBJECT;
                break;

            case 'to':
                $sort = SORTTO;
                break;
            
            case 'size':
                $sort = SORTSIZE;
                break;

            case 'date':
                $sort = SORTDATE;
                break;

            case 'arrival':
                $sort = SORTARRIVAL;
                break;
        }

        if ($sort == SORTARRIVAL AND $search == 'ALL') {
            $msg_ids = array();
            $total_num_msgs = imap_num_msg($this->conn);
            $num_msgs = $total_num_msgs;
            if ($total_num_msgs > 0) {
                $msg_ids = $ascdesc ? array_reverse(range(1, $total_num_msgs)) : range(1, $total_num_msgs);
                $msg_ids = array_slice($msg_ids, $start_msg-1, $per_page == 0 ? count($msg_ids) : $per_page);
                if ($GLOBALS['SESSION']['email']['use_uids']) {
                    for($i=0; $i<count($msg_ids); $i++){
                        $msg_ids[$i] = @imap_uid($this->conn, $msg_ids[$i]);
                    }
                }
            }
        } else {
            $options  = $GLOBALS['SESSION']['email']['use_uids'] ? SE_UID|SE_NOPREFETCH : SE_NOPREFETCH;
            $msg_ids  = imap_sort($this->conn, $sort, $ascdesc, $options, $search);
            $num_msgs = count($msg_ids);
            $msg_ids  = array_slice($msg_ids, $start_msg-1, $per_page == 0 ? count($msg_ids) : $per_page);
        }

        // Clear cache if number of messages has changed?
        if (@$HOSTINFO['refresh_email_list'] AND @$SESSION['email']['num_msgs'] != $num_msgs) {
            $this->cacheClear();
        }

        return $msg_ids;
    }

    /**
    * Returns a summary of the message for
    * the listing page.
    */
    function getMsgSummary($msg_id)
    {
        global $LANG, $SESSION, $HOSTINFO;

        /**
        * Get from cache if it's there
        */
        if ($data = $this->cacheGet($msg_id . ':' . $this->mbox)) {
            return $data;
        }

        /**
        * Not in the cache :(
        */
        $headers = $this->headerinfo($msg_id);

        /**
        * With extended_listing turned Off, this code
        * does not run as it requires the whole message
        * header
        */
        if ($GLOBALS['SESSION']['email']['extended_listing']) {
            // Any attachments?
            $options     = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;
            $raw_header  = imap_fetchheader($this->conn, $msg_id, $options);
            $attachments = preg_match('/Content-Type: multipart\/mixed/im', $raw_header);
    
            // Importance header?
            $importance = preg_match('/^Importance: High/im', $raw_header);

        } else {
            $attachments = false;
            $importance  = false;
        }

        // Determine Size
        if (isset($headers->Size)) {
            if ($headers->Size > 1048576) { 
                $size = round($headers->Size/1048576, 1).' Mb';

            } elseif ($headers->Size > 1024) {
                $size = round($headers->Size/1024   , 1).' Kb';

            } else {
                $size = $headers->Size.' b';
            }
        } else {
            $size = '&nbsp;';
        }

        // From address
        if (isset($headers->from)) {
            $email = $headers->from[0]->mailbox.'@'.$headers->from[0]->host;
            $name  = isset($headers->from[0]->personal) ? decode_header($headers->from[0]->personal) : $email;
            if (strlen($name) > 30) {
                $name = substr($name, 0, 30) . '...';
            }

        } else {
            $email = '';
            $name  = 'Unknown';
        }

        // To address
        if (isset($headers->to)) {
            $to_email = $headers->to[0]->mailbox.'@'.@$headers->to[0]->host;
            $to_name  = isset($headers->to[0]->personal) ? decode_header($headers->to[0]->personal) : $to_email;
            if (strlen($to_name) > 30) {
                $to_name = substr($to_name, 0, 30) . '...';
            }
        } else {
            $to_email = '';
            $to_name  = 'Unknown';
        }

        // Subject
        $subject = (isset($headers->subject) AND trim($headers->subject) != '') ? decode_header($headers->subject) : lang('[no subject]');
        // FIXME Make this max subject length configurable
        if (strlen($subject) > 80) {
            $subject = substr($subject, 0, 80).'...';
        }

        /**
        * Why is this here?
        * So the lang() parser picks them up. Used
        * for the date() operations below
        *
        * lang('Jan')
        * lang('Feb')
        * lang('Mar')
        * lang('Apr')
        * lang('May')
        * lang('Jun')
        * lang('Jul')
        * lang('Aug')
        * lang('Sep')
        * lang('Oct')
        * lang('Nov')
        * lang('Dec')
        */
        // Return as array of properties
        $ret = array(
                     'msg_id'        => $msg_id,
                     'date'          => sprintf('%s %s %s', date('H:i jS', $headers->udate),
                                                            lang(date('M', $headers->udate)),
                                                            date('Y', $headers->udate)),
                     'email'         => htmlspecialchars($email),
                     'name'          => htmlspecialchars($name),
                     'email_urlsafe' => urlencode(sprintf('"%s" <%s>', $name, $email)), // Err on caution as imap functions don't always return quoted strings
                     'to_email'      => htmlspecialchars($to_email),
                     'to_name'       => htmlspecialchars($to_name),
                     'to_urlsafe'    => urlencode(sprintf('"%s" <%s>', $to_name, $to_email)), // Err on caution as imap functions don't always return quoted strings
                     'subject'       => htmlspecialchars($subject),
                     'subject_raw'   => $subject,
                     'size'          => $size,
                     'attachments'   => $attachments,
                     'importance'    => $importance,
                     'deleted'       => ($headers->Deleted  == 'D'),
                     'flagged'       => ($headers->Flagged  == 'F'),
                     'draft'         => ($headers->Draft    == 'X'),
                     'answered'      => ($headers->Answered == 'A'),
                     'unread'        => (preg_match('/^imap/i', $this->type) AND ($headers->Unseen == 'U' OR $headers->Recent == 'N'))
                    );

        // Cache the summary information
        if (@$HOSTINFO['cache_email_list']) {
            $this->cacheAdd($msg_id . ':' . $this->mbox, $ret);
        }
        return $ret;
    }

//////////////////////////////////////////////////// Folder functions ////////////////////////////////////////////////////
    
    /**
    * Creates a folder string in the format:
    * {host:port/type}folder
    */
    function createFolderString($folder = '', $mailbox_only = false)
    {
        global $SESSION;

        if ($mailbox_only == true) {
            $return = (strcasecmp($folder, 'INBOX') == 0 ?  'INBOX' :  imap_utf7_encode($folder));

        } else {
            if ($folder == '') {
                $return = sprintf('{%s:%s/%s}', $this->host, $this->port, $this->type);

            } elseif (strcasecmp($folder, 'INBOX') == 0) {
                $return = sprintf('{%s:%s/%s}INBOX', $this->host, $this->port, $this->type);

            } else {
                $return = sprintf('{%s:%s/%s}%s', $this->host, $this->port, $this->type, imap_utf7_encode($folder));
            }
        }
        
        return $return;
    }

    /**
    * Determines if the mailbox is valid or not
    */
    function isValidMailbox($mbox)
    {
        if (strcasecmp($mbox, 'INBOX') == 0) {
            return true;
        }

        if (preg_match('/^pop3/', $this->type)) {
            return false;
        }

        $prefix = preg_quote(@$GLOBALS['SESSION']['email']['fold'], '/');
        $result = (preg_match(sprintf('/^%s/', $prefix), $mbox) AND (strpos($mbox, '../') === false));

        if (!$result) {
            $GLOBALS['logger']->log(sprintf('Invalid mailbox specified: %s by user: %s from IP: %s', $mbox, $this->user, common::getClientIP()), LOG_WARNING);
        }

        return $result;
    }

    /**
    * Gets folders, either subscribed or all.
    */
    function getFolders($func_type, $get_sizes = null, $get_unseen = null)
    {
        global $SESSION, $CONFIG;

        $treeMenu = new HTML_TreeMenu();

        // Init the "Tree tracking" arrays
        $tree['branches']   = array(); // Numeric
        $tree['root_nodes'] = array(); // Numeric
        $tree['relations']  = array(); // Associative/Numeric
        $tree['attributes'] = array(); // Associative
        $tree['textnames']  = array(); // Associative
        $tree['statistics'] = array(); // Associative/Associative
        $flat_list          = array(); // Numeric/Associative

        $folder_prefix = $SESSION['email']['fold'];

        // Get the folders
        $function = ($func_type == 'subscribed' ? 'imap_getsubscribed' : 'imap_getmailboxes');
        $folders  = $function($this->conn, $this->createFolderString(), $folder_prefix . '*');

        // Get rid of any prefix only folders and add the
        // attributes to the attributes array. Also get
        // rid of the identifying string {localhost/imap} etc
        $folders = array_merge(imap_getmailboxes($this->conn, $this->createFolderString(), 'INBOX'), $folders);
        for ($i=0; $i<count($folders); $i++) {
            // Delete empty entries
            if (!isset($folders[$i]->name) OR !isset($folders[$i]->attributes) OR !isset($folders[$i]->delimiter)) {
                unset($folders[$i]);
                continue;
            }
            // Delete prefix only folders
            if ($folders[$i]->name == $this->createFolderString() . $folder_prefix) {
                unset($folders[$i]);
                continue;
            }
            $folders[$i]->name = preg_replace('/^' . preg_quote($this->createFolderString(), '/') . '/', '', $folders[$i]->name);
            $folders[$i]->name = imap_utf7_decode($folders[$i]->name);
        }
        $folders = array_values($folders);

        foreach ($folders as $folder) {

            // See above for test code 
            $folder->name = preg_replace('/^' . preg_quote($this->createFolderString(), '/') . '/', '', $folder->name);

            // Save the attributes
            $tree['attributes'][$folder->name] = $folder->attributes;

            // Get folderstats
            $folderinfo = $this->getFolderInfo($folder->name, $get_sizes, $get_unseen);
            if (is_array(@$folderinfo[$folder->name])) {
                $tree['statistics'][$folder->name] = $folderinfo[$folder->name];
                $tree['statistics'][$folder->name]['delimiter'] = $folder->delimiter;
            } else {
                $tree['statistics'][$folder->name] = array('noinferiors' => false, 'noselect' => true, 'messages' => '-', 'unseen' => '-', 'size' => '-');
            }

            // Seperate into path parts
            if (!empty($folder->delimiter)) {
                $folder_parts = explode($folder->delimiter, $folder->name);
            } else {
                $folder_parts = array($folder->name);
            }

            // Save the name
            $tree['textnames'][$folder->name] = end($folder_parts);

            for ($i=0; $i<count($folder_parts); $i++) {
                // Make this folders' id
                $folder_id = implode($folder->delimiter, array_slice($folder_parts, 0, $i+1));

                // Is the root node for this folder in the root_nodes array?
                if ($i == 0 AND !in_array($folder_id, $tree['root_nodes'])) {
                    $tree['root_nodes'][] = $folder_id;
                }

                // Need adding to branches array?
                if ($i < (count($folder_parts) - 1) AND !in_array($folder_id, $tree['branches'])) {
                    $tree['branches'][] = $folder_id;
                }

                // Need to add to relations array?
                if ($i != 0) {
                    $parent_id = implode($folder->delimiter, array_slice($folder_parts, 0, $i));

                    if (!isset($tree['relations'][$parent_id])) {
                        $tree['relations'][$parent_id] = array($folder_id);

                    } elseif (!in_array($folder_id, $tree['relations'][$parent_id])) {
                        $tree['relations'][$parent_id][] = $folder_id;
                    }
                }
            }
        }

        foreach ($tree['root_nodes'] as $root_node) {
            // Handle top level branches
            $noselect       = isset($tree['statistics'][$root_node]['noselect'])  ? $tree['statistics'][$root_node]['noselect']  : true;
            $stats_messages = isset($tree['statistics'][$root_node]['messages'])  ? $tree['statistics'][$root_node]['messages']  : '-';
            $stats_unseen   = isset($tree['statistics'][$root_node]['unseen'])    ? $tree['statistics'][$root_node]['unseen']    : '-';
            $stats_size     = isset($tree['statistics'][$root_node]['size'])      ? $tree['statistics'][$root_node]['size']      : '-';
            $stats_bytes    = isset($tree['statistics'][$root_node]['bytes'])     ? $tree['statistics'][$root_node]['bytes']     : '-';
            $delimiter      = isset($tree['statistics'][$root_node]['delimiter']) ? $tree['statistics'][$root_node]['delimiter'] : '';
            $rename_prompt  = htmlspecialchars(str_replace($GLOBALS['SESSION']['email']['fold'], '', $root_node));

            $tree['nodes'][$root_node] = &$treeMenu->addItem(new HTML_TreeNode(array('text' => javascript::escapeString($root_node) . ($stats_unseen != '-' ? ' (' . $stats_unseen . ')': ''), 'link' => $noselect ? null : 'email.list.php?' . VWEBMAILSESSION . '&mbox=' . $root_node, 'icon' => 'folder.gif', 'expandedIcon' => 'folder-expanded.gif'), array('oncontextmenu' => 'return showFolderContext(event, \'' . $root_node . '\', \'' . $rename_prompt . '\')', 'ondragenter' => 'enterDrag()', 'ondrop' => 'drop(\'' . $root_node . '\')', 'ondragover' => 'overDrag()')));

            $flat_list[] = array('name'          => $root_node,
                                 'name_urlsafe'  => $noselect ? '' : urlencode($root_node),
                                 'name_htmlsafe' => htmlspecialchars($root_node),
                                 'text'          => htmlspecialchars($root_node),
                                 'depth'         => 0,
                                 'space_prefix'  => '',
                                 'image_prefix'  => sprintf('<img src="%s/%s" align="top">', 'images/TreeMenu', 'folder.gif'),
                                 'noselect'      => $noselect,
                                 'messages'      => $stats_messages,
                                 'unseen'        => $stats_unseen,
                                 'size'          => $stats_size,
                                 'bytes'         => $stats_bytes,
                                 'delimiter'     => $delimiter);

            if (in_array($root_node, $tree['branches'])) {
                $this->traverse_tree($root_node, 1, $flat_list, $tree, $tree['nodes'][$root_node]);
            }
        }

        return array($flat_list, $treeMenu);
    }

    /**
    * Traverses the tree built in the above function and does various
    * things with the data.
    */
    function traverse_tree($branch, $depth, &$flat_list, &$tree, &$treeMenu, $prepend = '')
    {
        if (!empty($tree['relations'][$branch])) {
            for ($i=0; $i<count($tree['relations'][$branch]); $i++) {

                // Gif modifier
                if ($i == 0 AND count($tree['relations'][$branch]) > 1) {
                    $gif_modifier = '';
                } elseif ($i == (count($tree['relations'][$branch]) - 1)) {
                    $gif_modifier = 'bottom';
                } else {
                    $gif_modifier = '';
                }

                $folder_id = $tree['relations'][$branch][$i];
                
                /**
                * If this folder is not subscribed, it will not have been given in the list.
                * This is not a problem unless it's a parent of a folder that *is* subscribed.
                * Therefore, if the folder isn't in the textnames array, we need to find its
                * details.
                */
                if (!isset($tree['textnames'][$folder_id])) {
                    $folder_info = imap_getmailboxes($this->conn, $this->createFolderString(), $folder_id);
                    $folder_info[0]->name = preg_replace('/^' . preg_quote($this->createFolderString(), '/') . '/', '', $folder_info[0]->name);
                    $folder_info[0]->name = imap_utf7_decode($folder_info[0]->name);
                    $folder_parts = !empty($folder_info[0]->delimiter) ? explode($folder_info[0]->delimiter, $folder_info[0]->name) : array($folder_info[0]->name);
                    $tree['textnames'][$folder_id] = end($folder_parts);
                    $tree['attributes'] = $folder_info[0]->attributes;
                    unset($folder_info);
                    unset($folder_parts);
                }

                $noselect       = isset($tree['statistics'][$folder_id]['noselect'])  ? $tree['statistics'][$folder_id]['noselect']  : true;
                $stats_messages = isset($tree['statistics'][$folder_id]['messages'])  ? $tree['statistics'][$folder_id]['messages']  : '-';
                $stats_unseen   = isset($tree['statistics'][$folder_id]['unseen'])    ? $tree['statistics'][$folder_id]['unseen']    : '-';
                $stats_size     = isset($tree['statistics'][$folder_id]['size'])      ? $tree['statistics'][$folder_id]['size']      : '-';
                $stats_bytes    = isset($tree['statistics'][$folder_id]['bytes'])     ? $tree['statistics'][$folder_id]['bytes']     : '-';
                $delimiter      = isset($tree['statistics'][$folder_id]['delimiter']) ? $tree['statistics'][$folder_id]['delimiter'] : '';
                $rename_prompt  = htmlspecialchars(str_replace($GLOBALS['SESSION']['email']['fold'], '', $folder_id));

                $tree['nodes'][$folder_id] = &$treeMenu->addItem(new HTML_TreeNode(array('text' => javascript::escapeString(htmlspecialchars($tree['textnames'][$folder_id])) . ($stats_unseen != '-' ? ' (' . $stats_unseen . ')' : ''), 'link' => $noselect ? null : 'email.list.php?' . VWEBMAILSESSION . '&mbox=' . urlencode($folder_id), 'icon' => 'folder.gif', 'expandedIcon' => 'folder-expanded.gif'), array('oncontextmenu' => 'return showFolderContext(event, \'' . urlencode($folder_id) . '\', \'' . $rename_prompt . '\')', 'ondragenter' => 'enterDrag()', 'ondrop' => 'drop(\'' . $folder_id . '\')', 'ondragover' => 'overDrag()')));

                // Add to flat list
                $flat_list[] = array('name'          => $folder_id,
                                     'name_urlsafe'  => $noselect ? '' : urlencode($folder_id),
                                     'name_htmlsafe' => htmlspecialchars($folder_id),
                                     'text'          => htmlspecialchars($tree['textnames'][$folder_id]),
                                     'depth'         => $depth,
                                     'space_prefix'  => str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $depth),
                                     'image_prefix'  => sprintf('%s<img src="%s/%s%s%s" align="top"><img src="%s/%s" align="top">', $prepend, 'images/TreeMenu', 'branch', $gif_modifier, '.gif', 'images/TreeMenu', 'folder.gif'),
                                     'noselect'      => $noselect,
                                     'messages'      => $stats_messages,
                                     'unseen'        => $stats_unseen,
                                     'size'          => $stats_size,
                                     'bytes'         => $stats_bytes,
                                     'delimiter'     => $delimiter);

                // Traverse sub branches
                if (in_array($folder_id, $tree['branches'])) {
                    if ($i < (count($tree['relations'][$branch]) - 1)) {
                        $new_prepend = sprintf('%s<img src="%s/line.gif" align="top">', $prepend, 'images/TreeMenu/');
                    } else {
                        $new_prepend = sprintf('%s<img src="%s/linebottom.gif" align="top">', $prepend, 'images/TreeMenu/');
                    }
                    $this->traverse_tree($folder_id, $depth + 1, $flat_list, $tree, $tree['nodes'][$folder_id], $new_prepend);
                }
            }
        }
    }

    /**
    * Returns list of subscribed folders
    */
    function getSubscribedFolders($get_sizes = null, $nocache = false, $get_unseen = null)
    {
        global $HOSTINFO;

        if (@$HOSTINFO['cache_email_folders'] AND !$nocache) {
            
            if ($folders = $this->cacheGet('subscribed_folders')) {
                return $folders;
            } else {
                $folders = $this->getFolders('subscribed');
                $this->cacheAdd('subscribed_folders', $folders);
                return $folders;
            }
        } else {
            return $this->getFolders('subscribed', $get_sizes, $get_unseen);
        }
    }

    /**
    * Returns a list of all folders
    */
    function getAllFolders($get_sizes = null, $get_unseen = null)
    {
        return $this->getFolders('all', $get_sizes, $get_unseen);
    }

    /**
    * Returns some information about a folder.
    */
    function getFolderInfo($folder_name, $get_sizes = null, $get_unseen = null)
    {
        global $CONFIG;

        $mbox    = $this->mbox;
        $folders = imap_getmailboxes($this->conn, $this->createFolderString(), imap_utf7_encode($folder_name));

        if ($folders) {
            foreach ($folders as $folder) {
                $name = substr(imap_utf7_decode($folder->name), strpos(imap_utf7_decode($folder->name), '}') + 1);

                $return[$name]['noinferiors'] = ($folder->attributes & LATT_NOINFERIORS) == LATT_NOINFERIORS;
                $return[$name]['noselect']    = ($folder->attributes & LATT_NOSELECT) == LATT_NOSELECT;
                $return[$name]['messages']    = 0;
                $return[$name]['unseen']      = 0;
                $return[$name]['size']        = '';

                if ($get_sizes === true) {
                    $this->reconnect($folder_name);
                    $mailboxmsginfo = imap_mailboxmsginfo($this->conn);
                    $this->reconnect($mbox); // Previous mailbox

                    $return[$name]['messages'] = $mailboxmsginfo->Nmsgs;
                    $return[$name]['unseen']   = $mailboxmsginfo->Unread;
                    $return[$name]['size']     = '';

                    // Format the size
                    if ($mailboxmsginfo->Size == 0) {
                        $size = '0';

                    } elseif($mailboxmsginfo->Size >= 1048576) {
                        $size = number_format($mailboxmsginfo->Size / 1048576, 1) . 'Mb';

                    } elseif($mailboxmsginfo->Size >= 1024) {
                        $size = number_format($mailboxmsginfo->Size / 1024) . 'Kb';

                    } else {
                        $size = $mailboxmsginfo->Size . 'b';
                    }

                    $return[$name]['size']  = $size;
                    $return[$name]['bytes'] = $mailboxmsginfo->Size;

                } elseif ($get_unseen === true AND $CONFIG['c-client'] >= 2001) {
                    $status = imap_status($this->conn, $this->createFolderString($name), SA_ALL);

                    $return[$name]['messages'] = $status->messages;
                    $return[$name]['unseen']   = $status->unseen;
                    $return[$name]['size']     = '-';

                // No stats
                } elseif ($get_sizes === false) {

                    $return[$name]['messages'] = '-';
                    $return[$name]['unseen']   = '-';
                    $return[$name]['size']     = '-';
                }
            } // End foreach
        }

        imap_errors();
        return @$return;
    }

    /**
    * Moves message from folder to folder
    */
    function moveMsg($msg_ids, $to)
    {
        $d_msg_ids = implode(',', $this->getMsgIds(1,1,1,0, 'DELETED', $num_msgs));
        $this->setflag($d_msg_ids, 'undeleted');

        $options = $GLOBALS['SESSION']['email']['use_uids'] ? CP_UID : 0;
        $result = imap_mail_move($this->conn, $msg_ids, $this->createFolderString($to, true), $options);
        $this->expunge();
        $this->setflag($d_msg_ids, 'deleted');

        return $result;
    }
    
    /**
    * Copies message from folder to folder
    */		
    function copyMsg($msg_ids, $to)
    {
        $options = $GLOBALS['SESSION']['email']['use_uids'] ? CP_UID : 0;
        return imap_mail_copy($this->conn, $msg_ids, $this->createFolderString($to, true), $options);
    }

    /**
    * Checks whether a folder exists or not
    */
    function folderExists($folder)
    {
        $folders = imap_listmailbox($this->conn, $this->createFolderString(), $this->createFolderString($folder, true));

        return (!empty($folders));
    }

    /**
    * Create a folder
    */
    function createFolder($folder)
    {
        $this->cacheRemove('subscribed_folders');

        return imap_createmailbox($this->conn, $this->createFolderString($folder));
    }
    
    /**
    * Create a subfolder
    */
    function createSubFolder($parentFolder, $childFolder)
    {
        $this->cacheRemove('subscribed_folders');

        $parentFolderInfo = imap_getmailboxes($this->conn, $this->createFolderString(), $this->createFolderString($parentFolder, true));
        $newFolder        = $parentFolder . $parentFolderInfo[0]->delimiter . $childFolder;

        $result = $this->createFolder($newFolder);

        return $result ? $newFolder : false;
    }

    /**
    * Delete a folder
    */		
    function deleteFolder($folder)
    {
        $this->cacheRemove('subscribed_folders');

        return imap_deletemailbox($this->conn, $this->createFolderString($folder));
    }
    
    /**
    * Empties a folders contents
    */
    function emptyFolder($folder)
    {
        if ($this->reconnect($folder)) {
            $msg_ids = $this->getMsgIds(1, 1, 1, 0, 'ALL', $num_msgs);
            $options = $GLOBALS['SESSION']['email']['use_uids'] ? FT_UID : 0;
            if (imap_delete($this->conn, implode(',', $msg_ids), $options) AND imap_expunge($this->conn)) {
                return count($msg_ids);
            }
        }
        return false;
    }

    /**
    * Rename a folder
    */		
    function renameFolder($from, $to)
    {
        $this->cacheRemove('subscribed_folders');

        if ($this->isSubscribed($from)) {
            $this->unsubscribeFolder($from);
            $result = imap_renamemailbox($this->conn, $this->createFolderString($from), $this->createFolderString($to));
            $this->subscribeFolder($result ? $to : $from); // Either subscribe to new, or resubscribe to old.
        } else {
            $result = imap_renamemailbox($this->conn, $this->createFolderString($from), $this->createFolderString($to));
        }

        return $result;
    }

    /**
    * Tells whether a folder is subscribed or not
    */
    function isSubscribed($folder)
    {
        global $SESSION;
        static $folders;

        if (!isset($folders)) {
            $folders = imap_getsubscribed($this->conn, $this->createFolderString(), $SESSION['email']['fold'] . '*');
            foreach ($folders as $value) {
                $tmp[] = substr($value->name, strpos($value->name, '}') + 1);
            }
            $folders = $tmp;
        }

        return in_array($folder, $folders);
    }

    /**
    * Function to subscribe to a folder
    */
    function subscribeFolder($folder)
    {
        return imap_subscribe($this->conn, $this->createFolderString($folder));
    }
    
    /**
    * Function to unsubscribe from a folder
    */
    function unsubscribeFolder($folder)
    {
        $this->cacheRemove('subscribed_folders');

        return imap_unsubscribe($this->conn, $this->createFolderString($folder));
    }

    /**
    * Sets flags on messages
    */
    function setflag($msg_ids, $flag)
    {
        switch($flag){
            case 'answered':
                $function = 'imap_setflag_full';
                $flag     = '\\Answered';
                break;

            case 'unanswered':
                $function = 'imap_clearflag_full';
                $flag     = '\\Answered';
                break;

            case 'deleted':
                $function = 'imap_setflag_full';
                $flag     = '\\Deleted';
                break;

            case 'undeleted':
                $function = 'imap_clearflag_full';
                $flag     = '\\Deleted';
                break;

            case 'draft':
                $function = 'imap_setflag_full';
                $flag     = '\\Draft';
                break;

            case 'undraft': // :-/
                $function = 'imap_clearflag_full';
                $flag     = '\\Draft';
                break;
            
            case 'flagged':
                $function = 'imap_setflag_full';
                $flag     = '\\Flagged';
                break;
            
            case 'unflagged':
                $function = 'imap_clearflag_full';
                $flag     = '\\Flagged';
                break;

            case 'read':
                $function = 'imap_setflag_full';
                $flag     = '\\Seen';
                break;

            case 'unread':
                $function = 'imap_clearflag_full';
                $flag     = '\\Seen';
                break;

            default:
                return false;
        }
        
        $options = $GLOBALS['SESSION']['email']['use_uids'] ? ST_UID : 0;
        $function($this->conn, $msg_ids, $flag, $options);
        return true;
    }

    /**
    * Appends a message to a folder
    */
    function append($message, $folder = null, $flags = null)
    {
        global $SESSION, $USERPREFS;

        if (!isset($folder)) {
            $folder = !empty($USERPREFS['settings']['sent_items_folder']) ? $USERPREFS['settings']['sent_items_folder'] : $SESSION['email']['fold'] . 'sent-mail';
        }
        
        if (!isset($flags) OR !in_array('\\Seen', $flags)) {
            $flags[] = '\\Seen';
        }

        return imap_append($this->conn, $this->createFolderString($folder), $message, implode(' ', $flags));
    }

    /**
    * Function checks for key folders,
    * ie Deleted|Sent Items
    */
    function checkKeyFolders()
    {
        global $SESSION, $USERPREFS;
        
        $draft_folder   = !empty($USERPREFS['settings']['draft_items_folder'])   ? $USERPREFS['settings']['draft_items_folder']   : $SESSION['email']['fold'] . 'drafts';
        $deleted_folder = !empty($USERPREFS['settings']['deleted_items_folder']) ? $USERPREFS['settings']['deleted_items_folder'] : $SESSION['email']['fold'] . 'deleted-mail';
        $sent_folder    = !empty($USERPREFS['settings']['sent_items_folder'])    ? $USERPREFS['settings']['sent_items_folder']    : $SESSION['email']['fold'] . 'sent-mail';

        foreach (array('INBOX', $draft_folder, $deleted_folder, $sent_folder) as $folder) {
            if (!$this->folderExists($folder)) {
                $this->createFolder($folder);
            }
            $this->subscribeFolder($folder);
        }
        
        imap_errors();
    }
} // End of mailaccess_imapext
?>
Return current item: V-webmail