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