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