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