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