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