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