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