From 00290a603237e719cc4ec3db65e6661ba7d46a51 Mon Sep 17 00:00:00 2001 From: alecpl Date: Tue, 9 Nov 2010 07:54:34 +0000 Subject: [PATCH] - Add support for shared folders (#1403507) --- CHANGELOG | 1 + config/main.inc.php.dist | 12 +- installer/rcube_install.php | 20 +-- program/include/rcmail.php | 6 - program/include/rcube_imap.php | 212 ++++++++++++++++++------- program/include/rcube_imap_generic.php | 69 +------- 6 files changed, 180 insertions(+), 140 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index de5c1494d..ce4509a46 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -65,6 +65,7 @@ CHANGELOG Roundcube Webmail - Improve responsiveness of messages displaying (#1486986) - Add option for minimum length of autocomplete's string (#1486428) - Fix operations on messages in unsubscribed folder (#1487107) +- Add support for shared folders (#1403507) RELEASE 0.4.2 ------------- diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 0d71c44eb..996b2ecdb 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -74,11 +74,17 @@ $rcmail_config['default_port'] = 143; // best server supported one) $rcmail_config['imap_auth_type'] = null; -// If you know your imap's root directory and its folder delimiter, -// you can specify them here. Otherwise they will be determined automatically. -$rcmail_config['imap_root'] = null; +// If you know your imap's folder delimiter, you can specify it here. +// Otherwise it will be determined automatically $rcmail_config['imap_delimiter'] = null; +// If IMAP server doesn't support NAMESPACE extension, but you're +// using shared folders or personal root folder is non-empty, you'll need to +// set these options. All can be strings or arrays of strings. +$rcmail_config['imap_ns_personal'] = null; +$rcmail_config['imap_ns_other'] = null; +$rcmail_config['imap_ns_shared'] = null; + // By default IMAP capabilities are readed after connection to IMAP server // In some cases, e.g. when using IMAP proxy, there's a need to refresh the list // after login. Set to True if you've got this case. diff --git a/installer/rcube_install.php b/installer/rcube_install.php index 677dda1e5..69be02563 100644 --- a/installer/rcube_install.php +++ b/installer/rcube_install.php @@ -38,6 +38,7 @@ class rcube_install 'locale_string' => 'language', 'multiple_identities' => 'identities_level', 'addrbook_show_images' => 'show_images', + 'imap_root' => 'imap_ns_personal', ); // these config options are required for a working system @@ -169,16 +170,17 @@ class rcube_install else if ($prop == 'smtp_pass' && !empty($_POST['_smtp_user_u'])) { $value = '%p'; } - else if ($prop == 'default_imap_folders'){ - $value = Array(); - foreach($this->config['default_imap_folders'] as $_folder){ - switch($_folder) { - case 'Drafts': $_folder = $this->config['drafts_mbox']; break; - case 'Sent': $_folder = $this->config['sent_mbox']; break; - case 'Junk': $_folder = $this->config['junk_mbox']; break; - case 'Trash': $_folder = $this->config['trash_mbox']; break; + else if ($prop == 'default_imap_folders') { + $value = Array(); + foreach ($this->config['default_imap_folders'] as $_folder) { + switch($_folder) { + case 'Drafts': $_folder = $this->config['drafts_mbox']; break; + case 'Sent': $_folder = $this->config['sent_mbox']; break; + case 'Junk': $_folder = $this->config['junk_mbox']; break; + case 'Trash': $_folder = $this->config['trash_mbox']; break; } - if (!in_array($_folder, $value)) $value[] = $_folder; + if (!in_array($_folder, $value)) + $value[] = $_folder; } } else if (is_bool($default)) { diff --git a/program/include/rcmail.php b/program/include/rcmail.php index c7ba41935..8fa9df72f 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -503,8 +503,6 @@ class rcmail 'auth_method' => $this->config->get('imap_auth_type', 'check'), 'auth_cid' => $this->config->get('imap_auth_cid'), 'auth_pw' => $this->config->get('imap_auth_pw'), - 'delimiter' => isset($_SESSION['imap_delimiter']) ? $_SESSION['imap_delimiter'] : $this->config->get('imap_delimiter'), - 'rootdir' => isset($_SESSION['imap_root']) ? $_SESSION['imap_root'] : $this->config->get('imap_root'), 'debug_mode' => (bool) $this->config->get('imap_debug', 0), 'force_caps' => (bool) $this->config->get('imap_force_caps'), 'timeout' => (int) $this->config->get('imap_timeout', 0), @@ -790,10 +788,6 @@ class rcmail if (isset($_SESSION['page'])) { $this->imap->set_page($_SESSION['page']); } - - // cache IMAP root and delimiter in session for performance reasons - $_SESSION['imap_root'] = $this->imap->root_dir; - $_SESSION['imap_delimiter'] = $this->imap->delimiter; } diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index acf0aa0e0..2b77bf55d 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -33,10 +33,8 @@ class rcube_imap { public $debug_level = 1; public $skip_deleted = false; - public $root_dir = ''; public $page_size = 10; public $list_page = 1; - public $delimiter = NULL; public $threading = false; public $fetch_add_headers = ''; public $get_all_headers = false; @@ -54,8 +52,9 @@ class rcube_imap * @var rcube_mdb2 */ private $db; - private $root_ns = ''; private $mailbox = 'INBOX'; + private $delimiter = NULL; + private $namespace = NULL; private $sort_field = ''; private $sort_order = 'DESC'; private $caching_enabled = false; @@ -156,18 +155,13 @@ class rcube_imap $this->port = $port; $this->ssl = $use_ssl; - // print trace messages if ($this->conn->connected()) { + // print trace messages if ($this->conn->message && ($this->debug_level & 8)) { console($this->conn->message); } - - // get server properties - $rootdir = $this->conn->getRootDir(); - if (!empty($rootdir)) - $this->set_rootdir($rootdir); - if (empty($this->delimiter)) - $this->get_hierarchy_delimiter(); + // get namespace and delimiter + $this->set_env(); return true; } @@ -246,28 +240,6 @@ class rcube_imap } - /** - * Set a root folder for the IMAP connection. - * - * Only folders within this root folder will be displayed - * and all folder paths will be translated using this folder name - * - * @param string $root Root folder - * @access public - */ - function set_rootdir($root) - { - if (preg_match('/[.\/]$/', $root)) //(substr($root, -1, 1)==='/') - $root = substr($root, 0, -1); - - $this->root_dir = $root; - $this->options['rootdir'] = $root; - - if (empty($this->delimiter)) - $this->get_hierarchy_delimiter(); - } - - /** * Set default message charset * @@ -482,13 +454,101 @@ class rcube_imap */ function get_hierarchy_delimiter() { - if ($this->conn && empty($this->delimiter)) - $this->delimiter = $this->conn->getHierarchyDelimiter(); + return $this->delimiter; + } - if (empty($this->delimiter)) - $this->delimiter = '/'; - return $this->delimiter; + /** + * Get namespace + * + * @return array Namespace data + * @access public + */ + function get_namespace() + { + return $this->namespace; + } + + + /** + * Sets delimiter and namespaces + * + * @access private + */ + private function set_env() + { + if ($this->delimiter !== null && $this->namespace !== null) { + return; + } + + if (isset($_SESSION['imap_namespace']) && isset($_SESSION['imap_delimiter'])) { + $this->namespace = $_SESSION['imap_namespace']; + $this->delimiter = $_SESSION['imap_delimiter']; + return; + } + + $config = rcmail::get_instance()->config; + $imap_personal = $config->get('imap_ns_personal'); + $imap_other = $config->get('imap_ns_other'); + $imap_shared = $config->get('imap_ns_shared'); + $imap_delimiter = $config->get('imap_delimiter'); + + if ($imap_delimiter) { + $this->delimiter = $imap_delimiter; + } + + if (!$this->conn) + return; + + $ns = $this->conn->getNamespace(); + + // NAMESPACE supported + if (is_array($ns)) { + $this->namespace = $ns; + + if (empty($this->delimiter)) + $this->delimiter = $ns['personal'][0][1]; + if (empty($this->delimiter)) + $this->delimiter = $this->conn->getHierarchyDelimiter(); + if (empty($this->delimiter)) + $this->delimiter = '/'; + } + // not supported, get namespace from config + else if ($imap_personal !== null || $imap_shared !== null || $imap_other !== null) { + if (empty($this->delimiter)) + $this->delimiter = $this->conn->getHierarchyDelimiter(); + if (empty($this->delimiter)) + $this->delimiter = '/'; + + $this->namespace = array( + 'personal' => NULL, + 'other' => NULL, + 'shared' => NULL, + ); + + if ($imap_personal !== null) { + foreach ((array)$imap_personal as $dir) { + $this->namespace['personal'][] = array($dir, $this->delimiter); + } + } + if ($imap_other !== null) { + foreach ((array)$imap_other as $dir) { + if ($dir) { + $this->namespace['other'][] = array($dir, $this->delimiter); + } + } + } + if ($imap_shared !== null) { + foreach ((array)$imap_shared as $dir) { + if ($dir) { + $this->namespace['shared'][] = array($dir, $this->delimiter); + } + } + } + } + + $_SESSION['imap_namespace'] = $this->namespace; + $_SESSION['imap_delimiter'] = $this->delimiter; } @@ -891,7 +951,7 @@ class rcube_imap // flatten threads array // @TODO: fetch children only in expanded mode (?) $all_ids = array(); - foreach($msg_index as $root) { + foreach ($msg_index as $root) { $all_ids[] = $root; if (!empty($thread_tree[$root])) $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); @@ -1463,7 +1523,7 @@ class rcube_imap // flatten threads array $all_ids = array(); - foreach($msg_index as $root) { + foreach ($msg_index as $root) { $all_ids[] = $root; if (!empty($thread_tree[$root])) { foreach (array_keys_recursive($thread_tree[$root]) as $val) @@ -3073,10 +3133,11 @@ class rcube_imap $a_mboxes = explode(',', $mbox_name); if (is_array($a_mboxes)) { + $delimiter = $this->get_hierarchy_delimiter(); + foreach ($a_mboxes as $mbox_name) { $mailbox = $this->mod_mailbox($mbox_name); - $sub_mboxes = $this->conn->listMailboxes($this->mod_mailbox(''), - $mbox_name . $this->delimiter . '*'); + $sub_mboxes = $this->conn->listMailboxes('', $mbox_name . $delimiter . '*'); // unsubscribe mailbox before deleting $this->conn->unsubscribe($mailbox); @@ -3137,19 +3198,20 @@ class rcube_imap if ($mbox_name == 'INBOX') return true; - $key = $subscription ? 'subscribed' : 'existing'; - if (is_array($this->icache[$key]) && in_array($mbox_name, $this->icache[$key])) + $key = $subscription ? 'subscribed' : 'existing'; + $mbox = $this->mod_mailbox($mbox_name) + if (is_array($this->icache[$key]) && in_array($mbox, $this->icache[$key])) return true; if ($subscription) { - $a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name); + $a_folders = $this->conn->listSubscribed('', $mbox); } else { - $a_folders = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_name); + $a_folders = $this->conn->listMailboxes('', $mbox); } - if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) { - $this->icache[$key][] = $mbox_name; + if (is_array($a_folders) && in_array($mbox, $a_folders)) { + $this->icache[$key][] = $mbox; return true; } } @@ -3167,14 +3229,52 @@ class rcube_imap */ function mod_mailbox($mbox_name, $mode='in') { - if ($mbox_name == 'INBOX') - return $mbox_name; + if (empty($mbox_name)) + return ''; - if (!empty($this->root_dir)) { - if ($mode=='in') - $mbox_name = $this->root_dir.$this->delimiter.$mbox_name; - else if (!empty($mbox_name)) // $mode=='out' - $mbox_name = substr($mbox_name, strlen($this->root_dir)+1); + if ($mode == 'in') { + // If folder contains namespace prefix, don't modify it + if (is_array($this->namespace['shared'])) { + foreach ($this->namespace['shared'] as $ns) { + foreach ((array)$ns as $root) { + if (strpos($mbox_name, $root[0]) === 0) { + return $mbox_name; + } + } + } + } + if (is_array($this->namespace['other'])) { + foreach ($this->namespace['other'] as $ns) { + foreach ((array)$ns as $root) { + if (strpos($mbox_name, $root[0]) === 0) { + return $mbox_name; + } + } + } + } + if (is_array($this->namespace['personal'])) { + foreach ($this->namespace['personal'] as $ns) { + foreach ((array)$ns as $root) { + if ($root[0] && strpos($mbox_name, $root[0]) === 0) { + return $mbox_name; + } + } + } + // Add prefix if first personal namespace is non-empty + if ($this->namespace['personal'][0][0]) { + return $this->namespace['personal'][0][0].$mbox_name; + } + } + } + else { + // Remove prefix if folder is from first ("non-empty") personal namespace + if (is_array($this->namespace['personal'])) { + if ($prefix = $this->namespace['personal'][0][0]) { + if (strpos($mbox_name, $prefix) === 0) { + return substr($mbox_name, strlen($prefix)); + } + } + } } return $mbox_name; @@ -3200,7 +3300,7 @@ class rcube_imap if (!is_array($this->conn->data['LIST']) || !is_array($this->conn->data['LIST'][$mbox])) { if ($force) { - $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_name); + $this->conn->listMailboxes('', $mbox_name); } else { return array(); diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index 0cb9f2441..6de27e806 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -549,54 +549,15 @@ class rcube_imap_generic } /** - * Gets the root directory and delimiter (of personal namespace) + * Gets the delimiter * - * @return mixed A root directory name, or false. - */ - function getRootDir() - { - if (isset($this->prefs['rootdir']) && is_string($this->prefs['rootdir'])) { - return $this->prefs['rootdir']; - } - - if (!is_array($data = $this->getNamespace())) { - return false; - } - - $user_space_data = $data['personal']; - if (!is_array($user_space_data)) { - return false; - } - - $first_userspace = $user_space_data[0]; - if (count($first_userspace) !=2) { - return false; - } - - $rootdir = $first_userspace[0]; - $this->prefs['delimiter'] = $first_userspace[1]; - $this->prefs['rootdir'] = $rootdir ? substr($rootdir, 0, -1) : ''; - - return $this->prefs['rootdir']; - } - - /** - * Gets the delimiter, for example: - * INBOX.foo -> . - * INBOX/foo -> / - * INBOX\foo -> \ - * - * @return mixed A delimiter (string), or false. - * @see connect() + * @return string The delimiter */ function getHierarchyDelimiter() { if ($this->prefs['delimiter']) { return $this->prefs['delimiter']; } - if (!empty($this->prefs['delimiter'])) { - return $this->prefs['delimiter']; - } // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8) list($code, $response) = $this->execute('LIST', @@ -611,26 +572,7 @@ class rcube_imap_generic } } - // if that fails, try namespace extension - // try to fetch namespace data - if (!is_array($data = $this->getNamespace())) { - return false; - } - - // extract user space data (opposed to global/shared space) - $user_space_data = $data['personal']; - if (!is_array($user_space_data)) { - return false; - } - - // get first element - $first_userspace = $user_space_data[0]; - if (!is_array($first_userspace)) { - return false; - } - - // extract delimiter - return $this->prefs['delimiter'] = $first_userspace[1]; + return NULL; } /** @@ -830,7 +772,6 @@ class rcube_imap_generic if ($this->prefs['force_caps']) { $this->clearCapability(); } - $this->getRootDir(); $this->logged = true; return true; @@ -1943,10 +1884,6 @@ class rcube_imap_generic $mailbox = '*'; } - if (empty($ref) && $this->prefs['rootdir']) { - $ref = $this->prefs['rootdir']; - } - $args = array(); if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) {