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