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