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