Fix for updating the filters rules when a folder is renamed.
[squirrelmail.git] / functions / imap_mailbox.php
... / ...
CommitLineData
1<?php
2
3/**
4 * imap_mailbox.php
5 *
6 * Copyright (c) 1999-2005 The SquirrelMail Project Team
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
8 *
9 * This impliments all functions that manipulate mailboxes
10 *
11 * @version $Id$
12 * @package squirrelmail
13 * @subpackage imap
14 */
15
16/** UTF7 support */
17require_once(SM_PATH . 'functions/imap_utf7_local.php');
18
19global $boxesnew;
20
21/**
22 * Mailboxes class
23 *
24 * FIXME. This class should be extracted and placed in a separate file that
25 * can be included before we start the session. That makes caching of the tree
26 * possible. On a refresh mailboxes from left_main.php the only function that
27 * should be called is the sqimap_get_status_mbx_tree. In case of subscribe
28 * / rename / delete / new we have to create methods for adding/changing the
29 * mailbox in the mbx_tree without the need for a refresh.
30 * @package squirrelmail
31 * @subpackage imap
32 * @since 1.3.0
33 */
34class mailboxes {
35 var $mailboxname_full = '', $mailboxname_sub= '', $is_noselect = false, $is_noinferiors = false,
36 $is_special = false, $is_root = false, $is_inbox = false, $is_sent = false,
37 $is_trash = false, $is_draft = false, $mbxs = array(),
38 $unseen = false, $total = false;
39
40 function addMbx($mbx, $delimiter, $start, $specialfirst) {
41 $ary = explode($delimiter, $mbx->mailboxname_full);
42 $mbx_parent =& $this;
43 for ($i = $start, $c = count($ary)-1; $i < $c; $i++) {
44 $mbx_childs =& $mbx_parent->mbxs;
45 $found = false;
46 if ($mbx_childs) {
47 foreach ($mbx_childs as $key => $parent) {
48 if ($parent->mailboxname_sub == $ary[$i]) {
49 $mbx_parent =& $mbx_parent->mbxs[$key];
50 $found = true;
51 break;
52 }
53 }
54 }
55 if (!$found) {
56 $no_select_mbx = new mailboxes();
57 if (isset($mbx_parent->mailboxname_full) && $mbx_parent->mailboxname_full != '') {
58 $no_select_mbx->mailboxname_full = $mbx_parent->mailboxname_full.$delimiter.$ary[$i];
59 } else {
60 $no_select_mbx->mailboxname_full = $ary[$i];
61 }
62 $no_select_mbx->mailboxname_sub = $ary[$i];
63 $no_select_mbx->is_noselect = true;
64 $mbx_parent->mbxs[] = $no_select_mbx;
65 $i--;
66 }
67 }
68 $mbx_parent->mbxs[] = $mbx;
69 if ($mbx->is_special && $specialfirst) {
70 usort($mbx_parent->mbxs, 'sortSpecialMbx');
71 }
72 }
73}
74
75/**
76 * array callback used for sorting in mailboxes class
77 * @param object $a
78 * @param object $b
79 * @since 1.3.0
80 */
81function sortSpecialMbx($a, $b) {
82 if ($a->is_inbox) {
83 $acmp = '0'. $a->mailboxname_full;
84 } else if ($a->is_special) {
85 $acmp = '1'. $a->mailboxname_full;
86 } else {
87 $acmp = '2' . $a->mailboxname_full;
88 }
89 if ($b->is_inbox) {
90 $bcmp = '0'. $b->mailboxname_full;
91 }else if ($b->is_special) {
92 $bcmp = '1' . $b->mailboxname_full;
93 } else {
94 $bcmp = '2' . $b->mailboxname_full;
95 }
96 return strnatcasecmp($acmp, $bcmp);
97}
98
99/**
100 * @param array $ary
101 * @return array
102 * @since 1.5.0
103 */
104function compact_mailboxes_response($ary) {
105 /*
106 * Workaround for mailboxes returned as literal
107 * FIXME : Doesn't work if the mailbox name is multiple lines
108 * (larger then fgets buffer)
109 */
110 for ($i = 0, $iCnt=count($ary); $i < $iCnt; $i++) {
111 if (isset($ary[$i + 1]) && substr($ary[$i], -3) == "}\r\n") {
112 if (ereg("^(\\* [A-Z]+.*)\\{[0-9]+\\}([ \n\r\t]*)$",
113 $ary[$i], $regs)) {
114 $ary[$i] = $regs[1] . '"' . addslashes(trim($ary[$i+1])) . '"' . $regs[2];
115 array_splice($ary, $i+1, 2);
116 }
117 }
118 }
119 /* remove duplicates and ensure array is contiguous */
120 return array_values(array_unique($ary));
121}
122
123/**
124 * Extract the mailbox name from an untagged LIST (7.2.2) or LSUB (7.2.3) answer
125 * (LIST|LSUB) (<Flags list>) (NIL|"<separator atom>") <mailbox name string>\r\n
126 * mailbox name in quoted string MUST be unquoted and stripslashed (sm API)
127 *
128 * Originally stored in functions/strings.php. Since 1.2.6 stored in
129 * functions/imap_mailbox.php
130 * @param string $line imap LIST/LSUB response line
131 * @return string mailbox name
132 */
133function find_mailbox_name($line) {
134 if (preg_match('/^\* (?:LIST|LSUB) \([^\)]*\) (?:NIL|\"[^\"]*\") ([^\r\n]*)[\r\n]*$/i', $line, $regs)) {
135 if (substr($regs[1], 0, 1) == '"')
136 return stripslashes(substr($regs[1], 1, -1));
137 return $regs[1];
138 }
139 return '';
140}
141
142/**
143 * Detects if mailbox has noselect flag (can't store messages)
144 * @param string $lsub_line mailbox line from untagged LIST or LSUB response
145 * @return bool whether this is a Noselect mailbox.
146 * @since 1.3.2
147 */
148function check_is_noselect ($lsub_line) {
149 return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noselect[^\)]*\)/i", $lsub_line);
150}
151
152/**
153 * Detects if mailbox has noinferiors flag (can't store subfolders)
154 * @param string $lsub_line mailbox line from untagged LIST or LSUB response
155 * @return bool whether this is a Noinferiors mailbox.
156 * @since 1.5.0
157 */
158function check_is_noinferiors ($lsub_line) {
159 return preg_match("/^\* (LSUB|LIST) \([^\)]*\\\\Noinferiors[^\)]*\)/i", $lsub_line);
160}
161
162/**
163 * Detects mailbox's parent folder
164 *
165 * If $haystack is a full mailbox name, and $needle is the mailbox
166 * separator character, returns the second last part of the full
167 * mailbox name (i.e. the mailbox's parent mailbox)
168 *
169 * Originally stored in functions/strings.php. Since 1.2.6 stored in
170 * functions/imap_mailbox.php
171 * @param string $haystack full mailbox name
172 * @param string $needle delimiter
173 * @return string parent mailbox
174 */
175function readMailboxParent($haystack, $needle) {
176 if ($needle == '') {
177 $ret = '';
178 } else {
179 $parts = explode($needle, $haystack);
180 $elem = array_pop($parts);
181 while ($elem == '' && count($parts)) {
182 $elem = array_pop($parts);
183 }
184 $ret = join($needle, $parts);
185 }
186 return( $ret );
187}
188
189/**
190 * Check if $subbox is below the specified $parentbox
191 * @param string $subbox potential sub folder
192 * @param string $parentbox potential parent
193 * @return boolean
194 * @since 1.2.3
195 */
196function isBoxBelow( $subbox, $parentbox ) {
197 global $delimiter;
198 /*
199 * Eliminate the obvious mismatch, where the
200 * subfolder path is shorter than that of the potential parent
201 */
202 if ( strlen($subbox) < strlen($parentbox) ) {
203 return false;
204 }
205 /* check for delimiter */
206 if (substr($parentbox,-1) != $delimiter) {
207 $parentbox .= $delimiter;
208 }
209
210 return (substr($subbox,0,strlen($parentbox)) == $parentbox);
211}
212
213/**
214 * Defines special mailboxes: given a mailbox name, it checks if this is a
215 * "special" one: INBOX, Trash, Sent or Draft.
216 *
217 * Since 1.2.5 function includes special_mailbox hook.<br>
218 * Since 1.4.3 hook supports more than one plugin.
219 * @param string $box mailbox name
220 * @return boolean
221 * @since 1.2.3
222 */
223function isSpecialMailbox( $box ) {
224 $ret = ( (strtolower($box) == 'inbox') ||
225 isTrashMailbox($box) || isSentMailbox($box) || isDraftMailbox($box) );
226
227 if ( !$ret ) {
228 $ret = boolean_hook_function('special_mailbox',$box,1);
229 }
230 return $ret;
231}
232
233/**
234 * Detects if mailbox is a Trash folder or subfolder of Trash
235 * @param string $box mailbox name
236 * @return bool whether this is a Trash folder
237 * @since 1.4.0
238 */
239function isTrashMailbox ($box) {
240 global $trash_folder, $move_to_trash;
241 return $move_to_trash && $trash_folder &&
242 ( $box == $trash_folder || isBoxBelow($box, $trash_folder) );
243}
244
245/**
246 * Detects if mailbox is a Sent folder or subfolder of Sent
247 * @param string $box mailbox name
248 * @return bool whether this is a Sent folder
249 * @since 1.4.0
250 */
251function isSentMailbox($box) {
252 global $sent_folder, $move_to_sent;
253 return $move_to_sent && $sent_folder &&
254 ( $box == $sent_folder || isBoxBelow($box, $sent_folder) );
255}
256
257/**
258 * Detects if mailbox is a Drafts folder or subfolder of Drafts
259 * @param string $box mailbox name
260 * @return bool whether this is a Draft folder
261 * @since 1.4.0
262 */
263function isDraftMailbox($box) {
264 global $draft_folder, $save_as_draft;
265 return $save_as_draft &&
266 ( $box == $draft_folder || isBoxBelow($box, $draft_folder) );
267}
268
269/**
270 * Expunges a mailbox
271 *
272 * WARNING: Select mailbox before calling this function.
273 *
274 * permanently removes all messages that have the \Deleted flag
275 * set from the selected mailbox. See EXPUNGE command chapter in
276 * IMAP RFC.
277 * @param stream $imap_stream imap connection resource
278 * @param string $mailbox mailbox name (unused since 1.1.3).
279 * @param boolean $handle_errors error handling control (displays error_box on error).
280 * @param mixed $id (since 1.3.0) integer message id or array with integer ids
281 * @return integer number of expunged messages
282 *
283 */
284function sqimap_mailbox_expunge ($imap_stream, $mailbox, $handle_errors = true, $id='') {
285 if ($id) {
286 if (is_array($id)) {
287 $id = sqimap_message_list_squisher($id);
288 }
289 $id = ' '.$id;
290 $uid = TRUE;
291 } else {
292 $uid = false;
293 }
294 $read = sqimap_run_command($imap_stream, 'EXPUNGE'.$id, $handle_errors,
295 $response, $message, $uid);
296 $cnt = 0;
297
298 if (is_array($read)) {
299 foreach ($read as $r) {
300 if (preg_match('/^\*\s[0-9]+\sEXPUNGE/AUi',$r,$regs)) {
301 $cnt++;
302 }
303 }
304 }
305 return $cnt;
306}
307
308/**
309 * Checks whether or not the specified mailbox exists
310 * @param stream $imap_stream imap connection resource
311 * @param string $mailbox mailbox name
312 * @return boolean
313 * @since 1.0 or older
314 */
315function sqimap_mailbox_exists ($imap_stream, $mailbox) {
316 if (!isset($mailbox) || empty($mailbox)) {
317 return false;
318 }
319 $mbx = sqimap_run_command($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mailbox),
320 true, $response, $message);
321 return isset($mbx[0]);
322}
323
324/**
325 * Selects a mailbox
326 * Before 1.3.0 used more arguments and returned data depended on those argumements.
327 * @param stream $imap_stream imap connection resource
328 * @param string $mailbox mailbox name
329 * @return array results of select command (on success - permanentflags, flags and rights)
330 * @since 1.0 or older
331 */
332function sqimap_mailbox_select ($imap_stream, $mailbox) {
333 if ($mailbox == 'None') {
334 return;
335 }
336
337 $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
338 true, $response, $message);
339 $result = array();
340 for ($i = 0, $cnt = count($read); $i < $cnt; $i++) {
341 if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
342 $result[strtoupper($regs[1])] = $regs[2];
343 } else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i], $regs)) {
344 $result[strtoupper($regs[2])] = $regs[1];
345 } else {
346 if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i], $regs)) {
347 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/","/\]/") ,'', $regs[1])) ;
348 $result['PERMANENTFLAGS'] = explode(' ',strtolower($regs[1]));
349 } else if (preg_match("/FLAGS(.*)/i",$read[$i], $regs)) {
350 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/") ,'', $regs[1])) ;
351 $result['FLAGS'] = explode(' ',strtolower($regs[1]));
352 }
353 }
354 }
355 if (!isset($result['PERMANENTFLAGS'])) {
356 $result['PERMANENTFLAGS'] = $result['FLAGS'];
357 }
358 if (preg_match('/^\[(.+)\]/',$message, $regs)) {
359 $result['RIGHTS']=strtoupper($regs[1]);
360 }
361
362 return $result;
363}
364
365/**
366 * Creates a folder.
367 *
368 * Mailbox is automatically subscribed.
369 *
370 * Set $type to string that does not match 'noselect' (case insensitive),
371 * if you don't want to prepend delimiter to mailbox name. Please note
372 * that 'noinferiors' might be used someday as keyword for folders
373 * that store only messages.
374 * @param stream $imap_steam imap connection resource
375 * @param string $mailbox mailbox name
376 * @param string $type folder type.
377 * @since 1.0 or older
378 */
379function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
380 global $delimiter;
381 if (strtolower($type) == 'noselect') {
382 $mailbox .= $delimiter;
383 }
384
385 $read_ary = sqimap_run_command($imap_stream, 'CREATE ' .
386 sqimap_encode_mailbox_name($mailbox),
387 true, $response, $message);
388 sqimap_subscribe ($imap_stream, $mailbox);
389}
390
391/**
392 * Subscribes to an existing folder.
393 * @param stream $imap_stream imap connection resource
394 * @param string $mailbox mailbox name
395 * @param boolean $debug (since 1.5.1)
396 * @since 1.0 or older
397 */
398function sqimap_subscribe ($imap_stream, $mailbox,$debug=true) {
399 $read_ary = sqimap_run_command($imap_stream, 'SUBSCRIBE ' .
400 sqimap_encode_mailbox_name($mailbox),
401 $debug, $response, $message);
402}
403
404/**
405 * Unsubscribes from an existing folder
406 * @param stream $imap_stream imap connection resource
407 * @param string $mailbox mailbox name
408 * @since 1.0 or older
409 */
410function sqimap_unsubscribe ($imap_stream, $mailbox) {
411 $read_ary = sqimap_run_command($imap_stream, 'UNSUBSCRIBE ' .
412 sqimap_encode_mailbox_name($mailbox),
413 false, $response, $message);
414}
415
416/**
417 * Deletes the given folder
418 */
419function sqimap_mailbox_delete ($imap_stream, $mailbox) {
420 global $data_dir, $username;
421 sqimap_unsubscribe ($imap_stream, $mailbox);
422 $read_ary = sqimap_run_command($imap_stream, 'DELETE ' .
423 sqimap_encode_mailbox_name($mailbox),
424 true, $response, $message);
425 if ($response !== 'OK') {
426 // subscribe again
427 sqimap_subscribe ($imap_stream, $mailbox);
428 } else {
429 do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
430 removePref($data_dir, $username, "thread_$mailbox");
431 removePref($data_dir, $username, "collapse_folder_$mailbox");
432 }
433}
434
435/**
436 * Determines if the user is subscribed to the folder or not
437 */
438function sqimap_mailbox_is_subscribed($imap_stream, $folder) {
439 $boxesall = sqimap_mailbox_list ($imap_stream);
440 foreach ($boxesall as $ref) {
441 if ($ref['unformatted'] == $folder) {
442 return true;
443 }
444 }
445 return false;
446}
447
448/**
449 * Renames a mailbox.
450 */
451function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
452 if ( $old_name != $new_name ) {
453 global $delimiter, $imap_server_type, $data_dir, $username;
454 if ( substr( $old_name, -1 ) == $delimiter ) {
455 $old_name = substr( $old_name, 0, strlen( $old_name ) - 1 );
456 $new_name = substr( $new_name, 0, strlen( $new_name ) - 1 );
457 $postfix = $delimiter;
458 } else {
459 $postfix = '';
460 }
461
462 $boxesall = sqimap_mailbox_list_all($imap_stream);
463 $cmd = 'RENAME ' . sqimap_encode_mailbox_name($old_name) .
464 ' ' . sqimap_encode_mailbox_name($new_name);
465 $data = sqimap_run_command($imap_stream, $cmd, true, $response, $message);
466 sqimap_unsubscribe($imap_stream, $old_name.$postfix);
467 $oldpref_thread = getPref($data_dir, $username, 'thread_'.$old_name.$postfix);
468 $oldpref_collapse = getPref($data_dir, $username, 'collapse_folder_'.$old_name.$postfix);
469 removePref($data_dir, $username, 'thread_'.$old_name.$postfix);
470 removePref($data_dir, $username, 'collapse_folder_'.$old_name.$postfix);
471 sqimap_subscribe($imap_stream, $new_name.$postfix);
472 setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref_thread);
473 setPref($data_dir, $username, 'collapse_folder_'.$new_name.$postfix, $oldpref_collapse);
474 do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
475 $l = strlen( $old_name ) + 1;
476 $p = 'unformatted';
477
478 foreach ($boxesall as $box) {
479 if (substr($box[$p], 0, $l) == $old_name . $delimiter) {
480 $new_sub = $new_name . $delimiter . substr($box[$p], $l);
481 /* With Cyrus IMAPd >= 2.0 rename is recursive, so don't check for errors here */
482 if ($imap_server_type == 'cyrus') {
483 $cmd = 'RENAME "' . $box[$p] . '" "' . $new_sub . '"';
484 $data = sqimap_run_command($imap_stream, $cmd, false,
485 $response, $message);
486 }
487 $was_subscribed = sqimap_mailbox_is_subscribed($imap_stream, $box[$p]);
488 if ( $was_subscribed ) {
489 sqimap_unsubscribe($imap_stream, $box[$p]);
490 }
491 $oldpref_thread = getPref($data_dir, $username, 'thread_'.$box[$p]);
492 $oldpref_collapse = getPref($data_dir, $username, 'collapse_folder_'.$box[$p]);
493 removePref($data_dir, $username, 'thread_'.$box[$p]);
494 removePref($data_dir, $username, 'collapse_folder_'.$box[$p]);
495 if ( $was_subscribed ) {
496 sqimap_subscribe($imap_stream, $new_sub);
497 }
498 setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref_thread);
499 setPref($data_dir, $username, 'collapse_folder_'.$new_sub, $oldpref_collapse);
500 do_hook_function('rename_or_delete_folder',
501 $args = array($box[$p], 'rename', $new_sub));
502 }
503 }
504 }
505}
506
507/**
508 * Formats a mailbox into parts for the $boxesall array
509 *
510 * The parts are:
511 *
512 * raw - Raw LIST/LSUB response from the IMAP server
513 * formatted - nicely formatted folder name
514 * unformatted - unformatted, but with delimiter at end removed
515 * unformatted-dm - folder name as it appears in raw response
516 * unformatted-disp - unformatted without $folder_prefix
517 */
518function sqimap_mailbox_parse ($line, $line_lsub) {
519 global $folder_prefix, $delimiter;
520
521 /* Process each folder line */
522 for ($g = 0, $cnt = count($line); $g < $cnt; ++$g) {
523 /* Store the raw IMAP reply */
524 if (isset($line[$g])) {
525 $boxesall[$g]['raw'] = $line[$g];
526 } else {
527 $boxesall[$g]['raw'] = '';
528 }
529
530 /* Count number of delimiters ($delimiter) in folder name */
531 $mailbox = /*trim(*/$line_lsub[$g]/*)*/;
532 $dm_count = substr_count($mailbox, $delimiter);
533 if (substr($mailbox, -1) == $delimiter) {
534 /* If name ends in delimiter, decrement count by one */
535 $dm_count--;
536 }
537
538 /* Format folder name, but only if it's a INBOX.* or has a parent. */
539 $boxesallbyname[$mailbox] = $g;
540 $parentfolder = readMailboxParent($mailbox, $delimiter);
541 if ( (strtolower(substr($mailbox, 0, 5)) == "inbox") ||
542 (substr($mailbox, 0, strlen($folder_prefix)) == $folder_prefix) ||
543 (isset($boxesallbyname[$parentfolder]) &&
544 (strlen($parentfolder) > 0) ) ) {
545 $indent = $dm_count - (substr_count($folder_prefix, $delimiter));
546 if ($indent > 0) {
547 $boxesall[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $indent);
548 } else {
549 $boxesall[$g]['formatted'] = '';
550 }
551 $boxesall[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
552 } else {
553 $boxesall[$g]['formatted'] = imap_utf7_decode_local($mailbox);
554 }
555
556 $boxesall[$g]['unformatted-dm'] = $mailbox;
557 if (substr($mailbox, -1) == $delimiter) {
558 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
559 }
560 $boxesall[$g]['unformatted'] = $mailbox;
561 if (substr($mailbox,0,strlen($folder_prefix))==$folder_prefix) {
562 $mailbox = substr($mailbox, strlen($folder_prefix));
563 }
564 $boxesall[$g]['unformatted-disp'] = $mailbox;
565 $boxesall[$g]['id'] = $g;
566
567 $boxesall[$g]['flags'] = array();
568 if (isset($line[$g])) {
569 ereg("\(([^)]*)\)",$line[$g],$regs);
570 // FIXME Flags do contain the \ character. \NoSelect \NoInferiors
571 // and $MDNSent <= last one doesn't have the \
572 // It's better to follow RFC3501 instead of using our own naming.
573 $flags = trim(strtolower(str_replace('\\', '',$regs[1])));
574 if ($flags) {
575 $boxesall[$g]['flags'] = explode(' ', $flags);
576 }
577 }
578 }
579 return $boxesall;
580}
581
582/**
583 * Returns list of options (to be echoed into select statement
584 * based on available mailboxes and separators
585 * Caller should surround options with <select ...> </select> and
586 * any formatting.
587 * $imap_stream - $imapConnection to query for mailboxes
588 * $show_selected - array containing list of mailboxes to pre-select (0 if none)
589 * $folder_skip - array of folders to keep out of option list (compared in lower)
590 * $boxes - list of already fetched boxes (for places like folder panel, where
591 * you know these options will be shown 3 times in a row.. (most often unset).
592 * $flag - flag to check for in mailbox flags, used to filter out mailboxes.
593 * 'noselect' by default to remove unselectable mailboxes.
594 * 'noinferiors' used to filter out folders that can not contain subfolders.
595 * NULL to avoid flag check entirely.
596 * NOTE: noselect and noiferiors are used internally. The IMAP representation is
597 * \NoSelect and \NoInferiors
598 * $use_long_format - override folder display preference and always show full folder name.
599 */
600function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
601 $flag = 'noselect', $use_long_format = false ) {
602 global $username, $data_dir;
603 $mbox_options = '';
604 if ( $use_long_format ) {
605 $shorten_box_names = 0;
606 } else {
607 $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF);
608 }
609
610 if ($boxes == 0) {
611 $boxes = sqimap_mailbox_list($imap_stream);
612 }
613
614 foreach ($boxes as $boxes_part) {
615 if ($flag == NULL || (is_array($boxes_part['flags'])
616 && !in_array($flag, $boxes_part['flags']))) {
617 $box = $boxes_part['unformatted'];
618
619 if ($folder_skip != 0 && in_array($box, $folder_skip) ) {
620 continue;
621 }
622 $lowerbox = strtolower($box);
623 // mailboxes are casesensitive => inbox.sent != inbox.Sent
624 // nevermind, to many dependencies this should be fixed!
625
626 if (strtolower($box) == 'inbox') { // inbox is special and not casesensitive
627 $box2 = _("INBOX");
628 } else {
629 switch ($shorten_box_names)
630 {
631 case 2: /* delimited, style = 2 */
632 $box2 = str_replace('&amp;nbsp;&amp;nbsp;', '.&nbsp;', htmlspecialchars($boxes_part['formatted']));
633 break;
634 case 1: /* indent, style = 1 */
635 $box2 = str_replace('&amp;nbsp;&amp;nbsp;', '&nbsp;&nbsp;', htmlspecialchars($boxes_part['formatted']));
636 break;
637 default: /* default, long names, style = 0 */
638 $box2 = str_replace(' ', '&nbsp;', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
639 break;
640 }
641 }
642 if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
643 $mbox_options .= '<option value="' . htmlspecialchars($box) .'" selected="selected">'.$box2.'</option>' . "\n";
644 } else {
645 $mbox_options .= '<option value="' . htmlspecialchars($box) .'">'.$box2.'</option>' . "\n";
646 }
647 }
648 }
649 return $mbox_options;
650}
651
652/**
653 * Returns sorted mailbox lists in several different ways.
654 * See comment on sqimap_mailbox_parse() for info about the returned array.
655 * @param resource $imap_stream imap connection resource
656 * @param boolean $force force update of mailbox listing. available since 1.4.2 and 1.5.0
657 * @return array list of mailboxes
658 */
659function sqimap_mailbox_list($imap_stream, $force=false) {
660 if (!sqgetGlobalVar('boxesnew',$boxesnew,SQ_SESSION) || $force) {
661 global $data_dir, $username, $list_special_folders_first,
662 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
663 $move_to_trash, $move_to_sent, $save_as_draft,
664 $delimiter, $noselect_fix_enable, $imap_server_type,
665 $show_only_subscribed_folders;
666 $inbox_subscribed = false;
667 $listsubscribed = sqimap_capability($imap_stream,'LIST-SUBSCRIBED');
668
669 require_once(SM_PATH . 'include/load_prefs.php');
670
671 if (!$show_only_subscribed_folders) {
672 $lsub = 'LIST';
673 } elseif ($listsubscribed) {
674 $lsub = 'LIST (SUBSCRIBED)';
675 } else {
676 $lsub = 'LSUB';
677 }
678
679 if ($noselect_fix_enable) {
680 $lsub_args = "$lsub \"$folder_prefix\" \"*%\"";
681 } else {
682 $lsub_args = "$lsub \"$folder_prefix\" \"*\"";
683 }
684 /* LSUB array */
685 $lsub_ary = sqimap_run_command ($imap_stream, $lsub_args,
686 true, $response, $message);
687 $lsub_ary = compact_mailboxes_response($lsub_ary);
688
689 $sorted_lsub_ary = array();
690 for ($i = 0, $cnt = count($lsub_ary);$i < $cnt; $i++) {
691
692 $temp_mailbox_name = find_mailbox_name($lsub_ary[$i]);
693 $sorted_lsub_ary[] = $temp_mailbox_name;
694 if (!$inbox_subscribed && strtoupper($temp_mailbox_name) == 'INBOX') {
695 $inbox_subscribed = true;
696 }
697 }
698
699 /* natural sort mailboxes */
700 if (isset($sorted_lsub_ary)) {
701 usort($sorted_lsub_ary, 'strnatcasecmp');
702 }
703 /*
704 * The LSUB response doesn't provide us information about \Noselect
705 * mail boxes. The LIST response does, that's why we need to do a LIST
706 * call to retrieve the flags for the mailbox
707 * Note: according RFC2060 an imap server may provide \NoSelect flags in the LSUB response.
708 * in other words, we cannot rely on it.
709 */
710 $sorted_list_ary = array();
711 // if (!$listsubscribed) {
712 for ($i=0; $i < count($sorted_lsub_ary); $i++) {
713 if (substr($sorted_lsub_ary[$i], -1) == $delimiter) {
714 $mbx = substr($sorted_lsub_ary[$i], 0, strlen($sorted_lsub_ary[$i])-1);
715 }
716 else {
717 $mbx = $sorted_lsub_ary[$i];
718 }
719
720 $read = sqimap_run_command ($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mbx),
721 true, $response, $message);
722
723 $read = compact_mailboxes_response($read);
724
725 if (isset($read[0])) {
726 $sorted_list_ary[$i] = $read[0];
727 } else {
728 $sorted_list_ary[$i] = '';
729 }
730 }
731 // }
732 /*
733 * Just in case they're not subscribed to their inbox,
734 * we'll get it for them anyway
735 */
736 if (!$inbox_subscribed) {
737 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" "INBOX"',
738 true, $response, $message);
739 $sorted_list_ary[] = implode('',compact_mailboxes_response($inbox_ary));
740 $sorted_lsub_ary[] = find_mailbox_name($inbox_ary[0]);
741 }
742
743 $boxesall = sqimap_mailbox_parse ($sorted_list_ary, $sorted_lsub_ary);
744
745 /* Now, lets sort for special folders */
746 $boxesnew = $used = array();
747
748 /* Find INBOX */
749 $cnt = count($boxesall);
750 $used = array_pad($used,$cnt,false);
751 for($k = 0; $k < $cnt; ++$k) {
752 if (strtolower($boxesall[$k]['unformatted']) == 'inbox') {
753 $boxesnew[] = $boxesall[$k];
754 $used[$k] = true;
755 break;
756 }
757 }
758 /* List special folders and their subfolders, if requested. */
759 if ($list_special_folders_first) {
760 for($k = 0; $k < $cnt; ++$k) {
761 if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
762 $boxesnew[] = $boxesall[$k];
763 $used[$k] = true;
764 }
765 }
766 }
767
768 /* Find INBOX's children */
769 for($k = 0; $k < $cnt; ++$k) {
770 if (!$used[$k] && isBoxBelow(strtolower($boxesall[$k]['unformatted']), 'inbox') &&
771 strtolower($boxesall[$k]['unformatted']) != 'inbox') {
772 $boxesnew[] = $boxesall[$k];
773 $used[$k] = true;
774 }
775 }
776
777 /* Rest of the folders */
778 for($k = 0; $k < $cnt; $k++) {
779 if (!$used[$k]) {
780 $boxesnew[] = $boxesall[$k];
781 }
782 }
783 sqsession_register($boxesnew,'boxesnew');
784 }
785 return $boxesnew;
786}
787
788/**
789 * Returns a list of all folders, subscribed or not
790 */
791function sqimap_mailbox_list_all($imap_stream) {
792 global $list_special_folders_first, $folder_prefix, $delimiter;
793
794 $read_ary = sqimap_run_command($imap_stream,"LIST \"$folder_prefix\" *",true,$response, $message,false);
795 $read_ary = compact_mailboxes_response($read_ary);
796
797 $g = 0;
798 $fld_pre_length = strlen($folder_prefix);
799 for ($i = 0, $cnt = count($read_ary); $i < $cnt; $i++) {
800 /* Store the raw IMAP reply */
801 $boxes[$g]['raw'] = $read_ary[$i];
802
803 /* Count number of delimiters ($delimiter) in folder name */
804 $mailbox = find_mailbox_name($read_ary[$i]);
805 $dm_count = substr_count($mailbox, $delimiter);
806 if (substr($mailbox, -1) == $delimiter) {
807 /* If name ends in delimiter - decrement count by one */
808 $dm_count--;
809 }
810
811 /* Format folder name, but only if it's a INBOX.* or has a parent. */
812 $boxesallbyname[$mailbox] = $g;
813 $parentfolder = readMailboxParent($mailbox, $delimiter);
814 if((eregi('^inbox'.quotemeta($delimiter), $mailbox)) ||
815 (ereg('^'.$folder_prefix, $mailbox)) ||
816 ( isset($boxesallbyname[$parentfolder]) && (strlen($parentfolder) > 0) ) ) {
817 if ($dm_count) {
818 $boxes[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $dm_count);
819 } else {
820 $boxes[$g]['formatted'] = '';
821 }
822 $boxes[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
823 } else {
824 $boxes[$g]['formatted'] = imap_utf7_decode_local($mailbox);
825 }
826
827 $boxes[$g]['unformatted-dm'] = $mailbox;
828 if (substr($mailbox, -1) == $delimiter) {
829 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
830 }
831 $boxes[$g]['unformatted'] = $mailbox;
832 $boxes[$g]['unformatted-disp'] = substr($mailbox,$fld_pre_length);
833
834 $boxes[$g]['id'] = $g;
835
836 /* Now lets get the flags for this mailbox */
837 $read_mlbx = $read_ary[$i];
838 $flags = substr($read_mlbx, strpos($read_mlbx, '(')+1);
839 $flags = substr($flags, 0, strpos($flags, ')'));
840 $flags = str_replace('\\', '', $flags);
841 $flags = trim(strtolower($flags));
842 if ($flags) {
843 $boxes[$g]['flags'] = explode(' ', $flags);
844 } else {
845 $boxes[$g]['flags'] = array();
846 }
847 $g++;
848 }
849 if(is_array($boxes)) {
850 sort ($boxes);
851 }
852
853 return $boxes;
854}
855
856function sqimap_mailbox_tree($imap_stream) {
857 global $default_folder_prefix;
858 if (true) {
859 global $data_dir, $username, $list_special_folders_first,
860 $folder_prefix, $delimiter, $trash_folder, $move_to_trash,
861 $imap_server_type, $show_only_subscribed_folders;
862
863 $noselect = false;
864 $noinferiors = false;
865
866 require_once(SM_PATH . 'include/load_prefs.php');
867
868 if ($show_only_subscribed_folders) {
869 $lsub_cmd = 'LSUB';
870 } else {
871 $lsub_cmd = 'LIST';
872 }
873
874 /* LSUB array */
875 $lsub_ary = sqimap_run_command ($imap_stream, "$lsub_cmd \"$folder_prefix\" \"*\"",
876 true, $response, $message);
877 $lsub_ary = compact_mailboxes_response($lsub_ary);
878
879 /* Check to see if we have an INBOX */
880 $has_inbox = false;
881
882 for ($i = 0, $cnt = count($lsub_ary); $i < $cnt; $i++) {
883 if (preg_match("/^\*\s+$lsub_cmd.*\s\"?INBOX\"?[^(\/\.)].*$/i",$lsub_ary[$i])) {
884 $lsub_ary[$i] = strtoupper($lsub_ary[$i]);
885 // in case of an unsubscribed inbox an imap server can
886 // return the inbox in the lsub results with a \NoSelect
887 // flag.
888 if (!preg_match("/\*\s+$lsub_cmd\s+\(.*\\\\NoSelect.*\).*/i",$lsub_ary[$i])) {
889 $has_inbox = true;
890 } else {
891 // remove the result and request it again with a list
892 // response at a later stage.
893 unset($lsub_ary[$i]);
894 // re-index the array otherwise the addition of the LIST
895 // response will fail in PHP 4.1.2 and probably other older versions
896 $lsub_ary = array_values($lsub_ary);
897 }
898 break;
899 }
900 }
901
902 if ($has_inbox == false) {
903 // do a list request for inbox because we should always show
904 // inbox even if the user isn't subscribed to it.
905 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" "INBOX"',
906 true, $response, $message);
907 $inbox_ary = compact_mailboxes_response($inbox_ary);
908 if (count($inbox_ary)) {
909 $lsub_ary[] = $inbox_ary[0];
910 }
911 }
912
913 /*
914 * Section about removing the last element was removed
915 * We don't return "* OK" anymore from sqimap_read_data
916 */
917
918 $sorted_lsub_ary = array();
919 $cnt = count($lsub_ary);
920 for ($i = 0; $i < $cnt; $i++) {
921 $mbx = find_mailbox_name($lsub_ary[$i]);
922
923 // only do the noselect test if !uw, is checked later. FIX ME see conf.pl setting
924 if ($imap_server_type != "uw") {
925 $noselect = check_is_noselect($lsub_ary[$i]);
926 $noinferiors = check_is_noinferiors($lsub_ary[$i]);
927 }
928 if (substr($mbx, -1) == $delimiter) {
929 $mbx = substr($mbx, 0, strlen($mbx) - 1);
930 }
931 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
932 }
933 // FIX ME this requires a config setting inside conf.pl instead of checking on server type
934 if ($imap_server_type == "uw") {
935 $aQuery = array();
936 $aTag = array();
937 // prepare an array with queries
938 foreach ($sorted_lsub_ary as $aMbx) {
939 $mbx = stripslashes($aMbx['mbx']);
940 sqimap_prepare_pipelined_query('LIST "" ' . sqimap_encode_mailbox_name($mbx), $tag, $aQuery, false);
941 $aTag[$tag] = $mbx;
942 }
943 $sorted_lsub_ary = array();
944 // execute all the queries at once
945 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
946 foreach($aTag as $tag => $mbx) {
947 if ($aServerResponse[$tag] == 'OK') {
948 $sResponse = implode('', $aResponse[$tag]);
949 $noselect = check_is_noselect($sResponse);
950 $noinferiors = check_is_noinferiors($sResponse);
951 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
952 }
953 }
954 $cnt = count($sorted_lsub_ary);
955 }
956 $sorted_lsub_ary = array_values($sorted_lsub_ary);
957 usort($sorted_lsub_ary, 'mbxSort');
958 $boxestree = sqimap_fill_mailbox_tree($sorted_lsub_ary,false,$imap_stream);
959 return $boxestree;
960 }
961}
962
963function mbxSort($a, $b) {
964 return strnatcasecmp($a['mbx'], $b['mbx']);
965}
966
967function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
968 global $data_dir, $username, $list_special_folders_first,
969 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
970 $move_to_trash, $move_to_sent, $save_as_draft,
971 $delimiter, $imap_server_type;
972
973 // $special_folders = array ('INBOX', $sent_folder, $draft_folder, $trash_folder);
974
975 /* create virtual root node */
976 $mailboxes= new mailboxes();
977 $mailboxes->is_root = true;
978 $trail_del = false;
979 $start = 0;
980
981
982 if (isset($folder_prefix) && ($folder_prefix != '')) {
983 $start = substr_count($folder_prefix,$delimiter);
984 if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
985 $mailboxes->mailboxname_full = substr($folder_prefix,0, (strlen($folder_prefix)-1));
986 } else {
987 $mailboxes->mailboxname_full = $folder_prefix;
988 $start++;
989 }
990 $mailboxes->mailboxname_sub = $mailboxes->mailboxname_full;
991 } else {
992 $start = 0;
993 }
994
995 $cnt = count($mbx_ary);
996 for ($i=0; $i < $cnt; $i++) {
997 if ($mbx_ary[$i]['mbx'] !='' ) {
998 $mbx = new mailboxes();
999 $mailbox = $mbx_ary[$i]['mbx'];
1000
1001 /*
1002 sent subfolders messes up using existing code as subfolders
1003 were marked, but the parents were ordered somewhere else in
1004 the list, despite having "special folders at top" option set.
1005 Need a better method than this.
1006 */
1007/*
1008 if ($mailbox == 'INBOX') {
1009 $mbx->is_special = true;
1010 } elseif (stristr($trash_folder , $mailbox)) {
1011 $mbx->is_special = true;
1012 } elseif (stristr($sent_folder , $mailbox)) {
1013 $mbx->is_special = true;
1014 } elseif (stristr($draft_folder , $mailbox)) {
1015 $mbx->is_special = true;
1016 }
1017
1018 switch ($mailbox) {
1019 case 'INBOX':
1020 $mbx->is_inbox = true;
1021 $mbx->is_special = true;
1022 $mbx_ary[$i]['noselect'] = false;
1023 break;
1024 case $trash_folder:
1025 $mbx->is_trash = true;
1026 $mbx->is_special = true;
1027 break;
1028 case $sent_folder:
1029 $mbx->is_sent = true;
1030 $mbx->is_special = true;
1031 break;
1032 case $draft_folder:
1033 $mbx->is_draft = true;
1034 $mbx->is_special = true;
1035 break;
1036 }
1037*/
1038 $mbx->is_special |= ($mbx->is_inbox = (strtoupper($mailbox) == 'INBOX'));
1039 $mbx->is_special |= ($mbx->is_trash = isTrashMailbox($mailbox));
1040 $mbx->is_special |= ($mbx->is_sent = isSentMailbox($mailbox));
1041 $mbx->is_special |= ($mbx->is_draft = isDraftMailbox($mailbox));
1042 if (!$mbx->is_special)
1043 $mbx->is_special = boolean_hook_function('special_mailbox', $mailbox, 1);
1044
1045 if (isset($mbx_ary[$i]['unseen'])) {
1046 $mbx->unseen = $mbx_ary[$i]['unseen'];
1047 }
1048 if (isset($mbx_ary[$i]['nummessages'])) {
1049 $mbx->total = $mbx_ary[$i]['nummessages'];
1050 }
1051
1052 $mbx->is_noselect = $mbx_ary[$i]['noselect'];
1053 $mbx->is_noinferiors = $mbx_ary[$i]['noinferiors'];
1054
1055 $r_del_pos = strrpos($mbx_ary[$i]['mbx'], $delimiter);
1056 if ($r_del_pos) {
1057 $mbx->mailboxname_sub = substr($mbx_ary[$i]['mbx'],$r_del_pos+1);
1058 } else { /* mailbox is root folder */
1059 $mbx->mailboxname_sub = $mbx_ary[$i]['mbx'];
1060 }
1061 $mbx->mailboxname_full = $mbx_ary[$i]['mbx'];
1062
1063 $mailboxes->addMbx($mbx, $delimiter, $start, $list_special_folders_first);
1064 }
1065 }
1066 sqimap_utf7_decode_mbx_tree($mailboxes);
1067 sqimap_get_status_mbx_tree($imap_stream,$mailboxes);
1068 return $mailboxes;
1069}
1070
1071function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
1072 if (strtoupper($mbx_tree->mailboxname_full) == 'INBOX')
1073 $mbx_tree->mailboxname_sub = _("INBOX");
1074 else
1075 $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
1076 if ($mbx_tree->mbxs) {
1077 $iCnt = count($mbx_tree->mbxs);
1078 for ($i=0;$i<$iCnt;++$i) {
1079 $mbxs_tree->mbxs[$i] = sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs[$i]);
1080 }
1081 }
1082}
1083
1084
1085function sqimap_tree_to_ref_array(&$mbx_tree,&$aMbxs) {
1086 if ($mbx_tree)
1087 $aMbxs[] =& $mbx_tree;
1088 if ($mbx_tree->mbxs) {
1089 $iCnt = count($mbx_tree->mbxs);
1090 for ($i=0;$i<$iCnt;++$i) {
1091 sqimap_tree_to_ref_array($mbx_tree->mbxs[$i],$aMbxs);
1092 }
1093 }
1094}
1095
1096function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
1097 global $unseen_notify, $unseen_type, $trash_folder,$move_to_trash;
1098 $aMbxs = $aQuery = array();
1099 sqimap_tree_to_ref_array($mbx_tree,$aMbxs);
1100 // remove the root node
1101 array_shift($aMbxs);
1102
1103 if($unseen_notify == 3) {
1104 $cnt = count($aMbxs);
1105 for($i=0;$i<$cnt;++$i) {
1106 $oMbx =& $aMbxs[$i];
1107 if (!$oMbx->is_noselect) {
1108 $mbx = $oMbx->mailboxname_full;
1109 if ($unseen_type == 2 ||
1110 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1111 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
1112 } else {
1113 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
1114 }
1115 sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
1116 } else {
1117 $oMbx->unseen = $oMbx->total = false;
1118 $tag = false;
1119 }
1120 $oMbx->tag = $tag;
1121 $aMbxs[$i] =& $oMbx;
1122 }
1123 // execute all the queries at once
1124 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
1125 $cnt = count($aMbxs);
1126 for($i=0;$i<$cnt;++$i) {
1127 $oMbx =& $aMbxs[$i];
1128 $tag = $oMbx->tag;
1129 if ($tag && $aServerResponse[$tag] == 'OK') {
1130 $sResponse = implode('', $aResponse[$tag]);
1131 if (preg_match('/UNSEEN\s+([0-9]+)/i', $sResponse, $regs)) {
1132 $oMbx->unseen = $regs[1];
1133 }
1134 if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
1135 $oMbx->total = $regs[1];
1136 }
1137 }
1138 unset($oMbx->tag);
1139 }
1140 } else if ($unseen_notify == 2) { // INBOX only
1141 $cnt = count($aMbxs);
1142 for($i=0;$i<$cnt;++$i) {
1143 $oMbx =& $aMbxs[$i];
1144 if (strtoupper($oMbx->mailboxname_full) == 'INBOX' ||
1145 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1146 if ($unseen_type == 2 ||
1147 ($oMbx->mailboxname_full == $trash_folder && $move_to_trash)) {
1148 $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full);
1149 $oMbx->unseen = $aStatus['UNSEEN'];
1150 $oMbx->total = $aStatus['MESSAGES'];
1151 } else {
1152 $oMbx->unseen = sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full);
1153 }
1154 $aMbxs[$i] =& $oMbx;
1155 if (!$move_to_trash && $trash_folder) {
1156 break;
1157 } else {
1158 // trash comes after INBOX
1159 if ($oMbx->mailboxname_full == $trash_folder) {
1160 break;
1161 }
1162 }
1163 }
1164 }
1165 }
1166}
1167
1168?>