adding two hooks that allow integrating third party address book backends
[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 * $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 if ($id) {
228 if (is_array($id)) {
229 $id = sqimap_message_list_squisher($id);
230 }
231 $id = ' '.$id;
232 $uid = TRUE;
233 } else {
234 $uid = false;
235 }
236 $read = sqimap_run_command($imap_stream, 'EXPUNGE'.$id, $handle_errors,
237 $response, $message, $uid);
238 $cnt = 0;
239
240 if (is_array($read)) {
241 foreach ($read as $r) {
242 if (preg_match('/^\*\s[0-9]+\sEXPUNGE/AUi',$r,$regs)) {
243 $cnt++;
244 }
245 }
246 }
247 return $cnt;
248 }
249
250 /**
251 * Expunge specified message, updated $msgs and $msort
252 *
253 * Until Marc and I come up with a better way to maintain
254 * these stupid arrays, we'll use this wrapper function to
255 * remove the message with the matching UID .. the order
256 * won't be changed - the array element for the message
257 * will just be removed.
258 */
259 function sqimap_mailbox_expunge_dmn($message_id)
260 {
261 global $msgs, $msort, $sort, $imapConnection,
262 $mailbox, $mbx_response, $auto_expunge,
263 $sort, $allow_server_sort, $thread_sort_messages, $allow_thread_sort,
264 $username, $data_dir;
265 $cnt = 0;
266
267 // Got to grab this out of prefs, since it isn't saved from mailbox_view.php
268 if ($allow_thread_sort) {
269 $thread_sort_messages = getPref($data_dir, $username, "thread_$mailbox",0);
270 }
271
272 for ($i = 0; $i < count($msort); $i++) {
273 if ($msgs[$i]['ID'] == $message_id) {
274 break;
275 }
276 }
277
278 if ( isset($msgs) ) {
279 unset($msgs[$i]);
280 $msgs = array_values($msgs);
281 sqsession_register($msgs, 'msgs');
282 }
283
284 if ( isset($msort) ) {
285 unset($msort[$i]);
286 $msort = array_values($msort);
287 sqsession_register($msort, 'msort');
288 }
289
290 if ($auto_expunge) {
291 $cnt = sqimap_mailbox_expunge($imapConnection, $mailbox, true);
292 }
293
294 // And after all that mucking around, update the sort list!
295 // Remind me why the hell we need those two arrays again?!
296 if ( $allow_thread_sort && $thread_sort_messages ) {
297 $server_sort_array = get_thread_sort($imapConnection);
298 } elseif ( $allow_server_sort ) {
299 $server_sort_array = sqimap_get_sort_order($imapConnection, $sort, $mbx_response);
300 } else {
301 $server_sort_array = sqimap_get_php_sort_order($imapConnection, $mbx_response);
302 }
303 return $cnt;
304 }
305
306 /**
307 * Checks whether or not the specified mailbox exists
308 */
309 function sqimap_mailbox_exists ($imap_stream, $mailbox) {
310 if (!isset($mailbox) || empty($mailbox)) {
311 return false;
312 }
313 $mbx = sqimap_run_command($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mailbox),
314 true, $response, $message);
315 return isset($mbx[0]);
316 }
317
318 /**
319 * Selects a mailbox
320 */
321 function sqimap_mailbox_select ($imap_stream, $mailbox) {
322 global $auto_expunge;
323
324 if ($mailbox == 'None') {
325 return;
326 }
327
328 $read = sqimap_run_command($imap_stream, 'SELECT ' . sqimap_encode_mailbox_name($mailbox),
329 true, $response, $message);
330 $result = array();
331 for ($i = 0, $cnt = count($read); $i < $cnt; $i++) {
332 if (preg_match('/^\*\s+OK\s\[(\w+)\s(\w+)\]/',$read[$i], $regs)) {
333 $result[strtoupper($regs[1])] = $regs[2];
334 } else if (preg_match('/^\*\s([0-9]+)\s(\w+)/',$read[$i], $regs)) {
335 $result[strtoupper($regs[2])] = $regs[1];
336 } else {
337 if (preg_match("/PERMANENTFLAGS(.*)/i",$read[$i], $regs)) {
338 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/","/\]/") ,'', $regs[1])) ;
339 $result['PERMANENTFLAGS'] = $regs[1];
340 } else if (preg_match("/FLAGS(.*)/i",$read[$i], $regs)) {
341 $regs[1]=trim(preg_replace ( array ("/\(/","/\)/") ,'', $regs[1])) ;
342 $result['FLAGS'] = $regs[1];
343 }
344 }
345 }
346 if (preg_match('/^\[(.+)\]/',$message, $regs)) {
347 $result['RIGHTS']=$regs[1];
348 }
349
350 if ($auto_expunge) {
351 $tmp = sqimap_run_command($imap_stream, 'EXPUNGE', false, $a, $b);
352 }
353 return $result;
354 }
355
356 /**
357 * Creates a folder.
358 */
359 function sqimap_mailbox_create ($imap_stream, $mailbox, $type) {
360 global $delimiter;
361 if (strtolower($type) == 'noselect') {
362 $mailbox .= $delimiter;
363 }
364
365 $read_ary = sqimap_run_command($imap_stream, 'CREATE ' .
366 sqimap_encode_mailbox_name($mailbox),
367 true, $response, $message);
368 sqimap_subscribe ($imap_stream, $mailbox);
369 }
370
371 /**
372 * Subscribes to an existing folder.
373 */
374 function sqimap_subscribe ($imap_stream, $mailbox) {
375 $read_ary = sqimap_run_command($imap_stream, 'SUBSCRIBE ' .
376 sqimap_encode_mailbox_name($mailbox),
377 true, $response, $message);
378 }
379
380 /**
381 * Unsubscribes from an existing folder
382 */
383 function sqimap_unsubscribe ($imap_stream, $mailbox) {
384 $read_ary = sqimap_run_command($imap_stream, 'UNSUBSCRIBE ' .
385 sqimap_encode_mailbox_name($mailbox),
386 false, $response, $message);
387 }
388
389 /**
390 * Deletes the given folder
391 */
392 function sqimap_mailbox_delete ($imap_stream, $mailbox) {
393 global $data_dir, $username;
394 sqimap_unsubscribe ($imap_stream, $mailbox);
395 $read_ary = sqimap_run_command($imap_stream, 'DELETE ' .
396 sqimap_encode_mailbox_name($mailbox),
397 true, $response, $message);
398 if ($response !== 'OK') {
399 // subscribe again
400 sqimap_subscribe ($imap_stream, $mailbox);
401 } else {
402 do_hook_function('rename_or_delete_folder', $args = array($mailbox, 'delete', ''));
403 removePref($data_dir, $username, "thread_$mailbox");
404 }
405 }
406
407 /**
408 * Determines if the user is subscribed to the folder or not
409 */
410 function sqimap_mailbox_is_subscribed($imap_stream, $folder) {
411 $boxesall = sqimap_mailbox_list ($imap_stream);
412 foreach ($boxesall as $ref) {
413 if ($ref['unformatted'] == $folder) {
414 return true;
415 }
416 }
417 return false;
418 }
419
420 /**
421 * Renames a mailbox.
422 */
423 function sqimap_mailbox_rename( $imap_stream, $old_name, $new_name ) {
424 if ( $old_name != $new_name ) {
425 global $delimiter, $imap_server_type, $data_dir, $username;
426 if ( substr( $old_name, -1 ) == $delimiter ) {
427 $old_name = substr( $old_name, 0, strlen( $old_name ) - 1 );
428 $new_name = substr( $new_name, 0, strlen( $new_name ) - 1 );
429 $postfix = $delimiter;
430 } else {
431 $postfix = '';
432 }
433
434 $boxesall = sqimap_mailbox_list($imap_stream);
435 $cmd = 'RENAME ' . sqimap_encode_mailbox_name($old_name) .
436 ' ' . sqimap_encode_mailbox_name($new_name);
437 $data = sqimap_run_command($imap_stream, $cmd, true, $response, $message);
438 sqimap_unsubscribe($imap_stream, $old_name.$postfix);
439 $oldpref = getPref($data_dir, $username, 'thread_'.$old_name.$postfix);
440 removePref($data_dir, $username, 'thread_'.$old_name.$postfix);
441 sqimap_subscribe($imap_stream, $new_name.$postfix);
442 setPref($data_dir, $username, 'thread_'.$new_name.$postfix, $oldpref);
443 do_hook_function('rename_or_delete_folder',$args = array($old_name, 'rename', $new_name));
444 $l = strlen( $old_name ) + 1;
445 $p = 'unformatted';
446
447 foreach ($boxesall as $box) {
448 if (substr($box[$p], 0, $l) == $old_name . $delimiter) {
449 $new_sub = $new_name . $delimiter . substr($box[$p], $l);
450 if ($imap_server_type == 'cyrus') {
451 $cmd = 'RENAME "' . $box[$p] . '" "' . $new_sub . '"';
452 $data = sqimap_run_command($imap_stream, $cmd, true,
453 $response, $message);
454 }
455 sqimap_unsubscribe($imap_stream, $box[$p]);
456 $oldpref = getPref($data_dir, $username, 'thread_'.$box[$p]);
457 removePref($data_dir, $username, 'thread_'.$box[$p]);
458 sqimap_subscribe($imap_stream, $new_sub);
459 setPref($data_dir, $username, 'thread_'.$new_sub, $oldpref);
460 do_hook_function('rename_or_delete_folder',
461 $args = array($box[$p], 'rename', $new_sub));
462 }
463 }
464 }
465 }
466
467 /**
468 * Formats a mailbox into parts for the $boxesall array
469 *
470 * The parts are:
471 *
472 * raw - Raw LIST/LSUB response from the IMAP server
473 * formatted - nicely formatted folder name
474 * unformatted - unformatted, but with delimiter at end removed
475 * unformatted-dm - folder name as it appears in raw response
476 * unformatted-disp - unformatted without $folder_prefix
477 */
478 function sqimap_mailbox_parse ($line, $line_lsub) {
479 global $folder_prefix, $delimiter;
480
481 /* Process each folder line */
482 for ($g = 0, $cnt = count($line); $g < $cnt; ++$g) {
483 /* Store the raw IMAP reply */
484 if (isset($line[$g])) {
485 $boxesall[$g]['raw'] = $line[$g];
486 } else {
487 $boxesall[$g]['raw'] = '';
488 }
489
490 /* Count number of delimiters ($delimiter) in folder name */
491 $mailbox = /*trim(*/$line_lsub[$g]/*)*/;
492 $dm_count = substr_count($mailbox, $delimiter);
493 if (substr($mailbox, -1) == $delimiter) {
494 /* If name ends in delimiter, decrement count by one */
495 $dm_count--;
496 }
497
498 /* Format folder name, but only if it's a INBOX.* or has a parent. */
499 $boxesallbyname[$mailbox] = $g;
500 $parentfolder = readMailboxParent($mailbox, $delimiter);
501 if ( (strtolower(substr($mailbox, 0, 5)) == "inbox") ||
502 (substr($mailbox, 0, strlen($folder_prefix)) == $folder_prefix) ||
503 (isset($boxesallbyname[$parentfolder]) &&
504 (strlen($parentfolder) > 0) ) ) {
505 $indent = $dm_count - (substr_count($folder_prefix, $delimiter));
506 if ($indent > 0) {
507 $boxesall[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $indent);
508 } else {
509 $boxesall[$g]['formatted'] = '';
510 }
511 $boxesall[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
512 } else {
513 $boxesall[$g]['formatted'] = imap_utf7_decode_local($mailbox);
514 }
515
516 $boxesall[$g]['unformatted-dm'] = $mailbox;
517 if (substr($mailbox, -1) == $delimiter) {
518 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
519 }
520 $boxesall[$g]['unformatted'] = $mailbox;
521 if (substr($mailbox,0,strlen($folder_prefix))==$folder_prefix) {
522 $mailbox = substr($mailbox, strlen($folder_prefix));
523 }
524 $boxesall[$g]['unformatted-disp'] = $mailbox;
525 $boxesall[$g]['id'] = $g;
526
527 $boxesall[$g]['flags'] = array();
528 if (isset($line[$g])) {
529 ereg("\(([^)]*)\)",$line[$g],$regs);
530 // FIXME Flags do contain the \ character. \NoSelect \NoInferiors
531 // and $MDNSent <= last one doesn't have the \
532 // It's better to follow RFC3501 instead of using our own naming.
533 $flags = trim(strtolower(str_replace('\\', '',$regs[1])));
534 if ($flags) {
535 $boxesall[$g]['flags'] = explode(' ', $flags);
536 }
537 }
538 }
539 return $boxesall;
540 }
541
542 /**
543 * Returns list of options (to be echoed into select statement
544 * based on available mailboxes and separators
545 * Caller should surround options with <SELECT..> </SELECT> and
546 * any formatting.
547 * $imap_stream - $imapConnection to query for mailboxes
548 * $show_selected - array containing list of mailboxes to pre-select (0 if none)
549 * $folder_skip - array of folders to keep out of option list (compared in lower)
550 * $boxes - list of already fetched boxes (for places like folder panel, where
551 * you know these options will be shown 3 times in a row.. (most often unset).
552 * $flag - flag to check for in mailbox flags, used to filter out mailboxes.
553 * 'noselect' by default to remove unselectable mailboxes.
554 * 'noinferiors' used to filter out folders that can not contain subfolders.
555 * NULL to avoid flag check entirely.
556 * NOTE: noselect and noiferiors are used internally. The IMAP representation is
557 * \NoSelect and \NoInferiors
558 * $use_long_format - override folder display preference and always show full folder name.
559 */
560 function sqimap_mailbox_option_list($imap_stream, $show_selected = 0, $folder_skip = 0, $boxes = 0,
561 $flag = 'noselect', $use_long_format = false ) {
562 global $username, $data_dir;
563 $mbox_options = '';
564 if ( $use_long_format ) {
565 $shorten_box_names = 0;
566 } else {
567 $shorten_box_names = getPref($data_dir, $username, 'mailbox_select_style', SMPREF_OFF);
568 }
569
570 if ($boxes == 0) {
571 $boxes = sqimap_mailbox_list($imap_stream);
572 }
573
574 foreach ($boxes as $boxes_part) {
575 if ($flag == NULL || !in_array($flag, $boxes_part['flags'])) {
576 $box = $boxes_part['unformatted'];
577
578 if ($folder_skip != 0 && in_array($box, $folder_skip) ) {
579 continue;
580 }
581 $lowerbox = strtolower($box);
582 // mailboxes are casesensitive => inbox.sent != inbox.Sent
583 // nevermind, to many dependencies this should be fixed!
584
585 if (strtolower($box) == 'inbox') { // inbox is special and not casesensitive
586 $box2 = _("INBOX");
587 } else {
588 switch ($shorten_box_names)
589 {
590 case 2: /* delimited, style = 2 */
591 $box2 = str_replace('&nbsp;&nbsp;', '.&nbsp;', $boxes_part['formatted']);
592 break;
593 case 1: /* indent, style = 1 */
594 $box2 = $boxes_part['formatted'];
595 break;
596 default: /* default, long names, style = 0 */
597 $box2 = str_replace(' ', '&nbsp;', htmlspecialchars(imap_utf7_decode_local($boxes_part['unformatted-disp'])));
598 break;
599 }
600 }
601 if ($show_selected != 0 && in_array($lowerbox, $show_selected) ) {
602 $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'" SELECTED>'.$box2.'</OPTION>' . "\n";
603 } else {
604 $mbox_options .= '<OPTION VALUE="' . htmlspecialchars($box) .'">'.$box2.'</OPTION>' . "\n";
605 }
606 }
607 }
608 return $mbox_options;
609 }
610
611 /**
612 * Returns sorted mailbox lists in several different ways.
613 * See comment on sqimap_mailbox_parse() for info about the returned array.
614 */
615 function sqimap_mailbox_list($imap_stream) {
616 global $default_folder_prefix;
617
618 if (!isset($boxesnew)) {
619 global $data_dir, $username, $list_special_folders_first,
620 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
621 $move_to_trash, $move_to_sent, $save_as_draft,
622 $delimiter, $noselect_fix_enable;
623
624 $inbox_in_list = false;
625 $inbox_subscribed = false;
626
627 require_once(SM_PATH . 'include/load_prefs.php');
628
629 if ($noselect_fix_enable) {
630 $lsub_args = "LSUB \"$folder_prefix\" \"*%\"";
631 } else {
632 $lsub_args = "LSUB \"$folder_prefix\" \"*\"";
633 }
634 /* LSUB array */
635 $lsub_ary = sqimap_run_command ($imap_stream, $lsub_args,
636 true, $response, $message);
637 $lsub_ary = compact_mailboxes_response($lsub_ary);
638
639 $sorted_lsub_ary = array();
640 for ($i = 0, $cnt = count($lsub_ary);$i < $cnt; $i++) {
641 $temp_mailbox_name = find_mailbox_name($lsub_ary[$i]);
642 $sorted_lsub_ary[] = $temp_mailbox_name;
643 if (!$inbox_subscribed && strtoupper($temp_mailbox_name) == 'INBOX') {
644 $inbox_subscribed = true;
645 }
646 }
647
648 /* natural sort mailboxes */
649 if (isset($sorted_lsub_ary)) {
650 usort($sorted_lsub_ary, 'strnatcasecmp');
651 }
652 /*
653 * The LSUB response doesn't provide us information about \Noselect
654 * mail boxes. The LIST response does, that's why we need to do a LIST
655 * call to retrieve the flags for the mailbox
656 * Note: according RFC2060 an imap server may provide \NoSelect flags in the LSUB response.
657 * in other words, we cannot rely on it.
658 */
659 $sorted_list_ary = array();
660 for ($i=0; $i < count($sorted_lsub_ary); $i++) {
661 if (substr($sorted_lsub_ary[$i], -1) == $delimiter) {
662 $mbx = substr($sorted_lsub_ary[$i], 0, strlen($sorted_lsub_ary[$i])-1);
663 }
664 else {
665 $mbx = $sorted_lsub_ary[$i];
666 }
667 $mbx = stripslashes($mbx);
668 $read = sqimap_run_command ($imap_stream, 'LIST "" ' . sqimap_encode_mailbox_name($mbx),
669 true, $response, $message);
670 $read = compact_mailboxes_response($read);
671 if (isset($read[0])) {
672 $sorted_list_ary[$i] = $read[0];
673 } else {
674 $sorted_list_ary[$i] = '';
675 }
676 }
677 /*
678 * Just in case they're not subscribed to their inbox,
679 * we'll get it for them anyway
680 */
681 if (!$inbox_subscribed) {
682 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" INBOX',
683 true, $response, $message);
684 $sorted_list_ary[] = implode('', compact_mailboxes_response($inbox_ary));
685 $sorted_lsub_ary[] = find_mailbox_name($inbox_ary[0]);
686 }
687
688 $boxesall = sqimap_mailbox_parse ($sorted_list_ary, $sorted_lsub_ary);
689
690 /* Now, lets sort for special folders */
691 $boxesnew = $used = array();
692
693 /* Find INBOX */
694 $cnt = count($boxesall);
695 $used = array_pad($used,$cnt,false);
696 for($k = 0; $k < $cnt; ++$k) {
697 if (strtolower($boxesall[$k]['unformatted']) == 'inbox') {
698 $boxesnew[] = $boxesall[$k];
699 $used[$k] = true;
700 break;
701 }
702 }
703 /* List special folders and their subfolders, if requested. */
704 if ($list_special_folders_first) {
705 for($k = 0; $k < $cnt; ++$k) {
706 if (!$used[$k] && isSpecialMailbox($boxesall[$k]['unformatted'])) {
707 $boxesnew[] = $boxesall[$k];
708 $used[$k] = true;
709 }
710 }
711 }
712
713 /* Rest of the folders */
714 for($k = 0; $k < $cnt; $k++) {
715 if (!$used[$k]) {
716 $boxesnew[] = $boxesall[$k];
717 }
718 }
719 }
720
721 return $boxesnew;
722 }
723
724 /**
725 * Returns a list of all folders, subscribed or not
726 */
727 function sqimap_mailbox_list_all($imap_stream) {
728 global $list_special_folders_first, $folder_prefix, $delimiter;
729
730 $read_ary = sqimap_run_command($imap_stream,"LIST \"$folder_prefix\" *",true,$response, $message,false);
731 $read_ary = compact_mailboxes_response($read_ary);
732
733 $g = 0;
734 $phase = 'inbox';
735 $fld_pre_length = strlen($folder_prefix);
736 for ($i = 0, $cnt = count($read_ary); $i < $cnt; $i++) {
737 /* Store the raw IMAP reply */
738 $boxes[$g]['raw'] = $read_ary[$i];
739
740 /* Count number of delimiters ($delimiter) in folder name */
741 $mailbox = find_mailbox_name($read_ary[$i]);
742 $dm_count = substr_count($mailbox, $delimiter);
743 if (substr($mailbox, -1) == $delimiter) {
744 /* If name ends in delimiter - decrement count by one */
745 $dm_count--;
746 }
747
748 /* Format folder name, but only if it's a INBOX.* or has a parent. */
749 $boxesallbyname[$mailbox] = $g;
750 $parentfolder = readMailboxParent($mailbox, $delimiter);
751 if((eregi('^inbox'.quotemeta($delimiter), $mailbox)) ||
752 (ereg('^'.$folder_prefix, $mailbox)) ||
753 ( isset($boxesallbyname[$parentfolder]) && (strlen($parentfolder) > 0) ) ) {
754 if ($dm_count) {
755 $boxes[$g]['formatted'] = str_repeat('&nbsp;&nbsp;', $dm_count);
756 } else {
757 $boxes[$g]['formatted'] = '';
758 }
759 $boxes[$g]['formatted'] .= imap_utf7_decode_local(readShortMailboxName($mailbox, $delimiter));
760 } else {
761 $boxes[$g]['formatted'] = imap_utf7_decode_local($mailbox);
762 }
763
764 $boxes[$g]['unformatted-dm'] = $mailbox;
765 if (substr($mailbox, -1) == $delimiter) {
766 $mailbox = substr($mailbox, 0, strlen($mailbox) - 1);
767 }
768 $boxes[$g]['unformatted'] = $mailbox;
769 $boxes[$g]['unformatted-disp'] = substr($mailbox,$fld_pre_length);
770
771 $boxes[$g]['id'] = $g;
772
773 /* Now lets get the flags for this mailbox */
774 $read_mlbx = $read_ary[$i];
775 $flags = substr($read_mlbx, strpos($read_mlbx, '(')+1);
776 $flags = substr($flags, 0, strpos($flags, ')'));
777 $flags = str_replace('\\', '', $flags);
778 $flags = trim(strtolower($flags));
779 if ($flags) {
780 $boxes[$g]['flags'] = explode(' ', $flags);
781 } else {
782 $boxes[$g]['flags'] = array();
783 }
784 $g++;
785 }
786 if(is_array($boxes)) {
787 sort ($boxes);
788 }
789
790 return $boxes;
791 }
792
793 function sqimap_mailbox_tree($imap_stream) {
794 global $boxesnew, $default_folder_prefix, $unseen_notify, $unseen_type;
795 if (!isset($boxesnew)) {
796
797 global $data_dir, $username, $list_special_folders_first,
798 $folder_prefix, $delimiter, $trash_folder, $move_to_trash,
799 $imap_server_type;
800
801
802 $inbox_in_list = false;
803 $inbox_subscribed = false;
804 $noselect = false;
805 $noinferiors = false;
806
807 require_once(SM_PATH . 'include/load_prefs.php');
808
809 /* LSUB array */
810 $lsub_ary = sqimap_run_command ($imap_stream, "LSUB \"$folder_prefix\" \"*\"",
811 true, $response, $message);
812 $lsub_ary = compact_mailboxes_response($lsub_ary);
813
814 /* Check to see if we have an INBOX */
815 $has_inbox = false;
816
817 for ($i = 0, $cnt = count($lsub_ary); $i < $cnt; $i++) {
818 if (preg_match("/^\*\s+LSUB.*\s\"?INBOX\"?[^(\/\.)].*$/i",$lsub_ary[$i])) {
819 $lsub_ary[$i] = strtoupper($lsub_ary[$i]);
820 // in case of an unsubscribed inbox an imap server can
821 // return the inbox in the lsub results with a \NoSelect
822 // flag.
823 if (!preg_match("/\*\s+LSUB\s+\(.*\\\\NoSelect.*\).*/i",$lsub_ary[$i])) {
824 $has_inbox = true;
825 } else {
826 // remove the result and request it again with a list
827 // response at a later stage.
828 unset($lsub_ary[$i]);
829 // re-index the array otherwise the addition of the LIST
830 // response will fail in PHP 4.1.2 and probably other older versions
831 $lsub_ary = array_values($lsub_ary);
832 }
833 break;
834 }
835 }
836
837 if ($has_inbox == false) {
838 // do a list request for inbox because we should always show
839 // inbox even if the user isn't subscribed to it.
840 $inbox_ary = sqimap_run_command ($imap_stream, 'LIST "" INBOX',
841 true, $response, $message);
842 $inbox_ary = compact_mailboxes_response($inbox_ary);
843 if (count($inbox_ary)) {
844 $lsub_ary[] = $inbox_ary[0];
845 }
846 }
847
848 /*
849 * Section about removing the last element was removed
850 * We don't return "* OK" anymore from sqimap_read_data
851 */
852
853 $sorted_lsub_ary = array();
854 $cnt = count($lsub_ary);
855 for ($i = 0; $i < $cnt; $i++) {
856 $mbx = find_mailbox_name($lsub_ary[$i]);
857
858 // only do the noselect test if !uw, is checked later. FIX ME see conf.pl setting
859 if ($imap_server_type != "uw") {
860 $noselect = check_is_noselect($lsub_ary[$i]);
861 $noinferiors = check_is_noinferiors($lsub_ary[$i]);
862 }
863 if (substr($mbx, -1) == $delimiter) {
864 $mbx = substr($mbx, 0, strlen($mbx) - 1);
865 }
866 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
867 }
868 // FIX ME this requires a config setting inside conf.pl instead of checking on server type
869 if ($imap_server_type == "uw") {
870 $aQuery = array();
871 $aTag = array();
872 // prepare an array with queries
873 foreach ($sorted_lsub_ary as $aMbx) {
874 $mbx = stripslashes($aMbx['mbx']);
875 sqimap_prepare_pipelined_query('LIST "" ' . sqimap_encode_mailbox_name($mbx), $tag, $aQuery, false);
876 $aTag[$tag] = $mbx;
877 }
878 $sorted_lsub_ary = array();
879 // execute all the queries at once
880 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
881 foreach($aTag as $tag => $mbx) {
882 if ($aServerResponse[$tag] == 'OK') {
883 $sResponse = implode('', $aResponse[$tag]);
884 $noselect = check_is_noselect($sResponse);
885 $noinferiors = check_is_noinferiors($sResponse);
886 $sorted_lsub_ary[] = array ('mbx' => $mbx, 'noselect' => $noselect, 'noinferiors' => $noinferiors);
887 }
888 }
889 $cnt = count($sorted_lsub_ary);
890 }
891 $sorted_lsub_ary = array_values($sorted_lsub_ary);
892 array_multisort($sorted_lsub_ary, SORT_ASC, SORT_REGULAR);
893 $boxesnew = sqimap_fill_mailbox_tree($sorted_lsub_ary,false,$imap_stream);
894 return $boxesnew;
895 }
896 }
897
898 function sqimap_fill_mailbox_tree($mbx_ary, $mbxs=false,$imap_stream) {
899 global $data_dir, $username, $list_special_folders_first,
900 $folder_prefix, $trash_folder, $sent_folder, $draft_folder,
901 $move_to_trash, $move_to_sent, $save_as_draft,
902 $delimiter, $imap_server_type;
903
904 $special_folders = array ('INBOX', $sent_folder, $draft_folder, $trash_folder);
905
906 /* create virtual root node */
907 $mailboxes= new mailboxes();
908 $mailboxes->is_root = true;
909 $trail_del = false;
910 $start = 0;
911
912
913 if (isset($folder_prefix) && ($folder_prefix != '')) {
914 $start = substr_count($folder_prefix,$delimiter);
915 if (strrpos($folder_prefix, $delimiter) == (strlen($folder_prefix)-1)) {
916 $trail_del = true;
917 $mailboxes->mailboxname_full = substr($folder_prefix,0, (strlen($folder_prefix)-1));
918 } else {
919 $mailboxes->mailboxname_full = $folder_prefix;
920 $start++;
921 }
922 $mailboxes->mailboxname_sub = $mailboxes->mailboxname_full;
923 } else {
924 $start = 0;
925 }
926
927 $cnt = count($mbx_ary);
928 for ($i=0; $i < $cnt; $i++) {
929 if ($mbx_ary[$i]['mbx'] !='' ) {
930 $mbx = new mailboxes();
931 $mailbox = $mbx_ary[$i]['mbx'];
932
933 /*
934 sent subfolders messes up using existing code as subfolders
935 were marked, but the parents were ordered somewhere else in
936 the list, despite having "special folders at top" option set.
937 Need a better method than this.
938 */
939 /*
940 if ($mailbox == 'INBOX') {
941 $mbx->is_special = true;
942 } elseif (stristr($trash_folder , $mailbox)) {
943 $mbx->is_special = true;
944 } elseif (stristr($sent_folder , $mailbox)) {
945 $mbx->is_special = true;
946 } elseif (stristr($draft_folder , $mailbox)) {
947 $mbx->is_special = true;
948 }
949
950 switch ($mailbox) {
951 case 'INBOX':
952 $mbx->is_inbox = true;
953 $mbx->is_special = true;
954 $mbx_ary[$i]['noselect'] = false;
955 break;
956 case $trash_folder:
957 $mbx->is_trash = true;
958 $mbx->is_special = true;
959 break;
960 case $sent_folder:
961 $mbx->is_sent = true;
962 $mbx->is_special = true;
963 break;
964 case $draft_folder:
965 $mbx->is_draft = true;
966 $mbx->is_special = true;
967 break;
968 }
969 */
970 $mbx->is_special |= ($mbx->is_inbox = (strtoupper($mailbox) == 'INBOX'));
971 $mbx->is_special |= ($mbx->is_trash = isTrashMailbox($mailbox));
972 $mbx->is_special |= ($mbx->is_sent = isSentMailbox($mailbox));
973 $mbx->is_special |= ($mbx->is_draft = isDraftMailbox($mailbox));
974 if (!$mbx->is_special)
975 $mbx->is_special = boolean_hook_function('special_mailbox', $mailbox, 1);
976
977 if (isset($mbx_ary[$i]['unseen'])) {
978 $mbx->unseen = $mbx_ary[$i]['unseen'];
979 }
980 if (isset($mbx_ary[$i]['nummessages'])) {
981 $mbx->total = $mbx_ary[$i]['nummessages'];
982 }
983
984 $mbx->is_noselect = $mbx_ary[$i]['noselect'];
985 $mbx->is_noinferiors = $mbx_ary[$i]['noinferiors'];
986
987 $r_del_pos = strrpos($mbx_ary[$i]['mbx'], $delimiter);
988 if ($r_del_pos) {
989 $mbx->mailboxname_sub = substr($mbx_ary[$i]['mbx'],$r_del_pos+1);
990 } else { /* mailbox is root folder */
991 $mbx->mailboxname_sub = $mbx_ary[$i]['mbx'];
992 }
993 $mbx->mailboxname_full = $mbx_ary[$i]['mbx'];
994
995 $mailboxes->addMbx($mbx, $delimiter, $start, $list_special_folders_first);
996 }
997 }
998 sqimap_utf7_decode_mbx_tree($mailboxes);
999 sqimap_get_status_mbx_tree($imap_stream,$mailboxes);
1000 return $mailboxes;
1001 }
1002
1003 function sqimap_utf7_decode_mbx_tree(&$mbx_tree) {
1004 if (strtoupper($mbx_tree->mailboxname_full) == 'INBOX')
1005 $mbx_tree->mailboxname_sub = _("INBOX");
1006 else
1007 $mbx_tree->mailboxname_sub = imap_utf7_decode_local($mbx_tree->mailboxname_sub);
1008 if ($mbx_tree->mbxs) {
1009 $iCnt = count($mbx_tree->mbxs);
1010 for ($i=0;$i<$iCnt;++$i) {
1011 $mbxs_tree->mbxs[$i] = sqimap_utf7_decode_mbx_tree($mbx_tree->mbxs[$i]);
1012 }
1013 }
1014 }
1015
1016
1017 function sqimap_tree_to_ref_array(&$mbx_tree,&$aMbxs) {
1018 if ($mbx_tree)
1019 $aMbxs[] =& $mbx_tree;
1020 if ($mbx_tree->mbxs) {
1021 $iCnt = count($mbx_tree->mbxs);
1022 for ($i=0;$i<$iCnt;++$i) {
1023 sqimap_tree_to_ref_array($mbx_tree->mbxs[$i],$aMbxs);
1024 }
1025 }
1026 }
1027
1028 function sqimap_get_status_mbx_tree($imap_stream,&$mbx_tree) {
1029 global $unseen_notify, $unseen_type, $trash_folder,$move_to_trash;
1030 $aMbxs = $aQuery = $aTag = array();
1031 sqimap_tree_to_ref_array($mbx_tree,$aMbxs);
1032 // remove the root node
1033 array_shift($aMbxs);
1034
1035 if($unseen_notify == 3) {
1036 $cnt = count($aMbxs);
1037 for($i=0;$i<$cnt;++$i) {
1038 $oMbx =& $aMbxs[$i];
1039 if (!$oMbx->is_noselect) {
1040 $mbx = $oMbx->mailboxname_full;
1041 if ($unseen_type == 2 ||
1042 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1043 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (MESSAGES UNSEEN)';
1044 } else {
1045 $query = 'STATUS ' . sqimap_encode_mailbox_name($mbx) . ' (UNSEEN)';
1046 }
1047 sqimap_prepare_pipelined_query($query,$tag,$aQuery,false);
1048 } else {
1049 $oMbx->unseen = $oMbx->total = false;
1050 $tag = false;
1051 }
1052 $oMbx->tag = $tag;
1053 $aMbxs[$i] =& $oMbx;
1054 }
1055 // execute all the queries at once
1056 $aResponse = sqimap_run_pipelined_command ($imap_stream, $aQuery, false, $aServerResponse, $aServerMessage);
1057 $cnt = count($aMbxs);
1058 for($i=0;$i<$cnt;++$i) {
1059 $oMbx =& $aMbxs[$i];
1060 $tag = $oMbx->tag;
1061 if ($tag && $aServerResponse[$tag] == 'OK') {
1062 $sResponse = implode('', $aResponse[$tag]);
1063 if (preg_match('/UNSEEN\s+([0-9]+)/i', $sResponse, $regs)) {
1064 $oMbx->unseen = $regs[1];
1065 }
1066 if (preg_match('/MESSAGES\s+([0-9]+)/i', $sResponse, $regs)) {
1067 $oMbx->total = $regs[1];
1068 }
1069 }
1070 unset($oMbx->tag);
1071 }
1072 } else if ($unseen_notify == 2) { // INBOX only
1073 $cnt = count($aMbxs);
1074 for($i=0;$i<$cnt;++$i) {
1075 $oMbx =& $aMbxs[$i];
1076 if (strtoupper($oMbx->mailboxname_full) == 'INBOX' ||
1077 ($move_to_trash && $oMbx->mailboxname_full == $trash_folder)) {
1078 if ($unseen_type == 2 ||
1079 ($oMbx->mailboxname_full == $trash_folder && $move_to_trash)) {
1080 $aStatus = sqimap_status_messages($imap_stream,$oMbx->mailboxname_full);
1081 $oMbx->unseen = $aStatus['UNSEEN'];
1082 $oMbx->total = $aStatus['MESSAGES'];
1083 } else {
1084 $oMbx->unseen = sqimap_unseen_messages($imap_stream,$oMbx->mailboxname_full);
1085 }
1086 $aMbxs[$i] =& $oMbx;
1087 if (!$move_to_trash && $trash_folder) {
1088 break;
1089 } else {
1090 // trash comes after INBOX
1091 if ($oMbx->mailboxname_full == $trash_folder) {
1092 break;
1093 }
1094 }
1095 }
1096 }
1097 }
1098 }
1099
1100 ?>