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