diff options
Diffstat (limited to 'program/include/rcube_storage.php')
-rw-r--r-- | program/include/rcube_storage.php | 1055 |
1 files changed, 1055 insertions, 0 deletions
diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php new file mode 100644 index 000000000..124d148e4 --- /dev/null +++ b/program/include/rcube_storage.php @@ -0,0 +1,1055 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | program/include/rcube_storage.php | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2012, The Roundcube Dev Team | + | Copyright (C) 2012, Kolab Systems AG | + | Licensed under the GNU GPL | + | | + | PURPOSE: | + | Mail Storage Engine | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + | Author: Aleksander Machniak <alec@alec.pl> | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + + +/** + * Abstract class for accessing mail messages storage server + * + * @package Mail + * @author Thomas Bruederli <roundcube@gmail.com> + * @author Aleksander Machniak <alec@alec.pl> + * @version 2.0 + */ +abstract class rcube_storage +{ + /** + * Instance of connection object e.g. rcube_imap_generic + * + * @var mixed + */ + public $conn; + + protected $folder = 'INBOX'; + protected $default_charset = 'ISO-8859-1'; + protected $default_folders = array('INBOX'); + protected $search_set; + protected $options = array('auth_method' => 'check'); + protected $page_size = 10; + protected $threading = false; + + /** + * All (additional) headers used (in any way) by Roundcube + * Not listed here: DATE, FROM, TO, CC, REPLY-TO, SUBJECT, CONTENT-TYPE, LIST-POST + * (used for messages listing) are hardcoded in rcube_imap_generic::fetchHeaders() + * + * @var array + */ + protected $all_headers = array( + 'IN-REPLY-TO', + 'BCC', + 'MESSAGE-ID', + 'CONTENT-TRANSFER-ENCODING', + 'REFERENCES', + 'X-DRAFT-INFO', + 'MAIL-FOLLOWUP-TO', + 'MAIL-REPLY-TO', + 'RETURN-PATH', + ); + + const UNKNOWN = 0; + const NOPERM = 1; + const READONLY = 2; + const TRYCREATE = 3; + const INUSE = 4; + const OVERQUOTA = 5; + const ALREADYEXISTS = 6; + const NONEXISTENT = 7; + const CONTACTADMIN = 8; + + + /** + * Connect to the server + * + * @param string $host Host to connect + * @param string $user Username for IMAP account + * @param string $pass Password for IMAP account + * @param integer $port Port to connect to + * @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection + * + * @return boolean TRUE on success, FALSE on failure + */ + abstract function connect($host, $user, $pass, $port = 143, $use_ssl = null); + + + /** + * Close connection. Usually done on script shutdown + */ + abstract function close(); + + + /** + * Checks connection state. + * + * @return boolean TRUE on success, FALSE on failure + */ + abstract function is_connected(); + + + /** + * Returns code of last error + * + * @return int Error code + */ + abstract function get_error_code(); + + + /** + * Returns message of last error + * + * @return string Error message + */ + abstract function get_error_str(); + + + /** + * Returns code of last command response + * + * @return int Response code (class constant) + */ + abstract function get_response_code(); + + + /** + * Set connection and class options + * + * @param array $opt Options array + */ + public function set_options($opt) + { + $this->options = array_merge($this->options, (array)$opt); + } + + + /** + * Activate/deactivate debug mode. + * + * @param boolean $dbg True if conversation with the server should be logged + */ + abstract function set_debug($dbg = true); + + + /** + * Set default message charset. + * + * This will be used for message decoding if a charset specification is not available + * + * @param string $cs Charset string + */ + public function set_charset($cs) + { + $this->default_charset = $cs; + } + + + /** + * This list of folders will be listed above all other folders + * + * @param array $arr Indexed list of folder names + */ + public function set_default_folders($arr) + { + if (is_array($arr)) { + $this->default_folders = $arr; + + // add inbox if not included + if (!in_array('INBOX', $this->default_folders)) { + array_unshift($this->default_folders, 'INBOX'); + } + } + } + + + /** + * Set internal folder reference. + * All operations will be perfomed on this folder. + * + * @param string $folder Folder name + */ + public function set_folder($folder) + { + if ($this->folder == $folder) { + return; + } + + $this->folder = $folder; + } + + + /** + * Returns the currently used folder name + * + * @return string Name of the folder + */ + public function get_folder() + { + return $this->folder; + } + + + /** + * Set internal list page number. + * + * @param int $page Page number to list + */ + public function set_page($page) + { + $this->list_page = (int) $page; + } + + + /** + * Gets internal list page number. + * + * @return int Page number + */ + public function get_page() + { + return $this->list_page; + } + + + /** + * Set internal page size + * + * @param int $size Number of messages to display on one page + */ + public function set_pagesize($size) + { + $this->page_size = (int) $size; + } + + + /** + * Get internal page size + * + * @return int Number of messages to display on one page + */ + public function get_pagesize() + { + return $this->page_size; + } + + + /** + * Save a search result for future message listing methods. + * + * @param mixed $set Search set in driver specific format + */ + abstract function set_search_set($set); + + + /** + * Return the saved search set. + * + * @return array Search set in driver specific format, NULL if search wasn't initialized + */ + abstract function get_search_set(); + + + /** + * Returns the storage server's (IMAP) capability + * + * @param string $cap Capability name + * + * @return mixed Capability value or TRUE if supported, FALSE if not + */ + abstract function get_capability($cap); + + + /** + * Sets threading flag to the best supported THREAD algorithm. + * Enable/Disable threaded mode. + * + * @param boolean $enable TRUE to enable and FALSE + * + * @return mixed Threading algorithm or False if THREAD is not supported + */ + public function set_threading($enable = false) + { + $this->threading = false; + + if ($enable && ($caps = $this->get_capability('THREAD'))) { + $methods = array('REFS', 'REFERENCES', 'ORDEREDSUBJECT'); + $methods = array_intersect($methods, $caps); + + $this->threading = array_shift($methods); + } + + return $this->threading; + } + + + /** + * Get current threading flag. + * + * @return mixed Threading algorithm or False if THREAD is not supported or disabled + */ + public function get_threading() + { + return $this->threading; + } + + + /** + * Checks the PERMANENTFLAGS capability of the current folder + * and returns true if the given flag is supported by the server. + * + * @param string $flag Permanentflag name + * + * @return boolean True if this flag is supported + */ + abstract function check_permflag($flag); + + + /** + * Returns the delimiter that is used by the server + * for folder hierarchy separation. + * + * @return string Delimiter string + */ + abstract function get_hierarchy_delimiter(); + + + /** + * Get namespace + * + * @param string $name Namespace array index: personal, other, shared, prefix + * + * @return array Namespace data + */ + abstract function get_namespace($name = null); + + + /** + * Get messages count for a specific folder. + * + * @param string $folder Folder name + * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT] + * @param boolean $force Force reading from server and update cache + * @param boolean $status Enables storing folder status info (max UID/count), + * required for folder_status() + * + * @return int Number of messages + */ + abstract function count($folder = null, $mode = 'ALL', $force = false, $status = true); + + + /** + * Public method for listing headers. + * + * @param string $folder Folder name + * @param int $page Current page to list + * @param string $sort_field Header field to sort by + * @param string $sort_order Sort order [ASC|DESC] + * @param int $slice Number of slice items to extract from result array + * + * @return array Indexed array with message header objects + */ + abstract function list_messages($folder = null, $page = null, $sort_field = null, $sort_order = null, $slice = 0); + + + /** + * Return sorted list of message UIDs + * + * @param string $folder Folder to get index from + * @param string $sort_field Sort column + * @param string $sort_order Sort order [ASC, DESC] + * + * @return rcube_result_index|rcube_result_thread List of messages (UIDs) + */ + abstract function index($folder = null, $sort_field = null, $sort_order = null); + + + /** + * Invoke search request to the server. + * + * @param string $folder Folder name to search in + * @param string $str Search criteria + * @param string $charset Search charset + * @param string $sort_field Header field to sort by + * + * @todo: Search criteria should be provided in non-IMAP format, eg. array + */ + abstract function search($folder = null, $str = 'ALL', $charset = null, $sort_field = null); + + + /** + * Direct (real and simple) search request (without result sorting and caching). + * + * @param string $folder Folder name to search in + * @param string $str Search string + * + * @return rcube_result_index Search result (UIDs) + */ + abstract function search_once($folder = null, $str = 'ALL'); + + + /** + * Refresh saved search set + * + * @return array Current search set + */ + abstract function refresh_search(); + + + /* -------------------------------- + * messages management + * --------------------------------*/ + + /** + * Fetch message headers and body structure from the server and build + * an object structure similar to the one generated by PEAR::Mail_mimeDecode + * + * @param int $uid Message UID to fetch + * @param string $folder Folder to read from + * + * @return object rcube_mail_header Message data + */ + abstract function get_message($uid, $folder = null); + + + /** + * Return message headers object of a specific message + * + * @param int $id Message sequence ID or UID + * @param string $folder Folder to read from + * @param bool $force True to skip cache + * + * @return rcube_mail_header Message headers + */ + abstract function get_message_headers($uid, $folder = null, $force = false); + + + /** + * Fetch message body of a specific message from the server + * + * @param int $uid Message UID + * @param string $part Part number + * @param rcube_message_part $o_part Part object created by get_structure() + * @param mixed $print True to print part, ressource to write part contents in + * @param resource $fp File pointer to save the message part + * @param boolean $skip_charset_conv Disables charset conversion + * + * @return string Message/part body if not printed + */ + abstract function get_message_part($uid, $part = 1, $o_part = null, $print = null, $fp = null, $skip_charset_conv = false); + + + /** + * Fetch message body of a specific message from the server + * + * @param int $uid Message UID + * + * @return string $part Message/part body + * @see rcube_imap::get_message_part() + */ + public function get_body($uid, $part = 1) + { + $headers = $this->get_message_headers($uid); + return rcube_charset_convert($this->get_message_part($uid, $part, null), + $headers->charset ? $headers->charset : $this->default_charset); + } + + + /** + * Returns the whole message source as string (or saves to a file) + * + * @param int $uid Message UID + * @param resource $fp File pointer to save the message + * + * @return string Message source string + */ + abstract function get_raw_body($uid, $fp = null); + + + /** + * Returns the message headers as string + * + * @param int $uid Message UID + * + * @return string Message headers string + */ + abstract function get_raw_headers($uid); + + + /** + * Sends the whole message source to stdout + */ + abstract function print_raw_body($uid); + + + /** + * Set message flag to one or several messages + * + * @param mixed $uids Message UIDs as array or comma-separated string, or '*' + * @param string $flag Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT + * @param string $folder Folder name + * @param boolean $skip_cache True to skip message cache clean up + * + * @return bool Operation status + */ + abstract function set_flag($uids, $flag, $folder = null, $skip_cache = false); + + + /** + * Remove message flag for one or several messages + * + * @param mixed $uids Message UIDs as array or comma-separated string, or '*' + * @param string $flag Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT + * @param string $folder Folder name + * + * @return bool Operation status + * @see set_flag + */ + public function unset_flag($uids, $flag, $folder = null) + { + return $this->set_flag($uids, 'UN'.$flag, $folder); + } + + + /** + * Append a mail message (source) to a specific folder. + * + * @param string $folder Target folder + * @param string $message The message source string or filename + * @param string $headers Headers string if $message contains only the body + * @param boolean $is_file True if $message is a filename + * + * @return int|bool Appended message UID or True on success, False on error + */ + abstract function save_message($folder, &$message, $headers = '', $is_file = false); + + + /** + * Move message(s) from one folder to another. + * + * @param mixed $uids Message UIDs as array or comma-separated string, or '*' + * @param string $to Target folder + * @param string $from Source folder + * + * @return boolean True on success, False on error + */ + abstract function move_message($uids, $to, $from = null); + + + /** + * Copy message(s) from one mailbox to another. + * + * @param mixed $uids Message UIDs as array or comma-separated string, or '*' + * @param string $to Target folder + * @param string $from Source folder + * + * @return boolean True on success, False on error + */ + abstract function copy_message($uids, $to, $from = null); + + + /** + * Mark message(s) as deleted and expunge. + * + * @param mixed $uids Message UIDs as array or comma-separated string, or '*' + * @param string $folder Source folder + * + * @return boolean True on success, False on error + */ + abstract function delete_message($uids, $folder = null); + + + /** + * Expunge message(s) and clear the cache. + * + * @param mixed $uids Message UIDs as array or comma-separated string, or '*' + * @param string $folder Folder name + * @param boolean $clear_cache False if cache should not be cleared + * + * @return boolean True on success, False on error + */ + abstract function expunge_message($uids, $folder = null, $clear_cache = true); + + + /** + * Parse message UIDs input + * + * @param mixed $uids UIDs array or comma-separated list or '*' or '1:*' + * + * @return array Two elements array with UIDs converted to list and ALL flag + */ + protected function parse_uids($uids) + { + if ($uids === '*' || $uids === '1:*') { + if (empty($this->search_set)) { + $uids = '1:*'; + $all = true; + } + // get UIDs from current search set + else { + $uids = join(',', $this->search_set->get()); + } + } + else { + if (is_array($uids)) { + $uids = join(',', $uids); + } + + if (preg_match('/[^0-9,]/', $uids)) { + $uids = ''; + } + } + + return array($uids, (bool) $all); + } + + + /* -------------------------------- + * folder managment + * --------------------------------*/ + + /** + * Get a list of subscribed folders. + * + * @param string $root Optional root folder + * @param string $name Optional name pattern + * @param string $filter Optional filter + * @param string $rights Optional ACL requirements + * @param bool $skip_sort Enable to return unsorted list (for better performance) + * + * @return array List of folders + */ + abstract function list_folders_subscribed($root = '', $name = '*', $filter = null, $rights = null, $skip_sort = false); + + + /** + * Get a list of all folders available on the server. + * + * @param string $root IMAP root dir + * @param string $name Optional name pattern + * @param mixed $filter Optional filter + * @param string $rights Optional ACL requirements + * @param bool $skip_sort Enable to return unsorted list (for better performance) + * + * @return array Indexed array with folder names + */ + abstract function list_folders($root = '', $name = '*', $filter = null, $rights = null, $skip_sort = false); + + + /** + * Subscribe to a specific folder(s) + * + * @param array $folders Folder name(s) + * + * @return boolean True on success + */ + abstract function subscribe($folders); + + + /** + * Unsubscribe folder(s) + * + * @param array $folders Folder name(s) + * + * @return boolean True on success + */ + abstract function unsubscribe($folders); + + + /** + * Create a new folder on the server. + * + * @param string $folder New folder name + * @param boolean $subscribe True if the newvfolder should be subscribed + * + * @return boolean True on success, False on error + */ + abstract function create_folder($folder, $subscribe = false); + + + /** + * Set a new name to an existing folder + * + * @param string $folder Folder to rename + * @param string $new_name New folder name + * + * @return boolean True on success, False on error + */ + abstract function rename_folder($folder, $new_name); + + + /** + * Remove a folder from the server. + * + * @param string $folder Folder name + * + * @return boolean True on success, False on error + */ + abstract function delete_folder($folder); + + + /** + * Send expunge command and clear the cache. + * + * @param string $folder Folder name + * @param boolean $clear_cache False if cache should not be cleared + * + * @return boolean True on success, False on error + */ + public function expunge_folder($folder = null, $clear_cache = true) + { + return $this->expunge_message('*', $folder, $clear_cache); + } + + + /** + * Remove all messages in a folder.. + * + * @param string $folder Folder name + * + * @return boolean True on success, False on error + */ + public function clear_folder($folder = null) + { + return $this->delete_message('*', $folder); + } + + + /** + * Checks if folder exists and is subscribed + * + * @param string $folder Folder name + * @param boolean $subscription Enable subscription checking + * + * @return boolean True if folder exists, False otherwise + */ + abstract function folder_exists($folder, $subscription = false); + + + /** + * Get folder size (size of all messages in a folder) + * + * @param string $folder Folder name + * + * @return int Folder size in bytes, False on error + */ + abstract function folder_size($folder); + + + /** + * Returns the namespace where the folder is in + * + * @param string $folder Folder name + * + * @return string One of 'personal', 'other' or 'shared' + */ + abstract function folder_namespace($folder); + + + /** + * Gets folder attributes (from LIST response, e.g. \Noselect, \Noinferiors). + * + * @param string $folder Folder name + * @param bool $force Set to True if attributes should be refreshed + * + * @return array Options list + */ + abstract function folder_attributes($folder, $force = false); + + + /** + * Gets connection (and current folder) data: UIDVALIDITY, EXISTS, RECENT, + * PERMANENTFLAGS, UIDNEXT, UNSEEN + * + * @param string $folder Folder name + * + * @return array Data + */ + abstract function folder_data($folder); + + + /** + * Returns extended information about the folder. + * + * @param string $folder Folder name + * + * @return array Data + */ + abstract function folder_info($folder); + + + /** + * Returns current status of a folder + * + * @param string $folder Folder name + * + * @return int Folder status + */ + abstract function folder_status($folder = null); + + + /** + * Synchronizes messages cache. + * + * @param string $folder Folder name + */ + abstract function folder_sync($folder); + + + /** + * Modify folder name according to namespace. + * For output it removes prefix of the personal namespace if it's possible. + * For input it adds the prefix. Use it before creating a folder in root + * of the folders tree. + * + * @param string $folder Folder name + * @param string $mode Mode name (out/in) + * + * @return string Folder name + */ + abstract function mod_folder($folder, $mode = 'out'); + + + /** + * Create all folders specified as default + */ + public function create_default_folders() + { + // create default folders if they do not exist + foreach ($this->default_folders as $folder) { + if (!$this->folder_exists($folder)) { + $this->create_folder($folder, true); + } + else if (!$this->folder_exists($folder, true)) { + $this->subscribe($folder); + } + } + } + + + /** + * Get mailbox quota information. + * + * @return mixed Quota info or False if not supported + */ + abstract function get_quota(); + + + /* ----------------------------------------- + * ACL and METADATA methods + * ----------------------------------------*/ + + /** + * Changes the ACL on the specified folder (SETACL) + * + * @param string $folder Folder name + * @param string $user User name + * @param string $acl ACL string + * + * @return boolean True on success, False on failure + */ + abstract function set_acl($folder, $user, $acl); + + + /** + * Removes any <identifier,rights> pair for the + * specified user from the ACL for the specified + * folder (DELETEACL). + * + * @param string $folder Folder name + * @param string $user User name + * + * @return boolean True on success, False on failure + */ + abstract function delete_acl($folder, $user); + + + /** + * Returns the access control list for a folder (GETACL). + * + * @param string $folder Folder name + * + * @return array User-rights array on success, NULL on error + */ + abstract function get_acl($folder); + + + /** + * Returns information about what rights can be granted to the + * user (identifier) in the ACL for the folder (LISTRIGHTS). + * + * @param string $folder Folder name + * @param string $user User name + * + * @return array List of user rights + */ + abstract function list_rights($folder, $user); + + + /** + * Returns the set of rights that the current user has to a folder (MYRIGHTS). + * + * @param string $folder Folder name + * + * @return array MYRIGHTS response on success, NULL on error + */ + abstract function my_rights($folder); + + + /** + * Sets metadata/annotations (SETMETADATA/SETANNOTATION) + * + * @param string $folder Folder name (empty for server metadata) + * @param array $entries Entry-value array (use NULL value as NIL) + * + * @return boolean True on success, False on failure + */ + abstract function set_metadata($folder, $entries); + + + /** + * Unsets metadata/annotations (SETMETADATA/SETANNOTATION) + * + * @param string $folder Folder name (empty for server metadata) + * @param array $entries Entry names array + * + * @return boolean True on success, False on failure + */ + abstract function delete_metadata($folder, $entries); + + + /** + * Returns folder metadata/annotations (GETMETADATA/GETANNOTATION). + * + * @param string $folder Folder name (empty for server metadata) + * @param array $entries Entries + * @param array $options Command options (with MAXSIZE and DEPTH keys) + * + * @return array Metadata entry-value hash array on success, NULL on error + */ + abstract function get_metadata($folder, $entries, $options = array()); + + + /* ----------------------------------------- + * Cache related functions + * ----------------------------------------*/ + + /** + * Clears the cache. + * + * @param string $key Cache key name or pattern + * @param boolean $prefix_mode Enable it to clear all keys starting + * with prefix specified in $key + */ + abstract function clear_cache($key = null, $prefix_mode = false); + + /** + * Returns cached value + * + * @param string $key Cache key + * + * @return mixed Cached value + */ + abstract function get_cache($key); + +} // end class rcube_storage + + +/** + * Class representing a message part + * + * @package Mail + */ +class rcube_message_part +{ + var $mime_id = ''; + var $ctype_primary = 'text'; + var $ctype_secondary = 'plain'; + var $mimetype = 'text/plain'; + var $disposition = ''; + var $filename = ''; + var $encoding = '8bit'; + var $charset = ''; + var $size = 0; + var $headers = array(); + var $d_parameters = array(); + var $ctype_parameters = array(); + + function __clone() + { + if (isset($this->parts)) { + foreach ($this->parts as $idx => $part) { + if (is_object($part)) { + $this->parts[$idx] = clone $part; + } + } + } + } +} + + +/** + * Class for sorting an array of rcube_mail_header objects in a predetermined order. + * + * @package Mail + * @author Eric Stadtherr + */ +class rcube_header_sorter +{ + private $uids = array(); + + + /** + * Set the predetermined sort order. + * + * @param array $index Numerically indexed array of IMAP UIDs + */ + function set_index($index) + { + $index = array_flip($index); + + $this->uids = $index; + } + + /** + * Sort the array of header objects + * + * @param array $headers Array of rcube_mail_header objects indexed by UID + */ + function sort_headers(&$headers) + { + uksort($headers, array($this, "compare_uids")); + } + + /** + * Sort method called by uksort() + * + * @param int $a Array key (UID) + * @param int $b Array key (UID) + */ + function compare_uids($a, $b) + { + // then find each sequence number in my ordered list + $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1; + $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1; + + // return the relative position as the comparison value + return $posa - $posb; + } +} |