| 44 | | public $debug_level = 1; |
| 45 | | public $error_code = 0; |
| 46 | | public $skip_deleted = false; |
| 47 | | public $root_dir = ''; |
| 48 | | public $page_size = 10; |
| 49 | | public $list_page = 1; |
| 50 | | public $delimiter = NULL; |
| 51 | | public $threading = false; |
| 52 | | public $fetch_add_headers = ''; |
| 53 | | public $conn; |
| 54 | | |
| 55 | | private $db; |
| 56 | | private $root_ns = ''; |
| 57 | | private $mailbox = 'INBOX'; |
| 58 | | private $sort_field = ''; |
| 59 | | private $sort_order = 'DESC'; |
| 60 | | private $caching_enabled = false; |
| 61 | | private $default_charset = 'ISO-8859-1'; |
| 62 | | private $struct_charset = NULL; |
| 63 | | private $default_folders = array('INBOX'); |
| 64 | | private $default_folders_lc = array('inbox'); |
| 65 | | private $icache = array(); |
| 66 | | private $cache = array(); |
| 67 | | private $cache_keys = array(); |
| 68 | | private $cache_changes = array(); |
| 69 | | private $uid_id_map = array(); |
| 70 | | private $msg_headers = array(); |
| 71 | | public $search_set = NULL; |
| 72 | | public $search_string = ''; |
| 73 | | private $search_charset = ''; |
| 74 | | private $search_sort_field = ''; |
| 75 | | private $search_threads = false; |
| 76 | | private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); |
| 77 | | private $options = array('auth_method' => 'check'); |
| 78 | | private $host, $user, $pass, $port, $ssl; |
| 79 | | |
| 80 | | |
| 81 | | /** |
| 82 | | * Object constructor |
| 83 | | * |
| 84 | | * @param object DB Database connection |
| 85 | | */ |
| 86 | | function __construct($db_conn) |
| 87 | | { |
| 88 | | $this->db = $db_conn; |
| 89 | | } |
| 90 | | |
| 91 | | |
| 92 | | /** |
| 93 | | * Connect to an IMAP server |
| 94 | | * |
| 95 | | * @param string Host to connect |
| 96 | | * @param string Username for IMAP account |
| 97 | | * @param string Password for IMAP account |
| 98 | | * @param number Port to connect to |
| 99 | | * @param string SSL schema (either ssl or tls) or null if plain connection |
| 100 | | * @return boolean TRUE on success, FALSE on failure |
| 101 | | * @access public |
| 102 | | */ |
| 103 | | function connect($host, $user, $pass, $port=143, $use_ssl=null) |
| 104 | | { |
| 105 | | global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE; |
| 106 | | |
| 107 | | // check for Open-SSL support in PHP build |
| 108 | | if ($use_ssl && extension_loaded('openssl')) |
| 109 | | $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; |
| 110 | | else if ($use_ssl) { |
| 111 | | raise_error(array('code' => 403, 'type' => 'imap', |
| 112 | | 'file' => __FILE__, 'line' => __LINE__, |
| 113 | | 'message' => "Open SSL not available"), true, false); |
| 114 | | $port = 143; |
| 115 | | } |
| 116 | | |
| 117 | | $ICL_PORT = $port; |
| 118 | | $IMAP_USE_INTERNAL_DATE = false; |
| 119 | | |
| 120 | | $attempt = 0; |
| 121 | | do { |
| 122 | | $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); |
| 123 | | if (!empty($data['pass'])) |
| 124 | | $pass = $data['pass']; |
| 125 | | |
| 126 | | $this->conn = iil_Connect($data['host'], $data['user'], $pass, $this->options); |
| 127 | | } while(!$this->conn && $data['retry']); |
| 128 | | |
| 129 | | $this->host = $data['host']; |
| 130 | | $this->user = $data['user']; |
| 131 | | $this->pass = $pass; |
| 132 | | $this->port = $port; |
| 133 | | $this->ssl = $use_ssl; |
| 134 | | |
| 135 | | // print trace messages |
| 136 | | if ($this->conn && ($this->debug_level & 8)) |
| 137 | | console($this->conn->message); |
| 138 | | |
| 139 | | // write error log |
| 140 | | else if (!$this->conn && $GLOBALS['iil_error']) |
| 141 | | { |
| 142 | | $this->error_code = $GLOBALS['iil_errornum']; |
| 143 | | raise_error(array('code' => 403, 'type' => 'imap', |
| 144 | | 'file' => __FILE__, 'line' => __LINE__, |
| 145 | | 'message' => $GLOBALS['iil_error']), true, false); |
| 146 | | } |
| 147 | | |
| 148 | | // get server properties |
| 149 | | if ($this->conn) |
| 150 | | { |
| 151 | | if (!empty($this->conn->rootdir)) |
| 152 | | $this->set_rootdir($this->conn->rootdir); |
| 153 | | if (empty($this->delimiter)) |
| 154 | | $this->get_hierarchy_delimiter(); |
| 155 | | } |
| 156 | | |
| 157 | | return $this->conn ? true : false; |
| 158 | | } |
| 159 | | |
| 160 | | |
| 161 | | /** |
| 162 | | * Close IMAP connection |
| 163 | | * Usually done on script shutdown |
| 164 | | * |
| 165 | | * @access public |
| 166 | | */ |
| 167 | | function close() |
| | 37 | public $debug_level = 1; |
| | 38 | public $error_code = 0; |
| | 39 | public $skip_deleted = false; |
| | 40 | public $root_dir = ''; |
| | 41 | public $page_size = 10; |
| | 42 | public $list_page = 1; |
| | 43 | public $delimiter = NULL; |
| | 44 | public $threading = false; |
| | 45 | public $fetch_add_headers = ''; |
| | 46 | public $conn; // rcube_imap_generic object |
| | 47 | |
| | 48 | private $db; |
| | 49 | private $root_ns = ''; |
| | 50 | private $mailbox = 'INBOX'; |
| | 51 | private $sort_field = ''; |
| | 52 | private $sort_order = 'DESC'; |
| | 53 | private $caching_enabled = false; |
| | 54 | private $default_charset = 'ISO-8859-1'; |
| | 55 | private $struct_charset = NULL; |
| | 56 | private $default_folders = array('INBOX'); |
| | 57 | private $icache = array(); |
| | 58 | private $cache = array(); |
| | 59 | private $cache_keys = array(); |
| | 60 | private $cache_changes = array(); |
| | 61 | private $uid_id_map = array(); |
| | 62 | private $msg_headers = array(); |
| | 63 | public $search_set = NULL; |
| | 64 | public $search_string = ''; |
| | 65 | private $search_charset = ''; |
| | 66 | private $search_sort_field = ''; |
| | 67 | private $search_threads = false; |
| | 68 | private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); |
| | 69 | private $options = array('auth_method' => 'check'); |
| | 70 | private $host, $user, $pass, $port, $ssl; |
| | 71 | |
| | 72 | |
| | 73 | /** |
| | 74 | * Object constructor |
| | 75 | * |
| | 76 | * @param object DB Database connection |
| | 77 | */ |
| | 78 | function __construct($db_conn) |
| | 79 | { |
| | 80 | $this->db = $db_conn; |
| | 81 | $this->conn = new rcube_imap_generic(); |
| | 82 | } |
| | 83 | |
| | 84 | |
| | 85 | /** |
| | 86 | * Connect to an IMAP server |
| | 87 | * |
| | 88 | * @param string Host to connect |
| | 89 | * @param string Username for IMAP account |
| | 90 | * @param string Password for IMAP account |
| | 91 | * @param number Port to connect to |
| | 92 | * @param string SSL schema (either ssl or tls) or null if plain connection |
| | 93 | * @return boolean TRUE on success, FALSE on failure |
| | 94 | * @access public |
| | 95 | */ |
| | 96 | function connect($host, $user, $pass, $port=143, $use_ssl=null) |
| | 97 | { |
| | 98 | // check for Open-SSL support in PHP build |
| | 99 | if ($use_ssl && extension_loaded('openssl')) |
| | 100 | $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; |
| | 101 | else if ($use_ssl) { |
| | 102 | raise_error(array('code' => 403, 'type' => 'imap', |
| | 103 | 'file' => __FILE__, 'line' => __LINE__, |
| | 104 | 'message' => "OpenSSL not available"), true, false); |
| | 105 | $port = 143; |
| | 106 | } |
| | 107 | |
| | 108 | $this->options['port'] = $port; |
| | 109 | |
| | 110 | $attempt = 0; |
| | 111 | do { |
| | 112 | $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', |
| | 113 | array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); |
| | 114 | |
| | 115 | if (!empty($data['pass'])) |
| | 116 | $pass = $data['pass']; |
| | 117 | |
| | 118 | $this->conn->connect($data['host'], $data['user'], $pass, $this->options); |
| | 119 | } while(!$this->conn->connected() && $data['retry']); |
| | 120 | |
| | 121 | $this->host = $data['host']; |
| | 122 | $this->user = $data['user']; |
| | 123 | $this->pass = $pass; |
| | 124 | $this->port = $port; |
| | 125 | $this->ssl = $use_ssl; |
| | 126 | |
| | 127 | // print trace messages |
| | 128 | if ($this->conn->connected()) { |
| | 129 | if ($this->conn->message && ($this->debug_level & 8)) { |
| | 130 | console($this->conn->message); |
| | 131 | } |
| | 132 | |
| | 133 | // get server properties |
| | 134 | if (!empty($this->conn->rootdir)) |
| | 135 | $this->set_rootdir($this->conn->rootdir); |
| | 136 | if (empty($this->delimiter)) |
| | 137 | $this->get_hierarchy_delimiter(); |
| | 138 | } |
| | 139 | // write error log |
| | 140 | else if ($this->conn->error) { |
| | 141 | $this->error_code = $this->conn->errornum; |
| | 142 | raise_error(array('code' => 403, 'type' => 'imap', |
| | 143 | 'file' => __FILE__, 'line' => __LINE__, |
| | 144 | 'message' => $this->conn->error), true, false); |
| | 145 | } |
| | 146 | |
| | 147 | return $this->conn ? true : false; |
| | 148 | } |
| | 149 | |
| | 150 | |
| | 151 | /** |
| | 152 | * Close IMAP connection |
| | 153 | * Usually done on script shutdown |
| | 154 | * |
| | 155 | * @access public |
| | 156 | */ |
| | 157 | function close() |
| 169 | | if ($this->conn) |
| 170 | | iil_Close($this->conn); |
| 171 | | } |
| 172 | | |
| 173 | | |
| 174 | | /** |
| 175 | | * Close IMAP connection and re-connect |
| 176 | | * This is used to avoid some strange socket errors when talking to Courier IMAP |
| 177 | | * |
| 178 | | * @access public |
| 179 | | */ |
| 180 | | function reconnect() |
| 181 | | { |
| 182 | | $this->close(); |
| 183 | | $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); |
| 184 | | |
| 185 | | // issue SELECT command to restore connection status |
| 186 | | if ($this->mailbox) |
| 187 | | iil_C_Select($this->conn, $this->mailbox); |
| 188 | | } |
| 189 | | |
| 190 | | /** |
| 191 | | * Set options to be used in iil_Connect() |
| 192 | | */ |
| 193 | | function set_options($opt) |
| 194 | | { |
| 195 | | $this->options = array_merge($this->options, (array)$opt); |
| 196 | | } |
| 197 | | |
| 198 | | /** |
| 199 | | * Set a root folder for the IMAP connection. |
| 200 | | * |
| 201 | | * Only folders within this root folder will be displayed |
| 202 | | * and all folder paths will be translated using this folder name |
| 203 | | * |
| 204 | | * @param string Root folder |
| 205 | | * @access public |
| 206 | | */ |
| 207 | | function set_rootdir($root) |
| 208 | | { |
| 209 | | if (preg_match('/[.\/]$/', $root)) //(substr($root, -1, 1)==='/') |
| 210 | | $root = substr($root, 0, -1); |
| 211 | | |
| 212 | | $this->root_dir = $root; |
| 213 | | $this->options['rootdir'] = $root; |
| 214 | | |
| 215 | | if (empty($this->delimiter)) |
| 216 | | $this->get_hierarchy_delimiter(); |
| 217 | | } |
| 218 | | |
| 219 | | |
| 220 | | /** |
| 221 | | * Set default message charset |
| 222 | | * |
| 223 | | * This will be used for message decoding if a charset specification is not available |
| 224 | | * |
| 225 | | * @param string Charset string |
| 226 | | * @access public |
| 227 | | */ |
| 228 | | function set_charset($cs) |
| 229 | | { |
| 230 | | $this->default_charset = $cs; |
| 231 | | } |
| 232 | | |
| 233 | | |
| 234 | | /** |
| 235 | | * This list of folders will be listed above all other folders |
| 236 | | * |
| 237 | | * @param array Indexed list of folder names |
| 238 | | * @access public |
| 239 | | */ |
| 240 | | function set_default_mailboxes($arr) |
| 241 | | { |
| 242 | | if (is_array($arr)) |
| 243 | | { |
| 244 | | $this->default_folders = $arr; |
| 245 | | |
| 246 | | // add inbox if not included |
| 247 | | if (!in_array('INBOX', $this->default_folders)) |
| 248 | | array_unshift($this->default_folders, 'INBOX'); |
| 249 | | } |
| 250 | | } |
| 251 | | |
| 252 | | |
| 253 | | /** |
| 254 | | * Set internal mailbox reference. |
| 255 | | * |
| 256 | | * All operations will be perfomed on this mailbox/folder |
| 257 | | * |
| 258 | | * @param string Mailbox/Folder name |
| 259 | | * @access public |
| 260 | | */ |
| 261 | | function set_mailbox($new_mbox) |
| 262 | | { |
| 263 | | $mailbox = $this->mod_mailbox($new_mbox); |
| 264 | | |
| 265 | | if ($this->mailbox == $mailbox) |
| 266 | | return; |
| 267 | | |
| 268 | | $this->mailbox = $mailbox; |
| 269 | | |
| 270 | | // clear messagecount cache for this mailbox |
| 271 | | $this->_clear_messagecount($mailbox); |
| 272 | | } |
| 273 | | |
| 274 | | |
| 275 | | /** |
| 276 | | * Set internal list page |
| 277 | | * |
| 278 | | * @param number Page number to list |
| 279 | | * @access public |
| 280 | | */ |
| 281 | | function set_page($page) |
| 282 | | { |
| 283 | | $this->list_page = (int)$page; |
| 284 | | } |
| 285 | | |
| 286 | | |
| 287 | | /** |
| 288 | | * Set internal page size |
| 289 | | * |
| 290 | | * @param number Number of messages to display on one page |
| 291 | | * @access public |
| 292 | | */ |
| 293 | | function set_pagesize($size) |
| 294 | | { |
| 295 | | $this->page_size = (int)$size; |
| 296 | | } |
| 297 | | |
| 298 | | |
| 299 | | /** |
| 300 | | * Save a set of message ids for future message listing methods |
| 301 | | * |
| 302 | | * @param string IMAP Search query |
| 303 | | * @param array List of message ids or NULL if empty |
| 304 | | * @param string Charset of search string |
| 305 | | * @param string Sorting field |
| 306 | | */ |
| 307 | | function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false) |
| 308 | | { |
| 309 | | if (is_array($str) && $msgs == null) |
| 310 | | list($str, $msgs, $charset, $sort_field, $threads) = $str; |
| 311 | | if ($msgs != null && !is_array($msgs)) |
| 312 | | $msgs = explode(',', $msgs); |
| 313 | | |
| 314 | | $this->search_string = $str; |
| 315 | | $this->search_set = $msgs; |
| 316 | | $this->search_charset = $charset; |
| 317 | | $this->search_sort_field = $sort_field; |
| 318 | | $this->search_threads = $threads; |
| 319 | | } |
| 320 | | |
| 321 | | |
| 322 | | /** |
| 323 | | * Return the saved search set as hash array |
| 324 | | * @return array Search set |
| 325 | | */ |
| 326 | | function get_search_set() |
| 327 | | { |
| 328 | | return array($this->search_string, |
| 329 | | $this->search_set, |
| 330 | | $this->search_charset, |
| 331 | | $this->search_sort_field, |
| 332 | | $this->search_threads, |
| 333 | | ); |
| 334 | | } |
| 335 | | |
| 336 | | |
| 337 | | /** |
| 338 | | * Returns the currently used mailbox name |
| 339 | | * |
| 340 | | * @return string Name of the mailbox/folder |
| 341 | | * @access public |
| 342 | | */ |
| 343 | | function get_mailbox_name() |
| 344 | | { |
| 345 | | return $this->conn ? $this->mod_mailbox($this->mailbox, 'out') : ''; |
| 346 | | } |
| 347 | | |
| 348 | | |
| 349 | | /** |
| 350 | | * Returns the IMAP server's capability |
| 351 | | * |
| 352 | | * @param string Capability name |
| 353 | | * @return mixed Capability value or TRUE if supported, FALSE if not |
| 354 | | * @access public |
| 355 | | */ |
| 356 | | function get_capability($cap) |
| 357 | | { |
| 358 | | return iil_C_GetCapability($this->conn, strtoupper($cap)); |
| 359 | | } |
| 360 | | |
| 361 | | |
| 362 | | /** |
| 363 | | * Sets threading flag to the best supported THREAD algorithm |
| 364 | | * |
| 365 | | * @param boolean TRUE to enable and FALSE |
| 366 | | * @return string Algorithm or false if THREAD is not supported |
| 367 | | * @access public |
| 368 | | */ |
| 369 | | function set_threading($enable=false) |
| 370 | | { |
| 371 | | $this->threading = false; |
| 372 | | |
| 373 | | if ($enable) { |
| 374 | | if ($this->get_capability('THREAD=REFS')) |
| 375 | | $this->threading = 'REFS'; |
| 376 | | else if ($this->get_capability('THREAD=REFERENCES')) |
| 377 | | $this->threading = 'REFERENCES'; |
| 378 | | else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) |
| 379 | | $this->threading = 'ORDEREDSUBJECT'; |
| 380 | | } |
| | 159 | if ($this->conn && $this->conn->connected()) |
| | 160 | $this->conn->close(); |
| | 161 | $this->write_cache(); |
| | 162 | } |
| | 163 | |
| | 164 | |
| | 165 | /** |
| | 166 | * Close IMAP connection and re-connect |
| | 167 | * This is used to avoid some strange socket errors when talking to Courier IMAP |
| | 168 | * |
| | 169 | * @access public |
| | 170 | */ |
| | 171 | function reconnect() |
| | 172 | { |
| | 173 | $this->close(); |
| | 174 | $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); |
| | 175 | |
| | 176 | // issue SELECT command to restore connection status |
| | 177 | if ($this->mailbox) |
| | 178 | $this->conn->select($this->mailbox); |
| | 179 | } |
| | 180 | |
| | 181 | /** |
| | 182 | * Set options to be used in rcube_imap_generic::connect() |
| | 183 | */ |
| | 184 | function set_options($opt) |
| | 185 | { |
| | 186 | $this->options = array_merge($this->options, (array)$opt); |
| | 187 | } |
| | 188 | |
| | 189 | /** |
| | 190 | * Set a root folder for the IMAP connection. |
| | 191 | * |
| | 192 | * Only folders within this root folder will be displayed |
| | 193 | * and all folder paths will be translated using this folder name |
| | 194 | * |
| | 195 | * @param string Root folder |
| | 196 | * @access public |
| | 197 | */ |
| | 198 | function set_rootdir($root) |
| | 199 | { |
| | 200 | if (preg_match('/[.\/]$/', $root)) //(substr($root, -1, 1)==='/') |
| | 201 | $root = substr($root, 0, -1); |
| | 202 | |
| | 203 | $this->root_dir = $root; |
| | 204 | $this->options['rootdir'] = $root; |
| | 205 | |
| | 206 | if (empty($this->delimiter)) |
| | 207 | $this->get_hierarchy_delimiter(); |
| | 208 | } |
| | 209 | |
| | 210 | |
| | 211 | /** |
| | 212 | * Set default message charset |
| | 213 | * |
| | 214 | * This will be used for message decoding if a charset specification is not available |
| | 215 | * |
| | 216 | * @param string Charset string |
| | 217 | * @access public |
| | 218 | */ |
| | 219 | function set_charset($cs) |
| | 220 | { |
| | 221 | $this->default_charset = $cs; |
| | 222 | } |
| | 223 | |
| | 224 | |
| | 225 | /** |
| | 226 | * This list of folders will be listed above all other folders |
| | 227 | * |
| | 228 | * @param array Indexed list of folder names |
| | 229 | * @access public |
| | 230 | */ |
| | 231 | function set_default_mailboxes($arr) |
| | 232 | { |
| | 233 | if (is_array($arr)) { |
| | 234 | $this->default_folders = $arr; |
| | 235 | |
| | 236 | // add inbox if not included |
| | 237 | if (!in_array('INBOX', $this->default_folders)) |
| | 238 | array_unshift($this->default_folders, 'INBOX'); |
| | 239 | } |
| | 240 | } |
| | 241 | |
| | 242 | |
| | 243 | /** |
| | 244 | * Set internal mailbox reference. |
| | 245 | * |
| | 246 | * All operations will be perfomed on this mailbox/folder |
| | 247 | * |
| | 248 | * @param string Mailbox/Folder name |
| | 249 | * @access public |
| | 250 | */ |
| | 251 | function set_mailbox($new_mbox) |
| | 252 | { |
| | 253 | $mailbox = $this->mod_mailbox($new_mbox); |
| | 254 | |
| | 255 | if ($this->mailbox == $mailbox) |
| | 256 | return; |
| | 257 | |
| | 258 | $this->mailbox = $mailbox; |
| | 259 | |
| | 260 | // clear messagecount cache for this mailbox |
| | 261 | $this->_clear_messagecount($mailbox); |
| | 262 | } |
| | 263 | |
| | 264 | |
| | 265 | /** |
| | 266 | * Set internal list page |
| | 267 | * |
| | 268 | * @param number Page number to list |
| | 269 | * @access public |
| | 270 | */ |
| | 271 | function set_page($page) |
| | 272 | { |
| | 273 | $this->list_page = (int)$page; |
| | 274 | } |
| | 275 | |
| | 276 | |
| | 277 | /** |
| | 278 | * Set internal page size |
| | 279 | * |
| | 280 | * @param number Number of messages to display on one page |
| | 281 | * @access public |
| | 282 | */ |
| | 283 | function set_pagesize($size) |
| | 284 | { |
| | 285 | $this->page_size = (int)$size; |
| | 286 | } |
| | 287 | |
| | 288 | |
| | 289 | /** |
| | 290 | * Save a set of message ids for future message listing methods |
| | 291 | * |
| | 292 | * @param string IMAP Search query |
| | 293 | * @param array List of message ids or NULL if empty |
| | 294 | * @param string Charset of search string |
| | 295 | * @param string Sorting field |
| | 296 | */ |
| | 297 | function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false) |
| | 298 | { |
| | 299 | if (is_array($str) && $msgs == null) |
| | 300 | list($str, $msgs, $charset, $sort_field, $threads) = $str; |
| | 301 | if ($msgs != null && !is_array($msgs)) |
| | 302 | $msgs = explode(',', $msgs); |
| | 303 | |
| | 304 | $this->search_string = $str; |
| | 305 | $this->search_set = $msgs; |
| | 306 | $this->search_charset = $charset; |
| | 307 | $this->search_sort_field = $sort_field; |
| | 308 | $this->search_threads = $threads; |
| | 309 | } |
| | 310 | |
| | 311 | |
| | 312 | /** |
| | 313 | * Return the saved search set as hash array |
| | 314 | * @return array Search set |
| | 315 | */ |
| | 316 | function get_search_set() |
| | 317 | { |
| | 318 | return array($this->search_string, |
| | 319 | $this->search_set, |
| | 320 | $this->search_charset, |
| | 321 | $this->search_sort_field, |
| | 322 | $this->search_threads, |
| | 323 | ); |
| | 324 | } |
| | 325 | |
| | 326 | |
| | 327 | /** |
| | 328 | * Returns the currently used mailbox name |
| | 329 | * |
| | 330 | * @return string Name of the mailbox/folder |
| | 331 | * @access public |
| | 332 | */ |
| | 333 | function get_mailbox_name() |
| | 334 | { |
| | 335 | return $this->conn->connected() ? $this->mod_mailbox($this->mailbox, 'out') : ''; |
| | 336 | } |
| | 337 | |
| | 338 | |
| | 339 | /** |
| | 340 | * Returns the IMAP server's capability |
| | 341 | * |
| | 342 | * @param string Capability name |
| | 343 | * @return mixed Capability value or TRUE if supported, FALSE if not |
| | 344 | * @access public |
| | 345 | */ |
| | 346 | function get_capability($cap) |
| | 347 | { |
| | 348 | return $this->conn->getCapability(strtoupper($cap)); |
| | 349 | } |
| | 350 | |
| | 351 | |
| | 352 | /** |
| | 353 | * Sets threading flag to the best supported THREAD algorithm |
| | 354 | * |
| | 355 | * @param boolean TRUE to enable and FALSE |
| | 356 | * @return string Algorithm or false if THREAD is not supported |
| | 357 | * @access public |
| | 358 | */ |
| | 359 | function set_threading($enable=false) |
| | 360 | { |
| | 361 | $this->threading = false; |
| | 362 | |
| | 363 | if ($enable) { |
| | 364 | if ($this->get_capability('THREAD=REFS')) |
| | 365 | $this->threading = 'REFS'; |
| | 366 | else if ($this->get_capability('THREAD=REFERENCES')) |
| | 367 | $this->threading = 'REFERENCES'; |
| | 368 | else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) |
| | 369 | $this->threading = 'ORDEREDSUBJECT'; |
| | 370 | } |
| | 371 | |
| | 372 | return $this->threading; |
| | 373 | } |
| | 374 | |
| | 375 | |
| | 376 | /** |
| | 377 | * Checks the PERMANENTFLAGS capability of the current mailbox |
| | 378 | * and returns true if the given flag is supported by the IMAP server |
| | 379 | * |
| | 380 | * @param string Permanentflag name |
| | 381 | * @return mixed True if this flag is supported |
| | 382 | * @access public |
| | 383 | */ |
| | 384 | function check_permflag($flag) |
| | 385 | { |
| | 386 | $flag = strtoupper($flag); |
| | 387 | $imap_flag = $this->conn->flags[$flag]; |
| | 388 | return (in_array_nocase($imap_flag, $this->conn->permanentflags)); |
| | 389 | } |
| | 390 | |
| | 391 | |
| | 392 | /** |
| | 393 | * Returns the delimiter that is used by the IMAP server for folder separation |
| | 394 | * |
| | 395 | * @return string Delimiter string |
| | 396 | * @access public |
| | 397 | */ |
| | 398 | function get_hierarchy_delimiter() |
| | 399 | { |
| | 400 | if ($this->conn && empty($this->delimiter)) |
| | 401 | $this->delimiter = $this->conn->getHierarchyDelimiter(); |
| | 402 | |
| | 403 | if (empty($this->delimiter)) |
| | 404 | $this->delimiter = '/'; |
| | 405 | |
| | 406 | return $this->delimiter; |
| | 407 | } |
| | 408 | |
| | 409 | |
| | 410 | /** |
| | 411 | * Get message count for a specific mailbox |
| | 412 | * |
| | 413 | * @param string Mailbox/folder name |
| | 414 | * @param string Mode for count [ALL|THREADS|UNSEEN|RECENT] |
| | 415 | * @param boolean Force reading from server and update cache |
| | 416 | * @return int Number of messages |
| | 417 | * @access public |
| | 418 | */ |
| | 419 | function messagecount($mbox_name='', $mode='ALL', $force=false) |
| | 420 | { |
| | 421 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 422 | return $this->_messagecount($mailbox, $mode, $force); |
| | 423 | } |
| | 424 | |
| | 425 | |
| | 426 | /** |
| | 427 | * Private method for getting nr of messages |
| | 428 | * |
| | 429 | * @access private |
| | 430 | * @see rcube_imap::messagecount() |
| | 431 | */ |
| | 432 | private function _messagecount($mailbox='', $mode='ALL', $force=false) |
| | 433 | { |
| | 434 | $mode = strtoupper($mode); |
| | 435 | |
| | 436 | if (empty($mailbox)) |
| | 437 | $mailbox = $this->mailbox; |
| | 438 | |
| | 439 | // count search set |
| | 440 | if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) { |
| | 441 | if ($this->search_threads) |
| | 442 | return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']); |
| | 443 | else |
| | 444 | return count((array)$this->search_set); |
| | 445 | } |
| | 446 | |
| | 447 | $a_mailbox_cache = $this->get_cache('messagecount'); |
| | 448 | |
| | 449 | // return cached value |
| | 450 | if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) |
| | 451 | return $a_mailbox_cache[$mailbox][$mode]; |
| | 452 | |
| | 453 | if (!is_array($a_mailbox_cache[$mailbox])) |
| | 454 | $a_mailbox_cache[$mailbox] = array(); |
| | 455 | |
| | 456 | if ($mode == 'THREADS') { |
| | 457 | $count = $this->_threadcount($mailbox, $msg_count); |
| | 458 | $_SESSION['maxuid'][$mailbox] = $msg_count ? $this->_id2uid($msg_count) : 0; |
| | 459 | } |
| | 460 | // RECENT count is fetched a bit different |
| | 461 | else if ($mode == 'RECENT') { |
| | 462 | $count = $this->conn->checkForRecent($mailbox); |
| | 463 | } |
| | 464 | // use SEARCH for message counting |
| | 465 | else if ($this->skip_deleted) { |
| | 466 | $search_str = "ALL UNDELETED"; |
| | 467 | |
| | 468 | // get message count and store in cache |
| | 469 | if ($mode == 'UNSEEN') |
| | 470 | $search_str .= " UNSEEN"; |
| | 471 | |
| | 472 | // get message count using SEARCH |
| | 473 | // not very performant but more precise (using UNDELETED) |
| | 474 | // disable THREADS for this request |
| | 475 | $threads = $this->threading; |
| | 476 | $this->threading = false; |
| | 477 | $index = $this->_search_index($mailbox, $search_str); |
| | 478 | $this->threading = $threads; |
| 598 | | return count($thread_tree); |
| 599 | | } |
| 600 | | |
| 601 | | |
| 602 | | /** |
| 603 | | * Public method for listing headers |
| 604 | | * convert mailbox name with root dir first |
| 605 | | * |
| 606 | | * @param string Mailbox/folder name |
| 607 | | * @param int Current page to list |
| 608 | | * @param string Header field to sort by |
| 609 | | * @param string Sort order [ASC|DESC] |
| 610 | | * @param boolean Number of slice items to extract from result array |
| 611 | | * @return array Indexed array with message header objects |
| 612 | | * @access public |
| 613 | | */ |
| 614 | | function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| 615 | | { |
| 616 | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| 617 | | return $this->_list_headers($mailbox, $page, $sort_field, $sort_order, false, $slice); |
| 618 | | } |
| 619 | | |
| 620 | | |
| 621 | | /** |
| 622 | | * Private method for listing message headers |
| 623 | | * |
| 624 | | * @access private |
| 625 | | * @see rcube_imap::list_headers |
| 626 | | */ |
| 627 | | private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=false, $slice=0) |
| 628 | | { |
| 629 | | if (!strlen($mailbox)) |
| 630 | | return array(); |
| 631 | | |
| 632 | | // use saved message set |
| 633 | | if ($this->search_string && $mailbox == $this->mailbox) |
| 634 | | return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); |
| 635 | | |
| 636 | | if ($this->threading) |
| 637 | | return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); |
| 638 | | |
| 639 | | $this->_set_sort_order($sort_field, $sort_order); |
| 640 | | |
| 641 | | $page = $page ? $page : $this->list_page; |
| 642 | | $cache_key = $mailbox.'.msg'; |
| 643 | | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| 644 | | |
| 645 | | // cache is OK, we can get all messages from local cache |
| 646 | | if ($cache_status>0) |
| 647 | | { |
| 648 | | $start_msg = ($page-1) * $this->page_size; |
| 649 | | $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order); |
| 650 | | $result = array_values($a_msg_headers); |
| 651 | | if ($slice) |
| 652 | | $result = array_slice($result, -$slice, $slice); |
| 653 | | return $result; |
| 654 | | } |
| 655 | | // cache is dirty, sync it |
| 656 | | else if ($this->caching_enabled && $cache_status==-1 && !$recursive) |
| 657 | | { |
| 658 | | $this->sync_header_index($mailbox); |
| 659 | | return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); |
| 660 | | } |
| 661 | | |
| 662 | | // retrieve headers from IMAP |
| 663 | | $a_msg_headers = array(); |
| 664 | | |
| 665 | | // use message index sort as default sorting (for better performance) |
| 666 | | if (!$this->sort_field) |
| 667 | | { |
| 668 | | if ($this->skip_deleted) { |
| 669 | | // @TODO: this could be cached |
| 670 | | if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { |
| | 519 | return count($thread_tree); |
| | 520 | } |
| | 521 | |
| | 522 | |
| | 523 | /** |
| | 524 | * Public method for listing headers |
| | 525 | * convert mailbox name with root dir first |
| | 526 | * |
| | 527 | * @param string Mailbox/folder name |
| | 528 | * @param int Current page to list |
| | 529 | * @param string Header field to sort by |
| | 530 | * @param string Sort order [ASC|DESC] |
| | 531 | * @param boolean Number of slice items to extract from result array |
| | 532 | * @return array Indexed array with message header objects |
| | 533 | * @access public |
| | 534 | */ |
| | 535 | function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| | 536 | { |
| | 537 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 538 | return $this->_list_headers($mailbox, $page, $sort_field, $sort_order, false, $slice); |
| | 539 | } |
| | 540 | |
| | 541 | |
| | 542 | /** |
| | 543 | * Private method for listing message headers |
| | 544 | * |
| | 545 | * @access private |
| | 546 | * @see rcube_imap::list_headers |
| | 547 | */ |
| | 548 | private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=false, $slice=0) |
| | 549 | { |
| | 550 | if (!strlen($mailbox)) |
| | 551 | return array(); |
| | 552 | |
| | 553 | // use saved message set |
| | 554 | if ($this->search_string && $mailbox == $this->mailbox) |
| | 555 | return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); |
| | 556 | |
| | 557 | if ($this->threading) |
| | 558 | return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); |
| | 559 | |
| | 560 | $this->_set_sort_order($sort_field, $sort_order); |
| | 561 | |
| | 562 | $page = $page ? $page : $this->list_page; |
| | 563 | $cache_key = $mailbox.'.msg'; |
| | 564 | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| | 565 | |
| | 566 | // cache is OK, we can get all messages from local cache |
| | 567 | if ($cache_status>0) { |
| | 568 | $start_msg = ($page-1) * $this->page_size; |
| | 569 | $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, |
| | 570 | $start_msg+$this->page_size, $this->sort_field, $this->sort_order); |
| | 571 | $result = array_values($a_msg_headers); |
| | 572 | if ($slice) |
| | 573 | $result = array_slice($result, -$slice, $slice); |
| | 574 | return $result; |
| | 575 | } |
| | 576 | // cache is dirty, sync it |
| | 577 | else if ($this->caching_enabled && $cache_status==-1 && !$recursive) { |
| | 578 | $this->sync_header_index($mailbox); |
| | 579 | return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); |
| | 580 | } |
| | 581 | |
| | 582 | // retrieve headers from IMAP |
| | 583 | $a_msg_headers = array(); |
| | 584 | |
| | 585 | // use message index sort as default sorting (for better performance) |
| | 586 | if (!$this->sort_field) { |
| | 587 | if ($this->skip_deleted) { |
| | 588 | // @TODO: this could be cached |
| | 589 | if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { |
| | 590 | $max = max($msg_index); |
| | 591 | list($begin, $end) = $this->_get_message_range(count($msg_index), $page); |
| | 592 | $msg_index = array_slice($msg_index, $begin, $end-$begin); |
| | 593 | } |
| | 594 | } |
| | 595 | else if ($max = $this->conn->countMessages($mailbox)) { |
| | 596 | list($begin, $end) = $this->_get_message_range($max, $page); |
| | 597 | $msg_index = range($begin+1, $end); |
| | 598 | } |
| | 599 | else |
| | 600 | $msg_index = array(); |
| | 601 | |
| | 602 | if ($slice) |
| | 603 | $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); |
| | 604 | |
| | 605 | // fetch reqested headers from server |
| | 606 | if ($msg_index) |
| | 607 | $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); |
| | 608 | } |
| | 609 | // use SORT command |
| | 610 | else if ($this->get_capability('SORT')) { |
| | 611 | if ($msg_index = $this->conn->sort($mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { |
| | 612 | list($begin, $end) = $this->_get_message_range(count($msg_index), $page); |
| | 613 | $max = max($msg_index); |
| | 614 | $msg_index = array_slice($msg_index, $begin, $end-$begin); |
| | 615 | |
| | 616 | if ($slice) |
| | 617 | $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); |
| | 618 | |
| | 619 | // fetch reqested headers from server |
| | 620 | $this->_fetch_headers($mailbox, join(',', $msg_index), $a_msg_headers, $cache_key); |
| | 621 | } |
| | 622 | } |
| | 623 | // fetch specified header for all messages and sort |
| | 624 | else if ($a_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { |
| | 625 | asort($a_index); // ASC |
| | 626 | $msg_index = array_keys($a_index); |
| 785 | | // add to internal (fast) cache |
| 786 | | $this->icache['threads'] = array(); |
| 787 | | $this->icache['threads']['tree'] = $thread_tree; |
| 788 | | $this->icache['threads']['depth'] = $msg_depth; |
| 789 | | $this->icache['threads']['has_children'] = $has_children; |
| 790 | | } |
| 791 | | |
| 792 | | return array( |
| 793 | | $this->icache['threads']['tree'], |
| 794 | | $this->icache['threads']['depth'], |
| 795 | | $this->icache['threads']['has_children'], |
| 796 | | ); |
| 797 | | } |
| 798 | | |
| 799 | | |
| 800 | | /** |
| 801 | | * Private method for fetching threaded messages headers |
| 802 | | * |
| 803 | | * @access private |
| 804 | | */ |
| 805 | | private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) |
| 806 | | { |
| 807 | | $cache_key = $mailbox.'.msg'; |
| 808 | | // now get IDs for current page |
| 809 | | list($begin, $end) = $this->_get_message_range(count($msg_index), $page); |
| 810 | | $msg_index = array_slice($msg_index, $begin, $end-$begin); |
| 811 | | |
| 812 | | if ($slice) |
| 813 | | $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); |
| 814 | | |
| 815 | | if ($this->sort_order == 'DESC') |
| 816 | | $msg_index = array_reverse($msg_index); |
| 817 | | |
| 818 | | // flatten threads array |
| 819 | | // @TODO: fetch children only in expanded mode |
| 820 | | $all_ids = array(); |
| 821 | | foreach($msg_index as $root) { |
| 822 | | $all_ids[] = $root; |
| 823 | | if (!empty($thread_tree[$root])) |
| 824 | | $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); |
| 825 | | } |
| 826 | | |
| 827 | | // fetch reqested headers from server |
| 828 | | $this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key); |
| 829 | | |
| 830 | | // return empty array if no messages found |
| 831 | | if (!is_array($a_msg_headers) || empty($a_msg_headers)) |
| 832 | | return array(); |
| 833 | | |
| 834 | | // use this class for message sorting |
| 835 | | $sorter = new rcube_header_sorter(); |
| 836 | | $sorter->set_sequence_numbers($all_ids); |
| 837 | | $sorter->sort_headers($a_msg_headers); |
| 838 | | |
| 839 | | // Set depth, has_children and unread_children fields in headers |
| 840 | | $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children); |
| 841 | | |
| 842 | | return array_values($a_msg_headers); |
| 843 | | } |
| 844 | | |
| 845 | | |
| 846 | | /** |
| 847 | | * Private method for setting threaded messages flags: |
| 848 | | * depth, has_children and unread_children |
| 849 | | * |
| 850 | | * @param array Reference to headers array indexed by message ID |
| 851 | | * @param array Array of messages depth indexed by message ID |
| 852 | | * @param array Array of messages children flags indexed by message ID |
| 853 | | * @return array Message headers array indexed by message ID |
| 854 | | * @access private |
| 855 | | */ |
| 856 | | private function _set_thread_flags(&$headers, $msg_depth, $msg_children) |
| 857 | | { |
| 858 | | $parents = array(); |
| 859 | | |
| 860 | | foreach ($headers as $idx => $header) { |
| 861 | | $id = $header->id; |
| 862 | | $depth = $msg_depth[$id]; |
| 863 | | $parents = array_slice($parents, 0, $depth); |
| 864 | | |
| 865 | | if (!empty($parents)) { |
| 866 | | $headers[$idx]->parent_uid = end($parents); |
| 867 | | if (!$header->seen) |
| 868 | | $headers[$parents[0]]->unread_children++; |
| 869 | | } |
| 870 | | array_push($parents, $header->uid); |
| 871 | | |
| 872 | | $headers[$idx]->depth = $depth; |
| 873 | | $headers[$idx]->has_children = $msg_children[$id]; |
| 874 | | } |
| 875 | | } |
| 876 | | |
| 877 | | |
| 878 | | /** |
| 879 | | * Private method for listing a set of message headers (search results) |
| 880 | | * |
| 881 | | * @param string Mailbox/folder name |
| 882 | | * @param int Current page to list |
| 883 | | * @param string Header field to sort by |
| 884 | | * @param string Sort order [ASC|DESC] |
| 885 | | * @param boolean Number of slice items to extract from result array |
| 886 | | * @return array Indexed array with message header objects |
| 887 | | * @access private |
| 888 | | * @see rcube_imap::list_header_set() |
| 889 | | */ |
| 890 | | private function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| 891 | | { |
| 892 | | if (!strlen($mailbox) || empty($this->search_set)) |
| 893 | | return array(); |
| 894 | | |
| 895 | | // use saved messages from searching |
| 896 | | if ($this->threading) |
| 897 | | return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); |
| 898 | | |
| 899 | | // search set is threaded, we need a new one |
| 900 | | if ($this->search_threads) |
| 901 | | $this->search('', $this->search_string, $this->search_charset, $sort_field); |
| 902 | | |
| 903 | | $msgs = $this->search_set; |
| 904 | | $a_msg_headers = array(); |
| 905 | | $page = $page ? $page : $this->list_page; |
| 906 | | $start_msg = ($page-1) * $this->page_size; |
| 907 | | |
| 908 | | $this->_set_sort_order($sort_field, $sort_order); |
| 909 | | |
| 910 | | // quickest method (default sorting) |
| 911 | | if (!$this->search_sort_field && !$this->sort_field) |
| 912 | | { |
| 913 | | if ($sort_order == 'DESC') |
| 914 | | $msgs = array_reverse($msgs); |
| 915 | | |
| 916 | | // get messages uids for one page |
| 917 | | $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); |
| 918 | | |
| 919 | | if ($slice) |
| 920 | | $msgs = array_slice($msgs, -$slice, $slice); |
| 921 | | |
| 922 | | // fetch headers |
| 923 | | $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); |
| 924 | | |
| 925 | | // I didn't found in RFC that FETCH always returns messages sorted by index |
| 926 | | $sorter = new rcube_header_sorter(); |
| 927 | | $sorter->set_sequence_numbers($msgs); |
| 928 | | $sorter->sort_headers($a_msg_headers); |
| 929 | | |
| 930 | | return array_values($a_msg_headers); |
| 931 | | } |
| 932 | | |
| 933 | | // sorted messages, so we can first slice array and then fetch only wanted headers |
| 934 | | if ($this->get_capability('SORT')) // SORT searching result |
| 935 | | { |
| 936 | | // reset search set if sorting field has been changed |
| 937 | | if ($this->sort_field && $this->search_sort_field != $this->sort_field) |
| 938 | | $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field); |
| 939 | | |
| 940 | | // return empty array if no messages found |
| 941 | | if (empty($msgs)) |
| 942 | | return array(); |
| 943 | | |
| 944 | | if ($sort_order == 'DESC') |
| 945 | | $msgs = array_reverse($msgs); |
| 946 | | |
| 947 | | // get messages uids for one page |
| 948 | | $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); |
| 949 | | |
| 950 | | if ($slice) |
| 951 | | $msgs = array_slice($msgs, -$slice, $slice); |
| 952 | | |
| 953 | | // fetch headers |
| 954 | | $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); |
| 955 | | |
| 956 | | $sorter = new rcube_header_sorter(); |
| 957 | | $sorter->set_sequence_numbers($msgs); |
| 958 | | $sorter->sort_headers($a_msg_headers); |
| 959 | | |
| 960 | | return array_values($a_msg_headers); |
| 961 | | } |
| 962 | | else { // SEARCH result, need sorting |
| 963 | | $cnt = count($msgs); |
| 964 | | // 300: experimantal value for best result |
| 965 | | if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { |
| 966 | | // use memory less expensive (and quick) method for big result set |
| 967 | | $a_index = $this->message_index('', $this->sort_field, $this->sort_order); |
| 968 | | // get messages uids for one page... |
| 969 | | $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); |
| | 702 | // add to internal (fast) cache |
| | 703 | $this->icache['threads'] = array(); |
| | 704 | $this->icache['threads']['tree'] = $thread_tree; |
| | 705 | $this->icache['threads']['depth'] = $msg_depth; |
| | 706 | $this->icache['threads']['has_children'] = $has_children; |
| | 707 | } |
| | 708 | |
| | 709 | return array( |
| | 710 | $this->icache['threads']['tree'], |
| | 711 | $this->icache['threads']['depth'], |
| | 712 | $this->icache['threads']['has_children'], |
| | 713 | ); |
| | 714 | } |
| | 715 | |
| | 716 | |
| | 717 | /** |
| | 718 | * Private method for fetching threaded messages headers |
| | 719 | * |
| | 720 | * @access private |
| | 721 | */ |
| | 722 | private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) |
| | 723 | { |
| | 724 | $cache_key = $mailbox.'.msg'; |
| | 725 | // now get IDs for current page |
| | 726 | list($begin, $end) = $this->_get_message_range(count($msg_index), $page); |
| | 727 | $msg_index = array_slice($msg_index, $begin, $end-$begin); |
| | 728 | |
| 984 | | } |
| 985 | | else { |
| 986 | | // for small result set we can fetch all messages headers |
| 987 | | $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); |
| 988 | | |
| 989 | | // return empty array if no messages found |
| 990 | | if (!is_array($a_msg_headers) || empty($a_msg_headers)) |
| 991 | | return array(); |
| 992 | | |
| 993 | | // if not already sorted |
| 994 | | $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); |
| 995 | | |
| 996 | | // only return the requested part of the set |
| 997 | | $a_msg_headers = array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size)); |
| 998 | | if ($slice) |
| 999 | | $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); |
| 1000 | | |
| 1001 | | return $a_msg_headers; |
| 1002 | | } |
| 1003 | | } |
| 1004 | | } |
| 1005 | | |
| 1006 | | |
| 1007 | | /** |
| 1008 | | * Private method for listing a set of threaded message headers (search results) |
| 1009 | | * |
| 1010 | | * @param string Mailbox/folder name |
| 1011 | | * @param int Current page to list |
| 1012 | | * @param string Header field to sort by |
| 1013 | | * @param string Sort order [ASC|DESC] |
| 1014 | | * @param boolean Number of slice items to extract from result array |
| 1015 | | * @return array Indexed array with message header objects |
| 1016 | | * @access private |
| 1017 | | * @see rcube_imap::list_header_set() |
| 1018 | | */ |
| 1019 | | private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| 1020 | | { |
| 1021 | | // update search_set if previous data was fetched with disabled threading |
| 1022 | | if (!$this->search_threads) |
| 1023 | | $this->search('', $this->search_string, $this->search_charset, $sort_field); |
| 1024 | | |
| 1025 | | $thread_tree = $this->search_set['tree']; |
| 1026 | | $msg_depth = $this->search_set['depth']; |
| 1027 | | $has_children = $this->search_set['children']; |
| 1028 | | $a_msg_headers = array(); |
| 1029 | | |
| 1030 | | $page = $page ? $page : $this->list_page; |
| 1031 | | $start_msg = ($page-1) * $this->page_size; |
| 1032 | | |
| 1033 | | $this->_set_sort_order($sort_field, $sort_order); |
| 1034 | | |
| 1035 | | $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); |
| 1036 | | |
| 1037 | | return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); |
| 1038 | | } |
| 1039 | | |
| 1040 | | |
| 1041 | | /** |
| 1042 | | * Helper function to get first and last index of the requested set |
| 1043 | | * |
| 1044 | | * @param int message count |
| 1045 | | * @param mixed page number to show, or string 'all' |
| 1046 | | * @return array array with two values: first index, last index |
| 1047 | | * @access private |
| 1048 | | */ |
| 1049 | | private function _get_message_range($max, $page) |
| 1050 | | { |
| 1051 | | $start_msg = ($page-1) * $this->page_size; |
| 1052 | | |
| 1053 | | if ($page=='all') |
| 1054 | | { |
| 1055 | | $begin = 0; |
| 1056 | | $end = $max; |
| 1057 | | } |
| 1058 | | else if ($this->sort_order=='DESC') |
| 1059 | | { |
| 1060 | | $begin = $max - $this->page_size - $start_msg; |
| 1061 | | $end = $max - $start_msg; |
| 1062 | | } |
| 1063 | | else |
| 1064 | | { |
| 1065 | | $begin = $start_msg; |
| 1066 | | $end = $start_msg + $this->page_size; |
| 1067 | | } |
| 1068 | | |
| 1069 | | if ($begin < 0) $begin = 0; |
| 1070 | | if ($end < 0) $end = $max; |
| 1071 | | if ($end > $max) $end = $max; |
| 1072 | | |
| 1073 | | return array($begin, $end); |
| 1074 | | } |
| 1075 | | |
| 1076 | | |
| 1077 | | /** |
| 1078 | | * Fetches message headers |
| 1079 | | * Used for loop |
| 1080 | | * |
| 1081 | | * @param string Mailbox name |
| 1082 | | * @param string Message index to fetch |
| 1083 | | * @param array Reference to message headers array |
| 1084 | | * @param array Array with cache index |
| 1085 | | * @return int Messages count |
| 1086 | | * @access private |
| 1087 | | */ |
| 1088 | | private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) |
| 1089 | | { |
| 1090 | | // fetch reqested headers from server |
| 1091 | | $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs, false, false, $this->fetch_add_headers); |
| 1092 | | |
| 1093 | | if (empty($a_header_index)) |
| 1094 | | return 0; |
| 1095 | | |
| 1096 | | // cache is incomplete |
| 1097 | | $cache_index = $this->get_message_cache_index($cache_key); |
| 1098 | | |
| 1099 | | foreach ($a_header_index as $i => $headers) { |
| 1100 | | if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) { |
| 1101 | | // prevent index duplicates |
| 1102 | | if ($cache_index[$headers->id]) { |
| 1103 | | $this->remove_message_cache($cache_key, $headers->id, true); |
| 1104 | | unset($cache_index[$headers->id]); |
| | 760 | } |
| | 761 | |
| | 762 | |
| | 763 | /** |
| | 764 | * Private method for setting threaded messages flags: |
| | 765 | * depth, has_children and unread_children |
| | 766 | * |
| | 767 | * @param array Reference to headers array indexed by message ID |
| | 768 | * @param array Array of messages depth indexed by message ID |
| | 769 | * @param array Array of messages children flags indexed by message ID |
| | 770 | * @return array Message headers array indexed by message ID |
| | 771 | * @access private |
| | 772 | */ |
| | 773 | private function _set_thread_flags(&$headers, $msg_depth, $msg_children) |
| | 774 | { |
| | 775 | $parents = array(); |
| | 776 | |
| | 777 | foreach ($headers as $idx => $header) { |
| | 778 | $id = $header->id; |
| | 779 | $depth = $msg_depth[$id]; |
| | 780 | $parents = array_slice($parents, 0, $depth); |
| | 781 | |
| | 782 | if (!empty($parents)) { |
| | 783 | $headers[$idx]->parent_uid = end($parents); |
| | 784 | if (!$header->seen) |
| | 785 | $headers[$parents[0]]->unread_children++; |
| | 786 | } |
| | 787 | array_push($parents, $header->uid); |
| | 788 | |
| | 789 | $headers[$idx]->depth = $depth; |
| | 790 | $headers[$idx]->has_children = $msg_children[$id]; |
| | 791 | } |
| | 792 | } |
| | 793 | |
| | 794 | |
| | 795 | /** |
| | 796 | * Private method for listing a set of message headers (search results) |
| | 797 | * |
| | 798 | * @param string Mailbox/folder name |
| | 799 | * @param int Current page to list |
| | 800 | * @param string Header field to sort by |
| | 801 | * @param string Sort order [ASC|DESC] |
| | 802 | * @param boolean Number of slice items to extract from result array |
| | 803 | * @return array Indexed array with message header objects |
| | 804 | * @access private |
| | 805 | * @see rcube_imap::list_header_set() |
| | 806 | */ |
| | 807 | private function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| | 808 | { |
| | 809 | if (!strlen($mailbox) || empty($this->search_set)) |
| | 810 | return array(); |
| | 811 | |
| | 812 | // use saved messages from searching |
| | 813 | if ($this->threading) |
| | 814 | return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); |
| | 815 | |
| | 816 | // search set is threaded, we need a new one |
| | 817 | if ($this->search_threads) |
| | 818 | $this->search('', $this->search_string, $this->search_charset, $sort_field); |
| | 819 | |
| | 820 | $msgs = $this->search_set; |
| | 821 | $a_msg_headers = array(); |
| | 822 | $page = $page ? $page : $this->list_page; |
| | 823 | $start_msg = ($page-1) * $this->page_size; |
| | 824 | |
| | 825 | $this->_set_sort_order($sort_field, $sort_order); |
| | 826 | |
| | 827 | // quickest method (default sorting) |
| | 828 | if (!$this->search_sort_field && !$this->sort_field) { |
| | 829 | if ($sort_order == 'DESC') |
| | 830 | $msgs = array_reverse($msgs); |
| | 831 | |
| | 832 | // get messages uids for one page |
| | 833 | $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); |
| | 834 | |
| | 835 | if ($slice) |
| | 836 | $msgs = array_slice($msgs, -$slice, $slice); |
| | 837 | |
| | 838 | // fetch headers |
| | 839 | $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); |
| | 840 | |
| | 841 | // I didn't found in RFC that FETCH always returns messages sorted by index |
| | 842 | $sorter = new rcube_header_sorter(); |
| | 843 | $sorter->set_sequence_numbers($msgs); |
| | 844 | $sorter->sort_headers($a_msg_headers); |
| | 845 | |
| | 846 | return array_values($a_msg_headers); |
| | 847 | } |
| | 848 | |
| | 849 | // sorted messages, so we can first slice array and then fetch only wanted headers |
| | 850 | if ($this->get_capability('SORT')) { // SORT searching result |
| | 851 | // reset search set if sorting field has been changed |
| | 852 | if ($this->sort_field && $this->search_sort_field != $this->sort_field) |
| | 853 | $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field); |
| | 854 | |
| | 855 | // return empty array if no messages found |
| | 856 | if (empty($msgs)) |
| | 857 | return array(); |
| | 858 | |
| | 859 | if ($sort_order == 'DESC') |
| | 860 | $msgs = array_reverse($msgs); |
| | 861 | |
| | 862 | // get messages uids for one page |
| | 863 | $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); |
| | 864 | |
| | 865 | if ($slice) |
| | 866 | $msgs = array_slice($msgs, -$slice, $slice); |
| | 867 | |
| | 868 | // fetch headers |
| | 869 | $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); |
| | 870 | |
| | 871 | $sorter = new rcube_header_sorter(); |
| | 872 | $sorter->set_sequence_numbers($msgs); |
| | 873 | $sorter->sort_headers($a_msg_headers); |
| | 874 | |
| | 875 | return array_values($a_msg_headers); |
| | 876 | } |
| | 877 | else { // SEARCH result, need sorting |
| | 878 | $cnt = count($msgs); |
| | 879 | // 300: experimantal value for best result |
| | 880 | if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { |
| | 881 | // use memory less expensive (and quick) method for big result set |
| | 882 | $a_index = $this->message_index('', $this->sort_field, $this->sort_order); |
| | 883 | // get messages uids for one page... |
| | 884 | $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); |
| | 885 | if ($slice) |
| | 886 | $msgs = array_slice($msgs, -$slice, $slice); |
| | 887 | // ...and fetch headers |
| | 888 | $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); |
| | 889 | |
| | 890 | // return empty array if no messages found |
| | 891 | if (!is_array($a_msg_headers) || empty($a_msg_headers)) |
| | 892 | return array(); |
| | 893 | |
| | 894 | $sorter = new rcube_header_sorter(); |
| | 895 | $sorter->set_sequence_numbers($msgs); |
| | 896 | $sorter->sort_headers($a_msg_headers); |
| | 897 | |
| | 898 | return array_values($a_msg_headers); |
| | 899 | } |
| | 900 | else { |
| | 901 | // for small result set we can fetch all messages headers |
| | 902 | $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); |
| | 903 | |
| | 904 | // return empty array if no messages found |
| | 905 | if (!is_array($a_msg_headers) || empty($a_msg_headers)) |
| | 906 | return array(); |
| | 907 | |
| | 908 | // if not already sorted |
| | 909 | $a_msg_headers = $this->conn->sortHeaders( |
| | 910 | $a_msg_headers, $this->sort_field, $this->sort_order); |
| | 911 | |
| | 912 | // only return the requested part of the set |
| | 913 | $a_msg_headers = array_slice(array_values($a_msg_headers), |
| | 914 | $start_msg, min($cnt-$start_msg, $this->page_size)); |
| | 915 | |
| | 916 | if ($slice) |
| | 917 | $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); |
| | 918 | |
| | 919 | return $a_msg_headers; |
| | 920 | } |
| | 921 | } |
| | 922 | } |
| | 923 | |
| | 924 | |
| | 925 | /** |
| | 926 | * Private method for listing a set of threaded message headers (search results) |
| | 927 | * |
| | 928 | * @param string Mailbox/folder name |
| | 929 | * @param int Current page to list |
| | 930 | * @param string Header field to sort by |
| | 931 | * @param string Sort order [ASC|DESC] |
| | 932 | * @param boolean Number of slice items to extract from result array |
| | 933 | * @return array Indexed array with message header objects |
| | 934 | * @access private |
| | 935 | * @see rcube_imap::list_header_set() |
| | 936 | */ |
| | 937 | private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| | 938 | { |
| | 939 | // update search_set if previous data was fetched with disabled threading |
| | 940 | if (!$this->search_threads) |
| | 941 | $this->search('', $this->search_string, $this->search_charset, $sort_field); |
| | 942 | |
| | 943 | $thread_tree = $this->search_set['tree']; |
| | 944 | $msg_depth = $this->search_set['depth']; |
| | 945 | $has_children = $this->search_set['children']; |
| | 946 | $a_msg_headers = array(); |
| | 947 | |
| | 948 | $page = $page ? $page : $this->list_page; |
| | 949 | $start_msg = ($page-1) * $this->page_size; |
| | 950 | |
| | 951 | $this->_set_sort_order($sort_field, $sort_order); |
| | 952 | |
| | 953 | $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); |
| | 954 | |
| | 955 | return $this->_fetch_thread_headers($mailbox, |
| | 956 | $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); |
| | 957 | } |
| | 958 | |
| | 959 | |
| | 960 | /** |
| | 961 | * Helper function to get first and last index of the requested set |
| | 962 | * |
| | 963 | * @param int message count |
| | 964 | * @param mixed page number to show, or string 'all' |
| | 965 | * @return array array with two values: first index, last index |
| | 966 | * @access private |
| | 967 | */ |
| | 968 | private function _get_message_range($max, $page) |
| | 969 | { |
| | 970 | $start_msg = ($page-1) * $this->page_size; |
| | 971 | |
| | 972 | if ($page=='all') { |
| | 973 | $begin = 0; |
| | 974 | $end = $max; |
| | 975 | } |
| | 976 | else if ($this->sort_order=='DESC') { |
| | 977 | $begin = $max - $this->page_size - $start_msg; |
| | 978 | $end = $max - $start_msg; |
| | 979 | } |
| | 980 | else { |
| | 981 | $begin = $start_msg; |
| | 982 | $end = $start_msg + $this->page_size; |
| | 983 | } |
| | 984 | |
| | 985 | if ($begin < 0) $begin = 0; |
| | 986 | if ($end < 0) $end = $max; |
| | 987 | if ($end > $max) $end = $max; |
| | 988 | |
| | 989 | return array($begin, $end); |
| | 990 | } |
| | 991 | |
| | 992 | |
| | 993 | /** |
| | 994 | * Fetches message headers |
| | 995 | * Used for loop |
| | 996 | * |
| | 997 | * @param string Mailbox name |
| | 998 | * @param string Message index to fetch |
| | 999 | * @param array Reference to message headers array |
| | 1000 | * @param array Array with cache index |
| | 1001 | * @return int Messages count |
| | 1002 | * @access private |
| | 1003 | */ |
| | 1004 | private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) |
| | 1005 | { |
| | 1006 | // fetch reqested headers from server |
| | 1007 | $a_header_index = $this->conn->fetchHeaders( |
| | 1008 | $mailbox, $msgs, false, false, $this->fetch_add_headers); |
| | 1009 | |
| | 1010 | if (empty($a_header_index)) |
| | 1011 | return 0; |
| | 1012 | |
| | 1013 | // cache is incomplete |
| | 1014 | $cache_index = $this->get_message_cache_index($cache_key); |
| | 1015 | |
| | 1016 | foreach ($a_header_index as $i => $headers) { |
| | 1017 | if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) { |
| | 1018 | // prevent index duplicates |
| | 1019 | if ($cache_index[$headers->id]) { |
| | 1020 | $this->remove_message_cache($cache_key, $headers->id, true); |
| | 1021 | unset($cache_index[$headers->id]); |
| | 1022 | } |
| | 1023 | // add message to cache |
| | 1024 | $this->add_message_cache($cache_key, $headers->id, $headers, NULL, |
| | 1025 | !in_array($headers->uid, $cache_index)); |
| | 1026 | } |
| | 1027 | |
| | 1028 | $a_msg_headers[$headers->uid] = $headers; |
| | 1029 | } |
| | 1030 | |
| | 1031 | return count($a_msg_headers); |
| | 1032 | } |
| | 1033 | |
| | 1034 | /** |
| | 1035 | * Fetches IDS of pseudo recent messages. |
| | 1036 | * |
| | 1037 | * We compare the maximum UID to determine the number of |
| | 1038 | * new messages because the RECENT flag is not reliable. |
| | 1039 | * |
| | 1040 | * @param string Mailbox/folder name |
| | 1041 | * @return array List of recent message UIDs |
| | 1042 | */ |
| | 1043 | function recent_uids($mbox_name = null, $nofetch = false) |
| | 1044 | { |
| | 1045 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 1046 | $old_maxuid = intval($_SESSION['maxuid'][$mailbox]); |
| | 1047 | |
| | 1048 | // refresh message count -> will update $_SESSION['maxuid'][$mailbox] |
| | 1049 | $this->_messagecount($mailbox, 'ALL', true); |
| | 1050 | |
| | 1051 | if ($_SESSION['maxuid'][$mailbox] > $old_maxuid) { |
| | 1052 | $maxuid = max(1, $old_maxuid+1); |
| | 1053 | return array_values((array)$this->conn->fetchHeaderIndex( |
| | 1054 | $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true)); |
| | 1055 | } |
| | 1056 | |
| | 1057 | return array(); |
| | 1058 | } |
| | 1059 | |
| | 1060 | /** |
| | 1061 | * Return sorted array of message IDs (not UIDs) |
| | 1062 | * |
| | 1063 | * @param string Mailbox to get index from |
| | 1064 | * @param string Sort column |
| | 1065 | * @param string Sort order [ASC, DESC] |
| | 1066 | * @return array Indexed array with message ids |
| | 1067 | */ |
| | 1068 | function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) |
| | 1069 | { |
| | 1070 | if ($this->threading) |
| | 1071 | return $this->thread_index($mbox_name, $sort_field, $sort_order); |
| | 1072 | |
| | 1073 | $this->_set_sort_order($sort_field, $sort_order); |
| | 1074 | |
| | 1075 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 1076 | $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi"; |
| | 1077 | |
| | 1078 | // we have a saved search result, get index from there |
| | 1079 | if (!isset($this->cache[$key]) && $this->search_string |
| | 1080 | && !$this->search_threads && $mailbox == $this->mailbox) { |
| | 1081 | // use message index sort as default sorting |
| | 1082 | if (!$this->sort_field) { |
| | 1083 | $msgs = $this->search_set; |
| | 1084 | |
| | 1085 | if ($this->search_sort_field != 'date') |
| | 1086 | sort($msgs); |
| | 1087 | |
| | 1088 | if ($this->sort_order == 'DESC') |
| | 1089 | $this->cache[$key] = array_reverse($msgs); |
| | 1090 | else |
| | 1091 | $this->cache[$key] = $msgs; |
| | 1092 | } |
| | 1093 | // sort with SORT command |
| | 1094 | else if ($this->get_capability('SORT')) { |
| | 1095 | if ($this->sort_field && $this->search_sort_field != $this->sort_field) |
| | 1096 | $this->search('', $this->search_string, $this->search_charset, $this->sort_field); |
| | 1097 | |
| | 1098 | if ($this->sort_order == 'DESC') |
| | 1099 | $this->cache[$key] = array_reverse($this->search_set); |
| | 1100 | else |
| | 1101 | $this->cache[$key] = $this->search_set; |
| | 1102 | } |
| | 1103 | else { |
| | 1104 | $a_index = $this->conn->fetchHeaderIndex($mailbox, |
| | 1105 | join(',', $this->search_set), $this->sort_field, $this->skip_deleted); |
| | 1106 | |
| | 1107 | if (is_array($a_index)) { |
| | 1108 | if ($this->sort_order=="ASC") |
| | 1109 | asort($a_index); |
| | 1110 | else if ($this->sort_order=="DESC") |
| | 1111 | arsort($a_index); |
| | 1112 | |
| | 1113 | $this->cache[$key] = array_keys($a_index); |
| | 1114 | } |
| | 1115 | else { |
| | 1116 | $this->cache[$key] = array(); |
| | 1117 | } |
| | 1118 | } |
| | 1119 | } |
| | 1120 | |
| | 1121 | // have stored it in RAM |
| | 1122 | if (isset($this->cache[$key])) |
| | 1123 | return $this->cache[$key]; |
| | 1124 | |
| | 1125 | // check local cache |
| | 1126 | $cache_key = $mailbox.'.msg'; |
| | 1127 | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| | 1128 | |
| | 1129 | // cache is OK |
| | 1130 | if ($cache_status>0) { |
| | 1131 | $a_index = $this->get_message_cache_index($cache_key, |
| | 1132 | true, $this->sort_field, $this->sort_order); |
| | 1133 | return array_keys($a_index); |
| | 1134 | } |
| | 1135 | |
| | 1136 | // use message index sort as default sorting |
| | 1137 | if (!$this->sort_field) { |
| | 1138 | if ($this->skip_deleted) { |
| | 1139 | $a_index = $this->_search_index($mailbox, 'ALL'); |
| | 1140 | } else if ($max = $this->_messagecount($mailbox)) { |
| | 1141 | $a_index = range(1, $max); |
| | 1142 | } |
| | 1143 | |
| | 1144 | if ($this->sort_order == 'DESC') |
| | 1145 | $a_index = array_reverse($a_index); |
| | 1146 | |
| | 1147 | $this->cache[$key] = $a_index; |
| | 1148 | } |
| | 1149 | // fetch complete message index |
| | 1150 | else if ($this->get_capability('SORT')) { |
| | 1151 | if ($a_index = $this->conn->sort($mailbox, |
| | 1152 | $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { |
| | 1153 | if ($this->sort_order == 'DESC') |
| | 1154 | $a_index = array_reverse($a_index); |
| | 1155 | |
| | 1156 | $this->cache[$key] = $a_index; |
| | 1157 | } |
| | 1158 | } |
| | 1159 | else if ($a_index = $this->conn->fetchHeaderIndex( |
| | 1160 | $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { |
| | 1161 | if ($this->sort_order=="ASC") |
| | 1162 | asort($a_index); |
| | 1163 | else if ($this->sort_order=="DESC") |
| | 1164 | arsort($a_index); |
| | 1165 | |
| | 1166 | $this->cache[$key] = array_keys($a_index); |
| | 1167 | } |
| | 1168 | |
| | 1169 | return $this->cache[$key]; |
| | 1170 | } |
| | 1171 | |
| | 1172 | |
| | 1173 | /** |
| | 1174 | * Return sorted array of threaded message IDs (not UIDs) |
| | 1175 | * |
| | 1176 | * @param string Mailbox to get index from |
| | 1177 | * @param string Sort column |
| | 1178 | * @param string Sort order [ASC, DESC] |
| | 1179 | * @return array Indexed array with message IDs |
| | 1180 | */ |
| | 1181 | function thread_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) |
| | 1182 | { |
| | 1183 | $this->_set_sort_order($sort_field, $sort_order); |
| | 1184 | |
| | 1185 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 1186 | $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi"; |
| | 1187 | |
| | 1188 | // we have a saved search result, get index from there |
| | 1189 | if (!isset($this->cache[$key]) && $this->search_string |
| | 1190 | && $this->search_threads && $mailbox == $this->mailbox) { |
| | 1191 | // use message IDs for better performance |
| | 1192 | $ids = array_keys_recursive($this->search_set['tree']); |
| | 1193 | $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); |
| | 1194 | } |
| | 1195 | |
| | 1196 | // have stored it in RAM |
| | 1197 | if (isset($this->cache[$key])) |
| | 1198 | return $this->cache[$key]; |
| | 1199 | /* |
| | 1200 | // check local cache |
| | 1201 | $cache_key = $mailbox.'.msg'; |
| | 1202 | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| | 1203 | |
| | 1204 | // cache is OK |
| | 1205 | if ($cache_status>0) { |
| | 1206 | $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); |
| | 1207 | return array_keys($a_index); |
| | 1208 | } |
| | 1209 | */ |
| | 1210 | // get all threads (default sort order) |
| | 1211 | list ($thread_tree) = $this->_fetch_threads($mailbox); |
| | 1212 | |
| | 1213 | $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree); |
| | 1214 | |
| | 1215 | return $this->cache[$key]; |
| | 1216 | } |
| | 1217 | |
| | 1218 | |
| | 1219 | /** |
| | 1220 | * Return array of threaded messages (all, not only roots) |
| | 1221 | * |
| | 1222 | * @param string Mailbox to get index from |
| | 1223 | * @param array Threaded messages array (see _fetch_threads()) |
| | 1224 | * @param array Message IDs if we know what we need (e.g. search result) |
| | 1225 | * for better performance |
| | 1226 | * @return array Indexed array with message IDs |
| | 1227 | * |
| | 1228 | * @access private |
| | 1229 | */ |
| | 1230 | private function _flatten_threads($mailbox, $thread_tree, $ids=null) |
| | 1231 | { |
| | 1232 | if (empty($thread_tree)) |
| | 1233 | return array(); |
| | 1234 | |
| | 1235 | $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids); |
| | 1236 | |
| | 1237 | if ($this->sort_order == 'DESC') |
| | 1238 | $msg_index = array_reverse($msg_index); |
| | 1239 | |
| | 1240 | // flatten threads array |
| | 1241 | $all_ids = array(); |
| | 1242 | foreach($msg_index as $root) { |
| | 1243 | $all_ids[] = $root; |
| | 1244 | if (!empty($thread_tree[$root])) |
| | 1245 | $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); |
| | 1246 | } |
| | 1247 | |
| | 1248 | return $all_ids; |
| | 1249 | } |
| | 1250 | |
| | 1251 | |
| | 1252 | /** |
| | 1253 | * @access private |
| | 1254 | */ |
| | 1255 | private function sync_header_index($mailbox) |
| | 1256 | { |
| | 1257 | $cache_key = $mailbox.'.msg'; |
| | 1258 | $cache_index = $this->get_message_cache_index($cache_key); |
| | 1259 | |
| | 1260 | // fetch complete message index |
| | 1261 | $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); |
| | 1262 | |
| | 1263 | if ($a_message_index === false) |
| | 1264 | return false; |
| | 1265 | |
| | 1266 | foreach ($a_message_index as $id => $uid) { |
| | 1267 | // message in cache at correct position |
| | 1268 | if ($cache_index[$id] == $uid) { |
| | 1269 | unset($cache_index[$id]); |
| | 1270 | continue; |
| | 1271 | } |
| | 1272 | |
| | 1273 | // message in cache but in wrong position |
| | 1274 | if (in_array((string)$uid, $cache_index, true)) { |
| | 1275 | unset($cache_index[$id]); |
| | 1276 | } |
| | 1277 | |
| | 1278 | // other message at this position |
| | 1279 | if (isset($cache_index[$id])) { |
| | 1280 | $for_remove[] = $cache_index[$id]; |
| | 1281 | unset($cache_index[$id]); |
| | 1282 | } |
| | 1283 | |
| | 1284 | $for_update[] = $id; |
| | 1285 | } |
| | 1286 | |
| | 1287 | // clear messages at wrong positions and those deleted that are still in cache_index |
| | 1288 | if (!empty($for_remove)) |
| | 1289 | $cache_index = array_merge($cache_index, $for_remove); |
| | 1290 | |
| | 1291 | if (!empty($cache_index)) |
| | 1292 | $this->remove_message_cache($cache_key, $cache_index); |
| | 1293 | |
| | 1294 | // fetch complete headers and add to cache |
| | 1295 | if (!empty($for_update)) { |
| | 1296 | if ($headers = $this->conn->fetchHeader($mailbox, |
| | 1297 | join(',', $for_update), false, $this->fetch_add_headers)) { |
| | 1298 | foreach ($headers as $header) { |
| | 1299 | $this->add_message_cache($cache_key, $header->id, $header, NULL, |
| | 1300 | in_array($header->uid, (array)$for_remove)); |
| | 1301 | } |
| | 1302 | } |
| | 1303 | } |
| | 1304 | } |
| | 1305 | |
| | 1306 | |
| | 1307 | /** |
| | 1308 | * Invoke search request to IMAP server |
| | 1309 | * |
| | 1310 | * @param string mailbox name to search in |
| | 1311 | * @param string search string |
| | 1312 | * @param string search string charset |
| | 1313 | * @param string header field to sort by |
| | 1314 | * @return array search results as list of message ids |
| | 1315 | * @access public |
| | 1316 | */ |
| | 1317 | function search($mbox_name='', $str=NULL, $charset=NULL, $sort_field=NULL) |
| | 1318 | { |
| | 1319 | if (!$str) |
| | 1320 | return false; |
| | 1321 | |
| | 1322 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 1323 | |
| | 1324 | $results = $this->_search_index($mailbox, $str, $charset, $sort_field); |
| | 1325 | |
| | 1326 | // try search with US-ASCII charset (should be supported by server) |
| | 1327 | // only if UTF-8 search is not supported |
| | 1328 | if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') |
| | 1329 | { |
| | 1330 | // convert strings to US_ASCII |
| | 1331 | if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { |
| | 1332 | $last = 0; $res = ''; |
| | 1333 | foreach($matches[1] as $m) |
| | 1334 | { |
| | 1335 | $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n |
| | 1336 | $string = substr($str, $string_offset - 1, $m[0]); |
| | 1337 | $string = rcube_charset_convert($string, $charset, 'US-ASCII'); |
| | 1338 | if (!$string) |
| | 1339 | continue; |
| | 1340 | $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); |
| | 1341 | $last = $m[0] + $string_offset - 1; |
| | 1342 | } |
| | 1343 | if ($last < strlen($str)) |
| | 1344 | $res .= substr($str, $last, strlen($str)-$last); |
| | 1345 | } |
| | 1346 | else // strings for conversion not found |
| | 1347 | $res = $str; |
| | 1348 | |
| | 1349 | $results = $this->search($mbox_name, $res, NULL, $sort_field); |
| | 1350 | } |
| | 1351 | |
| | 1352 | $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); |
| | 1353 | |
| | 1354 | return $results; |
| | 1355 | } |
| | 1356 | |
| | 1357 | |
| | 1358 | /** |
| | 1359 | * Private search method |
| | 1360 | * |
| | 1361 | * @return array search results as list of message ids |
| | 1362 | * @access private |
| | 1363 | * @see rcube_imap::search() |
| | 1364 | */ |
| | 1365 | private function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL) |
| | 1366 | { |
| | 1367 | $orig_criteria = $criteria; |
| | 1368 | |
| | 1369 | if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria)) |
| | 1370 | $criteria = 'UNDELETED '.$criteria; |
| | 1371 | |
| | 1372 | if ($this->threading) { |
| | 1373 | list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( |
| | 1374 | $mailbox, $this->threading, $criteria, $charset); |
| | 1375 | |
| | 1376 | $a_messages = array( |
| | 1377 | 'tree' => $thread_tree, |
| | 1378 | 'depth' => $msg_depth, |
| | 1379 | 'children' => $has_children |
| | 1380 | ); |
| | 1381 | } |
| | 1382 | else if ($sort_field && $this->get_capability('SORT')) { |
| | 1383 | $charset = $charset ? $charset : $this->default_charset; |
| | 1384 | $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); |
| | 1385 | |
| | 1386 | if (!$a_messages) |
| | 1387 | return array(); |
| | 1388 | } |
| | 1389 | else { |
| | 1390 | if ($orig_criteria == 'ALL') { |
| | 1391 | $max = $this->_messagecount($mailbox); |
| | 1392 | $a_messages = $max ? range(1, $max) : array(); |
| | 1393 | } |
| | 1394 | else { |
| | 1395 | $a_messages = $this->conn->search($mailbox, |
| | 1396 | ($charset ? "CHARSET $charset " : '') . $criteria); |
| | 1397 | |
| | 1398 | if (!$a_messages) |
| | 1399 | return array(); |
| | 1400 | |
| | 1401 | // I didn't found that SEARCH always returns sorted IDs |
| | 1402 | if (!$this->sort_field) |
| | 1403 | sort($a_messages); |
| | 1404 | } |
| | 1405 | } |
| | 1406 | |
| | 1407 | // update messagecount cache ? |
| | 1408 | // $a_mailbox_cache = get_cache('messagecount'); |
| | 1409 | // $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); |
| | 1410 | // $this->update_cache('messagecount', $a_mailbox_cache); |
| | 1411 | |
| | 1412 | return $a_messages; |
| | 1413 | } |
| | 1414 | |
| | 1415 | |
| | 1416 | /** |
| | 1417 | * Sort thread |
| | 1418 | * |
| | 1419 | * @param string Mailbox name |
| | 1420 | * @param array Unsorted thread tree (rcube_imap_generic::thread() result) |
| | 1421 | * @param array Message IDs if we know what we need (e.g. search result) |
| | 1422 | * @return array Sorted roots IDs |
| | 1423 | * @access private |
| | 1424 | */ |
| | 1425 | private function _sort_threads($mailbox, $thread_tree, $ids=NULL) |
| | 1426 | { |
| | 1427 | // THREAD=ORDEREDSUBJECT: sorting by sent date of root message |
| | 1428 | // THREAD=REFERENCES: sorting by sent date of root message |
| | 1429 | // THREAD=REFS: sorting by the most recent date in each thread |
| | 1430 | // default sorting |
| | 1431 | if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { |
| | 1432 | return array_keys((array)$thread_tree); |
| 1166 | | // use message index sort as default sorting |
| 1167 | | if (!$this->sort_field) |
| 1168 | | { |
| 1169 | | $msgs = $this->search_set; |
| 1170 | | |
| 1171 | | if ($this->search_sort_field != 'date') |
| 1172 | | sort($msgs); |
| 1173 | | |
| 1174 | | if ($this->sort_order == 'DESC') |
| 1175 | | $this->cache[$key] = array_reverse($msgs); |
| 1176 | | else |
| 1177 | | $this->cache[$key] = $msgs; |
| 1178 | | } |
| 1179 | | // sort with SORT command |
| 1180 | | else if ($this->get_capability('SORT')) |
| 1181 | | { |
| 1182 | | if ($this->sort_field && $this->search_sort_field != $this->sort_field) |
| 1183 | | $this->search('', $this->search_string, $this->search_charset, $this->sort_field); |
| 1184 | | |
| 1185 | | if ($this->sort_order == 'DESC') |
| 1186 | | $this->cache[$key] = array_reverse($this->search_set); |
| 1187 | | else |
| 1188 | | $this->cache[$key] = $this->search_set; |
| 1189 | | } |
| 1190 | | else |
| 1191 | | { |
| 1192 | | $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, |
| 1193 | | join(',', $this->search_set), $this->sort_field, $this->skip_deleted); |
| 1194 | | |
| 1195 | | if ($this->sort_order=="ASC") |
| 1196 | | asort($a_index); |
| 1197 | | else if ($this->sort_order=="DESC") |
| 1198 | | arsort($a_index); |
| 1199 | | |
| 1200 | | $this->cache[$key] = array_keys($a_index); |
| 1201 | | } |
| 1202 | | } |
| 1203 | | |
| 1204 | | // have stored it in RAM |
| 1205 | | if (isset($this->cache[$key])) |
| 1206 | | return $this->cache[$key]; |
| 1207 | | |
| 1208 | | // check local cache |
| 1209 | | $cache_key = $mailbox.'.msg'; |
| 1210 | | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| 1211 | | |
| 1212 | | // cache is OK |
| 1213 | | if ($cache_status>0) |
| 1214 | | { |
| 1215 | | $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); |
| 1216 | | return array_keys($a_index); |
| 1217 | | } |
| 1218 | | |
| 1219 | | // use message index sort as default sorting |
| 1220 | | if (!$this->sort_field) |
| 1221 | | { |
| 1222 | | if ($this->skip_deleted) { |
| 1223 | | $a_index = $this->_search_index($mailbox, 'ALL'); |
| 1224 | | } else if ($max = $this->_messagecount($mailbox)) { |
| 1225 | | $a_index = range(1, $max); |
| 1226 | | } |
| 1227 | | |
| 1228 | | if ($this->sort_order == 'DESC') |
| 1229 | | $a_index = array_reverse($a_index); |
| 1230 | | |
| 1231 | | $this->cache[$key] = $a_index; |
| 1232 | | } |
| 1233 | | // fetch complete message index |
| 1234 | | else if ($this->get_capability('SORT')) |
| 1235 | | { |
| 1236 | | if ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { |
| 1237 | | if ($this->sort_order == 'DESC') |
| 1238 | | $a_index = array_reverse($a_index); |
| 1239 | | |
| 1240 | | $this->cache[$key] = $a_index; |
| 1241 | | } |
| 1242 | | } |
| 1243 | | else if ($a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { |
| 1244 | | if ($this->sort_order=="ASC") |
| 1245 | | asort($a_index); |
| 1246 | | else if ($this->sort_order=="DESC") |
| 1247 | | arsort($a_index); |
| 1248 | | |
| 1249 | | $this->cache[$key] = array_keys($a_index); |
| 1250 | | } |
| 1251 | | |
| 1252 | | return $this->cache[$key]; |
| 1253 | | } |
| 1254 | | |
| 1255 | | |
| 1256 | | /** |
| 1257 | | * Return sorted array of threaded message IDs (not UIDs) |
| 1258 | | * |
| 1259 | | * @param string Mailbox to get index from |
| 1260 | | * @param string Sort column |
| 1261 | | * @param string Sort order [ASC, DESC] |
| 1262 | | * @return array Indexed array with message IDs |
| 1263 | | */ |
| 1264 | | function thread_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) |
| 1265 | | { |
| 1266 | | $this->_set_sort_order($sort_field, $sort_order); |
| 1267 | | |
| 1268 | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| 1269 | | $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi"; |
| 1270 | | |
| 1271 | | // we have a saved search result, get index from there |
| 1272 | | if (!isset($this->cache[$key]) && $this->search_string |
| 1273 | | && $this->search_threads && $mailbox == $this->mailbox) |
| 1274 | | { |
| 1275 | | // use message IDs for better performance |
| 1276 | | $ids = array_keys_recursive($this->search_set['tree']); |
| 1277 | | $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); |
| 1278 | | } |
| 1279 | | |
| 1280 | | // have stored it in RAM |
| 1281 | | if (isset($this->cache[$key])) |
| 1282 | | return $this->cache[$key]; |
| 1283 | | /* |
| 1284 | | // check local cache |
| 1285 | | $cache_key = $mailbox.'.msg'; |
| 1286 | | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| 1287 | | |
| 1288 | | // cache is OK |
| 1289 | | if ($cache_status>0) |
| 1290 | | { |
| 1291 | | $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); |
| 1292 | | return array_keys($a_index); |
| 1293 | | } |
| 1294 | | */ |
| 1295 | | // get all threads (default sort order) |
| 1296 | | list ($thread_tree) = $this->_fetch_threads($mailbox); |
| 1297 | | |
| 1298 | | $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree); |
| 1299 | | |
| 1300 | | return $this->cache[$key]; |
| 1301 | | } |
| 1302 | | |
| 1303 | | |
| 1304 | | /** |
| 1305 | | * Return array of threaded messages (all, not only roots) |
| 1306 | | * |
| 1307 | | * @param string Mailbox to get index from |
| 1308 | | * @param array Threaded messages array (see _fetch_threads()) |
| 1309 | | * @param array Message IDs if we know what we need (e.g. search result) |
| 1310 | | * for better performance |
| 1311 | | * @return array Indexed array with message IDs |
| 1312 | | * |
| 1313 | | * @access private |
| 1314 | | */ |
| 1315 | | private function _flatten_threads($mailbox, $thread_tree, $ids=null) |
| 1316 | | { |
| 1317 | | if (empty($thread_tree)) |
| 1318 | | return array(); |
| 1319 | | |
| 1320 | | $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids); |
| 1321 | | |
| 1322 | | if ($this->sort_order == 'DESC') |
| 1323 | | $msg_index = array_reverse($msg_index); |
| 1324 | | |
| 1325 | | // flatten threads array |
| 1326 | | $all_ids = array(); |
| 1327 | | foreach($msg_index as $root) { |
| 1328 | | $all_ids[] = $root; |
| 1329 | | if (!empty($thread_tree[$root])) |
| 1330 | | $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); |
| 1331 | | } |
| 1332 | | |
| 1333 | | return $all_ids; |
| 1334 | | } |
| 1335 | | |
| 1336 | | |
| 1337 | | /** |
| 1338 | | * @access private |
| 1339 | | */ |
| 1340 | | private function sync_header_index($mailbox) |
| 1341 | | { |
| 1342 | | $cache_key = $mailbox.'.msg'; |
| 1343 | | $cache_index = $this->get_message_cache_index($cache_key); |
| 1344 | | |
| 1345 | | // fetch complete message index |
| 1346 | | $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", 'UID', $this->skip_deleted); |
| 1347 | | |
| 1348 | | if ($a_message_index === false) |
| 1349 | | return false; |
| 1350 | | |
| 1351 | | foreach ($a_message_index as $id => $uid) |
| 1352 | | { |
| 1353 | | // message in cache at correct position |
| 1354 | | if ($cache_index[$id] == $uid) |
| 1355 | | { |
| 1356 | | unset($cache_index[$id]); |
| 1357 | | continue; |
| 1358 | | } |
| 1359 | | |
| 1360 | | // message in cache but in wrong position |
| 1361 | | if (in_array((string)$uid, $cache_index, true)) |
| 1362 | | { |
| 1363 | | unset($cache_index[$id]); |
| 1364 | | } |
| 1365 | | |
| 1366 | | // other message at this position |
| 1367 | | if (isset($cache_index[$id])) |
| 1368 | | { |
| 1369 | | $for_remove[] = $cache_index[$id]; |
| 1370 | | unset($cache_index[$id]); |
| 1371 | | } |
| 1372 | | |
| 1373 | | $for_update[] = $id; |
| 1374 | | } |
| 1375 | | |
| 1376 | | // clear messages at wrong positions and those deleted that are still in cache_index |
| 1377 | | if (!empty($for_remove)) |
| 1378 | | $cache_index = array_merge($cache_index, $for_remove); |
| 1379 | | |
| 1380 | | if (!empty($cache_index)) |
| 1381 | | $this->remove_message_cache($cache_key, $cache_index); |
| 1382 | | |
| 1383 | | // fetch complete headers and add to cache |
| 1384 | | if (!empty($for_update)) { |
| 1385 | | if ($headers = iil_C_FetchHeader($this->conn, $mailbox, join(',', $for_update), false, $this->fetch_add_headers)) |
| 1386 | | foreach ($headers as $header) |
| 1387 | | $this->add_message_cache($cache_key, $header->id, $header, NULL, |
| 1388 | | in_array($header->uid, (array)$for_remove)); |
| 1389 | | } |
| 1390 | | } |
| 1391 | | |
| 1392 | | |
| 1393 | | /** |
| 1394 | | * Invoke search request to IMAP server |
| 1395 | | * |
| 1396 | | * @param string mailbox name to search in |
| 1397 | | * @param string search string |
| 1398 | | * @param string search string charset |
| 1399 | | * @param string header field to sort by |
| 1400 | | * @return array search results as list of message ids |
| 1401 | | * @access public |
| 1402 | | */ |
| 1403 | | function search($mbox_name='', $str=NULL, $charset=NULL, $sort_field=NULL) |
| 1404 | | { |
| 1405 | | if (!$str) |
| 1406 | | return false; |
| 1407 | | |
| 1408 | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| 1409 | | |
| 1410 | | $results = $this->_search_index($mailbox, $str, $charset, $sort_field); |
| 1411 | | |
| 1412 | | // try search with US-ASCII charset (should be supported by server) |
| 1413 | | // only if UTF-8 search is not supported |
| 1414 | | if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') |
| 1415 | | { |
| 1416 | | // convert strings to US_ASCII |
| 1417 | | if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) |
| 1418 | | { |
| 1419 | | $last = 0; $res = ''; |
| 1420 | | foreach($matches[1] as $m) |
| 1421 | | { |
| 1422 | | $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n |
| 1423 | | $string = substr($str, $string_offset - 1, $m[0]); |
| 1424 | | $string = rcube_charset_convert($string, $charset, 'US-ASCII'); |
| 1425 | | if (!$string) continue; |
| 1426 | | $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); |
| 1427 | | $last = $m[0] + $string_offset - 1; |
| 1428 | | } |
| 1429 | | if ($last < strlen($str)) |
| 1430 | | $res .= substr($str, $last, strlen($str)-$last); |
| 1431 | | } |
| 1432 | | else // strings for conversion not found |
| 1433 | | $res = $str; |
| 1434 | | |
| 1435 | | $results = $this->search($mbox_name, $res, NULL, $sort_field); |
| 1436 | | } |
| 1437 | | |
| 1438 | | $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); |
| 1439 | | |
| 1440 | | return $results; |
| 1441 | | } |
| 1442 | | |
| 1443 | | |
| 1444 | | /** |
| 1445 | | * Private search method |
| 1446 | | * |
| 1447 | | * @return array search results as list of message ids |
| 1448 | | * @access private |
| 1449 | | * @see rcube_imap::search() |
| 1450 | | */ |
| 1451 | | private function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL) |
| 1452 | | { |
| 1453 | | $orig_criteria = $criteria; |
| 1454 | | |
| 1455 | | if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria)) |
| 1456 | | $criteria = 'UNDELETED '.$criteria; |
| 1457 | | |
| 1458 | | if ($this->threading) { |
| 1459 | | list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, |
| 1460 | | $mailbox, $this->threading, $criteria, $charset); |
| 1461 | | |
| 1462 | | $a_messages = array( |
| 1463 | | 'tree' => $thread_tree, |
| 1464 | | 'depth' => $msg_depth, |
| 1465 | | 'children' => $has_children |
| 1466 | | ); |
| 1467 | | } |
| 1468 | | else if ($sort_field && $this->get_capability('SORT')) { |
| 1469 | | $charset = $charset ? $charset : $this->default_charset; |
| 1470 | | $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, false, $charset); |
| 1471 | | |
| 1472 | | if (!$a_messages) |
| 1473 | | return array(); |
| 1474 | | } |
| 1475 | | else { |
| 1476 | | if ($orig_criteria == 'ALL') { |
| 1477 | | $max = $this->_messagecount($mailbox); |
| 1478 | | $a_messages = $max ? range(1, $max) : array(); |
| 1479 | | } |
| 1480 | | else { |
| 1481 | | $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria); |
| 1482 | | |
| 1483 | | if (!$a_messages) |
| 1484 | | return array(); |
| 1485 | | |
| 1486 | | // I didn't found that SEARCH always returns sorted IDs |
| 1487 | | if (!$this->sort_field) |
| 1488 | | sort($a_messages); |
| 1489 | | } |
| 1490 | | } |
| 1491 | | // update messagecount cache ? |
| 1492 | | // $a_mailbox_cache = get_cache('messagecount'); |
| 1493 | | // $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); |
| 1494 | | // $this->update_cache('messagecount', $a_mailbox_cache); |
| 1495 | | |
| 1496 | | return $a_messages; |
| 1497 | | } |
| 1498 | | |
| 1499 | | |
| 1500 | | /** |
| 1501 | | * Sort thread |
| 1502 | | * |
| 1503 | | * @param string Mailbox name |
| 1504 | | * @param array Unsorted thread tree (iil_C_Thread() result) |
| 1505 | | * @param array Message IDs if we know what we need (e.g. search result) |
| 1506 | | * @return array Sorted roots IDs |
| 1507 | | * @access private |
| 1508 | | */ |
| 1509 | | private function _sort_threads($mailbox, $thread_tree, $ids=NULL) |
| 1510 | | { |
| 1511 | | // THREAD=ORDEREDSUBJECT: sorting by sent date of root message |
| 1512 | | // THREAD=REFERENCES: sorting by sent date of root message |
| 1513 | | // THREAD=REFS: sorting by the most recent date in each thread |
| 1514 | | // default sorting |
| 1515 | | if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { |
| 1516 | | return array_keys((array)$thread_tree); |
| 1517 | | } |
| 1518 | | // here we'll implement REFS sorting, for performance reason |
| 1519 | | else { // ($sort_field == 'date' && $this->threading != 'REFS') |
| 1520 | | // use SORT command |
| 1521 | | if ($this->get_capability('SORT')) { |
| 1522 | | $a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, |
| 1523 | | !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : '')); |
| 1524 | | |
| 1525 | | // return unsorted tree if we've got no index data |
| 1526 | | if (!$a_index) |
| 1527 | | return array_keys((array)$thread_tree); |
| 1528 | | } |
| 1529 | | else { |
| 1530 | | // fetch specified headers for all messages and sort them |
| 1531 | | $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, !empty($ids) ? $ids : "1:*", |
| 1532 | | $this->sort_field, $this->skip_deleted); |
| 1533 | | |
| 1534 | | // return unsorted tree if we've got no index data |
| 1535 | | if (!$a_index) |
| 1536 | | return array_keys((array)$thread_tree); |
| 1537 | | |
| 1538 | | asort($a_index); // ASC |
| 1539 | | $a_index = array_values($a_index); |
| 1540 | | } |
| 1541 | | |
| 1542 | | return $this->_sort_thread_refs($thread_tree, $a_index); |
| 1543 | | } |
| 1544 | | } |
| 1545 | | |
| 1546 | | |
| 1547 | | /** |
| 1548 | | * THREAD=REFS sorting implementation |
| 1549 | | * |
| 1550 | | * @param array Thread tree array (message identifiers as keys) |
| 1551 | | * @param array Array of sorted message identifiers |
| 1552 | | * @return array Array of sorted roots messages |
| 1553 | | * @access private |
| 1554 | | */ |
| 1555 | | private function _sort_thread_refs($tree, $index) |
| 1556 | | { |
| 1557 | | if (empty($tree)) |
| 1558 | | return array(); |
| 1559 | | |
| 1560 | | $index = array_combine(array_values($index), $index); |
| 1561 | | |
| 1562 | | // assign roots |
| 1563 | | foreach ($tree as $idx => $val) { |
| 1564 | | $index[$idx] = $idx; |
| 1565 | | if (!empty($val)) { |
| 1566 | | $idx_arr = array_keys_recursive($tree[$idx]); |
| 1567 | | foreach ($idx_arr as $subidx) |
| 1568 | | $index[$subidx] = $idx; |
| 1569 | | } |
| 1570 | | } |
| 1571 | | |
| 1572 | | $index = array_values($index); |
| 1573 | | |
| 1574 | | // create sorted array of roots |
| 1575 | | $msg_index = array(); |
| 1576 | | if ($this->sort_order != 'DESC') { |
| 1577 | | foreach ($index as $idx) |
| 1578 | | if (!isset($msg_index[$idx])) |
| 1579 | | $msg_index[$idx] = $idx; |
| 1580 | | $msg_index = array_values($msg_index); |
| 1581 | | } |
| 1582 | | else { |
| 1583 | | for ($x=count($index)-1; $x>=0; $x--) |
| 1584 | | if (!isset($msg_index[$index[$x]])) |
| 1585 | | $msg_index[$index[$x]] = $index[$x]; |
| 1586 | | $msg_index = array_reverse($msg_index); |
| 1587 | | } |
| 1588 | | |
| 1589 | | return $msg_index; |
| 1590 | | } |
| 1591 | | |
| 1592 | | |
| 1593 | | /** |
| 1594 | | * Refresh saved search set |
| 1595 | | * |
| 1596 | | * @return array Current search set |
| 1597 | | */ |
| 1598 | | function refresh_search() |
| 1599 | | { |
| 1600 | | if (!empty($this->search_string)) |
| 1601 | | $this->search_set = $this->search('', $this->search_string, $this->search_charset, |
| 1602 | | $this->search_sort_field, $this->search_threads); |
| 1603 | | |
| 1604 | | return $this->get_search_set(); |
| | 1521 | return $this->get_search_set(); |
| 1608 | | /** |
| 1609 | | * Check if the given message ID is part of the current search set |
| 1610 | | * |
| 1611 | | * @return boolean True on match or if no search request is stored |
| 1612 | | */ |
| 1613 | | function in_searchset($msgid) |
| 1614 | | { |
| 1615 | | if (!empty($this->search_string)) |
| 1616 | | return in_array("$msgid", (array)$this->search_set, true); |
| 1617 | | else |
| 1618 | | return true; |
| 1619 | | } |
| 1620 | | |
| 1621 | | |
| 1622 | | /** |
| 1623 | | * Return message headers object of a specific message |
| 1624 | | * |
| 1625 | | * @param int Message ID |
| 1626 | | * @param string Mailbox to read from |
| 1627 | | * @param boolean True if $id is the message UID |
| 1628 | | * @param boolean True if we need also BODYSTRUCTURE in headers |
| 1629 | | * @return object Message headers representation |
| 1630 | | */ |
| 1631 | | function get_headers($id, $mbox_name=NULL, $is_uid=true, $bodystr=false) |
| 1632 | | { |
| 1633 | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| 1634 | | $uid = $is_uid ? $id : $this->_id2uid($id); |
| 1635 | | |
| 1636 | | // get cached headers |
| 1637 | | if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) |
| 1638 | | return $headers; |
| 1639 | | |
| 1640 | | $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); |
| 1641 | | |
| 1642 | | // write headers cache |
| 1643 | | if ($headers) |
| 1644 | | { |
| 1645 | | if ($headers->uid && $headers->id) |
| 1646 | | $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; |
| 1647 | | |
| 1648 | | $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true); |
| 1649 | | } |
| 1650 | | |
| 1651 | | return $headers; |
| 1652 | | } |
| 1653 | | |
| 1654 | | |
| 1655 | | /** |
| 1656 | | * Fetch body structure from the IMAP server and build |
| 1657 | | * an object structure similar to the one generated by PEAR::Mail_mimeDecode |
| 1658 | | * |
| 1659 | | * @param int Message UID to fetch |
| 1660 | | * @param string Message BODYSTRUCTURE string (optional) |
| 1661 | | * @return object rcube_message_part Message part tree or False on failure |
| 1662 | | */ |
| 1663 | | function &get_structure($uid, $structure_str='') |
| 1664 | | { |
| 1665 | | $cache_key = $this->mailbox.'.msg'; |
| 1666 | | $headers = &$this->get_cached_message($cache_key, $uid); |
| 1667 | | |
| 1668 | | // return cached message structure |
| 1669 | | if (is_object($headers) && is_object($headers->structure)) { |
| 1670 | | return $headers->structure; |
| 1671 | | } |
| 1672 | | |
| 1673 | | if (!$structure_str) |
| 1674 | | $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); |
| 1675 | | $structure = iml_GetRawStructureArray($structure_str); |
| 1676 | | $struct = false; |
| 1677 | | |
| 1678 | | // parse structure and add headers |
| 1679 | | if (!empty($structure)) |
| 1680 | | { |
| 1681 | | $headers = $this->get_headers($uid); |
| 1682 | | $this->_msg_id = $headers->id; |
| 1683 | | |
| 1684 | | // set message charset from message headers |
| 1685 | | if ($headers->charset) |
| 1686 | | $this->struct_charset = $headers->charset; |
| 1687 | | else |
| 1688 | | $this->struct_charset = $this->_structure_charset($structure); |
| 1689 | | |
| 1690 | | // Here we can recognize malformed BODYSTRUCTURE and |
| 1691 | | // 1. [@TODO] parse the message in other way to create our own message structure |
| 1692 | | // 2. or just show the raw message body. |
| 1693 | | // Example of structure for malformed MIME message: |
| 1694 | | // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL) |
| 1695 | | if ($headers->ctype && $headers->ctype != 'text/plain' |
| 1696 | | && $structure[0] == 'text' && $structure[1] == 'plain') { |
| | 1525 | /** |
| | 1526 | * Check if the given message ID is part of the current search set |
| | 1527 | * |
| | 1528 | * @return boolean True on match or if no search request is stored |
| | 1529 | */ |
| | 1530 | function in_searchset($msgid) |
| | 1531 | { |
| | 1532 | if (!empty($this->search_string)) { |
| | 1533 | if ($this->search_threads) |
| | 1534 | return isset($this->search_set['depth']["$msgid"]); |
| | 1535 | else |
| | 1536 | return in_array("$msgid", (array)$this->search_set, true); |
| | 1537 | } |
| | 1538 | else |
| | 1539 | return true; |
| | 1540 | } |
| | 1541 | |
| | 1542 | |
| | 1543 | /** |
| | 1544 | * Return message headers object of a specific message |
| | 1545 | * |
| | 1546 | * @param int Message ID |
| | 1547 | * @param string Mailbox to read from |
| | 1548 | * @param boolean True if $id is the message UID |
| | 1549 | * @param boolean True if we need also BODYSTRUCTURE in headers |
| | 1550 | * @return object Message headers representation |
| | 1551 | */ |
| | 1552 | function get_headers($id, $mbox_name=NULL, $is_uid=true, $bodystr=false) |
| | 1553 | { |
| | 1554 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 1555 | $uid = $is_uid ? $id : $this->_id2uid($id); |
| | 1556 | |
| | 1557 | // get cached headers |
| | 1558 | if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) |
| | 1559 | return $headers; |
| | 1560 | |
| | 1561 | $headers = $this->conn->fetchHeader( |
| | 1562 | $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); |
| | 1563 | |
| | 1564 | // write headers cache |
| | 1565 | if ($headers) { |
| | 1566 | if ($headers->uid && $headers->id) |
| | 1567 | $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; |
| | 1568 | |
| | 1569 | $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true); |
| | 1570 | } |
| | 1571 | |
| | 1572 | return $headers; |
| | 1573 | } |
| | 1574 | |
| | 1575 | |
| | 1576 | /** |
| | 1577 | * Fetch body structure from the IMAP server and build |
| | 1578 | * an object structure similar to the one generated by PEAR::Mail_mimeDecode |
| | 1579 | * |
| | 1580 | * @param int Message UID to fetch |
| | 1581 | * @param string Message BODYSTRUCTURE string (optional) |
| | 1582 | * @return object rcube_message_part Message part tree or False on failure |
| | 1583 | */ |
| | 1584 | function &get_structure($uid, $structure_str='') |
| | 1585 | { |
| | 1586 | $cache_key = $this->mailbox.'.msg'; |
| | 1587 | $headers = &$this->get_cached_message($cache_key, $uid); |
| | 1588 | |
| | 1589 | // return cached message structure |
| | 1590 | if (is_object($headers) && is_object($headers->structure)) { |
| | 1591 | return $headers->structure; |
| | 1592 | } |
| | 1593 | |
| | 1594 | if (!$structure_str) |
| | 1595 | $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); |
| | 1596 | $structure = iml_GetRawStructureArray($structure_str); |
| | 1597 | $struct = false; |
| | 1598 | |
| | 1599 | // parse structure and add headers |
| | 1600 | if (!empty($structure)) { |
| | 1601 | $headers = $this->get_headers($uid); |
| | 1602 | $this->_msg_id = $headers->id; |
| | 1603 | |
| | 1604 | // set message charset from message headers |
| | 1605 | if ($headers->charset) |
| | 1606 | $this->struct_charset = $headers->charset; |
| | 1607 | else |
| | 1608 | $this->struct_charset = $this->_structure_charset($structure); |
| | 1609 | |
| | 1610 | // Here we can recognize malformed BODYSTRUCTURE and |
| | 1611 | // 1. [@TODO] parse the message in other way to create our own message structure |
| | 1612 | // 2. or just show the raw message body. |
| | 1613 | // Example of structure for malformed MIME message: |
| | 1614 | // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL) |
| | 1615 | if ($headers->ctype && $headers->ctype != 'text/plain' |
| | 1616 | && $structure[0] == 'text' && $structure[1] == 'plain') { |
| | 1617 | return false; |
| | 1618 | } |
| | 1619 | |
| | 1620 | $struct = &$this->_structure_part($structure); |
| | 1621 | $struct->headers = get_object_vars($headers); |
| | 1622 | |
| | 1623 | // don't trust given content-type |
| | 1624 | if (empty($struct->parts) && !empty($struct->headers['ctype'])) { |
| | 1625 | $struct->mime_id = '1'; |
| | 1626 | $struct->mimetype = strtolower($struct->headers['ctype']); |
| | 1627 | list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); |
| | 1628 | } |
| | 1629 | |
| | 1630 | // write structure to cache |
| | 1631 | if ($this->caching_enabled) |
| | 1632 | $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct); |
| | 1633 | } |
| | 1634 | |
| | 1635 | return $struct; |
| | 1636 | } |
| | 1637 | |
| | 1638 | |
| | 1639 | /** |
| | 1640 | * Build message part object |
| | 1641 | * |
| | 1642 | * @access private |
| | 1643 | */ |
| | 1644 | function &_structure_part($part, $count=0, $parent='', $mime_headers=null, $raw_headers=null) |
| | 1645 | { |
| | 1646 | $struct = new rcube_message_part; |
| | 1647 | $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; |
| | 1648 | |
| | 1649 | // multipart |
| | 1650 | if (is_array($part[0])) { |
| | 1651 | $struct->ctype_primary = 'multipart'; |
| | 1652 | |
| | 1653 | // find first non-array entry |
| | 1654 | for ($i=1; $i<count($part); $i++) { |
| | 1655 | if (!is_array($part[$i])) { |
| | 1656 | $struct->ctype_secondary = strtolower($part[$i]); |
| | 1657 | break; |
| | 1658 | } |
| | 1659 | } |
| | 1660 | |
| | 1661 | $struct->mimetype = 'multipart/'.$struct->ctype_secondary; |
| | 1662 | |
| | 1663 | // build parts list for headers pre-fetching |
| | 1664 | for ($i=0, $count=0; $i<count($part); $i++) { |
| | 1665 | if (is_array($part[$i]) && count($part[$i]) > 3) { |
| | 1666 | // fetch message headers if message/rfc822 |
| | 1667 | // or named part (could contain Content-Location header) |
| | 1668 | if (!is_array($part[$i][0])) { |
| | 1669 | $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; |
| | 1670 | if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') { |
| | 1671 | $raw_part_headers[] = $tmp_part_id; |
| | 1672 | $mime_part_headers[] = $tmp_part_id; |
| | 1673 | } |
| | 1674 | else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) { |
| | 1675 | $mime_part_headers[] = $tmp_part_id; |
| | 1676 | } |
| | 1677 | } |
| | 1678 | } |
| | 1679 | } |
| | 1680 | |
| | 1681 | // pre-fetch headers of all parts (in one command for better performance) |
| | 1682 | // @TODO: we could do this before _structure_part() call, to fetch |
| | 1683 | // headers for parts on all levels |
| | 1684 | if ($mime_part_headers) { |
| | 1685 | $mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, |
| | 1686 | $this->_msg_id, $mime_part_headers); |
| | 1687 | } |
| | 1688 | // we'll need a real content-type of message/rfc822 part |
| | 1689 | if ($raw_part_headers) { |
| | 1690 | $raw_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, |
| | 1691 | $this->_msg_id, $raw_part_headers, false); |
| | 1692 | } |
| | 1693 | $struct->parts = array(); |
| | 1694 | for ($i=0, $count=0; $i<count($part); $i++) { |
| | 1695 | if (is_array($part[$i]) && count($part[$i]) > 3) { |
| | 1696 | $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; |
| | 1697 | $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, |
| | 1698 | $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]); |
| | 1699 | } |
| | 1700 | } |
| | 1701 | |
| | 1702 | return $struct; |
| | 1703 | } |
| | 1704 | |
| | 1705 | // regular part |
| | 1706 | $struct->ctype_primary = strtolower($part[0]); |
| | 1707 | $struct->ctype_secondary = strtolower($part[1]); |
| | 1708 | $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary; |
| | 1709 | |
| | 1710 | // read content type parameters |
| | 1711 | if (is_array($part[2])) { |
| | 1712 | $struct->ctype_parameters = array(); |
| | 1713 | for ($i=0; $i<count($part[2]); $i+=2) |
| | 1714 | $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1]; |
| | 1715 | |
| | 1716 | if (isset($struct->ctype_parameters['charset'])) |
| | 1717 | $struct->charset = $struct->ctype_parameters['charset']; |
| | 1718 | } |
| | 1719 | |
| | 1720 | // read content encoding |
| | 1721 | if (!empty($part[5]) && $part[5]!='NIL') { |
| | 1722 | $struct->encoding = strtolower($part[5]); |
| | 1723 | $struct->headers['content-transfer-encoding'] = $struct->encoding; |
| | 1724 | } |
| | 1725 | |
| | 1726 | // get part size |
| | 1727 | if (!empty($part[6]) && $part[6]!='NIL') |
| | 1728 | $struct->size = intval($part[6]); |
| | 1729 | |
| | 1730 | // read part disposition |
| | 1731 | $di = count($part) - 2; |
| | 1732 | if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) || |
| | 1733 | (is_array($part[--$di]) && count($part[$di]) == 2)) { |
| | 1734 | $struct->disposition = strtolower($part[$di][0]); |
| | 1735 | |
| | 1736 | if (is_array($part[$di][1])) |
| | 1737 | for ($n=0; $n<count($part[$di][1]); $n+=2) |
| | 1738 | $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; |
| | 1739 | } |
| | 1740 | |
| | 1741 | // get child parts |
| | 1742 | if (is_array($part[8]) && $di != 8) { |
| | 1743 | $struct->parts = array(); |
| | 1744 | for ($i=0, $count=0; $i<count($part[8]); $i++) |
| | 1745 | if (is_array($part[8][$i]) && count($part[8][$i]) > 5) |
| | 1746 | $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id); |
| | 1747 | } |
| | 1748 | |
| | 1749 | // get part ID |
| | 1750 | if (!empty($part[3]) && $part[3]!='NIL') { |
| | 1751 | $struct->content_id = $part[3]; |
| | 1752 | $struct->headers['content-id'] = $part[3]; |
| | 1753 | |
| | 1754 | if (empty($struct->disposition)) |
| | 1755 | $struct->disposition = 'inline'; |
| | 1756 | } |
| | 1757 | |
| | 1758 | // fetch message headers if message/rfc822 or named part (could contain Content-Location header) |
| | 1759 | if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { |
| | 1760 | if (empty($mime_headers)) { |
| | 1761 | $mime_headers = $this->conn->fetchPartHeader( |
| | 1762 | $this->mailbox, $this->_msg_id, false, $struct->mime_id); |
| | 1763 | } |
| | 1764 | $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; |
| | 1765 | |
| | 1766 | // get real headers for message of type 'message/rfc822' |
| | 1767 | if ($struct->mimetype == 'message/rfc822') { |
| | 1768 | if (empty($raw_headers)) { |
| | 1769 | $raw_headers = $this->conn->fetchMIMEHeaders( |
| | 1770 | $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false); |
| | 1771 | } |
| | 1772 | $struct->real_headers = $this->_parse_headers($raw_headers); |
| | 1773 | |
| | 1774 | // get real content-type of message/rfc822 |
| | 1775 | if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) { |
| | 1776 | $struct->real_mimetype = strtolower($matches[1]); |
| | 1777 | } |
| | 1778 | } |
| | 1779 | } |
| | 1780 | |
| | 1781 | if ($struct->ctype_primary=='message') { |
| | 1782 | if (is_array($part[8]) && $di != 8 && empty($struct->parts)) |
| | 1783 | $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id); |
| | 1784 | } |
| | 1785 | |
| | 1786 | // normalize filename property |
| | 1787 | $this->_set_part_filename($struct, $mime_headers); |
| | 1788 | |
| | 1789 | return $struct; |
| | 1790 | } |
| | 1791 | |
| | 1792 | |
| | 1793 | /** |
| | 1794 | * Set attachment filename from message part structure |
| | 1795 | * |
| | 1796 | * @access private |
| | 1797 | * @param object rcube_message_part Part object |
| | 1798 | * @param string Part's raw headers |
| | 1799 | */ |
| | 1800 | private function _set_part_filename(&$part, $headers=null) |
| | 1801 | { |
| | 1802 | if (!empty($part->d_parameters['filename'])) |
| | 1803 | $filename_mime = $part->d_parameters['filename']; |
| | 1804 | else if (!empty($part->d_parameters['filename*'])) |
| | 1805 | $filename_encoded = $part->d_parameters['filename*']; |
| | 1806 | else if (!empty($part->ctype_parameters['name*'])) |
| | 1807 | $filename_encoded = $part->ctype_parameters['name*']; |
| | 1808 | // RFC2231 value continuations |
| | 1809 | // TODO: this should be rewrited to support RFC2231 4.1 combinations |
| | 1810 | else if (!empty($part->d_parameters['filename*0'])) { |
| | 1811 | $i = 0; |
| | 1812 | while (isset($part->d_parameters['filename*'.$i])) { |
| | 1813 | $filename_mime .= $part->d_parameters['filename*'.$i]; |
| | 1814 | $i++; |
| | 1815 | } |
| | 1816 | // some servers (eg. dovecot-1.x) have no support for parameter value continuations |
| | 1817 | // we must fetch and parse headers "manually" |
| | 1818 | if ($i<2) { |
| | 1819 | if (!$headers) { |
| | 1820 | $headers = $this->conn->fetchPartHeader( |
| | 1821 | $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| | 1822 | } |
| | 1823 | $filename_mime = ''; |
| | 1824 | $i = 0; |
| | 1825 | while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| | 1826 | $filename_mime .= $matches[1]; |
| | 1827 | $i++; |
| | 1828 | } |
| | 1829 | } |
| | 1830 | } |
| | 1831 | else if (!empty($part->d_parameters['filename*0*'])) { |
| | 1832 | $i = 0; |
| | 1833 | while (isset($part->d_parameters['filename*'.$i.'*'])) { |
| | 1834 | $filename_encoded .= $part->d_parameters['filename*'.$i.'*']; |
| | 1835 | $i++; |
| | 1836 | } |
| | 1837 | if ($i<2) { |
| | 1838 | if (!$headers) { |
| | 1839 | $headers = $this->conn->fetchPartHeader( |
| | 1840 | $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| | 1841 | } |
| | 1842 | $filename_encoded = ''; |
| | 1843 | $i = 0; $matches = array(); |
| | 1844 | while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| | 1845 | $filename_encoded .= $matches[1]; |
| | 1846 | $i++; |
| | 1847 | } |
| | 1848 | } |
| | 1849 | } |
| | 1850 | else if (!empty($part->ctype_parameters['name*0'])) { |
| | 1851 | $i = 0; |
| | 1852 | while (isset($part->ctype_parameters['name*'.$i])) { |
| | 1853 | $filename_mime .= $part->ctype_parameters['name*'.$i]; |
| | 1854 | $i++; |
| | 1855 | } |
| | 1856 | if ($i<2) { |
| | 1857 | if (!$headers) { |
| | 1858 | $headers = $this->conn->fetchPartHeader( |
| | 1859 | $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| | 1860 | } |
| | 1861 | $filename_mime = ''; |
| | 1862 | $i = 0; $matches = array(); |
| | 1863 | while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| | 1864 | $filename_mime .= $matches[1]; |
| | 1865 | $i++; |
| | 1866 | } |
| | 1867 | } |
| | 1868 | } |
| | 1869 | else if (!empty($part->ctype_parameters['name*0*'])) { |
| | 1870 | $i = 0; |
| | 1871 | while (isset($part->ctype_parameters['name*'.$i.'*'])) { |
| | 1872 | $filename_encoded .= $part->ctype_parameters['name*'.$i.'*']; |
| | 1873 | $i++; |
| | 1874 | } |
| | 1875 | if ($i<2) { |
| | 1876 | if (!$headers) { |
| | 1877 | $headers = $this->conn->fetchPartHeader( |
| | 1878 | $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| | 1879 | } |
| | 1880 | $filename_encoded = ''; |
| | 1881 | $i = 0; $matches = array(); |
| | 1882 | while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| | 1883 | $filename_encoded .= $matches[1]; |
| | 1884 | $i++; |
| | 1885 | } |
| | 1886 | } |
| | 1887 | } |
| | 1888 | // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird) |
| | 1889 | else if (!empty($part->ctype_parameters['name'])) |
| | 1890 | $filename_mime = $part->ctype_parameters['name']; |
| | 1891 | // Content-Disposition |
| | 1892 | else if (!empty($part->headers['content-description'])) |
| | 1893 | $filename_mime = $part->headers['content-description']; |
| | 1894 | else |
| | 1895 | return; |
| | 1896 | |
| | 1897 | // decode filename |
| | 1898 | if (!empty($filename_mime)) { |
| | 1899 | $part->filename = rcube_imap::decode_mime_string($filename_mime, |
| | 1900 | $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset : |
| | 1901 | rc_detect_encoding($filename_mime, $this->default_charset))); |
| | 1902 | } |
| | 1903 | else if (!empty($filename_encoded)) { |
| | 1904 | // decode filename according to RFC 2231, Section 4 |
| | 1905 | if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) { |
| | 1906 | $filename_charset = $fmatches[1]; |
| | 1907 | $filename_encoded = $fmatches[2]; |
| | 1908 | } |
| | 1909 | $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); |
| | 1910 | } |
| | 1911 | } |
| | 1912 | |
| | 1913 | |
| | 1914 | /** |
| | 1915 | * Get charset name from message structure (first part) |
| | 1916 | * |
| | 1917 | * @access private |
| | 1918 | * @param array Message structure |
| | 1919 | * @return string Charset name |
| | 1920 | */ |
| | 1921 | function _structure_charset($structure) |
| | 1922 | { |
| | 1923 | while (is_array($structure)) { |
| | 1924 | if (is_array($structure[2]) && $structure[2][0] == 'charset') |
| | 1925 | return $structure[2][1]; |
| | 1926 | $structure = $structure[0]; |
| | 1927 | } |
| | 1928 | } |
| | 1929 | |
| | 1930 | |
| | 1931 | /** |
| | 1932 | * Fetch message body of a specific message from the server |
| | 1933 | * |
| | 1934 | * @param int Message UID |
| | 1935 | * @param string Part number |
| | 1936 | * @param object rcube_message_part Part object created by get_structure() |
| | 1937 | * @param mixed True to print part, ressource to write part contents in |
| | 1938 | * @param resource File pointer to save the message part |
| | 1939 | * @return string Message/part body if not printed |
| | 1940 | */ |
| | 1941 | function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL) |
| | 1942 | { |
| | 1943 | // get part encoding if not provided |
| | 1944 | if (!is_object($o_part)) { |
| | 1945 | $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); |
| | 1946 | $structure = iml_GetRawStructureArray($structure_str); |
| | 1947 | // error or message not found |
| | 1948 | if (empty($structure)) |
| | 1949 | return false; |
| | 1950 | |
| | 1951 | $part_type = iml_GetPartTypeCode($structure, $part); |
| | 1952 | $o_part = new rcube_message_part; |
| | 1953 | $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other'); |
| | 1954 | $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part)); |
| | 1955 | $o_part->charset = iml_GetPartCharset($structure, $part); |
| | 1956 | } |
| | 1957 | |
| | 1958 | // TODO: Add caching for message parts |
| | 1959 | |
| | 1960 | if (!$part) $part = 'TEXT'; |
| | 1961 | |
| | 1962 | $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part, |
| | 1963 | $o_part->encoding, $print, $fp); |
| | 1964 | |
| | 1965 | if ($fp || $print) |
| | 1966 | return true; |
| | 1967 | |
| | 1968 | // convert charset (if text or message part) |
| | 1969 | if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { |
| | 1970 | // assume default if no charset specified |
| | 1971 | if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') |
| | 1972 | $o_part->charset = $this->default_charset; |
| | 1973 | |
| | 1974 | $body = rcube_charset_convert($body, $o_part->charset); |
| | 1975 | } |
| | 1976 | |
| | 1977 | return $body; |
| | 1978 | } |
| | 1979 | |
| | 1980 | |
| | 1981 | /** |
| | 1982 | * Fetch message body of a specific message from the server |
| | 1983 | * |
| | 1984 | * @param int Message UID |
| | 1985 | * @return string Message/part body |
| | 1986 | * @see rcube_imap::get_message_part() |
| | 1987 | */ |
| | 1988 | function &get_body($uid, $part=1) |
| | 1989 | { |
| | 1990 | $headers = $this->get_headers($uid); |
| | 1991 | return rcube_charset_convert($this->get_message_part($uid, $part, NULL), |
| | 1992 | $headers->charset ? $headers->charset : $this->default_charset); |
| | 1993 | } |
| | 1994 | |
| | 1995 | |
| | 1996 | /** |
| | 1997 | * Returns the whole message source as string |
| | 1998 | * |
| | 1999 | * @param int Message UID |
| | 2000 | * @return string Message source string |
| | 2001 | */ |
| | 2002 | function &get_raw_body($uid) |
| | 2003 | { |
| | 2004 | return $this->conn->handlePartBody($this->mailbox, $uid, true); |
| | 2005 | } |
| | 2006 | |
| | 2007 | |
| | 2008 | /** |
| | 2009 | * Returns the message headers as string |
| | 2010 | * |
| | 2011 | * @param int Message UID |
| | 2012 | * @return string Message headers string |
| | 2013 | */ |
| | 2014 | function &get_raw_headers($uid) |
| | 2015 | { |
| | 2016 | return $this->conn->fetchPartHeader($this->mailbox, $uid, true); |
| | 2017 | } |
| | 2018 | |
| | 2019 | |
| | 2020 | /** |
| | 2021 | * Sends the whole message source to stdout |
| | 2022 | * |
| | 2023 | * @param int Message UID |
| | 2024 | */ |
| | 2025 | function print_raw_body($uid) |
| | 2026 | { |
| | 2027 | $this->conn->handlePartBody($this->mailbox, $uid, true, NULL, NULL, true); |
| | 2028 | } |
| | 2029 | |
| | 2030 | |
| | 2031 | /** |
| | 2032 | * Set message flag to one or several messages |
| | 2033 | * |
| | 2034 | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| | 2035 | * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT |
| | 2036 | * @param string Folder name |
| | 2037 | * @param boolean True to skip message cache clean up |
| | 2038 | * @return int Number of flagged messages, -1 on failure |
| | 2039 | */ |
| | 2040 | function set_flag($uids, $flag, $mbox_name=NULL, $skip_cache=false) |
| | 2041 | { |
| | 2042 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 2043 | |
| | 2044 | $flag = strtoupper($flag); |
| | 2045 | list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); |
| | 2046 | |
| | 2047 | if (strpos($flag, 'UN') === 0) |
| | 2048 | $result = $this->conn->unflag($mailbox, $uids, substr($flag, 2)); |
| | 2049 | else |
| | 2050 | $result = $this->conn->flag($mailbox, $uids, $flag); |
| | 2051 | |
| | 2052 | if ($result >= 0) { |
| | 2053 | // reload message headers if cached |
| | 2054 | if ($this->caching_enabled && !$skip_cache) { |
| | 2055 | $cache_key = $mailbox.'.msg'; |
| | 2056 | if ($all_mode) |
| | 2057 | $this->clear_message_cache($cache_key); |
| | 2058 | else |
| | 2059 | $this->remove_message_cache($cache_key, explode(',', $uids)); |
| | 2060 | } |
| | 2061 | // update counters |
| | 2062 | if ($flag=='SEEN') |
| | 2063 | $this->_set_messagecount($mailbox, 'UNSEEN', $result*(-1)); |
| | 2064 | else if ($flag=='UNSEEN') |
| | 2065 | $this->_set_messagecount($mailbox, 'UNSEEN', $result); |
| | 2066 | else if ($flag=='DELETED') |
| | 2067 | $this->_set_messagecount($mailbox, 'ALL', $result*(-1)); |
| | 2068 | } |
| | 2069 | |
| | 2070 | return $result; |
| | 2071 | } |
| | 2072 | |
| | 2073 | |
| | 2074 | /** |
| | 2075 | * Remove message flag for one or several messages |
| | 2076 | * |
| | 2077 | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| | 2078 | * @param string Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT |
| | 2079 | * @param string Folder name |
| | 2080 | * @return int Number of flagged messages, -1 on failure |
| | 2081 | * @see set_flag |
| | 2082 | */ |
| | 2083 | function unset_flag($uids, $flag, $mbox_name=NULL) |
| | 2084 | { |
| | 2085 | return $this->set_flag($uids, 'UN'.$flag, $mbox_name); |
| | 2086 | } |
| | 2087 | |
| | 2088 | |
| | 2089 | /** |
| | 2090 | * Append a mail message (source) to a specific mailbox |
| | 2091 | * |
| | 2092 | * @param string Target mailbox |
| | 2093 | * @param string The message source string or filename |
| | 2094 | * @param string Headers string if $message contains only the body |
| | 2095 | * @param boolean True if $message is a filename |
| | 2096 | * |
| | 2097 | * @return boolean True on success, False on error |
| | 2098 | */ |
| | 2099 | function save_message($mbox_name, &$message, $headers='', $is_file=false) |
| | 2100 | { |
| | 2101 | $mailbox = $this->mod_mailbox($mbox_name); |
| | 2102 | |
| | 2103 | // make sure mailbox exists |
| | 2104 | if ($this->mailbox_exists($mbox_name, true)) { |
| | 2105 | if ($is_file) { |
| | 2106 | $separator = rcmail::get_instance()->config->header_delimiter(); |
| | 2107 | $saved = $this->conn->appendFromFile($mailbox, $message, |
| | 2108 | $headers, $separator.$separator); |
| | 2109 | } |
| | 2110 | else |
| | 2111 | $saved = $this->conn->append($mailbox, $message); |
| | 2112 | } |
| | 2113 | |
| | 2114 | if ($saved) { |
| | 2115 | // increase messagecount of the target mailbox |
| | 2116 | $this->_set_messagecount($mailbox, 'ALL', 1); |
| | 2117 | } |
| | 2118 | |
| | 2119 | return $saved; |
| | 2120 | } |
| | 2121 | |
| | 2122 | |
| | 2123 | /** |
| | 2124 | * Move a message from one mailbox to another |
| | 2125 | * |
| | 2126 | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| | 2127 | * @param string Target mailbox |
| | 2128 | * @param string Source mailbox |
| | 2129 | * @return boolean True on success, False on error |
| | 2130 | */ |
| | 2131 | function move_message($uids, $to_mbox, $from_mbox='') |
| | 2132 | { |
| | 2133 | $fbox = $from_mbox; |
| | 2134 | $tbox = $to_mbox; |
| | 2135 | $to_mbox = $this->mod_mailbox($to_mbox); |
| | 2136 | $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; |
| | 2137 | |
| | 2138 | list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); |
| | 2139 | |
| | 2140 | // exit if no message uids are specified |
| | 2141 | if (empty($uids)) |
| | 2142 | return false; |
| | 2143 | |
| | 2144 | // make sure mailbox exists |
| | 2145 | if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { |
| | 2146 | if (in_array($tbox, $this->default_folders)) |
| | 2147 | $this->create_mailbox($tbox, true); |
| | 2148 | else |
| | 2149 | return false; |
| | 2150 | } |
| | 2151 | |
| | 2152 | // flag messages as read before moving them |
| | 2153 | $config = rcmail::get_instance()->config; |
| | 2154 | if ($config->get('read_when_deleted') && $tbox == $config->get('trash_mbox')) { |
| | 2155 | // don't flush cache (4th argument) |
| | 2156 | $this->set_flag($uids, 'SEEN', $fbox, true); |
| | 2157 | } |
| | 2158 | |
| | 2159 | // move messages |
| | 2160 | $move = $this->conn->move($uids, $from_mbox, $to_mbox); |
| | 2161 | $moved = !($move === false || $move < 0); |
| | 2162 | |
| | 2163 | // send expunge command in order to have the moved message |
| | 2164 | // really deleted from the source mailbox |
| | 2165 | if ($moved) { |
| | 2166 | $this->_expunge($from_mbox, false, $uids); |
| | 2167 | $this->_clear_messagecount($from_mbox); |
| | 2168 | $this->_clear_messagecount($to_mbox); |
| | 2169 | } |
| | 2170 | // moving failed |
| | 2171 | else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) { |
| | 2172 | $moved = $this->delete_message($uids, $fbox); |
| | 2173 | } |
| | 2174 | |
| | 2175 | if ($moved) { |
| | 2176 | // unset threads internal cache |
| | 2177 | unset($this->icache['threads']); |
| | 2178 | |
| | 2179 | // remove message ids from search set |
| | 2180 | if ($this->search_set && $from_mbox == $this->mailbox) { |
| | 2181 | // threads are too complicated to just remove messages from set |
| | 2182 | if ($this->search_threads || $all_mode) |
| | 2183 | $this->refresh_search(); |
| | 2184 | else { |
| | 2185 | $uids = explode(',', $uids); |
| | 2186 | foreach ($uids as $uid) |
| | 2187 | $a_mids[] = $this->_uid2id($uid, $from_mbox); |
| | 2188 | $this->search_set = array_diff($this->search_set, $a_mids); |
| | 2189 | } |
| | 2190 | } |
| | 2191 | |
| | 2192 | // update cached message headers |
| | 2193 | $cache_key = $from_mbox.'.msg'; |
| | 2194 | if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { |
| | 2195 | // clear cache from the lowest index on |
| | 2196 | $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); |
| | 2197 | } |
| | 2198 | } |
| | 2199 | |
| | 2200 | return $moved; |
| | 2201 | } |
| | 2202 | |
| | 2203 | |
| | 2204 | /** |
| | 2205 | * Copy a message from one mailbox to another |
| | 2206 | * |
| | 2207 | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| | 2208 | * @param string Target mailbox |
| | 2209 | * @param string Source mailbox |
| | 2210 | * @return boolean True on success, False on error |
| | 2211 | */ |
| | 2212 | function copy_message($uids, $to_mbox, $from_mbox='') |
| | 2213 | { |
| | 2214 | $fbox = $from_mbox; |
| | 2215 | $tbox = $to_mbox; |
| | 2216 | $to_mbox = $this->mod_mailbox($to_mbox); |
| | 2217 | $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; |
| | 2218 | |
| | 2219 | list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); |
| | 2220 | |
| | 2221 | // exit if no message uids are specified |
| | 2222 | if (empty($uids)) |
| | 2223 | return false; |
| | 2224 | |
| | 2225 | // make sure mailbox exists |
| | 2226 | if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { |
| | 2227 | if (in_array($tbox, $this->default_folders)) |
| | 2228 | $this->create_mailbox($tbox, true); |
| | 2229 | else |
| | 2230 | return false; |
| | 2231 | } |
| | 2232 | |
| | 2233 | // copy messages |
| | 2234 | $copy = $this->conn->copy($uids, $from_mbox, $to_mbox); |
| | 2235 | $copied = !($copy === false || $copy < 0); |
| | 2236 | |
| | 2237 | if ($copied) { |
| | 2238 | $this->_clear_messagecount($to_mbox); |
| | 2239 | } |
| | 2240 | |
| | 2241 | return $copied; |
| | 2242 | } |
| | 2243 | |
| | 2244 | |
| | 2245 | /** |
| | 2246 | * Mark messages as deleted and expunge mailbox |
| | 2247 | * |
| | 2248 | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| | 2249 | * @param string Source mailbox |
| | 2250 | * @return boolean True on success, False on error |
| | 2251 | */ |
| | 2252 | function delete_message($uids, $mbox_name='') |
| | 2253 | { |
| | 2254 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 2255 | |
| | 2256 | list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); |
| | 2257 | |
| | 2258 | // exit if no message uids are specified |
| | 2259 | if (empty($uids)) |
| | 2260 | return false; |
| | 2261 | |
| | 2262 | $deleted = $this->conn->delete($mailbox, $uids); |
| | 2263 | |
| | 2264 | if ($deleted) { |
| | 2265 | // send expunge command in order to have the deleted message |
| | 2266 | // really deleted from the mailbox |
| | 2267 | $this->_expunge($mailbox, false, $uids); |
| | 2268 | $this->_clear_messagecount($mailbox); |
| | 2269 | unset($this->uid_id_map[$mailbox]); |
| | 2270 | |
| | 2271 | // unset threads internal cache |
| | 2272 | unset($this->icache['threads']); |
| | 2273 | |
| | 2274 | // remove message ids from search set |
| | 2275 | if ($this->search_set && $mailbox == $this->mailbox) { |
| | 2276 | // threads are too complicated to just remove messages from set |
| | 2277 | if ($this->search_threads || $all_mode) |
| | 2278 | $this->refresh_search(); |
| | 2279 | else { |
| | 2280 | $uids = explode(',', $uids); |
| | 2281 | foreach ($uids as $uid) |
| | 2282 | $a_mids[] = $this->_uid2id($uid, $mailbox); |
| | 2283 | $this->search_set = array_diff($this->search_set, $a_mids); |
| | 2284 | } |
| | 2285 | } |
| | 2286 | |
| | 2287 | // remove deleted messages from cache |
| | 2288 | $cache_key = $mailbox.'.msg'; |
| | 2289 | if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { |
| | 2290 | // clear cache from the lowest index on |
| | 2291 | $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); |
| | 2292 | } |
| | 2293 | } |
| | 2294 | |
| | 2295 | return $deleted; |
| | 2296 | } |
| | 2297 | |
| | 2298 | |
| | 2299 | /** |
| | 2300 | * Clear all messages in a specific mailbox |
| | 2301 | * |
| | 2302 | * @param string Mailbox name |
| | 2303 | * @return int Above 0 on success |
| | 2304 | */ |
| | 2305 | function clear_mailbox($mbox_name=NULL) |
| | 2306 | { |
| | 2307 | $mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 2308 | $msg_count = $this->_messagecount($mailbox, 'ALL'); |
| | 2309 | |
| | 2310 | if (!$msg_count) { |
| | 2311 | return 0; |
| | 2312 | } |
| | 2313 | |
| | 2314 | $cleared = $this->conn->clearFolder($mailbox); |
| | 2315 | |
| | 2316 | // make sure the message count cache is cleared as well |
| | 2317 | if ($cleared) { |
| | 2318 | $this->clear_message_cache($mailbox.'.msg'); |
| | 2319 | $a_mailbox_cache = $this->get_cache('messagecount'); |
| | 2320 | unset($a_mailbox_cache[$mailbox]); |
| | 2321 | $this->update_cache('messagecount', $a_mailbox_cache); |
| | 2322 | } |
| | 2323 | |
| | 2324 | return $cleared; |
| | 2325 | } |
| | 2326 | |
| | 2327 | |
| | 2328 | /** |
| | 2329 | * Send IMAP expunge command and clear cache |
| | 2330 | * |
| | 2331 | * @param string Mailbox name |
| | 2332 | * @param boolean False if cache should not be cleared |
| | 2333 | * @return boolean True on success |
| | 2334 | */ |
| | 2335 | function expunge($mbox_name='', $clear_cache=true) |
| | 2336 | { |
| | 2337 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 2338 | return $this->_expunge($mailbox, $clear_cache); |
| | 2339 | } |
| | 2340 | |
| | 2341 | |
| | 2342 | /** |
| | 2343 | * Send IMAP expunge command and clear cache |
| | 2344 | * |
| | 2345 | * @param string Mailbox name |
| | 2346 | * @param boolean False if cache should not be cleared |
| | 2347 | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| | 2348 | * @return boolean True on success |
| | 2349 | * @access private |
| | 2350 | * @see rcube_imap::expunge() |
| | 2351 | */ |
| | 2352 | private function _expunge($mailbox, $clear_cache=true, $uids=NULL) |
| | 2353 | { |
| | 2354 | if ($uids && $this->get_capability('UIDPLUS')) |
| | 2355 | $a_uids = is_array($uids) ? join(',', $uids) : $uids; |
| | 2356 | else |
| | 2357 | $a_uids = NULL; |
| | 2358 | |
| | 2359 | $result = $this->conn->expunge($mailbox, $a_uids); |
| | 2360 | |
| | 2361 | if ($result>=0 && $clear_cache) { |
| | 2362 | $this->clear_message_cache($mailbox.'.msg'); |
| | 2363 | $this->_clear_messagecount($mailbox); |
| | 2364 | } |
| | 2365 | |
| | 2366 | return $result; |
| | 2367 | } |
| | 2368 | |
| | 2369 | |
| | 2370 | /** |
| | 2371 | * Parse message UIDs input |
| | 2372 | * |
| | 2373 | * @param mixed UIDs array or comma-separated list or '*' or '1:*' |
| | 2374 | * @param string Mailbox name |
| | 2375 | * @return array Two elements array with UIDs converted to list and ALL flag |
| | 2376 | * @access private |
| | 2377 | */ |
| | 2378 | private function _parse_uids($uids, $mailbox) |
| | 2379 | { |
| | 2380 | if ($uids === '*' || $uids === '1:*') { |
| | 2381 | if (empty($this->search_set)) { |
| | 2382 | $uids = '1:*'; |
| | 2383 | $all = true; |
| | 2384 | } |
| | 2385 | // get UIDs from current search set |
| | 2386 | // @TODO: skip fetchUIDs() and work with IDs instead of UIDs (?) |
| | 2387 | else { |
| | 2388 | if ($this->search_threads) |
| | 2389 | $uids = $this->conn->fetchUIDs($mailbox, array_keys($this->search_set['depth'])); |
| | 2390 | else |
| | 2391 | $uids = $this->conn->fetchUIDs($mailbox, $this->search_set); |
| | 2392 | |
| | 2393 | // save ID-to-UID mapping in local cache |
| | 2394 | if (is_array($uids)) |
| | 2395 | foreach ($uids as $id => $uid) |
| | 2396 | $this->uid_id_map[$mailbox][$uid] = $id; |
| | 2397 | |
| | 2398 | $uids = join(',', $uids); |
| | 2399 | } |
| | 2400 | } |
| | 2401 | else { |
| | 2402 | if (is_array($uids)) |
| | 2403 | $uids = join(',', $uids); |
| | 2404 | |
| | 2405 | if (preg_match('/[^0-9,]/', $uids)) |
| | 2406 | $uids = ''; |
| | 2407 | } |
| | 2408 | |
| | 2409 | return array($uids, (bool) $all); |
| | 2410 | } |
| | 2411 | |
| | 2412 | |
| | 2413 | /** |
| | 2414 | * Translate UID to message ID |
| | 2415 | * |
| | 2416 | * @param int Message UID |
| | 2417 | * @param string Mailbox name |
| | 2418 | * @return int Message ID |
| | 2419 | */ |
| | 2420 | function get_id($uid, $mbox_name=NULL) |
| | 2421 | { |
| | 2422 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 2423 | return $this->_uid2id($uid, $mailbox); |
| | 2424 | } |
| | 2425 | |
| | 2426 | |
| | 2427 | /** |
| | 2428 | * Translate message number to UID |
| | 2429 | * |
| | 2430 | * @param int Message ID |
| | 2431 | * @param string Mailbox name |
| | 2432 | * @return int Message UID |
| | 2433 | */ |
| | 2434 | function get_uid($id,$mbox_name=NULL) |
| | 2435 | { |
| | 2436 | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | 2437 | return $this->_id2uid($id, $mailbox); |
| | 2438 | } |
| | 2439 | |
| | 2440 | |
| | 2441 | |
| | 2442 | /* -------------------------------- |
| | 2443 | * folder managment |
| | 2444 | * --------------------------------*/ |
| | 2445 | |
| | 2446 | /** |
| | 2447 | * Public method for mailbox listing. |
| | 2448 | * |
| | 2449 | * Converts mailbox name with root dir first |
| | 2450 | * |
| | 2451 | * @param string Optional root folder |
| | 2452 | * @param string Optional filter for mailbox listing |
| | 2453 | * @return array List of mailboxes/folders |
| | 2454 | * @access public |
| | 2455 | */ |
| | 2456 | function list_mailboxes($root='', $filter='*') |
| | 2457 | { |
| | 2458 | $a_out = array(); |
| | 2459 | $a_mboxes = $this->_list_mailboxes($root, $filter); |
| | 2460 | |
| | 2461 | foreach ($a_mboxes as $mbox_row) { |
| | 2462 | $name = $this->mod_mailbox($mbox_row, 'out'); |
| | 2463 | if (strlen($name)) |
| | 2464 | $a_out[] = $name; |
| | 2465 | } |
| | 2466 | |
| | 2467 | // INBOX should always be available |
| | 2468 | if (!in_array('INBOX', $a_out)) |
| | 2469 | array_unshift($a_out, 'INBOX'); |
| | 2470 | |
| | 2471 | // sort mailboxes |
| | 2472 | $a_out = $this->_sort_mailbox_list($a_out); |
| | 2473 | |
| | 2474 | return $a_out; |
| | 2475 | } |
| | 2476 | |
| | 2477 | |
| | 2478 | /** |
| | 2479 | * Private method for mailbox listing |
| | 2480 | * |
| | 2481 | * @return array List of mailboxes/folders |
| | 2482 | * @see rcube_imap::list_mailboxes() |
| | 2483 | * @access private |
| | 2484 | */ |
| | 2485 | private function _list_mailboxes($root='', $filter='*') |
| | 2486 | { |
| | 2487 | $a_defaults = $a_out = array(); |
| | 2488 | |
| | 2489 | // get cached folder list |
| | 2490 | $a_mboxes = $this->get_cache('mailboxes'); |
| | 2491 | if (is_array($a_mboxes)) |
| | 2492 | return $a_mboxes; |
| | 2493 | |
| | 2494 | // Give plugins a chance to provide a list of mailboxes |
| | 2495 | $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', |
| | 2496 | array('root'=>$root,'filter'=>$filter)); |
| | 2497 | |
| | 2498 | if (isset($data['folders'])) { |
| | 2499 | $a_folders = $data['folders']; |
| | 2500 | } |
| | 2501 | else { |
| | 2502 | // retrieve list of folders from IMAP server |
| | 2503 | $a_folders = $this->conn->listSubscribed($this->mod_mailbox($root), $filter); |
| | 2504 | } |
| | 2505 | |
| | 2506 | if (!is_array($a_folders) || !sizeof($a_folders)) |
| | 2507 | $a_folders = array(); |
| | 2508 | |
| | 2509 | // write mailboxlist to cache |
| | 2510 | $this->update_cache('mailboxes', $a_folders); |
| | 2511 | |
| | 2512 | return $a_folders; |
| | 2513 | } |
| | 2514 | |
| | 2515 | |
| | 2516 | /** |
| | 2517 | * Get a list of all folders available on the IMAP server |
| | 2518 | * |
| | 2519 | * @param string IMAP root dir |
| | 2520 | * @return array Indexed array with folder names |
| | 2521 | */ |
| | 2522 | function list_unsubscribed($root='') |
| | 2523 | { |
| | 2524 | static $sa_unsubscribed; |
| | 2525 | |
| | 2526 | if (is_array($sa_unsubscribed)) |
| | 2527 | return $sa_unsubscribed; |
| | 2528 | |
| | 2529 | // retrieve list of folders from IMAP server |
| | 2530 | $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), '*'); |
| | 2531 | |
| | 2532 | // modify names with root dir |
| | 2533 | foreach ($a_mboxes as $mbox_name) { |
| | 2534 | $name = $this->mod_mailbox($mbox_name, 'out'); |
| | 2535 | if (strlen($name)) |
| | 2536 | $a_folders[] = $name; |
| | 2537 | } |
| | 2538 | |
| | 2539 | // filter folders and sort them |
| | 2540 | $sa_unsubscribed = $this->_sort_mailbox_list($a_folders); |
| | 2541 | return $sa_unsubscribed; |
| | 2542 | } |
| | 2543 | |
| | 2544 | |
| | 2545 | /** |
| | 2546 | * Get mailbox quota information |
| | 2547 | * added by Nuny |
| | 2548 | * |
| | 2549 | * @return mixed Quota info or False if not supported |
| | 2550 | */ |
| | 2551 | function get_quota() |
| | 2552 | { |
| | 2553 | if ($this->get_capability('QUOTA')) |
| | 2554 | return $this->conn->getQuota(); |
| | 2555 | |
| 1735 | | // find first non-array entry |
| 1736 | | for ($i=1; $i<count($part); $i++) |
| 1737 | | if (!is_array($part[$i])) |
| 1738 | | { |
| 1739 | | $struct->ctype_secondary = strtolower($part[$i]); |
| 1740 | | break; |
| 1741 | | } |
| 1742 | | |
| 1743 | | $struct->mimetype = 'multipart/'.$struct->ctype_secondary; |
| 1744 | | |
| 1745 | | // build parts list for headers pre-fetching |
| 1746 | | for ($i=0, $count=0; $i<count($part); $i++) |
| 1747 | | if (is_array($part[$i]) && count($part[$i]) > 3) { |
| 1748 | | // fetch message headers if message/rfc822 or named part (could contain Content-Location header) |
| 1749 | | if (!is_array($part[$i][0])) { |
| 1750 | | $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; |
| 1751 | | if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') { |
| 1752 | | $raw_part_headers[] = $tmp_part_id; |
| 1753 | | $mime_part_headers[] = $tmp_part_id; |
| 1754 | | } |
| 1755 | | else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) { |
| 1756 | | $mime_part_headers[] = $tmp_part_id; |
| 1757 | | } |
| 1758 | | } |
| 1759 | | } |
| 1760 | | |
| 1761 | | // pre-fetch headers of all parts (in one command for better performance) |
| 1762 | | // @TODO: we could do this before _structure_part() call, to fetch |
| 1763 | | // headers for parts on all levels |
| 1764 | | if ($mime_part_headers) |
| 1765 | | $mime_part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, |
| 1766 | | $this->_msg_id, $mime_part_headers); |
| 1767 | | // we'll need a real content-type of message/rfc822 part |
| 1768 | | if ($raw_part_headers) |
| 1769 | | $raw_part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, |
| 1770 | | $this->_msg_id, $raw_part_headers, false); |
| 1771 | | |
| 1772 | | $struct->parts = array(); |
| 1773 | | for ($i=0, $count=0; $i<count($part); $i++) |
| 1774 | | if (is_array($part[$i]) && count($part[$i]) > 3) { |
| 1775 | | $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; |
| 1776 | | $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, |
| 1777 | | $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]); |
| 1778 | | } |
| 1779 | | |
| 1780 | | return $struct; |
| 1781 | | } |
| 1782 | | |
| 1783 | | |
| 1784 | | // regular part |
| 1785 | | $struct->ctype_primary = strtolower($part[0]); |
| 1786 | | $struct->ctype_secondary = strtolower($part[1]); |
| 1787 | | $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary; |
| 1788 | | |
| 1789 | | // read content type parameters |
| 1790 | | if (is_array($part[2])) |
| 1791 | | { |
| 1792 | | $struct->ctype_parameters = array(); |
| 1793 | | for ($i=0; $i<count($part[2]); $i+=2) |
| 1794 | | $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1]; |
| 1795 | | |
| 1796 | | if (isset($struct->ctype_parameters['charset'])) |
| 1797 | | $struct->charset = $struct->ctype_parameters['charset']; |
| 1798 | | } |
| 1799 | | |
| 1800 | | // read content encoding |
| 1801 | | if (!empty($part[5]) && $part[5]!='NIL') |
| 1802 | | { |
| 1803 | | $struct->encoding = strtolower($part[5]); |
| 1804 | | $struct->headers['content-transfer-encoding'] = $struct->encoding; |
| 1805 | | } |
| 1806 | | |
| 1807 | | // get part size |
| 1808 | | if (!empty($part[6]) && $part[6]!='NIL') |
| 1809 | | $struct->size = intval($part[6]); |
| 1810 | | |
| 1811 | | // read part disposition |
| 1812 | | $di = count($part) - 2; |
| 1813 | | if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) || |
| 1814 | | (is_array($part[--$di]) && count($part[$di]) == 2)) |
| 1815 | | { |
| 1816 | | $struct->disposition = strtolower($part[$di][0]); |
| 1817 | | |
| 1818 | | if (is_array($part[$di][1])) |
| 1819 | | for ($n=0; $n<count($part[$di][1]); $n+=2) |
| 1820 | | $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; |
| 1821 | | } |
| | 2648 | // check if mailbox children are subscribed |
| | 2649 | foreach ($a_subscribed as $c_subscribed) |
| | 2650 | if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) { |
| | 2651 | $this->conn->unsubscribe($c_subscribed); |
| | 2652 | $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/', |
| | 2653 | $abs_name, $c_subscribed)); |
| | 2654 | } |
| | 2655 | |
| | 2656 | // clear cache |
| | 2657 | $this->clear_message_cache($mailbox.'.msg'); |
| | 2658 | $this->clear_cache('mailboxes'); |
| | 2659 | } |
| | 2660 | |
| | 2661 | // try to subscribe it |
| | 2662 | if ($result && $subscribed) |
| | 2663 | $this->conn->subscribe($abs_name); |
| | 2664 | |
| | 2665 | return $result ? $name : false; |
| | 2666 | } |
| | 2667 | |
| | 2668 | |
| | 2669 | /** |
| | 2670 | * Remove mailboxes from server |
| | 2671 | * |
| | 2672 | * @param string Mailbox name(s) string/array |
| | 2673 | * @return boolean True on success |
| | 2674 | */ |
| | 2675 | function delete_mailbox($mbox_name) |
| | 2676 | { |
| | 2677 | $deleted = false; |
| | 2678 | |
| | 2679 | if (is_array($mbox_name)) |
| | 2680 | $a_mboxes = $mbox_name; |
| | 2681 | else if (is_string($mbox_name) && strlen($mbox_name)) |
| | 2682 | $a_mboxes = explode(',', $mbox_name); |
| | 2683 | |
| | 2684 | if (is_array($a_mboxes)) { |
| | 2685 | foreach ($a_mboxes as $mbox_name) { |
| | 2686 | $mailbox = $this->mod_mailbox($mbox_name); |
| | 2687 | $sub_mboxes = $this->conn->listMailboxes($this->mod_mailbox(''), |
| | 2688 | $mbox_name . $this->delimiter . '*'); |
| | 2689 | |
| | 2690 | // unsubscribe mailbox before deleting |
| | 2691 | $this->conn->unsubscribe($mailbox); |
| | 2692 | |
| | 2693 | // send delete command to server |
| | 2694 | $result = $this->conn->deleteFolder($mailbox); |
| | 2695 | if ($result >= 0) { |
| | 2696 | $deleted = true; |
| | 2697 | $this->clear_message_cache($mailbox.'.msg'); |
| | 2698 | } |
| | 2699 | |
| | 2700 | foreach ($sub_mboxes as $c_mbox) { |
| | 2701 | if ($c_mbox != 'INBOX') { |
| | 2702 | $this->conn->unsubscribe($c_mbox); |
| | 2703 | $result = $this->conn->deleteFolder($c_mbox); |
| | 2704 | if ($result >= 0) { |
| | 2705 | $deleted = true; |
| | 2706 | $this->clear_message_cache($c_mbox.'.msg'); |
| | 2707 | } |
| | 2708 | } |
| | 2709 | } |
| | 2710 | } |
| | 2711 | } |
| | 2712 | |
| | 2713 | // clear mailboxlist cache |
| | 2714 | if ($deleted) |
| | 2715 | $this->clear_cache('mailboxes'); |
| | 2716 | |
| | 2717 | return $deleted; |
| | 2718 | } |
| | 2719 | |
| | 2720 | |
| | 2721 | /** |
| | 2722 | * Create all folders specified as default |
| | 2723 | */ |
| | 2724 | function create_default_folders() |
| | 2725 | { |
| | 2726 | // create default folders if they do not exist |
| | 2727 | foreach ($this->default_folders as $folder) { |
| | 2728 | if (!$this->mailbox_exists($folder)) |
| | 2729 | $this->create_mailbox($folder, true); |
| | 2730 | else if (!$this->mailbox_exists($folder, true)) |
| | 2731 | $this->subscribe($folder); |
| | 2732 | } |
| | 2733 | } |
| | 2734 | |
| | 2735 | |
| | 2736 | /** |
| | 2737 | * Checks if folder exists and is subscribed |
| | 2738 | * |
| | 2739 | * @param string Folder name |
| | 2740 | * @param boolean Enable subscription checking |
| | 2741 | * @return boolean TRUE or FALSE |
| | 2742 | */ |
| | 2743 | function mailbox_exists($mbox_name, $subscription=false) |
| | 2744 | { |
| | 2745 | if ($mbox_name) { |
| | 2746 | if ($mbox_name == 'INBOX') |
| | 2747 | return true; |
| | 2748 | |
| | 2749 | if ($subscription) { |
| | 2750 | if ($a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name)) |
| | 2751 | return true; |
| | 2752 | } |
| | 2753 | else { |
| | 2754 | $a_folders = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_mbox); |
| | 2755 | |
| | 2756 | if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) |
| | 2757 | return true; |
| | 2758 | } |
| | 2759 | } |
| | 2760 | |
| | 2761 | return false; |
| | 2762 | } |
| | 2763 | |
| | 2764 | |
| | 2765 | /** |
| | 2766 | * Modify folder name for input/output according to root dir and namespace |
| | 2767 | * |
| | 2768 | * @param string Folder name |
| | 2769 | * @param string Mode |
| | 2770 | * @return string Folder name |
| | 2771 | */ |
| | 2772 | function mod_mailbox($mbox_name, $mode='in') |
| | 2773 | { |
| | 2774 | if ($mbox_name == 'INBOX') |
| | 2775 | return $mbox_name; |
| | 2776 | |
| | 2777 | if (!empty($this->root_dir)) { |
| | 2778 | if ($mode=='in') |
| | 2779 | $mbox_name = $this->root_dir.$this->delimiter.$mbox_name; |
| | 2780 | else if (!empty($mbox_name)) // $mode=='out' |
| | 2781 | $mbox_name = substr($mbox_name, strlen($this->root_dir)+1); |
| | 2782 | } |
| | 2783 | |
| | 2784 | return $mbox_name; |
| | 2785 | } |
| | 2786 | |
| | 2787 | |
| | 2788 | /* -------------------------------- |
| | 2789 | * internal caching methods |
| | 2790 | * --------------------------------*/ |
| | 2791 | |
| | 2792 | /** |
| | 2793 | * @access public |
| | 2794 | */ |
| | 2795 | function set_caching($set) |
| | 2796 | { |
| | 2797 | if ($set && is_object($this->db)) |
| | 2798 | $this->caching_enabled = true; |
| | 2799 | else |
| | 2800 | $this->caching_enabled = false; |
| | 2801 | } |
| | 2802 | |
| | 2803 | /** |
| | 2804 | * @access public |
| | 2805 | */ |
| | 2806 | function get_cache($key) |
| | 2807 | { |
| | 2808 | // read cache (if it was not read before) |
| | 2809 | if (!count($this->cache) && $this->caching_enabled) { |
| | 2810 | return $this->_read_cache_record($key); |
| | 2811 | } |
| | 2812 | |
| | 2813 | return $this->cache[$key]; |
| | 2814 | } |
| | 2815 | |
| | 2816 | /** |
| | 2817 | * @access private |
| | 2818 | */ |
| | 2819 | private function update_cache($key, $data) |
| | 2820 | { |
| | 2821 | $this->cache[$key] = $data; |
| | 2822 | $this->cache_changed = true; |
| | 2823 | $this->cache_changes[$key] = true; |
| | 2824 | } |
| | 2825 | |
| | 2826 | /** |
| | 2827 | * @access private |
| | 2828 | */ |
| | 2829 | private function write_cache() |
| | 2830 | { |
| | 2831 | if ($this->caching_enabled && $this->cache_changed) { |
| | 2832 | foreach ($this->cache as $key => $data) { |
| | 2833 | if ($this->cache_changes[$key]) |
| | 2834 | $this->_write_cache_record($key, serialize($data)); |
| | 2835 | } |
| | 2836 | } |
| | 2837 | } |
| | 2838 | |
| | 2839 | /** |
| | 2840 | * @access public |
| | 2841 | */ |
| | 2842 | function clear_cache($key=NULL) |
| | 2843 | { |
| | 2844 | if (!$this->caching_enabled) |
| | 2845 | return; |
| | 2846 | |
| | 2847 | if ($key===NULL) { |
| | 2848 | foreach ($this->cache as $key => $data) |
| | 2849 | $this->_clear_cache_record($key); |
| | 2850 | |
| | 2851 | $this->cache = array(); |
| | 2852 | $this->cache_changed = false; |
| | 2853 | $this->cache_changes = array(); |
| | 2854 | } |
| | 2855 | else { |
| | 2856 | $this->_clear_cache_record($key); |
| | 2857 | $this->cache_changes[$key] = false; |
| | 2858 | unset($this->cache[$key]); |
| | 2859 | } |
| | 2860 | } |
| | 2861 | |
| | 2862 | /** |
| | 2863 | * @access private |
| | 2864 | */ |
| | 2865 | private function _read_cache_record($key) |
| | 2866 | { |
| | 2867 | if ($this->db) { |
| | 2868 | // get cached data from DB |
| | 2869 | $sql_result = $this->db->query( |
| | 2870 | "SELECT cache_id, data, cache_key ". |
| | 2871 | "FROM ".get_table_name('cache'). |
| | 2872 | " WHERE user_id=? ". |
| | 2873 | "AND cache_key LIKE 'IMAP.%'", |
| | 2874 | $_SESSION['user_id']); |
| | 2875 | |
| | 2876 | while ($sql_arr = $this->db->fetch_assoc($sql_result)) { |
| | 2877 | $sql_key = preg_replace('/^IMAP\./', '', $sql_arr['cache_key']); |
| | 2878 | $this->cache_keys[$sql_key] = $sql_arr['cache_id']; |
| | 2879 | if (!isset($this->cache[$sql_key])) |
| | 2880 | $this->cache[$sql_key] = $sql_arr['data'] ? unserialize($sql_arr['data']) : false; |
| | 2881 | } |
| | 2882 | } |
| | 2883 | |
| | 2884 | return $this->cache[$key]; |
| | 2885 | } |
| | 2886 | |
| | 2887 | /** |
| | 2888 | * @access private |
| | 2889 | */ |
| | 2890 | private function _write_cache_record($key, $data) |
| | 2891 | { |
| | 2892 | if (!$this->db) |
| | 2893 | return false; |
| | 2894 | |
| | 2895 | // update existing cache record |
| | 2896 | if ($this->cache_keys[$key]) { |
| | 2897 | $this->db->query( |
| | 2898 | "UPDATE ".get_table_name('cache'). |
| | 2899 | " SET created=". $this->db->now().", data=? ". |
| | 2900 | "WHERE user_id=? ". |
| | 2901 | "AND cache_key=?", |
| | 2902 | $data, |
| | 2903 | $_SESSION['user_id'], |
| | 2904 | 'IMAP.'.$key); |
| | 2905 | } |
| | 2906 | // add new cache record |
| | 2907 | else { |
| | 2908 | $this->db->query( |
| | 2909 | "INSERT INTO ".get_table_name('cache'). |
| | 2910 | " (created, user_id, cache_key, data) ". |
| | 2911 | "VALUES (".$this->db->now().", ?, ?, ?)", |
| | 2912 | $_SESSION['user_id'], |
| | 2913 | 'IMAP.'.$key, |
| | 2914 | $data); |
| | 2915 | |
| | 2916 | // get cache entry ID for this key |
| | 2917 | $sql_result = $this->db->query( |
| | 2918 | "SELECT cache_id ". |
| | 2919 | "FROM ".get_table_name('cache'). |
| | 2920 | " WHERE user_id=? ". |
| | 2921 | "AND cache_key=?", |
| | 2922 | $_SESSION['user_id'], |
| | 2923 | 'IMAP.'.$key); |
| | 2924 | |
| | 2925 | if ($sql_arr = $this->db->fetch_assoc($sql_result)) |
| | 2926 | $this->cache_keys[$key] = $sql_arr['cache_id']; |
| | 2927 | } |
| | 2928 | } |
| | 2929 | |
| | 2930 | /** |
| | 2931 | * @access private |
| | 2932 | */ |
| | 2933 | private function _clear_cache_record($key) |
| | 2934 | { |
| | 2935 | $this->db->query( |
| | 2936 | "DELETE FROM ".get_table_name('cache'). |
| | 2937 | " WHERE user_id=? ". |
| | 2938 | "AND cache_key=?", |
| | 2939 | $_SESSION['user_id'], |
| | 2940 | 'IMAP.'.$key); |
| 1823 | | // get child parts |
| 1824 | | if (is_array($part[8]) && $di != 8) |
| 1825 | | { |
| 1826 | | $struct->parts = array(); |
| 1827 | | for ($i=0, $count=0; $i<count($part[8]); $i++) |
| 1828 | | if (is_array($part[8][$i]) && count($part[8][$i]) > 5) |
| 1829 | | $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id); |
| 1830 | | } |
| 1831 | | |
| 1832 | | // get part ID |
| 1833 | | if (!empty($part[3]) && $part[3]!='NIL') |
| 1834 | | { |
| 1835 | | $struct->content_id = $part[3]; |
| 1836 | | $struct->headers['content-id'] = $part[3]; |
| 1837 | | |
| 1838 | | if (empty($struct->disposition)) |
| 1839 | | $struct->disposition = 'inline'; |
| 1840 | | } |
| 1841 | | |
| 1842 | | // fetch message headers if message/rfc822 or named part (could contain Content-Location header) |
| 1843 | | if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { |
| 1844 | | if (empty($mime_headers)) |
| 1845 | | $mime_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $struct->mime_id); |
| 1846 | | $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; |
| 1847 | | |
| 1848 | | // get real headers for message of type 'message/rfc822' |
| 1849 | | if ($struct->mimetype == 'message/rfc822') { |
| 1850 | | if (empty($raw_headers)) |
| 1851 | | $raw_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false); |
| 1852 | | $struct->real_headers = $this->_parse_headers($raw_headers); |
| 1853 | | |
| 1854 | | // get real content-type of message/rfc822 |
| 1855 | | if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) { |
| 1856 | | $struct->real_mimetype = strtolower($matches[1]); |
| 1857 | | } |
| 1858 | | } |
| 1859 | | } |
| 1860 | | |
| 1861 | | if ($struct->ctype_primary=='message') { |
| 1862 | | if (is_array($part[8]) && $di != 8 && empty($struct->parts)) |
| 1863 | | $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id); |
| 1864 | | } |
| 1865 | | |
| 1866 | | // normalize filename property |
| 1867 | | $this->_set_part_filename($struct, $mime_headers); |
| 1868 | | |
| 1869 | | return $struct; |
| 1870 | | } |
| 1871 | | |
| 1872 | | |
| 1873 | | /** |
| 1874 | | * Set attachment filename from message part structure |
| 1875 | | * |
| 1876 | | * @access private |
| 1877 | | * @param object rcube_message_part Part object |
| 1878 | | * @param string Part's raw headers |
| 1879 | | */ |
| 1880 | | private function _set_part_filename(&$part, $headers=null) |
| 1881 | | { |
| 1882 | | if (!empty($part->d_parameters['filename'])) |
| 1883 | | $filename_mime = $part->d_parameters['filename']; |
| 1884 | | else if (!empty($part->d_parameters['filename*'])) |
| 1885 | | $filename_encoded = $part->d_parameters['filename*']; |
| 1886 | | else if (!empty($part->ctype_parameters['name*'])) |
| 1887 | | $filename_encoded = $part->ctype_parameters['name*']; |
| 1888 | | // RFC2231 value continuations |
| 1889 | | // TODO: this should be rewrited to support RFC2231 4.1 combinations |
| 1890 | | else if (!empty($part->d_parameters['filename*0'])) { |
| 1891 | | $i = 0; |
| 1892 | | while (isset($part->d_parameters['filename*'.$i])) { |
| 1893 | | $filename_mime .= $part->d_parameters['filename*'.$i]; |
| 1894 | | $i++; |
| 1895 | | } |
| 1896 | | // some servers (eg. dovecot-1.x) have no support for parameter value continuations |
| 1897 | | // we must fetch and parse headers "manually" |
| 1898 | | if ($i<2) { |
| 1899 | | if (!$headers) |
| 1900 | | $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| 1901 | | $filename_mime = ''; |
| 1902 | | $i = 0; |
| 1903 | | while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| 1904 | | $filename_mime .= $matches[1]; |
| 1905 | | $i++; |
| 1906 | | } |
| 1907 | | } |
| 1908 | | } |
| 1909 | | else if (!empty($part->d_parameters['filename*0*'])) { |
| 1910 | | $i = 0; |
| 1911 | | while (isset($part->d_parameters['filename*'.$i.'*'])) { |
| 1912 | | $filename_encoded .= $part->d_parameters['filename*'.$i.'*']; |
| 1913 | | $i++; |
| 1914 | | } |
| 1915 | | if ($i<2) { |
| 1916 | | if (!$headers) |
| 1917 | | $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| 1918 | | $filename_encoded = ''; |
| 1919 | | $i = 0; $matches = array(); |
| 1920 | | while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| 1921 | | $filename_encoded .= $matches[1]; |
| 1922 | | $i++; |
| 1923 | | } |
| 1924 | | } |
| 1925 | | } |
| 1926 | | else if (!empty($part->ctype_parameters['name*0'])) { |
| 1927 | | $i = 0; |
| 1928 | | while (isset($part->ctype_parameters['name*'.$i])) { |
| 1929 | | $filename_mime .= $part->ctype_parameters['name*'.$i]; |
| 1930 | | $i++; |
| 1931 | | } |
| 1932 | | if ($i<2) { |
| 1933 | | if (!$headers) |
| 1934 | | $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| 1935 | | $filename_mime = ''; |
| 1936 | | $i = 0; $matches = array(); |
| 1937 | | while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| 1938 | | $filename_mime .= $matches[1]; |
| 1939 | | $i++; |
| 1940 | | } |
| 1941 | | } |
| 1942 | | } |
| 1943 | | else if (!empty($part->ctype_parameters['name*0*'])) { |
| 1944 | | $i = 0; |
| 1945 | | while (isset($part->ctype_parameters['name*'.$i.'*'])) { |
| 1946 | | $filename_encoded .= $part->ctype_parameters['name*'.$i.'*']; |
| 1947 | | $i++; |
| 1948 | | } |
| 1949 | | if ($i<2) { |
| 1950 | | if (!$headers) |
| 1951 | | $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); |
| 1952 | | $filename_encoded = ''; |
| 1953 | | $i = 0; $matches = array(); |
| 1954 | | while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { |
| 1955 | | $filename_encoded .= $matches[1]; |
| 1956 | | $i++; |
| 1957 | | } |
| 1958 | | } |
| 1959 | | } |
| 1960 | | // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird) |
| 1961 | | else if (!empty($part->ctype_parameters['name'])) |
| 1962 | | $filename_mime = $part->ctype_parameters['name']; |
| 1963 | | // Content-Disposition |
| 1964 | | else if (!empty($part->headers['content-description'])) |
| 1965 | | $filename_mime = $part->headers['content-description']; |
| 1966 | | else |
| 1967 | | return; |
| 1968 | | |
| 1969 | | // decode filename |
| 1970 | | if (!empty($filename_mime)) { |
| 1971 | | $part->filename = rcube_imap::decode_mime_string($filename_mime, |
| 1972 | | $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset : |
| 1973 | | rc_detect_encoding($filename_mime, $this->default_charset))); |
| 1974 | | } |
| 1975 | | else if (!empty($filename_encoded)) { |
| 1976 | | // decode filename according to RFC 2231, Section 4 |
| 1977 | | if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) { |
| 1978 | | $filename_charset = $fmatches[1]; |
| 1979 | | $filename_encoded = $fmatches[2]; |
| 1980 | | } |
| 1981 | | $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); |
| 1982 | | } |
| 1983 | | } |
| 1984 | | |
| 1985 | | |
| 1986 | | /** |
| 1987 | | * Get charset name from message structure (first part) |
| 1988 | | * |
| 1989 | | * @access private |
| 1990 | | * @param array Message structure |
| 1991 | | * @return string Charset name |
| 1992 | | */ |
| 1993 | | function _structure_charset($structure) |
| 1994 | | { |
| 1995 | | while (is_array($structure)) { |
| 1996 | | if (is_array($structure[2]) && $structure[2][0] == 'charset') |
| 1997 | | return $structure[2][1]; |
| 1998 | | $structure = $structure[0]; |
| 1999 | | } |
| 2000 | | } |
| 2001 | | |
| 2002 | | |
| 2003 | | /** |
| 2004 | | * Fetch message body of a specific message from the server |
| 2005 | | * |
| 2006 | | * @param int Message UID |
| 2007 | | * @param string Part number |
| 2008 | | * @param object rcube_message_part Part object created by get_structure() |
| 2009 | | * @param mixed True to print part, ressource to write part contents in |
| 2010 | | * @param resource File pointer to save the message part |
| 2011 | | * @return string Message/part body if not printed |
| 2012 | | */ |
| 2013 | | function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL) |
| 2014 | | { |
| 2015 | | // get part encoding if not provided |
| 2016 | | if (!is_object($o_part)) |
| 2017 | | { |
| 2018 | | $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); |
| 2019 | | $structure = iml_GetRawStructureArray($structure_str); |
| 2020 | | // error or message not found |
| 2021 | | if (empty($structure)) |
| 2022 | | return false; |
| 2023 | | |
| 2024 | | $part_type = iml_GetPartTypeCode($structure, $part); |
| 2025 | | $o_part = new rcube_message_part; |
| 2026 | | $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other'); |
| 2027 | | $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part)); |
| 2028 | | $o_part->charset = iml_GetPartCharset($structure, $part); |
| 2029 | | } |
| | 2942 | unset($this->cache_keys[$key]); |
| | 2943 | } |
| | 2944 | |
| | 2945 | |
| | 2946 | |
| | 2947 | /* -------------------------------- |
| | 2948 | * message caching methods |
| | 2949 | * --------------------------------*/ |
| | 2950 | |
| | 2951 | /** |
| | 2952 | * Checks if the cache is up-to-date |
| | 2953 | * |
| | 2954 | * @param string Mailbox name |
| | 2955 | * @param string Internal cache key |
| | 2956 | * @return int Cache status: -3 = off, -2 = incomplete, -1 = dirty, 1 = OK |
| | 2957 | */ |
| | 2958 | private function check_cache_status($mailbox, $cache_key) |
| | 2959 | { |
| | 2960 | if (!$this->caching_enabled) |
| | 2961 | return -3; |
| | 2962 | |
| | 2963 | $cache_index = $this->get_message_cache_index($cache_key); |
| | 2964 | $msg_count = $this->_messagecount($mailbox); |
| | 2965 | $cache_count = count($cache_index); |
| | 2966 | |
| | 2967 | // empty mailbox |
| | 2968 | if (!$msg_count) |
| | 2969 | return $cache_count ? -2 : 1; |
| | 2970 | |
| | 2971 | // @TODO: We've got one big performance problem in cache status checking method |
| | 2972 | // E.g. mailbox contains 1000 messages, in cache table we've got first 100 |
| | 2973 | // of them. Now if we want to display only that 100 (which we've got) |
| | 2974 | // check_cache_status returns 'incomplete' and messages are fetched |
| | 2975 | // from IMAP instead of DB. |
| | 2976 | |
| | 2977 | if ($cache_count==$msg_count) { |
| | 2978 | if ($this->skip_deleted) { |
| | 2979 | $h_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); |
| | 2980 | |
| | 2981 | if (empty($h_index)) |
| | 2982 | return -2; |
| | 2983 | |
| | 2984 | if (sizeof($h_index) == $cache_count) { |
| | 2985 | $cache_index = array_flip($cache_index); |
| | 2986 | foreach ($h_index as $idx => $uid) |
| | 2987 | unset($cache_index[$uid]); |
| | 2988 | |
| | 2989 | if (empty($cache_index)) |
| | 2990 | return 1; |
| | 2991 | } |
| | 2992 | return -2; |
| | 2993 | } else { |
| | 2994 | // get UID of message with highest index |
| | 2995 | $uid = $this->conn->ID2UID($mailbox, $msg_count); |
| | 2996 | $cache_uid = array_pop($cache_index); |
| 2132 | | $this->remove_message_cache($cache_key, explode(',', $uids)); |
| 2133 | | } |
| 2134 | | // update counters |
| 2135 | | if ($flag=='SEEN') |
| 2136 | | $this->_set_messagecount($mailbox, 'UNSEEN', $result*(-1)); |
| 2137 | | else if ($flag=='UNSEEN') |
| 2138 | | $this->_set_messagecount($mailbox, 'UNSEEN', $result); |
| 2139 | | else if ($flag=='DELETED') |
| 2140 | | $this->_set_messagecount($mailbox, 'ALL', $result*(-1)); |
| 2141 | | } |
| 2142 | | |
| 2143 | | return $result; |
| 2144 | | } |
| 2145 | | |
| 2146 | | |
| 2147 | | /** |
| 2148 | | * Remove message flag for one or several messages |
| 2149 | | * |
| 2150 | | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| 2151 | | * @param string Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT |
| 2152 | | * @param string Folder name |
| 2153 | | * @return int Number of flagged messages, -1 on failure |
| 2154 | | * @see set_flag |
| 2155 | | */ |
| 2156 | | function unset_flag($uids, $flag, $mbox_name=NULL) |
| 2157 | | { |
| 2158 | | return $this->set_flag($uids, 'UN'.$flag, $mbox_name); |
| 2159 | | } |
| 2160 | | |
| 2161 | | |
| 2162 | | /** |
| 2163 | | * Append a mail message (source) to a specific mailbox |
| 2164 | | * |
| 2165 | | * @param string Target mailbox |
| 2166 | | * @param string The message source string or filename |
| 2167 | | * @param string Headers string if $message contains only the body |
| 2168 | | * @param boolean True if $message is a filename |
| 2169 | | * |
| 2170 | | * @return boolean True on success, False on error |
| 2171 | | */ |
| 2172 | | function save_message($mbox_name, &$message, $headers='', $is_file=false) |
| 2173 | | { |
| 2174 | | $mailbox = $this->mod_mailbox($mbox_name); |
| 2175 | | |
| 2176 | | // make sure mailbox exists |
| 2177 | | if ($this->mailbox_exists($mbox_name, true)) { |
| 2178 | | if ($is_file) { |
| 2179 | | $separator = rcmail::get_instance()->config->header_delimiter(); |
| 2180 | | $saved = iil_C_AppendFromFile($this->conn, $mailbox, $message, |
| 2181 | | $headers, $separator.$separator); |
| 2182 | | } |
| 2183 | | else |
| 2184 | | $saved = iil_C_Append($this->conn, $mailbox, $message); |
| 2185 | | } |
| 2186 | | |
| 2187 | | if ($saved) |
| 2188 | | { |
| 2189 | | // increase messagecount of the target mailbox |
| 2190 | | $this->_set_messagecount($mailbox, 'ALL', 1); |
| 2191 | | } |
| 2192 | | |
| 2193 | | return $saved; |
| 2194 | | } |
| 2195 | | |
| 2196 | | |
| 2197 | | /** |
| 2198 | | * Move a message from one mailbox to another |
| 2199 | | * |
| 2200 | | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| 2201 | | * @param string Target mailbox |
| 2202 | | * @param string Source mailbox |
| 2203 | | * @return boolean True on success, False on error |
| 2204 | | */ |
| 2205 | | function move_message($uids, $to_mbox, $from_mbox='') |
| 2206 | | { |
| 2207 | | $fbox = $from_mbox; |
| 2208 | | $tbox = $to_mbox; |
| 2209 | | $to_mbox = $this->mod_mailbox($to_mbox); |
| 2210 | | $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; |
| 2211 | | |
| 2212 | | list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); |
| 2213 | | |
| 2214 | | // exit if no message uids are specified |
| 2215 | | if (empty($uids)) |
| 2216 | | return false; |
| 2217 | | |
| 2218 | | // make sure mailbox exists |
| 2219 | | if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) |
| 2220 | | { |
| 2221 | | if (in_array($tbox, $this->default_folders)) |
| 2222 | | $this->create_mailbox($tbox, true); |
| 2223 | | else |
| 2224 | | return false; |
| 2225 | | } |
| 2226 | | |
| 2227 | | // flag messages as read before moving them |
| 2228 | | $config = rcmail::get_instance()->config; |
| 2229 | | if ($config->get('read_when_deleted') && $tbox == $config->get('trash_mbox')) { |
| 2230 | | // don't flush cache (4th argument) |
| 2231 | | $this->set_flag($uids, 'SEEN', $fbox, true); |
| 2232 | | } |
| 2233 | | |
| 2234 | | // move messages |
| 2235 | | $iil_move = iil_C_Move($this->conn, $uids, $from_mbox, $to_mbox); |
| 2236 | | $moved = !($iil_move === false || $iil_move < 0); |
| 2237 | | |
| 2238 | | // send expunge command in order to have the moved message |
| 2239 | | // really deleted from the source mailbox |
| 2240 | | if ($moved) { |
| 2241 | | $this->_expunge($from_mbox, false, $uids); |
| 2242 | | $this->_clear_messagecount($from_mbox); |
| 2243 | | $this->_clear_messagecount($to_mbox); |
| 2244 | | } |
| 2245 | | // moving failed |
| 2246 | | else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) { |
| 2247 | | $moved = $this->delete_message($uids, $fbox); |
| 2248 | | } |
| 2249 | | |
| 2250 | | if ($moved) { |
| 2251 | | // unset threads internal cache |
| 2252 | | unset($this->icache['threads']); |
| 2253 | | |
| 2254 | | // remove message ids from search set |
| 2255 | | if ($this->search_set && $from_mbox == $this->mailbox) { |
| 2256 | | // threads are too complicated to just remove messages from set |
| 2257 | | if ($this->search_threads || $all_mode) |
| 2258 | | $this->refresh_search(); |
| 2259 | | else { |
| 2260 | | $uids = explode(',', $uids); |
| 2261 | | foreach ($uids as $uid) |
| 2262 | | $a_mids[] = $this->_uid2id($uid, $from_mbox); |
| 2263 | | $this->search_set = array_diff($this->search_set, $a_mids); |
| 2264 | | } |
| 2265 | | } |
| 2266 | | |
| 2267 | | // update cached message headers |
| 2268 | | $cache_key = $from_mbox.'.msg'; |
| 2269 | | if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { |
| 2270 | | // clear cache from the lowest index on |
| 2271 | | $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); |
| 2272 | | } |
| 2273 | | } |
| 2274 | | |
| 2275 | | return $moved; |
| 2276 | | } |
| 2277 | | |
| 2278 | | |
| 2279 | | /** |
| 2280 | | * Copy a message from one mailbox to another |
| 2281 | | * |
| 2282 | | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| 2283 | | * @param string Target mailbox |
| 2284 | | * @param string Source mailbox |
| 2285 | | * @return boolean True on success, False on error |
| 2286 | | */ |
| 2287 | | function copy_message($uids, $to_mbox, $from_mbox='') |
| 2288 | | { |
| 2289 | | $fbox = $from_mbox; |
| 2290 | | $tbox = $to_mbox; |
| 2291 | | $to_mbox = $this->mod_mailbox($to_mbox); |
| 2292 | | $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; |
| 2293 | | |
| 2294 | | list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); |
| 2295 | | |
| 2296 | | // exit if no message uids are specified |
| 2297 | | if (empty($uids)) |
| 2298 | | return false; |
| 2299 | | |
| 2300 | | // make sure mailbox exists |
| 2301 | | if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) |
| 2302 | | { |
| 2303 | | if (in_array($tbox, $this->default_folders)) |
| 2304 | | $this->create_mailbox($tbox, true); |
| 2305 | | else |
| 2306 | | return false; |
| 2307 | | } |
| 2308 | | |
| 2309 | | // copy messages |
| 2310 | | $iil_copy = iil_C_Copy($this->conn, $uids, $from_mbox, $to_mbox); |
| 2311 | | $copied = !($iil_copy === false || $iil_copy < 0); |
| 2312 | | |
| 2313 | | if ($copied) { |
| 2314 | | $this->_clear_messagecount($to_mbox); |
| 2315 | | } |
| 2316 | | |
| 2317 | | return $copied; |
| 2318 | | } |
| 2319 | | |
| 2320 | | |
| 2321 | | /** |
| 2322 | | * Mark messages as deleted and expunge mailbox |
| 2323 | | * |
| 2324 | | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| 2325 | | * @param string Source mailbox |
| 2326 | | * @return boolean True on success, False on error |
| 2327 | | */ |
| 2328 | | function delete_message($uids, $mbox_name='') |
| 2329 | | { |
| 2330 | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| 2331 | | |
| 2332 | | list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); |
| 2333 | | |
| 2334 | | // exit if no message uids are specified |
| 2335 | | if (empty($uids)) |
| 2336 | | return false; |
| 2337 | | |
| 2338 | | $deleted = iil_C_Delete($this->conn, $mailbox, $uids); |
| 2339 | | |
| 2340 | | if ($deleted) { |
| 2341 | | // send expunge command in order to have the deleted message |
| 2342 | | // really deleted from the mailbox |
| 2343 | | $this->_expunge($mailbox, false, $uids); |
| 2344 | | $this->_clear_messagecount($mailbox); |
| 2345 | | unset($this->uid_id_map[$mailbox]); |
| 2346 | | |
| 2347 | | // unset threads internal cache |
| 2348 | | unset($this->icache['threads']); |
| | 3009 | return -2; |
| | 3010 | } |
| | 3011 | |
| | 3012 | /** |
| | 3013 | * @access private |
| | 3014 | */ |
| | 3015 | private function get_message_cache($key, $from, $to, $sort_field, $sort_order) |
| | 3016 | { |
| | 3017 | $cache_key = "$key:$from:$to:$sort_field:$sort_order"; |
| | 3018 | |
| | 3019 | // use idx sort as default sorting |
| | 3020 | if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { |
| | 3021 | $sort_field = 'idx'; |
| | 3022 | } |
| | 3023 | |
| | 3024 | if ($this->caching_enabled && !isset($this->cache[$cache_key])) { |
| | 3025 | $this->cache[$cache_key] = array(); |
| | 3026 | $sql_result = $this->db->limitquery( |
| | 3027 | "SELECT idx, uid, headers". |
| | 3028 | " FROM ".get_table_name('messages'). |
| | 3029 | " WHERE user_id=?". |
| | 3030 | " AND cache_key=?". |
| | 3031 | " ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".strtoupper($sort_order), |
| | 3032 | $from, |
| | 3033 | $to - $from, |
| | 3034 | $_SESSION['user_id'], |
| | 3035 | $key); |
| | 3036 | |
| | 3037 | while ($sql_arr = $this->db->fetch_assoc($sql_result)) { |
| | 3038 | $uid = $sql_arr['uid']; |
| | 3039 | $this->cache[$cache_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); |
| | 3040 | |
| | 3041 | // featch headers if unserialize failed |
| | 3042 | if (empty($this->cache[$cache_key][$uid])) |
| | 3043 | $this->cache[$cache_key][$uid] = $this->conn->fetchHeader( |
| | 3044 | preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); |
| | 3045 | } |
| | 3046 | } |
| | 3047 | |
| | 3048 | return $this->cache[$cache_key]; |
| | 3049 | } |
| | 3050 | |
| | 3051 | /** |
| | 3052 | * @access private |
| | 3053 | */ |
| | 3054 | private function &get_cached_message($key, $uid) |
| | 3055 | { |
| | 3056 | $internal_key = 'message'; |
| | 3057 | |
| | 3058 | if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) { |
| | 3059 | $sql_result = $this->db->query( |
| | 3060 | "SELECT idx, headers, structure". |
| | 3061 | " FROM ".get_table_name('messages'). |
| | 3062 | " WHERE user_id=?". |
| | 3063 | " AND cache_key=?". |
| | 3064 | " AND uid=?", |
| | 3065 | $_SESSION['user_id'], |
| | 3066 | $key, |
| | 3067 | $uid); |
| | 3068 | |
| | 3069 | if ($sql_arr = $this->db->fetch_assoc($sql_result)) { |
| | 3070 | $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; |
| | 3071 | $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); |
| | 3072 | if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure'])) |
| | 3073 | $this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); |
| | 3074 | } |
| | 3075 | } |
| | 3076 | |
| | 3077 | return $this->icache[$internal_key][$uid]; |
| | 3078 | } |
| | 3079 | |
| | 3080 | /** |
| | 3081 | * @access private |
| | 3082 | */ |
| | 3083 | private function get_message_cache_index($key, $force=false, $sort_field='idx', $sort_order='ASC') |
| | 3084 | { |
| | 3085 | static $sa_message_index = array(); |
| | 3086 | |
| | 3087 | // empty key -> empty array |
| | 3088 | if (!$this->caching_enabled || empty($key)) |
| | 3089 | return array(); |
| | 3090 | |
| | 3091 | if (!empty($sa_message_index[$key]) && !$force) |
| | 3092 | return $sa_message_index[$key]; |
| | 3093 | |
| | 3094 | // use idx sort as default |
| | 3095 | if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) |
| | 3096 | $sort_field = 'idx'; |
| | 3097 | |
| | 3098 | $sa_message_index[$key] = array(); |
| | 3099 | $sql_result = $this->db->query( |
| | 3100 | "SELECT idx, uid". |
| | 3101 | " FROM ".get_table_name('messages'). |
| | 3102 | " WHERE user_id=?". |
| | 3103 | " AND cache_key=?". |
| | 3104 | " ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order, |
| | 3105 | $_SESSION['user_id'], |
| | 3106 | $key); |
| | 3107 | |
| | 3108 | while ($sql_arr = $this->db->fetch_assoc($sql_result)) |
| | 3109 | $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid']; |
| 2390 | | // make sure the message count cache is cleared as well |
| 2391 | | if ($cleared) |
| 2392 | | { |
| 2393 | | $this->clear_message_cache($mailbox.'.msg'); |
| 2394 | | $a_mailbox_cache = $this->get_cache('messagecount'); |
| 2395 | | unset($a_mailbox_cache[$mailbox]); |
| 2396 | | $this->update_cache('messagecount', $a_mailbox_cache); |
| 2397 | | } |
| 2398 | | |
| 2399 | | return $cleared; |
| 2400 | | } |
| 2401 | | else |
| 2402 | | return 0; |
| 2403 | | } |
| 2404 | | |
| 2405 | | |
| 2406 | | /** |
| 2407 | | * Send IMAP expunge command and clear cache |
| 2408 | | * |
| 2409 | | * @param string Mailbox name |
| 2410 | | * @param boolean False if cache should not be cleared |
| 2411 | | * @return boolean True on success |
| 2412 | | */ |
| 2413 | | function expunge($mbox_name='', $clear_cache=true) |
| 2414 | | { |
| 2415 | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| 2416 | | return $this->_expunge($mailbox, $clear_cache); |
| 2417 | | } |
| 2418 | | |
| 2419 | | |
| 2420 | | /** |
| 2421 | | * Send IMAP expunge command and clear cache |
| 2422 | | * |
| 2423 | | * @param string Mailbox name |
| 2424 | | * @param boolean False if cache should not be cleared |
| 2425 | | * @param mixed Message UIDs as array or comma-separated string, or '*' |
| 2426 | | * @return boolean True on success |
| 2427 | | * @access private |
| 2428 | | * @see rcube_imap::expunge() |
| 2429 | | */ |
| 2430 | | private function _expunge($mailbox, $clear_cache=true, $uids=NULL) |
| 2431 | | { |
| 2432 | | if ($uids && $this->get_capability('UIDPLUS')) |
| 2433 | | $a_uids = is_array($uids) ? join(',', $uids) : $uids; |
| 2434 | | else |
| 2435 | | $a_uids = NULL; |
| 2436 | | |
| 2437 | | $result = iil_C_Expunge($this->conn, $mailbox, $a_uids); |
| 2438 | | |
| 2439 | | if ($result>=0 && $clear_cache) |
| 2440 | | { |
| 2441 | | $this->clear_message_cache($mailbox.'.msg'); |
| 2442 | | $this->_clear_messagecount($mailbox); |
| 2443 | | } |
| 2444 | | |
| 2445 | | return $result; |
| 2446 | | } |
| 2447 | | |
| 2448 | | |
| 2449 | | /** |
| 2450 | | * Parse message UIDs input |
| 2451 | | * |
| 2452 | | * @param mixed UIDs array or comma-separated list or '*' or '1:*' |
| 2453 | | * @param string Mailbox name |
| 2454 | | * @return array Two elements array with UIDs converted to list and ALL flag |
| 2455 | | * @access private |
| 2456 | | */ |
| 2457 | | private function _parse_uids($uids, $mailbox) |
| 2458 | | { |
| 2459 | | if ($uids === '*' || $uids === '1:*') { |
| 2460 | | if (empty($this->search_set)) { |
| 2461 | | $uids = '1:*'; |
| 2462 | | $all = true; |
| 2463 | | } |
| 2464 | | // get UIDs from current search set |
| 2465 | | // @TODO: skip iil_C_FetchUIDs() and work with IDs instead of UIDs (?) |
| 2466 | | else { |
| 2467 | | if ($this->search_threads) |
| 2468 | | $uids = iil_C_FetchUIDs($this->conn, $mailbox, array_keys($this->search_set['depth'])); |
| 2469 | | else |
| 2470 | | $uids = iil_C_FetchUIDs($this->conn, $mailbox, $this->search_set); |
| 2471 | | |
| 2472 | | // save ID-to-UID mapping in local cache |
| 2473 | | if (is_array($uids)) |
| 2474 | | foreach ($uids as $id => $uid) |
| 2475 | | $this->uid_id_map[$mailbox][$uid] = $id; |
| 2476 | | |
| 2477 | | $uids = join(',', $uids); |
| 2478 | | } |
| 2479 | | } |
| 2480 | | else { |
| 2481 | | if (is_array($uids)) |
| 2482 | | $uids = join(',', $uids); |
| 2483 | | |
| 2484 | | if (preg_match('/[^0-9,]/', $uids)) |
| 2485 | | $uids = ''; |
| 2486 | | } |
| 2487 | | |
| 2488 | | return array($uids, (bool) $all); |
| 2489 | | } |
| 2490 | | |
| 2491 | | |
| 2492 | | /* -------------------------------- |
| 2493 | | * folder managment |
| 2494 | | * --------------------------------*/ |
| 2495 | | |
| 2496 | | /** |
| 2497 | | * Get a list of all folders available on the IMAP server |
| 2498 | | * |
| 2499 | | * @param string IMAP root dir |
| 2500 | | * @return array Indexed array with folder names |
| 2501 | | */ |
| 2502 | | function list_unsubscribed($root='') |
| 2503 | | { |
| 2504 | | static $sa_unsubscribed; |
| 2505 | | |
| 2506 | | if (is_array($sa_unsubscribed)) |
| 2507 | | return $sa_unsubscribed; |
| 2508 | | |
| 2509 | | // retrieve list of folders from IMAP server |
| 2510 | | $a_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox($root), '*'); |
| 2511 | | |
| 2512 | | // modify names with root dir |
| 2513 | | foreach ($a_mboxes as $mbox_name) |
| 2514 | | { |
| 2515 | | $name = $this->mod_mailbox($mbox_name, 'out'); |
| 2516 | | if (strlen($name)) |
| 2517 | | $a_folders[] = $name; |
| 2518 | | } |
| 2519 | | |
| 2520 | | // filter folders and sort them |
| 2521 | | $sa_unsubscribed = $this->_sort_mailbox_list($a_folders); |
| 2522 | | return $sa_unsubscribed; |
| 2523 | | } |
| 2524 | | |
| 2525 | | |
| 2526 | | /** |
| 2527 | | * Get mailbox quota information |
| 2528 | | * added by Nuny |
| 2529 | | * |
| 2530 | | * @return mixed Quota info or False if not supported |
| 2531 | | */ |
| 2532 | | function get_quota() |
| 2533 | | { |
| 2534 | | if ($this->get_capability('QUOTA')) |
| 2535 | | return iil_C_GetQuota($this->conn); |
| 2536 | | |
| 2537 | | return false; |
| 2538 | | } |
| 2539 | | |
| 2540 | | |
| 2541 | | /** |
| 2542 | | * Subscribe to a specific mailbox(es) |
| 2543 | | * |
| 2544 | | * @param array Mailbox name(s) |
| 2545 | | * @return boolean True on success |
| 2546 | | */ |
| 2547 | | function subscribe($a_mboxes) |
| 2548 | | { |
| 2549 | | if (!is_array($a_mboxes)) |
| 2550 | | $a_mboxes = array($a_mboxes); |
| 2551 | | |
| 2552 | | // let this common function do the main work |
| 2553 | | return $this->_change_subscription($a_mboxes, 'subscribe'); |
| 2554 | | } |
| 2555 | | |
| 2556 | | |
| 2557 | | /** |
| 2558 | | * Unsubscribe mailboxes |
| 2559 | | * |
| 2560 | | * @param array Mailbox name(s) |
| 2561 | | * @return boolean True on success |
| 2562 | | */ |
| 2563 | | function unsubscribe($a_mboxes) |
| 2564 | | { |
| 2565 | | if (!is_array($a_mboxes)) |
| 2566 | | $a_mboxes = array($a_mboxes); |
| 2567 | | |
| 2568 | | // let this common function do the main work |
| 2569 | | return $this->_change_subscription($a_mboxes, 'unsubscribe'); |
| 2570 | | } |
| 2571 | | |
| 2572 | | |
| 2573 | | /** |
| 2574 | | * Create a new mailbox on the server and register it in local cache |
| 2575 | | * |
| 2576 | | * @param string New mailbox name (as utf-7 string) |
| 2577 | | * @param boolean True if the new mailbox should be subscribed |
| 2578 | | * @param string Name of the created mailbox, false on error |
| 2579 | | */ |
| 2580 | | function create_mailbox($name, $subscribe=false) |
| 2581 | | { |
| 2582 | | $result = false; |
| 2583 | | |
| 2584 | | // reduce mailbox name to 100 chars |
| 2585 | | $name = substr($name, 0, 100); |
| 2586 | | $abs_name = $this->mod_mailbox($name); |
| 2587 | | $result = iil_C_CreateFolder($this->conn, $abs_name); |
| 2588 | | |
| 2589 | | // try to subscribe it |
| 2590 | | if ($result && $subscribe) |
| 2591 | | $this->subscribe($name); |
| 2592 | | |
| 2593 | | return $result ? $name : false; |
| 2594 | | } |
| 2595 | | |
| 2596 | | |
| 2597 | | /** |
| 2598 | | * Set a new name to an existing mailbox |
| 2599 | | * |
| 2600 | | * @param string Mailbox to rename (as utf-7 string) |
| 2601 | | * @param string New mailbox name (as utf-7 string) |
| 2602 | | * @return string Name of the renames mailbox, False on error |
| 2603 | | */ |
| 2604 | | function rename_mailbox($mbox_name, $new_name) |
| 2605 | | { |
| 2606 | | $result = false; |
| 2607 | | |
| 2608 | | // encode mailbox name and reduce it to 100 chars |
| 2609 | | $name = substr($new_name, 0, 100); |
| 2610 | | |
| 2611 | | // make absolute path |
| 2612 | | $mailbox = $this->mod_mailbox($mbox_name); |
| 2613 | | $abs_name = $this->mod_mailbox($name); |
| 2614 | | |
| 2615 | | // check if mailbox is subscribed |
| 2616 | | $a_subscribed = $this->_list_mailboxes(); |
| 2617 | | $subscribed = in_array($mailbox, $a_subscribed); |
| 2618 | | |
| 2619 | | // unsubscribe folder |
| 2620 | | if ($subscribed) |
| 2621 | | iil_C_UnSubscribe($this->conn, $mailbox); |
| 2622 | | |
| 2623 | | if (strlen($abs_name)) |
| 2624 | | $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name); |
| 2625 | | |
| 2626 | | if ($result) |
| 2627 | | { |
| 2628 | | $delm = $this->get_hierarchy_delimiter(); |
| 2629 | | |
| 2630 | | // check if mailbox children are subscribed |
| 2631 | | foreach ($a_subscribed as $c_subscribed) |
| 2632 | | if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) |
| 2633 | | { |
| 2634 | | iil_C_UnSubscribe($this->conn, $c_subscribed); |
| 2635 | | iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed)); |
| 2636 | | } |
| 2637 | | |
| 2638 | | // clear cache |
| 2639 | | $this->clear_message_cache($mailbox.'.msg'); |
| 2640 | | $this->clear_cache('mailboxes'); |
| 2641 | | } |
| 2642 | | |
| 2643 | | // try to subscribe it |
| 2644 | | if ($result && $subscribed) |
| 2645 | | iil_C_Subscribe($this->conn, $abs_name); |
| 2646 | | |
| 2647 | | return $result ? $name : false; |
| 2648 | | } |
| 2649 | | |
| 2650 | | |
| 2651 | | /** |
| 2652 | | * Remove mailboxes from server |
| 2653 | | * |
| 2654 | | * @param string Mailbox name(s) string/array |
| 2655 | | * @return boolean True on success |
| 2656 | | */ |
| 2657 | | function delete_mailbox($mbox_name) |
| 2658 | | { |
| 2659 | | $deleted = false; |
| 2660 | | |
| 2661 | | if (is_array($mbox_name)) |
| 2662 | | $a_mboxes = $mbox_name; |
| 2663 | | else if (is_string($mbox_name) && strlen($mbox_name)) |
| 2664 | | $a_mboxes = explode(',', $mbox_name); |
| 2665 | | |
| 2666 | | if (is_array($a_mboxes)) |
| 2667 | | foreach ($a_mboxes as $mbox_name) |
| 2668 | | { |
| 2669 | | $mailbox = $this->mod_mailbox($mbox_name); |
| 2670 | | $sub_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), |
| 2671 | | $mbox_name . $this->delimiter . '*'); |
| 2672 | | |
| 2673 | | // unsubscribe mailbox before deleting |
| 2674 | | iil_C_UnSubscribe($this->conn, $mailbox); |
| 2675 | | |
| 2676 | | // send delete command to server |
| 2677 | | $result = iil_C_DeleteFolder($this->conn, $mailbox); |
| 2678 | | if ($result >= 0) { |
| 2679 | | $deleted = true; |
| 2680 | | $this->clear_message_cache($mailbox.'.msg'); |
| 2681 | | } |
| 2682 | | |
| 2683 | | foreach ($sub_mboxes as $c_mbox) |
| 2684 | | if ($c_mbox != 'INBOX') { |
| 2685 | | iil_C_UnSubscribe($this->conn, $c_mbox); |
| 2686 | | $result = iil_C_DeleteFolder($this->conn, $c_mbox); |
| 2687 | | if ($result >= 0) { |
| 2688 | | $deleted = true; |
| 2689 | | $this->clear_message_cache($c_mbox.'.msg'); |
| 2690 | | } |
| 2691 | | } |
| 2692 | | } |
| 2693 | | |
| 2694 | | // clear mailboxlist cache |
| 2695 | | if ($deleted) |
| 2696 | | $this->clear_cache('mailboxes'); |
| 2697 | | |
| 2698 | | return $deleted; |
| 2699 | | } |
| 2700 | | |
| 2701 | | |
| 2702 | | /** |
| 2703 | | * Create all folders specified as default |
| 2704 | | */ |
| 2705 | | function create_default_folders() |
| 2706 | | { |
| 2707 | | // create default folders if they do not exist |
| 2708 | | foreach ($this->default_folders as $folder) |
| 2709 | | { |
| 2710 | | if (!$this->mailbox_exists($folder)) |
| 2711 | | $this->create_mailbox($folder, true); |
| 2712 | | else if (!$this->mailbox_exists($folder, true)) |
| 2713 | | $this->subscribe($folder); |
| 2714 | | } |
| 2715 | | } |
| 2716 | | |
| 2717 | | |
| 2718 | | /** |
| 2719 | | * Checks if folder exists and is subscribed |
| 2720 | | * |
| 2721 | | * @param string Folder name |
| 2722 | | * @param boolean Enable subscription checking |
| 2723 | | * @return boolean TRUE or FALSE |
| 2724 | | */ |
| 2725 | | function mailbox_exists($mbox_name, $subscription=false) |
| 2726 | | { |
| 2727 | | if ($mbox_name) { |
| 2728 | | if ($mbox_name == 'INBOX') |
| 2729 | | return true; |
| 2730 | | |
| 2731 | | if ($subscription) { |
| 2732 | | if ($a_folders = iil_C_ListSubscribed($this->conn, $this->mod_mailbox(''), $mbox_name)) |
| 2733 | | return true; |
| 2734 | | } |
| 2735 | | else { |
| 2736 | | $a_folders = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), $mbox_mbox); |
| 2737 | | |
| 2738 | | if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) |
| 2739 | | return true; |
| 2740 | | } |
| 2741 | | } |
| 2742 | | |
| 2743 | | return false; |
| 2744 | | } |
| 2745 | | |
| 2746 | | |
| 2747 | | |
| 2748 | | /* -------------------------------- |
| 2749 | | * internal caching methods |
| 2750 | | * --------------------------------*/ |
| 2751 | | |
| 2752 | | /** |
| 2753 | | * @access private |
| 2754 | | */ |
| 2755 | | function set_caching($set) |
| 2756 | | { |
| 2757 | | if ($set && is_object($this->db)) |
| 2758 | | $this->caching_enabled = true; |
| 2759 | | else |
| 2760 | | $this->caching_enabled = false; |
| 2761 | | } |
| 2762 | | |
| 2763 | | /** |
| 2764 | | * @access private |
| 2765 | | */ |
| 2766 | | function get_cache($key) |
| 2767 | | { |
| 2768 | | // read cache (if it was not read before) |
| 2769 | | if (!count($this->cache) && $this->caching_enabled) |
| 2770 | | { |
| 2771 | | return $this->_read_cache_record($key); |
| 2772 | | } |
| 2773 | | |
| 2774 | | return $this->cache[$key]; |
| 2775 | | } |
| 2776 | | |
| 2777 | | /** |
| 2778 | | * @access private |
| 2779 | | */ |
| 2780 | | function update_cache($key, $data) |
| 2781 | | { |
| 2782 | | $this->cache[$key] = $data; |
| 2783 | | $this->cache_changed = true; |
| 2784 | | $this->cache_changes[$key] = true; |
| 2785 | | } |
| 2786 | | |
| 2787 | | /** |
| 2788 | | * @access private |
| 2789 | | */ |
| 2790 | | function write_cache() |
| 2791 | | { |
| 2792 | | if ($this->caching_enabled && $this->cache_changed) |
| 2793 | | { |
| 2794 | | foreach ($this->cache as $key => $data) |
| 2795 | | { |
| 2796 | | if ($this->cache_changes[$key]) |
| 2797 | | $this->_write_cache_record($key, serialize($data)); |
| 2798 | | } |
| 2799 | | } |
| 2800 | | } |
| 2801 | | |
| 2802 | | /** |
| 2803 | | * @access private |
| 2804 | | */ |
| 2805 | | function clear_cache($key=NULL) |
| 2806 | | { |
| 2807 | | if (!$this->caching_enabled) |
| 2808 | | return; |
| 2809 | | |
| 2810 | | if ($key===NULL) |
| 2811 | | { |
| 2812 | | foreach ($this->cache as $key => $data) |
| 2813 | | $this->_clear_cache_record($key); |
| 2814 | | |
| 2815 | | $this->cache = array(); |
| 2816 | | $this->cache_changed = false; |
| 2817 | | $this->cache_changes = array(); |
| 2818 | | } |
| 2819 | | else |
| 2820 | | { |
| 2821 | | $this->_clear_cache_record($key); |
| 2822 | | $this->cache_changes[$key] = false; |
| 2823 | | unset($this->cache[$key]); |
| 2824 | | } |
| 2825 | | } |
| 2826 | | |
| 2827 | | /** |
| 2828 | | * @access private |
| 2829 | | */ |
| 2830 | | private function _read_cache_record($key) |
| 2831 | | { |
| 2832 | | if ($this->db) |
| 2833 | | { |
| 2834 | | // get cached data from DB |
| 2835 | | $sql_result = $this->db->query( |
| 2836 | | "SELECT cache_id, data, cache_key |
| 2837 | | FROM ".get_table_name('cache')." |
| 2838 | | WHERE user_id=? |
| 2839 | | AND cache_key LIKE 'IMAP.%'", |
| 2840 | | $_SESSION['user_id']); |
| 2841 | | |
| 2842 | | while ($sql_arr = $this->db->fetch_assoc($sql_result)) |
| 2843 | | { |
| 2844 | | $sql_key = preg_replace('/^IMAP\./', '', $sql_arr['cache_key']); |
| 2845 | | $this->cache_keys[$sql_key] = $sql_arr['cache_id']; |
| 2846 | | if (!isset($this->cache[$sql_key])) |
| 2847 | | $this->cache[$sql_key] = $sql_arr['data'] ? unserialize($sql_arr['data']) : false; |
| 2848 | | } |
| 2849 | | } |
| 2850 | | |
| 2851 | | return $this->cache[$key]; |
| 2852 | | } |
| 2853 | | |
| 2854 | | /** |
| 2855 | | * @access private |
| 2856 | | */ |
| 2857 | | private function _write_cache_record($key, $data) |
| 2858 | | { |
| 2859 | | if (!$this->db) |
| 2860 | | return false; |
| 2861 | | |
| 2862 | | // update existing cache record |
| 2863 | | if ($this->cache_keys[$key]) |
| 2864 | | { |
| 2865 | | $this->db->query( |
| 2866 | | "UPDATE ".get_table_name('cache')." |
| 2867 | | SET created=". $this->db->now().", data=? |
| 2868 | | WHERE user_id=? |
| 2869 | | AND cache_key=?", |
| 2870 | | $data, |
| 2871 | | $_SESSION['user_id'], |
| 2872 | | 'IMAP.'.$key); |
| 2873 | | } |
| 2874 | | // add new cache record |
| 2875 | | else |
| 2876 | | { |
| 2877 | | $this->db->query( |
| 2878 | | "INSERT INTO ".get_table_name('cache')." |
| 2879 | | (created, user_id, cache_key, data) |
| 2880 | | VALUES (".$this->db->now().", ?, ?, ?)", |
| 2881 | | $_SESSION['user_id'], |
| 2882 | | 'IMAP.'.$key, |
| 2883 | | $data); |
| 2884 | | |
| 2885 | | // get cache entry ID for this key |
| 2886 | | $sql_result = $this->db->query( |
| 2887 | | "SELECT cache_id |
| 2888 | | FROM ".get_table_name('cache')." |
| 2889 | | WHERE user_id=? |
| 2890 | | AND cache_key=?", |
| 2891 | | $_SESSION['user_id'], |
| 2892 | | 'IMAP.'.$key); |
| 2893 | | |
| 2894 | | if ($sql_arr = $this->db->fetch_assoc($sql_result)) |
| 2895 | | $this->cache_keys[$key] = $sql_arr['cache_id']; |
| 2896 | | } |
| 2897 | | } |
| 2898 | | |
| 2899 | | /** |
| 2900 | | * @access private |
| 2901 | | */ |
| 2902 | | private function _clear_cache_record($key) |
| 2903 | | { |
| 2904 | | $this->db->query( |
| 2905 | | "DELETE FROM ".get_table_name('cache')." |
| 2906 | | WHERE user_id=? |
| 2907 | | AND cache_key=?", |
| 2908 | | $_SESSION['user_id'], |
| 2909 | | 'IMAP.'.$key); |
| 2910 | | |
| 2911 | | unset($this->cache_keys[$key]); |
| 2912 | | } |
| 2913 | | |
| 2914 | | |
| 2915 | | |
| 2916 | | /* -------------------------------- |
| 2917 | | * message caching methods |
| 2918 | | * --------------------------------*/ |
| 2919 | | |
| 2920 | | |
| 2921 | | /** |
| 2922 | | * Checks if the cache is up-to-date |
| 2923 | | * |
| 2924 | | * @param string Mailbox name |
| 2925 | | * @param string Internal cache key |
| 2926 | | * @return int Cache status: -3 = off, -2 = incomplete, -1 = dirty |
| 2927 | | */ |
| 2928 | | private function check_cache_status($mailbox, $cache_key) |
| 2929 | | { |
| 2930 | | if (!$this->caching_enabled) |
| 2931 | | return -3; |
| 2932 | | |
| 2933 | | $cache_index = $this->get_message_cache_index($cache_key); |
| 2934 | | $msg_count = $this->_messagecount($mailbox); |
| 2935 | | $cache_count = count($cache_index); |
| 2936 | | |
| 2937 | | // empty mailbox |
| 2938 | | if (!$msg_count) |
| 2939 | | return $cache_count ? -2 : 1; |