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