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