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