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