Location: PHPKode > projects > V-webmail > includes/mailaccess/imapext.php
<?php
/**
* Filename.......: imaext.php
* Project........: V-webmail
* Last Modified..: $Date: 2006/03/10 13:12:04 $
* CVS Revision...: $Revision: 1.8 $
* 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')
	{
		$imp = @imap_open(sprintf('{%s:%s/%s}%s', $host, $port, $type, imap_utf7_encode($mbox)), $user, $pass);

		if ($imp) {
			$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 is_resource(@$this->conn);
	}

	/**
	* 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 @ is necessary
		if (@$info[$mbox]['noselect']) {
			return false;
		}

		$this->mbox = $mbox;
		return @imap_reopen($this->conn, $this->createFolderString($mbox));
	}

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

	/**
	* Returns capabilities
	*
	* @param  string  $attrib Capability to determine
	* @return boolean         Capable or not
	*/
	function capabilities($attrib)
	{
		global $SESSION;

		switch ($attrib) {
			case 'folders':

				if (isset($SESSION['email']['capa'])
						&& strpos($SESSION['email']['capa'], 'folders') === false) {
					return false;
				}

				return preg_match('/^imap/', $SESSION['email']['type']);
				break;

			case 'search':

				if (isset($SESSION['email']['capa']) 
						&& strpos($SESSION['email']['capa'], 'search') === false) {
					return false;
				}

				return true;
				break;

			default:
				return false;
		}
	}

	/**
	* Returns structured header information
	*/
	function headerinfo($msg_id)
	{
		if ($GLOBALS['SESSION']['email']['use_uids']) {
			$msg_id = imap_msgno($this->conn, $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) && @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 && $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'] && @$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) && 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'),
					 'flagLevel'     => ($headers->Flagged  == 'F' ? '_flag1' : ''),
					 'draft'         => ($headers->Draft    == 'X'),
					 'answered'      => ($headers->Answered == 'A'),
					 'unread'        => (preg_match('/^imap/i', $this->type) && ($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) && (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 . '*');

		// imap_getmailboxes() with '*' as pattern already returns all mailboxes,
		// But to get INBOX statistics also, we need to execute this extra call.
		// See bug 54: dovecot 1.0-beta3 returns false.
		$inboxfolder = imap_getmailboxes($this->conn, $this->createFolderString(), 'INBOX');
		if (!empty($inboxfolder) && is_array($inboxfolder)) {
			// I do not know why, but sometimes imap_getsubscribed() randomly fails
			// to return an array. No big deal under PHP4, but PHP5 will choke in a
			// most annoying way on array_merge if $folders is not an array. The
			// obvious workaround is a type cast to array:
			$folders = array_merge($inboxfolder, (array)$folders);
		}

		// 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
		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 && !in_array($folder_id, $tree['root_nodes'])) {
					$tree['root_nodes'][] = $folder_id;
				}

				// Need adding to branches array?
				if ($i < (count($folder_parts) - 1) && !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 && 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;
		$cache = !$nocache;

		if (@$HOSTINFO['cache_email_folders'] && $cache) {

			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 && $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) && 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