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